engine/gavania-core/src/game/content/objects/hero.rs
2024-08-26 15:44:19 +02:00

498 lines
16 KiB
Rust

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