Scrolling Combat Text (SCT)

This commit is contained in:
Monty Marz 2020-01-10 00:33:38 +00:00
parent 4e7c955490
commit 851d7858e6
49 changed files with 1411 additions and 173 deletions

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ todo.txt
# direnv # direnv
/.envrc /.envrc
*.bat

View File

@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added changelog - Added changelog
- Added animated Map and Minimap position indicator - Added animated Map and Minimap position indicator
- Added visuals to indicate strength compared to the player - 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 ### 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 ghosts when going back to character screen
- Fixed not being able to unmount - Fixed not being able to unmount
- Fixed non-humanoids being able to climb and glide - Fixed non-humanoids being able to climb and glide
- Made shadows and lights use interpolated positions
### Removed ### Removed

1
Cargo.lock generated
View File

@ -3544,6 +3544,7 @@ dependencies = [
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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 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)", "treeculler 0.1.0 (git+https://gitlab.com/yusdacra/treeculler.git)",
"vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
"veloren-client 0.4.0", "veloren-client 0.4.0",

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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)

Binary file not shown.

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)

Binary file not shown.

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

Binary file not shown.

View File

@ -25,6 +25,10 @@ void main() {
if (w_pos.w == 1.0) { if (w_pos.w == 1.0) {
// In-game element // In-game element
gl_Position = proj_mat * (view_mat * w_pos + vec4(v_pos, 0.0, 0.0)); 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 { } else {
// Interface element // Interface element
gl_Position = vec4(v_pos, 0.0, 1.0); gl_Position = vec4(v_pos, 0.0, 1.0);

View File

@ -65,7 +65,11 @@ fn main() {
client.send_chat(msg) 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, Ok(events) => events,
Err(err) => { Err(err) => {
error!("Error: {:?}", err); error!("Error: {:?}", err);

View File

@ -8,7 +8,7 @@ pub use crate::error::Error;
pub use specs::{ pub use specs::{
join::Join, join::Join,
saveload::{Marker, MarkerAllocator}, saveload::{Marker, MarkerAllocator},
Builder, Entity as EcsEntity, ReadStorage, WorldExt, Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, WorldExt,
}; };
use common::{ use common::{
@ -316,7 +316,12 @@ impl Client {
} }
/// Execute a single client tick, handle input and update the game state by the given duration. /// 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 // 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 // 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 // the core developers before making significant changes to this code. Here is the
@ -374,7 +379,7 @@ impl Client {
// 3) Update client local data // 3) Update client local data
// 4) Tick the client's LocalState // 4) Tick the client's LocalState
self.state.tick(dt, |_| {}); self.state.tick(dt, add_foreign_systems);
// 5) Terrain // 5) Terrain
let pos = self let pos = self

View File

@ -12,15 +12,15 @@ mod stats;
use specs::DispatcherBuilder; use specs::DispatcherBuilder;
// System names // System names
const AGENT_SYS: &str = "agent_sys"; pub const AGENT_SYS: &str = "agent_sys";
const CONTROLLER_SYS: &str = "controller_sys"; pub const CONTROLLER_SYS: &str = "controller_sys";
const MOUNT_SYS: &str = "mount_sys"; pub const MOUNT_SYS: &str = "mount_sys";
const PHYS_SYS: &str = "phys_sys"; pub const PHYS_SYS: &str = "phys_sys";
const MOVEMENT_SYS: &str = "movement_sys"; pub const MOVEMENT_SYS: &str = "movement_sys";
const PROJECTILE_SYS: &str = "projectile_sys"; pub const PROJECTILE_SYS: &str = "projectile_sys";
const COMBAT_SYS: &str = "combat_sys"; pub const COMBAT_SYS: &str = "combat_sys";
const STATS_SYS: &str = "stats_sys"; pub const STATS_SYS: &str = "stats_sys";
const CLEANUP_SYS: &str = "cleanup_sys"; pub const CLEANUP_SYS: &str = "cleanup_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);

View File

@ -1 +1 @@
settings.ron

View File

@ -87,9 +87,9 @@ lazy_static! {
/// Static list of chat commands available to the server. /// Static list of chat commands available to the server.
pub static ref CHAT_COMMANDS: Vec<ChatCommand> = vec![ pub static ref CHAT_COMMANDS: Vec<ChatCommand> = vec![
ChatCommand::new( ChatCommand::new(
"giveitem", "give_item",
"{d}", "{d}",
"/giveitem <path to item>\n\ "/give_item <path to item>\n\
Example: common/items/debug/boost", Example: common/items/debug/boost",
true, true,
handle_give,), handle_give,),

View File

@ -22,7 +22,7 @@ impl Default for ServerSettings {
Self { Self {
gameserver_address: SocketAddr::from(([0; 4], 14004)), gameserver_address: SocketAddr::from(([0; 4], 14004)),
metrics_address: SocketAddr::from(([0; 4], 14005)), metrics_address: SocketAddr::from(([0; 4], 14005)),
world_seed: 1337, world_seed: 5284,
server_name: "Veloren Alpha".to_owned(), server_name: "Veloren Alpha".to_owned(),
server_description: "This is the best Veloren server.".to_owned(), server_description: "This is the best Veloren server.".to_owned(),
max_players: 100, max_players: 100,

View File

@ -24,8 +24,12 @@ const TERRAIN_SYS: &str = "server_terrain_sys";
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
// TODO: makes some of these dependent on systems in common like the phys system // TODO: makes some of these dependent on systems in common like the phys system
dispatch_builder.add(sentinel::Sys, SENTINEL_SYS, &[]); dispatch_builder.add(sentinel::Sys, SENTINEL_SYS, &[common::sys::PHYS_SYS]);
dispatch_builder.add(subscription::Sys, SUBSCRIPTION_SYS, &[]); dispatch_builder.add(
subscription::Sys,
SUBSCRIPTION_SYS,
&[common::sys::PHYS_SYS],
);
dispatch_builder.add( dispatch_builder.add(
entity_sync::Sys, entity_sync::Sys,
ENTITY_SYNC_SYS, ENTITY_SYNC_SYS,

View File

@ -28,6 +28,7 @@ euc = "0.3.0"
# ECS # ECS
specs = "0.15.1" specs = "0.15.1"
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
# Mathematics # Mathematics
vek = { version = "0.9.9", features = ["serde"] } vek = { version = "0.9.9", features = ["serde"] }

34
voxygen/src/ecs/comp.rs Normal file
View 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
View 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
View 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]);
}

View 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;
}
}
}

View 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);
}
}
}

View File

@ -231,8 +231,7 @@ image_ids! {
key: "voxygen.voxel.object.key", key: "voxygen.voxel.object.key",
key_gold: "voxygen.voxel.object.key_gold", 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", charwindow_gradient:"voxygen.element.misc_bg.charwindow",
map_placeholder: "voxygen.background.map", 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 // Spell Book Window
spellbook_icon: "voxygen.element.icons.spellbook", spellbook_icon: "voxygen.element.icons.spellbook",
// Bag // Bag

View File

@ -9,7 +9,6 @@ use conrod_core::{
}; };
use specs::WorldExt; use specs::WorldExt;
use vek::*; use vek::*;
widget_ids! { widget_ids! {
struct Ids { struct Ids {
map_frame, map_frame,
@ -37,6 +36,7 @@ pub struct Map<'a> {
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
pulse: f32, pulse: f32,
velocity: f32,
} }
impl<'a> Map<'a> { impl<'a> Map<'a> {
pub fn new( pub fn new(
@ -46,6 +46,7 @@ impl<'a> Map<'a> {
world_map: Id, world_map: Id,
fonts: &'a Fonts, fonts: &'a Fonts,
pulse: f32, pulse: f32,
velocity: f32,
) -> Self { ) -> Self {
Self { Self {
_show: show, _show: show,
@ -55,6 +56,7 @@ impl<'a> Map<'a> {
fonts: fonts, fonts: fonts,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
pulse, pulse,
velocity,
} }
} }
} }
@ -84,6 +86,15 @@ impl<'a> Widget for Map<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args; 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 // BG
Rectangle::fill_with([824.0, 976.0], color::TRANSPARENT) 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) Image::new(self.imgs.map_frame_l)
.top_left_with_margins_on(state.ids.map_bg, 0.0, 0.0) .top_left_with_margins_on(state.ids.map_bg, 0.0, 0.0)
.w_h(412.0, 488.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); .set(state.ids.map_frame_l, ui);
Image::new(self.imgs.map_frame_r) Image::new(self.imgs.map_frame_r)
.right_from(state.ids.map_frame_l, 0.0) .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) .w_h(412.0, 488.0)
.set(state.ids.map_frame_r, ui); .set(state.ids.map_frame_r, ui);
Image::new(self.imgs.map_frame_br) Image::new(self.imgs.map_frame_br)
.down_from(state.ids.map_frame_r, 0.0) .down_from(state.ids.map_frame_r, 0.0)
.w_h(412.0, 488.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); .set(state.ids.map_frame_br, ui);
Image::new(self.imgs.map_frame_bl) Image::new(self.imgs.map_frame_bl)
.down_from(state.ids.map_frame_l, 0.0) .down_from(state.ids.map_frame_l, 0.0)
.w_h(412.0, 488.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); .set(state.ids.map_frame_bl, ui);
// Icon // Icon
Image::new(self.imgs.map_icon) Image::new(self.imgs.map_icon)
.w_h(224.0 / 3.0, 224.0 / 3.0) .w_h(224.0 / 3.0, 224.0 / 3.0)
.top_left_with_margins_on(state.ids.map_frame, -10.0, -10.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); .set(state.ids.map_icon, ui);
// X-Button // X-Button
@ -121,6 +137,7 @@ impl<'a> Widget for Map<'a> {
.w_h(28.0, 28.0) .w_h(28.0, 28.0)
.hover_image(self.imgs.close_button_hover) .hover_image(self.imgs.close_button_hover)
.press_image(self.imgs.close_button_press) .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) .top_right_with_margins_on(state.ids.map_frame_r, 0.0, 0.0)
.set(state.ids.map_close, ui) .set(state.ids.map_close, ui)
.was_clicked() .was_clicked()
@ -144,9 +161,11 @@ impl<'a> Widget for Map<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.location_name, ui), .set(state.ids.location_name, ui),
} }
// Map Image // Map Image
Image::new(/*self.world_map*/ self.imgs.map_placeholder) Image::new(/*self.world_map*/ self.imgs.map_placeholder)
.middle_of(state.ids.map_bg) .middle_of(state.ids.map_bg)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
.w_h(700.0, 700.0) .w_h(700.0, 700.0)
.parent(state.ids.map_bg) .parent(state.ids.map_bg)
.set(state.ids.grid, ui); .set(state.ids.grid, ui);
@ -183,7 +202,7 @@ impl<'a> Widget for Map<'a> {
34.0 * indic_scale 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) .floating(true)
.parent(ui.window) .parent(ui.window)
.set(state.ids.indicator, ui); .set(state.ids.indicator, ui);

View File

@ -13,8 +13,7 @@ mod skillbar;
mod social; mod social;
mod spell; mod spell;
use crate::hud::img_ids::ImgsRot; use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot};
//use rand::Rng;
pub use settings_window::ScaleChange; pub use settings_window::ScaleChange;
use std::time::Duration; use std::time::Duration;
@ -36,6 +35,7 @@ use social::{Social, SocialTab};
use spell::Spell; use spell::Spell;
use crate::{ use crate::{
ecs::comp as vcomp,
render::{AaMode, Consts, Globals, Renderer}, render::{AaMode, Consts, Globals, Renderer},
scene::camera::Camera, scene::camera::Camera,
//settings::ControlSettings, //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 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: 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 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_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); const TEXT_COLOR_3: Color = Color::Rgba(1.0, 1.0, 1.0, 0.1);
@ -88,6 +89,7 @@ widget_ids! {
// Character Names // Character Names
name_tags[], name_tags[],
name_tags_bgs[],
levels[], levels[],
levels_skull[], levels_skull[],
// Health Bars // Health Bars
@ -96,6 +98,18 @@ widget_ids! {
health_bar_fronts[], health_bar_fronts[],
health_bar_backs[], 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 Text
intro_bg, intro_bg,
intro_text, intro_text,
@ -128,6 +142,7 @@ widget_ids! {
// Help // Help
help, help,
help_info, help_info,
debug_info,
// Window Frames // Window Frames
window_frame_0, window_frame_0,
@ -200,6 +215,10 @@ pub enum Event {
Intro(Intro), Intro(Intro),
ToggleBarNumbers(BarNumbers), ToggleBarNumbers(BarNumbers),
ToggleShortcutNumbers(ShortcutNumbers), ToggleShortcutNumbers(ShortcutNumbers),
Sct(bool),
SctPlayerBatch(bool),
SctDamageBatch(bool),
ToggleDebug(bool),
UiScale(ScaleChange), UiScale(ScaleChange),
CharacterSelection, CharacterSelection,
UseInventorySlot(usize), UseInventorySlot(usize),
@ -265,7 +284,6 @@ pub struct Show {
ingame: bool, ingame: bool,
settings_tab: SettingsTab, settings_tab: SettingsTab,
social_tab: SocialTab, social_tab: SocialTab,
want_grab: bool, want_grab: bool,
} }
impl Show { impl Show {
@ -424,6 +442,7 @@ pub struct Hud {
force_chat_cursor: Option<Index>, force_chat_cursor: Option<Index>,
pulse: f32, pulse: f32,
zoom: f32, zoom: f32,
velocity: f32,
} }
impl Hud { impl Hud {
@ -461,7 +480,7 @@ impl Hud {
show: Show { show: Show {
help: false, help: false,
intro: true, intro: true,
debug: true, debug: false,
bag: false, bag: false,
esc_menu: false, esc_menu: false,
open_windows: Windows::None, open_windows: Windows::None,
@ -485,6 +504,7 @@ impl Hud {
force_chat_cursor: None, force_chat_cursor: None,
pulse: 0.0, pulse: 0.0,
zoom: 1.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(); let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets();
// pulse time for pulsating elements // pulse time for pulsating elements
self.pulse = self.pulse + dt.as_secs_f32(); 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!( let version = format!(
"{}-{}", "{}-{}",
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
common::util::GIT_VERSION.to_string() common::util::GIT_VERSION.to_string()
); );
if self.show.ingame { 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 // Crosshair
if !self.show.help { if !self.show.help && !stats.is_dead {
Image::new( Image::new(
// TODO: Do we want to match on this every frame? // TODO: Do we want to match on this every frame?
match global_state.settings.gameplay.crosshair_type { 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))) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.6)))
.set(self.ids.crosshair_inner, ui_widgets); .set(self.ids.crosshair_inner, ui_widgets);
} }
}
// Nametags and healthbars // 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. // Get player position.
let player_pos = client let player_pos = client
.state() .state()
@ -553,21 +609,31 @@ impl Hud {
.get(client.entity()) .get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .map_or(Vec3::zero(), |pos| pos.0);
let mut name_id_walker = self.ids.name_tags.walk(); 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_id_walker = self.ids.levels.walk();
let mut level_skull_id_walker = self.ids.levels_skull.walk(); let mut level_skull_id_walker = self.ids.levels_skull.walk();
let mut health_id_walker = self.ids.health_bars.walk(); let mut health_id_walker = self.ids.health_bars.walk();
let mut mana_id_walker = self.ids.mana_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_back_id_walker = self.ids.health_bar_backs.walk();
let mut health_front_id_walker = self.ids.health_bar_fronts.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 // 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() .join()
.filter(|(entity, _, stats, _)| { .filter(|(entity, _, _, stats, _, _)| {
*entity != me && !stats.is_dead *entity != me && !stats.is_dead
//&& stats.health.current() != stats.health.maximum() //&& stats.health.current() != stats.health.maximum()
}) })
// Don't process health bars outside the vd (visibility further limited by ui backend) // Don't process health bars outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _)| { .filter(|(_, pos, _, _, _, _)| {
Vec2::from(pos.0 - player_pos) Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| { .map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32 d.abs() as f32 / sz as f32
@ -575,9 +641,15 @@ impl Hud {
.magnitude() .magnitude()
< view_distance as f32 < 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( let back_id = health_back_id_walker.next(
&mut self.ids.health_bar_backs, &mut self.ids.health_bar_backs,
&mut ui_widgets.widget_id_generator(), &mut ui_widgets.widget_id_generator(),
@ -596,20 +668,22 @@ impl Hud {
); );
let hp_percentage = let hp_percentage =
stats.health.current() as f64 / stats.health.maximum() as f64 * 100.0; 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 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); let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
// Background // Background
Rectangle::fill_with([82.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5)) Rectangle::fill_with([82.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5))
.x_y(0.0, -25.0) .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) .resolution(100.0)
.set(back_id, ui_widgets); .set(back_id, ui_widgets);
// % HP Filling // % HP Filling
Image::new(self.imgs.bar_content) Image::new(self.imgs.enemy_bar)
.w_h(72.9 * (hp_percentage / 100.0), 5.9) .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 { .color(Some(if hp_percentage <= 25.0 {
crit_hp_color crit_hp_color
} else if hp_percentage <= 50.0 { } else if hp_percentage <= 50.0 {
@ -617,7 +691,7 @@ impl Hud {
} else { } else {
HP_COLOR 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) .resolution(100.0)
.set(health_bar_id, ui_widgets); .set(health_bar_id, ui_widgets);
// % Mana Filling // % Mana Filling
@ -628,8 +702,8 @@ impl Hud {
], ],
MANA_COLOR, MANA_COLOR,
) )
.x_y(4.5, -28.0) .x_y(4.5 + (energy_percentage / 100.0 * 36.5) - 36.45, -28.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) .resolution(100.0)
.set(mana_bar_id, ui_widgets); .set(mana_bar_id, ui_widgets);
@ -638,18 +712,395 @@ impl Hud {
.w_h(84.0, 10.0) .w_h(84.0, 10.0)
.x_y(0.0, -25.0) .x_y(0.0, -25.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99))) .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) .resolution(100.0)
.set(front_id, ui_widgets); .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 // Render Name Tags
for (pos, name, level, scale) in for (pos, name, level, scale) in (
(&entities, &pos, &stats, players.maybe(), scales.maybe()) &entities,
&pos,
interpolated.maybe(),
&stats,
players.maybe(),
scales.maybe(),
)
.join() .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) // Don't process nametags outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _, _)| { .filter(|(_, pos, _, _, _, _)| {
Vec2::from(pos.0 - player_pos) Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| { .map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32 d.abs() as f32 / sz as f32
@ -657,7 +1108,7 @@ impl Hud {
.magnitude() .magnitude()
< view_distance as f32 < view_distance as f32
}) })
.map(|(_, pos, stats, player, scale)| { .map(|(_, pos, interpolated, stats, player, scale)| {
// TODO: This is temporary // TODO: This is temporary
// If the player used the default character name display their name instead // If the player used the default character name display their name instead
let name = if stats.name == "Character Name" { let name = if stats.name == "Character Name" {
@ -665,15 +1116,22 @@ impl Hud {
} else { } else {
&stats.name &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( let name_id = name_id_walker.next(
&mut self.ids.name_tags, &mut self.ids.name_tags,
&mut ui_widgets.widget_id_generator(), &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 let level_id = level_id_walker
.next(&mut self.ids.levels, &mut ui_widgets.widget_id_generator()); .next(&mut self.ids.levels, &mut ui_widgets.widget_id_generator());
let level_skull_id = level_skull_id_walker.next( let level_skull_id = level_skull_id_walker.next(
@ -682,6 +1140,13 @@ impl Hud {
); );
// Name // 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) Text::new(&name)
.font_size(20) .font_size(20)
.color(Color::Rgba(0.61, 0.61, 0.89, 1.0)) .color(Color::Rgba(0.61, 0.61, 0.89, 1.0))
@ -726,15 +1191,16 @@ impl Hud {
} else { } else {
self.imgs.skull self.imgs.skull
}) })
.w_h(30.0, 30.0) .w_h(18.0, 18.0)
.x_y(0.0, 24.0) .x_y(-39.0, -25.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8))) .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)) .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0) .resolution(100.0)
.set(level_skull_id, ui_widgets); .set(level_skull_id, ui_widgets);
} }
} }
} }
// Introduction Text // Introduction Text
let intro_text: &'static str = let intro_text: &'static str =
"Welcome to the Veloren Alpha!\n\ "Welcome to the Veloren Alpha!\n\
@ -906,7 +1372,7 @@ impl Hud {
} }
// Display debug window. // Display debug window.
if self.show.debug { if global_state.settings.gameplay.toggle_debug {
// Alpha Version // Alpha Version
Text::new(&version) Text::new(&version)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) .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); .set(self.ids.num_figures, ui_widgets);
// Help Window // Help Window
Text::new("Press 'F1' to show Keybindings") Text::new(&format!(
"Press {:?} to show keybindings",
global_state.settings.controls.help
))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.num_figures, 5.0) .down_from(self.ids.num_figures, 5.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.font_size(14) .font_size(14)
.set(self.ids.help_info, ui_widgets); .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. // Add Bag-Space Button.
@ -1185,6 +1685,15 @@ impl Hud {
.set(self.ids.settings_window, ui_widgets) .set(self.ids.settings_window, ui_widgets)
{ {
match event { 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::ToggleHelp => self.show.help = !self.show.help,
settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug, settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug,
settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab), settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab),
@ -1319,6 +1828,7 @@ impl Hud {
self.world_map, self.world_map,
&self.fonts, &self.fonts,
self.pulse, self.pulse,
self.velocity,
) )
.set(self.ids.map, ui_widgets) .set(self.ids.map, ui_widgets)
{ {
@ -1471,7 +1981,8 @@ impl Hud {
true true
} }
GameInput::ToggleDebug => { GameInput::ToggleDebug => {
self.show.debug = !self.show.debug; global_state.settings.gameplay.toggle_debug =
!global_state.settings.gameplay.toggle_debug;
true true
} }
GameInput::ToggleIngameUi => { GameInput::ToggleIngameUi => {

View File

@ -18,6 +18,7 @@ const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]
widget_ids! { widget_ids! {
struct Ids { struct Ids {
settings_content, settings_content,
settings_content_r,
settings_icon, settings_icon,
settings_button_mo, settings_button_mo,
settings_close, settings_close,
@ -109,7 +110,24 @@ widget_ids! {
placeholder, placeholder,
chat_transp_title, chat_transp_title,
chat_transp_text, 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), CrosshairType(CrosshairType),
UiScale(ScaleChange), UiScale(ScaleChange),
ChatTransp(f32), ChatTransp(f32),
Sct(bool),
SctPlayerBatch(bool),
SctDamageBatch(bool),
} }
pub enum ScaleChange { pub enum ScaleChange {
@ -229,6 +250,10 @@ impl<'a> Widget for SettingsWindow<'a> {
.scroll_kids() .scroll_kids()
.scroll_kids_vertically() .scroll_kids_vertically()
.set(state.ids.settings_content, ui); .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) Scrollbar::y_axis(state.ids.settings_content)
.thickness(5.0) .thickness(5.0)
.rgba(0.33, 0.33, 0.33, 1.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)); events.push(Event::ChangeTab(SettingsTab::Interface));
} }
// Contents // Contents Left Side
if let SettingsTab::Interface = self.show.settings_tab { if let SettingsTab::Interface = self.show.settings_tab {
let crosshair_transp = self.global_state.settings.gameplay.crosshair_transp; let crosshair_transp = self.global_state.settings.gameplay.crosshair_transp;
let crosshair_type = self.global_state.settings.gameplay.crosshair_type; let crosshair_type = self.global_state.settings.gameplay.crosshair_type;
@ -309,7 +334,7 @@ impl<'a> Widget for SettingsWindow<'a> {
events.push(Event::ToggleHelp); events.push(Event::ToggleHelp);
} }
Text::new("Show Help Window") Text::new("Help Window")
.right_from(state.ids.button_help, 10.0) .right_from(state.ids.button_help, 10.0)
.font_size(14) .font_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
@ -333,7 +358,7 @@ impl<'a> Widget for SettingsWindow<'a> {
events.push(Event::ToggleDebug); events.push(Event::ToggleDebug);
} }
Text::new("Show Debug Info") Text::new("Debug Info")
.right_from(state.ids.debug_button, 10.0) .right_from(state.ids.debug_button, 10.0)
.font_size(14) .font_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
@ -360,7 +385,7 @@ impl<'a> Widget for SettingsWindow<'a> {
Intro::Never => events.push(Event::Intro(Intro::Show)), 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) .right_from(state.ids.tips_button, 10.0)
.font_size(14) .font_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
@ -683,7 +708,7 @@ impl<'a> Widget for SettingsWindow<'a> {
XpBar::OnGain => events.push(Event::ToggleXpBar(XpBar::Always)), 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) .right_from(state.ids.show_xpbar_button, 10.0)
.font_size(14) .font_size(14)
.font_id(self.fonts.cyri) .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) .right_from(state.ids.show_shortcuts_button, 10.0)
.font_size(14) .font_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
@ -725,10 +750,151 @@ impl<'a> Widget for SettingsWindow<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.show_shortcuts_text, ui); .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 // Energybars Numbers
// Hotbar text // Hotbar text
Text::new("Energybar Numbers") 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_size(18)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -857,9 +1023,6 @@ impl<'a> Widget for SettingsWindow<'a> {
{ {
events.push(Event::ChatTransp(new_val)); 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 -------------------------------- // 2) Gameplay Tab --------------------------------

View File

@ -70,14 +70,19 @@ widget_ids! {
healthbar_bg, healthbar_bg,
healthbar_filling, healthbar_filling,
health_text, health_text,
health_text_bg,
energybar_bg, energybar_bg,
energybar_filling, energybar_filling,
energy_text, energy_text,
energy_text_bg,
level_up, level_up,
level_down, level_down,
level_align, level_align,
level_message, level_message,
level_message_bg,
stamina_wheel, stamina_wheel,
death_bg,
hurt_bg,
} }
} }
@ -215,7 +220,7 @@ impl<'a> Widget for Skillbar<'a> {
// Update last_value // Update last_value
state.update(|s| s.last_level = current_level); state.update(|s| s.last_level = current_level);
state.update(|s| s.last_update_level = Instant::now()); state.update(|s| s.last_update_level = Instant::now());
} };
let seconds_level = state.last_update_level.elapsed().as_secs_f32(); let seconds_level = state.last_update_level.elapsed().as_secs_f32();
let fade_level = if current_level == 1 { let fade_level = if current_level == 1 {
@ -236,6 +241,12 @@ impl<'a> Widget for Skillbar<'a> {
.middle_of(state.ids.level_align) .middle_of(state.ids.level_align)
.font_size(30) .font_size(30)
.font_id(self.fonts.cyri) .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)) .color(Color::Rgba(1.0, 1.0, 1.0, fade_level))
.set(state.ids.level_message, ui); .set(state.ids.level_message, ui);
Image::new(self.imgs.level_up) Image::new(self.imgs.level_up)
@ -251,35 +262,38 @@ impl<'a> Widget for Skillbar<'a> {
.graphics_for(state.ids.level_align) .graphics_for(state.ids.level_align)
.set(state.ids.level_down, ui); .set(state.ids.level_down, ui);
// Death message // Death message
if hp_percentage == 0.0 { if self.stats.is_dead {
Text::new("You Died") Text::new("You Died")
.mid_top_with_margin_on(ui.window, 60.0) .middle_of(ui.window)
.font_size(40) .font_size(50)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.death_message_1_bg, ui); .set(state.ids.death_message_1_bg, ui);
Text::new(&format!( 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 self.global_state.settings.controls.respawn
)) ))
.mid_bottom_with_margin_on(state.ids.death_message_1, -30.0) .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
.font_size(15) .font_size(30)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.death_message_2_bg, ui); .set(state.ids.death_message_2_bg, ui);
Text::new("You Died") Text::new("You Died")
.top_left_with_margins_on(state.ids.death_message_1_bg, -2.0, -2.0) .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0)
.font_size(40) .font_size(50)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(CRITICAL_HP_COLOR) .color(CRITICAL_HP_COLOR)
.set(state.ids.death_message_1, ui); .set(state.ids.death_message_1, ui);
Text::new(&format!( 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 self.global_state.settings.controls.respawn
)) ))
.top_left_with_margins_on(state.ids.death_message_2_bg, -1.5, -1.5) .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
.font_size(15) .font_size(30)
.color(CRITICAL_HP_COLOR) .color(CRITICAL_HP_COLOR)
.set(state.ids.death_message_2, ui); .set(state.ids.death_message_2, ui);
} }
@ -812,7 +826,13 @@ impl<'a> Widget for Skillbar<'a> {
self.stats.health.maximum() as u32 self.stats.health.maximum() as u32
); );
Text::new(&hp_text) 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_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -823,7 +843,13 @@ impl<'a> Widget for Skillbar<'a> {
self.stats.energy.maximum() as u32 self.stats.energy.maximum() as u32
); );
Text::new(&energy_text) 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_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -833,14 +859,26 @@ impl<'a> Widget for Skillbar<'a> {
if let BarNumbers::Percent = bar_values { if let BarNumbers::Percent = bar_values {
let hp_text = format!("{}%", hp_percentage as u32); let hp_text = format!("{}%", hp_percentage as u32);
Text::new(&hp_text) 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_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.health_text, ui); .set(state.ids.health_text, ui);
let energy_text = format!("{}%", energy_percentage as u32); let energy_text = format!("{}%", energy_percentage as u32);
Text::new(&energy_text) 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_size(14)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(TEXT_COLOR) .color(TEXT_COLOR)

View File

@ -16,6 +16,7 @@ use std::sync::Mutex;
pub mod ui; pub mod ui;
pub mod anim; pub mod anim;
pub mod audio; pub mod audio;
mod ecs;
pub mod error; pub mod error;
pub mod hud; pub mod hud;
pub mod key_state; pub mod key_state;

View File

@ -108,11 +108,11 @@ impl PlayState for CharSelectionState {
.render(global_state.window.renderer_mut(), self.scene.globals()); .render(global_state.window.renderer_mut(), self.scene.globals());
// Tick the client (currently only to keep the connection alive). // Tick the client (currently only to keep the connection alive).
if let Err(err) = self if let Err(err) = self.client.borrow_mut().tick(
.client comp::ControllerInputs::default(),
.borrow_mut() clock.get_last_delta(),
.tick(comp::ControllerInputs::default(), clock.get_last_delta()) |_| {},
{ ) {
global_state.info_message = Some( global_state.info_message = Some(
"Connection lost!\nDid the server restart?\nIs the client up to date?" "Connection lost!\nDid the server restart?\nIs the client up to date?"
.to_owned(), .to_owned(),

View File

@ -69,8 +69,10 @@ impl PlayState for MainMenuState {
// Poll client creation. // Poll client creation.
match client_init.as_ref().and_then(|init| init.poll()) { match client_init.as_ref().and_then(|init| init.poll()) {
Some(Ok(client)) => { Some(Ok(mut client)) => {
self.main_menu_ui.connected(); self.main_menu_ui.connected();
// Register voxygen components / resources
crate::ecs::init(client.state_mut().ecs_mut());
return PlayStateResult::Push(Box::new(CharSelectionState::new( return PlayStateResult::Push(Box::new(CharSelectionState::new(
global_state, global_state,
std::rc::Rc::new(std::cell::RefCell::new(client)), std::rc::Rc::new(std::cell::RefCell::new(client)),

View File

@ -1,3 +1,4 @@
use crate::ui::Graphic;
use crate::{ use crate::{
render::Renderer, render::Renderer,
ui::{ ui::{
@ -7,6 +8,7 @@ use crate::{
}, },
GlobalState, GlobalState,
}; };
use common::assets::load_expect;
use conrod_core::{ use conrod_core::{
color, color,
color::TRANSPARENT, color::TRANSPARENT,
@ -14,6 +16,7 @@ use conrod_core::{
widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox}, widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox},
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
}; };
use rand::{seq::SliceRandom, thread_rng};
use std::time::Duration; use std::time::Duration;
widget_ids! { widget_ids! {
@ -84,7 +87,6 @@ image_ids! {
<ImageGraphic> <ImageGraphic>
bg: "voxygen.background.bg_main", bg: "voxygen.background.bg_main",
load: "voxygen.background.bg_load",
<BlankGraphic> <BlankGraphic>
nothing: (), nothing: (),
@ -129,6 +131,7 @@ pub enum PopupType {
Error, Error,
ConnectionInfo, ConnectionInfo,
} }
pub struct PopupData { pub struct PopupData {
msg: String, msg: String,
button_text: String, button_text: String,
@ -150,6 +153,7 @@ pub struct MainMenuUi {
show_servers: bool, show_servers: bool,
show_disclaimer: bool, show_disclaimer: bool,
time: f32, time: f32,
bg_img_id: conrod_core::image::Id,
} }
impl MainMenuUi { impl MainMenuUi {
@ -157,6 +161,18 @@ impl MainMenuUi {
let window = &mut global_state.window; let window = &mut global_state.window;
let networking = &global_state.settings.networking; let networking = &global_state.settings.networking;
let gameplay = &global_state.settings.gameplay; 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(); let mut ui = Ui::new(window).unwrap();
ui.set_scaling_mode(gameplay.ui_scale); ui.set_scaling_mode(gameplay.ui_scale);
@ -165,6 +181,9 @@ impl MainMenuUi {
// Load images // Load images
let imgs = Imgs::load(&mut ui).expect("Failed to 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 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 // Load fonts
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts"); let fonts = Fonts::load(&mut ui).expect("Failed to load fonts");
@ -183,6 +202,7 @@ impl MainMenuUi {
connect: false, connect: false,
time: 0.0, time: 0.0,
show_disclaimer: global_state.settings.show_disclaimer, show_disclaimer: global_state.settings.show_disclaimer,
bg_img_id,
} }
} }
@ -225,14 +245,14 @@ impl MainMenuUi {
.desc_text_color(TEXT_COLOR_2); .desc_text_color(TEXT_COLOR_2);
// Background image, Veloren logo, Alpha-Version Label // Background image, Veloren logo, Alpha-Version Label
Image::new(if self.connect { Image::new(if self.connect {
self.imgs.load self.bg_img_id
} else { } else {
self.imgs.bg self.imgs.bg
}) })
.middle_of(ui_widgets.window) .middle_of(ui_widgets.window)
.set(self.ids.bg, ui_widgets); .set(self.ids.bg, ui_widgets);
// Version displayed top right corner // Version displayed top right corner
Text::new(&version) Text::new(&version)
.color(TEXT_COLOR) .color(TEXT_COLOR)

View File

@ -43,10 +43,10 @@ impl Pipeline for UiPipeline {
type Vertex = Vertex; type Vertex = Vertex;
} }
impl From<Vec3<f32>> for Locals { impl From<Vec4<f32>> for Locals {
fn from(pos: Vec3<f32>) -> Self { fn from(pos: Vec4<f32>) -> Self {
Self { Self {
pos: [pos.x, pos.y, pos.z, 1.0], pos: pos.into_array(),
} }
} }
} }

View File

@ -1194,6 +1194,7 @@ impl<S: Skeleton> FigureState<S> {
self.last_ori = Lerp::lerp(self.last_ori, ori, 15.0 * dt); self.last_ori = Lerp::lerp(self.last_ori, ori, 15.0 * dt);
// Update interpolation values // Update interpolation values
// TODO: use values from Interpolated component instead of recalculating
if self.pos.distance_squared(pos) < 64.0 * 64.0 { if self.pos.distance_squared(pos) < 64.0 * 64.0 {
self.pos = Lerp::lerp(self.pos, pos + vel * 0.03, 10.0 * dt); self.pos = Lerp::lerp(self.pos, pos + vel * 0.03, 10.0 * dt);
self.ori = Slerp::slerp(self.ori, ori, 5.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.movement_time += (dt * movement_rate) as f64;
self.action_time += (dt * action_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() let mat = Mat4::<f32>::identity()
* Mat4::translation_3d(self.pos) * Mat4::translation_3d(self.pos)
* Mat4::rotation_z(-ori.x.atan2(ori.y)) * Mat4::rotation_z(-ori.x.atan2(ori.y))

View File

@ -215,23 +215,31 @@ impl Scene {
let mut lights = ( let mut lights = (
&client.state().ecs().read_storage::<comp::Pos>(), &client.state().ecs().read_storage::<comp::Pos>(),
client.state().ecs().read_storage::<comp::Ori>().maybe(), 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>(), &client.state().ecs().read_storage::<comp::LightEmitter>(),
) )
.join() .join()
.filter(|(pos, _, _)| { .filter(|(pos, _, _, _)| {
(pos.0.distance_squared(player_pos) as f32) (pos.0.distance_squared(player_pos) as f32)
< self.loaded_distance.powf(2.0) + LIGHT_DIST_RADIUS < 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 = { let rot = {
if let Some(o) = ori { if let Some(o) = ori {
Mat3::rotation_z(-o.0.x.atan2(o.0.y)) Mat3::rotation_z(-o.x.atan2(o.y))
} else { } else {
Mat3::identity() Mat3::identity()
} }
}; };
Light::new( Light::new(
pos.0 + (rot * light_emitter.offset), pos + (rot * light_emitter.offset),
light_emitter.col, light_emitter.col,
light_emitter.strength, light_emitter.strength,
) )
@ -246,17 +254,28 @@ impl Scene {
// Update shadow constants // Update shadow constants
let mut shadows = ( let mut shadows = (
&client.state().ecs().read_storage::<comp::Pos>(), &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::Scale>().maybe(),
&client.state().ecs().read_storage::<comp::Body>(), &client.state().ecs().read_storage::<comp::Body>(),
&client.state().ecs().read_storage::<comp::Stats>(), &client.state().ecs().read_storage::<comp::Stats>(),
) )
.join() .join()
.filter(|(_, _, _, stats)| !stats.is_dead) .filter(|(_, _, _, _, stats)| !stats.is_dead)
.filter(|(pos, _, _, _)| { .filter(|(pos, _, _, _, _)| {
(pos.0.distance_squared(player_pos) as f32) (pos.0.distance_squared(player_pos) as f32)
< (self.loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powf(2.0) < (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<_>>(); .collect::<Vec<_>>();
shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(player_pos) as i32); shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(player_pos) as i32);
shadows.truncate(MAX_SHADOW_COUNT); shadows.truncate(MAX_SHADOW_COUNT);

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
ecs::MyEntity,
hud::{DebugInfo, Event as HudEvent, Hud}, hud::{DebugInfo, Event as HudEvent, Hud},
key_state::KeyState, key_state::KeyState,
render::Renderer, render::Renderer,
@ -40,6 +41,14 @@ impl SessionState {
.camera_mut() .camera_mut()
.set_fov_deg(global_state.settings.graphics.fov); .set_fov_deg(global_state.settings.graphics.fov);
let hud = Hud::new(global_state, &client.borrow()); 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 { Self {
scene, scene,
client, client,
@ -55,7 +64,11 @@ impl SessionState {
/// Tick the session (and the client attached to it). /// Tick the session (and the client attached to it).
fn tick(&mut self, dt: Duration) -> Result<(), Error> { fn tick(&mut self, dt: Duration) -> Result<(), Error> {
self.inputs.tick(dt); 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 { match event {
Chat { Chat {
chat_type: _, chat_type: _,
@ -423,6 +436,22 @@ impl PlayState for SessionState {
global_state.settings.gameplay.zoom_inversion = zoom_inverted; global_state.settings.gameplay.zoom_inversion = zoom_inverted;
global_state.settings.save_to_file_warn(); 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) => { HudEvent::ToggleMouseYInvert(mouse_y_inverted) => {
global_state.window.mouse_y_inversion = mouse_y_inverted; global_state.window.mouse_y_inversion = mouse_y_inverted;
global_state.settings.gameplay.mouse_y_inversion = mouse_y_inverted; global_state.settings.gameplay.mouse_y_inversion = mouse_y_inverted;

View File

@ -85,8 +85,8 @@ impl Default for ControlSettings {
screenshot: KeyMouse::Key(VirtualKeyCode::F4), screenshot: KeyMouse::Key(VirtualKeyCode::F4),
toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6),
roll: KeyMouse::Mouse(MouseButton::Middle), roll: KeyMouse::Mouse(MouseButton::Middle),
respawn: KeyMouse::Mouse(MouseButton::Left), respawn: KeyMouse::Key(VirtualKeyCode::Space),
interact: KeyMouse::Key(VirtualKeyCode::E), interact: KeyMouse::Mouse(MouseButton::Right),
toggle_wield: KeyMouse::Key(VirtualKeyCode::T), toggle_wield: KeyMouse::Key(VirtualKeyCode::T),
charge: KeyMouse::Key(VirtualKeyCode::V), charge: KeyMouse::Key(VirtualKeyCode::V),
} }
@ -100,6 +100,10 @@ pub struct GameplaySettings {
pub pan_sensitivity: u32, pub pan_sensitivity: u32,
pub zoom_sensitivity: u32, pub zoom_sensitivity: u32,
pub zoom_inversion: bool, 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 mouse_y_inversion: bool,
pub crosshair_transp: f32, pub crosshair_transp: f32,
pub chat_transp: f32, pub chat_transp: f32,
@ -118,6 +122,10 @@ impl Default for GameplaySettings {
zoom_sensitivity: 100, zoom_sensitivity: 100,
zoom_inversion: false, zoom_inversion: false,
mouse_y_inversion: false, mouse_y_inversion: false,
toggle_debug: false,
sct: true,
sct_player_batch: true,
sct_damage_batch: false,
crosshair_transp: 0.6, crosshair_transp: 0.6,
chat_transp: 0.4, chat_transp: 0.4,
crosshair_type: CrosshairType::Round, crosshair_type: CrosshairType::Round,
@ -179,9 +187,9 @@ pub struct GraphicsSettings {
impl Default for GraphicsSettings { impl Default for GraphicsSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
view_distance: 5, view_distance: 10,
max_fps: 60, max_fps: 60,
fov: 75, fov: 50,
aa_mode: AaMode::Fxaa, aa_mode: AaMode::Fxaa,
} }
} }

View File

@ -305,7 +305,7 @@ impl Ui {
enum Placement { enum Placement {
Interface, Interface,
// Number of primitives left to render ingame and relative scaling/resolution // 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; 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). // 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 { 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 // Behind the camera or far away
Placement::InWorld(_, None) => continue, Placement::InWorld(_, None) => continue,
Placement::Interface => (self.ui.win_w, self.ui.win_h), Placement::Interface => (self.ui.win_w, self.ui.win_h),
@ -624,10 +624,12 @@ impl Ui {
.parameters; .parameters;
let pos_in_view = view_mat * Vec4::from_point(parameters.pos); let pos_in_view = view_mat * Vec4::from_point(parameters.pos);
let scale_factor = self.ui.win_w as f64 let scale_factor = self.ui.win_w as f64
/ (-2.0 / (-2.0
* pos_in_view.z as f64 * pos_in_view.z as f64
* (0.5 * fov as f64).tan() * (0.5 * fov as f64).tan()
// TODO: make this have no effect for fixed scale
* parameters.res as f64); * parameters.res as f64);
// Don't process ingame elements behind the camera or very far away // Don't process ingame elements behind the camera or very far away
placement = if scale_factor > 0.2 { placement = if scale_factor > 0.2 {
@ -642,32 +644,43 @@ impl Ui {
}); });
start = mesh.vertices().len(); start = mesh.vertices().len();
// Push new position command // 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 { if self.ingame_locals.len() > ingame_local_index {
renderer renderer
.update_consts( .update_consts(
&mut self.ingame_locals[ingame_local_index], &mut self.ingame_locals[ingame_local_index],
&[parameters.pos.into()], &[world_pos.into()],
) )
.unwrap(); .unwrap();
} else { } else {
self.ingame_locals.push( self.ingame_locals
renderer.create_consts(&[parameters.pos.into()]).unwrap(), .push(renderer.create_consts(&[world_pos.into()]).unwrap());
);
} }
self.draw_commands self.draw_commands
.push(DrawCommand::WorldPos(Some(ingame_local_index))); .push(DrawCommand::WorldPos(Some(ingame_local_index)));
ingame_local_index += 1; ingame_local_index += 1;
p_scale_factor = ((scale_factor * 10.0).log2().round().powi(2) p_scale_factor = if parameters.fixed_scale {
/ 10.0) self.scale.scale_factor_physical()
} else {
((scale_factor * 10.0).log2().round().powi(2) / 10.0)
.min(1.6) .min(1.6)
.max(0.2); .max(0.2)
};
// Scale down ingame elements that are close to the camera // Scale down ingame elements that are close to the camera
let res = if scale_factor > 3.2 { let res = if parameters.fixed_scale {
parameters.res * scale_factor as f32 / 3.2 (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 { } else {
parameters.res let res = parameters.res;
(res as f64, res as f64)
}; };
Placement::InWorld(parameters.num, Some(res)) Placement::InWorld(parameters.num, Some(res))

View File

@ -57,6 +57,9 @@ pub struct IngameParameters {
// Used for widgets that are rasterized before being sent to the gpu (text & images) // Used for widgets that are rasterized before being sent to the gpu (text & images)
// Potentially make this automatic based on distance to camera? // Potentially make this automatic based on distance to camera?
pub res: f32, 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 { pub struct State {
@ -74,10 +77,15 @@ impl<W: Ingameable> Ingame<W> {
num: widget.prim_count(), num: widget.prim_count(),
pos, pos,
res: 1.0, res: 1.0,
fixed_scale: false,
}, },
widget, widget,
} }
} }
pub fn fixed_scale(mut self) -> Self {
self.parameters.fixed_scale = true;
self
}
builder_methods! { builder_methods! {
pub resolution { parameters.res = f32 } pub resolution { parameters.res = f32 }
} }
@ -147,6 +155,7 @@ impl IngameAnchor {
num: 0, num: 0,
pos, pos,
res: 1.0, res: 1.0,
fixed_scale: false,
}, },
} }
} }