mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Purged stats, including level and experience
This commit is contained in:
parent
82885af9c4
commit
c0c45a1996
@ -4,7 +4,7 @@ ItemDef(
|
||||
kind: Consumable(
|
||||
kind: "PotionExp",
|
||||
effect: [
|
||||
Xp(250),
|
||||
/*Xp(250),*/
|
||||
]
|
||||
),
|
||||
quality: High,
|
||||
|
@ -4,7 +4,7 @@ ItemDef(
|
||||
kind: Consumable(
|
||||
kind: "Potion",
|
||||
effect: [
|
||||
Xp(250),
|
||||
/*Xp(250),*/
|
||||
]
|
||||
),
|
||||
quality: High,
|
||||
|
@ -4,7 +4,7 @@ ItemDef(
|
||||
kind: Consumable(
|
||||
kind: "Velorite",
|
||||
effect: [
|
||||
Xp(20),
|
||||
/*Xp(20),*/
|
||||
]
|
||||
),
|
||||
quality: High,
|
||||
|
@ -4,7 +4,7 @@ ItemDef(
|
||||
kind: Consumable(
|
||||
kind: "VeloriteFrag",
|
||||
effect: [
|
||||
Xp(10),
|
||||
/*Xp(10),*/
|
||||
]
|
||||
),
|
||||
quality: Moderate,
|
||||
|
@ -1732,7 +1732,6 @@ impl Client {
|
||||
player_info.character = match &player_info.character {
|
||||
Some(character) => Some(msg::CharacterInfo {
|
||||
name: character.name.to_string(),
|
||||
level: next_level,
|
||||
}),
|
||||
None => {
|
||||
warn!(
|
||||
|
@ -157,7 +157,6 @@ pub struct PlayerInfo {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CharacterInfo {
|
||||
pub name: String,
|
||||
pub level: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -20,6 +20,5 @@ pub struct Character {
|
||||
pub struct CharacterItem {
|
||||
pub character: Character,
|
||||
pub body: comp::Body,
|
||||
pub level: usize,
|
||||
pub inventory: Inventory,
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ pub enum ChatCommand {
|
||||
Dummy,
|
||||
Explosion,
|
||||
Faction,
|
||||
GiveExp,
|
||||
GiveItem,
|
||||
Goto,
|
||||
Group,
|
||||
@ -72,7 +71,6 @@ pub enum ChatCommand {
|
||||
Region,
|
||||
RemoveLights,
|
||||
Say,
|
||||
SetLevel,
|
||||
SetMotd,
|
||||
Spawn,
|
||||
Sudo,
|
||||
@ -99,7 +97,6 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
||||
ChatCommand::Dummy,
|
||||
ChatCommand::Explosion,
|
||||
ChatCommand::Faction,
|
||||
ChatCommand::GiveExp,
|
||||
ChatCommand::GiveItem,
|
||||
ChatCommand::Goto,
|
||||
ChatCommand::Group,
|
||||
@ -125,7 +122,6 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
||||
ChatCommand::Region,
|
||||
ChatCommand::RemoveLights,
|
||||
ChatCommand::Say,
|
||||
ChatCommand::SetLevel,
|
||||
ChatCommand::SetMotd,
|
||||
ChatCommand::Spawn,
|
||||
ChatCommand::Sudo,
|
||||
@ -244,11 +240,6 @@ impl ChatCommand {
|
||||
"Send messages to your faction",
|
||||
NoAdmin,
|
||||
),
|
||||
ChatCommand::GiveExp => cmd(
|
||||
vec![Integer("amount", 50, Required)],
|
||||
"Give experience to yourself",
|
||||
Admin,
|
||||
),
|
||||
ChatCommand::GiveItem => cmd(
|
||||
vec![
|
||||
Enum("item", ITEM_SPECS.clone(), Required),
|
||||
@ -378,11 +369,6 @@ impl ChatCommand {
|
||||
"Send messages to everyone within shouting distance",
|
||||
NoAdmin,
|
||||
),
|
||||
ChatCommand::SetLevel => cmd(
|
||||
vec![Integer("level", 10, Required)],
|
||||
"Set player Level",
|
||||
Admin,
|
||||
),
|
||||
ChatCommand::SetMotd => {
|
||||
cmd(vec![Message(Optional)], "Set the server description", Admin)
|
||||
},
|
||||
@ -452,7 +438,6 @@ impl ChatCommand {
|
||||
ChatCommand::Dummy => "dummy",
|
||||
ChatCommand::Explosion => "explosion",
|
||||
ChatCommand::Faction => "faction",
|
||||
ChatCommand::GiveExp => "give_exp",
|
||||
ChatCommand::GiveItem => "give_item",
|
||||
ChatCommand::Goto => "goto",
|
||||
ChatCommand::Group => "group",
|
||||
@ -478,7 +463,6 @@ impl ChatCommand {
|
||||
ChatCommand::Region => "region",
|
||||
ChatCommand::RemoveLights => "remove_lights",
|
||||
ChatCommand::Say => "say",
|
||||
ChatCommand::SetLevel => "set_level",
|
||||
ChatCommand::SetMotd => "set_motd",
|
||||
ChatCommand::Spawn => "spawn",
|
||||
ChatCommand::Sudo => "sudo",
|
||||
|
@ -68,5 +68,5 @@ pub use player::Player;
|
||||
pub use projectile::{Projectile, ProjectileConstructor};
|
||||
pub use shockwave::{Shockwave, ShockwaveHitEntities};
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||
pub use stats::{Exp, Level, Stats};
|
||||
pub use stats::Stats;
|
||||
pub use visual::{LightAnimation, LightEmitter};
|
||||
|
@ -1,23 +1,12 @@
|
||||
use crate::{
|
||||
comp,
|
||||
comp::{body::humanoid::Species, skills::SkillSet, Body},
|
||||
comp::{skills::SkillSet, Body},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct Exp {
|
||||
current: u32,
|
||||
maximum: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct Level {
|
||||
amount: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StatChangeError {
|
||||
Underflow,
|
||||
@ -34,84 +23,18 @@ impl fmt::Display for StatChangeError {
|
||||
}
|
||||
impl Error for StatChangeError {}
|
||||
|
||||
impl Exp {
|
||||
/// Used to determine how much exp is required to reach the next level. When
|
||||
/// a character levels up, the next level target is increased by this value
|
||||
const EXP_INCREASE_FACTOR: u32 = 25;
|
||||
|
||||
pub fn current(&self) -> u32 { self.current }
|
||||
|
||||
pub fn maximum(&self) -> u32 { self.maximum }
|
||||
|
||||
pub fn set_current(&mut self, current: u32) { self.current = current; }
|
||||
|
||||
pub fn change_by(&mut self, current: i64) {
|
||||
self.current = ((self.current as i64) + current) as u32;
|
||||
}
|
||||
|
||||
pub fn change_maximum_by(&mut self, maximum: i64) {
|
||||
self.maximum = ((self.maximum as i64) + maximum) as u32;
|
||||
}
|
||||
|
||||
pub fn update_maximum(&mut self, level: u32) {
|
||||
self.maximum = level
|
||||
.saturating_mul(Self::EXP_INCREASE_FACTOR)
|
||||
.saturating_add(Self::EXP_INCREASE_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
impl Level {
|
||||
pub fn set_level(&mut self, level: u32) { self.amount = level; }
|
||||
|
||||
pub fn level(&self) -> u32 { self.amount }
|
||||
|
||||
pub fn change_by(&mut self, level: u32) { self.amount += level; }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Stats {
|
||||
pub name: String,
|
||||
pub level: Level,
|
||||
pub exp: Exp,
|
||||
pub skill_set: SkillSet,
|
||||
pub endurance: u32,
|
||||
pub fitness: u32,
|
||||
pub willpower: u32,
|
||||
pub body_type: Body,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn new(name: String, body: Body) -> Self {
|
||||
let species = if let comp::Body::Humanoid(hbody) = body {
|
||||
Some(hbody.species)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// TODO: define base stats somewhere else (maybe method on Body?)
|
||||
let (endurance, fitness, willpower) = match species {
|
||||
Some(Species::Danari) => (0, 2, 3), // Small, flexible, intelligent, physically weak
|
||||
Some(Species::Dwarf) => (2, 2, 1), // physically strong, intelligent, slow reflexes
|
||||
Some(Species::Elf) => (1, 2, 2), // Intelligent, quick, physically weak
|
||||
Some(Species::Human) => (2, 1, 2), // Perfectly balanced
|
||||
Some(Species::Orc) => (3, 2, 0), /* Physically strong, non intelligent, medium */
|
||||
// reflexes
|
||||
Some(Species::Undead) => (1, 3, 1), /* Very good reflexes, equally intelligent and */
|
||||
// strong
|
||||
None => (0, 0, 0),
|
||||
};
|
||||
|
||||
Self {
|
||||
name,
|
||||
level: Level { amount: 1 },
|
||||
exp: Exp {
|
||||
current: 0,
|
||||
maximum: 50,
|
||||
},
|
||||
skill_set: SkillSet::default(),
|
||||
endurance,
|
||||
fitness,
|
||||
willpower,
|
||||
body_type: body,
|
||||
}
|
||||
}
|
||||
@ -121,23 +44,10 @@ impl Stats {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
name: "".to_owned(),
|
||||
level: Level { amount: 1 },
|
||||
exp: Exp {
|
||||
current: 0,
|
||||
maximum: 50,
|
||||
},
|
||||
skill_set: SkillSet::default(),
|
||||
endurance: 0,
|
||||
fitness: 0,
|
||||
willpower: 0,
|
||||
body_type: comp::Body::Humanoid(comp::body::humanoid::Body::random()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_level(mut self, level: u32) -> Self {
|
||||
self.level.set_level(level);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Stats {
|
||||
|
@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Effect {
|
||||
Health(comp::HealthChange),
|
||||
Xp(i64),
|
||||
Damage(combat::Damage),
|
||||
Buff(BuffEffect),
|
||||
}
|
||||
@ -22,7 +21,6 @@ impl Effect {
|
||||
pub fn info(&self) -> String {
|
||||
match self {
|
||||
Effect::Health(c) => format!("{:+} health", c.amount),
|
||||
Effect::Xp(n) => format!("{:+} exp", n),
|
||||
Effect::Damage(d) => format!("{:+}", d.value),
|
||||
Effect::Buff(e) => format!("{:?} buff", e),
|
||||
}
|
||||
@ -33,9 +31,6 @@ impl Effect {
|
||||
Effect::Health(change) => {
|
||||
change.amount = (change.amount as f32 * modifier) as i32;
|
||||
},
|
||||
Effect::Xp(amount) => {
|
||||
*amount = (*amount as f32 * modifier) as i64;
|
||||
},
|
||||
Effect::Damage(damage) => {
|
||||
damage.interpolate_damage(modifier, 0.0);
|
||||
},
|
||||
|
@ -86,7 +86,6 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
||||
ChatCommand::Dummy => handle_spawn_training_dummy,
|
||||
ChatCommand::Explosion => handle_explosion,
|
||||
ChatCommand::Faction => handle_faction,
|
||||
ChatCommand::GiveExp => handle_give_exp,
|
||||
ChatCommand::GiveItem => handle_give_item,
|
||||
ChatCommand::Goto => handle_goto,
|
||||
ChatCommand::Group => handle_group,
|
||||
@ -112,7 +111,6 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
||||
ChatCommand::Region => handle_region,
|
||||
ChatCommand::RemoveLights => handle_remove_lights,
|
||||
ChatCommand::Say => handle_say,
|
||||
ChatCommand::SetLevel => handle_set_level,
|
||||
ChatCommand::SetMotd => handle_set_motd,
|
||||
ChatCommand::Spawn => handle_spawn,
|
||||
ChatCommand::Sudo => handle_sudo,
|
||||
@ -927,10 +925,7 @@ fn handle_spawn_training_dummy(
|
||||
|
||||
let body = comp::Body::Object(comp::object::Body::TrainingDummy);
|
||||
|
||||
let mut stats = comp::Stats::new("Training Dummy".to_string(), body);
|
||||
|
||||
// Level 0 will prevent exp gain from kill
|
||||
stats.level.set_level(0);
|
||||
let stats = comp::Stats::new("Training Dummy".to_string(), body);
|
||||
|
||||
let health = comp::Health::new(body, 0);
|
||||
|
||||
@ -1019,11 +1014,10 @@ fn handle_players(
|
||||
format!("{} online players:", entity_tuples.join().count()),
|
||||
|s, (_, player, stat)| {
|
||||
format!(
|
||||
"{}\n[{}]{} Lvl {}",
|
||||
"{}\n[{}]{}",
|
||||
s,
|
||||
player.alias,
|
||||
stat.name,
|
||||
stat.level.level()
|
||||
)
|
||||
},
|
||||
),
|
||||
@ -2000,130 +1994,6 @@ spawn_rate {:?} "#,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_target(
|
||||
ecs: &specs::World,
|
||||
opt_alias: Option<String>,
|
||||
fallback: EcsEntity,
|
||||
) -> Result<EcsEntity, ServerGeneral> {
|
||||
if let Some(alias) = opt_alias {
|
||||
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
|
||||
.join()
|
||||
.find(|(_, player)| player.alias == alias)
|
||||
.map(|(entity, _)| entity)
|
||||
.ok_or_else(|| {
|
||||
ServerGeneral::server_msg(
|
||||
ChatType::CommandError,
|
||||
format!("Player '{}' not found!", alias),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Ok(fallback)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_give_exp(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: String,
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
let (a_exp, a_alias) = scan_fmt_some!(&args, &action.arg_fmt(), i64, String);
|
||||
|
||||
if let Some(exp) = a_exp {
|
||||
let ecs = server.state.ecs_mut();
|
||||
let target = find_target(&ecs, a_alias, target);
|
||||
|
||||
let mut error_msg = None;
|
||||
|
||||
match target {
|
||||
Ok(player) => {
|
||||
if let Some(mut stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
|
||||
stats.exp.change_by(exp);
|
||||
} else {
|
||||
error_msg = Some(ServerGeneral::server_msg(
|
||||
ChatType::CommandError,
|
||||
"Player has no stats!",
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error_msg = Some(e);
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(msg) = error_msg {
|
||||
server.notify_client(client, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_set_level(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: String,
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
let (a_lvl, a_alias) = scan_fmt_some!(&args, &action.arg_fmt(), u32, String);
|
||||
|
||||
if let Some(lvl) = a_lvl {
|
||||
let target = find_target(&server.state.ecs(), a_alias, target);
|
||||
|
||||
let mut error_msg = None;
|
||||
|
||||
match target {
|
||||
Ok(player) => {
|
||||
let uid = *server
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<Uid>()
|
||||
.get(player)
|
||||
.expect("Failed to get uid for player");
|
||||
server.state.notify_players(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::LevelChange(uid, lvl),
|
||||
));
|
||||
|
||||
let body_type: Option<comp::Body>;
|
||||
|
||||
if let Some(mut stats) = server
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage::<comp::Stats>()
|
||||
.get_mut(player)
|
||||
{
|
||||
stats.level.set_level(lvl);
|
||||
body_type = Some(stats.body_type);
|
||||
} else {
|
||||
error_msg = Some(ServerGeneral::server_msg(
|
||||
ChatType::CommandError,
|
||||
"Player has no stats!",
|
||||
));
|
||||
body_type = None;
|
||||
}
|
||||
|
||||
if let Some(mut health) = server
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(player)
|
||||
{
|
||||
let health = &mut *health;
|
||||
health.update_max_hp(body_type, lvl);
|
||||
health.set_to(health.maximum(), comp::HealthSource::LevelUp);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error_msg = Some(e);
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(msg) = error_msg {
|
||||
server.notify_client(client, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_debug(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
|
@ -189,8 +189,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
const MAX_EXP_DIST: f32 = 150.0;
|
||||
// Attacker gets same as exp of everyone else
|
||||
const ATTACKER_EXP_WEIGHT: f32 = 1.0;
|
||||
let mut exp_reward = entity_stats.body_type.base_exp() as f32
|
||||
* (1.0 + entity_stats.level.level() as f32 * 0.1);
|
||||
let mut exp_reward = entity_stats.body_type.base_exp() as f32;
|
||||
|
||||
// Distribute EXP to group
|
||||
let positions = state.ecs().read_storage::<Pos>();
|
||||
|
@ -1,4 +1 @@
|
||||
-- Drops skill and skill_group tables
|
||||
|
||||
DROP TABLE skill;
|
||||
DROP TABLE skill_group;
|
||||
-- What's a down migration?
|
@ -1,5 +1,37 @@
|
||||
-- Creates skill and skill_group tables. Adds General skill tree for players that are already created
|
||||
|
||||
-- Creates new character table
|
||||
CREATE TABLE "_character_new" (
|
||||
"character_id" INT NOT NULL,
|
||||
"player_uuid" TEXT NOT NULL,
|
||||
"alias" TEXT NOT NULL,
|
||||
"waypoint" TEXT NOT NULL,
|
||||
PRIMARY KEY("character_id"),
|
||||
FOREIGN KEY("character_id") REFERENCES "body"("body_id"),
|
||||
FOREIGN KEY("character_id") REFERENCES "item"("item_id")
|
||||
);
|
||||
|
||||
-- Inserts information into new character table
|
||||
INSERT INTO _character_new
|
||||
SELECT c.character_id,
|
||||
c.player_uuid,
|
||||
c.alias,
|
||||
s.waypoint
|
||||
FROM character c
|
||||
JOIN stats s ON (s.stats_id = c.character_id);
|
||||
|
||||
-- Drops old character rable
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
DROP TABLE character;
|
||||
ALTER TABLE _character_new RENAME TO character;
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- Drops deprecated stats table
|
||||
DROP TABLE stats;
|
||||
|
||||
-- Creates new table for skill groups
|
||||
CREATE TABLE skill_group (
|
||||
character_id INTEGER NOT NULL,
|
||||
skill_group_type TEXT NOT NULL,
|
||||
@ -9,6 +41,7 @@ CREATE TABLE skill_group (
|
||||
PRIMARY KEY(character_id,skill_group_type)
|
||||
);
|
||||
|
||||
-- Creates new table for skills
|
||||
CREATE TABLE skill (
|
||||
character_id INTEGER NOT NULL,
|
||||
skill TEXT NOT NULL,
|
||||
@ -17,6 +50,7 @@ CREATE TABLE skill (
|
||||
PRIMARY KEY(character_id,skill)
|
||||
);
|
||||
|
||||
-- Inserts starting skill group for everyone
|
||||
INSERT INTO skill_group
|
||||
SELECT c.character_id, '"General"', 0, 0
|
||||
FROM character c
|
@ -16,8 +16,8 @@ use crate::{
|
||||
convert_character_from_database, convert_inventory_from_database_items,
|
||||
convert_items_to_database_items, convert_loadout_from_database_items,
|
||||
convert_skill_groups_to_database, convert_skills_to_database,
|
||||
convert_stats_from_database, convert_stats_to_database,
|
||||
convert_waypoint_from_database_json,
|
||||
convert_stats_from_database, convert_waypoint_from_database_json,
|
||||
convert_waypoint_to_database_json,
|
||||
},
|
||||
character_loader::{CharacterCreationResult, CharacterDataResult, CharacterListResult},
|
||||
error::Error::DatabaseError,
|
||||
@ -75,33 +75,30 @@ pub fn load_character_data(
|
||||
.filter(parent_container_item_id.eq(character_containers.loadout_container_id))
|
||||
.load::<Item>(&*connection)?;
|
||||
|
||||
let (character_data, stats_data) = character
|
||||
let character_data = character
|
||||
.filter(
|
||||
schema::character::dsl::character_id
|
||||
.eq(char_id)
|
||||
.and(player_uuid.eq(requesting_player_uuid)),
|
||||
)
|
||||
.inner_join(schema::stats::dsl::stats)
|
||||
.first::<(Character, Stats)>(&*connection)?;
|
||||
.first::<Character>(&*connection)?;
|
||||
|
||||
let char_body = body
|
||||
.filter(schema::body::dsl::body_id.eq(char_id))
|
||||
.first::<Body>(&*connection)?;
|
||||
|
||||
let char_waypoint =
|
||||
stats_data
|
||||
.waypoint
|
||||
.as_ref()
|
||||
.and_then(|x| match convert_waypoint_from_database_json(&x) {
|
||||
Ok(w) => Some(w),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Error reading waypoint from database for character ID {}, error: {}",
|
||||
char_id, e
|
||||
);
|
||||
None
|
||||
},
|
||||
});
|
||||
let char_waypoint = character_data.waypoint.as_ref().and_then(|x| {
|
||||
match convert_waypoint_from_database_json(&x) {
|
||||
Ok(w) => Some(w),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Error reading waypoint from database for character ID {}, error: {}",
|
||||
char_id, e
|
||||
);
|
||||
None
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
let skill_data = schema::skill::dsl::skill
|
||||
.filter(schema::skill::dsl::character_id.eq(char_id))
|
||||
@ -113,12 +110,7 @@ pub fn load_character_data(
|
||||
|
||||
Ok((
|
||||
convert_body_from_database(&char_body)?,
|
||||
convert_stats_from_database(
|
||||
&stats_data,
|
||||
character_data.alias,
|
||||
&skill_data,
|
||||
&skill_group_data,
|
||||
),
|
||||
convert_stats_from_database(character_data.alias, &skill_data, &skill_group_data),
|
||||
convert_inventory_from_database_items(&inventory_items, &loadout_items)?,
|
||||
char_waypoint,
|
||||
))
|
||||
@ -135,17 +127,16 @@ pub fn load_character_list(
|
||||
player_uuid_: &str,
|
||||
connection: VelorenTransaction,
|
||||
) -> CharacterListResult {
|
||||
use schema::{body::dsl::*, character::dsl::*, item::dsl::*, stats::dsl::*};
|
||||
use schema::{body::dsl::*, character::dsl::*, item::dsl::*};
|
||||
|
||||
let result = character
|
||||
.filter(player_uuid.eq(player_uuid_))
|
||||
.inner_join(stats)
|
||||
.order(schema::character::dsl::character_id.desc())
|
||||
.load::<(Character, Stats)>(&*connection)?;
|
||||
.load::<Character>(&*connection)?;
|
||||
|
||||
result
|
||||
.iter()
|
||||
.map(|(character_data, char_stats)| {
|
||||
.map(|character_data| {
|
||||
let char = convert_character_from_database(character_data);
|
||||
|
||||
let db_body = body
|
||||
@ -171,7 +162,6 @@ pub fn load_character_list(
|
||||
Ok(CharacterItem {
|
||||
character: char,
|
||||
body: char_body,
|
||||
level: char_stats.level as usize,
|
||||
inventory: Inventory::new_with_loadout(loadout),
|
||||
})
|
||||
})
|
||||
@ -234,20 +224,7 @@ pub fn create_character(
|
||||
)));
|
||||
}
|
||||
|
||||
let skill_set = stats.skill_set.clone();
|
||||
|
||||
// Insert stats record
|
||||
let db_stats = convert_stats_to_database(character_id, &stats, &waypoint)?;
|
||||
let stats_count = diesel::insert_into(schema::stats::table)
|
||||
.values(&db_stats)
|
||||
.execute(&*connection)?;
|
||||
|
||||
if stats_count != 1 {
|
||||
return Err(Error::OtherError(format!(
|
||||
"Error inserting into stats table for char_id {}",
|
||||
character_id
|
||||
)));
|
||||
}
|
||||
let skill_set = stats.skill_set;
|
||||
|
||||
// Insert body record
|
||||
let new_body = Body {
|
||||
@ -272,6 +249,7 @@ pub fn create_character(
|
||||
character_id,
|
||||
player_uuid: uuid,
|
||||
alias: &character_alias,
|
||||
waypoint: convert_waypoint_to_database_json(waypoint),
|
||||
};
|
||||
let character_count = diesel::insert_into(character::table)
|
||||
.values(&new_character)
|
||||
@ -335,9 +313,7 @@ pub fn delete_character(
|
||||
char_id: CharacterId,
|
||||
connection: VelorenTransaction,
|
||||
) -> CharacterListResult {
|
||||
use schema::{
|
||||
body::dsl::*, character::dsl::*, skill::dsl::*, skill_group::dsl::*, stats::dsl::*,
|
||||
};
|
||||
use schema::{body::dsl::*, character::dsl::*, skill::dsl::*, skill_group::dsl::*};
|
||||
|
||||
// Load the character to delete - ensures that the requesting player
|
||||
// owns the character
|
||||
@ -371,16 +347,6 @@ pub fn delete_character(
|
||||
)));
|
||||
}
|
||||
|
||||
// Delete stats
|
||||
let stats_count = diesel::delete(stats.filter(schema::stats::dsl::stats_id.eq(char_id)))
|
||||
.execute(&*connection)?;
|
||||
|
||||
if stats_count != 1 {
|
||||
return Err(Error::OtherError(format!(
|
||||
"Error deleting from stats table for char_id {}",
|
||||
char_id
|
||||
)));
|
||||
}
|
||||
// Delete body
|
||||
let body_count = diesel::delete(body.filter(schema::body::dsl::body_id.eq(char_id)))
|
||||
.execute(&*connection)?;
|
||||
@ -568,7 +534,7 @@ pub fn update(
|
||||
char_waypoint: Option<comp::Waypoint>,
|
||||
connection: VelorenTransaction,
|
||||
) -> Result<Vec<Arc<common::comp::item::ItemId>>, Error> {
|
||||
use super::schema::{item::dsl::*, skill_group::dsl::*};
|
||||
use super::schema::{character::dsl::*, item::dsl::*, skill_group::dsl::*};
|
||||
|
||||
let pseudo_containers = get_pseudo_containers(connection, char_id)?;
|
||||
|
||||
@ -630,7 +596,7 @@ pub fn update(
|
||||
}
|
||||
}
|
||||
|
||||
let char_skill_set = char_stats.skill_set.clone();
|
||||
let char_skill_set = char_stats.skill_set;
|
||||
|
||||
let db_skill_groups = convert_skill_groups_to_database(char_id, char_skill_set.skill_groups);
|
||||
|
||||
@ -644,15 +610,15 @@ pub fn update(
|
||||
.values(&db_skills)
|
||||
.execute(&*connection)?;
|
||||
|
||||
let db_stats = convert_stats_to_database(char_id, &char_stats, &char_waypoint)?;
|
||||
let stats_count =
|
||||
diesel::update(schema::stats::dsl::stats.filter(schema::stats::dsl::stats_id.eq(char_id)))
|
||||
.set(db_stats)
|
||||
let db_waypoint = convert_waypoint_to_database_json(char_waypoint);
|
||||
let waypoint_count =
|
||||
diesel::update(character.filter(schema::character::dsl::character_id.eq(char_id)))
|
||||
.set(waypoint.eq(db_waypoint))
|
||||
.execute(&*connection)?;
|
||||
|
||||
if stats_count != 1 {
|
||||
if waypoint_count != 1 {
|
||||
return Err(Error::OtherError(format!(
|
||||
"Error updating stats table for char_id {}",
|
||||
"Error updating character table for char_id {}",
|
||||
char_id
|
||||
)));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::persistence::{
|
||||
character::EntityId,
|
||||
models::{Body, Character, Item, Skill, SkillGroup, Stats},
|
||||
models::{Body, Character, Item, Skill, SkillGroup},
|
||||
};
|
||||
|
||||
use crate::persistence::{
|
||||
@ -164,12 +164,22 @@ pub fn convert_body_to_database_json(body: &CompBody) -> Result<String, Error> {
|
||||
serde_json::to_string(&json_model).map_err(Error::SerializationError)
|
||||
}
|
||||
|
||||
fn convert_waypoint_to_database_json(waypoint: &Waypoint) -> Result<String, Error> {
|
||||
let charpos = CharacterPosition {
|
||||
waypoint: waypoint.get_pos(),
|
||||
};
|
||||
serde_json::to_string(&charpos)
|
||||
.map_err(|err| Error::ConversionError(format!("Error encoding waypoint: {:?}", err)))
|
||||
pub fn convert_waypoint_to_database_json(waypoint: Option<Waypoint>) -> Option<String> {
|
||||
match waypoint {
|
||||
Some(w) => {
|
||||
let charpos = CharacterPosition {
|
||||
waypoint: w.get_pos(),
|
||||
};
|
||||
Some(
|
||||
serde_json::to_string(&charpos)
|
||||
.map_err(|err| {
|
||||
Error::ConversionError(format!("Error encoding waypoint: {:?}", err))
|
||||
})
|
||||
.ok()?,
|
||||
)
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_waypoint_from_database_json(position: &str) -> Result<Waypoint, Error> {
|
||||
@ -183,27 +193,6 @@ pub fn convert_waypoint_from_database_json(position: &str) -> Result<Waypoint, E
|
||||
Ok(Waypoint::new(character_position.waypoint, Time(0.0)))
|
||||
}
|
||||
|
||||
pub fn convert_stats_to_database(
|
||||
character_id: CharacterId,
|
||||
stats: &common::comp::Stats,
|
||||
waypoint: &Option<common::comp::Waypoint>,
|
||||
) -> Result<Stats, Error> {
|
||||
let waypoint = match waypoint {
|
||||
Some(w) => Some(convert_waypoint_to_database_json(&w)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Stats {
|
||||
stats_id: character_id,
|
||||
level: stats.level.level() as i32,
|
||||
exp: stats.exp.current() as i32,
|
||||
endurance: stats.endurance as i32,
|
||||
fitness: stats.fitness as i32,
|
||||
willpower: stats.willpower as i32,
|
||||
waypoint,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn convert_inventory_from_database_items(
|
||||
inventory_items: &[Item],
|
||||
loadout_items: &[Item],
|
||||
@ -345,24 +334,17 @@ pub fn convert_character_from_database(character: &Character) -> common::charact
|
||||
}
|
||||
|
||||
pub fn convert_stats_from_database(
|
||||
stats: &Stats,
|
||||
alias: String,
|
||||
skills: &[Skill],
|
||||
skill_groups: &[SkillGroup],
|
||||
) -> common::comp::Stats {
|
||||
let mut new_stats = common::comp::Stats::empty();
|
||||
new_stats.name = alias;
|
||||
new_stats.level.set_level(stats.level as u32);
|
||||
new_stats.exp.update_maximum(stats.level as u32);
|
||||
new_stats.exp.set_current(stats.exp as u32);
|
||||
/*new_stats.update_max_hp(new_stats.body_type);
|
||||
new_stats.health.set_to(
|
||||
new_stats.health.maximum(),
|
||||
common::comp::HealthSource::Revive,
|
||||
);*/
|
||||
new_stats.endurance = stats.endurance as u32;
|
||||
new_stats.fitness = stats.fitness as u32;
|
||||
new_stats.willpower = stats.willpower as u32;
|
||||
new_stats.skill_set = skills::SkillSet {
|
||||
skill_groups: convert_skill_groups_from_database(skill_groups),
|
||||
skills: convert_skills_from_database(skills),
|
||||
|
@ -1,6 +1,6 @@
|
||||
extern crate serde_json;
|
||||
|
||||
use super::schema::{body, character, entity, item, skill, skill_group, stats};
|
||||
use super::schema::{body, character, entity, item, skill, skill_group};
|
||||
|
||||
#[derive(Debug, Insertable, PartialEq)]
|
||||
#[table_name = "entity"]
|
||||
@ -14,6 +14,7 @@ pub struct NewCharacter<'a> {
|
||||
pub character_id: i64,
|
||||
pub player_uuid: &'a str,
|
||||
pub alias: &'a str,
|
||||
pub waypoint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Debug)]
|
||||
@ -23,6 +24,7 @@ pub struct Character {
|
||||
pub character_id: i64,
|
||||
pub player_uuid: String,
|
||||
pub alias: String,
|
||||
pub waypoint: Option<String>,
|
||||
}
|
||||
|
||||
#[primary_key(item_id)]
|
||||
@ -36,19 +38,6 @@ pub struct Item {
|
||||
pub position: String,
|
||||
}
|
||||
|
||||
#[derive(Associations, AsChangeset, Identifiable, Queryable, Debug, Insertable)]
|
||||
#[primary_key(stats_id)]
|
||||
#[table_name = "stats"]
|
||||
pub struct Stats {
|
||||
pub stats_id: i64,
|
||||
pub level: i32,
|
||||
pub exp: i32,
|
||||
pub endurance: i32,
|
||||
pub fitness: i32,
|
||||
pub willpower: i32,
|
||||
pub waypoint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Associations, Identifiable, Insertable, Queryable, Debug)]
|
||||
#[primary_key(body_id)]
|
||||
#[table_name = "body"]
|
||||
|
@ -11,6 +11,7 @@ table! {
|
||||
character_id -> BigInt,
|
||||
player_uuid -> Text,
|
||||
alias -> Text,
|
||||
waypoint -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,18 +31,6 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
stats (stats_id) {
|
||||
stats_id -> BigInt,
|
||||
level -> Integer,
|
||||
exp -> Integer,
|
||||
endurance -> Integer,
|
||||
fitness -> Integer,
|
||||
willpower -> Integer,
|
||||
waypoint -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
skill (character_id, skill_type) {
|
||||
character_id -> BigInt,
|
||||
@ -61,6 +50,5 @@ table! {
|
||||
}
|
||||
|
||||
joinable!(character -> body (character_id));
|
||||
joinable!(character -> stats (character_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(body, character, entity, item, stats,);
|
||||
allow_tables_to_appear_in_same_query!(body, character, entity, item);
|
||||
|
@ -97,7 +97,7 @@ impl<'a> System<'a> for Sys {
|
||||
let body = entity.get_body();
|
||||
server_emitter.emit(ServerEvent::CreateNpc {
|
||||
pos: comp::Pos(spawn_pos),
|
||||
stats: comp::Stats::new(entity.get_name(), body).with_level(entity.get_level()),
|
||||
stats: comp::Stats::new(entity.get_name(), body),
|
||||
health: comp::Health::new(body, 10),
|
||||
loadout: match body {
|
||||
comp::Body::Humanoid(_) => entity.get_loadout(),
|
||||
|
@ -84,12 +84,6 @@ impl StateExt for State {
|
||||
.get_mut(entity)
|
||||
.map(|mut health| health.change_by(change));
|
||||
},
|
||||
Effect::Xp(xp) => {
|
||||
self.ecs()
|
||||
.write_storage::<comp::Stats>()
|
||||
.get_mut(entity)
|
||||
.map(|mut stats| stats.exp.change_by(xp));
|
||||
},
|
||||
Effect::Damage(damage) => {
|
||||
let inventories = self.ecs().read_storage::<Inventory>();
|
||||
let change = damage.modify_damage(inventories.get(entity), source);
|
||||
@ -266,7 +260,6 @@ impl StateExt for State {
|
||||
self.notify_players(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
|
||||
name: String::from(&stats.name),
|
||||
level: stats.level.level(),
|
||||
}),
|
||||
));
|
||||
|
||||
@ -278,7 +271,7 @@ impl StateExt for State {
|
||||
self.write_component(entity, body);
|
||||
self.write_component(
|
||||
entity,
|
||||
comp::Health::new(stats.body_type, stats.level.level()),
|
||||
comp::Health::new(stats.body_type, 0), //Placeholder 0
|
||||
);
|
||||
self.write_component(entity, stats);
|
||||
self.write_component(entity, inventory);
|
||||
|
@ -122,7 +122,6 @@ impl<'a> System<'a> for Sys {
|
||||
player_alias: player.alias.clone(),
|
||||
character: stats.map(|stats| CharacterInfo {
|
||||
name: stats.name.clone(),
|
||||
level: stats.level.level(),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
@ -13,7 +13,6 @@ use common::{
|
||||
};
|
||||
use common_net::msg::ServerGeneral;
|
||||
use common_sys::state::TerrainChanges;
|
||||
use rand::Rng;
|
||||
use specs::{Join, Read, ReadStorage, System, Write, WriteExpect};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
@ -131,14 +130,6 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let mut scale = entity.scale;
|
||||
|
||||
// TODO: Remove this and implement scaling or level depending on stuff like
|
||||
// species instead
|
||||
stats.level.set_level(
|
||||
entity.level.unwrap_or_else(|| {
|
||||
(rand::thread_rng().gen_range(1, 9) as f32 * scale) as u32
|
||||
}),
|
||||
);
|
||||
|
||||
// Replace stuff if it's a boss
|
||||
if entity.is_giant {
|
||||
if rand::random::<f32>() < 0.65 && entity.alignment != Alignment::Enemy {
|
||||
@ -154,7 +145,6 @@ impl<'a> System<'a> for Sys {
|
||||
body,
|
||||
);
|
||||
}
|
||||
stats.level.set_level(rand::thread_rng().gen_range(25, 30));
|
||||
scale = 2.0 + rand::random::<f32>();
|
||||
}
|
||||
|
||||
@ -162,7 +152,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let loadout = LoadoutBuilder::build_loadout(body, main_tool, config).build();
|
||||
|
||||
let health = comp::Health::new(stats.body_type, stats.level.level());
|
||||
let health = comp::Health::new(stats.body_type, 0); // Placeholder 0
|
||||
|
||||
let can_speak = match body {
|
||||
comp::Body::Humanoid(_) => alignment == comp::Alignment::Npc,
|
||||
|
@ -8,25 +8,9 @@ use specs::{Entity, World, WorldExt};
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MyEntity(pub Entity);
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ExpFloater {
|
||||
pub exp_change: i32, // Maybe you can loose exp :p
|
||||
pub timer: f32,
|
||||
// Used to randomly offset position
|
||||
pub rand: (f32, f32),
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MyExpFloaterList {
|
||||
pub floaters: Vec<ExpFloater>,
|
||||
pub last_exp: u32,
|
||||
pub last_level: u32,
|
||||
pub last_exp_max: u32,
|
||||
}
|
||||
|
||||
pub fn init(world: &mut World) {
|
||||
world.register::<comp::HpFloaterList>();
|
||||
world.register::<comp::Interpolated>();
|
||||
world.insert(MyExpFloaterList::default());
|
||||
world.insert(Vec::<Outcome>::new());
|
||||
|
||||
// Voxygen event buses
|
||||
|
@ -1,18 +1,17 @@
|
||||
use crate::ecs::{
|
||||
comp::{HpFloater, HpFloaterList},
|
||||
ExpFloater, MyEntity, MyExpFloaterList,
|
||||
MyEntity,
|
||||
};
|
||||
use common::{
|
||||
comp::{Health, HealthSource, Pos, Stats},
|
||||
comp::{Health, HealthSource, Pos},
|
||||
resources::DeltaTime,
|
||||
uid::Uid,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
|
||||
|
||||
// How long floaters last (in seconds)
|
||||
pub const HP_SHOWTIME: f32 = 3.0;
|
||||
pub const MY_HP_SHOWTIME: f32 = 2.5;
|
||||
pub const MY_EXP_SHOWTIME: f32 = 4.0;
|
||||
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
@ -21,10 +20,8 @@ impl<'a> System<'a> for Sys {
|
||||
Entities<'a>,
|
||||
ReadExpect<'a, MyEntity>,
|
||||
Read<'a, DeltaTime>,
|
||||
Write<'a, MyExpFloaterList>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
WriteStorage<'a, HpFloaterList>,
|
||||
);
|
||||
@ -32,17 +29,7 @@ impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
my_entity,
|
||||
dt,
|
||||
mut my_exp_floater_list,
|
||||
uids,
|
||||
pos,
|
||||
stats,
|
||||
healths,
|
||||
mut hp_floater_lists,
|
||||
): Self::SystemData,
|
||||
(entities, my_entity, dt, uids, pos, healths, mut hp_floater_lists): Self::SystemData,
|
||||
) {
|
||||
// Add hp floater lists to all entities with health and a position
|
||||
// Note: necessary in order to know last_hp
|
||||
@ -149,57 +136,5 @@ impl<'a> System<'a> for Sys {
|
||||
floaters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Update MyExpFloaterList
|
||||
if let Some(stats) = stats.get(my_entity.0) {
|
||||
let mut fl = my_exp_floater_list;
|
||||
// Add a floater if exp changed
|
||||
// TODO: can't handle if you level up more than once (maybe store total exp in
|
||||
// stats)
|
||||
let exp_change = if stats.level.level() != fl.last_level {
|
||||
if stats.level.level() > fl.last_level {
|
||||
stats.exp.current() as i32 + fl.last_exp_max as i32 - fl.last_exp as i32
|
||||
} else {
|
||||
// Level down
|
||||
stats.exp.current() as i32 - stats.exp.maximum() as i32 - fl.last_exp as i32
|
||||
}
|
||||
} else {
|
||||
stats.exp.current() as i32 - fl.last_exp as i32
|
||||
};
|
||||
|
||||
if exp_change != 0 {
|
||||
fl.floaters.push(ExpFloater {
|
||||
timer: 0.0,
|
||||
exp_change,
|
||||
rand: (rand::random(), rand::random()),
|
||||
});
|
||||
}
|
||||
|
||||
// Increment timers
|
||||
for mut floater in &mut fl.floaters {
|
||||
floater.timer += dt.0;
|
||||
}
|
||||
|
||||
// Clear if the newest is past show time
|
||||
if fl
|
||||
.floaters
|
||||
.last()
|
||||
.map_or(false, |f| f.timer > MY_EXP_SHOWTIME)
|
||||
{
|
||||
fl.floaters.clear();
|
||||
}
|
||||
|
||||
// Update stored values
|
||||
fl.last_exp = stats.exp.current();
|
||||
fl.last_exp_max = stats.exp.maximum();
|
||||
fl.last_level = stats.level.level();
|
||||
} else {
|
||||
// Clear if stats component doesn't exist
|
||||
my_exp_floater_list.floaters.clear();
|
||||
// Clear stored values
|
||||
my_exp_floater_list.last_exp = 0;
|
||||
my_exp_floater_list.last_exp_max = 0;
|
||||
my_exp_floater_list.last_level = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,6 @@ use std::{
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0);
|
||||
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||
const TEXT_GRAY_COLOR: Color = Color::Rgba(0.5, 0.5, 0.5, 1.0);
|
||||
const TEXT_DULL_RED_COLOR: Color = Color::Rgba(0.56, 0.2, 0.2, 1.0);
|
||||
@ -825,9 +824,6 @@ impl Hud {
|
||||
let items = ecs.read_storage::<comp::Item>();
|
||||
let entities = ecs.entities();
|
||||
let me = client.entity();
|
||||
let own_level = stats
|
||||
.get(client.entity())
|
||||
.map_or(0, |stats| stats.level.level());
|
||||
//self.input = client.read_storage::<comp::ControllerInputs>();
|
||||
if let Some(health) = healths.get(me) {
|
||||
// Hurt Frame
|
||||
@ -1061,111 +1057,6 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
}
|
||||
// EXP Numbers
|
||||
if let (Some(floaters), Some(stats)) = (
|
||||
Some(&*ecs.read_resource::<crate::ecs::MyExpFloaterList>())
|
||||
.map(|l| &l.floaters)
|
||||
.filter(|f| !f.is_empty()),
|
||||
stats.get(me),
|
||||
) {
|
||||
// TODO replace with setting
|
||||
let batched_sct = false;
|
||||
if batched_sct {
|
||||
let number_speed = 50.0; // Number Speed for Cumulated EXP
|
||||
let player_sct_bg_id = player_sct_bg_id_walker.next(
|
||||
&mut self.ids.player_sct_bgs,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
let player_sct_id = player_sct_id_walker.next(
|
||||
&mut self.ids.player_scts,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
// Sum xp change
|
||||
let exp_change = floaters.iter().fold(0, |acc, f| f.exp_change + acc);
|
||||
// Can't fail since we filtered out empty lists above
|
||||
let (timer, rand) = floaters
|
||||
.last()
|
||||
.map(|f| (f.timer, f.rand))
|
||||
.expect("Impossible");
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size_xp = 30
|
||||
+ ((exp_change.abs() as f32 / stats.exp.maximum() as f32).min(1.0)
|
||||
* 50.0) as u32
|
||||
+ if timer < 0.1 {
|
||||
FLASH_MAX * (((1.0 - timer / 0.1) * 10.0) as u32)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let y = timer as f64 * number_speed; // Timer sets the widget offset
|
||||
let fade = ((4.0 - timer as f32) * 0.25) + 0.2; // Timer sets text transparency
|
||||
|
||||
Text::new(&format!("{} Exp", exp_change))
|
||||
.font_size(font_size_xp)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
|
||||
.x_y(
|
||||
ui_widgets.win_w * (0.5 * rand.0 as f64 - 0.25),
|
||||
ui_widgets.win_h * (0.15 * rand.1 as f64) + y - 3.0,
|
||||
)
|
||||
.set(player_sct_bg_id, ui_widgets);
|
||||
Text::new(&format!("{} Exp", exp_change))
|
||||
.font_size(font_size_xp)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(0.59, 0.41, 0.67, fade))
|
||||
.x_y(
|
||||
ui_widgets.win_w * (0.5 * rand.0 as f64 - 0.25),
|
||||
ui_widgets.win_h * (0.15 * rand.1 as f64) + y,
|
||||
)
|
||||
.set(player_sct_id, ui_widgets);
|
||||
} else {
|
||||
for floater in floaters {
|
||||
let number_speed = 50.0; // Number Speed for Single EXP
|
||||
let player_sct_bg_id = player_sct_bg_id_walker.next(
|
||||
&mut self.ids.player_sct_bgs,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
let player_sct_id = player_sct_id_walker.next(
|
||||
&mut self.ids.player_scts,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size_xp = 30
|
||||
+ ((floater.exp_change.abs() as f32 / stats.exp.maximum() as f32)
|
||||
.min(1.0)
|
||||
* 50.0) as u32
|
||||
+ if floater.timer < 0.1 {
|
||||
FLASH_MAX * (((1.0 - floater.timer / 0.1) * 10.0) as u32)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let y = floater.timer as f64 * number_speed; // Timer sets the widget offset
|
||||
let fade = ((4.0 - floater.timer as f32) * 0.25) + 0.2; // Timer sets text transparency
|
||||
|
||||
Text::new(&format!("{} Exp", floater.exp_change))
|
||||
.font_size(font_size_xp)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
|
||||
.x_y(
|
||||
ui_widgets.win_w * (0.5 * floater.rand.0 as f64 - 0.25),
|
||||
ui_widgets.win_h * (0.15 * floater.rand.1 as f64) + y - 3.0,
|
||||
)
|
||||
.set(player_sct_bg_id, ui_widgets);
|
||||
Text::new(&format!("{} Exp", floater.exp_change))
|
||||
.font_size(font_size_xp)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(0.59, 0.41, 0.67, fade))
|
||||
.x_y(
|
||||
ui_widgets.win_w * (0.5 * floater.rand.0 as f64 - 0.25),
|
||||
ui_widgets.win_h * (0.15 * floater.rand.1 as f64) + y,
|
||||
)
|
||||
.set(player_sct_id, ui_widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop speech bubbles
|
||||
@ -1273,7 +1164,6 @@ impl Hud {
|
||||
|
||||
let info = display_overhead_info.then(|| overhead::Info {
|
||||
name: &stats.name,
|
||||
stats,
|
||||
health,
|
||||
buffs,
|
||||
energy,
|
||||
@ -1313,7 +1203,6 @@ impl Hud {
|
||||
overhead::Overhead::new(
|
||||
info,
|
||||
bubble,
|
||||
own_level,
|
||||
in_group,
|
||||
&global_state.settings.gameplay,
|
||||
self.pulse,
|
||||
@ -1974,7 +1863,6 @@ impl Hud {
|
||||
// Get player stats
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let healths = ecs.read_storage::<comp::Health>();
|
||||
let inventories = ecs.read_storage::<comp::Inventory>();
|
||||
let energies = ecs.read_storage::<comp::Energy>();
|
||||
@ -1983,14 +1871,12 @@ impl Hud {
|
||||
let ability_map = ecs.fetch::<comp::item::tool::AbilityMap>();
|
||||
|
||||
if let (
|
||||
Some(stats),
|
||||
Some(health),
|
||||
Some(inventory),
|
||||
Some(energy),
|
||||
Some(_character_state),
|
||||
Some(_controller),
|
||||
) = (
|
||||
stats.get(entity),
|
||||
healths.get(entity),
|
||||
inventories.get(entity),
|
||||
energies.get(entity),
|
||||
@ -2003,7 +1889,6 @@ impl Hud {
|
||||
&self.item_imgs,
|
||||
&self.fonts,
|
||||
&self.rot_imgs,
|
||||
&stats,
|
||||
&health,
|
||||
&inventory,
|
||||
&energy,
|
||||
@ -2014,7 +1899,6 @@ impl Hud {
|
||||
tooltip_manager,
|
||||
&mut self.slot_manager,
|
||||
i18n,
|
||||
&self.show,
|
||||
&ability_map,
|
||||
)
|
||||
.set(self.ids.skillbar, ui_widgets);
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
settings::GameplaySettings,
|
||||
ui::{fonts::Fonts, Ingameable},
|
||||
};
|
||||
use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stats};
|
||||
use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType};
|
||||
use conrod_core::{
|
||||
color,
|
||||
position::Align,
|
||||
@ -57,7 +57,6 @@ widget_ids! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Info<'a> {
|
||||
pub name: &'a str,
|
||||
pub stats: &'a Stats,
|
||||
pub health: &'a Health,
|
||||
pub buffs: &'a Buffs,
|
||||
pub energy: Option<&'a Energy>,
|
||||
@ -72,7 +71,6 @@ pub fn should_show_healthbar(health: &Health) -> bool { health.current() != heal
|
||||
pub struct Overhead<'a> {
|
||||
info: Option<Info<'a>>,
|
||||
bubble: Option<&'a SpeechBubble>,
|
||||
own_level: u32,
|
||||
in_group: bool,
|
||||
settings: &'a GameplaySettings,
|
||||
pulse: f32,
|
||||
@ -89,7 +87,6 @@ impl<'a> Overhead<'a> {
|
||||
pub fn new(
|
||||
info: Option<Info<'a>>,
|
||||
bubble: Option<&'a SpeechBubble>,
|
||||
own_level: u32,
|
||||
in_group: bool,
|
||||
settings: &'a GameplaySettings,
|
||||
pulse: f32,
|
||||
@ -100,7 +97,6 @@ impl<'a> Overhead<'a> {
|
||||
Self {
|
||||
info,
|
||||
bubble,
|
||||
own_level,
|
||||
in_group,
|
||||
settings,
|
||||
pulse,
|
||||
@ -171,7 +167,6 @@ impl<'a> Widget for Overhead<'a> {
|
||||
const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0;
|
||||
if let Some(Info {
|
||||
name,
|
||||
stats,
|
||||
health,
|
||||
buffs,
|
||||
energy,
|
||||
@ -180,13 +175,10 @@ impl<'a> Widget for Overhead<'a> {
|
||||
// Used to set healthbar colours based on hp_percentage
|
||||
let hp_percentage = health.current() as f64 / health.maximum() as f64 * 100.0;
|
||||
// Compare levels to decide if a skull is shown
|
||||
let level_comp = stats.level.level() as i64 - self.own_level as i64;
|
||||
let health_current = (health.current() / 10) as f64;
|
||||
let health_max = (health.maximum() / 10) as f64;
|
||||
let name_y = if (health_current - health_max).abs() < 1e-6 {
|
||||
MANA_BAR_Y + 20.0
|
||||
} else if level_comp > 9 && !self.in_group {
|
||||
MANA_BAR_Y + 38.0
|
||||
} else {
|
||||
MANA_BAR_Y + 32.0
|
||||
};
|
||||
@ -373,51 +365,6 @@ impl<'a> Widget for Overhead<'a> {
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99)))
|
||||
.parent(id)
|
||||
.set(state.ids.health_bar_fg, ui);
|
||||
|
||||
// Level
|
||||
const LOW: Color = Color::Rgba(0.54, 0.81, 0.94, 0.4);
|
||||
const HIGH: Color = Color::Rgba(1.0, 0.0, 0.0, 1.0);
|
||||
const EQUAL: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||
// Change visuals of the level display depending on the player level/opponent
|
||||
// level
|
||||
let level_comp = stats.level.level() as i64 - self.own_level as i64;
|
||||
// + 10 level above player -> skull
|
||||
// + 5-10 levels above player -> high
|
||||
// -5 - +5 levels around player level -> equal
|
||||
// - 5 levels below player -> low
|
||||
if level_comp > 9 && !self.in_group {
|
||||
let skull_ani = ((self.pulse * 0.7/* speed factor */).cos() * 0.5 + 0.5) * 10.0; //Animation timer
|
||||
Image::new(if skull_ani as i32 == 1 && rand::random::<f32>() < 0.9 {
|
||||
self.imgs.skull_2
|
||||
} else {
|
||||
self.imgs.skull
|
||||
})
|
||||
.w_h(18.0 * BARSIZE, 18.0 * BARSIZE)
|
||||
.x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
|
||||
.parent(id)
|
||||
.set(state.ids.level_skull, ui);
|
||||
} else {
|
||||
let fnt_size = match stats.level.level() {
|
||||
0..=9 => 15,
|
||||
10..=99 => 12,
|
||||
100..=999 => 9,
|
||||
_ => 2,
|
||||
};
|
||||
Text::new(&format!("{}", stats.level.level()))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(fnt_size)
|
||||
.color(if level_comp > 4 {
|
||||
HIGH
|
||||
} else if level_comp < -5 {
|
||||
LOW
|
||||
} else {
|
||||
EQUAL
|
||||
})
|
||||
.x_y(-37.0 * BARSIZE, MANA_BAR_Y + 9.0)
|
||||
.parent(id)
|
||||
.set(state.ids.level, ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use super::{
|
||||
hotbar,
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::ItemImgs,
|
||||
slots, BarNumbers, ShortcutNumbers, Show, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
|
||||
slots, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
|
||||
STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
|
||||
};
|
||||
use crate::{
|
||||
@ -21,7 +21,7 @@ use common::comp::{
|
||||
tool::{AbilityMap, Tool, ToolKind},
|
||||
Hands, ItemKind,
|
||||
},
|
||||
Energy, Health, Inventory, Stats,
|
||||
Energy, Health, Inventory,
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
@ -29,7 +29,6 @@ use conrod_core::{
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use inline_tweak::*;
|
||||
use std::time::{Duration, Instant};
|
||||
use vek::*;
|
||||
|
||||
widget_ids! {
|
||||
@ -129,7 +128,6 @@ pub struct Skillbar<'a> {
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
stats: &'a Stats,
|
||||
health: &'a Health,
|
||||
inventory: &'a Inventory,
|
||||
energy: &'a Energy,
|
||||
@ -142,7 +140,6 @@ pub struct Skillbar<'a> {
|
||||
pulse: f32,
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
show: &'a Show,
|
||||
ability_map: &'a AbilityMap,
|
||||
}
|
||||
|
||||
@ -154,7 +151,6 @@ impl<'a> Skillbar<'a> {
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
stats: &'a Stats,
|
||||
health: &'a Health,
|
||||
inventory: &'a Inventory,
|
||||
energy: &'a Energy,
|
||||
@ -165,7 +161,6 @@ impl<'a> Skillbar<'a> {
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
slot_manager: &'a mut slots::SlotManager,
|
||||
localized_strings: &'a Localization,
|
||||
show: &'a Show,
|
||||
ability_map: &'a AbilityMap,
|
||||
) -> Self {
|
||||
Self {
|
||||
@ -174,7 +169,6 @@ impl<'a> Skillbar<'a> {
|
||||
item_imgs,
|
||||
fonts,
|
||||
rot_imgs,
|
||||
stats,
|
||||
health,
|
||||
inventory,
|
||||
energy,
|
||||
@ -186,7 +180,6 @@ impl<'a> Skillbar<'a> {
|
||||
tooltip_manager,
|
||||
slot_manager,
|
||||
localized_strings,
|
||||
show,
|
||||
ability_map,
|
||||
}
|
||||
}
|
||||
@ -194,8 +187,6 @@ impl<'a> Skillbar<'a> {
|
||||
|
||||
pub struct State {
|
||||
ids: Ids,
|
||||
last_level: u32,
|
||||
last_update_level: Instant,
|
||||
}
|
||||
|
||||
impl<'a> Widget for Skillbar<'a> {
|
||||
@ -206,8 +197,6 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||
State {
|
||||
ids: Ids::new(id_gen),
|
||||
last_level: 1,
|
||||
last_update_level: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,14 +206,6 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
|
||||
let _level = if self.stats.level.level() > 999 {
|
||||
"A".to_string()
|
||||
} else {
|
||||
(self.stats.level.level()).to_string()
|
||||
};
|
||||
|
||||
let _exp_percentage = (self.stats.exp.current() as f64) / (self.stats.exp.maximum() as f64);
|
||||
|
||||
let mut hp_percentage = self.health.current() as f64 / self.health.maximum() as f64 * 100.0;
|
||||
let mut energy_percentage =
|
||||
self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0;
|
||||
@ -243,65 +224,6 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
|
||||
let slot_offset = tweak!(3.0);
|
||||
|
||||
// Level Up Message
|
||||
if !self.show.intro {
|
||||
let current_level = self.stats.level.level();
|
||||
const FADE_IN_LVL: f32 = 1.0;
|
||||
const FADE_HOLD_LVL: f32 = 3.0;
|
||||
const FADE_OUT_LVL: f32 = 2.0;
|
||||
// Fade
|
||||
// Check if no other popup is displayed and a new one is needed
|
||||
if state.last_update_level.elapsed()
|
||||
> Duration::from_secs_f32(FADE_IN_LVL + FADE_HOLD_LVL + FADE_OUT_LVL)
|
||||
&& state.last_level != current_level
|
||||
{
|
||||
// Update last_value
|
||||
state.update(|s| s.last_level = current_level);
|
||||
state.update(|s| s.last_update_level = Instant::now());
|
||||
};
|
||||
|
||||
let seconds_level = state.last_update_level.elapsed().as_secs_f32();
|
||||
let fade_level = if current_level == 1 {
|
||||
0.0
|
||||
} else if seconds_level < FADE_IN_LVL {
|
||||
seconds_level / FADE_IN_LVL
|
||||
} else if seconds_level < FADE_IN_LVL + FADE_HOLD_LVL {
|
||||
1.0
|
||||
} else {
|
||||
(1.0 - (seconds_level - FADE_IN_LVL - FADE_HOLD_LVL) / FADE_OUT_LVL).max(0.0)
|
||||
};
|
||||
// Contents
|
||||
Rectangle::fill_with([82.0 * 4.0, 40.0 * 4.0], color::TRANSPARENT)
|
||||
.mid_top_with_margin_on(ui.window, 300.0)
|
||||
.set(state.ids.level_align, ui);
|
||||
let level_up_text = &localized_strings
|
||||
.get("char_selection.level_fmt")
|
||||
.replace("{level_nb}", &self.stats.level.level().to_string());
|
||||
Text::new(&level_up_text)
|
||||
.middle_of(state.ids.level_align)
|
||||
.font_size(self.fonts.cyri.scale(30))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, fade_level))
|
||||
.set(state.ids.level_message_bg, ui);
|
||||
Text::new(&level_up_text)
|
||||
.bottom_left_with_margins_on(state.ids.level_message_bg, 2.0, 2.0)
|
||||
.font_size(self.fonts.cyri.scale(30))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(1.0, 1.0, 1.0, fade_level))
|
||||
.set(state.ids.level_message, ui);
|
||||
Image::new(self.imgs.level_up)
|
||||
.w_h(82.0 * 4.0, 9.0 * 4.0)
|
||||
.mid_top_with_margin_on(state.ids.level_align, 0.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_level)))
|
||||
.graphics_for(state.ids.level_align)
|
||||
.set(state.ids.level_up, ui);
|
||||
Image::new(self.imgs.level_down)
|
||||
.w_h(82.0 * 4.0, 9.0 * 4.0)
|
||||
.mid_bottom_with_margin_on(state.ids.level_align, 0.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_level)))
|
||||
.graphics_for(state.ids.level_align)
|
||||
.set(state.ids.level_down, ui);
|
||||
}
|
||||
// Death message
|
||||
if self.health.is_dead {
|
||||
if let Some(key) = self
|
||||
|
@ -401,16 +401,6 @@ impl<'a> Widget for Social<'a> {
|
||||
},
|
||||
None => alias.clone(), // character select or spectating
|
||||
};
|
||||
let level = match &player_info.character {
|
||||
Some(character) => {
|
||||
if character.level > 999 {
|
||||
"[A]".to_string() // Hide player levels that can't be obtained by normal means. As "infinte" levels are temporary this will avoid clipping.
|
||||
} else {
|
||||
character.level.to_string()
|
||||
}
|
||||
},
|
||||
None => "".to_string(), // character select or spectating
|
||||
};
|
||||
let zone_name = match &player_info.character {
|
||||
None => self.localized_strings.get("hud.group.in_menu").to_string(), /* character select or spectating */
|
||||
_ => format!("{} ", &zone),
|
||||
@ -456,21 +446,6 @@ impl<'a> Widget for Social<'a> {
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.ids.player_names[i], ui);
|
||||
// Player Levels
|
||||
Button::image(if !selected {
|
||||
self.imgs.nothing
|
||||
} else {
|
||||
self.imgs.selection
|
||||
})
|
||||
.w_h(39.0, 20.0)
|
||||
.right_from(state.ids.player_names[i], 2.0)
|
||||
.label(&level)
|
||||
.label_font_size(self.fonts.cyri.scale(14))
|
||||
.label_font_id(self.fonts.cyri.conrod_id)
|
||||
.label_color(TEXT_COLOR)
|
||||
.label_y(conrod_core::position::Relative::Scalar(1.0))
|
||||
.parent(state.ids.levels_align)
|
||||
.set(state.ids.player_levels[i], ui);
|
||||
// Player Zones
|
||||
Button::image(if !selected {
|
||||
self.imgs.nothing
|
||||
|
@ -447,13 +447,6 @@ impl Controls {
|
||||
// TODO: only construct string once when characters
|
||||
// are
|
||||
// loaded
|
||||
Text::new(
|
||||
i18n.get("char_selection.level_fmt").replace(
|
||||
"{level_nb}",
|
||||
&character.level.to_string(),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
Text::new(
|
||||
i18n.get("char_selection.uncanny_valley"),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user