diff --git a/common/src/combat.rs b/common/src/combat.rs index 709a050c5c..1ba9de057d 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -9,7 +9,7 @@ use crate::{ slot::EquipSlot, }, skills::{SkillGroupKind, SkillSet}, - BuffKind, Health, HealthChange, HealthSource, Inventory, Stats, + Body, BuffKind, Health, HealthChange, HealthSource, Inventory, Stats, }, uid::Uid, util::Dir, @@ -245,7 +245,7 @@ fn offensive_rating(inv: &Inventory, skillset: &SkillSet) -> f32 { active_damage.max(second_damage) } -pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats) -> f32 { +pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats, body: Body) -> f32 { let defensive_weighting = 1.0; let offensive_weighting = 1.0; let defensive_rating = health.maximum() as f32 @@ -256,5 +256,5 @@ pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats) -> f let combined_rating = (offensive_rating * offensive_weighting + defensive_rating * defensive_weighting) / (offensive_weighting + defensive_weighting); - combined_rating * stats.body_type.combat_multiplier() + combined_rating * body.combat_multiplier() } diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 4fc5dd7dbc..e333420e5e 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -1,7 +1,4 @@ -use crate::{ - comp, - comp::{skills::SkillSet, Body}, -}; +use crate::comp::skills::SkillSet; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; @@ -27,15 +24,13 @@ impl Error for StatChangeError {} pub struct Stats { pub name: String, pub skill_set: SkillSet, - pub body_type: Body, } impl Stats { - pub fn new(name: String, body: Body) -> Self { + pub fn new(name: String) -> Self { Self { name, skill_set: SkillSet::default(), - body_type: body, } } @@ -45,7 +40,6 @@ impl Stats { Self { name: "".to_owned(), skill_set: SkillSet::default(), - body_type: comp::Body::Humanoid(comp::body::humanoid::Body::random()), } } } diff --git a/common/sys/src/stats.rs b/common/sys/src/stats.rs index 2f6bfe3aeb..26137611b5 100644 --- a/common/sys/src/stats.rs +++ b/common/sys/src/stats.rs @@ -1,7 +1,7 @@ use common::{ comp::{ skills::{GeneralSkill, Skill}, - CharacterState, Energy, EnergyChange, EnergySource, Health, Pos, Stats, + Body, CharacterState, Energy, EnergyChange, EnergySource, Health, Pos, Stats, }, event::{EventBus, ServerEvent}, metrics::SysMetrics, @@ -31,6 +31,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, ReadStorage<'a, Pos>, Write<'a, Vec>, + ReadStorage<'a, Body>, ); fn run( @@ -47,6 +48,7 @@ impl<'a> System<'a> for Sys { uids, positions, mut outcomes, + bodies, ): Self::SystemData, ) { let start_time = std::time::Instant::now(); @@ -111,10 +113,11 @@ impl<'a> System<'a> for Sys { } // Apply effects from leveling skills - for (mut stats, mut health, mut energy) in ( + for (mut stats, mut health, mut energy, body) in ( &mut stats.restrict_mut(), &mut healths.restrict_mut(), &mut energies.restrict_mut(), + &bodies, ) .join() { @@ -126,7 +129,7 @@ impl<'a> System<'a> for Sys { .skill_level(Skill::General(GeneralSkill::HealthIncrease)) .unwrap_or(None) .unwrap_or(0); - health.update_max_hp(Some(stat.body_type), health_level); + health.update_max_hp(Some(*body), health_level); let mut stat = stats.get_mut_unchecked(); stat.skill_set.modify_health = false; } @@ -138,7 +141,7 @@ impl<'a> System<'a> for Sys { .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) .unwrap_or(None) .unwrap_or(0); - energy.update_max_energy(Some(stat.body_type), energy_level); + energy.update_max_energy(Some(*body), energy_level); let mut stat = stats.get_mut_unchecked(); stat.skill_set.modify_energy = false; } diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs index 32199d2c6a..0d8fd31e51 100644 --- a/server/src/character_creator.rs +++ b/server/src/character_creator.rs @@ -10,7 +10,7 @@ pub fn create_character( body: Body, character_loader: &ReadExpect<'_, CharacterLoader>, ) { - let stats = Stats::new(character_alias.to_string(), body); + let stats = Stats::new(character_alias.to_string()); let loadout = LoadoutBuilder::new() .defaults() diff --git a/server/src/cmd.rs b/server/src/cmd.rs index c4c32b96ed..1acc34e994 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -820,10 +820,10 @@ fn handle_spawn( .state .create_npc( pos, - comp::Stats::new( - get_npc_name(id, npc::BodyType::from_body(body)), - body, - ), + comp::Stats::new(get_npc_name( + id, + npc::BodyType::from_body(body), + )), comp::Health::new(body, 1), inventory, body, @@ -929,7 +929,7 @@ fn handle_spawn_training_dummy( let body = comp::Body::Object(comp::object::Body::TrainingDummy); - let stats = comp::Stats::new("Training Dummy".to_string(), body); + let stats = comp::Stats::new("Training Dummy".to_string()); let health = comp::Health::new(body, 0); diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index c4eb0d0244..503c026c03 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -164,6 +164,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc let healths = state.ecs().read_storage::(); let inventories = state.ecs().read_storage::(); let players = state.ecs().read_storage::(); + let bodies = state.ecs().read_storage::(); let by = if let HealthSource::Damage { by: Some(by), .. } = cause { by } else { @@ -174,16 +175,21 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc } else { return; }; - let (entity_stats, entity_health, entity_inventory) = - if let (Some(entity_stats), Some(entity_health), Some(entity_inventory)) = ( - stats.get(entity), - healths.get(entity), - inventories.get(entity), - ) { - (entity_stats, entity_health, entity_inventory) - } else { - return; - }; + let (entity_stats, entity_health, entity_inventory, entity_body) = if let ( + Some(entity_stats), + Some(entity_health), + Some(entity_inventory), + Some(entity_body), + ) = ( + stats.get(entity), + healths.get(entity), + inventories.get(entity), + bodies.get(entity), + ) { + (entity_stats, entity_health, entity_inventory, entity_body) + } else { + return; + }; let groups = state.ecs().read_storage::(); let attacker_group = groups.get(attacker); @@ -201,7 +207,8 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc // TODO: Scale xp from skillset rather than health, when NPCs have their own // skillsets let mut exp_reward = - combat::combat_rating(entity_inventory, entity_health, entity_stats) * 2.5; + combat::combat_rating(entity_inventory, entity_health, entity_stats, *entity_body) + * 2.5; // Distribute EXP to group let positions = state.ecs().read_storage::(); diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 5e63197e58..22439d623f 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -562,10 +562,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv }); }, item::Throwable::TrainingDummy => { - new_entity = new_entity.with(comp::Stats::new( - "Training Dummy".to_string(), - comp::object::Body::TrainingDummy.into(), - )); + new_entity = new_entity.with(comp::Stats::new("Training Dummy".to_string())); }, }; diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 48e260a3c2..bc8631ca16 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -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), + stats: comp::Stats::new(entity.get_name()), health: comp::Health::new(body, 10), loadout: match body { comp::Body::Humanoid(_) => entity.get_loadout(), diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 6031bb07d6..f7df8128ab 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -290,8 +290,8 @@ impl StateExt for State { .unwrap_or(None) .unwrap_or(0), ); - self.write_component(entity, comp::Health::new(stats.body_type, health_level)); - self.write_component(entity, comp::Energy::new(stats.body_type, energy_level)); + self.write_component(entity, comp::Health::new(body, health_level)); + self.write_component(entity, comp::Energy::new(body, energy_level)); self.write_component(entity, stats); self.write_component(entity, inventory); self.write_component( diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 1f33c7f64a..1b6c2000f1 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -124,7 +124,7 @@ impl<'a> System<'a> for Sys { let name = entity.name.unwrap_or_else(|| "Unnamed".to_string()); let alignment = entity.alignment; let main_tool = entity.main_tool; - let mut stats = comp::Stats::new(name, body); + let mut stats = comp::Stats::new(name); let mut scale = entity.scale; @@ -135,13 +135,10 @@ impl<'a> System<'a> for Sys { let npc_names = NPC_NAMES.read(); body = comp::Body::Humanoid(body_new); - stats = comp::Stats::new( - format!( - "Gentle Giant {}", - get_npc_name(&npc_names.humanoid, body_new.species) - ), - body, - ); + stats = comp::Stats::new(format!( + "Gentle Giant {}", + get_npc_name(&npc_names.humanoid, body_new.species) + )); } scale = 2.0 + rand::random::(); } @@ -154,7 +151,7 @@ impl<'a> System<'a> for Sys { let loadout = LoadoutBuilder::build_loadout(body, main_tool, loadout_config).build(); - let health = comp::Health::new(stats.body_type, entity.level.unwrap_or(0)); + let health = comp::Health::new(body, entity.level.unwrap_or(0)); let can_speak = match body { comp::Body::Humanoid(_) => alignment == comp::Alignment::Npc, diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 28601fee5e..8194443e8f 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -18,7 +18,7 @@ use crate::{ use client::Client; use common::{ combat::{combat_rating, Damage}, - comp::{item::Quality, Energy, Health, Stats}, + comp::{item::Quality, Body, Energy, Health, Stats}, }; use conrod_core::{ color, @@ -100,6 +100,7 @@ pub struct Bag<'a> { health: &'a Health, energy: &'a Energy, show: &'a Show, + body: &'a Body, } impl<'a> Bag<'a> { @@ -118,6 +119,7 @@ impl<'a> Bag<'a> { health: &'a Health, energy: &'a Energy, show: &'a Show, + body: &'a Body, ) -> Self { Self { client, @@ -134,6 +136,7 @@ impl<'a> Bag<'a> { energy, health, show, + body, } } } @@ -428,7 +431,8 @@ impl<'a> Widget for Bag<'a> { .resize(STATS.len(), &mut ui.widget_id_generator()) }); // Stats - let combat_rating = combat_rating(inventory, self.health, self.stats).min(999.9); + let combat_rating = + combat_rating(inventory, self.health, self.stats, *self.body).min(999.9); let indicator_col = cr_color(combat_rating); for i in STATS.iter().copied().enumerate() { let btn = Button::image(match i.1 { diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 64777a1f41..b6d568bcd6 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -340,6 +340,7 @@ impl<'a> Widget for Group<'a> { let buffs = client_state.ecs().read_storage::(); let inventory = client_state.ecs().read_storage::(); let uid_allocator = client_state.ecs().read_resource::(); + let bodies = client_state.ecs().read_storage::(); // Keep track of the total number of widget ids we are using for buffs let mut total_buff_count = 0; @@ -352,80 +353,80 @@ impl<'a> Widget for Group<'a> { let buffs = entity.and_then(|entity| buffs.get(entity)); let inventory = entity.and_then(|entity| inventory.get(entity)); let is_leader = uid == leader; + let body = entity.and_then(|entity| bodies.get(entity)); - if let Some(stats) = stats { - let combat_rating = - combat::combat_rating(inventory.unwrap(), &health.unwrap(), stats); // We can unwrap here because we check for stats first + if let (Some(stats), Some(inventory), Some(health), Some(body)) = + (stats, inventory, health, body) + { + let combat_rating = combat::combat_rating(inventory, health, stats, *body); let char_name = stats.name.to_string(); - if let Some(health) = health { - let health_perc = health.current() as f64 / health.maximum() as f64; - // change panel positions when debug info is shown - let x = if debug_on { i / 8 } else { i / 12 }; - let y = if debug_on { i % 8 } else { i % 12 }; - let back = Image::new(self.imgs.member_bg).top_left_with_margins_on( - ui.window, - 50.0 + offset + y as f64 * 77.0, - 10.0 + x as f64 * 180.0, - ); - let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer - let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); - let health_col = match (health_perc * 100.0) as u8 { - 0..=20 => crit_hp_color, - 21..=40 => LOW_HP_COLOR, - _ => HP_COLOR, - }; - // Don't show panel for the player! - // Panel BG - back.w_h(152.0, 36.0) - .color(if is_leader { - Some(ERROR_COLOR) - } else { - Some(TEXT_COLOR) - }) - .set(state.ids.member_panels_bg[i], ui); - // Health - Image::new(self.imgs.bar_content) - .w_h(148.0 * health_perc, 22.0) - .color(Some(health_col)) - .top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0) - .set(state.ids.member_health[i], ui); - if health.is_dead { - // Death Text - Text::new(&self.localized_strings.get("hud.group.dead")) - .mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0) - .font_size(20) - .font_id(self.fonts.cyri.conrod_id) - .color(KILL_COLOR) - .set(state.ids.dead_txt[i], ui); + let health_perc = health.current() as f64 / health.maximum() as f64; + // change panel positions when debug info is shown + let x = if debug_on { i / 8 } else { i / 12 }; + let y = if debug_on { i % 8 } else { i % 12 }; + let back = Image::new(self.imgs.member_bg).top_left_with_margins_on( + ui.window, + 50.0 + offset + y as f64 * 77.0, + 10.0 + x as f64 * 180.0, + ); + let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer + let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); + let health_col = match (health_perc * 100.0) as u8 { + 0..=20 => crit_hp_color, + 21..=40 => LOW_HP_COLOR, + _ => HP_COLOR, + }; + // Don't show panel for the player! + // Panel BG + back.w_h(152.0, 36.0) + .color(if is_leader { + Some(ERROR_COLOR) } else { - // Health Text - let txt = format!( - "{}/{}", - health.current() / 10_u32, - health.maximum() / 10_u32, - ); - // Change font size depending on health amount - let font_size = match health.maximum() { - 0..=999 => 14, - 1000..=9999 => 13, - 10000..=99999 => 12, - _ => 11, - }; - // Change text offset depending on health amount - let txt_offset = match health.maximum() { - 0..=999 => 4.0, - 1000..=9999 => 4.5, - 10000..=99999 => 5.0, - _ => 5.5, - }; - Text::new(&txt) - .mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset) - .font_size(font_size) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 0.5)) - .set(state.ids.health_txt[i], ui); + Some(TEXT_COLOR) + }) + .set(state.ids.member_panels_bg[i], ui); + // Health + Image::new(self.imgs.bar_content) + .w_h(148.0 * health_perc, 22.0) + .color(Some(health_col)) + .top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0) + .set(state.ids.member_health[i], ui); + if health.is_dead { + // Death Text + Text::new(&self.localized_strings.get("hud.group.dead")) + .mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0) + .font_size(20) + .font_id(self.fonts.cyri.conrod_id) + .color(KILL_COLOR) + .set(state.ids.dead_txt[i], ui); + } else { + // Health Text + let txt = format!( + "{}/{}", + health.current() / 10_u32, + health.maximum() / 10_u32, + ); + // Change font size depending on health amount + let font_size = match health.maximum() { + 0..=999 => 14, + 1000..=9999 => 13, + 10000..=99999 => 12, + _ => 11, }; - } + // Change text offset depending on health amount + let txt_offset = match health.maximum() { + 0..=999 => 4.0, + 1000..=9999 => 4.5, + 10000..=99999 => 5.0, + _ => 5.5, + }; + Text::new(&txt) + .mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(1.0, 1.0, 1.0, 0.5)) + .set(state.ids.health_txt[i], ui); + }; // Panel Frame Image::new(self.imgs.member_frame) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 75be629adb..f011bbc420 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1341,7 +1341,7 @@ impl Hud { health, buffs, energy, - combat_rating: combat::combat_rating(inventory, health, stats), + combat_rating: combat::combat_rating(inventory, health, stats, *body), }); let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) { speech_bubbles.get(uid) @@ -2011,6 +2011,7 @@ impl Hud { let character_states = ecs.read_storage::(); let controllers = ecs.read_storage::(); let ability_map = ecs.fetch::(); + let bodies = ecs.read_storage::(); if let ( Some(health), @@ -2047,10 +2048,11 @@ impl Hud { } // Bag contents if self.show.bag { - if let (Some(player_stats), Some(health), Some(energy)) = ( + if let (Some(player_stats), Some(health), Some(energy), Some(body)) = ( stats.get(client.entity()), healths.get(entity), energies.get(entity), + bodies.get(entity), ) { match Bag::new( client, @@ -2066,6 +2068,7 @@ impl Hud { &health, &energy, &self.show, + &body, ) .set(self.ids.bag, ui_widgets) {