mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Scrolling Combat Text (SCT)
This commit is contained in:
parent
4e7c955490
commit
851d7858e6
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,3 +38,4 @@ todo.txt
|
||||
|
||||
# direnv
|
||||
/.envrc
|
||||
*.bat
|
||||
|
@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added changelog
|
||||
- Added animated Map and Minimap position indicator
|
||||
- Added visuals to indicate strength compared to the player
|
||||
- Added Scrolling Combat Text (SCT) & Settings for it
|
||||
- Added a Death Screen and Hurt Screen
|
||||
- Added randomly selected Loading Screen background images
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
@ -50,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fixed ghosts when going back to character screen
|
||||
- Fixed not being able to unmount
|
||||
- Fixed non-humanoids being able to climb and glide
|
||||
- Made shadows and lights use interpolated positions
|
||||
|
||||
### Removed
|
||||
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3544,6 +3544,7 @@ dependencies = [
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git)",
|
||||
"treeculler 0.1.0 (git+https://gitlab.com/yusdacra/treeculler.git)",
|
||||
"vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"veloren-client 0.4.0",
|
||||
|
BIN
assets/voxygen/background/bg_1.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/bg_3.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_3.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/bg_4.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_4.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/bg_5.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_5.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/bg_6.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_6.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/bg_7.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_7.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/bg_8.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/bg_8.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/death.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/death.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/hurt.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/background/hurt.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/background/map.png
(Stored with Git LFS)
BIN
assets/voxygen/background/map.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/enemybar.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/skull.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/skull.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/skull_2.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/skull_2.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/enemy_bar_content.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/skillbar/enemy_bar_content.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -25,6 +25,10 @@ void main() {
|
||||
if (w_pos.w == 1.0) {
|
||||
// In-game element
|
||||
gl_Position = proj_mat * (view_mat * w_pos + vec4(v_pos, 0.0, 0.0));
|
||||
} else if (w_pos.w == -1.0 ) {
|
||||
// Fixed scale In-game element
|
||||
vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0);
|
||||
gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0);
|
||||
} else {
|
||||
// Interface element
|
||||
gl_Position = vec4(v_pos, 0.0, 1.0);
|
||||
|
@ -65,7 +65,11 @@ fn main() {
|
||||
client.send_chat(msg)
|
||||
}
|
||||
|
||||
let events = match client.tick(comp::ControllerInputs::default(), clock.get_last_delta()) {
|
||||
let events = match client.tick(
|
||||
comp::ControllerInputs::default(),
|
||||
clock.get_last_delta(),
|
||||
|_| {},
|
||||
) {
|
||||
Ok(events) => events,
|
||||
Err(err) => {
|
||||
error!("Error: {:?}", err);
|
||||
|
@ -8,7 +8,7 @@ pub use crate::error::Error;
|
||||
pub use specs::{
|
||||
join::Join,
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Builder, Entity as EcsEntity, ReadStorage, WorldExt,
|
||||
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, WorldExt,
|
||||
};
|
||||
|
||||
use common::{
|
||||
@ -316,7 +316,12 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Execute a single client tick, handle input and update the game state by the given duration.
|
||||
pub fn tick(&mut self, inputs: ControllerInputs, dt: Duration) -> Result<Vec<Event>, Error> {
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
inputs: ControllerInputs,
|
||||
dt: Duration,
|
||||
add_foreign_systems: impl Fn(&mut DispatcherBuilder),
|
||||
) -> Result<Vec<Event>, Error> {
|
||||
// This tick function is the centre of the Veloren universe. Most client-side things are
|
||||
// managed from here, and as such it's important that it stays organised. Please consult
|
||||
// the core developers before making significant changes to this code. Here is the
|
||||
@ -374,7 +379,7 @@ impl Client {
|
||||
// 3) Update client local data
|
||||
|
||||
// 4) Tick the client's LocalState
|
||||
self.state.tick(dt, |_| {});
|
||||
self.state.tick(dt, add_foreign_systems);
|
||||
|
||||
// 5) Terrain
|
||||
let pos = self
|
||||
|
@ -12,15 +12,15 @@ mod stats;
|
||||
use specs::DispatcherBuilder;
|
||||
|
||||
// System names
|
||||
const AGENT_SYS: &str = "agent_sys";
|
||||
const CONTROLLER_SYS: &str = "controller_sys";
|
||||
const MOUNT_SYS: &str = "mount_sys";
|
||||
const PHYS_SYS: &str = "phys_sys";
|
||||
const MOVEMENT_SYS: &str = "movement_sys";
|
||||
const PROJECTILE_SYS: &str = "projectile_sys";
|
||||
const COMBAT_SYS: &str = "combat_sys";
|
||||
const STATS_SYS: &str = "stats_sys";
|
||||
const CLEANUP_SYS: &str = "cleanup_sys";
|
||||
pub const AGENT_SYS: &str = "agent_sys";
|
||||
pub const CONTROLLER_SYS: &str = "controller_sys";
|
||||
pub const MOUNT_SYS: &str = "mount_sys";
|
||||
pub const PHYS_SYS: &str = "phys_sys";
|
||||
pub const MOVEMENT_SYS: &str = "movement_sys";
|
||||
pub const PROJECTILE_SYS: &str = "projectile_sys";
|
||||
pub const COMBAT_SYS: &str = "combat_sys";
|
||||
pub const STATS_SYS: &str = "stats_sys";
|
||||
pub const CLEANUP_SYS: &str = "cleanup_sys";
|
||||
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
|
||||
|
2
server-cli/.gitignore
vendored
2
server-cli/.gitignore
vendored
@ -1 +1 @@
|
||||
settings.ron
|
||||
|
||||
|
@ -87,9 +87,9 @@ lazy_static! {
|
||||
/// Static list of chat commands available to the server.
|
||||
pub static ref CHAT_COMMANDS: Vec<ChatCommand> = vec![
|
||||
ChatCommand::new(
|
||||
"giveitem",
|
||||
"give_item",
|
||||
"{d}",
|
||||
"/giveitem <path to item>\n\
|
||||
"/give_item <path to item>\n\
|
||||
Example: common/items/debug/boost",
|
||||
true,
|
||||
handle_give,),
|
||||
|
@ -22,7 +22,7 @@ impl Default for ServerSettings {
|
||||
Self {
|
||||
gameserver_address: SocketAddr::from(([0; 4], 14004)),
|
||||
metrics_address: SocketAddr::from(([0; 4], 14005)),
|
||||
world_seed: 1337,
|
||||
world_seed: 5284,
|
||||
server_name: "Veloren Alpha".to_owned(),
|
||||
server_description: "This is the best Veloren server.".to_owned(),
|
||||
max_players: 100,
|
||||
|
@ -24,8 +24,12 @@ const TERRAIN_SYS: &str = "server_terrain_sys";
|
||||
|
||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
// TODO: makes some of these dependent on systems in common like the phys system
|
||||
dispatch_builder.add(sentinel::Sys, SENTINEL_SYS, &[]);
|
||||
dispatch_builder.add(subscription::Sys, SUBSCRIPTION_SYS, &[]);
|
||||
dispatch_builder.add(sentinel::Sys, SENTINEL_SYS, &[common::sys::PHYS_SYS]);
|
||||
dispatch_builder.add(
|
||||
subscription::Sys,
|
||||
SUBSCRIPTION_SYS,
|
||||
&[common::sys::PHYS_SYS],
|
||||
);
|
||||
dispatch_builder.add(
|
||||
entity_sync::Sys,
|
||||
ENTITY_SYNC_SYS,
|
||||
|
@ -28,6 +28,7 @@ euc = "0.3.0"
|
||||
|
||||
# ECS
|
||||
specs = "0.15.1"
|
||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
|
||||
|
||||
# Mathematics
|
||||
vek = { version = "0.9.9", features = ["serde"] }
|
||||
|
34
voxygen/src/ecs/comp.rs
Normal file
34
voxygen/src/ecs/comp.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use specs::Component;
|
||||
use specs_idvs::IDVStorage;
|
||||
use vek::*;
|
||||
|
||||
// Floats over entity that has had a health change, rising up over time until it vanishes
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct HpFloater {
|
||||
pub timer: f32,
|
||||
// Numbers of times significant damage has been dealt
|
||||
pub hp_change: i32,
|
||||
// Used for randomly offseting
|
||||
pub rand: f32,
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct HpFloaterList {
|
||||
// Order oldest to newest
|
||||
pub floaters: Vec<HpFloater>,
|
||||
// Keep from spawning more floaters from same hp change
|
||||
// Note: this can't detect a change if equivalent healing and damage take place simultaneously
|
||||
pub last_hp: u32,
|
||||
}
|
||||
impl Component for HpFloaterList {
|
||||
type Storage = IDVStorage<Self>;
|
||||
}
|
||||
|
||||
// Used for smooth interpolation of visual elements that are tied to entity position
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Interpolated {
|
||||
pub pos: Vec3<f32>,
|
||||
pub ori: Vec3<f32>,
|
||||
}
|
||||
impl Component for Interpolated {
|
||||
type Storage = IDVStorage<Self>;
|
||||
}
|
28
voxygen/src/ecs/mod.rs
Normal file
28
voxygen/src/ecs/mod.rs
Normal file
@ -0,0 +1,28 @@
|
||||
pub mod comp;
|
||||
pub mod sys;
|
||||
|
||||
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());
|
||||
}
|
17
voxygen/src/ecs/sys.rs
Normal file
17
voxygen/src/ecs/sys.rs
Normal file
@ -0,0 +1,17 @@
|
||||
pub mod floater;
|
||||
mod interpolation;
|
||||
|
||||
use specs::DispatcherBuilder;
|
||||
|
||||
// System names
|
||||
const FLOATER_SYS: &str = "floater_voxygen_sys";
|
||||
const INTERPOLATION_SYS: &str = "interpolation_voxygen_sys";
|
||||
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(
|
||||
interpolation::Sys,
|
||||
INTERPOLATION_SYS,
|
||||
&[common::sys::PHYS_SYS],
|
||||
);
|
||||
dispatch_builder.add(floater::Sys, FLOATER_SYS, &[INTERPOLATION_SYS]);
|
||||
}
|
181
voxygen/src/ecs/sys/floater.rs
Normal file
181
voxygen/src/ecs/sys/floater.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use crate::ecs::{
|
||||
comp::{HpFloater, HpFloaterList},
|
||||
ExpFloater, MyEntity, MyExpFloaterList,
|
||||
};
|
||||
use common::{
|
||||
comp::{HealthSource, Pos, Stats},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write, 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 {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
ReadExpect<'a, MyEntity>,
|
||||
Read<'a, DeltaTime>,
|
||||
Write<'a, MyExpFloaterList>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Stats>,
|
||||
WriteStorage<'a, HpFloaterList>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, my_entity, dt, mut my_exp_floater_list, uids, pos, stats, mut hp_floater_lists): Self::SystemData,
|
||||
) {
|
||||
// Add hp floater lists to all entities with stats and a postion
|
||||
// Note: neccessary in order to know last_hp
|
||||
for (entity, last_hp) in (&entities, &stats, &pos, !&hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, s, _, _)| (e, s.health.current()))
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let _ = hp_floater_lists.insert(
|
||||
entity,
|
||||
HpFloaterList {
|
||||
floaters: Vec::new(),
|
||||
last_hp,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Add hp floaters to all entities that have been damaged
|
||||
let my_uid = uids.get(my_entity.0);
|
||||
for (entity, health, hp_floater_list) in (&entities, &stats, &mut hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, s, fl)| (e, s.health, fl))
|
||||
{
|
||||
// Check if health has changed (won't work if damaged and then healed with
|
||||
// equivalently in the same frame)
|
||||
if hp_floater_list.last_hp != health.current() {
|
||||
hp_floater_list.last_hp = health.current();
|
||||
// TODO: What if multiple health changes occured since last check here
|
||||
// Also, If we make stats store a vec of the last_changes (from say the last frame),
|
||||
// what if the client recieves the stats component from two different server ticks at
|
||||
// once, then one will be lost (tbf this is probably a rare occurance and the results
|
||||
// would just be a transient glitch in the display of these damage numbers) (maybe
|
||||
// health changes could be sent to the client as a list of events)
|
||||
if match health.last_change.1.cause {
|
||||
HealthSource::Attack { by } => {
|
||||
my_entity.0 == entity || my_uid.map_or(false, |&uid| by == uid)
|
||||
}
|
||||
HealthSource::Suicide => my_entity.0 == entity,
|
||||
HealthSource::World => my_entity.0 == entity,
|
||||
HealthSource::Revive => false,
|
||||
HealthSource::Command => true,
|
||||
HealthSource::LevelUp => my_entity.0 == entity,
|
||||
HealthSource::Item => true,
|
||||
HealthSource::Unknown => false,
|
||||
} {
|
||||
hp_floater_list.floaters.push(HpFloater {
|
||||
timer: 0.0,
|
||||
hp_change: health.last_change.1.amount,
|
||||
rand: rand::random(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove floater lists on entities without stats or without posistion
|
||||
for entity in (&entities, !&stats, &hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
hp_floater_lists.remove(entity);
|
||||
}
|
||||
for entity in (&entities, !&pos, &hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
hp_floater_lists.remove(entity);
|
||||
}
|
||||
|
||||
// Maintain existing floaters
|
||||
for (
|
||||
entity,
|
||||
HpFloaterList {
|
||||
ref mut floaters,
|
||||
ref last_hp,
|
||||
},
|
||||
) in (&entities, &mut hp_floater_lists).join()
|
||||
{
|
||||
for mut floater in floaters.iter_mut() {
|
||||
// Increment timer
|
||||
floater.timer += dt.0;
|
||||
}
|
||||
// Clear floaters if newest floater is past show time or health runs out
|
||||
if floaters.last().map_or(false, |f| {
|
||||
f.timer
|
||||
> if entity != my_entity.0 {
|
||||
HP_SHOWTIME
|
||||
} else {
|
||||
MY_HP_SHOWTIME
|
||||
}
|
||||
|| *last_hp == 0
|
||||
}) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
80
voxygen/src/ecs/sys/interpolation.rs
Normal file
80
voxygen/src/ecs/sys/interpolation.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::ecs::comp::Interpolated;
|
||||
use common::{
|
||||
comp::{Ori, Pos, Vel},
|
||||
state::DeltaTime,
|
||||
};
|
||||
use log::warn;
|
||||
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
/// This system will allow NPCs to modify their controller
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, DeltaTime>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Vel>,
|
||||
WriteStorage<'a, Interpolated>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, dt, positions, orientations, velocities, mut interpolated): Self::SystemData,
|
||||
) {
|
||||
// Update interpolated positions and orientations
|
||||
for (pos, ori, i, vel) in (&positions, &orientations, &mut interpolated, &velocities).join()
|
||||
{
|
||||
// Update interpolation values
|
||||
if i.pos.distance_squared(pos.0) < 64.0 * 64.0 {
|
||||
i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, 10.0 * dt.0);
|
||||
let ori_interp = Slerp::slerp(i.ori, ori.0, 5.0 * dt.0);
|
||||
// Check for NaNs
|
||||
// TODO: why are we getting NaNs here! Zero-length ori vectors?
|
||||
i.ori = if !ori_interp.map(|e| e.is_nan()).reduce_or() {
|
||||
ori_interp
|
||||
} else {
|
||||
ori.0
|
||||
};
|
||||
} else {
|
||||
i.pos = pos.0;
|
||||
i.ori = ori.0;
|
||||
}
|
||||
}
|
||||
// Insert interpolation components for entities which don't have them
|
||||
for (entity, pos, ori) in (&entities, &positions, &orientations, !&interpolated)
|
||||
.join()
|
||||
.map(|(e, p, o, _)| (e, p.0, o.0))
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
interpolated
|
||||
.insert(entity, Interpolated { pos, ori })
|
||||
.err()
|
||||
.map(|err| warn!("Error inserting Interpolated component: {}", err));
|
||||
}
|
||||
// Remove Interpolated component from entities which don't have a position or an
|
||||
// orientation or a velocity
|
||||
for entity in (&entities, !&positions, &interpolated)
|
||||
.join()
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
interpolated.remove(entity);
|
||||
}
|
||||
for entity in (&entities, !&orientations, &interpolated)
|
||||
.join()
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
interpolated.remove(entity);
|
||||
}
|
||||
for entity in (&entities, !&velocities, &interpolated)
|
||||
.join()
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
interpolated.remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
@ -231,8 +231,7 @@ image_ids! {
|
||||
key: "voxygen.voxel.object.key",
|
||||
key_gold: "voxygen.voxel.object.key_gold",
|
||||
|
||||
// Enemy Healthbar
|
||||
enemy_health: "voxygen.element.frames.enemybar",
|
||||
|
||||
|
||||
|
||||
|
||||
@ -247,6 +246,13 @@ image_ids! {
|
||||
charwindow_gradient:"voxygen.element.misc_bg.charwindow",
|
||||
map_placeholder: "voxygen.background.map",
|
||||
|
||||
death_bg: "voxygen.background.death",
|
||||
hurt_bg: "voxygen.background.hurt",
|
||||
|
||||
// Enemy Healthbar
|
||||
enemy_health: "voxygen.element.frames.enemybar",
|
||||
// Enemy Bar Content:
|
||||
enemy_bar: "voxygen.element.skillbar.enemy_bar_content",
|
||||
// Spell Book Window
|
||||
spellbook_icon: "voxygen.element.icons.spellbook",
|
||||
// Bag
|
||||
|
@ -9,7 +9,6 @@ use conrod_core::{
|
||||
};
|
||||
use specs::WorldExt;
|
||||
use vek::*;
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
map_frame,
|
||||
@ -37,6 +36,7 @@ pub struct Map<'a> {
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
pulse: f32,
|
||||
velocity: f32,
|
||||
}
|
||||
impl<'a> Map<'a> {
|
||||
pub fn new(
|
||||
@ -46,6 +46,7 @@ impl<'a> Map<'a> {
|
||||
world_map: Id,
|
||||
fonts: &'a Fonts,
|
||||
pulse: f32,
|
||||
velocity: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
_show: show,
|
||||
@ -55,6 +56,7 @@ impl<'a> Map<'a> {
|
||||
fonts: fonts,
|
||||
common: widget::CommonBuilder::default(),
|
||||
pulse,
|
||||
velocity,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,6 +86,15 @@ impl<'a> Widget for Map<'a> {
|
||||
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
// Set map transparency to 0.5 when player is moving
|
||||
/*let vel = match self.velocity {
|
||||
Some(velocity) => velocity.0.magnitude(),
|
||||
None => 0.0,
|
||||
};*/
|
||||
let mut fade = 1.0;
|
||||
if self.velocity > 7.0 {
|
||||
fade = 0.7
|
||||
};
|
||||
|
||||
// BG
|
||||
Rectangle::fill_with([824.0, 976.0], color::TRANSPARENT)
|
||||
@ -96,24 +107,29 @@ impl<'a> Widget for Map<'a> {
|
||||
Image::new(self.imgs.map_frame_l)
|
||||
.top_left_with_margins_on(state.ids.map_bg, 0.0, 0.0)
|
||||
.w_h(412.0, 488.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
|
||||
.set(state.ids.map_frame_l, ui);
|
||||
Image::new(self.imgs.map_frame_r)
|
||||
.right_from(state.ids.map_frame_l, 0.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
|
||||
.w_h(412.0, 488.0)
|
||||
.set(state.ids.map_frame_r, ui);
|
||||
Image::new(self.imgs.map_frame_br)
|
||||
.down_from(state.ids.map_frame_r, 0.0)
|
||||
.w_h(412.0, 488.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
|
||||
.set(state.ids.map_frame_br, ui);
|
||||
Image::new(self.imgs.map_frame_bl)
|
||||
.down_from(state.ids.map_frame_l, 0.0)
|
||||
.w_h(412.0, 488.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
|
||||
.set(state.ids.map_frame_bl, ui);
|
||||
|
||||
// Icon
|
||||
Image::new(self.imgs.map_icon)
|
||||
.w_h(224.0 / 3.0, 224.0 / 3.0)
|
||||
.top_left_with_margins_on(state.ids.map_frame, -10.0, -10.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
|
||||
.set(state.ids.map_icon, ui);
|
||||
|
||||
// X-Button
|
||||
@ -121,6 +137,7 @@ impl<'a> Widget for Map<'a> {
|
||||
.w_h(28.0, 28.0)
|
||||
.hover_image(self.imgs.close_button_hover)
|
||||
.press_image(self.imgs.close_button_press)
|
||||
.color(Color::Rgba(1.0, 1.0, 1.0, fade - 0.5))
|
||||
.top_right_with_margins_on(state.ids.map_frame_r, 0.0, 0.0)
|
||||
.set(state.ids.map_close, ui)
|
||||
.was_clicked()
|
||||
@ -144,9 +161,11 @@ impl<'a> Widget for Map<'a> {
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.location_name, ui),
|
||||
}
|
||||
|
||||
// Map Image
|
||||
Image::new(/*self.world_map*/ self.imgs.map_placeholder)
|
||||
.middle_of(state.ids.map_bg)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
|
||||
.w_h(700.0, 700.0)
|
||||
.parent(state.ids.map_bg)
|
||||
.set(state.ids.grid, ui);
|
||||
@ -183,7 +202,7 @@ impl<'a> Widget for Map<'a> {
|
||||
34.0 * indic_scale
|
||||
},
|
||||
)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade + 0.2)))
|
||||
.floating(true)
|
||||
.parent(ui.window)
|
||||
.set(state.ids.indicator, ui);
|
||||
|
@ -13,8 +13,7 @@ mod skillbar;
|
||||
mod social;
|
||||
mod spell;
|
||||
|
||||
use crate::hud::img_ids::ImgsRot;
|
||||
//use rand::Rng;
|
||||
use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot};
|
||||
pub use settings_window::ScaleChange;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -36,6 +35,7 @@ use social::{Social, SocialTab};
|
||||
use spell::Spell;
|
||||
|
||||
use crate::{
|
||||
ecs::comp as vcomp,
|
||||
render::{AaMode, Consts, Globals, Renderer},
|
||||
scene::camera::Camera,
|
||||
//settings::ControlSettings,
|
||||
@ -60,6 +60,7 @@ use crate::{discord, discord::DiscordUpdate};
|
||||
|
||||
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_COLOR_GREY: Color = Color::Rgba(1.0, 1.0, 1.0, 0.5);
|
||||
const MENU_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 0.4);
|
||||
//const TEXT_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
|
||||
const TEXT_COLOR_3: Color = Color::Rgba(1.0, 1.0, 1.0, 0.1);
|
||||
@ -88,6 +89,7 @@ widget_ids! {
|
||||
|
||||
// Character Names
|
||||
name_tags[],
|
||||
name_tags_bgs[],
|
||||
levels[],
|
||||
levels_skull[],
|
||||
// Health Bars
|
||||
@ -96,6 +98,18 @@ widget_ids! {
|
||||
health_bar_fronts[],
|
||||
health_bar_backs[],
|
||||
|
||||
// SCT
|
||||
player_scts[],
|
||||
player_sct_bgs[],
|
||||
sct_exp_bgs[],
|
||||
sct_exps[],
|
||||
sct_lvl_bg,
|
||||
sct_lvl,
|
||||
hurt_bg,
|
||||
death_bg,
|
||||
sct_bgs[],
|
||||
scts[],
|
||||
|
||||
// Intro Text
|
||||
intro_bg,
|
||||
intro_text,
|
||||
@ -128,6 +142,7 @@ widget_ids! {
|
||||
// Help
|
||||
help,
|
||||
help_info,
|
||||
debug_info,
|
||||
|
||||
// Window Frames
|
||||
window_frame_0,
|
||||
@ -200,6 +215,10 @@ pub enum Event {
|
||||
Intro(Intro),
|
||||
ToggleBarNumbers(BarNumbers),
|
||||
ToggleShortcutNumbers(ShortcutNumbers),
|
||||
Sct(bool),
|
||||
SctPlayerBatch(bool),
|
||||
SctDamageBatch(bool),
|
||||
ToggleDebug(bool),
|
||||
UiScale(ScaleChange),
|
||||
CharacterSelection,
|
||||
UseInventorySlot(usize),
|
||||
@ -265,7 +284,6 @@ pub struct Show {
|
||||
ingame: bool,
|
||||
settings_tab: SettingsTab,
|
||||
social_tab: SocialTab,
|
||||
|
||||
want_grab: bool,
|
||||
}
|
||||
impl Show {
|
||||
@ -424,6 +442,7 @@ pub struct Hud {
|
||||
force_chat_cursor: Option<Index>,
|
||||
pulse: f32,
|
||||
zoom: f32,
|
||||
velocity: f32,
|
||||
}
|
||||
|
||||
impl Hud {
|
||||
@ -461,7 +480,7 @@ impl Hud {
|
||||
show: Show {
|
||||
help: false,
|
||||
intro: true,
|
||||
debug: true,
|
||||
debug: false,
|
||||
bag: false,
|
||||
esc_menu: false,
|
||||
open_windows: Windows::None,
|
||||
@ -485,6 +504,7 @@ impl Hud {
|
||||
force_chat_cursor: None,
|
||||
pulse: 0.0,
|
||||
zoom: 1.0,
|
||||
velocity: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,15 +519,59 @@ impl Hud {
|
||||
let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets();
|
||||
// pulse time for pulsating elements
|
||||
self.pulse = self.pulse + dt.as_secs_f32();
|
||||
self.velocity = match debug_info.velocity {
|
||||
Some(velocity) => velocity.0.magnitude(),
|
||||
None => 0.0,
|
||||
};
|
||||
|
||||
let version = format!(
|
||||
"{}-{}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
common::util::GIT_VERSION.to_string()
|
||||
);
|
||||
|
||||
if self.show.ingame {
|
||||
let ecs = client.state().ecs();
|
||||
let pos = ecs.read_storage::<comp::Pos>();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>();
|
||||
let interpolated = ecs.read_storage::<vcomp::Interpolated>();
|
||||
let players = ecs.read_storage::<comp::Player>();
|
||||
let scales = ecs.read_storage::<comp::Scale>();
|
||||
let entities = ecs.entities();
|
||||
let me = client.entity();
|
||||
let view_distance = client.view_distance().unwrap_or(1);
|
||||
let own_level = stats
|
||||
.get(client.entity())
|
||||
.map_or(0, |stats| stats.level.level());
|
||||
|
||||
if let Some(stats) = stats.get(me) {
|
||||
// Hurt Frame
|
||||
let hp_percentage =
|
||||
stats.health.current() as f32 / stats.health.maximum() as f32 * 100.0;
|
||||
if hp_percentage < 10.0 && !stats.is_dead {
|
||||
let hurt_fade =
|
||||
(self.pulse * (10.0 - hp_percentage as f32) * 0.1/*speed factor*/).sin()
|
||||
* 0.5
|
||||
+ 0.6; //Animation timer
|
||||
Image::new(self.imgs.hurt_bg)
|
||||
.wh_of(ui_widgets.window)
|
||||
.middle_of(ui_widgets.window)
|
||||
.graphics_for(ui_widgets.window)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, hurt_fade)))
|
||||
.set(self.ids.hurt_bg, ui_widgets);
|
||||
}
|
||||
// Death Frame
|
||||
if stats.is_dead {
|
||||
Image::new(self.imgs.death_bg)
|
||||
.wh_of(ui_widgets.window)
|
||||
.middle_of(ui_widgets.window)
|
||||
.graphics_for(ui_widgets.window)
|
||||
.color(Some(Color::Rgba(0.0, 0.0, 0.0, 1.0)))
|
||||
.set(self.ids.death_bg, ui_widgets);
|
||||
}
|
||||
// Crosshair
|
||||
if !self.show.help {
|
||||
if !self.show.help && !stats.is_dead {
|
||||
Image::new(
|
||||
// TODO: Do we want to match on this every frame?
|
||||
match global_state.settings.gameplay.crosshair_type {
|
||||
@ -531,20 +595,12 @@ impl Hud {
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.6)))
|
||||
.set(self.ids.crosshair_inner, ui_widgets);
|
||||
}
|
||||
}
|
||||
|
||||
// Nametags and healthbars
|
||||
let ecs = client.state().ecs();
|
||||
let pos = ecs.read_storage::<comp::Pos>();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let players = ecs.read_storage::<comp::Player>();
|
||||
let scales = ecs.read_storage::<comp::Scale>();
|
||||
let entities = ecs.entities();
|
||||
let me = client.entity();
|
||||
let view_distance = client.view_distance().unwrap_or(1);
|
||||
let own_level = stats
|
||||
.get(client.entity())
|
||||
.map_or(0, |stats| stats.level.level());
|
||||
|
||||
// Max amount the sct font size increases when "flashing"
|
||||
const FLASH_MAX: f32 = 25.0;
|
||||
// Get player position.
|
||||
let player_pos = client
|
||||
.state()
|
||||
@ -553,21 +609,31 @@ impl Hud {
|
||||
.get(client.entity())
|
||||
.map_or(Vec3::zero(), |pos| pos.0);
|
||||
let mut name_id_walker = self.ids.name_tags.walk();
|
||||
let mut name_id_bg_walker = self.ids.name_tags_bgs.walk();
|
||||
let mut level_id_walker = self.ids.levels.walk();
|
||||
let mut level_skull_id_walker = self.ids.levels_skull.walk();
|
||||
let mut health_id_walker = self.ids.health_bars.walk();
|
||||
let mut mana_id_walker = self.ids.mana_bars.walk();
|
||||
let mut health_back_id_walker = self.ids.health_bar_backs.walk();
|
||||
let mut health_front_id_walker = self.ids.health_bar_fronts.walk();
|
||||
let mut sct_bg_id_walker = self.ids.sct_bgs.walk();
|
||||
let mut sct_id_walker = self.ids.scts.walk();
|
||||
// Render Health Bars
|
||||
for (_entity, pos, stats, scale) in (&entities, &pos, &stats, scales.maybe())
|
||||
for (pos, stats, scale, hp_floater_list) in (
|
||||
&entities,
|
||||
&pos,
|
||||
interpolated.maybe(),
|
||||
&stats,
|
||||
scales.maybe(),
|
||||
hp_floater_lists.maybe(), // Potentially move this to its own loop
|
||||
)
|
||||
.join()
|
||||
.filter(|(entity, _, stats, _)| {
|
||||
.filter(|(entity, _, _, stats, _, _)| {
|
||||
*entity != me && !stats.is_dead
|
||||
//&& stats.health.current() != stats.health.maximum()
|
||||
})
|
||||
// Don't process health bars outside the vd (visibility further limited by ui backend)
|
||||
.filter(|(_, pos, _, _)| {
|
||||
.filter(|(_, pos, _, _, _, _)| {
|
||||
Vec2::from(pos.0 - player_pos)
|
||||
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
|
||||
d.abs() as f32 / sz as f32
|
||||
@ -575,9 +641,15 @@ impl Hud {
|
||||
.magnitude()
|
||||
< view_distance as f32
|
||||
})
|
||||
.map(|(_, pos, interpolated, stats, scale, f)| {
|
||||
(
|
||||
interpolated.map_or(pos.0, |i| i.pos),
|
||||
stats,
|
||||
scale.map_or(1.0, |s| s.0),
|
||||
f,
|
||||
)
|
||||
})
|
||||
{
|
||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
|
||||
let back_id = health_back_id_walker.next(
|
||||
&mut self.ids.health_bar_backs,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
@ -596,20 +668,22 @@ impl Hud {
|
||||
);
|
||||
let hp_percentage =
|
||||
stats.health.current() as f64 / stats.health.maximum() as f64 * 100.0;
|
||||
let energy_percentage =
|
||||
stats.energy.current() as f64 / stats.energy.maximum() as f64 * 100.0;
|
||||
let hp_ani = (self.pulse * 4.0/*speed factor*/).cos() * 0.5 + 1.0; //Animation timer
|
||||
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
|
||||
|
||||
// Background
|
||||
Rectangle::fill_with([82.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5))
|
||||
.x_y(0.0, -25.0)
|
||||
.position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.resolution(100.0)
|
||||
.set(back_id, ui_widgets);
|
||||
|
||||
// % HP Filling
|
||||
Image::new(self.imgs.bar_content)
|
||||
Image::new(self.imgs.enemy_bar)
|
||||
.w_h(72.9 * (hp_percentage / 100.0), 5.9)
|
||||
.x_y(4.5, -24.0)
|
||||
.x_y(4.5 + (hp_percentage / 100.0 * 36.45) - 36.45, -24.0)
|
||||
.color(Some(if hp_percentage <= 25.0 {
|
||||
crit_hp_color
|
||||
} else if hp_percentage <= 50.0 {
|
||||
@ -617,7 +691,7 @@ impl Hud {
|
||||
} else {
|
||||
HP_COLOR
|
||||
}))
|
||||
.position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.resolution(100.0)
|
||||
.set(health_bar_id, ui_widgets);
|
||||
// % Mana Filling
|
||||
@ -628,8 +702,8 @@ impl Hud {
|
||||
],
|
||||
MANA_COLOR,
|
||||
)
|
||||
.x_y(4.5, -28.0)
|
||||
.position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.x_y(4.5 + (energy_percentage / 100.0 * 36.5) - 36.45, -28.0)
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.resolution(100.0)
|
||||
.set(mana_bar_id, ui_widgets);
|
||||
|
||||
@ -638,18 +712,395 @@ impl Hud {
|
||||
.w_h(84.0, 10.0)
|
||||
.x_y(0.0, -25.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99)))
|
||||
.position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.resolution(100.0)
|
||||
.set(front_id, ui_widgets);
|
||||
|
||||
// Enemy SCT
|
||||
if let Some(HpFloaterList { floaters, .. }) = hp_floater_list
|
||||
.filter(|fl| !fl.floaters.is_empty() && global_state.settings.gameplay.sct)
|
||||
{
|
||||
// Colors
|
||||
const WHITE: Rgb<f32> = Rgb::new(1.0, 0.9, 0.8);
|
||||
const LIGHT_OR: Rgb<f32> = Rgb::new(1.0, 0.925, 0.749);
|
||||
const LIGHT_MED_OR: Rgb<f32> = Rgb::new(1.0, 0.85, 0.498);
|
||||
const MED_OR: Rgb<f32> = Rgb::new(1.0, 0.776, 0.247);
|
||||
const DARK_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.7, 0.0);
|
||||
const RED_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.349, 0.0);
|
||||
const DAMAGE_COLORS: [Rgb<f32>; 6] = [
|
||||
WHITE,
|
||||
LIGHT_OR,
|
||||
LIGHT_MED_OR,
|
||||
MED_OR,
|
||||
DARK_ORANGE,
|
||||
RED_ORANGE,
|
||||
];
|
||||
// Largest value that select the first color is 40, then it shifts colors
|
||||
// every 5
|
||||
let font_col = |font_size: u32| {
|
||||
DAMAGE_COLORS[(font_size.saturating_sub(36) / 5).min(5) as usize]
|
||||
};
|
||||
|
||||
if global_state.settings.gameplay.sct_damage_batch {
|
||||
let number_speed = 50.0; // Damage number speed
|
||||
let sct_bg_id = sct_bg_id_walker
|
||||
.next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator());
|
||||
let sct_id = sct_id_walker
|
||||
.next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator());
|
||||
// Calculate total change
|
||||
// Ignores healing
|
||||
let hp_damage = floaters.iter().fold(0, |acc, f| {
|
||||
if f.hp_change < 0 {
|
||||
acc + f.hp_change
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32;
|
||||
let timer = floaters
|
||||
.last()
|
||||
.expect("There must be at least one floater")
|
||||
.timer;
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size = 30
|
||||
+ (max_hp_frac * 30.0) as u32
|
||||
+ if timer < 0.1 {
|
||||
(FLASH_MAX * (1.0 - timer / 0.1)) as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let font_col = font_col(font_size);
|
||||
// Timer sets the widget offset
|
||||
let y = (timer as f64 / crate::ecs::sys::floater::HP_SHOWTIME as f64
|
||||
* number_speed)
|
||||
+ 30.0;
|
||||
// Timer sets text transparency
|
||||
let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - timer) * 0.25) + 0.2;
|
||||
|
||||
Text::new(&format!("{}", (hp_damage).abs()))
|
||||
.font_size(font_size)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
|
||||
.x_y(0.0, y - 3.0)
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8))
|
||||
.fixed_scale()
|
||||
.resolution(100.0)
|
||||
.set(sct_bg_id, ui_widgets);
|
||||
Text::new(&format!("{}", hp_damage.abs()))
|
||||
.font_size(font_size)
|
||||
.x_y(0.0, y)
|
||||
.color(if hp_damage < 0 {
|
||||
Color::Rgba(font_col.r, font_col.g, font_col.b, fade)
|
||||
} else {
|
||||
Color::Rgba(0.1, 1.0, 0.1, fade)
|
||||
})
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8))
|
||||
.fixed_scale()
|
||||
.resolution(100.0)
|
||||
.set(sct_id, ui_widgets);
|
||||
} else {
|
||||
for floater in floaters {
|
||||
let number_speed = 250.0; // Single Numbers Speed
|
||||
let sct_bg_id = sct_bg_id_walker
|
||||
.next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator());
|
||||
let sct_id = sct_id_walker
|
||||
.next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator());
|
||||
// Calculate total change
|
||||
let max_hp_frac =
|
||||
floater.hp_change.abs() as f32 / stats.health.maximum() as f32;
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size = 30
|
||||
+ (max_hp_frac * 30.0) as u32
|
||||
+ if floater.timer < 0.1 {
|
||||
(FLASH_MAX * (1.0 - floater.timer / 0.1)) as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let font_col = font_col(font_size);
|
||||
// Timer sets the widget offset
|
||||
let y = (floater.timer as f64
|
||||
/ crate::ecs::sys::floater::HP_SHOWTIME as f64
|
||||
* number_speed)
|
||||
+ 30.0;
|
||||
// Timer sets text transparency
|
||||
let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - floater.timer)
|
||||
* 0.25)
|
||||
+ 0.2;
|
||||
|
||||
Text::new(&format!("{}", (floater.hp_change).abs()))
|
||||
.font_size(font_size)
|
||||
.color(if floater.hp_change < 0 {
|
||||
Color::Rgba(0.0, 0.0, 0.0, fade)
|
||||
} else {
|
||||
Color::Rgba(0.1, 1.0, 0.1, 0.0)
|
||||
})
|
||||
.x_y(0.0, y - 3.0)
|
||||
.position_ingame(
|
||||
pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8),
|
||||
)
|
||||
.fixed_scale()
|
||||
.resolution(100.0)
|
||||
.set(sct_bg_id, ui_widgets);
|
||||
Text::new(&format!("{}", (floater.hp_change).abs()))
|
||||
.font_size(font_size)
|
||||
.x_y(0.0, y)
|
||||
.color(if floater.hp_change < 0 {
|
||||
Color::Rgba(font_col.r, font_col.g, font_col.b, fade)
|
||||
} else {
|
||||
Color::Rgba(0.1, 1.0, 0.1, 0.0)
|
||||
})
|
||||
.position_ingame(
|
||||
pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8),
|
||||
)
|
||||
.fixed_scale()
|
||||
.resolution(100.0)
|
||||
.set(sct_id, ui_widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if global_state.settings.gameplay.sct {
|
||||
// Render Player SCT numbers
|
||||
let mut player_sct_bg_id_walker = self.ids.player_sct_bgs.walk();
|
||||
let mut player_sct_id_walker = self.ids.player_scts.walk();
|
||||
if let (Some(HpFloaterList { floaters, .. }), Some(stats)) = (
|
||||
hp_floater_lists
|
||||
.get(me)
|
||||
.filter(|fl| !fl.floaters.is_empty()),
|
||||
stats.get(me),
|
||||
) {
|
||||
if global_state.settings.gameplay.sct_player_batch {
|
||||
let number_speed = 100.0; // Player Batched Numbers Speed
|
||||
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(),
|
||||
);
|
||||
// Calculate total change
|
||||
// Ignores healing
|
||||
let hp_damage = floaters.iter().fold(0, |acc, f| f.hp_change.min(0) + acc);
|
||||
let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32;
|
||||
let timer = floaters
|
||||
.last()
|
||||
.expect("There must be at least one floater")
|
||||
.timer;
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size = 30
|
||||
+ (max_hp_frac * 30.0) as u32
|
||||
+ if timer < 0.1 {
|
||||
(FLASH_MAX * (1.0 - timer / 0.1)) as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// Timer sets the widget offset
|
||||
let y = timer as f64 * number_speed * -1.0;
|
||||
// Timer sets text transparency
|
||||
let hp_fade =
|
||||
((crate::ecs::sys::floater::MY_HP_SHOWTIME - timer) * 0.25) + 0.2;
|
||||
Text::new(&format!("{}", (hp_damage).abs()))
|
||||
.font_size(font_size)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, hp_fade))
|
||||
.mid_bottom_with_margin_on(ui_widgets.window, 297.0 + y)
|
||||
.set(player_sct_bg_id, ui_widgets);
|
||||
Text::new(&format!("{}", (hp_damage).abs()))
|
||||
.font_size(font_size)
|
||||
.color(if hp_damage < 0 {
|
||||
Color::Rgba(1.0, 0.1, 0.0, hp_fade)
|
||||
} else {
|
||||
Color::Rgba(0.1, 1.0, 0.1, 0.0)
|
||||
})
|
||||
.mid_bottom_with_margin_on(ui_widgets.window, 300.0 + y)
|
||||
.set(player_sct_id, ui_widgets);
|
||||
};
|
||||
for floater in floaters {
|
||||
// Healing always single numbers so just skip damage when in batch mode
|
||||
|
||||
if global_state.settings.gameplay.sct_player_batch && floater.hp_change < 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let number_speed = 50.0; // Player Heal Speed
|
||||
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(),
|
||||
);
|
||||
let max_hp_frac =
|
||||
floater.hp_change.abs() as f32 / stats.health.maximum() as f32;
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size = 30
|
||||
+ (max_hp_frac * 30.0) as u32
|
||||
+ if floater.timer < 0.1 {
|
||||
(FLASH_MAX * (1.0 - floater.timer / 0.1)) as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// Timer sets the widget offset
|
||||
let y = if floater.hp_change < 0 {
|
||||
floater.timer as f64
|
||||
* number_speed
|
||||
* floater.hp_change.signum() as f64
|
||||
//* -1.0
|
||||
+ 300.0
|
||||
- ui_widgets.win_h * 0.5
|
||||
} else {
|
||||
floater.timer as f64
|
||||
* number_speed
|
||||
* floater.hp_change.signum() as f64
|
||||
* -1.0
|
||||
+ 300.0
|
||||
- ui_widgets.win_h * 0.5
|
||||
};
|
||||
// Healing is offset randomly
|
||||
let x = if floater.hp_change < 0 {
|
||||
0.0
|
||||
} else {
|
||||
(floater.rand as f64 - 0.5) * 0.2 * ui_widgets.win_w
|
||||
};
|
||||
// Timer sets text transparency
|
||||
let hp_fade = ((crate::ecs::sys::floater::MY_HP_SHOWTIME - floater.timer)
|
||||
* 0.25)
|
||||
+ 0.2;
|
||||
Text::new(&format!("{}", (floater.hp_change).abs()))
|
||||
.font_size(font_size)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, hp_fade))
|
||||
.x_y(x, y - 3.0)
|
||||
.set(player_sct_bg_id, ui_widgets);
|
||||
Text::new(&format!("{}", (floater.hp_change).abs()))
|
||||
.font_size(font_size)
|
||||
.color(if floater.hp_change < 0 {
|
||||
Color::Rgba(1.0, 0.1, 0.0, hp_fade)
|
||||
} else {
|
||||
Color::Rgba(0.1, 1.0, 0.1, hp_fade)
|
||||
})
|
||||
.x_y(x, y)
|
||||
.set(player_sct_id, ui_widgets);
|
||||
}
|
||||
}
|
||||
// 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 * 50.0) as u32
|
||||
+ if timer < 0.1 {
|
||||
(FLASH_MAX * (1.0 - timer / 0.1)) 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)
|
||||
.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)
|
||||
.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
|
||||
* 50.0) as u32
|
||||
+ if floater.timer < 0.1 {
|
||||
(FLASH_MAX * (1.0 - floater.timer / 0.1)) 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)
|
||||
.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)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render Name Tags
|
||||
for (pos, name, level, scale) in
|
||||
(&entities, &pos, &stats, players.maybe(), scales.maybe())
|
||||
for (pos, name, level, scale) in (
|
||||
&entities,
|
||||
&pos,
|
||||
interpolated.maybe(),
|
||||
&stats,
|
||||
players.maybe(),
|
||||
scales.maybe(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(entity, _, stats, _, _)| *entity != me && !stats.is_dead)
|
||||
.filter(|(entity, _, _, stats, _, _)| *entity != me && !stats.is_dead)
|
||||
// Don't process nametags outside the vd (visibility further limited by ui backend)
|
||||
.filter(|(_, pos, _, _, _)| {
|
||||
.filter(|(_, pos, _, _, _, _)| {
|
||||
Vec2::from(pos.0 - player_pos)
|
||||
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
|
||||
d.abs() as f32 / sz as f32
|
||||
@ -657,7 +1108,7 @@ impl Hud {
|
||||
.magnitude()
|
||||
< view_distance as f32
|
||||
})
|
||||
.map(|(_, pos, stats, player, scale)| {
|
||||
.map(|(_, pos, interpolated, stats, player, scale)| {
|
||||
// TODO: This is temporary
|
||||
// If the player used the default character name display their name instead
|
||||
let name = if stats.name == "Character Name" {
|
||||
@ -665,15 +1116,22 @@ impl Hud {
|
||||
} else {
|
||||
&stats.name
|
||||
};
|
||||
(pos.0, name, stats.level, scale)
|
||||
(
|
||||
interpolated.map_or(pos.0, |i| i.pos),
|
||||
format!("{}", name),
|
||||
stats.level,
|
||||
scale.map_or(1.0, |s| s.0),
|
||||
)
|
||||
})
|
||||
{
|
||||
let name = format!("{}", name);
|
||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||
let name_id = name_id_walker.next(
|
||||
&mut self.ids.name_tags,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
let name_bg_id = name_id_bg_walker.next(
|
||||
&mut self.ids.name_tags_bgs,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
let level_id = level_id_walker
|
||||
.next(&mut self.ids.levels, &mut ui_widgets.widget_id_generator());
|
||||
let level_skull_id = level_skull_id_walker.next(
|
||||
@ -682,6 +1140,13 @@ impl Hud {
|
||||
);
|
||||
|
||||
// Name
|
||||
Text::new(&name)
|
||||
.font_size(20)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.x_y(-1.0, -1.0)
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.resolution(100.0)
|
||||
.set(name_bg_id, ui_widgets);
|
||||
Text::new(&name)
|
||||
.font_size(20)
|
||||
.color(Color::Rgba(0.61, 0.61, 0.89, 1.0))
|
||||
@ -726,15 +1191,16 @@ impl Hud {
|
||||
} else {
|
||||
self.imgs.skull
|
||||
})
|
||||
.w_h(30.0, 30.0)
|
||||
.x_y(0.0, 24.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
|
||||
.w_h(18.0, 18.0)
|
||||
.x_y(-39.0, -25.0)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
|
||||
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
|
||||
.resolution(100.0)
|
||||
.set(level_skull_id, ui_widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Introduction Text
|
||||
let intro_text: &'static str =
|
||||
"Welcome to the Veloren Alpha!\n\
|
||||
@ -906,7 +1372,7 @@ impl Hud {
|
||||
}
|
||||
|
||||
// Display debug window.
|
||||
if self.show.debug {
|
||||
if global_state.settings.gameplay.toggle_debug {
|
||||
// Alpha Version
|
||||
Text::new(&version)
|
||||
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
|
||||
@ -1018,12 +1484,46 @@ impl Hud {
|
||||
.set(self.ids.num_figures, ui_widgets);
|
||||
|
||||
// Help Window
|
||||
Text::new("Press 'F1' to show Keybindings")
|
||||
Text::new(&format!(
|
||||
"Press {:?} to show keybindings",
|
||||
global_state.settings.controls.help
|
||||
))
|
||||
.color(TEXT_COLOR)
|
||||
.down_from(self.ids.num_figures, 5.0)
|
||||
.font_id(self.fonts.cyri)
|
||||
.font_size(14)
|
||||
.set(self.ids.help_info, ui_widgets);
|
||||
// Info about Debug Shortcut
|
||||
Text::new(&format!(
|
||||
"Press {:?} to toggle debug info",
|
||||
global_state.settings.controls.toggle_debug
|
||||
))
|
||||
.color(TEXT_COLOR)
|
||||
.down_from(self.ids.help_info, 5.0)
|
||||
.font_id(self.fonts.cyri)
|
||||
.font_size(14)
|
||||
.set(self.ids.debug_info, ui_widgets);
|
||||
} else {
|
||||
// Help Window
|
||||
Text::new(&format!(
|
||||
"Press {:?} to show keybindings",
|
||||
global_state.settings.controls.help
|
||||
))
|
||||
.color(TEXT_COLOR)
|
||||
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
|
||||
.font_id(self.fonts.cyri)
|
||||
.font_size(16)
|
||||
.set(self.ids.help_info, ui_widgets);
|
||||
// Info about Debug Shortcut
|
||||
Text::new(&format!(
|
||||
"Press {:?} to toggle debug info",
|
||||
global_state.settings.controls.toggle_debug
|
||||
))
|
||||
.color(TEXT_COLOR)
|
||||
.down_from(self.ids.help_info, 5.0)
|
||||
.font_id(self.fonts.cyri)
|
||||
.font_size(12)
|
||||
.set(self.ids.debug_info, ui_widgets);
|
||||
}
|
||||
|
||||
// Add Bag-Space Button.
|
||||
@ -1185,6 +1685,15 @@ impl Hud {
|
||||
.set(self.ids.settings_window, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
settings_window::Event::Sct(sct) => {
|
||||
events.push(Event::Sct(sct));
|
||||
}
|
||||
settings_window::Event::SctPlayerBatch(sct_player_batch) => {
|
||||
events.push(Event::SctPlayerBatch(sct_player_batch));
|
||||
}
|
||||
settings_window::Event::SctDamageBatch(sct_damage_batch) => {
|
||||
events.push(Event::SctDamageBatch(sct_damage_batch));
|
||||
}
|
||||
settings_window::Event::ToggleHelp => self.show.help = !self.show.help,
|
||||
settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug,
|
||||
settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab),
|
||||
@ -1319,6 +1828,7 @@ impl Hud {
|
||||
self.world_map,
|
||||
&self.fonts,
|
||||
self.pulse,
|
||||
self.velocity,
|
||||
)
|
||||
.set(self.ids.map, ui_widgets)
|
||||
{
|
||||
@ -1471,7 +1981,8 @@ impl Hud {
|
||||
true
|
||||
}
|
||||
GameInput::ToggleDebug => {
|
||||
self.show.debug = !self.show.debug;
|
||||
global_state.settings.gameplay.toggle_debug =
|
||||
!global_state.settings.gameplay.toggle_debug;
|
||||
true
|
||||
}
|
||||
GameInput::ToggleIngameUi => {
|
||||
|
@ -18,6 +18,7 @@ const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
settings_content,
|
||||
settings_content_r,
|
||||
settings_icon,
|
||||
settings_button_mo,
|
||||
settings_close,
|
||||
@ -109,7 +110,24 @@ widget_ids! {
|
||||
placeholder,
|
||||
chat_transp_title,
|
||||
chat_transp_text,
|
||||
chat_transp_slider
|
||||
chat_transp_slider,
|
||||
sct_title,
|
||||
sct_show_text,
|
||||
sct_show_radio,
|
||||
sct_single_dmg_text,
|
||||
sct_single_dmg_radio,
|
||||
sct_show_batch_text,
|
||||
sct_show_batch_radio,
|
||||
sct_batched_dmg_text,
|
||||
sct_batched_dmg_radio,
|
||||
sct_inc_dmg_text,
|
||||
sct_inc_dmg_radio,
|
||||
sct_batch_inc_text,
|
||||
sct_batch_inc_radio,
|
||||
sct_num_dur_text,
|
||||
sct_num_dur_slider,
|
||||
sct_num_dur_value,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +196,9 @@ pub enum Event {
|
||||
CrosshairType(CrosshairType),
|
||||
UiScale(ScaleChange),
|
||||
ChatTransp(f32),
|
||||
Sct(bool),
|
||||
SctPlayerBatch(bool),
|
||||
SctDamageBatch(bool),
|
||||
}
|
||||
|
||||
pub enum ScaleChange {
|
||||
@ -229,6 +250,10 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
.scroll_kids()
|
||||
.scroll_kids_vertically()
|
||||
.set(state.ids.settings_content, ui);
|
||||
Rectangle::fill_with([198.0 * 4.0 * 0.5, 97.0 * 4.0], color::TRANSPARENT)
|
||||
.top_right_with_margins_on(state.ids.settings_content, 0.0, 0.0)
|
||||
.parent(state.ids.settings_content)
|
||||
.set(state.ids.settings_content_r, ui);
|
||||
Scrollbar::y_axis(state.ids.settings_content)
|
||||
.thickness(5.0)
|
||||
.rgba(0.33, 0.33, 0.33, 1.0)
|
||||
@ -279,7 +304,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
events.push(Event::ChangeTab(SettingsTab::Interface));
|
||||
}
|
||||
|
||||
// Contents
|
||||
// Contents Left Side
|
||||
if let SettingsTab::Interface = self.show.settings_tab {
|
||||
let crosshair_transp = self.global_state.settings.gameplay.crosshair_transp;
|
||||
let crosshair_type = self.global_state.settings.gameplay.crosshair_type;
|
||||
@ -309,7 +334,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
events.push(Event::ToggleHelp);
|
||||
}
|
||||
|
||||
Text::new("Show Help Window")
|
||||
Text::new("Help Window")
|
||||
.right_from(state.ids.button_help, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
@ -333,7 +358,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
events.push(Event::ToggleDebug);
|
||||
}
|
||||
|
||||
Text::new("Show Debug Info")
|
||||
Text::new("Debug Info")
|
||||
.right_from(state.ids.debug_button, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
@ -360,7 +385,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
Intro::Never => events.push(Event::Intro(Intro::Show)),
|
||||
}
|
||||
};
|
||||
Text::new("Show Tips on Startup")
|
||||
Text::new("Tips on Startup")
|
||||
.right_from(state.ids.tips_button, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
@ -683,7 +708,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
XpBar::OnGain => events.push(Event::ToggleXpBar(XpBar::Always)),
|
||||
}
|
||||
}
|
||||
Text::new("Always show Experience Bar")
|
||||
Text::new("Toggle Experience Bar")
|
||||
.right_from(state.ids.show_xpbar_button, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
@ -717,7 +742,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Text::new("Always show Shortcuts")
|
||||
Text::new("Toggle Shortcuts")
|
||||
.right_from(state.ids.show_shortcuts_button, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
@ -725,10 +750,151 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_shortcuts_text, ui);
|
||||
|
||||
Rectangle::fill_with([60.0 * 4.0, 1.0 * 4.0], color::TRANSPARENT)
|
||||
.down_from(state.ids.show_shortcuts_text, 30.0)
|
||||
.set(state.ids.placeholder, ui);
|
||||
|
||||
// Content Right Side
|
||||
|
||||
/*Scrolling Combat text
|
||||
|
||||
O Show Damage Numbers
|
||||
O Show single Damage Numbers
|
||||
O Show batched dealt Damage
|
||||
O Show incoming Damage
|
||||
O Batch incoming Numbers
|
||||
|
||||
Number Display Duration: 1s ----I----5s
|
||||
*/
|
||||
// SCT/ Scrolling Combat Text
|
||||
Text::new("Scrolling Combat Text")
|
||||
.top_left_with_margins_on(state.ids.settings_content_r, 5.0, 5.0)
|
||||
.font_size(18)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.sct_title, ui);
|
||||
// Generally toggle the SCT
|
||||
let show_sct = ToggleButton::new(
|
||||
self.global_state.settings.gameplay.sct,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.w_h(18.0, 18.0)
|
||||
.down_from(state.ids.sct_title, 20.0)
|
||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.set(state.ids.sct_show_radio, ui);
|
||||
|
||||
if self.global_state.settings.gameplay.sct != show_sct {
|
||||
events.push(Event::Sct(!self.global_state.settings.gameplay.sct))
|
||||
}
|
||||
Text::new("Scrolling Combat Text")
|
||||
.right_from(state.ids.sct_show_radio, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.graphics_for(state.ids.sct_show_radio)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.sct_show_text, ui);
|
||||
if self.global_state.settings.gameplay.sct {
|
||||
// Toggle single damage numbers
|
||||
let show_sct_damage_batch = !ToggleButton::new(
|
||||
!self.global_state.settings.gameplay.sct_damage_batch,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.w_h(18.0, 18.0)
|
||||
.down_from(state.ids.sct_show_text, 8.0)
|
||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.set(state.ids.sct_single_dmg_radio, ui);
|
||||
|
||||
Text::new("Single Damage Numbers")
|
||||
.right_from(state.ids.sct_single_dmg_radio, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.graphics_for(state.ids.sct_single_dmg_radio)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.sct_single_dmg_text, ui);
|
||||
// Toggle Batched Damage
|
||||
let show_sct_damage_batch = ToggleButton::new(
|
||||
show_sct_damage_batch,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.w_h(18.0, 18.0)
|
||||
.down_from(state.ids.sct_single_dmg_radio, 8.0)
|
||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.set(state.ids.sct_show_batch_radio, ui);
|
||||
|
||||
if self.global_state.settings.gameplay.sct_damage_batch != show_sct_damage_batch {
|
||||
events.push(Event::SctDamageBatch(
|
||||
!self.global_state.settings.gameplay.sct_damage_batch,
|
||||
))
|
||||
}
|
||||
Text::new("Cumulated Damage")
|
||||
.right_from(state.ids.sct_show_batch_radio, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.graphics_for(state.ids.sct_batched_dmg_radio)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.sct_show_batch_text, ui);
|
||||
// Toggle Incoming Damage
|
||||
let show_sct_player_batch = !ToggleButton::new(
|
||||
!self.global_state.settings.gameplay.sct_player_batch,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.w_h(18.0, 18.0)
|
||||
.down_from(state.ids.sct_show_batch_radio, 8.0)
|
||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.set(state.ids.sct_inc_dmg_radio, ui);
|
||||
|
||||
Text::new("Incoming Damage")
|
||||
.right_from(state.ids.sct_inc_dmg_radio, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.graphics_for(state.ids.sct_inc_dmg_radio)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.sct_inc_dmg_text, ui);
|
||||
// Toggle Batched Incoming Damage
|
||||
let show_sct_player_batch = ToggleButton::new(
|
||||
show_sct_player_batch,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.w_h(18.0, 18.0)
|
||||
.down_from(state.ids.sct_inc_dmg_radio, 8.0)
|
||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.set(state.ids.sct_batch_inc_radio, ui);
|
||||
|
||||
if self.global_state.settings.gameplay.sct_player_batch != show_sct_player_batch {
|
||||
events.push(Event::SctPlayerBatch(
|
||||
!self.global_state.settings.gameplay.sct_player_batch,
|
||||
))
|
||||
}
|
||||
Text::new("Cumulated Incoming Damage")
|
||||
.right_from(state.ids.sct_batch_inc_radio, 10.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.graphics_for(state.ids.sct_batch_inc_radio)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.sct_batch_inc_text, ui);
|
||||
}
|
||||
|
||||
// Energybars Numbers
|
||||
// Hotbar text
|
||||
Text::new("Energybar Numbers")
|
||||
.down_from(state.ids.show_shortcuts_button, 20.0)
|
||||
.down_from(
|
||||
if self.global_state.settings.gameplay.sct {
|
||||
state.ids.sct_batch_inc_radio
|
||||
} else {
|
||||
state.ids.sct_show_radio
|
||||
},
|
||||
20.0,
|
||||
)
|
||||
.font_size(18)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(TEXT_COLOR)
|
||||
@ -857,9 +1023,6 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
{
|
||||
events.push(Event::ChatTransp(new_val));
|
||||
}
|
||||
Rectangle::fill_with([60.0 * 4.0, 1.0 * 4.0], color::TRANSPARENT)
|
||||
.down_from(state.ids.chat_transp_title, 30.0)
|
||||
.set(state.ids.placeholder, ui);
|
||||
}
|
||||
|
||||
// 2) Gameplay Tab --------------------------------
|
||||
|
@ -70,14 +70,19 @@ widget_ids! {
|
||||
healthbar_bg,
|
||||
healthbar_filling,
|
||||
health_text,
|
||||
health_text_bg,
|
||||
energybar_bg,
|
||||
energybar_filling,
|
||||
energy_text,
|
||||
energy_text_bg,
|
||||
level_up,
|
||||
level_down,
|
||||
level_align,
|
||||
level_message,
|
||||
level_message_bg,
|
||||
stamina_wheel,
|
||||
death_bg,
|
||||
hurt_bg,
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +220,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
// 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 {
|
||||
@ -236,6 +241,12 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.middle_of(state.ids.level_align)
|
||||
.font_size(30)
|
||||
.font_id(self.fonts.cyri)
|
||||
.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(30)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(1.0, 1.0, 1.0, fade_level))
|
||||
.set(state.ids.level_message, ui);
|
||||
Image::new(self.imgs.level_up)
|
||||
@ -251,35 +262,38 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.graphics_for(state.ids.level_align)
|
||||
.set(state.ids.level_down, ui);
|
||||
// Death message
|
||||
if hp_percentage == 0.0 {
|
||||
if self.stats.is_dead {
|
||||
Text::new("You Died")
|
||||
.mid_top_with_margin_on(ui.window, 60.0)
|
||||
.font_size(40)
|
||||
.middle_of(ui.window)
|
||||
.font_size(50)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.death_message_1_bg, ui);
|
||||
Text::new(&format!(
|
||||
"Press {:?} to respawn.",
|
||||
"Press {:?} to respawn at your Waypoint.\n\
|
||||
\n\
|
||||
Press Enter, type in /waypoint and confirm to set it here.",
|
||||
self.global_state.settings.controls.respawn
|
||||
))
|
||||
.mid_bottom_with_margin_on(state.ids.death_message_1, -30.0)
|
||||
.font_size(15)
|
||||
.mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
|
||||
.font_size(30)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.death_message_2_bg, ui);
|
||||
|
||||
Text::new("You Died")
|
||||
.top_left_with_margins_on(state.ids.death_message_1_bg, -2.0, -2.0)
|
||||
.font_size(40)
|
||||
.bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0)
|
||||
.font_size(50)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(CRITICAL_HP_COLOR)
|
||||
.set(state.ids.death_message_1, ui);
|
||||
Text::new(&format!(
|
||||
"Press {:?} to respawn.",
|
||||
"Press {:?} to respawn at your Waypoint.\n\
|
||||
\n\
|
||||
Press Enter, type in /waypoint and confirm to set it here.",
|
||||
self.global_state.settings.controls.respawn
|
||||
))
|
||||
.top_left_with_margins_on(state.ids.death_message_2_bg, -1.5, -1.5)
|
||||
.font_size(15)
|
||||
.bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
|
||||
.font_size(30)
|
||||
.color(CRITICAL_HP_COLOR)
|
||||
.set(state.ids.death_message_2, ui);
|
||||
}
|
||||
@ -812,7 +826,13 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
self.stats.health.maximum() as u32
|
||||
);
|
||||
Text::new(&hp_text)
|
||||
.mid_top_with_margin_on(state.ids.healthbar_bg, 5.0 * scale)
|
||||
.mid_top_with_margin_on(state.ids.healthbar_bg, 6.0 * scale)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.health_text_bg, ui);
|
||||
Text::new(&hp_text)
|
||||
.bottom_left_with_margins_on(state.ids.health_text_bg, 2.0, 2.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(TEXT_COLOR)
|
||||
@ -823,7 +843,13 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
self.stats.energy.maximum() as u32
|
||||
);
|
||||
Text::new(&energy_text)
|
||||
.mid_top_with_margin_on(state.ids.energybar_bg, 5.0 * scale)
|
||||
.mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.energy_text_bg, ui);
|
||||
Text::new(&energy_text)
|
||||
.bottom_left_with_margins_on(state.ids.energy_text_bg, 2.0, 2.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(TEXT_COLOR)
|
||||
@ -833,14 +859,26 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
if let BarNumbers::Percent = bar_values {
|
||||
let hp_text = format!("{}%", hp_percentage as u32);
|
||||
Text::new(&hp_text)
|
||||
.mid_top_with_margin_on(state.ids.healthbar_bg, 5.0 * scale)
|
||||
.mid_top_with_margin_on(state.ids.healthbar_bg, 6.0 * scale)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.health_text_bg, ui);
|
||||
Text::new(&hp_text)
|
||||
.bottom_left_with_margins_on(state.ids.health_text_bg, 2.0, 2.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.health_text, ui);
|
||||
let energy_text = format!("{}%", energy_percentage as u32);
|
||||
Text::new(&energy_text)
|
||||
.mid_top_with_margin_on(state.ids.energybar_bg, 5.0 * scale)
|
||||
.mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.energy_text_bg, ui);
|
||||
Text::new(&energy_text)
|
||||
.bottom_left_with_margins_on(state.ids.energy_text_bg, 2.0, 2.0)
|
||||
.font_size(14)
|
||||
.font_id(self.fonts.cyri)
|
||||
.color(TEXT_COLOR)
|
||||
|
@ -16,6 +16,7 @@ use std::sync::Mutex;
|
||||
pub mod ui;
|
||||
pub mod anim;
|
||||
pub mod audio;
|
||||
mod ecs;
|
||||
pub mod error;
|
||||
pub mod hud;
|
||||
pub mod key_state;
|
||||
|
@ -108,11 +108,11 @@ impl PlayState for CharSelectionState {
|
||||
.render(global_state.window.renderer_mut(), self.scene.globals());
|
||||
|
||||
// Tick the client (currently only to keep the connection alive).
|
||||
if let Err(err) = self
|
||||
.client
|
||||
.borrow_mut()
|
||||
.tick(comp::ControllerInputs::default(), clock.get_last_delta())
|
||||
{
|
||||
if let Err(err) = self.client.borrow_mut().tick(
|
||||
comp::ControllerInputs::default(),
|
||||
clock.get_last_delta(),
|
||||
|_| {},
|
||||
) {
|
||||
global_state.info_message = Some(
|
||||
"Connection lost!\nDid the server restart?\nIs the client up to date?"
|
||||
.to_owned(),
|
||||
|
@ -69,8 +69,10 @@ impl PlayState for MainMenuState {
|
||||
|
||||
// Poll client creation.
|
||||
match client_init.as_ref().and_then(|init| init.poll()) {
|
||||
Some(Ok(client)) => {
|
||||
Some(Ok(mut client)) => {
|
||||
self.main_menu_ui.connected();
|
||||
// Register voxygen components / resources
|
||||
crate::ecs::init(client.state_mut().ecs_mut());
|
||||
return PlayStateResult::Push(Box::new(CharSelectionState::new(
|
||||
global_state,
|
||||
std::rc::Rc::new(std::cell::RefCell::new(client)),
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::ui::Graphic;
|
||||
use crate::{
|
||||
render::Renderer,
|
||||
ui::{
|
||||
@ -7,6 +8,7 @@ use crate::{
|
||||
},
|
||||
GlobalState,
|
||||
};
|
||||
use common::assets::load_expect;
|
||||
use conrod_core::{
|
||||
color,
|
||||
color::TRANSPARENT,
|
||||
@ -14,6 +16,7 @@ use conrod_core::{
|
||||
widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox},
|
||||
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
|
||||
};
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use std::time::Duration;
|
||||
|
||||
widget_ids! {
|
||||
@ -84,7 +87,6 @@ image_ids! {
|
||||
|
||||
<ImageGraphic>
|
||||
bg: "voxygen.background.bg_main",
|
||||
load: "voxygen.background.bg_load",
|
||||
|
||||
<BlankGraphic>
|
||||
nothing: (),
|
||||
@ -129,6 +131,7 @@ pub enum PopupType {
|
||||
Error,
|
||||
ConnectionInfo,
|
||||
}
|
||||
|
||||
pub struct PopupData {
|
||||
msg: String,
|
||||
button_text: String,
|
||||
@ -150,6 +153,7 @@ pub struct MainMenuUi {
|
||||
show_servers: bool,
|
||||
show_disclaimer: bool,
|
||||
time: f32,
|
||||
bg_img_id: conrod_core::image::Id,
|
||||
}
|
||||
|
||||
impl MainMenuUi {
|
||||
@ -157,6 +161,18 @@ impl MainMenuUi {
|
||||
let window = &mut global_state.window;
|
||||
let networking = &global_state.settings.networking;
|
||||
let gameplay = &global_state.settings.gameplay;
|
||||
// Randomly loaded background images
|
||||
let bg_imgs = [
|
||||
"voxygen.background.bg_1",
|
||||
"voxygen.background.bg_2",
|
||||
"voxygen.background.bg_3",
|
||||
"voxygen.background.bg_4",
|
||||
"voxygen.background.bg_5",
|
||||
"voxygen.background.bg_6",
|
||||
"voxygen.background.bg_7",
|
||||
"voxygen.background.bg_8",
|
||||
];
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut ui = Ui::new(window).unwrap();
|
||||
ui.set_scaling_mode(gameplay.ui_scale);
|
||||
@ -165,6 +181,9 @@ impl MainMenuUi {
|
||||
// Load images
|
||||
let imgs = Imgs::load(&mut ui).expect("Failed to load images");
|
||||
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load images!");
|
||||
let bg_img_id = ui.add_graphic(Graphic::Image(load_expect(
|
||||
bg_imgs.choose(&mut rng).unwrap(),
|
||||
)));
|
||||
// Load fonts
|
||||
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts");
|
||||
|
||||
@ -183,6 +202,7 @@ impl MainMenuUi {
|
||||
connect: false,
|
||||
time: 0.0,
|
||||
show_disclaimer: global_state.settings.show_disclaimer,
|
||||
bg_img_id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,14 +245,14 @@ impl MainMenuUi {
|
||||
.desc_text_color(TEXT_COLOR_2);
|
||||
|
||||
// Background image, Veloren logo, Alpha-Version Label
|
||||
|
||||
Image::new(if self.connect {
|
||||
self.imgs.load
|
||||
self.bg_img_id
|
||||
} else {
|
||||
self.imgs.bg
|
||||
})
|
||||
.middle_of(ui_widgets.window)
|
||||
.set(self.ids.bg, ui_widgets);
|
||||
|
||||
// Version displayed top right corner
|
||||
Text::new(&version)
|
||||
.color(TEXT_COLOR)
|
||||
|
@ -43,10 +43,10 @@ impl Pipeline for UiPipeline {
|
||||
type Vertex = Vertex;
|
||||
}
|
||||
|
||||
impl From<Vec3<f32>> for Locals {
|
||||
fn from(pos: Vec3<f32>) -> Self {
|
||||
impl From<Vec4<f32>> for Locals {
|
||||
fn from(pos: Vec4<f32>) -> Self {
|
||||
Self {
|
||||
pos: [pos.x, pos.y, pos.z, 1.0],
|
||||
pos: pos.into_array(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1194,6 +1194,7 @@ impl<S: Skeleton> FigureState<S> {
|
||||
self.last_ori = Lerp::lerp(self.last_ori, ori, 15.0 * dt);
|
||||
|
||||
// Update interpolation values
|
||||
// TODO: use values from Interpolated component instead of recalculating
|
||||
if self.pos.distance_squared(pos) < 64.0 * 64.0 {
|
||||
self.pos = Lerp::lerp(self.pos, pos + vel * 0.03, 10.0 * dt);
|
||||
self.ori = Slerp::slerp(self.ori, ori, 5.0 * dt);
|
||||
@ -1205,6 +1206,7 @@ impl<S: Skeleton> FigureState<S> {
|
||||
self.movement_time += (dt * movement_rate) as f64;
|
||||
self.action_time += (dt * action_rate) as f64;
|
||||
|
||||
// TODO: what are the interpolated ori values used for if not here???
|
||||
let mat = Mat4::<f32>::identity()
|
||||
* Mat4::translation_3d(self.pos)
|
||||
* Mat4::rotation_z(-ori.x.atan2(ori.y))
|
||||
|
@ -215,23 +215,31 @@ impl Scene {
|
||||
let mut lights = (
|
||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||
client.state().ecs().read_storage::<comp::Ori>().maybe(),
|
||||
client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<crate::ecs::comp::Interpolated>()
|
||||
.maybe(),
|
||||
&client.state().ecs().read_storage::<comp::LightEmitter>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(pos, _, _)| {
|
||||
.filter(|(pos, _, _, _)| {
|
||||
(pos.0.distance_squared(player_pos) as f32)
|
||||
< self.loaded_distance.powf(2.0) + LIGHT_DIST_RADIUS
|
||||
})
|
||||
.map(|(pos, ori, light_emitter)| {
|
||||
.map(|(pos, ori, interpolated, light_emitter)| {
|
||||
// Use interpolated values if they are available
|
||||
let (pos, ori) =
|
||||
interpolated.map_or((pos.0, ori.map(|o| o.0)), |i| (i.pos, Some(i.ori)));
|
||||
let rot = {
|
||||
if let Some(o) = ori {
|
||||
Mat3::rotation_z(-o.0.x.atan2(o.0.y))
|
||||
Mat3::rotation_z(-o.x.atan2(o.y))
|
||||
} else {
|
||||
Mat3::identity()
|
||||
}
|
||||
};
|
||||
Light::new(
|
||||
pos.0 + (rot * light_emitter.offset),
|
||||
pos + (rot * light_emitter.offset),
|
||||
light_emitter.col,
|
||||
light_emitter.strength,
|
||||
)
|
||||
@ -246,17 +254,28 @@ impl Scene {
|
||||
// Update shadow constants
|
||||
let mut shadows = (
|
||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||
client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<crate::ecs::comp::Interpolated>()
|
||||
.maybe(),
|
||||
client.state().ecs().read_storage::<comp::Scale>().maybe(),
|
||||
&client.state().ecs().read_storage::<comp::Body>(),
|
||||
&client.state().ecs().read_storage::<comp::Stats>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, _, stats)| !stats.is_dead)
|
||||
.filter(|(pos, _, _, _)| {
|
||||
.filter(|(_, _, _, _, stats)| !stats.is_dead)
|
||||
.filter(|(pos, _, _, _, _)| {
|
||||
(pos.0.distance_squared(player_pos) as f32)
|
||||
< (self.loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powf(2.0)
|
||||
})
|
||||
.map(|(pos, scale, _, _)| Shadow::new(pos.0, scale.map(|s| s.0).unwrap_or(1.0)))
|
||||
.map(|(pos, interpolated, scale, _, _)| {
|
||||
Shadow::new(
|
||||
// Use interpolated values pos if it is available
|
||||
interpolated.map_or(pos.0, |i| i.pos),
|
||||
scale.map_or(1.0, |s| s.0),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(player_pos) as i32);
|
||||
shadows.truncate(MAX_SHADOW_COUNT);
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
ecs::MyEntity,
|
||||
hud::{DebugInfo, Event as HudEvent, Hud},
|
||||
key_state::KeyState,
|
||||
render::Renderer,
|
||||
@ -40,6 +41,14 @@ impl SessionState {
|
||||
.camera_mut()
|
||||
.set_fov_deg(global_state.settings.graphics.fov);
|
||||
let hud = Hud::new(global_state, &client.borrow());
|
||||
{
|
||||
let my_entity = client.borrow().entity();
|
||||
client
|
||||
.borrow_mut()
|
||||
.state_mut()
|
||||
.ecs_mut()
|
||||
.insert(MyEntity(my_entity));
|
||||
}
|
||||
Self {
|
||||
scene,
|
||||
client,
|
||||
@ -55,7 +64,11 @@ impl SessionState {
|
||||
/// Tick the session (and the client attached to it).
|
||||
fn tick(&mut self, dt: Duration) -> Result<(), Error> {
|
||||
self.inputs.tick(dt);
|
||||
for event in self.client.borrow_mut().tick(self.inputs.clone(), dt)? {
|
||||
for event in self.client.borrow_mut().tick(
|
||||
self.inputs.clone(),
|
||||
dt,
|
||||
crate::ecs::sys::add_local_systems,
|
||||
)? {
|
||||
match event {
|
||||
Chat {
|
||||
chat_type: _,
|
||||
@ -423,6 +436,22 @@ impl PlayState for SessionState {
|
||||
global_state.settings.gameplay.zoom_inversion = zoom_inverted;
|
||||
global_state.settings.save_to_file_warn();
|
||||
}
|
||||
HudEvent::Sct(sct) => {
|
||||
global_state.settings.gameplay.sct = sct;
|
||||
global_state.settings.save_to_file_warn();
|
||||
}
|
||||
HudEvent::SctPlayerBatch(sct_player_batch) => {
|
||||
global_state.settings.gameplay.sct_player_batch = sct_player_batch;
|
||||
global_state.settings.save_to_file_warn();
|
||||
}
|
||||
HudEvent::SctDamageBatch(sct_damage_batch) => {
|
||||
global_state.settings.gameplay.sct_damage_batch = sct_damage_batch;
|
||||
global_state.settings.save_to_file_warn();
|
||||
}
|
||||
HudEvent::ToggleDebug(toggle_debug) => {
|
||||
global_state.settings.gameplay.toggle_debug = toggle_debug;
|
||||
global_state.settings.save_to_file_warn();
|
||||
}
|
||||
HudEvent::ToggleMouseYInvert(mouse_y_inverted) => {
|
||||
global_state.window.mouse_y_inversion = mouse_y_inverted;
|
||||
global_state.settings.gameplay.mouse_y_inversion = mouse_y_inverted;
|
||||
|
@ -85,8 +85,8 @@ impl Default for ControlSettings {
|
||||
screenshot: KeyMouse::Key(VirtualKeyCode::F4),
|
||||
toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6),
|
||||
roll: KeyMouse::Mouse(MouseButton::Middle),
|
||||
respawn: KeyMouse::Mouse(MouseButton::Left),
|
||||
interact: KeyMouse::Key(VirtualKeyCode::E),
|
||||
respawn: KeyMouse::Key(VirtualKeyCode::Space),
|
||||
interact: KeyMouse::Mouse(MouseButton::Right),
|
||||
toggle_wield: KeyMouse::Key(VirtualKeyCode::T),
|
||||
charge: KeyMouse::Key(VirtualKeyCode::V),
|
||||
}
|
||||
@ -100,6 +100,10 @@ pub struct GameplaySettings {
|
||||
pub pan_sensitivity: u32,
|
||||
pub zoom_sensitivity: u32,
|
||||
pub zoom_inversion: bool,
|
||||
pub toggle_debug: bool,
|
||||
pub sct: bool,
|
||||
pub sct_player_batch: bool,
|
||||
pub sct_damage_batch: bool,
|
||||
pub mouse_y_inversion: bool,
|
||||
pub crosshair_transp: f32,
|
||||
pub chat_transp: f32,
|
||||
@ -118,6 +122,10 @@ impl Default for GameplaySettings {
|
||||
zoom_sensitivity: 100,
|
||||
zoom_inversion: false,
|
||||
mouse_y_inversion: false,
|
||||
toggle_debug: false,
|
||||
sct: true,
|
||||
sct_player_batch: true,
|
||||
sct_damage_batch: false,
|
||||
crosshair_transp: 0.6,
|
||||
chat_transp: 0.4,
|
||||
crosshair_type: CrosshairType::Round,
|
||||
@ -179,9 +187,9 @@ pub struct GraphicsSettings {
|
||||
impl Default for GraphicsSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
view_distance: 5,
|
||||
view_distance: 10,
|
||||
max_fps: 60,
|
||||
fov: 75,
|
||||
fov: 50,
|
||||
aa_mode: AaMode::Fxaa,
|
||||
}
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ impl Ui {
|
||||
enum Placement {
|
||||
Interface,
|
||||
// Number of primitives left to render ingame and relative scaling/resolution
|
||||
InWorld(usize, Option<f32>),
|
||||
InWorld(usize, Option<(f64, f64)>),
|
||||
};
|
||||
|
||||
let mut placement = Placement::Interface;
|
||||
@ -399,7 +399,7 @@ impl Ui {
|
||||
|
||||
// Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0).
|
||||
let (ui_win_w, ui_win_h) = match placement {
|
||||
Placement::InWorld(_, Some(res)) => (res as f64, res as f64),
|
||||
Placement::InWorld(_, Some(res)) => res,
|
||||
// Behind the camera or far away
|
||||
Placement::InWorld(_, None) => continue,
|
||||
Placement::Interface => (self.ui.win_w, self.ui.win_h),
|
||||
@ -624,10 +624,12 @@ impl Ui {
|
||||
.parameters;
|
||||
|
||||
let pos_in_view = view_mat * Vec4::from_point(parameters.pos);
|
||||
|
||||
let scale_factor = self.ui.win_w as f64
|
||||
/ (-2.0
|
||||
* pos_in_view.z as f64
|
||||
* (0.5 * fov as f64).tan()
|
||||
// TODO: make this have no effect for fixed scale
|
||||
* parameters.res as f64);
|
||||
// Don't process ingame elements behind the camera or very far away
|
||||
placement = if scale_factor > 0.2 {
|
||||
@ -642,32 +644,43 @@ impl Ui {
|
||||
});
|
||||
start = mesh.vertices().len();
|
||||
// Push new position command
|
||||
let mut world_pos = Vec4::from_point(parameters.pos);
|
||||
if parameters.fixed_scale {
|
||||
world_pos.w = -1.0
|
||||
};
|
||||
|
||||
if self.ingame_locals.len() > ingame_local_index {
|
||||
renderer
|
||||
.update_consts(
|
||||
&mut self.ingame_locals[ingame_local_index],
|
||||
&[parameters.pos.into()],
|
||||
&[world_pos.into()],
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
self.ingame_locals.push(
|
||||
renderer.create_consts(&[parameters.pos.into()]).unwrap(),
|
||||
);
|
||||
self.ingame_locals
|
||||
.push(renderer.create_consts(&[world_pos.into()]).unwrap());
|
||||
}
|
||||
self.draw_commands
|
||||
.push(DrawCommand::WorldPos(Some(ingame_local_index)));
|
||||
ingame_local_index += 1;
|
||||
|
||||
p_scale_factor = ((scale_factor * 10.0).log2().round().powi(2)
|
||||
/ 10.0)
|
||||
p_scale_factor = if parameters.fixed_scale {
|
||||
self.scale.scale_factor_physical()
|
||||
} else {
|
||||
((scale_factor * 10.0).log2().round().powi(2) / 10.0)
|
||||
.min(1.6)
|
||||
.max(0.2);
|
||||
.max(0.2)
|
||||
};
|
||||
|
||||
// Scale down ingame elements that are close to the camera
|
||||
let res = if scale_factor > 3.2 {
|
||||
parameters.res * scale_factor as f32 / 3.2
|
||||
let res = if parameters.fixed_scale {
|
||||
(self.ui.win_w, self.ui.win_h)
|
||||
} else if scale_factor > 3.2 {
|
||||
let res = parameters.res * scale_factor as f32 / 3.2;
|
||||
(res as f64, res as f64)
|
||||
} else {
|
||||
parameters.res
|
||||
let res = parameters.res;
|
||||
(res as f64, res as f64)
|
||||
};
|
||||
|
||||
Placement::InWorld(parameters.num, Some(res))
|
||||
|
@ -57,6 +57,9 @@ pub struct IngameParameters {
|
||||
// Used for widgets that are rasterized before being sent to the gpu (text & images)
|
||||
// Potentially make this automatic based on distance to camera?
|
||||
pub res: f32,
|
||||
// Whether the widgets should be scaled based on distance to the camera or if they should be a
|
||||
// fixed size (res is ignored in that case)
|
||||
pub fixed_scale: bool,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
@ -74,10 +77,15 @@ impl<W: Ingameable> Ingame<W> {
|
||||
num: widget.prim_count(),
|
||||
pos,
|
||||
res: 1.0,
|
||||
fixed_scale: false,
|
||||
},
|
||||
widget,
|
||||
}
|
||||
}
|
||||
pub fn fixed_scale(mut self) -> Self {
|
||||
self.parameters.fixed_scale = true;
|
||||
self
|
||||
}
|
||||
builder_methods! {
|
||||
pub resolution { parameters.res = f32 }
|
||||
}
|
||||
@ -147,6 +155,7 @@ impl IngameAnchor {
|
||||
num: 0,
|
||||
pos,
|
||||
res: 1.0,
|
||||
fixed_scale: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user