Compare commits

..

4 commits

Author SHA1 Message Date
1963659673 Update Rust crate hostname to 0.4.0 2025-02-28 09:02:24 +00:00
6648926f08 Fix rpg components & move core 2025-02-28 08:39:15 +01:00
d7a58d7352 Fix map 2025-02-28 08:19:35 +01:00
7253251e9d Fix engine creation 2025-02-28 07:08:03 +01:00
59 changed files with 190 additions and 5698 deletions

View file

@ -12,7 +12,6 @@ members = [
"engine",
"entity_manager",
"examples/simple_window",
"gavania-core",
"gltf-loader",
"loading-screen",
"lua-wrapper",
@ -45,7 +44,10 @@ reqwest = { version = "0.12.5", features = ["blocking"] }
shared_library = "0.1.9"
gltf = { version = "1.4.1", features = ["extras", "names"] }
mlua = { version = "0.9.9", features = ["lua54", "send", "vendored"] }
public-ip = { version = "0.2.2", default-features = false, features = ["all-providers", "tokio-dns-resolver"] }
public-ip = { version = "0.2.2", default-features = false, features = [
"all-providers",
"tokio-dns-resolver",
] }
async-std = { version = "1.12.0" }
if-addrs = { version = "0.13.0" }
hostname = { version = "0.4.0" }

View file

@ -87,6 +87,10 @@ impl World {
self.entity_object_manager.create_entity()
}
pub fn clone_without_components(&mut self, entity: &EntityObject) -> EntityObject {
entity.clone_without_components(self.entity_object_manager.fetch_add_entity_id())
}
pub fn entities(&self) -> impl Iterator<Item = &EntityObject> {
self.entities.values()
}

View file

@ -5,7 +5,11 @@ use crate::prelude::*;
use anyhow::Result;
pub trait AssetLoader {
fn load_entity(&mut self, assets: AssetHandler<'_>, entity_file: &str) -> Result<EntityObject>;
fn load_entity(
&mut self,
assets: &mut AssetHandler<'_>,
entity_file: &str,
) -> Result<EntityObject>;
}
pub struct AssetHandler<'a> {

View file

@ -168,21 +168,13 @@ impl Engine {
let asset_manager = AssetManager::new(&engine_settings)?;
if let Some(mut gui_info) = create_info.gui_info {
if let Font::Path(path) = &mut gui_info.font {
path.set_prefix(&create_info.resource_base_path);
}
gui_info.resource_directory.assume_prefix_free();
let gui_handler = GuiHandler::new(create_info.gui_info, &context)?;
let gui_post_process = Arc::new(GuiPostProcess(gui_handler.clone()));
context
.render_core()
.add_post_processing_routine(gui_post_process.clone());
let gui_handler = GuiHandler::new(gui_info, &context)?;
let gui_post_process = Arc::new(GuiPostProcess(gui_handler.clone()));
context
.render_core()
.add_post_processing_routine(gui_post_process.clone());
world.resources.insert(gui_handler);
}
world.resources.insert(gui_handler);
// default keyboard navigation
let mut direction_mapping = HashMap::new();

View file

@ -13,7 +13,7 @@ pub struct EngineCreateInfo<'a> {
pub vulkan_debug_info: VulkanDebugInfo,
pub volume_info: HashMap<String, f32>,
pub gui_info: Option<GuiHandlerCreateInfo<'a>>,
pub gui_info: GuiHandlerCreateInfo<'a>,
pub enable_backtrace: bool,
pub enable_mouse: bool,
@ -60,7 +60,7 @@ impl<'a> Default for EngineCreateInfo<'a> {
renderdoc: false,
},
volume_info: HashMap::new(),
gui_info: None,
gui_info: GuiHandlerCreateInfo::new(),
enable_backtrace: true,
enable_mouse: true,
enable_keyboard: true,

View file

@ -1,24 +0,0 @@
use std::any::Any;
use crate::prelude::*;
use anyhow::Result;
use utilities::prelude::cgmath::{Vector2, Vector3};
pub trait Map: Any + Send + Sync {
fn name(&self) -> &str;
fn check_walkability(
&self,
position: Vector2<f32>,
direction: Vector2<f32>,
radius: f32,
) -> Result<bool>;
fn get_height(&self, x: f32, y: f32) -> Result<f32>;
fn spawn_positions(&self) -> Result<Vec<Vector3<f32>>>;
fn leave_markers(&self) -> Result<Vec<Entity>>;
fn disable(&self, scene: &mut Scene) -> Result<()>;
}

View file

@ -1,5 +1,4 @@
pub mod components;
pub mod map;
pub mod prelude;
// mod query;

View file

@ -1,5 +1,3 @@
pub use super::map::Map;
pub use super::components::{animation::Animation, audio::Audio, draw::Draw};
pub use super::components::{

View file

@ -126,7 +126,7 @@ impl AssetLoader for EntityManager {
/// Loads an entity file and creates an Entity
fn load_entity(
&mut self,
mut assets: AssetHandler<'_>,
assets: &mut AssetHandler<'_>,
entity_file: &str,
) -> Result<EntityObject> {
// load entity file

View file

@ -1,7 +0,0 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#4F1E1C",
"titleBar.activeBackground": "#6F2A27",
"titleBar.activeForeground": "#FEFCFC"
}
}

View file

@ -1,29 +0,0 @@
[package]
name = "gavania-core"
version = "0.1.0"
authors = ["hodasemi <superschneider@t-online.de>"]
build = "src/osbuild.rs"
edition = "2024"
[dependencies]
rusqlite = { workspace = true }
paste = { workspace = true }
cgmath = { workspace = true }
reqwest = { workspace = true }
http = { workspace = true }
anyhow = { workspace = true }
destructure_traitobject = { workspace = true }
serde = { workspace = true }
assetpath = { workspace = true }
iterchunks = { workspace = true }
engine = { path = "../engine" }
promise = { path = "../promise" }
lua-wrapper = { path = "../lua-wrapper" }
controllable_thread = { path = "../controllable_thread" }
map = { path = "../map" }
rpg_components = { path = "../rpg_components" }
entity_manager = { path = "../entity_manager" }
[features]
wayland = []

View file

@ -1,38 +0,0 @@
use engine::prelude::*;
create_settings_section!(
GeneralSettings,
"General",
{
special_chance: f32,
radius: f32,
boss_hp_bar_radius: f32,
}
);
create_settings_section!(
BasicMobSettings,
"BasicMobs",
{
min_count: u32,
max_count: u32,
}
);
create_settings_section!(
EliteMobSettings,
"EliteMobs",
{
min_count: u32,
max_count: u32,
}
);
create_settings_container!(
MobSettings,
{
general: GeneralSettings,
basic_mobs: BasicMobSettings,
elite_mobs: EliteMobSettings,
}
);

View file

@ -1,2 +0,0 @@
pub mod mobs;
pub use mobs::*;

View file

@ -1,139 +0,0 @@
pub mod on_hit_particles;
pub mod particle_spawn;
pub mod prelude;
pub mod projectile;
pub mod selfcast;
use anyhow::Result;
use cgmath::Vector2;
use engine::prelude::*;
use entity_manager::*;
use rpg_components::components::character_status::CharacterStatus;
use rpg_components::components::level::{Level, LevelUpEvent};
use rpg_components::components::npc_type::{NPCBoss, NPCElite, NPCNormal, NPCType};
use rpg_components::components::statistics::Statistics;
use rpg_components::damage_type::DamageType;
use crate::game::content::prelude::*;
use crate::Game;
pub fn handle_npc_death<'a>(
game: &Game,
s: &mut impl SceneEntities,
collider: Entity,
mut collider_components: MultiMut<'a>,
) -> Result<()> {
// check if collider is dead, if so potentially create a loot chest and destroy the entity
if let Ok(collider_status) = collider_components.get::<CharacterStatus>() {
if collider_status.is_dead() {
let collider_level = collider_components.get::<Level>()?;
let collider_location = collider_components.get::<Location>()?;
if collider_components.get::<FactionPlayer>().is_ok() {
let npc_movement = collider_components.get::<Movement>()?;
npc_movement.set_direction(Vector2::zero());
let mut grave_stone = game
.entity_manager()
.load_entity(game.engine().assets(), "Grave Stone")?;
#[cfg(debug_assertions)]
{
grave_stone.debug_name = Some(format!("Player Grave Stone ({})", collider));
}
Location::new_and_setup(&mut grave_stone)?
.set_position(collider_location.position());
s.add_entity(grave_stone)?;
} else if collider_components.get::<FactionNPC>().is_ok() {
let npc_hitbox = collider_components.get::<HitBox>()?;
let ghost = Ghost::new(
&game,
collider_location,
npc_hitbox.radius(),
npc_hitbox.height(),
s.now(),
)?;
s.add_entity(ghost)?;
s.remove_entity(collider)?;
if let Some(loot_chest) = LootStash::handle_npc_death(
&game,
collider_level,
collider_location,
if collider_components.get::<NPCNormal>().is_ok() {
NPCType::Normal
} else if collider_components.get::<NPCElite>().is_ok() {
NPCType::Elite
} else if collider_components.get::<NPCBoss>().is_ok() {
NPCType::Boss
} else {
unreachable!("missing component!");
},
)? {
s.add_entity(loot_chest)?;
}
}
}
}
Ok(())
}
pub fn damage_and_experience(
base_damage: u32,
damage_type: DamageType,
collider_multi_mut: &mut MultiMut<'_>,
mut owner_components: MultiMut<'_>,
scene: &mut SceneContents<'_>,
) -> Result<()> {
if let Ok(collider_current_status) = collider_multi_mut.get::<CharacterStatus>() {
if let Ok(collider_stats) = collider_multi_mut.get::<Statistics>() {
if let Ok(collider_level) = collider_multi_mut.get::<Level>() {
let resistance = collider_stats.calculate_resistance(damage_type);
if resistance < base_damage {
let damage = base_damage - resistance;
let owner_status = owner_components.get::<CharacterStatus>()?;
collider_current_status.apply_damage(damage as f32);
scene.write_event(DamageEvent {
damage,
damage_type,
position: collider_multi_mut.get::<Location>()?.position(),
});
if collider_current_status.is_dead() {
if owner_components.get::<FactionPlayer>().is_ok() {
if !owner_status.is_dead() {
let level = owner_components.get::<Level>()?;
if level.add_experience(
collider_level.level(),
if collider_multi_mut.get::<NPCNormal>().is_ok() {
NPCType::Normal
} else if collider_multi_mut.get::<NPCElite>().is_ok() {
NPCType::Elite
} else if collider_multi_mut.get::<NPCBoss>().is_ok() {
NPCType::Boss
} else {
unreachable!("missing component!");
},
)? {
scene.write_event(LevelUpEvent);
}
}
}
}
}
}
}
}
Ok(())
}

View file

@ -1,67 +0,0 @@
use crate::*;
use anyhow::Result;
use cgmath::{Deg, Vector3};
use rpg_components::{components::abilityloader::AbilityLoader, damage_type::DamageType};
pub struct ArcInfo {
pub radius: f32,
pub base_damage: u32,
pub damage_type: DamageType,
pub owner: Entity,
}
pub struct OnHitParticles {
pub particle_system_info: ParticleSystemInfo,
pub collision_sound: String,
pub position: Vector3<f32>,
pub offset: Vector3<f32>,
pub arc_info: Option<ArcInfo>,
}
impl OnHitParticles {
pub fn setup(game_handle: &GameHandle, scene: &mut Scene) {
scene.register_event::<Self>();
scene.add_event_reader({
let game_handle = game_handle.clone();
move |scene, on_hit_particles: &Self| on_hit_particles.spawn(scene, &game_handle)
});
}
fn spawn(&self, scene: &mut SceneContents<'_>, game_handle: &GameHandle) -> Result<()> {
let game = game_handle.upgrade();
let mut entity = AbilityLoader::create_on_hit_particles(
game.engine(),
self.particle_system_info.clone(),
&self.collision_sound,
self.position,
self.offset,
scene.particle_system_vulkan_objects(),
|s| game.build_data_path(s),
)?;
if let Some(arc_info) = &self.arc_info {
entity.insert_component(AreaOfEffectArc::new(
Deg(180.0),
arc_info.radius,
arc_info.base_damage,
arc_info.damage_type,
arc_info.owner,
));
Faction::copy(
&mut scene.entity_mut(arc_info.owner)?.multi_mut(),
&mut entity,
);
}
scene.add_entity(entity)?;
Ok(())
}
}

View file

@ -1,58 +0,0 @@
use rpg_components::components::abilityloader::AbilityLoader;
use crate::*;
#[derive(Debug, Clone, Copy)]
pub enum ParticleSpawnExecutor {
AoE,
Projectile,
}
pub struct ParticleSpawn {
pub entity: Entity,
pub info: AbilityLoader,
pub executor: ParticleSpawnExecutor,
}
impl ParticleSpawn {
pub fn setup_event(game_handle: &GameHandle, scene: &mut Scene) {
scene.register_event::<Self>();
scene.add_event_reader({
let game_handle = game_handle.clone();
move |scene, particle_spawn: &Self| {
let particle_system_vulkan_objects =
unsafe { remove_life_time_mut(scene) }.particle_system_vulkan_objects();
let particle_system = {
let owner = scene.entity_mut(particle_spawn.entity)?;
let mut multi_mut = owner.multi_mut();
let draw = multi_mut.get::<Draw>()?;
match particle_spawn.executor {
ParticleSpawnExecutor::AoE => {
let aoe = multi_mut.get::<AreaOfEffect>()?;
aoe.create_particle_effect(
&game_handle,
&particle_spawn.info,
draw,
particle_system_vulkan_objects,
)?
}
ParticleSpawnExecutor::Projectile => Projectile::create_particles(
&particle_spawn.info,
&game_handle,
draw,
particle_system_vulkan_objects,
)?,
}
};
scene.insert_component(particle_spawn.entity, particle_system)
}
});
}
}

View file

@ -1,4 +0,0 @@
pub use super::on_hit_particles::{ArcInfo, OnHitParticles};
pub use super::particle_spawn::{ParticleSpawn, ParticleSpawnExecutor};
pub use super::projectile::{Projectile, ProjectileMarker};
pub use super::selfcast::SelfCast;

View file

@ -1,371 +0,0 @@
use engine::prelude::*;
use entity_manager::*;
use rpg_components::{
components::{abilityloader::AbilityLoader, statistics::Statistics},
damage_type::DamageType,
items::{ability_addon::AbilityAddonCollection, ability_book::AbilityBook},
};
use crate::game::content::abilities::handle_npc_death;
use anyhow::Result;
use cgmath::{vec3, Deg, Matrix2, Vector2};
use super::{
super::prelude::*,
particle_spawn::{ParticleSpawn, ParticleSpawnExecutor},
};
use crate::game::game::GameHandle;
use std::time::Duration;
const ODD_ANGLES: [Deg<f32>; 11] = [
Deg(0.0),
Deg(10.0),
Deg(-10.0),
Deg(20.0),
Deg(-20.0),
Deg(30.0),
Deg(-30.0),
Deg(40.0),
Deg(-40.0),
Deg(50.0),
Deg(-50.0),
];
const EVEN_ANGLES: [Deg<f32>; 10] = [
Deg(5.0),
Deg(-5.0),
Deg(15.0),
Deg(-15.0),
Deg(25.0),
Deg(-25.0),
Deg(35.0),
Deg(-35.0),
Deg(45.0),
Deg(-45.0),
];
pub struct ProjectileMarker;
impl EntityComponent for ProjectileMarker {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for ProjectileMarker {
fn debug_name() -> &'static str {
"ProjectileMarker"
}
}
#[derive(Clone)]
pub struct Projectile;
impl Projectile {
fn hit_event(
me: Entity,
collider: Entity,
owner: Entity,
level: u32,
addons: &AbilityAddonCollection,
game_handle: &GameHandle,
scene: &mut SceneContents<'_>,
) -> Result<CollisionEventType> {
// this handle is only needed for calculating loot bonus when creating a loot stash
// it only queries all users and their loot bonus passive
let s = unsafe { remove_life_time_mut(scene) };
let game = game_handle.upgrade();
let mut entity_multi_mut = scene.entities_multi_mut();
let collider_object = match entity_multi_mut.get(collider) {
Ok(collider) => collider,
Err(_) => return Ok(CollisionEventType::Ignore),
};
let projectile_object = match entity_multi_mut.get(me) {
Ok(projectile_object) => projectile_object,
Err(_) => return Ok(CollisionEventType::Ignore),
};
let mut collider_components = collider_object.multi_mut();
let mut projectile_components = projectile_object.multi_mut();
let my_faction = Faction::get(&mut projectile_components).unwrap();
let ability_info = projectile_components.get::<AbilityLoader>()?;
// don't collide with same 'Team' or neutral NPC's
let collider_faction = Faction::get(&mut collider_components);
if !my_faction.check_collision(collider_faction) {
// if there is no entity faction, that means we hit an environment object
// e.g. a tree or a stone.
// we then bounce or destroy the projectile
if addons.bounce() {
let position = projectile_components.get::<Location>().unwrap().position();
let movement = projectile_components.get::<Movement>().unwrap();
let direction = movement.direction();
let inverted = -direction;
let random_angle = Deg(Random::range_f32(-60.0, 60.0));
let new_direction = Matrix2::from_angle(random_angle) * inverted;
movement.set_direction(new_direction);
let location_info = projectile_components.get::<AbilityLocationInfo>()?;
location_info.new_direction_at(position);
}
return Ok(CollisionEventType::Ignore);
}
// skip any damage related calculation when the owner isn't present anymore
if let Ok(owner_object) = entity_multi_mut.get(owner) {
let owner_stats = owner_object.get_component::<Statistics>()?;
let mut base_damage = ability_info.damage(level, addons, owner_stats);
let explosion_radius =
ability_info.settings.parameter.target_radius + addons.explosion_radius();
let npc_location = projectile_components.get::<Location>().unwrap();
// don't cast an explosion when the radius is 0
if explosion_radius == 0.0 {
base_damage = AbilityLoader::check_for_crit(base_damage, owner_stats);
super::damage_and_experience(
base_damage,
ability_info.settings.parameter.damage_type,
&mut collider_components,
owner_object.multi_mut(),
s,
)?;
unsafe {
collider_components.clear_all_usages();
}
// spawn on hit particle effects
if !ability_info.settings.parameter.on_hit_particles.is_empty() {
let file_name =
game.build_data_path(&ability_info.settings.parameter.on_hit_particles);
let info = ParticleSystemInfo::load(file_name)?;
s.write_event(OnHitParticles {
particle_system_info: info,
collision_sound: ability_info.settings.parameter.on_collision_sound.clone(),
position: npc_location.position(),
offset: npc_location.offset(),
arc_info: None,
});
}
unsafe {
collider_components.clear_all_usages();
}
handle_npc_death(&game, s, collider, collider_components)?;
}
// if there is a explosion radius, create an explosion according to its radius
else {
let file_name = match ability_info.settings.parameter.damage_type {
DamageType::Physical => todo!(),
DamageType::Fire => game.build_data_path("particles/fire_explosion.particle"),
DamageType::Water => game.build_data_path("particles/ice_explosion.particle"),
DamageType::Air => todo!(),
};
let mut info = ParticleSystemInfo::load(file_name)?;
info.particles.minimum_velocity =
explosion_radius / info.system.duration.as_secs_f32();
info.particles.maximum_velocity = info.particles.minimum_velocity;
// spawn on hit particle effects
s.write_event(OnHitParticles {
particle_system_info: info,
collision_sound: ability_info.settings.parameter.on_collision_sound.clone(),
position: npc_location.position(),
offset: npc_location.offset(),
arc_info: Some(ArcInfo {
radius: explosion_radius,
base_damage,
damage_type: ability_info.settings.parameter.damage_type,
owner,
}),
});
}
}
scene.remove_entity(me)?;
Ok(CollisionEventType::Ignore)
}
pub fn create_particles(
info: &AbilityLoader,
game_handle: &GameHandle,
draw: &mut Draw,
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
) -> Result<ParticleSystem> {
let game = game_handle.upgrade();
let file_name = game.build_data_path(&info.settings.parameter.trail);
let mut particle_info = ParticleSystemInfo::load(file_name)?;
particle_info.system.duration = Duration::from_secs(300).into();
particle_info.particles.minimum_velocity = info.settings.parameter.speed / 3.0;
particle_info.particles.maximum_velocity = info.settings.parameter.speed / 2.0;
ParticleSystem::new_with_vk_objects(
particle_info,
game.engine(),
particle_system_vulkan_objects,
draw,
)
}
}
impl Projectile {
pub fn execute(
ability: &AbilityLoader,
_owner: Entity,
owner_components: &mut MultiMut<'_>,
direction: Vector2<f32>,
book: &AbilityBook,
game_handle: &GameHandle,
entities: &mut Entities<'_>,
events: &mut ContentEvents<'_>,
) -> Result<()> {
let mut projectile_count = 1 + book.addons().additional_projectiles() as usize;
let angles: &[Deg<f32>] = if (projectile_count % 2) == 0 {
projectile_count = projectile_count.min(EVEN_ANGLES.len());
&EVEN_ANGLES
} else {
projectile_count = projectile_count.min(ODD_ANGLES.len());
&ODD_ANGLES
};
let owner_hitbox = owner_components.get::<HitBox>()?;
let owner_location = owner_components.get::<Location>()?;
let projectile_size = book.addons().size();
let projectile_speed = book.addons().projectile_speed();
let projectile_distance = ability.settings.parameter.distance + book.addons().distance();
let ability_data = book.ability().data();
let game = game_handle.upgrade();
(0..projectile_count)
.into_iter()
.try_for_each(|i| -> Result<()> {
let angle = &angles[i];
let mut projectile_entity = game
.entity_manager()
.load_entity(game.engine().assets(), &ability.settings.meta.entity)?;
{
// alter the hitbox parameters
let projectile_hitbox = projectile_entity.get_component_mut::<HitBox>()?;
// Multiply the radius with potential ability modifications
projectile_hitbox.set_radius(projectile_size * projectile_hitbox.radius());
// set on hit event for the hitbox (only the server side will check for it)
projectile_hitbox.set_event({
let owner = _owner;
let level = book.level();
let addons = book.addons().clone();
let game_handle = game_handle.clone();
move |scene, me, collider| {
Self::hit_event(
me,
collider,
owner,
level,
&addons,
&game_handle,
scene,
)
}
});
let hitbox_radius = projectile_hitbox.radius();
let projectile_location = Location::new_and_setup(&mut projectile_entity)?;
// set projectile to owner location and set rotation accordingly
projectile_location.set_scale_uniform(projectile_size);
let direction = Matrix2::from_angle(*angle) * direction;
let position = owner_location.position()
+ direction.extend(0.0) * (hitbox_radius * 1.1 + owner_hitbox.radius());
projectile_location.update(position, direction);
// apply height offset
projectile_location.set_offset(vec3(
0.0,
0.0,
ability_data.settings.parameter.height_offset,
));
let mut projectile_movement = Movement::new(
ability.settings.parameter.speed + projectile_speed,
projectile_entity.get_component_mut::<Audio>().ok(),
);
if !ability_data.settings.parameter.idle_sound.is_empty() {
let audio = projectile_entity.get_component_mut::<Audio>().unwrap();
audio.add_custom_sound(
game.build_data_path(&ability_data.settings.parameter.idle_sound),
"idle_sound",
)?;
};
// set movement
projectile_movement.set_direction(direction);
let ability_location_info = AbilityLocationInfo::new(
position,
projectile_distance,
projectile_entity.get_component_mut::<Audio>().unwrap(),
);
if !ability_data.settings.parameter.trail.is_empty() {
events.write_event(ParticleSpawn {
entity: projectile_entity.as_entity(),
info: ability_data.clone(),
executor: ParticleSpawnExecutor::Projectile,
});
}
projectile_entity.insert_component(ability_location_info);
projectile_entity.insert_component(ability.clone());
projectile_entity.insert_component(projectile_movement);
projectile_entity.insert_component(ProjectileMarker);
Faction::copy(owner_components, &mut projectile_entity);
}
entities.add_entity(projectile_entity)?;
Ok(())
})?;
Ok(())
}
}

View file

@ -1,64 +0,0 @@
#![allow(unused)]
use anyhow::Result;
use engine::prelude::*;
use entity_manager::*;
use rpg_components::{
components::{abilityloader::AbilityLoader, statistics::Statistics},
items::ability_book::AbilityBook,
};
use crate::game::{
content::prelude::*,
game::{Game, GameHandle},
};
use cgmath::{Deg, Vector2};
use std::sync::{Arc, Mutex, MutexGuard};
use super::particle_spawn::ParticleSpawn;
pub struct SelfCast;
impl SelfCast {
pub fn execute(
ability: &AbilityLoader,
owner: Entity,
owner_components: &mut MultiMut<'_>,
direction: Vector2<f32>,
book: &AbilityBook,
game_handle: &GameHandle,
entities: &mut Entities<'_>,
events: &mut ContentEvents<'_>,
) -> Result<()> {
let location = owner_components.get::<Location>()?;
let owner_stats = owner_components.get::<Statistics>()?;
let hit_box = owner_components.get::<HitBox>()?;
let mut base_damage = ability.damage(book.level(), book.addons(), owner_stats);
let range = ability.settings.parameter.radius * book.addons().size();
let (mut entity_object, particle_spawn) = AreaOfEffect::new(
direction,
location.position(),
Deg(ability.settings.parameter.arc),
range,
hit_box.height() * 0.75,
owner,
ability.clone(),
base_damage,
game_handle.clone(),
)?;
Faction::copy(owner_components, &mut entity_object);
entities.add_entity(entity_object)?;
if let Some(particle_spawn) = particle_spawn {
events.write_event(particle_spawn);
}
Ok(())
}
}

View file

@ -1,182 +0,0 @@
use crate::game::game::Game;
use anyhow::Result;
use engine::prelude::*;
use engine::prelude::gltf_loader::prelude::*;
use cgmath::{vec2, vec3, Matrix4, SquareMatrix};
use std::{
sync::{Arc, Weak},
time::Duration,
};
create_settings_section!(
SlideSettings,
"Meta",
{
duration: f32,
count: u32,
}
);
create_settings_container!(
SlideContainer,
{
settings: SlideSettings,
}
);
pub struct Slide {
weak_scene: Weak<Scene>,
name: String,
start: Duration,
duration: Duration,
count: u32,
single_width: f32,
vertex_buffer: Arc<Buffer<PositionColorUV>>,
}
impl Slide {
pub fn from_create_info(info: SlideCreateInfo) -> Result<Self> {
todo!()
}
pub fn new(name: &str, game: &GameHandle, scene: &Arc<Scene>) -> Result<Arc<Entity>> {
let info =
SlideContainer::load(&format!("{}/{}/info.conf", game.slides_directory(), name))?
.settings;
let image = {
let mut slide_images = game.slide_images()?;
let image_path = format!("{}/{}/texture.png", game.slides_directory(), name);
match slide_images.get(&image_path) {
Some(image) => image.clone(),
None => {
let image = Image::from_file(&image_path)?
.attach_sampler(Sampler::pretty_sampler().build(game.engine().device())?)
.build(game.engine().device(), game.engine().queue())?;
slide_images.insert(image_path, image.clone());
image
}
}
};
let (vertex_buffer, width_ratio) = {
let total_width = image.width();
let single_width = total_width / info.count;
let height = image.height();
let aspect_ratio = single_width as f32 / height as f32;
let half = aspect_ratio / 2.0;
let width_ratio = single_width as f32 / total_width as f32;
let data = [
PositionColorUV::new(vec3(-half, 0.0, 0.0), vec2(0.0, 1.0)),
PositionColorUV::new(vec3(half, 0.0, 0.0), vec2(width_ratio, 1.0)),
PositionColorUV::new(vec3(half, 0.0, 1.0), vec2(width_ratio, 0.0)),
PositionColorUV::new(vec3(half, 0.0, 1.0), vec2(width_ratio, 0.0)),
PositionColorUV::new(vec3(-half, 0.0, 1.0), vec2(0.0, 0.0)),
PositionColorUV::new(vec3(-half, 0.0, 0.0), vec2(0.0, 1.0)),
];
let buffer = Buffer::builder()
.set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)
.set_memory_usage(MemoryUsage::CpuOnly)
.set_data(&data)
.build(game.engine().device().clone())?;
(buffer, width_ratio)
};
let width_ratio = 0.0;
let meshes = {
let mut mesh = AssetMesh::new(game.engine().device(), scene.scene_type())?;
mesh.add_primitive(
vertex_buffer.clone(),
Some(image),
None,
PrimitiveMaterial {
emissive_factor: [1.0, 1.0, 1.0],
..PrimitiveMaterial::default()
},
false,
)?;
vec![mesh]
};
let entity_builder = Entity::new_custom(game.engine().context(), meshes);
let entity_builder = Entity::new_custom();
let entity = entity_builder.build()?;
let slide = Slide {
weak_scene: Arc::downgrade(scene),
name: name.to_string(),
start: Duration::default(),
duration: Duration::from_secs_f32(info.duration),
count: info.count,
single_width: width_ratio,
vertex_buffer,
};
entity.components()?.insert(slide);
Ok(entity)
}
pub fn update(&self, now: Duration) -> Result<()> {
let time_diff = now - self.start;
let time = time_diff.as_secs_f32() % self.duration.as_secs_f32();
let time_slice = self.duration.as_secs_f32() / self.count as f32;
let slice = (time / time_slice).floor();
let mut mapped = self.vertex_buffer.map_complete()?;
mapped[0].uv.x = slice * self.single_width;
mapped[1].uv.x = (slice + 1.0) * self.single_width;
mapped[2].uv.x = (slice + 1.0) * self.single_width;
mapped[3].uv.x = (slice + 1.0) * self.single_width;
mapped[4].uv.x = slice * self.single_width;
mapped[5].uv.x = slice * self.single_width;
Ok(())
}
}
impl EntityComponent for Slide {
fn enable(&mut self, scene: &Scene) -> Result<()> {
if let Some(scene) = self.weak_scene.upgrade() {
self.start = scene.now();
}
Ok(())
}
fn name(&self) -> &str {
"Slide"
}
}
#[derive(Serialize, Deserialize)]
pub struct SlideCreateInfo {
// TODO
}
impl<'a> ComponentCreateInfo<'a> for SlideCreateInfo {}

View file

@ -1,38 +0,0 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum AbilityType {
Projectile,
SelfCast,
}
impl Default for AbilityType {
fn default() -> Self {
Self::Projectile
}
}
impl std::str::FromStr for AbilityType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"Projectile" => Ok(AbilityType::Projectile),
"Self-Cast" => Ok(AbilityType::SelfCast),
_ => Err(anyhow::Error::msg(format!(
"Failed parsing AbilityType from {}",
s
))),
}
}
}
impl std::fmt::Display for AbilityType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
AbilityType::Projectile => write!(f, "Projectile"),
AbilityType::SelfCast => write!(f, "Self-Cast"),
}
}
}

View file

@ -1,98 +0,0 @@
use engine::prelude::*;
use anyhow::Result;
use cgmath::Vector3;
use cgmath::InnerSpace;
#[derive(Debug, Serialize, Deserialize)]
struct AbilityLocationSplit {
start: Vector3<f32>,
end: Option<Vector3<f32>>,
}
impl AbilityLocationSplit {
fn new(p: Vector3<f32>) -> Self {
Self {
start: p,
end: None,
}
}
// sets end point and creates a new split at this position
fn finalize(&mut self, p: Vector3<f32>) -> Self {
self.end = Some(p);
Self::new(p)
}
// calculates length, if end is not set it uses `p`
fn length(&self, p: Vector3<f32>) -> f32 {
let end = match self.end {
Some(p) => p,
None => p,
};
(end - self.start).magnitude()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AbilityLocationInfo {
splits: Vec<AbilityLocationSplit>,
allowed_distance: f32,
#[serde(skip)]
audio: UnsafeComponentStore<Audio>,
}
impl AbilityLocationInfo {
pub fn new(position: Vector3<f32>, distance: f32, audio: &mut Audio) -> Self {
AbilityLocationInfo {
splits: vec![AbilityLocationSplit::new(position)],
allowed_distance: distance,
audio: audio.into(),
}
}
pub fn post_setup(&mut self, audio: &mut Audio) {
self.audio = audio.into();
}
pub fn new_direction_at(&mut self, p: Vector3<f32>) {
let new_split = self.splits.last_mut().unwrap().finalize(p);
self.splits.push(new_split);
}
pub fn check_distance(&self, p: Vector3<f32>) -> bool {
let mut total = 0.0;
for split in self.splits.iter() {
total += split.length(p);
}
total < self.allowed_distance
}
}
impl EntityComponent for AbilityLocationInfo {
fn enable(&mut self, _scene: &mut Scene) -> Result<()> {
unsafe { self.audio.as_mut() }.play("idle_sound", true)?;
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for AbilityLocationInfo {
fn debug_name() -> &'static str {
"AbilityLocationInfo"
}
}

View file

@ -1,261 +0,0 @@
use anyhow::Result;
use assetpath::AssetPath;
use engine::prelude::*;
use cgmath::Vector3;
use std::path::Path;
const ABILITY_SUFFIX: &str = ".abil";
create_settings_section!(
AbilityMetaSettings,
"Meta",
{
icon: AssetPath,
entity: String,
particles: String,
},
Serialize, Deserialize,
);
create_settings_section!(
AbilityParameterSettings,
"Parameter",
{
// shared
ability_type: AbilityType,
light: bool,
idle_sound: String,
base_damage: u32,
damage_per_level: u32,
base_mana_cost: u32,
mana_cost_per_level: u32,
damage_type: DamageType,
cool_down: PersistentDuration,
// projectile
trail: String,
on_hit_particles: String,
height_offset: f32,
on_collision_sound: String,
target_radius: f32,
target_particles: String,
speed: f32,
distance: f32,
// self-cast
t_min: f32,
procedure_count: u32,
delay: PersistentDuration,
duration: PersistentDuration,
arc: f32,
radius: f32,
},
Serialize, Deserialize,
);
create_settings_container!(
AbilitySettings,
{
meta: AbilityMetaSettings,
parameter: AbilityParameterSettings,
},
Serialize, Deserialize,
);
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
pub struct AbilityLoader {
file_path: AssetPath,
name: String,
pub settings: AbilitySettings,
}
impl AbilityLoader {
pub fn load(data_path: &str, file: AssetPath) -> Result<Self> {
let mut settings = AbilitySettings::load(file.clone())?;
settings.meta.icon.set_prefix(data_path);
let mut ability_loader = AbilityLoader::default();
ability_loader.file_path = file.clone();
ability_loader.name = Path::new(&file.full_path())
.file_stem()
.expect("could not get file stem in ability loader")
.to_str()
.expect("failed converting OsStr to string")
.to_string();
ability_loader.settings = settings;
Ok(ability_loader)
}
pub fn save(&self) -> Result<()> {
self.settings.store()?;
Ok(())
}
pub fn list_all_ability_files(ability_directory: &AssetPath) -> Result<Vec<AssetPath>> {
Ok(search_dir_recursively(
&ability_directory.full_path(),
ABILITY_SUFFIX,
)?)
}
pub fn create_ability(&self) -> Ability {
Ability { data: self.clone() }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, name: &str) {
self.name = name.to_string();
}
pub fn set_path(&mut self, ability_directory: &str) {
self.file_path = AssetPath::from((
ability_directory,
format!("{}{}", self.name.clone(), ABILITY_SUFFIX),
));
self.settings.file_name = self.file_path.clone();
}
pub fn icon_name(&self) -> &AssetPath {
&self.settings.meta.icon
}
pub fn entity_name(&self) -> &str {
&self.settings.meta.entity
}
pub fn check_for_crit(dmg: u32, owner_stats: &Statistics) -> u32 {
if Coin::flip(owner_stats.critical_hit_chance.raw() / 100.0) {
let crit_dmg = ((owner_stats.critical_hit_damage.raw() + 1.0) * dmg as f32) as u32;
println!("CRIT: {} from base {}", crit_dmg, dmg);
crit_dmg
} else {
dmg
}
}
pub fn base_damage(&self, ability_level: u32) -> u32 {
self.settings.parameter.base_damage
+ self.settings.parameter.damage_per_level * (ability_level - 1)
}
pub fn damage(
&self,
ability_level: u32,
addons: &AbilityAddonCollection,
owner_stats: &Statistics,
) -> u32 {
// calculate damage of base ability
let ability_base_damage = self.base_damage(ability_level);
// get bonus damage from statistics
let stats_damage = match self.settings.parameter.damage_type {
DamageType::Air => owner_stats.air_damage.raw(),
DamageType::Fire => owner_stats.fire_damage.raw(),
DamageType::Water => owner_stats.water_damage.raw(),
DamageType::Physical => owner_stats.physical_damage.raw(),
};
// damage from addons multiplied with level
let addon_damage = addons.damage() * ability_level;
// sum up
ability_base_damage + stats_damage + addon_damage
}
pub fn create_on_hit_particles2(
&self,
engine: &Engine,
position: Vector3<f32>,
offset: Vector3<f32>,
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
build_path: impl Fn(&str) -> AssetPath,
) -> Result<Option<EntityObject>> {
Ok(if self.settings.parameter.on_hit_particles.is_empty() {
None
} else {
let file_name = build_path(&self.settings.parameter.on_hit_particles);
let info = ParticleSystemInfo::load(file_name)?;
Some(Self::create_on_hit_particles(
engine,
info,
&self.settings.parameter.on_collision_sound,
position,
offset,
particle_system_vulkan_objects,
build_path,
)?)
})
}
pub fn create_on_hit_particles(
engine: &Engine,
info: ParticleSystemInfo,
on_collision_sound: &str,
position: Vector3<f32>,
offset: Vector3<f32>,
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
build_path: impl Fn(&str) -> AssetPath,
) -> Result<EntityObject> {
let mut particle_entity = engine.assets().empty_entity();
{
let mut draw = Draw::new(Vec::new());
let particles = ParticleSystem::new_with_vk_objects(
info,
engine,
particle_system_vulkan_objects,
&mut draw,
)?;
if !on_collision_sound.is_empty() {
let mut audio = Audio::new(engine.context(), None)?;
audio.set_sound(build_path(on_collision_sound), ON_ENABLE_SOUND)?;
particle_entity.insert_component(audio);
}
particle_entity.insert_component(draw);
particle_entity.insert_component(particles);
let location = Location::new_and_setup(&mut particle_entity)?;
location.set_position(position);
location.set_offset(offset);
particle_entity.insert_component(BoundingBox {
min: [-0.5, -0.5, 0.0],
max: [0.5, 0.5, 1.0],
});
}
Ok(particle_entity)
}
}
impl EntityComponent for AbilityLoader {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for AbilityLoader {
fn debug_name() -> &'static str {
"AbilityLoader"
}
}

View file

@ -1,3 +0,0 @@
mod simple;
pub use simple::SimpleAI;

View file

@ -1,244 +0,0 @@
use crate::game::{content::prelude::*, game::GameHandle};
use crate::Result;
use engine::prelude::*;
use cgmath::{vec2, InnerSpace, Vector2, Vector3};
use entity_manager::*;
use rpg_components::ability_type::AbilityType;
use rpg_components::components::ability_slots::AbilitySlots;
use rpg_components::components::character_status::CharacterStatus;
use rpg_components::damage_type::DamageType;
// space between user and ai hitbox
const SAFETY_RADIUS: f32 = 0.3;
const ZERO_MOVEMENT: Vector2<f32> = vec2(0.0, 0.0);
pub struct SimpleAI {
pub aggro_radius: f32,
pub deaggro_radius: f32,
pub owner: Entity,
pub target: Option<Entity>,
}
impl SimpleAI {
pub fn new(aggro_radius: f32, deaggro_radius: f32, owner: Entity) -> Self {
Self {
aggro_radius,
deaggro_radius,
owner,
target: None,
}
}
pub fn update(
&mut self,
animation: &mut Animation,
draw: &mut Draw,
animation_info: &mut AnimationInfo,
character_status: &mut CharacterStatus,
movement: &mut Movement,
location: &Location,
abilities: &mut AbilitySlots,
hitbox: &mut HitBox,
scene_contents: &mut SceneContents<'_>,
game: &GameHandle,
user_entity: Entity,
) -> Result<()> {
match self.check_target(location, hitbox, scene_contents, user_entity)? {
Some((direction, distance_to_target, min_distance)) => {
let mut can_move = true;
for ability_opt in abilities.iter_mut() {
if let Some(book) = ability_opt {
// do nothing with this ability when user is out of range
if distance_to_target > book.ability().data().settings.parameter.distance {
continue;
}
let now = scene_contents.now();
// check if ability can be cast
if !animation_info.is_locked()
&& book.validate_use(now, character_status, location)?
{
{
// TODO: further separation of animation types (bows, ...)
let animation_type =
match book.ability().data().settings.parameter.damage_type {
DamageType::Physical => AnimationType::Attack,
_ => AnimationType::Cast,
};
animation_info.set_animation(
animation,
draw,
Some(animation_type),
now,
true,
false,
)?;
}
let owner = unsafe {
scene_contents.entities.entity_mut_unchecked(self.owner)?
};
let mut multi_mut = owner.multi_mut();
// actually cast the ability
println!("ai casts ability");
match book.ability().data().settings.parameter.ability_type {
AbilityType::Projectile => Projectile::execute(
book.ability().data(),
self.owner,
&mut multi_mut,
location.direction(),
book,
game,
&mut scene_contents.entities,
&mut scene_contents.events,
)?,
AbilityType::SelfCast => todo!(),
}
can_move = false;
break;
}
}
}
if can_move {
if distance_to_target > min_distance {
movement.set_direction(direction);
} else {
movement.set_direction(ZERO_MOVEMENT);
}
} else {
movement.set_direction(ZERO_MOVEMENT);
}
}
None => {
movement.set_direction(ZERO_MOVEMENT);
}
}
Ok(())
}
#[inline]
fn check_target(
&mut self,
location: &Location,
hitbox: &HitBox,
scene: &impl SceneEntities,
user_entity: Entity,
) -> Result<Option<(Vector2<f32>, f32, f32)>> {
let my_position = location.position();
match &self.target {
Some(target) => {
let distance = match scene.entity(*target).ok() {
Some(object) => {
let target_position = object.get_component::<Location>()?.position();
(target_position - my_position).magnitude()
}
None => 1_000_000.0,
};
if distance > self.deaggro_radius {
self.target = None;
self.find_target(my_position, scene, user_entity)?;
}
}
None => {
self.find_target(my_position, scene, user_entity)?;
}
}
self.target
.as_ref()
.map(|target| {
let (position, radius) = {
let object = scene.entity(*target)?;
(
object.get_component::<Location>()?.position(),
object.get_component::<HitBox>()?.radius(),
)
};
let direction = position - my_position;
let magnitude = direction.magnitude();
Ok((
direction.truncate().normalize(),
magnitude,
radius + hitbox.radius() + SAFETY_RADIUS,
))
})
.transpose()
}
// If there is no target set, choose the closest user
#[inline]
fn find_target(
&mut self,
my_position: Vector3<f32>,
scene: &impl SceneEntities,
user_entity: Entity,
) -> Result<()> {
assert!(self.target.is_none());
let mut closest = None;
if let Some(user_position) = scene
.entity(user_entity)
.ok()
.map(|user_object| user_object.get_component::<Location>().unwrap().position())
{
let distance = (user_position - my_position).magnitude();
if distance <= self.aggro_radius {
match &closest {
Some((_entity, current_distance)) => {
if distance < *current_distance {
closest = Some((user_entity, distance));
}
}
None => {
closest = Some((user_entity, distance));
}
}
}
}
if let Some((entity, _)) = closest {
self.target = Some(entity);
}
Ok(())
}
}
impl EntityComponent for SimpleAI {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for SimpleAI {
fn debug_name() -> &'static str {
"SimpleAI"
}
}

View file

@ -1,384 +0,0 @@
use anyhow::Result;
use cgmath::{Deg, Vector2, Vector3};
use rpg_components::components::abilityloader::AbilityLoader;
use std::collections::HashSet;
use std::time::Duration;
use crate::game::{
content::{
abilities::particle_spawn::{ParticleSpawn, ParticleSpawnExecutor},
prelude::*,
},
game::GameHandle,
};
use engine::prelude::*;
pub struct ParticleInfo {
pub radius: f32,
}
pub struct AreaOfEffect {
direction: Vector2<f32>,
position: Vector3<f32>,
arc: Deg<f32>,
radius: f32,
// height: f32,
ability_info: AbilityLoader,
base_damage: u32,
start: Duration,
owner: Entity,
game: GameHandle,
light: Option<Light>,
proc_slots: Vec<(Duration, HashSet<Entity>)>,
audio: Option<UnsafeComponentStore<Audio>>,
}
impl AreaOfEffect {
pub fn post_setup(&mut self, audio: Option<&mut Audio>) {
self.audio = audio.map(|audio| audio.into());
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::new_ret_no_self)]
pub fn new(
direction: Vector2<f32>,
position: Vector3<f32>,
arc: Deg<f32>,
radius: f32,
height: f32,
owner: Entity,
ability_info: AbilityLoader,
base_damage: u32,
game: GameHandle,
) -> Result<(EntityObject, Option<ParticleSpawn>)> {
let mut particle_spawn = None;
let proc_slots = Self::calculate_proc_slots(
ability_info.settings.parameter.procedure_count,
ability_info.settings.parameter.delay.into(),
ability_info.settings.parameter.duration.into(),
)?
.into_iter()
.map(|time| (time, HashSet::new()))
.collect();
let me = Self {
direction,
position: position,
arc,
radius,
// height,
ability_info: ability_info.clone(),
start: Duration::default(),
base_damage,
owner,
game,
light: None,
audio: None,
proc_slots,
};
let mut entity = me.game.upgrade().engine().assets().empty_entity();
// don't create an extra entity for now if duration is zero or if the ability has no delay
if me.ability_info.settings.parameter.duration != PersistentDuration::from_secs(0)
|| me.ability_info.settings.parameter.delay != PersistentDuration::from_secs(0)
{
entity.insert_component(Draw::new(vec![]));
if !me.ability_info.settings.parameter.idle_sound.is_empty() {
let mut audio = Audio::new(me.game.upgrade().engine().context(), None)?;
audio.add_custom_sound(
me.game
.upgrade()
.build_data_path(&me.ability_info.settings.parameter.idle_sound),
"idle_sound",
)?;
entity.insert_component(audio);
};
if !ability_info.settings.meta.particles.is_empty() {
particle_spawn = Some(ParticleSpawn {
entity: entity.as_entity(),
info: ability_info,
executor: ParticleSpawnExecutor::AoE,
});
}
Location::new_and_setup(&mut entity)?.set_position(position);
entity.insert_component(me.create_aoe_arc()?);
entity.insert_component(me);
entity.insert_component(BoundingBox {
min: [-radius, -radius, 0.0],
max: [radius, radius, height],
});
}
#[cfg(debug_assertions)]
{
entity.debug_name = Some("AoE".to_string());
}
Ok((entity, particle_spawn))
}
pub fn calculate_proc_slots(
count: u32,
delay: Duration,
duration: Duration,
) -> Result<Vec<Duration>> {
if count == 0 {
return Err(anyhow::Error::msg("Proc Count 0 in AoE is useless"));
}
// first proc is at `delay` time
let mut procs = vec![delay];
if count == 1 {
if duration > Duration::from_secs(0) {
return Err(anyhow::Error::msg(
"Undefined when proc should happen if there is only 1 but duration is there",
));
}
// nothing else needs to be done here
} else {
if duration == Duration::from_secs(0) {
return Err(anyhow::Error::msg("Multiple procs happen at the same time"));
}
let time_slice = duration / (count - 1);
for i in 1..count {
procs.push(delay + time_slice * i);
}
}
Ok(procs)
}
pub fn check_life_time(&mut self, now: Duration) -> bool {
let duration: Duration = self.ability_info.settings.parameter.duration.into();
let delay = self.ability_info.settings.parameter.delay.into();
(self.start + delay + duration) >= now
}
pub fn check_entity(&mut self, now: Duration, collider: Entity) -> bool {
// find next smallest time
match self
.proc_slots
.iter_mut()
.rev()
.find(|(time, _)| (self.start + *time) <= now)
{
Some((_, entities)) => entities.insert(collider),
None => false,
}
}
fn create_aoe_arc(&self) -> Result<AreaOfEffectArc> {
Ok(AreaOfEffectArc::new(
self.arc,
self.radius,
self.base_damage,
self.ability_info.settings.parameter.damage_type,
self.owner,
))
}
#[inline]
fn create_lighting(
engine: &Engine,
ability_info: &AbilityLoader,
mut position: Vector3<f32>,
info: &ParticleSystemInfo,
) -> Result<Option<Light>> {
let mut light = if ability_info.settings.parameter.light {
let mut light = engine.new_point_light()?;
position.z += 0.5;
light.set_position(position)?;
light.set_power(200.0)?;
Some(light)
} else {
None
};
if let Some(light) = &mut light {
let slice: [f32; 3] = info.particles.end_color.into();
light.set_color(slice.into())?;
}
Ok(light)
}
pub fn create_particles(
mut info: ParticleSystemInfo,
draw: &mut Draw,
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
engine: &Engine,
particle_info: ParticleInfo,
) -> Result<ParticleSystem> {
info.system.particle_count = (particle_info.radius * 250.0) as usize;
info.system.random_start_factor = particle_info.radius;
ParticleSystem::new_with_vk_objects(info, engine, particle_system_vulkan_objects, draw)
}
#[inline]
pub fn create_particle_effect(
&mut self,
game: &GameHandle,
loader: &AbilityLoader,
draw: &mut Draw,
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
) -> Result<ParticleSystem> {
let info = ParticleSystemInfo::load(game.build_data_path(&loader.settings.meta.particles))?;
if loader.settings.parameter.procedure_count > 1 {
self.light =
Self::create_lighting(game.upgrade().engine(), &loader, self.position, &info)?
}
Self::create_particles(
info,
draw,
particle_system_vulkan_objects,
game.upgrade().engine(),
ParticleInfo {
radius: self.radius,
},
)
}
pub fn direction(&self) -> Vector2<f32> {
self.direction
}
}
impl EntityComponent for AreaOfEffect {
fn enable(&mut self, scene: &mut Scene) -> Result<()> {
if let Some(light) = &self.light {
scene.add_light(light.clone())?;
}
if let Some(audio) = &mut self.audio {
unsafe { audio.as_mut() }.play("idle_sound", true)?;
}
self.start = scene.now();
Ok(())
}
fn disable(&mut self, scene: &mut Scene) -> Result<()> {
if let Some(light) = &self.light {
scene.remove_light(light.clone())?;
}
if let Some(audio) = &mut self.audio {
unsafe { audio.as_mut() }.stop()?;
}
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for AreaOfEffect {
fn debug_name() -> &'static str {
"AreaOfEffect"
}
}
#[derive(Serialize, Deserialize)]
pub struct AreaOfEffectCreateInfo {
direction: Vector2<f32>,
position: Vector2<f32>,
arc: f32,
radius: f32,
height: f32,
ability_info: AbilityLoader,
base_damage: u32,
owner: Entity,
}
impl<'a> ComponentCreateInfo<'a> for AreaOfEffectCreateInfo {}
#[test]
fn verify_procs() {
// ------ check if the procs are splitted correctly ------
let delay_only =
AreaOfEffect::calculate_proc_slots(1, Duration::from_secs(1), Duration::from_secs(0))
.unwrap();
assert_eq!(delay_only, vec![Duration::from_secs(1)]);
let split_5_without_delay =
AreaOfEffect::calculate_proc_slots(5, Duration::from_secs(0), Duration::from_secs(1))
.unwrap();
assert_eq!(
split_5_without_delay,
vec![
Duration::from_secs(0),
Duration::from_secs_f32(0.25),
Duration::from_secs_f32(0.5),
Duration::from_secs_f32(0.75),
Duration::from_secs(1),
]
);
let split_5_with_delay =
AreaOfEffect::calculate_proc_slots(5, Duration::from_secs(2), Duration::from_secs(1))
.unwrap();
assert_eq!(
split_5_with_delay,
vec![
Duration::from_secs(2),
Duration::from_millis(2250),
Duration::from_millis(2500),
Duration::from_millis(2750),
Duration::from_secs(3),
]
);
// ------ check error detection ------
assert!(
AreaOfEffect::calculate_proc_slots(2, Duration::from_secs(0), Duration::from_secs(0))
.is_err()
);
assert!(
AreaOfEffect::calculate_proc_slots(0, Duration::from_secs(0), Duration::from_secs(0))
.is_err()
);
assert!(
AreaOfEffect::calculate_proc_slots(1, Duration::from_secs(0), Duration::from_secs(1))
.is_err()
);
}

View file

@ -1,224 +0,0 @@
use std::{collections::HashSet, time::Duration};
use cgmath::{vec2, Deg, InnerSpace, Matrix2, Vector2};
use engine::prelude::*;
use entity_manager::*;
use rpg_components::{
components::{abilityloader::AbilityLoader, statistics::Statistics},
damage_type::DamageType,
};
use crate::*;
use self::game::content::abilities::{damage_and_experience, handle_npc_death};
pub struct AreaOfEffectArc {
arc: Deg<f32>,
radius: f32,
base_damage: u32,
damage_type: DamageType,
owner: Entity,
spawn_time: Duration,
collider: HashSet<Entity>,
}
impl AreaOfEffectArc {
pub fn new(
arc: impl Into<Deg<f32>>,
radius: f32,
base_damage: u32,
damage_type: DamageType,
owner: Entity,
) -> Self {
let arc = arc.into();
#[cfg(debug_assertions)]
{
let arc_deg: Deg<f32> = arc;
if arc_deg.0 < 0.0 {
panic!("arc should not be below 0°");
}
if arc_deg.0 > 180.0 {
panic!("arc should not be above 180°");
}
}
Self {
arc,
radius,
base_damage,
damage_type,
owner,
spawn_time: Duration::default(),
collider: HashSet::new(),
}
}
pub fn arc(&self) -> Deg<f32> {
self.arc
}
pub fn radius(&self) -> f32 {
self.radius
}
pub fn check_life_time(&self, now: Duration) -> bool {
now <= self.spawn_time + Duration::from_secs(1)
}
/// returns true if the collider was not present before
pub fn check_collider(&mut self, collider: Entity) -> bool {
self.collider.insert(collider)
}
pub fn check_collision(
&self,
my_location: &Location,
my_direction: Vector2<f32>,
collider_hit_box: &HitBox,
collider_location: &Location,
) -> bool {
let collider_pos = collider_location.position().xy();
let my_pos = my_location.position().xy();
if !collider_hit_box.check_collision(collider_pos, self.radius, my_pos) {
return false;
}
if self.arc.0 < 175.0 {
let left_border = Matrix2::from_angle(self.arc) * my_direction;
let right_border = Matrix2::from_angle(-self.arc) * my_direction;
let left_normal = vec2(-left_border.y, left_border.x);
let right_normal = vec2(right_border.y, -right_border.x);
let diff = my_pos - collider_pos;
let norm_diff = -diff.normalize();
let left_dot = left_normal.dot(norm_diff);
let right_dot = right_normal.dot(norm_diff);
if left_dot > 0.0 && right_dot > 0.0 {
return false;
}
let left_distance = (diff - (diff.dot(left_border)) * left_border).magnitude();
let right_distance = (diff - (diff.dot(right_border)) * right_border).magnitude();
if (left_distance > collider_hit_box.radius() && left_dot > 0.0)
|| (right_distance > collider_hit_box.radius() && right_dot > 0.0)
{
return false;
}
}
true
}
pub fn hit_event(
&mut self,
game_handle: &GameHandle,
scene: &mut SceneContents<'_>,
collider: Entity,
) -> Result<()> {
let s = unsafe { remove_life_time_mut(scene) };
let mut entity_multi_mut = scene.entities_multi_mut();
// collider
let Ok(collider_object) = entity_multi_mut.get(collider) else {
return Ok(());
};
let mut collider_multi_mut = collider_object.multi_mut();
// owner
let Ok(owner_object) = entity_multi_mut.get(self.owner) else {
return Ok(());
};
let mut owner_multi_mut = owner_object.multi_mut();
// factions
let collider_faction = Faction::get(&mut collider_multi_mut);
let owner_faction = Faction::get(&mut owner_multi_mut).expect("owner is not in a faction!");
if !owner_faction.check_collision(collider_faction) {
return Ok(());
}
unsafe {
owner_multi_mut.clear_all_usages();
}
let owner_stats = owner_multi_mut.get::<Statistics>()?;
let damage = AbilityLoader::check_for_crit(self.base_damage, owner_stats);
damage_and_experience(
damage,
self.damage_type,
&mut collider_multi_mut,
owner_multi_mut,
s,
)?;
unsafe {
collider_multi_mut.clear_all_usages();
}
handle_npc_death(&game_handle.upgrade(), s, collider, collider_multi_mut)?;
Ok(())
}
}
impl EntityComponent for AreaOfEffectArc {
fn name(&self) -> &str {
Self::debug_name()
}
fn enable(&mut self, scene: &mut Scene) -> Result<()> {
self.spawn_time = scene.now();
Ok(())
}
fn disable(&mut self, _scene: &mut Scene) -> Result<()> {
Ok(())
}
}
impl ComponentDebug for AreaOfEffectArc {
fn debug_name() -> &'static str {
"AreaOfEffectArc"
}
}
// #[cfg(test)]
// mod test {
// use cgmath::{vec2, vec3, Deg};
// use engine::prelude::*;
// use crate::{AreaOfEffectArc, HitBox, Location};
// #[test]
// fn verify_aoe_hitbox_collision() {
// let mut left_location = Location::new();
// left_location.set_position(vec3(35.93969, 14.690732, 0.0));
// let aoe_arc = AreaOfEffectArc::new(Deg(15.0), 0.8, |_, _, _| Ok(()));
// let left_direction = vec2(0.9995991, 0.02831539);
// let mut right_position = Location::new();
// right_position.set_position(vec3(36.608078, 14.694146, 0.0));
// let hitbox = HitBox::new(0.25689012, 1.0);
// assert!(aoe_arc.check_collision(&left_location, left_direction, &hitbox, &right_position));
// }
// }

View file

@ -1,116 +0,0 @@
use cgmath::Vector3;
use rpg_components::damage_type::DamageType;
use crate::*;
use std::{sync::Arc, time::Duration};
pub struct DamageEvent {
pub damage: u32,
pub damage_type: DamageType,
pub position: Vector3<f32>,
}
pub struct DamageNumber {
grid: Arc<Grid>,
start: Duration,
}
impl DamageNumber {
pub const LIFE_TIME: Duration = Duration::from_secs(2);
pub const SPEED: f32 = 1.5;
pub fn new(game_handle: &GameHandle, damage: u32, damage_type: DamageType) -> Result<Self> {
Ok(Self {
grid: Self::create_ui(game_handle, damage, damage_type)?,
start: Duration::default(),
})
}
fn create_ui(
game_handle: &GameHandle,
damage: u32,
damage_type: DamageType,
) -> Result<Arc<Grid>> {
let game = game_handle.upgrade();
let gui_handler = game.engine().gui_handler();
let grid = Grid::new(gui_handler.clone(), 1, 1, false)?;
grid.disallow_position_scale()?;
let label = Label::builder()
.set_text(damage.to_string())
.set_text_color(damage_type)
.build(gui_handler.clone())?;
grid.attach(label, 0, 0, 1, 1)?;
grid.set_frame(0, 0, 120, 40, VerticalAlign::Top, HorizontalAlign::Left)?;
Ok(grid)
}
pub fn update(&self, scene: &SceneContents<'_>, location: &mut Location) -> Result<bool> {
let diff = scene.now() - self.start;
if diff > Self::LIFE_TIME {
return Ok(false);
}
let ratio = diff.as_secs_f32() / Self::LIFE_TIME.as_secs_f32();
let mut position = location.position();
position.z += Self::SPEED * ratio;
let (x, y) = scene.world_to_screen_space(position)?;
let (width, height) = self.grid.extents();
self.grid
.set_position(x - (width as i32 / 2), y - (height as i32 / 2))?;
Ok(true)
}
}
impl DamageNumber {
pub fn setup_event(game_handle: &GameHandle, scene: &mut Scene) {
scene.register_event::<DamageEvent>();
scene.add_event_reader({
let game_handle = game_handle.clone();
move |scene, damage_event: &DamageEvent| {
let mut entity = game_handle.upgrade().engine().assets().empty_entity();
let location = Location::new_and_setup(&mut entity)?;
location.set_position(damage_event.position);
let damage_number =
DamageNumber::new(&game_handle, damage_event.damage, damage_event.damage_type)?;
entity.insert_component(damage_number);
scene.add_entity(entity)?;
Ok(())
}
});
}
}
impl EntityComponent for DamageNumber {
fn name(&self) -> &str {
Self::debug_name()
}
fn enable(&mut self, scene: &mut Scene) -> Result<()> {
self.start = scene.now();
self.grid.set_visibility(true)
}
fn disable(&mut self, _scene: &mut Scene) -> Result<()> {
self.grid.set_visibility(false)
}
}
impl ComponentDebug for DamageNumber {
fn debug_name() -> &'static str {
"DamageNumber"
}
}

View file

@ -1,91 +0,0 @@
use engine::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Faction {
Player,
Friendly,
NPC,
}
impl Faction {
pub fn copy(source: &mut MultiMut<'_>, destination: &mut EntityObject) {
if source.get::<FactionPlayer>().is_ok() {
destination.insert_component(FactionPlayer);
} else if source.get::<FactionNPC>().is_ok() {
destination.insert_component(FactionNPC);
} else {
unreachable!("missing component!");
}
}
pub fn get(obj: &mut MultiMut<'_>) -> Option<Self> {
if obj.get::<FactionPlayer>().is_ok() {
Some(Self::Player)
} else if obj.get::<FactionFriendly>().is_ok() {
Some(Self::Friendly)
} else if obj.get::<FactionNPC>().is_ok() {
Some(Self::NPC)
} else {
None
}
}
pub fn check_collision(self, other: Option<Self>) -> bool {
match other {
Some(collider_faction) => {
// same faction or friendly NPCs are ignored
if self == collider_faction || collider_faction == Self::Friendly {
return false;
}
true
}
None => false,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct FactionPlayer;
impl EntityComponent for FactionPlayer {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for FactionPlayer {
fn debug_name() -> &'static str {
"FactionPlayer"
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct FactionFriendly;
impl EntityComponent for FactionFriendly {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for FactionFriendly {
fn debug_name() -> &'static str {
"FactionFriendly"
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct FactionNPC;
impl EntityComponent for FactionNPC {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for FactionNPC {
fn debug_name() -> &'static str {
"FactionNPC"
}
}

View file

@ -1,78 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use std::time::Duration;
use crate::Game;
#[derive(Serialize, Deserialize)]
pub struct Ghost {
start: Duration,
duration: Duration,
start_height: f32,
end_height: f32,
}
impl Ghost {
pub fn new(
game: &Game,
location: &Location,
radius: f32,
height: f32,
start: Duration,
) -> Result<EntityObject> {
let mut ghost = game
.entity_manager()
.load_entity(game.engine().assets(), "Ghost")?;
let ghost_location = Location::new_and_setup(&mut ghost)?;
ghost_location.update(location.position(), location.direction());
ghost_location.set_scale_uniform(radius * 2.0);
let start_height = ghost_location.position().z + height;
let end_height = start_height + 2.0 * radius;
let me = Ghost {
start,
duration: Duration::from_secs(2),
start_height,
end_height,
};
ghost.insert_component(me);
Ok(ghost)
}
pub fn update(&self, now: Duration, location: &mut Location) -> Result<bool> {
if self.start + self.duration < now {
return Ok(false);
}
let diff = now - self.start;
let ratio = diff.as_secs_f32() / self.duration.as_secs_f32();
let final_height = self.start_height + self.end_height * ratio;
let mut position = location.position();
position.z = final_height;
location.set_position(position);
Ok(true)
}
}
impl EntityComponent for Ghost {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for Ghost {
fn debug_name() -> &'static str {
"Ghost"
}
}

View file

@ -1,89 +0,0 @@
use anyhow::Result;
use cgmath::vec3;
use engine::prelude::*;
use std::sync::Arc;
use crate::Game;
use super::health_bar_base::HealthBarBase;
pub struct BossHealthBar {
gui: Arc<GuiBuilder>,
hp_bar: Arc<ProgressBar>,
base: HealthBarBase,
}
impl BossHealthBar {
pub fn new(game: &Game, boss_name: impl ToString) -> Result<Self> {
let gui = GuiBuilder::new(
game.engine().gui_handler(),
&game.build_data_path("gui/xml/ingame/boss_healthbar.xml"),
)?;
let progress_bar: Arc<ProgressBar> = gui.element("health_bar")?;
progress_bar.set_progress(1.0)?;
let name: Arc<Label> = gui.element("boss name")?;
name.set_text(boss_name)?;
Ok(Self {
gui,
hp_bar: progress_bar,
base: HealthBarBase::new(vec3(0.0, 0.0, 0.0)),
})
}
pub fn update(&mut self, health: u32, hp_progress: f32, should_render: bool) -> Result<()> {
if self.base.should_enable && !self.base.is_enabled {
self.base.is_enabled = true;
self.gui.enable()?;
}
match (should_render, self.base.is_enabled) {
(true, false) | (true, true) => self.gui.enable()?,
(false, true) => self.gui.disable()?,
_ => (),
}
println!(
"should render: {}, enabled: {}",
should_render, self.base.is_enabled
);
self.hp_bar.set_progress(hp_progress)?;
self.hp_bar.set_text(health)?;
Ok(())
}
}
impl EntityComponent for BossHealthBar {
fn enable(&mut self, _scene: &mut Scene) -> Result<()> {
self.base.should_enable = true;
Ok(())
}
fn disable(&mut self, _scene: &mut Scene) -> Result<()> {
self.base.should_enable = false;
self.base.is_enabled = false;
self.gui.disable()?;
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for BossHealthBar {
fn debug_name() -> &'static str {
"BossHealthBar"
}
}

View file

@ -1,23 +0,0 @@
use cgmath::Vector3;
use engine::prelude::*;
pub struct HealthBarBase {
pub center: (i32, i32),
pub should_enable: bool,
pub is_enabled: bool,
pub world_offset: Vector3<f32>,
}
impl HealthBarBase {
pub fn new(offset: Vector3<f32>) -> Self {
Self {
center: (0, 0),
should_enable: false,
is_enabled: false,
world_offset: offset,
}
}
}

View file

@ -1,8 +0,0 @@
mod boss_health_bar;
mod health_bar_base;
mod npc_health_bar;
mod user_health_bar;
pub use boss_health_bar::BossHealthBar;
pub use npc_health_bar::NPCHealthBar;
pub use user_health_bar::UserHealthBar;

View file

@ -1,98 +0,0 @@
use anyhow::Result;
use cgmath::Vector3;
use engine::prelude::*;
use std::{convert::TryFrom, sync::Arc};
use crate::Game;
use super::health_bar_base::HealthBarBase;
pub struct NPCHealthBar {
gui: Arc<GuiBuilder>,
main_grid: Arc<Grid>,
hp_bar: Arc<ProgressBar>,
base: HealthBarBase,
}
impl NPCHealthBar {
pub fn new(game: &Game, elite: bool, world_offset: Vector3<f32>) -> Result<Self> {
let gui = GuiBuilder::new(
game.engine().gui_handler(),
&game.build_data_path("gui/xml/ingame/npc_healthbar.xml"),
)?;
let progress_bar: Arc<ProgressBar> = gui.element("health_bar")?;
progress_bar.set_progress(1.0)?;
let health_bar_grid: Arc<Grid> = gui.element("health_bar_grid")?;
// this is super important to prevent weird behavior
health_bar_grid.disallow_position_scale()?;
if elite {
health_bar_grid
.set_background((Color::try_from("#FFD700")?, DisplayableFillType::Expand))?;
}
Ok(Self {
gui,
main_grid: health_bar_grid,
hp_bar: progress_bar,
base: HealthBarBase::new(world_offset),
})
}
pub fn update(&mut self, (health, hp_progress): (u32, f32), (x, y): (i32, i32)) -> Result<()> {
if self.base.should_enable && !self.base.is_enabled {
self.base.is_enabled = true;
self.gui.enable()?;
}
self.hp_bar.set_progress(hp_progress)?;
self.hp_bar.set_text(health)?;
if self.base.center != (x, y) {
self.base.center = (x, y);
let (width, height) = self.main_grid.extents();
self.main_grid
.set_position(x - (width as i32 / 2), y - (height as i32 / 2))?;
}
Ok(())
}
pub fn world_offset(&self) -> Vector3<f32> {
self.base.world_offset
}
}
impl EntityComponent for NPCHealthBar {
fn enable(&mut self, _scene: &mut Scene) -> Result<()> {
self.base.should_enable = true;
Ok(())
}
fn disable(&mut self, _scene: &mut Scene) -> Result<()> {
self.base.should_enable = false;
self.base.is_enabled = false;
self.gui.disable()?;
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for NPCHealthBar {
fn debug_name() -> &'static str {
"NPCHealthBar"
}
}

View file

@ -1,129 +0,0 @@
use anyhow::Result;
use cgmath::Vector3;
use engine::prelude::*;
use std::sync::Arc;
use crate::Game;
use super::health_bar_base::HealthBarBase;
pub struct UserHealthBar {
gui: Arc<GuiBuilder>,
name_grid: Arc<Grid>,
bar_grid: Arc<Grid>,
hp_bar: Arc<ProgressBar>,
mana_bar: Arc<ProgressBar>,
base: HealthBarBase,
}
impl UserHealthBar {
pub fn new(game: &Game, user_name: &str, world_offset: Vector3<f32>) -> Result<Self> {
let gui = GuiBuilder::new(
game.engine().gui_handler(),
&game.build_data_path("gui/xml/ingame/user_healthbar.xml"),
)?;
let hp_bar: Arc<ProgressBar> = gui.element("health bar")?;
hp_bar.set_progress(1.0)?;
let mana_bar: Arc<ProgressBar> = gui.element("mana bar")?;
mana_bar.set_progress(1.0)?;
let name_grid: Arc<Grid> = gui.element("name grid")?;
// this is super important to prevent weird behavior
name_grid.disallow_position_scale()?;
let bar_grid: Arc<Grid> = gui.element("bar grid")?;
// this is super important to prevent weird behavior
bar_grid.disallow_position_scale()?;
let user_name_label: Arc<Label> = gui.element("user name")?;
user_name_label.set_text(&user_name)?;
Ok(UserHealthBar {
gui,
name_grid,
bar_grid,
hp_bar,
mana_bar,
base: HealthBarBase {
center: (0, 0),
should_enable: false,
is_enabled: false,
world_offset,
},
})
}
pub fn update(
&mut self,
hp_progress: f32,
mana_progress: f32,
(x, y): (i32, i32),
) -> Result<()> {
if self.base.should_enable && !self.base.is_enabled {
self.base.is_enabled = true;
self.gui.enable()?;
}
self.hp_bar.set_progress(hp_progress)?;
self.mana_bar.set_progress(mana_progress)?;
if self.base.center != (x, y) {
self.base.center = (x, y);
let (name_width, name_height) = self.name_grid.extents();
let (bar_width, _) = self.bar_grid.extents();
self.name_grid
.set_position(x - (name_width as i32 / 2), y - (name_height as i32 / 2))?;
self.bar_grid
.set_position(x - (bar_width as i32 / 2), y + (name_height as i32 / 2))?;
}
Ok(())
}
pub fn world_offset(&self) -> Vector3<f32> {
self.base.world_offset
}
}
impl EntityComponent for UserHealthBar {
fn enable(&mut self, __scene: &mut Scene) -> Result<()> {
self.base.should_enable = true;
Ok(())
}
fn disable(&mut self, _scene: &mut Scene) -> Result<()> {
self.base.should_enable = false;
self.base.is_enabled = false;
self.gui.disable()?;
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for UserHealthBar {
fn debug_name() -> &'static str {
"UserHealthBar"
}
}

View file

@ -1,265 +0,0 @@
use crate::*;
use anyhow::Result;
use cgmath::Vector3;
use engine::prelude::*;
use entity_manager::*;
use rpg_components::{
components::{level::Level, npc_type::NPCType},
items::Loot,
};
use std::sync::Arc;
pub struct LootStash {
pub loot: Vec<Option<Loot>>,
action_radius: f32,
open_chest_ui: Option<Arc<GuiBuilder>>,
is_enabled: bool,
}
impl LootStash {
pub const WORLD_OFFSET: f32 = 1.0;
pub const MAX_STASH_SIZE: usize = 49;
}
impl LootStash {
pub fn handle_npc_death(
game: &Game,
npc_level: &Level,
npc_location: &Location,
npc_type: NPCType,
) -> Result<Option<EntityObject>> {
let loot_multiplier = match npc_type {
NPCType::Boss => Some(game.item_settings.general.drop_chance_boss_multiplier),
NPCType::Elite => Some(game.item_settings.general.drop_chance_elite_multiplier),
NPCType::Normal => None,
};
let mut loot_list = vec![None; Self::MAX_STASH_SIZE];
for loot_opt in loot_list.iter_mut() {
match game
.item_system()
.get_random_loot(npc_level.level(), loot_multiplier)
{
Some(loot) => *loot_opt = Some(loot),
None => break,
}
}
if loot_list.iter().any(|loot_opt| loot_opt.is_some()) {
return Ok(Some(LootStash::create(
game,
loot_list,
npc_location.position(),
)?));
}
Ok(None)
}
fn create(
game: &Game,
loot: Vec<Option<Loot>>,
parent_position: Vector3<f32>,
) -> Result<EntityObject> {
let mut loot_stash = game
.entity_manager()
.load_entity(game.engine().assets(), "lootchest")?;
{
let location = Location::new_and_setup(&mut loot_stash)?;
location.set_position(parent_position);
location
.set_scale_uniform((loot.len() as f32 / (Self::MAX_STASH_SIZE * 2) as f32) + 0.5);
loot_stash.insert_component(Self {
loot,
action_radius: game.game_settings().loot_action_radius,
open_chest_ui: None,
is_enabled: false,
});
loot_stash.insert_component(FactionFriendly);
if let Ok(hitbox) = loot_stash.get_component_mut::<HitBox>() {
hitbox.set_event(move |scene, _me, collider| -> Result<CollisionEventType> {
let collider_entity_object = scene.entity(collider)?;
if collider_entity_object
.get_component::<FactionPlayer>()
.is_ok()
{
return Ok(CollisionEventType::Block);
}
Ok(CollisionEventType::Ignore)
})
}
}
Ok(loot_stash)
}
pub fn is_empty(&self) -> bool {
Self::is_list_empty(&self.loot)
}
pub fn remove_loot(&mut self, index: usize) -> Loot {
self.loot[index].take().unwrap()
}
}
impl LootStash {
pub fn update(&self, (x, y): (i32, i32)) -> Result<()> {
if let Some(ui) = &self.open_chest_ui {
if self.is_enabled && !ui.visible() {
ui.enable()?;
}
let grid: Arc<Grid> = ui.element("main grid")?;
let (width, height) = grid.extents();
grid.set_position(x - (width as i32 / 2), y - (height as i32 / 2))?;
}
Ok(())
}
pub fn enable_ui(&mut self, game: &Game) -> Result<()> {
let open_chest_ui = {
let ui = GuiBuilder::new(
game.engine().gui_handler(),
&game.build_data_path("gui/xml/ingame/open_loot.xml"),
)?;
let icon: Arc<Icon> = ui.element("open chest icon")?;
match game
.engine()
.settings()
.controller_icon(game.engine(), ControllerButton::DPadUp)?
{
Some(pic) => {
icon.set_icon(&pic)?;
}
None => {
icon.set_icon(game.engine().settings().dark_key().as_ref().unwrap())?;
icon.set_text("E")?;
}
};
let grid: Arc<Grid> = ui.element("main grid")?;
// this is super important to prevent weird behavior
grid.disallow_position_scale()?;
ui
};
debug_assert!(self.open_chest_ui.is_none());
self.open_chest_ui = Some(open_chest_ui);
Ok(())
}
pub fn disable_ui(&mut self) -> Result<()> {
debug_assert!(self.open_chest_ui.is_some());
if self.is_enabled {
self.open_chest_ui.as_ref().unwrap().disable()?;
}
self.open_chest_ui = None;
Ok(())
}
pub fn create_debug(
game_handle: &GameHandle,
loot: Vec<Option<Loot>>,
parent_position: Vector3<f32>,
) -> Result<EntityObject> {
let game = game_handle.upgrade();
let mut loot_stash = game
.entity_manager()
.load_entity(game.engine().assets(), "lootchest")?;
{
let location = Location::new_and_setup(&mut loot_stash)?;
location.set_position(parent_position);
location
.set_scale_uniform((loot.len() as f32 / (Self::MAX_STASH_SIZE * 2) as f32) + 0.5);
let me = Self {
loot,
action_radius: game.game_settings().loot_action_radius,
open_chest_ui: None,
is_enabled: false,
};
loot_stash.insert_component(me);
loot_stash.insert_component(FactionFriendly);
}
Ok(loot_stash)
}
}
impl LootStash {
pub fn is_list_empty(loot: &[Option<Loot>]) -> bool {
let mut is_empty = true;
for loot_opt in loot.iter() {
if loot_opt.is_some() {
is_empty = false;
break;
}
}
is_empty
}
pub fn radius(&self) -> f32 {
self.action_radius
}
}
impl EntityComponent for LootStash {
fn enable(&mut self, _scene: &mut Scene) -> Result<()> {
self.is_enabled = true;
if let Some(ui) = &self.open_chest_ui {
debug_assert!(!ui.visible());
ui.enable()?;
}
Ok(())
}
fn disable(&mut self, _scene: &mut Scene) -> Result<()> {
self.is_enabled = false;
if let Some(ui) = &self.open_chest_ui {
debug_assert!(ui.visible());
ui.disable()?;
}
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for LootStash {
fn debug_name() -> &'static str {
"LootStash"
}
}

View file

@ -1,22 +0,0 @@
mod ability_location_info;
pub mod abilityloader;
pub mod ai;
mod aoe;
pub mod aoe_arc;
pub mod damage_number;
mod entity_faction;
mod ghost;
pub mod health_bar;
mod loot_stash;
pub mod movement;
mod npc_name;
pub use self::{ai::*, aoe_arc::AreaOfEffectArc};
pub use self::{
ability_location_info::*, aoe::*, damage_number::*, entity_faction::*, ghost::*, health_bar::*,
loot_stash::*, movement::Movement, npc_name::NPCName,
};
pub const MOVEMENT_MAX_THRESHOLD: f32 = 0.8;
pub const MOVEMENT_MIN_THRESHOLD: f32 = 0.4;

View file

@ -1,133 +0,0 @@
use cgmath;
use cgmath::Vector2;
use engine::prelude::*;
use entity_manager::*;
use super::{MOVEMENT_MAX_THRESHOLD, MOVEMENT_MIN_THRESHOLD};
use std::time::Duration;
pub struct Movement {
direction: Vector2<f32>,
pub movement_speed: f32,
lock: bool,
pub(crate) base_movement_speed: f32,
pub current_distance: Option<f32>,
audio: Option<UnsafeComponentStore<Audio>>,
}
impl Clone for Movement {
fn clone(&self) -> Self {
Movement {
direction: self.direction,
movement_speed: self.base_movement_speed,
base_movement_speed: self.base_movement_speed,
lock: false,
current_distance: None,
audio: None,
}
}
}
impl Movement {
pub(crate) fn new(speed: f32, audio: Option<&mut Audio>) -> Self {
Self {
direction: Vector2::zero(),
movement_speed: speed,
base_movement_speed: speed,
lock: false,
current_distance: None,
audio: audio.map(|audio| audio.into()),
}
}
pub fn post_setup(&mut self, audio: Option<&mut Audio>) {
self.audio = audio.map(|audio| audio.into());
}
pub fn set_direction(&mut self, direction: Vector2<f32>) {
self.direction = self.check_direction(direction);
}
pub fn direction(&self) -> Vector2<f32> {
self.direction
}
pub fn movement_speed(&self) -> f32 {
self.movement_speed
}
pub fn base_movement_speed(&self) -> f32 {
self.base_movement_speed
}
pub fn is_locked(&self) -> bool {
self.lock
}
fn check_direction(&mut self, mut direction: Vector2<f32>) -> Vector2<f32> {
let length = direction.magnitude();
if length < MOVEMENT_MIN_THRESHOLD {
if let Some(ptr) = &mut self.audio {
let audio = unsafe { ptr.as_mut() };
audio.stop_looping().unwrap();
}
direction = Vector2::zero();
} else {
if let Some(ptr) = &mut self.audio {
let audio = unsafe { ptr.as_mut() };
audio.play(SOUND_MOVE_KEY, true).unwrap();
}
if length > MOVEMENT_MAX_THRESHOLD {
direction /= length;
}
}
direction
}
pub fn has_direction(&self) -> bool {
self.direction != Vector2::zero()
}
pub fn process_move(&mut self, time_delta: Duration) -> Vector2<f32> {
self.distance(time_delta) * self.direction
}
pub fn distance(&mut self, time_delta: Duration) -> f32 {
self.movement_speed * time_delta.as_secs_f32()
}
pub fn setup_after_clone(&mut self, multi_mut: &mut MultiMut<'_>) {
let audio = multi_mut.get::<Audio>();
self.audio = audio.map(UnsafeComponentStore::from).ok();
}
}
impl EntityComponent for Movement {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for Movement {
fn debug_name() -> &'static str {
"Movement"
}
}

View file

@ -1,26 +0,0 @@
use engine::prelude::*;
#[derive(Serialize, Deserialize)]
pub struct NPCName {
pub name: String,
}
impl NPCName {
pub fn new(name: impl ToString) -> Self {
Self {
name: name.to_string(),
}
}
}
impl EntityComponent for NPCName {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for NPCName {
fn debug_name() -> &'static str {
"NPCName"
}
}

View file

@ -1,162 +0,0 @@
use std::{collections::HashMap, str::FromStr};
use cgmath::{vec2, Deg, Rad, Vector2, Vector3};
use entity_manager::*;
use crate::*;
pub struct LightningMarker;
impl EntityComponent for LightningMarker {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for LightningMarker {
fn debug_name() -> &'static str {
"LightningMarker"
}
}
pub struct Lightning {
pub thickness: f32,
pub lines: Vec<Vector2<f32>>,
}
impl Lightning {
const WIDTH: f32 = 0.25;
const LENGTH: f32 = 1.0;
const DIRECTION: Vector2<f32> = Vector2::new(1.0, 0.0);
pub fn new(thickness: f32) -> Self {
Self {
thickness,
lines: Self::create_lines(),
}
}
pub fn create_vertices(&self) -> Vec<Vector2<f32>> {
self.lines
.iter()
.skip(1)
.enumerate()
.map(|(index, p)| {
let prev_p = self.lines[index];
let tangent = prev_p - p;
let normal = vec2(-tangent.y, tangent.x).normalize();
let p1 = prev_p - normal * 0.5 * self.thickness;
let p2 = p - normal * 0.5 * self.thickness;
let p3 = p + normal * 0.5 * self.thickness;
let p4 = prev_p + normal * 0.5 * self.thickness;
vec![p1, p2, p3, p3, p4, p1]
})
.flatten()
.collect()
}
pub fn create_entity(
&self,
game_handle: &GameHandle,
scene: &Scene,
position: Vector3<f32>,
target: Vector3<f32>,
) -> Result<EntityObject> {
let game = game_handle.upgrade();
let mut entity = game.engine().assets().empty_entity();
#[cfg(debug_assertions)]
{
entity.debug_name = Some("Lightning".to_string());
}
let tangent = (target - position).truncate();
let scale = tangent.magnitude();
let angle = tangent.angle(Self::DIRECTION);
let angle_offset: Rad<f32> = Deg(90.0).into();
let height = position.z;
let mut mesh = AssetMesh::new(&scene.device(), scene.render_type())?;
mesh.set_name("Lightning");
let data: Vec<PositionOnly> = self
.create_vertices()
.into_iter()
.rev()
.map(|p| PositionOnly::new(p.extend(0.0)))
.collect();
let vertex_buffer = Buffer::builder()
.set_memory_usage(MemoryUsage::CpuOnly)
.set_usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
.set_data(&data)
.build(scene.device().clone())?;
mesh.add_primitive(
vertex_buffer,
None,
None,
PrimitiveMaterial {
color: Color::from_str("#2660ff")?.into(),
..Default::default()
},
true,
)?;
entity.insert_component(Draw::new(vec![mesh]));
let audio = Audio::new(
&game.engine().context(),
[(
SOUND_CREATE_KEY.to_string(),
game_handle.build_data_path("sounds/shot2.ogg"),
)]
.into_iter()
.collect::<HashMap<_, _>>(),
)?;
entity.insert_component(audio);
let location = Location::new_and_setup(&mut entity)?;
location.set_position(position);
location.set_scale_uniform(scale);
location.set_rotation(angle - angle_offset);
entity.insert_component(LightningMarker);
entity.insert_component(BoundingBox {
min: [0.0, -0.25, 0.0],
max: [1.0, 0.25, 1.0],
});
println!(
"Lightning created with components: {:#?}",
entity.component_names()
);
Ok(entity)
}
fn create_lines() -> Vec<Vector2<f32>> {
let mut lines = Vec::new();
for _ in 0..50 {
lines.push(vec2(Random::range_f32(0.0, Self::LENGTH), 0.0));
}
lines.sort_by(|lhs, rhs| lhs.x.partial_cmp(&rhs.x).unwrap());
[vec2(0.0, 0.0)]
.into_iter()
.chain(
lines
.into_iter()
.map(|p| vec2(p.x, Random::range_f32(-Self::WIDTH, Self::WIDTH))),
)
.chain([vec2(1.0, 0.0)].into_iter())
.collect()
}
}

View file

@ -1,7 +0,0 @@
pub mod abilities;
pub mod components;
pub mod objects;
pub mod ability_type;
pub mod lightning;
pub mod prelude;

View file

@ -1,498 +0,0 @@
use anyhow::Result;
use assetpath::AssetPath;
use cgmath::{vec2, Vector2};
use engine::prelude::*;
use entity_manager::*;
use rpg_components::{
ability_type::AbilityType,
components::{
ability_slots::AbilitySlots,
attributes::Attributes,
character_status::CharacterStatus,
crafting_materials::CraftingMaterials,
inventory::Inventory,
item_slots::ItemSlotContainer,
level::{Level, LevelUpEvent},
statistics::Statistics,
},
config::{
attributes::AttributeSettings,
experience::ExperienceSettings,
save_game::{save_game_dir, SaveGame},
},
damage_type::DamageType,
items::{ItemSystem, Rarities},
};
use super::super::prelude::*;
use crate::GameHandle;
use std::{sync::Arc, time::Duration};
#[derive(Clone)]
pub enum HeroCreateType {
SaveGame(SaveGame),
New(String),
}
impl From<SaveGame> for HeroCreateType {
fn from(save_game: SaveGame) -> Self {
Self::SaveGame(save_game)
}
}
impl From<String> for HeroCreateType {
fn from(name: String) -> Self {
Self::New(name)
}
}
impl From<&str> for HeroCreateType {
fn from(name: &str) -> Self {
Self::New(name.to_string())
}
}
impl From<&String> for HeroCreateType {
fn from(name: &String) -> Self {
Self::New(name.clone())
}
}
pub struct MainUser;
impl EntityComponent for MainUser {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for MainUser {
fn debug_name() -> &'static str {
"MainUser"
}
}
#[derive(Clone)]
pub struct Hero {
name: String,
entity: Entity,
}
impl Hero {
pub fn reuse(
&self,
game_handle: &GameHandle,
hero_create_type: impl Into<HeroCreateType>,
scene: &mut Scene,
) -> Result<()> {
let game = game_handle.upgrade();
let hero_create_type = hero_create_type.into();
let mut entity_object = scene.entity_mut(self.entity)?;
Location::setup(entity_object.multi_mut())?;
let exp_settings = &game.experience_settings;
let attribute_settings = &game.attribute_settings;
let item_settings = &game.item_settings;
let item_system = &game.item_system();
let level = Self::level(&hero_create_type, exp_settings);
let mut attributes = Self::attributes(&hero_create_type, attribute_settings);
let items = Self::items(&hero_create_type, game_handle.clone(), &mut entity_object)?;
let inventory = Self::inventory(&hero_create_type, item_system)?;
let abilities = Self::abilities(&hero_create_type, game_handle.clone())?;
let crafting_materials: CraftingMaterials = Self::crafting_materials(&hero_create_type);
let movement = Self::movement(&mut entity_object);
let mut stats = Statistics::default();
stats.update(&mut attributes, attribute_settings, (&items, item_settings));
let current_status = CharacterStatus::new_full(&stats);
*entity_object.get_component_mut()? = level;
*entity_object.get_component_mut()? = attributes;
*entity_object.get_component_mut()? = items;
*entity_object.get_component_mut()? = inventory;
*entity_object.get_component_mut()? = abilities;
*entity_object.get_component_mut()? = crafting_materials;
*entity_object.get_component_mut()? = movement;
*entity_object.get_component_mut()? = stats;
*entity_object.get_component_mut()? = current_status;
Ok(())
}
pub fn new(
game_handle: &GameHandle,
hero_create_type: impl Into<HeroCreateType>,
) -> Result<(EntityObject, Hero)> {
let game = game_handle.upgrade();
let mut entity_object = game
.entity_manager()
.load_entity(game.engine().assets(), "character")?;
let entity = entity_object.as_entity();
let hero_create_type = hero_create_type.into();
let exp_settings = &game.experience_settings;
let attribute_settings = &game.attribute_settings;
let item_settings = &game.item_settings;
let item_system = &game.item_system();
{
Location::new_and_setup(&mut entity_object)?;
let level = Self::level(&hero_create_type, exp_settings);
let mut attributes = Self::attributes(&hero_create_type, attribute_settings);
let items = Self::items(&hero_create_type, game_handle.clone(), &mut entity_object)?;
let inventory = Self::inventory(&hero_create_type, item_system)?;
let abilities = Self::abilities(&hero_create_type, game_handle.clone())?;
let crafting_materials = Self::crafting_materials(&hero_create_type);
let movement = Self::movement(&mut entity_object);
let mut stats = Statistics::default();
stats.update(&mut attributes, attribute_settings, (&items, item_settings));
let current_status = CharacterStatus::new_full(&stats);
entity_object.insert_component(level);
entity_object.insert_component(attributes);
entity_object.insert_component(items);
entity_object.insert_component(inventory);
entity_object.insert_component(abilities);
entity_object.insert_component(crafting_materials);
entity_object.insert_component(movement);
entity_object.insert_component(stats);
entity_object.insert_component(current_status);
entity_object.insert_component(FactionPlayer);
entity_object.insert_component(MainUser);
}
let name = match hero_create_type {
HeroCreateType::SaveGame(save_game) => save_game.general.name,
HeroCreateType::New(name) => name,
};
Ok((entity_object, Hero { name, entity }))
}
pub fn set_level_up_effect(game: &GameHandle, entity: Entity) -> Result<()> {
let strong_game = game.upgrade();
let engine = strong_game.engine();
let sound = engine.context().sound().load_sound(
game.build_data_path("sounds/blessing2.ogg"),
"sfx",
SoundInterpretation::Spatial,
)?;
let particle_system_info =
ParticleSystemInfo::load(game.build_data_path("particles/level_up.particle"))?;
let game_handle = game.clone();
engine.on_scene_mut(move |scene| {
scene.add_event_reader(move |scene, _level_up: &LevelUpEvent| {
let position = scene
.entity(entity)?
.get_component::<Location>()?
.position();
let game = game_handle.upgrade();
let mut particle_entity = game.engine().assets().empty_entity();
let mut draw = Draw::new(Vec::new());
let mut audio = Audio::new(&game.engine().context(), None)?;
audio.add_sound(SOUND_CREATE_KEY, sound.clone());
let particle_system = ParticleSystem::new_with_vk_objects(
particle_system_info.clone(),
game_handle.upgrade().engine(),
scene.particle_system_vulkan_objects(),
&mut draw,
)?;
particle_entity.insert_component(audio);
particle_entity.insert_component(draw);
particle_entity.insert_component(particle_system);
particle_entity.insert_component(BoundingBox {
min: [-1.0, -1.0, 0.0],
max: [1.0, 1.0, 2.0],
});
Location::new_and_setup(&mut particle_entity)?.set_position(position);
scene.add_entity(particle_entity)?;
// ingame
// .level_up_ui
// .show(ingame.game().engine().time(), level)?;
Ok(())
});
Ok(())
})
}
pub fn move_in_direction(&self, movement: &mut Movement, left_stick: Vector2<f32>) {
movement.set_direction(left_stick);
}
pub fn execute_ability(
hero: Entity,
game_handle: &GameHandle,
time: Duration,
index: usize,
multi_mut: &mut MultiMut<'_>,
entities: &mut Entities<'_>,
events: &mut ContentEvents<'_>,
) -> Result<()> {
let animation = multi_mut.get::<Animation>()?;
let draw = multi_mut.get::<Draw>()?;
let animation_info = multi_mut.get::<AnimationInfo>()?;
let abilities = multi_mut.get::<AbilitySlots>()?;
let character_status = multi_mut.get::<CharacterStatus>()?;
let location = multi_mut.get::<Location>()?;
let direction = location.direction();
if let Some(ability_book) = abilities.book_mut(index) {
if !animation_info.is_locked()
&& ability_book.validate_use(time, character_status, location)?
{
unsafe {
multi_mut.clear_all_usages();
}
{
// TODO: further separation of animation types (bows, ...)
let animation_type =
match ability_book.ability().data().settings.parameter.damage_type {
DamageType::Physical => AnimationType::Attack,
_ => AnimationType::Cast,
};
animation_info.set_animation(
animation,
draw,
Some(animation_type),
time,
true,
false,
)?;
}
match ability_book
.ability()
.data()
.settings
.parameter
.ability_type
{
AbilityType::Projectile => Projectile::execute(
ability_book.ability().data(),
hero,
multi_mut,
direction,
&ability_book,
game_handle,
entities,
events,
)?,
AbilityType::SelfCast => SelfCast::execute(
ability_book.ability().data(),
hero,
multi_mut,
direction,
&ability_book,
game_handle,
entities,
events,
)?,
};
}
}
Ok(())
}
pub fn entity(&self) -> Entity {
self.entity
}
pub fn name(&self) -> &str {
&self.name
}
pub fn change_name(&mut self, name: &str) {
self.name = name.to_string();
}
pub fn save(&self, scene: &Scene) -> Result<()> {
let mut save_game = SaveGame::default();
save_game.file_name =
AssetPath::from((save_game_dir("gavania"), format!("{}.savegame", self.name)));
let entity = scene.entity(self.entity)?;
let level = entity.get_component::<Level>()?;
let attributes = entity.get_component::<Attributes>()?;
let items = entity.get_component::<ItemSlotContainer>()?;
let inventory = entity.get_component::<Inventory>()?;
let abilities = entity.get_component::<AbilitySlots>()?;
let crafting_materials = entity.get_component::<CraftingMaterials>()?;
save_game.general.name = self.name.clone();
save_game.general.level = level.level();
save_game.general.exp = level.current_experience;
save_game.general.strength = attributes.base_strength().raw();
save_game.general.agility = attributes.base_agility().raw();
save_game.general.intelligence = attributes.base_intelligence().raw();
items.store(&mut save_game);
inventory.store(&mut save_game);
abilities.store(&mut save_game);
crafting_materials.store(&mut save_game);
save_game.store()?;
Ok(())
}
}
impl Hero {
fn level(hero_create_type: &HeroCreateType, exp_settings: &ExperienceSettings) -> Level {
match hero_create_type {
HeroCreateType::SaveGame(save_game) => {
Level::load(save_game.general.level, save_game.general.exp, exp_settings)
}
HeroCreateType::New(_) => Level::new(exp_settings),
}
}
fn attributes(
hero_create_type: &HeroCreateType,
attribute_settings: &AttributeSettings,
) -> Attributes {
match hero_create_type {
HeroCreateType::SaveGame(save_game) => Attributes::load(
save_game.general.strength,
save_game.general.agility,
save_game.general.intelligence,
),
HeroCreateType::New(_) => Attributes::new(&attribute_settings.starting_attributes),
}
}
fn items(
hero_create_type: &HeroCreateType,
game_handle: GameHandle,
entity_object: &mut EntityObject,
) -> Result<ItemSlotContainer> {
let mut multi_mut = entity_object.multi_mut();
let draw = multi_mut.get::<Draw>().unwrap();
let game = game_handle.upgrade();
let mut items = match hero_create_type {
HeroCreateType::SaveGame(save_game) => {
ItemSlotContainer::load(save_game, &game.item_system())?
}
HeroCreateType::New(_) => ItemSlotContainer::new(),
};
items.set_item_change_callback(draw, multi_mut.get::<Location>()?)?;
Ok(items)
}
fn inventory(
hero_create_type: &HeroCreateType,
item_system: &Arc<ItemSystem>,
) -> Result<Inventory> {
match hero_create_type {
HeroCreateType::SaveGame(save_game) => Inventory::load(save_game, item_system),
HeroCreateType::New(_) => Ok(Inventory::default()),
}
}
fn abilities(
hero_create_type: &HeroCreateType,
game_handle: GameHandle,
) -> Result<AbilitySlots> {
let game = game_handle.upgrade();
match hero_create_type {
HeroCreateType::SaveGame(save_game) => {
AbilitySlots::load(&game.item_system(), save_game)
}
HeroCreateType::New(_) => {
let mut abilities = AbilitySlots::empty();
let book = game_handle.upgrade().item_system().ability_book(
"Basic Attack",
Rarities::Common,
Vec::new(),
1,
);
abilities.insert_book(book, 0);
Ok(abilities)
}
}
}
fn crafting_materials(hero_create_type: &HeroCreateType) -> CraftingMaterials {
match hero_create_type {
HeroCreateType::SaveGame(save_game) => CraftingMaterials::load(save_game),
HeroCreateType::New(_) => CraftingMaterials::default(),
}
}
fn movement(entity_object: &mut EntityObject) -> Movement {
entity_object
.get_component_mut::<HitBox>()
.unwrap()
.set_event(move |scene, me, collider| {
let mut entities_multi_mut = scene.entities_multi_mut();
if let Ok(my_object) = entities_multi_mut.get(me) {
if let Ok(collider_object) = entities_multi_mut.get(collider) {
if collider_object.get_component::<LootStash>().is_ok() {
my_object
.get_component_mut::<Movement>()?
.set_direction(vec2(0.0, 0.0));
}
if collider_object.get_component::<FactionFriendly>().is_ok()
|| collider_object.get_component::<FactionPlayer>().is_ok()
{
return Ok(CollisionEventType::Ignore);
}
}
}
Ok(CollisionEventType::Block)
});
let movement = {
let audio = entity_object.get_component_mut::<Audio>().ok();
Movement::new(1.0, audio)
};
movement
}
}

View file

@ -1,2 +0,0 @@
pub mod hero;
pub mod npc;

View file

@ -1,408 +0,0 @@
use std::{
collections::{hash_map::Iter, HashMap},
path::Path,
};
use crate::game::content::prelude::*;
use crate::game::game::GameHandle;
use anyhow::Result;
use assetpath::AssetPath;
use cgmath::{vec3, Deg, InnerSpace, Vector3, Zero};
use engine::prelude::*;
use entity_manager::*;
use rpg_components::{
components::{
attributes::Attributes,
character_status::CharacterStatus,
level::Level,
npc_type::{NPCBoss, NPCElite, NPCNormal, NPCType},
statistics::Statistics,
},
items::Rarities,
};
const NPC_BASE_STATS: u32 = 2;
create_settings_section!(
NPCMetaInfo,
"Meta",
{
entity_file: String,
name: String,
movement_speed: f32,
aggro_radius: f32,
deaggro_radius: f32,
scale: f32,
mob_type: NPCType,
strength_per_level: f32,
intelligence_per_level: f32,
agility_per_level: f32,
}
);
create_settings_section!(
AbilitySlot0,
"AbilitySlot_0",
{
used: bool,
name: String,
rarity: Rarities,
level: f32,
[addons: String],
}
);
create_settings_section!(
AbilitySlot1,
"AbilitySlot_1",
{
used: bool,
name: String,
rarity: Rarities,
level: f32,
[addons: String],
}
);
create_settings_section!(
AbilitySlot2,
"AbilitySlot_2",
{
used: bool,
name: String,
rarity: Rarities,
level: f32,
[addons: String],
}
);
create_settings_section!(
AbilitySlot3,
"AbilitySlot_3",
{
used: bool,
name: String,
rarity: Rarities,
level: f32,
[addons: String],
}
);
pub trait NPCAbilitySlot {
fn used(&mut self) -> &mut bool;
fn name(&mut self) -> &mut String;
fn rarity(&mut self) -> &mut Rarities;
fn level(&mut self) -> &mut f32;
fn addons(&mut self) -> &mut [String];
}
impl NPCAbilitySlot for AbilitySlot0 {
fn used(&mut self) -> &mut bool {
&mut self.used
}
fn name(&mut self) -> &mut String {
&mut self.name
}
fn rarity(&mut self) -> &mut Rarities {
&mut self.rarity
}
fn level(&mut self) -> &mut f32 {
&mut self.level
}
fn addons(&mut self) -> &mut [String] {
&mut self.addons
}
}
impl NPCAbilitySlot for AbilitySlot1 {
fn used(&mut self) -> &mut bool {
&mut self.used
}
fn name(&mut self) -> &mut String {
&mut self.name
}
fn rarity(&mut self) -> &mut Rarities {
&mut self.rarity
}
fn level(&mut self) -> &mut f32 {
&mut self.level
}
fn addons(&mut self) -> &mut [String] {
&mut self.addons
}
}
impl NPCAbilitySlot for AbilitySlot2 {
fn used(&mut self) -> &mut bool {
&mut self.used
}
fn name(&mut self) -> &mut String {
&mut self.name
}
fn rarity(&mut self) -> &mut Rarities {
&mut self.rarity
}
fn level(&mut self) -> &mut f32 {
&mut self.level
}
fn addons(&mut self) -> &mut [String] {
&mut self.addons
}
}
impl NPCAbilitySlot for AbilitySlot3 {
fn used(&mut self) -> &mut bool {
&mut self.used
}
fn name(&mut self) -> &mut String {
&mut self.name
}
fn rarity(&mut self) -> &mut Rarities {
&mut self.rarity
}
fn level(&mut self) -> &mut f32 {
&mut self.level
}
fn addons(&mut self) -> &mut [String] {
&mut self.addons
}
}
create_settings_container!(
NPCSettings,
{
meta: NPCMetaInfo,
ability_0: AbilitySlot0,
ability_1: AbilitySlot1,
ability_2: AbilitySlot2,
ability_3: AbilitySlot3,
}
);
pub struct NPCFactory {
elite_npcs: HashMap<String, NPCSettings>,
normal_npcs: HashMap<String, NPCSettings>,
boss_npcs: HashMap<String, NPCSettings>,
}
impl NPCFactory {
pub fn new(npc_directory: AssetPath) -> Result<Self> {
let files = search_dir_recursively(&npc_directory.full_path(), ".npc")?;
let mut elite_npcs = HashMap::new();
let mut normal_npcs = HashMap::new();
let mut boss_npcs = HashMap::new();
for npc_file in files {
let npc_name = Path::new(&npc_file.full_path())
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string();
let npc_setting = NPCSettings::load(npc_file)?;
match npc_setting.meta.mob_type {
NPCType::Normal => normal_npcs.insert(npc_name, npc_setting),
NPCType::Elite => elite_npcs.insert(npc_name, npc_setting),
NPCType::Boss => boss_npcs.insert(npc_name, npc_setting),
};
}
Ok(Self {
elite_npcs,
normal_npcs,
boss_npcs,
})
}
pub fn npc(&self, name: &String, npc_type: impl Into<NPCType>) -> Option<&NPCSettings> {
match npc_type.into() {
NPCType::Normal => self.normal_npcs.get(name),
NPCType::Elite => self.elite_npcs.get(name),
NPCType::Boss => self.boss_npcs.get(name),
}
}
pub fn npcs(&self, npc_type: impl Into<NPCType>) -> Iter<'_, String, NPCSettings> {
match npc_type.into() {
NPCType::Normal => self.normal_npcs.iter(),
NPCType::Elite => self.elite_npcs.iter(),
NPCType::Boss => self.boss_npcs.iter(),
}
}
fn find_setting(&self, npc: &String) -> Result<&NPCSettings> {
self.normal_npcs
.get(npc)
.or(self.elite_npcs.get(npc))
.or(self.boss_npcs.get(npc))
.ok_or(anyhow::Error::msg(format!("NPC file not found {}", npc)))
}
pub fn create_npc(
&self,
entity_manager: &mut EntityManager,
game_handle: &GameHandle,
npc: &String,
level: u32,
position: Vector3<f32>,
) -> Result<EntityObject> {
let game = game_handle.upgrade();
let npc_settings = self.find_setting(npc)?;
let mut entity_object =
entity_manager.load_entity(game.engine().assets(), &npc_settings.meta.entity_file)?;
// ========== create components ==========
// location
let location = Location::new_and_setup(&mut entity_object)?;
location.update_raw(position, Deg(Random::range_f32(0.0, 360.0)));
// scale
let scale = npc_settings.meta.scale;
location.set_scale(vec3(scale, scale, scale));
let hitbox = entity_object.get_component_mut::<HitBox>()?;
hitbox.set_radius(hitbox.radius() * scale);
hitbox.set_height(hitbox.height() * scale);
// movement
let movement = Movement::new(
npc_settings.meta.movement_speed,
entity_object.get_component_mut::<Audio>().ok(),
);
// attributes
let strength = (level as f32 * npc_settings.meta.strength_per_level) as u32;
let intelligence = (level as f32 * npc_settings.meta.intelligence_per_level) as u32;
let agility = (level as f32 * npc_settings.meta.agility_per_level) as u32;
let mut attributes = Attributes::load(
NPC_BASE_STATS + strength,
NPC_BASE_STATS + agility,
NPC_BASE_STATS + intelligence,
);
// statistics
let mut stats = Statistics::default();
stats.update(&mut attributes, &game.attribute_settings, None);
// character status
let character_status = CharacterStatus::new_full(&stats);
// name
let npc_name = NPCName::new(npc);
// entity faction
entity_object.insert_component(FactionNPC);
// npc type
match npc_settings.meta.mob_type {
NPCType::Normal => {
entity_object.insert_component(NPCNormal);
}
NPCType::Elite => {
entity_object.insert_component(NPCElite);
}
NPCType::Boss => {
entity_object.insert_component(NPCBoss);
}
}
// health bar
match npc_settings.meta.mob_type {
NPCType::Boss => {
let name = npc_name.name.clone();
entity_object.insert_component(BossHealthBar::new(&game, name)?);
}
_ => (),
}
// level
let level = Level::load(level, 0, &game.experience_settings);
// ai
let ai = SimpleAI::new(6.0, 9.0, entity_object.as_entity());
// abilities
// TODO
// let abilities =
// AbilitySlots::load_for_npc(level.level(), npc_settings, game_handle.clone())?;
// ========== insert components ==========
entity_object.insert_component(movement);
entity_object.insert_component(attributes);
entity_object.insert_component(stats);
entity_object.insert_component(character_status);
entity_object.insert_component(level);
// entity_object.insert_component(abilities);
entity_object.insert_component(npc_name);
entity_object.insert_component(ai);
// set hit box event
entity_object
.get_component_mut::<HitBox>()
.unwrap()
.set_event(move |scene, me, collider| {
let mut entity_multi_mut = scene.entities_multi_mut();
if let Ok(my_object) = entity_multi_mut.get(me) {
if let Ok(collider_object) = entity_multi_mut.get(collider) {
if collider_object.get_component::<FactionNPC>().is_ok() {
// if we hit an allied NPC, move both in parallel
let my_movement = my_object.get_component_mut::<Movement>().unwrap();
let other_movement =
collider_object.get_component_mut::<Movement>().unwrap();
if my_movement.direction().is_zero()
|| other_movement.direction().is_zero()
{
return Ok(CollisionEventType::Block);
}
let common_dir =
(my_movement.direction() + other_movement.direction()).normalize();
my_movement.set_direction(common_dir);
other_movement.set_direction(common_dir);
return Ok(CollisionEventType::Ignore);
} else if collider_object.get_component::<FactionFriendly>().is_ok() {
// friendly NPCs are ignored
return Ok(CollisionEventType::Ignore);
}
}
}
Ok(CollisionEventType::Block)
});
Ok(entity_object)
}
}

View file

@ -1,6 +0,0 @@
pub use super::abilities::prelude::*;
pub use super::ability_type::AbilityType;
pub use super::components::*;
pub use super::lightning::{Lightning, LightningMarker};
pub use super::objects::hero::{Hero, MainUser};
pub use super::objects::npc::{NPCAbilitySlot, NPCFactory, NPCSettings};

View file

@ -1,610 +0,0 @@
// engine
use engine::prelude::*;
use assetpath::AssetPath;
// use lua_wrapper::LuaFunction;
use anyhow::Result;
use entity_manager::*;
use lua_wrapper::LuaFunction;
use rpg_components::config::abilities::AbilitySettings;
use rpg_components::config::attributes::AttributeSettings;
use rpg_components::config::experience::ExperienceSettings;
use rpg_components::config::items::ItemSettings;
use rpg_components::items::ItemSystem;
// std
use std::collections::HashMap;
use std::sync::{Mutex, MutexGuard, RwLock};
use std::{fs::create_dir_all, path::Path, sync::Arc};
// game
use crate::game::configloader::*;
use crate::loader::settings::*;
use cgmath::Vector3;
use promise::Promise;
use super::handle::{StrongHandle, WeakHandle};
#[derive(Default, Debug, Clone)]
pub struct MapInformation {
// root directories for maps
pub map_directories: Vec<AssetPath>,
// possible start map, where players get spawned on connection
pub start_map: Option<String>,
// maps that are going to be ignored
pub black_list: Vec<String>,
}
pub struct Game {
engine: Arc<Engine>,
game_settings: GameSection,
_lua_scripts: LuaScripts,
data_directory: String,
pub attribute_settings: AttributeSettings,
pub item_settings: ItemSettings,
pub experience_settings: ExperienceSettings,
pub mob_settings: MobSettings,
pub ability_settings: AbilitySettings,
pub maps: RwLock<HashMap<String, AssetPath>>,
pub black_listed_maps: RwLock<HashMap<String, AssetPath>>,
pub tiles: RwLock<HashMap<String, AssetPath>>,
settings_file_location: AssetPath,
item_system: Promise<Arc<ItemSystem>>,
entity_manager: Mutex<EntityManager>,
slide_images: Mutex<HashMap<String, Arc<Image>>>,
pub change_map_context: LuaFunction,
map_info: MapInformation,
}
pub type GameHandle = WeakHandle<Game>;
pub type StrongGame = StrongHandle<Game>;
impl GameHandle {
pub fn gui_builder(&self, path: &str) -> Result<Arc<GuiBuilder>> {
let strong = self.upgrade();
GuiBuilder::new(strong.engine.gui_handler(), &strong.build_data_path(path))
}
pub fn gui_snippet(&self, path: &str) -> Result<Arc<GuiSnippet>> {
let strong = self.upgrade();
GuiSnippet::new(strong.engine.gui_handler(), &strong.build_data_path(path))
}
pub fn controller_icon(&self, button: ControllerButton) -> Result<Arc<Image>> {
let game = self.upgrade();
let engine = game.engine();
engine
.settings()
.controller_button_for(engine, button, ControllerType::XBox)
}
pub fn build_data_path(&self, path: &str) -> AssetPath {
self.upgrade().build_data_path(path)
}
}
pub struct Settings {
pub core_settings: CoreSettings,
pub user_settings: UserSettings,
pub data_path: String,
pub user_settings_file: AssetPath,
}
impl Game {
pub fn load_settings<'a>(data_path: impl Into<Option<&'a str>>) -> Result<Settings> {
let data_path = data_path.into().unwrap_or("data");
println!("Data directory: {}", data_path);
let mut core_settings = CoreSettings::load((data_path, "settings.conf"))?;
let mut user_settings = UserSettings::load((data_path, "user_settings.conf"))?;
let gavania_save_dir = settings_file_dir();
Self::verify_dir_existence(&gavania_save_dir)?;
let user_settings_file = AssetPath::from((gavania_save_dir, "user_settings.conf"));
if user_settings_file.exists() {
user_settings.load_with_default(user_settings_file.clone())?;
} else {
user_settings.file_name = user_settings_file.clone();
}
user_settings.store()?;
Self::apply_data_dir(&mut core_settings, data_path);
Ok(Settings {
core_settings,
user_settings,
data_path: data_path.to_string(),
user_settings_file,
})
}
pub fn new(settings: Settings) -> Result<StrongGame> {
#[cfg(target_os = "linux")]
{
Self::set_radeon_ray_tracing();
#[cfg(feature = "wayland")]
Self::check_wayland_for_sdl();
}
let game_settings = settings.core_settings.game.clone();
let lua_scripts = settings.core_settings.lua_scripts.clone();
let map_info = settings.core_settings.map_info();
#[allow(unused_mut)]
let mut engine_create_info =
into_engine_info(settings.core_settings, settings.user_settings);
{
engine_create_info.app_info.application_name = "Gavania".to_string();
engine_create_info.app_info.application_version = 1;
}
let attribute_settings =
AttributeSettings::load((settings.data_path.as_str(), "configs/stats.conf"))?;
let mut item_settings =
ItemSettings::load((settings.data_path.as_str(), "configs/items.conf"))?;
let experience_settings =
ExperienceSettings::load((settings.data_path.as_str(), "configs/experience.conf"))?;
let mob_settings = MobSettings::load((settings.data_path.as_str(), "configs/mobs.conf"))?;
let mut ability_settings =
AbilitySettings::load((settings.data_path.as_str(), "configs/abilities.conf"))?;
Self::apply_data_dir_to_configs(
&mut item_settings,
&mut ability_settings,
&settings.data_path,
);
// ----------------- maps ---------------------
let mut map_map = HashMap::new();
let mut black_listed_maps = HashMap::new();
for map_directory in map_info.map_directories.iter() {
let map_vector = search_dir_recursively(&map_directory.full_path(), "")?;
'outer: for file in &map_vector {
let s = file.full_path();
let path = Path::new(s.as_str());
if let Some(name) = path.file_stem() {
if let Some(name_str) = name.to_str() {
let string = name_str.to_string();
for blacked in map_info.black_list.iter() {
if *blacked == string {
assert!(
black_listed_maps
.insert(string.clone(), file.clone())
.is_none(),
"blacklisted map already present: {}",
string
);
continue 'outer;
}
}
assert!(
map_map.insert(string.clone(), file.clone()).is_none(),
"map already present: {}",
string
);
}
}
}
}
let game = {
engine_create_info.gui_info.resource_directory = settings.data_path.as_str().into();
engine_create_info.resource_base_path = settings.data_path.clone();
let engine = Engine::new(engine_create_info.clone())?;
// ----------------- tiles --------------------
let tile_vector = search_dir_recursively(
engine.settings().tiles_directory().full_path().as_str(),
".png",
)?;
let mut tile_map = HashMap::new();
for file in &tile_vector {
let s = file.full_path();
let path = Path::new(s.as_str());
if let Some(name) = path.file_stem() {
if let Some(name_str) = name.to_str() {
let string = name_str.to_string();
tile_map.insert(string.clone(), file.clone());
}
}
}
let item_system = Promise::new({
let engine = engine.clone();
let item_settings = item_settings.clone();
let ability_settings = ability_settings.clone();
let attribute_settings = attribute_settings.clone();
let ability_directory = game_settings.ability_directory.clone();
let data_path = settings.data_path.clone();
move || {
Ok(Arc::new(ItemSystem::new(
&engine,
&item_settings,
&ability_settings,
&attribute_settings,
&ability_directory,
&data_path,
)?))
}
});
StrongHandle::new(Game {
maps: RwLock::new(map_map),
black_listed_maps: RwLock::new(black_listed_maps),
tiles: RwLock::new(tile_map),
change_map_context: LuaFunction::new(&lua_scripts.change_map)?,
game_settings,
_lua_scripts: lua_scripts,
data_directory: settings.data_path.to_string(),
attribute_settings,
item_settings,
experience_settings,
mob_settings,
ability_settings,
item_system,
entity_manager: Mutex::new(EntityManager::new(
engine.settings().entity_directory().clone(),
)?),
settings_file_location: settings.user_settings_file,
slide_images: Mutex::new(HashMap::new()),
map_info,
engine,
})
};
Ok(game)
}
fn verify_dir_existence(dir: &str) -> Result<()> {
let path = Path::new(&dir);
if !path.exists() {
create_dir_all(path)?;
}
Ok(())
}
pub fn run(&self) -> Result<()> {
self.engine.run()
}
pub fn set_game_state<E>(&self, game_object: E) -> Result<()>
where
E: EngineObject + Send + Sync,
{
self.engine.set_game_object(Some(game_object))
}
pub fn quit(&self) -> Result<()> {
self.store_settings()?;
self.engine.quit()?;
Ok(())
}
#[cfg(target_os = "linux")]
fn set_radeon_ray_tracing() {
std::env::set_var("RADV_PERFTEST", "rt");
}
#[cfg(feature = "wayland")]
#[cfg(target_os = "linux")]
fn check_wayland_for_sdl() {
if let Ok(session_type) = std::env::var("XDG_SESSION_TYPE") {
if session_type == "wayland" {
std::env::set_var("SDL_VIDEODRIVER", "wayland");
}
}
}
pub fn map_directories(&self) -> &[AssetPath] {
&self.map_info.map_directories
}
pub fn home_map(&self) -> Option<&String> {
self.map_info.start_map.as_ref()
}
pub fn black_listed_maps(&self) -> &[String] {
&self.map_info.black_list
}
pub fn engine(&self) -> &Arc<Engine> {
&self.engine
}
pub fn game_settings(&self) -> &GameSection {
&self.game_settings
}
pub fn item_system(&self) -> Arc<ItemSystem> {
self.item_system.get()
}
pub fn data_directory(&self) -> &str {
&self.data_directory
}
pub fn build_data_path(&self, path: &str) -> AssetPath {
AssetPath::from((self.data_directory.as_str(), path))
}
pub fn entity_manager(&self) -> MutexGuard<'_, EntityManager> {
self.entity_manager.lock().unwrap()
}
pub fn slide_images(&self) -> MutexGuard<'_, HashMap<String, Arc<Image>>> {
self.slide_images.lock().unwrap()
}
pub fn gui_builder(&self, path: &str) -> Result<Arc<GuiBuilder>> {
GuiBuilder::new(self.engine.gui_handler(), &self.build_data_path(path))
}
pub fn gui_snippet(&self, path: &str) -> Result<Arc<GuiSnippet>> {
GuiSnippet::new(self.engine.gui_handler(), &self.build_data_path(path))
}
pub fn store_settings(&self) -> Result<()> {
let mut user_settings = UserSettings::load(self.settings_file_location.clone())?;
// first update settings
if self.engine.window_config().is_fullscreen() {
user_settings.window.fullscreen = true;
} else {
user_settings.window.fullscreen = false;
let render_core = self.engine.context().render_core();
user_settings.window.width = render_core.width();
user_settings.window.height = render_core.height();
}
{
let sound = self.engine.sound();
user_settings.audio.master = sound.volume("master")?;
user_settings.audio.gui = sound.volume("gui")?;
user_settings.audio.sfx = sound.volume("sfx")?;
user_settings.audio.music = sound.volume("music")?;
}
// check if renderer type actually changed
// if so, clear all loaded entities
let renderer_type = self.engine.settings().graphics_info()?.render_type;
if renderer_type != user_settings.graphics.renderer_type {
user_settings.graphics.renderer_type = renderer_type;
self.engine.assets().clear()?;
}
user_settings.store()?;
Ok(())
}
pub fn set_frustum_check(scene: &mut Scene) {
let entity_name = "Lightning".to_string();
scene.set_frustum_check(move |entity, _view_proj, _view_frustum, frustum_planes| {
// filter everything that isnt Draw
if !entity.contains_component::<Draw>() {
if entity.debug_name.as_ref() == Some(&entity_name) {
println!("{entity_name}-Entity does not contain Draw");
}
return Ok(false);
}
// filter everything that isnt Bounding Box
let bb = match entity.get_component::<BoundingBox>() {
Ok(bounding_box) => bounding_box,
Err(_) => {
if entity.debug_name.as_ref() == Some(&entity_name) {
println!("{entity_name}-Entity does not contain BoundingBox");
}
return Ok(false);
}
};
let transformation_matrix = entity
.get_component::<Location>()
.ok()
.map(|location| location.transformation_matrix());
let planes = frustum_planes.as_array();
let corners: Vec<Vector3<f32>> = bb
.corners()
.iter()
.map(|corner| match transformation_matrix {
Some(m) => (m * corner.extend(1.0)).xyz(),
None => *corner,
})
.collect();
// if there is a plane where all corners are outside, the box is not visible
for plane in planes {
let mut outside = true;
for corner in corners.iter() {
if !plane.is_above(*corner) {
outside = false;
break;
}
}
if outside {
return Ok(false);
}
}
if entity.debug_name.as_ref() == Some(&entity_name) {
println!("{entity_name}-Entity is inside");
}
Ok(true)
});
}
fn apply_data_dir(settings: &mut CoreSettings, data_dir: &str) {
// game
settings.game.sounds_directory.set_prefix(data_dir);
settings.game.music_directory.set_prefix(data_dir);
settings.game.ability_icon_directory.set_prefix(data_dir);
settings.game.ability_directory.set_prefix(data_dir);
settings.game.particle_directory.set_prefix(data_dir);
settings.game.slides_directory.set_prefix(data_dir);
settings.game.npc_directory.set_prefix(data_dir);
// map info
settings.map_infos.directory.set_prefix(data_dir);
// scripts
settings.lua_scripts.change_map.set_prefix(data_dir);
}
fn apply_data_dir_to_configs(
item_settings: &mut ItemSettings,
ability_settings: &mut AbilitySettings,
data_dir: &str,
) {
item_settings.icon_paths.amulet.set_prefix(data_dir);
item_settings.icon_paths.background.set_prefix(data_dir);
item_settings.icon_paths.boots.set_prefix(data_dir);
item_settings.icon_paths.chest.set_prefix(data_dir);
item_settings.icon_paths.helmet.set_prefix(data_dir);
item_settings.icon_paths.jewel.set_prefix(data_dir);
item_settings.icon_paths.ring.set_prefix(data_dir);
item_settings.icon_paths.belt.set_prefix(data_dir);
item_settings.icon_paths.gloves.set_prefix(data_dir);
item_settings.icon_paths.main_hand.set_prefix(data_dir);
item_settings.icon_paths.off_hand.set_prefix(data_dir);
item_settings.jewel_paths.first.set_prefix(data_dir);
item_settings.jewel_paths.second.set_prefix(data_dir);
item_settings.jewel_paths.third.set_prefix(data_dir);
item_settings.jewel_paths.fourth.set_prefix(data_dir);
ability_settings.icons.book.set_prefix(data_dir);
ability_settings.icons.damage.set_prefix(data_dir);
ability_settings.icons.projectile_speed.set_prefix(data_dir);
ability_settings.icons.bounce.set_prefix(data_dir);
ability_settings.icons.explosion.set_prefix(data_dir);
ability_settings.icons.size.set_prefix(data_dir);
ability_settings
.icons
.additional_projectiles
.set_prefix(data_dir);
ability_settings.icons.cool_down.set_prefix(data_dir);
ability_settings.icons.distance.set_prefix(data_dir);
ability_settings.icons.addon_background.set_prefix(data_dir);
}
fn gather_maps(
map_directories: &[AssetPath],
home_map: Option<&str>,
ignore_maps: &[String],
) -> Result<Vec<String>> {
let mut maps = Vec::new();
for map_directory in map_directories.iter() {
for map in search_dir_recursively(&map_directory.full_path(), "")? {
let map_name = Path::new(&map.full_path())
.file_name()
.expect("map is not a file name")
.to_str()
.expect("failed converting OsStr to str")
.to_string();
if let Some(home) = home_map {
if map_name == home {
continue;
}
}
if ignore_maps.contains(&map_name) {
continue;
}
maps.push(map_name);
}
}
Ok(maps)
}
pub fn next_map(&self, current_map: &str) -> Result<String> {
let map_directories = self.map_directories();
let home_map = self.home_map().map(|s| s.as_str());
let ignore_maps: Vec<String> = self
.black_listed_maps
.read()
.unwrap()
.keys()
.cloned()
.collect();
let maps = Self::gather_maps(map_directories, home_map, &ignore_maps)?;
let map_count = maps.len();
let next_map = self.change_map_context.execute((
current_map,
home_map.unwrap_or(""),
maps,
map_count,
))?;
Ok(next_map)
}
}
impl Drop for Game {
fn drop(&mut self) {}
}

View file

@ -1,76 +0,0 @@
use std::{
ops::Deref,
sync::{Arc, Weak},
};
use anyhow::{anyhow, Result};
pub struct StrongHandle<T> {
strong: Arc<T>,
}
impl<T> StrongHandle<T> {
pub fn new(t: T) -> Self {
Self {
strong: Arc::new(t),
}
}
}
impl<T> Deref for StrongHandle<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.strong
}
}
#[derive(Debug)]
pub struct WeakHandle<T> {
weak: Weak<T>,
}
impl<T> WeakHandle<T> {
pub fn on<F, R>(&self, f: F) -> Result<R>
where
F: FnOnce(&T) -> Result<R>,
{
let strong: Arc<T> = self
.weak
.upgrade()
.ok_or(anyhow!("failed to get strong handle"))?;
f(&strong)
}
pub fn upgrade(&self) -> StrongHandle<T> {
StrongHandle {
strong: self.weak.upgrade().unwrap(),
}
}
pub fn try_upgrade(&self) -> Result<StrongHandle<T>> {
Ok(StrongHandle {
strong: self
.weak
.upgrade()
.ok_or(anyhow!("failed to upgrade WeakHandle"))?,
})
}
}
impl<T> Clone for WeakHandle<T> {
fn clone(&self) -> Self {
Self {
weak: self.weak.clone(),
}
}
}
impl<T> From<&StrongHandle<T>> for WeakHandle<T> {
fn from(handle: &StrongHandle<T>) -> Self {
Self {
weak: Arc::downgrade(&handle.strong),
}
}
}

View file

@ -1,5 +0,0 @@
#[macro_use]
pub mod configloader;
pub mod content;
pub mod game;
pub mod handle;

View file

@ -1,14 +0,0 @@
//! This is the documentation for `gavania-core`
#![deny(rust_2018_idioms)]
#![deny(unused_must_use)]
pub mod game;
mod loader;
pub use anyhow::{Error, Result};
pub use engine::prelude::*;
pub use game::content::prelude::*;
pub use game::game::{Game, GameHandle, StrongGame};
pub use game::handle::*;
pub use loader::settings::*;

View file

@ -1 +0,0 @@
pub mod settings;

View file

@ -1,325 +0,0 @@
#![allow(unused)]
use assetpath::AssetPath;
use engine::prelude::*;
use std::{collections::HashMap, env::var, marker::PhantomData};
use crate::game::game::MapInformation;
#[cfg(target_os = "windows")]
pub fn settings_file_dir() -> String {
let b = var("LOCALAPPDATA").expect("couldn't get local appdata variable");
format!("{}\\gavania\\", b)
}
#[cfg(target_os = "linux")]
pub fn settings_file_dir() -> String {
let b = var("HOME").expect("couldn't get HOME variable");
format!("{}/.local/share/gavania/", b)
}
#[cfg(target_os = "macos")]
pub fn settings_file_dir() -> String {
compile_error!("settings directory not set!");
}
create_settings_section!(
LuaScripts,
"Scripts",
{
change_map: AssetPath,
}
);
create_settings_section!(
VulkanSection,
"Vulkan",
{
debugging: bool,
steam_layer: bool,
verbose: bool,
renderdoc: bool,
util: bool,
}
);
create_settings_section!(
WindowSection,
"Window",
{
window_title: String,
width: u32,
height: u32,
fullscreen: bool,
preferred_display: String,
}
);
create_settings_section!(
InputSection,
"Input",
{
keyboard_enabled: bool,
mouse_enabled: bool,
controller_enabled: bool,
axis_enable_deadzone: f32,
axis_disable_deadzone: f32,
trigger_enable_deadzone: f32,
trigger_disable_deadzone: f32,
}
);
create_settings_section!(
GuiSection,
"Gui",
{
font: AssetPath,
default_button: AssetPath,
default_selected_button: AssetPath,
default_click_sound: AssetPath,
default_hover_sound: AssetPath,
}
);
create_settings_section!(
GraphicsSection,
"Graphics",
{
renderer_type: SceneType,
render_scale: f32,
sample_count: u32,
vsync: bool,
}
);
create_settings_section!(
RaytracingSection,
"Raytracing",
{
tris_per_as: u32,
max_samplers: u32,
acceleration_count: u32,
stack_size: u32,
max_lights: u32,
recursion_depth: u32,
use_default_pipeline: bool,
}
);
create_settings_section!(
RasterizerSection,
"Rasterizing",
{
shadow_image_size: u32,
enable_lighting: bool,
use_deferred: bool,
use_shadow_maps: bool,
}
);
create_settings_section!(
EngineSection,
"Engine",
{
tile_directory: AssetPath,
entity_directory: AssetPath,
gltf_directory: AssetPath,
xbox_controller_directory: AssetPath,
steam_controller_directory: AssetPath,
ps4_controller_directory: AssetPath,
light_key: AssetPath,
dark_key: AssetPath,
}
);
create_settings_section!(
AudioSection,
"Audio",
{
master: f32,
music: f32,
gui: f32,
sfx: f32,
}
);
create_settings_section!(
TweakSection,
"Tweaks",
{
enable_gamemode: bool,
enable_backtrace: bool,
}
);
create_settings_section!(
GameSection,
"Game",
{
sounds_directory: AssetPath,
music_directory: AssetPath,
ability_icon_directory: AssetPath,
ability_directory: AssetPath,
slides_directory: AssetPath,
particle_directory: AssetPath,
npc_directory: AssetPath,
camera_distance: f32,
camera_angle: f32,
loot_action_radius: f32,
}
);
create_settings_section!(
MapInfos,
"Map Infos",
{
directory: AssetPath,
start: String,
[black_list: String],
}
);
create_settings_container!(
CoreSettings,
{
vulkan: VulkanSection,
gui: GuiSection,
engine: EngineSection,
game: GameSection,
map_infos: MapInfos,
lua_scripts: LuaScripts,
}
);
create_settings_container!(
UserSettings,
{
window: WindowSection,
input: InputSection,
graphics: GraphicsSection,
ray_tracing: RaytracingSection,
rasterizer: RasterizerSection,
audio: AudioSection,
tweaks: TweakSection,
}
);
impl CoreSettings {
pub fn map_info(&self) -> MapInformation {
MapInformation {
map_directories: vec![self.map_infos.directory.clone()],
start_map: if self.map_infos.start.is_empty() {
None
} else {
Some(self.map_infos.start.clone())
},
black_list: self.map_infos.black_list.clone(),
}
}
}
pub fn into_engine_info<'a>(
core_settings: CoreSettings,
user_settings: UserSettings,
) -> EngineCreateInfo<'a> {
EngineCreateInfo {
app_info: ApplicationInfo::default(),
window_info: WindowCreateInfo {
title: user_settings.window.window_title,
width: user_settings.window.width,
height: user_settings.window.height,
fullscreen: user_settings.window.fullscreen,
requested_display: if user_settings.window.preferred_display.is_empty() {
None
} else {
Some(user_settings.window.preferred_display)
},
},
os_specific_config: OsSpecificConfig {
enable_game_mode: user_settings.tweaks.enable_gamemode,
},
vulkan_debug_info: VulkanDebugInfo {
debugging: core_settings.vulkan.debugging,
steam_layer: core_settings.vulkan.steam_layer,
verbose: core_settings.vulkan.verbose,
renderdoc: core_settings.vulkan.renderdoc,
},
volume_info: {
let mut audio = HashMap::new();
audio.insert("master".to_string(), user_settings.audio.master);
audio.insert("music".to_string(), user_settings.audio.music);
audio.insert("gui".to_string(), user_settings.audio.gui);
audio.insert("sfx".to_string(), user_settings.audio.sfx);
audio
},
gui_info: GuiHandlerCreateInfo {
menu_button: core_settings.gui.default_button,
menu_button_selected: core_settings.gui.default_selected_button,
font: Font::Path(core_settings.gui.font),
click_sound: core_settings.gui.default_click_sound,
hover_sound: core_settings.gui.default_hover_sound,
resource_directory: AssetPath::default(),
},
enable_backtrace: user_settings.tweaks.enable_backtrace,
enable_mouse: user_settings.input.mouse_enabled,
enable_keyboard: user_settings.input.keyboard_enabled,
enable_controller: user_settings.input.controller_enabled,
resource_base_path: String::new(),
controller_deadzones: ControllerDeadzones {
axis_enable_deadzone: user_settings.input.axis_enable_deadzone,
axis_disable_deadzone: user_settings.input.axis_disable_deadzone,
trigger_enable_deadzone: user_settings.input.trigger_enable_deadzone,
trigger_disable_deadzone: user_settings.input.trigger_disable_deadzone,
},
controller_directories: ControllerPictureDirectories {
xbox_path: core_settings.engine.xbox_controller_directory,
steam_path: core_settings.engine.steam_controller_directory,
ps4_path: core_settings.engine.ps4_controller_directory,
},
asset_directories: AssetDirectories {
entity_file_directory: core_settings.engine.entity_directory,
gltf_file_directory: core_settings.engine.gltf_directory,
tile_file_directory: core_settings.engine.tile_directory,
},
graphics_info: GraphicsInfo {
sample_count: VkSampleCountFlags::from(user_settings.graphics.sample_count),
render_type: user_settings.graphics.renderer_type,
render_scale: user_settings.graphics.render_scale,
vsync: user_settings.graphics.vsync,
},
raytracing_info: RaytracingInfo {
triangles_per_as: user_settings.ray_tracing.tris_per_as,
accelerate_buffer_size: user_settings.ray_tracing.acceleration_count,
stack_size: user_settings.ray_tracing.stack_size,
max_lights: user_settings.ray_tracing.max_lights,
recursion_depth: user_settings.ray_tracing.recursion_depth,
use_default_pipeline: user_settings.ray_tracing.use_default_pipeline,
max_samplers: user_settings.ray_tracing.max_samplers,
},
rasterizer_info: RasterizerInfo {
shadow_image_size: user_settings.rasterizer.shadow_image_size,
enable_lighting: user_settings.rasterizer.enable_lighting,
use_deferred: user_settings.rasterizer.use_deferred,
use_shadow_maps: user_settings.rasterizer.use_shadow_maps,
},
key_backgrounds: ButtonBackgrounds {
light: core_settings.engine.light_key,
dark: core_settings.engine.dark_key,
},
}
}

View file

@ -1,36 +0,0 @@
#![allow(dead_code)]
fn dynamic_library(library_name: &str) {
println!("cargo:rustc-link-lib=dylib={}", library_name);
}
fn static_library(library_name: &str) {
println!("cargo:rustc-link-lib=static={}", library_name);
}
fn mac_framework_path(path: &str) {
println!("cargo:rustc-link-search=framework={}", path);
}
fn native_path(path: &str) {
println!("cargo:rustc-link-search=native={}", path);
}
#[cfg(target_os = "macos")]
fn set_compiler_flags() {
native_path("Gavania/lib/MacOS");
}
#[cfg(target_os = "windows")]
fn set_compiler_flags() {
native_path("lib/windows");
}
#[cfg(target_os = "linux")]
fn set_compiler_flags() {
native_path("lib/linux");
}
fn main() {
set_compiler_flags();
}

View file

@ -10,3 +10,4 @@ assetpath = { workspace = true }
destructure_traitobject = { workspace = true }
engine = { path = "../engine" }
ecs = { path = "../ecs" }

View file

@ -2,7 +2,6 @@
use anyhow::Result;
use assetpath::AssetPath;
use engine::prelude::PrimitiveMaterial;
use engine::prelude::*;
use super::{
@ -12,26 +11,20 @@ use super::{
};
use super::async_db::AsyncDBAccess;
use super::map_db::{DBEntityInfo, MapDataBase};
// std
use std::cmp::{max, min};
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::{Arc, RwLock};
use std::{collections::HashMap, intrinsics::transmute};
use std::collections::HashMap;
use std::ops::IndexMut;
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::{Arc, RwLock};
// cgmath
use cgmath::Rad;
use cgmath::{vec2, InnerSpace, Vector2, Vector3, Zero};
use cgmath::{InnerSpace, Vector2, Vector3, Zero, vec2};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MapObjectType {
@ -58,16 +51,11 @@ impl Map {
// describes the length of a chunk (e.g.: 10 in x and in y direction = 100 tiles per chunk)
const CHUNK_DIMENSION: u32 = 10;
pub fn downcast(upcasted: &Box<dyn engine::prelude::Map>) -> &Box<Self> {
unsafe { transmute(destructure_traitobject::data(upcasted)) }
}
pub fn new(
name: &str,
width: u32,
height: u32,
engine: &Engine,
scene: &mut Scene,
world: &mut World,
maps: &mut HashMap<String, AssetPath>,
tiles: &HashMap<String, AssetPath>,
entity_manager: &mut impl AssetLoader,
@ -76,8 +64,8 @@ impl Map {
let map_path = build_path(&("maps/".to_string() + name));
let sql_connection = MapDataBase::new(map_path.full_path(), name, width, height)?;
let map = Self::load_data_from_db(engine, scene, sql_connection, tiles, entity_manager)?;
scene.set_map(Some(Box::new(map)))?;
let map = Self::load_data_from_db(world, sql_connection, tiles, entity_manager)?;
world.resources.insert(map);
maps.insert(name.to_string(), map_path);
@ -85,15 +73,14 @@ impl Map {
}
pub fn load_raw_scene(
engine: &Engine,
scene: &mut Scene,
world: &mut World,
path: AssetPath,
tiles: &HashMap<String, AssetPath>,
entity_manager: &mut impl AssetLoader,
) -> Result<()> {
let sql_connection = MapDataBase::load(&path.full_path())?;
let map = Self::load_data_from_db(engine, scene, sql_connection, tiles, entity_manager)?;
scene.set_map(Some(Box::new(map)))?;
let map = Self::load_data_from_db(world, sql_connection, tiles, entity_manager)?;
world.resources.insert(map);
Ok(())
}
@ -110,13 +97,7 @@ impl Map {
self.async_db.dispatch()
}
pub fn add_height(
&self,
x: u32,
y: u32,
height: f32,
scene: &mut impl SceneEntities,
) -> Result<()> {
pub fn add_height(&self, x: u32, y: u32, height: f32, world: &mut World) -> Result<()> {
let mut data = self.data.write().unwrap();
data.add_height(x, y, height, &self.async_db)?;
@ -135,7 +116,7 @@ impl Map {
Self::calculate_chunk_and_primitive_indices(dx, dy);
let chunk_entity = data.chunk_handles[&chunk];
let entity = scene.entity_mut(chunk_entity)?;
let entity = world.entity_mut(chunk_entity)?;
let draw = entity.get_component_mut::<Draw>()?;
let mesh = &mut draw[0];
@ -195,8 +176,8 @@ impl Map {
self.updated.store(true, SeqCst);
}
pub fn check_height_at(&self, x: u32, y: u32, scene: &mut Scene) -> Result<()> {
self.data.write().unwrap().check_height_at(x, y, scene)
pub fn check_height_at(&self, x: u32, y: u32, world: &mut World) -> Result<()> {
self.data.write().unwrap().check_height_at(x, y, world)
}
pub fn npc_spawn_info(&self, position: Vector2<f32>) -> Result<MapNPCSpawnInfo> {
@ -251,13 +232,13 @@ impl Map {
pub fn change_npc_spawn(
&self,
scene_contents: &mut impl SceneEntities,
world: &mut World,
position: Vector2<f32>,
npc_spawn_parameter: NPCSpawnParameter,
) -> Result<()> {
self.data.write().unwrap().change_npc_spawn(
&self.async_db,
scene_contents,
world,
position,
npc_spawn_parameter,
)?;
@ -279,7 +260,7 @@ impl Map {
x: u32,
y: u32,
texture: &Arc<Image>,
scene: &mut impl SceneEntities,
world: &mut World,
) -> Result<()> {
let mut data = self.data.write().unwrap();
@ -289,7 +270,7 @@ impl Map {
let (chunk, primitive_index) = Self::calculate_chunk_and_primitive_indices(x, y);
let chunk_entity = data.chunk_handles[&chunk];
let entity = scene.entity_mut(chunk_entity)?;
let entity = world.entity_mut(chunk_entity)?;
let draw = entity.get_component_mut::<Draw>()?;
let mesh = &mut draw[0];
@ -318,7 +299,7 @@ impl Map {
pub fn set_entity(
&self,
scene: &mut impl SceneEntities,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: Vector2<f32>,
rotation: impl Into<Rad<f32>>,
@ -326,18 +307,18 @@ impl Map {
self.data
.write()
.unwrap()
.set_entity(scene, entity_opt, position, rotation, &self.async_db)
.set_entity(world, entity_opt, position, rotation, &self.async_db)
}
pub fn set_spawn_location(
&self,
scene: &mut impl SceneEntities,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: Vector2<f32>,
rotation: impl Into<Rad<f32>>,
) -> Result<()> {
self.data.write().unwrap().set_spawn_location(
scene,
world,
entity_opt,
position,
rotation,
@ -345,19 +326,19 @@ impl Map {
)
}
pub fn disable_spawns(&self, scene: &mut impl SceneEntities) -> Result<()> {
self.data.write().unwrap().disable_spawns(scene)
pub fn disable_spawns(&self, world: &mut World) -> Result<()> {
self.data.write().unwrap().disable_spawns(world)
}
pub fn set_leave_location(
&self,
scene: &mut impl SceneEntities,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: Vector2<f32>,
rotation: impl Into<Rad<f32>>,
) -> Result<()> {
self.data.write().unwrap().set_leave_location(
scene,
world,
entity_opt,
position,
rotation,
@ -365,19 +346,19 @@ impl Map {
)
}
pub fn toggle_npc_spawns(&self, engine: &Engine, scene: &mut impl SceneEntities) -> Result<()> {
self.data.write().unwrap().toggle_npc_spawns(engine, scene)
pub fn toggle_npc_spawns(&self, world: &mut World) -> Result<()> {
self.data.write().unwrap().toggle_npc_spawns(world)
}
pub fn set_npc_spawn_marker(
&self,
flag_name: &str,
area_name: &str,
engine: &Engine,
world: &mut World,
entity_manager: &mut impl AssetLoader,
) -> Result<()> {
self.data.write().unwrap().npc_spawn_marker = Some(SpawnMarkerEntities::new(
engine,
world,
entity_manager,
flag_name,
area_name,
@ -388,56 +369,38 @@ impl Map {
pub fn set_npc_spawn(
&self,
engine: &Engine,
scene: &mut impl SceneEntities,
world: &mut World,
position: Vector2<f32>,
radius: f32,
min: u32,
max: u32,
) -> Result<()> {
self.data.write().unwrap().set_npc_spawn(
engine,
scene,
position,
&self.async_db,
radius,
min,
max,
)
}
pub fn unset_npc_spawn(
&self,
scene: &mut impl SceneEntities,
position: Vector2<f32>,
) -> Result<()> {
self.data
.write()
.unwrap()
.unset_npc_spawn(scene, position, &self.async_db)
.set_npc_spawn(world, position, &self.async_db, radius, min, max)
}
pub fn set_boss_spawn(
&self,
engine: &Engine,
scene: &mut impl SceneEntities,
position: Vector2<f32>,
) -> Result<()> {
pub fn unset_npc_spawn(&self, world: &mut World, position: Vector2<f32>) -> Result<()> {
self.data
.write()
.unwrap()
.set_boss_spawn(engine, scene, position, &self.async_db)
.unset_npc_spawn(world, position, &self.async_db)
}
pub fn unset_boss_spawn(
&self,
scene: &mut impl SceneEntities,
position: Vector2<f32>,
) -> Result<()> {
pub fn set_boss_spawn(&self, world: &mut World, position: Vector2<f32>) -> Result<()> {
self.data
.write()
.unwrap()
.unset_boss_spawn(scene, position, &self.async_db)
.set_boss_spawn(world, position, &self.async_db)
}
pub fn unset_boss_spawn(&self, world: &mut World, position: Vector2<f32>) -> Result<()> {
self.data
.write()
.unwrap()
.unset_boss_spawn(world, position, &self.async_db)
}
#[inline]
@ -489,8 +452,7 @@ impl Map {
}
fn create_tiles(
engine: &Engine,
scene: &mut Scene,
world: &mut World,
textures: &HashMap<u32, OrderedElement<Arc<Image>>>,
width: u32,
height: u32,
@ -498,6 +460,9 @@ impl Map {
) -> Result<HashMap<(u32, u32), Entity>> {
let tile_handles = Map::create_tile_handles(width, height, &surface);
let context = world.resources.get_unchecked::<Context>();
let scene = world.resources.get_unchecked::<Scene>();
#[cfg(debug_assertions)]
Self::verify_tile_handles(width, height, &tile_handles);
@ -517,7 +482,7 @@ impl Map {
let mut tile_entity = {
let (tiles, bounding_box) = &tile_handles[&(chunk_x, chunk_y)];
let mut asset_mesh = AssetMesh::new(&scene.device(), scene.render_type())?;
let mut asset_mesh = AssetMesh::new(context.device(), scene.render_type())?;
for tile_x in 0..Self::CHUNK_DIMENSION {
for tile_y in 0..Self::CHUNK_DIMENSION {
@ -533,7 +498,7 @@ impl Map {
)
.set_memory_usage(MemoryUsage::CpuOnly)
.set_data(&buffer_data)
.build(engine.device().clone())?;
.build(context.device().clone())?;
asset_mesh.add_primitive(
buffer,
@ -545,7 +510,7 @@ impl Map {
}
}
let mut entity_object = engine.assets().empty_entity();
let mut entity_object = AssetHandler::create(world).empty_entity();
entity_object.insert_component(Draw::new(vec![asset_mesh]));
entity_object.insert_component(bounding_box.clone());
@ -558,7 +523,7 @@ impl Map {
tile_entity.debug_name = Some(format!("Chunk ({}, {})", chunk_x, chunk_y));
}
let entity = scene.add_entity(tile_entity)?;
let entity = world.add_entity(tile_entity)?;
tiles.insert((chunk_x, chunk_y), entity);
}
@ -662,8 +627,7 @@ impl Map {
// DB load and create map handle
impl Map {
fn load_data_from_db(
engine: &Engine,
scene: &mut Scene,
world: &mut World,
data_base: MapDataBase,
tiles: &HashMap<String, AssetPath>,
entity_manager: &mut impl AssetLoader,
@ -677,6 +641,8 @@ impl Map {
let mob_spawn_locations = data_base.read_mob_spawn_locations()?;
let boss_spawn_locations = data_base.read_boss_spawn_locations()?;
let context = world.resources.get_unchecked::<Context>();
// check if the amount of tiles and points is correct
let tile_count = data_base.width() * data_base.height();
let point_count = (data_base.width() + 1) * (data_base.height() + 1);
@ -713,7 +679,7 @@ impl Map {
return Err(anyhow::Error::msg(format!(
"Map: Could not find index ({}) in height map",
index
)))
)));
}
};
@ -733,13 +699,13 @@ impl Map {
let image = match tiles.get(&texture.name) {
Some(path) => Image::from_file(path.clone())?
.format(VK_FORMAT_R8G8B8A8_UNORM)
.attach_sampler(Sampler::nearest_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
.attach_sampler(Sampler::nearest_sampler().build(context.device())?)
.build(context.device(), context.queue())?,
None => {
return Err(anyhow::Error::msg(format!(
"Map: Image ({}) could not be found",
texture.name
)))
)));
}
};
@ -769,7 +735,7 @@ impl Map {
return Err(anyhow::Error::msg(format!(
"Map: Index ({}) of tiles map could not be found",
i
)))
)));
}
};
@ -779,7 +745,7 @@ impl Map {
return Err(anyhow::Error::msg(format!(
"Map: Texture with id ({}) of textures_info could not be found",
texture_id
)))
)));
}
}
}
@ -791,34 +757,39 @@ impl Map {
map_tiles,
)?;
let asset_manager = engine.assets();
let mut asset_manager = AssetHandler::create(unsafe { remove_life_time_mut(world) });
let entity_positions =
Self::create_entity_alike(asset_manager, entity_manager, entity_pos, &surface, scene)?;
let entity_positions = Self::create_entity_alike(
&mut asset_manager,
entity_manager,
entity_pos,
&surface,
world,
)?;
let spawn_positions = Self::create_entity_alike(
asset_manager,
&mut asset_manager,
entity_manager,
spawn_locations,
&surface,
scene,
world,
)?;
let mut spawns = HashMap::new();
for (coordinate, entity) in spawn_positions.into_iter() {
let e = scene.entity(entity)?;
let e = world.entity(entity)?;
let position = e.get_component::<Location>()?.position();
spawns.insert(coordinate, (entity, position));
}
let leave_positions = Self::create_entity_alike(
asset_manager,
&mut asset_manager,
entity_manager,
leave_locations,
&surface,
scene,
world,
)?;
let npc_spawn_positions = mob_spawn_locations
@ -882,8 +853,7 @@ impl Map {
data: RwLock::new(MapData {
chunk_handles: Self::create_tiles(
engine,
scene,
world,
&textures_info,
data_base.width(),
data_base.height(),
@ -925,11 +895,11 @@ impl Map {
#[inline]
fn create_entity_alike(
asset_manager: AssetHandler<'_>,
asset_manager: &mut AssetHandler<'_>,
entity_manager: &mut impl AssetLoader,
source: Vec<DBEntityInfo>,
surface: &Surface,
scene: &mut Scene,
world: &mut World,
) -> Result<HashMap<Coordinate, Entity>> {
let mut map = HashMap::new();
@ -949,7 +919,7 @@ impl Map {
Location::new_and_setup(&mut entity_object)?.update_raw(position, info.rotation);
let entity = scene.add_entity(entity_object)?;
let entity = world.add_entity(entity_object)?;
map.insert(
Coordinate {
@ -962,14 +932,12 @@ impl Map {
Ok(map)
}
}
impl engine::prelude::Map for Map {
fn name(&self) -> &str {
pub fn name(&self) -> &str {
&self.name
}
fn check_walkability(
pub fn check_walkability(
&self,
position: Vector2<f32>,
direction: Vector2<f32>,
@ -1016,45 +984,45 @@ impl engine::prelude::Map for Map {
Ok(true)
}
fn get_height(&self, x: f32, y: f32) -> Result<f32> {
pub fn get_height(&self, x: f32, y: f32) -> Result<f32> {
Ok(self.data.read().unwrap().get_height(x, y))
}
fn spawn_positions(&self) -> Result<Vec<Vector3<f32>>> {
pub fn spawn_positions(&self) -> Result<Vec<Vector3<f32>>> {
Ok(self.data.read().unwrap().spawn_locations())
}
fn leave_markers(&self) -> Result<Vec<Entity>> {
pub fn leave_markers(&self) -> Result<Vec<Entity>> {
Ok(self.data.read().unwrap().leave_markers())
}
fn disable(&self, scene: &mut Scene) -> Result<()> {
pub fn disable(&self, world: &mut World) -> Result<()> {
let data = self.data.read().unwrap();
// clear tiles
for chunk in data.chunk_handles.values() {
scene.remove_entity(*chunk)?;
world.remove_entity(*chunk)?;
}
// clear entities
for entity in data.entities.values() {
scene.remove_entity(*entity)?;
world.remove_entity(*entity)?;
}
// clear spawns
for (spawn, _) in data.spawn_locations.values() {
scene.remove_entity(*spawn)?;
world.remove_entity(*spawn)?;
}
// clear exits
for leave in data.leave_locations.values() {
scene.remove_entity(*leave)?;
world.remove_entity(*leave)?;
}
// clear npc spawns
for (_, marker) in data.npc_spawn_areas.iter() {
if let Some(marker) = marker {
marker.remove(scene)?;
marker.remove(world)?;
}
}

View file

@ -4,7 +4,7 @@ use engine::prelude::*;
use super::{async_db::AsyncDBAccess, map::MapObjectType, map_db::EntityDBType, surface::Surface};
use anyhow::Result;
use cgmath::{vec3, Rad};
use cgmath::{Rad, vec3};
use cgmath::{Vector2, Vector3};
// std
@ -53,14 +53,14 @@ pub struct SpawnMarkerEntities {
impl SpawnMarkerEntities {
pub fn new(
engine: &Engine,
world: &mut World,
entity_manager: &mut impl AssetLoader,
flag_name: &str,
area_name: &str,
) -> Result<Self> {
let assets = engine.assets();
let mut assets = AssetHandler::create(world);
let mut flag = entity_manager.load_entity(assets, flag_name)?;
let mut flag = entity_manager.load_entity(&mut assets, flag_name)?;
#[cfg(debug_assertions)]
{
@ -69,7 +69,7 @@ impl SpawnMarkerEntities {
Location::new_and_setup(&mut flag)?;
let mut area = entity_manager.load_entity(assets, area_name)?;
let mut area = entity_manager.load_entity(&mut assets, area_name)?;
#[cfg(debug_assertions)]
{
@ -81,8 +81,8 @@ impl SpawnMarkerEntities {
Ok(Self { flag, area })
}
fn clone_entity(entity: &EntityObject, engine: &Engine) -> Result<EntityObject> {
let mut flag = entity.clone_without_components(engine);
fn clone_entity(entity: &EntityObject, world: &mut World) -> Result<EntityObject> {
let mut flag = world.clone_without_components(entity);
flag.clone_component_from::<Draw>(entity)?;
flag.clone_component_from::<Location>(entity)?;
flag.clone_component_from::<BoundingBox>(entity)?;
@ -92,9 +92,9 @@ impl SpawnMarkerEntities {
Ok(flag)
}
fn clone(&self, engine: &Engine) -> Result<Self> {
let flag = Self::clone_entity(&self.flag, engine)?;
let area = Self::clone_entity(&self.area, engine)?;
fn clone(&self, world: &mut World) -> Result<Self> {
let flag = Self::clone_entity(&self.flag, world)?;
let area = Self::clone_entity(&self.area, world)?;
Ok(Self { flag, area })
}
@ -118,9 +118,9 @@ impl SpawnMarkerEntities {
.set_scale(vec3(radius, radius, 1.0));
}
pub fn add(self, scene: &mut impl SceneEntities) -> Result<SpawnMarker> {
let flag = scene.add_entity(self.flag)?;
let area = scene.add_entity(self.area)?;
pub fn add(self, world: &mut World) -> Result<SpawnMarker> {
let flag = world.add_entity(self.flag)?;
let area = world.add_entity(self.area)?;
Ok(SpawnMarker { flag, area })
}
@ -132,8 +132,8 @@ pub struct SpawnMarker {
}
impl SpawnMarker {
fn set_radius(&self, scene_contents: &mut impl SceneEntities, radius: f32) -> Result<()> {
let flag_object = scene_contents.entity_mut(self.area)?;
fn set_radius(&self, world: &mut World, radius: f32) -> Result<()> {
let flag_object = world.entity_mut(self.area)?;
flag_object
.get_component_mut::<Location>()?
@ -193,9 +193,9 @@ impl AlterEntities for HashMap<Coordinate, (Entity, Vector3<f32>)> {
}
impl SpawnMarker {
pub fn remove(&self, scene: &mut impl SceneEntities) -> Result<()> {
scene.remove_entity(self.flag)?;
scene.remove_entity(self.area)?;
pub fn remove(&self, world: &mut World) -> Result<()> {
world.remove_entity(self.flag)?;
world.remove_entity(self.area)?;
Ok(())
}
@ -222,10 +222,10 @@ pub struct MapBossSpawnInfo {
impl MapBossSpawnInfo {
fn setup_flag(
marker: &SpawnMarkerEntities,
engine: &Engine,
world: &mut World,
position: Vector3<f32>,
) -> Result<EntityObject> {
let mut flag = SpawnMarkerEntities::clone_entity(&marker.flag, engine)?;
let mut flag = SpawnMarkerEntities::clone_entity(&marker.flag, world)?;
let location = flag.get_component_mut::<Location>()?;
let scale = location.scale();
@ -472,9 +472,9 @@ impl MapData {
Ok(())
}
pub fn check_height_at(&mut self, x: u32, y: u32, scene: &mut Scene) -> Result<()> {
pub fn check_height_at(&mut self, x: u32, y: u32, world: &mut World) -> Result<()> {
self.execute_on_entity(x, y, |entity| {
let entity_object = scene.entity_mut(entity)?;
let entity_object = world.entity_mut(entity)?;
let location = entity_object.get_component_mut::<Location>()?;
@ -493,7 +493,7 @@ impl MapData {
pub fn set_spawn_location(
&mut self,
scene: &mut impl SceneEntities,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
@ -508,7 +508,7 @@ impl MapData {
(self.width, self.height),
&self.surface,
EntityDBType::spawn_location(),
scene,
world,
)?;
Ok(())
@ -539,7 +539,7 @@ impl MapData {
pub fn change_npc_spawn(
&mut self,
async_db: &AsyncDBAccess,
scene_contents: &mut impl SceneEntities,
world: &mut World,
position: Vector2<f32>,
npc_spawn_parameter: NPCSpawnParameter,
) -> Result<()> {
@ -580,10 +580,7 @@ impl MapData {
NPCSpawnParameter::Radius(radius) => {
spawn_info.radius = radius;
marker
.as_ref()
.unwrap()
.set_radius(scene_contents, radius)?;
marker.as_ref().unwrap().set_radius(world, radius)?;
async_db.add(move |sql| sql.update_npc_spawn_radius(&coordinate, radius))?;
}
@ -593,12 +590,12 @@ impl MapData {
Ok(())
}
pub fn disable_spawns(&mut self, scene: &mut impl SceneEntities) -> Result<()> {
pub fn disable_spawns(&mut self, world: &mut World) -> Result<()> {
if self.show_spawn_locations {
self.show_spawn_locations = false;
for (spawn_entity, _) in self.spawn_locations.values() {
scene.remove_entity(*spawn_entity)?;
world.remove_entity(*spawn_entity)?;
}
}
@ -621,7 +618,7 @@ impl MapData {
pub fn set_leave_location(
&mut self,
scene: &mut impl SceneEntities,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
@ -636,15 +633,11 @@ impl MapData {
(self.width, self.height),
&self.surface,
EntityDBType::leave_location(),
scene,
world,
)
}
pub fn toggle_npc_spawns(
&mut self,
engine: &Engine,
scene: &mut impl SceneEntities,
) -> Result<()> {
pub fn toggle_npc_spawns(&mut self, world: &mut World) -> Result<()> {
self.show_npc_spawns = !self.show_npc_spawns;
for (spawn_info, marker) in self.npc_spawn_areas.iter_mut() {
@ -660,13 +653,13 @@ impl MapData {
Some(marker) => Some(marker),
None => match &self.npc_spawn_marker {
Some(global_marker) => {
let mut new_marker = global_marker.clone(engine)?;
let mut new_marker = global_marker.clone(world)?;
// set position
new_marker.set_position(spawn_info.position.extend(height));
new_marker.set_radius(spawn_info.radius);
*marker = Some(new_marker.add(scene)?);
*marker = Some(new_marker.add(world)?);
marker.as_mut()
}
@ -680,7 +673,7 @@ impl MapData {
if self.show_npc_spawns {
// marker.add(scene)?;
} else {
marker.remove(scene)?;
marker.remove(world)?;
}
}
}
@ -700,11 +693,11 @@ impl MapData {
Some(global_marker) => {
let flag = MapBossSpawnInfo::setup_flag(
global_marker,
engine,
world,
spawn_info.position.extend(height),
)?;
*flag_marker = Some(scene.add_entity(flag)?);
*flag_marker = Some(world.add_entity(flag)?);
*flag_marker
}
@ -718,7 +711,7 @@ impl MapData {
if self.show_npc_spawns {
// marker.add(scene)?;
} else {
scene.remove_entity(marker)?;
world.remove_entity(marker)?;
}
}
}
@ -728,8 +721,7 @@ impl MapData {
pub fn set_npc_spawn(
&mut self,
engine: &Engine,
scene: &mut impl SceneEntities,
world: &mut World,
position: cgmath::Vector2<f32>,
async_db: &AsyncDBAccess,
radius: f32,
@ -749,13 +741,13 @@ impl MapData {
let marker = match self.npc_spawn_marker.as_ref() {
Some(managed_entity) => {
let mut marker = managed_entity.clone(engine)?;
let mut marker = managed_entity.clone(world)?;
// set position
marker.set_position(position.extend(self.get_height(position.x, position.y)));
marker.set_radius(radius);
Some(marker.add(scene)?)
Some(marker.add(world)?)
}
None => None,
};
@ -781,7 +773,7 @@ impl MapData {
pub fn unset_npc_spawn(
&mut self,
scene: &mut impl SceneEntities,
world: &mut World,
position: cgmath::Vector2<f32>,
async_db: &AsyncDBAccess,
) -> Result<()> {
@ -797,7 +789,7 @@ impl MapData {
async_db.add(move |sql| sql.remove_npc_spawn((coordinate.x, coordinate.y)))?;
if let Some(marker) = marker {
marker.remove(scene)?;
marker.remove(world)?;
}
}
@ -806,8 +798,7 @@ impl MapData {
pub fn set_boss_spawn(
&mut self,
engine: &Engine,
scene: &mut impl SceneEntities,
world: &mut World,
position: cgmath::Vector2<f32>,
async_db: &AsyncDBAccess,
) -> Result<()> {
@ -826,11 +817,11 @@ impl MapData {
Some(managed_entity) => {
let flag = MapBossSpawnInfo::setup_flag(
managed_entity,
engine,
world,
position.extend(self.get_height(position.x, position.y)),
)?;
Some(scene.add_entity(flag)?)
Some(world.add_entity(flag)?)
}
None => None,
};
@ -851,7 +842,7 @@ impl MapData {
pub fn unset_boss_spawn(
&mut self,
scene: &mut impl SceneEntities,
world: &mut World,
position: cgmath::Vector2<f32>,
async_db: &AsyncDBAccess,
) -> Result<()> {
@ -867,7 +858,7 @@ impl MapData {
async_db.add(move |sql| sql.remove_boss_spawn((coordinate.x, coordinate.y)))?;
if let Some(marker) = marker {
scene.remove_entity(marker)?;
world.remove_entity(marker)?;
}
}
@ -883,7 +874,7 @@ impl MapData {
(width, height): (u32, u32),
surface: &Surface,
table_name: EntityDBType,
scene: &mut impl SceneEntities,
world: &mut World,
) -> Result<()> {
let coordinate = Coordinate {
x: position.x as u32,
@ -927,7 +918,7 @@ impl MapData {
.unwrap()
.update_raw(position_3d, rad_rotation);
let handle = scene.add_entity(entity)?;
let handle = world.add_entity(entity)?;
entities.insert(coordinate, handle, position_3d);
}
@ -938,7 +929,7 @@ impl MapData {
sql.remove_entity(table_name, (coordinate.x, coordinate.y))
})?;
scene.remove_entity(entity)?;
world.remove_entity(entity)?;
return Ok(());
}
@ -950,7 +941,7 @@ impl MapData {
pub fn set_entity(
&mut self,
scene: &mut impl SceneEntities,
world: &mut World,
entity_opt: Option<(String, EntityObject)>,
position: cgmath::Vector2<f32>,
rotation: impl Into<Rad<f32>>,
@ -965,7 +956,7 @@ impl MapData {
(self.width, self.height),
&self.surface,
EntityDBType::entity(),
scene,
world,
)
}
@ -1122,7 +1113,7 @@ impl MapData {
return Err(anyhow::Error::msg(format!(
"Index ({}) in textures_info (MapData) not found",
remove_index
)))
)));
}
};
@ -1132,7 +1123,7 @@ impl MapData {
return Err(anyhow::Error::msg(format!(
"Could not remove key ({}) from textures_index",
info.name
)))
)));
}
};

View file

@ -91,7 +91,7 @@ impl CharacterStatus {
}
impl EntityComponent for CharacterStatus {
fn enable(&mut self, _scene: &mut Scene) -> Result<()> {
fn enable(&mut self, _world: &mut World) -> Result<()> {
self.last_tick = None;
Ok(())

View file

@ -7,7 +7,7 @@ use crate::{
crafting_materials::CraftingMaterials, inventory::Inventory, item_slots::ItemSlotContainer,
level::Level, statistics::Statistics,
},
items::{ability_book::Ability, ItemAffix, ItemSystem, Rarities},
items::{ItemAffix, ItemSystem, Rarities, ability_book::Ability},
};
use std::env::var;
@ -361,21 +361,19 @@ create_settings_container!(
impl SaveGame {
pub fn to_entity_object<A: Ability + 'static>(
self,
engine: &Engine,
world: &mut World,
) -> Result<(Entity, String)> {
let scene = engine.scene_mut();
let experience_settings = scene.resources.get::<ExperienceSettings>();
let attribute_settings = scene.resources.get::<AttributeSettings>();
let item_settings = scene.resources.get::<ItemSettings>();
let item_system = scene.resources.get::<ItemSystem<A>>();
let mut entity_object = engine.assets().empty_entity();
let mut entity_object = AssetHandler::create(world).empty_entity();
entity_object.insert_component(Draw::new(Vec::new()));
entity_object.insert_component(Audio::new(engine.context(), None)?);
entity_object.insert_component(Audio::new(world.resources.get_mut::<Context>(), None)?);
Location::new_and_setup(&mut entity_object)?;
let experience_settings = world.resources.get::<ExperienceSettings>();
let attribute_settings = world.resources.get::<AttributeSettings>();
let item_settings = world.resources.get::<ItemSettings>();
let item_system = world.resources.get::<ItemSystem<A>>();
let level = Level::load(self.general.level, self.general.exp, experience_settings);
let mut attributes = Attributes::load(
self.general.strength,
@ -399,6 +397,6 @@ impl SaveGame {
entity_object.insert_component(statistics);
entity_object.insert_component(current_status);
Ok((scene.add_entity(entity_object)?, self.general.name))
Ok((world.add_entity(entity_object)?, self.general.name))
}
}

View file

@ -15,12 +15,12 @@ use crate::{
};
use super::{
ability_addon::{AbilityAddon, AbilityAddonCollection, AbilityAddonTypes},
ItemSystem, Rarities, Tooltip,
ability_addon::{AbilityAddon, AbilityAddonCollection, AbilityAddonTypes},
};
pub trait Ability: Send + Sync + Clone + Default {
fn create(engine: &Engine, asset_path: impl Into<AssetPath>) -> Result<Self>;
fn create(context: &Context, asset_path: impl Into<AssetPath>) -> Result<Self>;
fn name(&self) -> &str;
fn icon_path(&self) -> &AssetPath;

View file

@ -5,7 +5,7 @@ use engine::prelude::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use image::{imageops::FilterType, DynamicImage, ImageBuffer, Pixel, Rgba, RgbaImage};
use image::{DynamicImage, ImageBuffer, Pixel, Rgba, RgbaImage, imageops::FilterType};
use crate::components::attributes::{Attribute, Attributes};
use crate::components::inventory::Storable;
@ -53,7 +53,7 @@ pub struct ItemSystem<A: Ability> {
impl<A: Ability> ItemSystem<A> {
pub fn new(
engine: &Arc<Engine>,
context: &Context,
item_settings: &ItemSettings,
ability_settings: &AbilitySettings,
attribute_settings: &AttributeSettings,
@ -64,7 +64,7 @@ impl<A: Ability> ItemSystem<A> {
let abilities = search_dir_recursively(&ability_directory.full_path(), ".abil")?
.into_iter()
.map(|path| A::create(engine, path))
.map(|path| A::create(context, path))
.collect::<Result<Vec<A>>>()?;
let (
@ -149,8 +149,8 @@ impl<A: Ability> ItemSystem<A> {
item_icon_combinations.insert(
(*rarity, *slot),
Self::create_icon(
engine.device(),
engine.queue(),
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
@ -177,8 +177,8 @@ impl<A: Ability> ItemSystem<A> {
ability_icon_combinations.insert(
(*rarity, name.clone()),
Self::create_icon(
engine.device(),
engine.queue(),
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
@ -215,8 +215,8 @@ impl<A: Ability> ItemSystem<A> {
addon_icon_combinations.insert(
(*rarity, *addon_type),
Self::create_icon(
engine.device(),
engine.queue(),
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
@ -247,8 +247,8 @@ impl<A: Ability> ItemSystem<A> {
jewel_icon_combinations.insert(
(*rarity, *level + 1, *attribute),
Self::create_icon(
engine.device(),
engine.queue(),
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),