2024-08-23 11:22:09 +00:00
|
|
|
#![allow(unused)]
|
|
|
|
|
|
|
|
use assetpath::AssetPath;
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use engine::prelude::*;
|
2025-02-28 09:22:25 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-08-23 11:22:09 +00:00
|
|
|
|
2024-08-25 07:11:52 +00:00
|
|
|
use crate::animation_info::AnimationType;
|
|
|
|
|
|
|
|
use super::entity_tags::EntityTags;
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::{fmt::Display, str::FromStr, time::Duration};
|
|
|
|
|
|
|
|
// parse state keys
|
|
|
|
const META_KEY: &str = "Meta";
|
|
|
|
const ANIMATION_KEY: &str = "Animation";
|
|
|
|
const AUDIO_KEY: &str = "Sounds";
|
|
|
|
|
|
|
|
// meta information keys
|
|
|
|
const GLTF_FILE_NAME: &str = "Object";
|
|
|
|
const ENTITY_TAGS: &str = "Tags";
|
|
|
|
const HITBOX: &str = "HitBox";
|
|
|
|
const HITBOX_RADIUS: &str = "HitboxRadius";
|
|
|
|
const HITBOX_HEIGHT: &str = "HitboxHeight";
|
|
|
|
|
|
|
|
// animation information keys
|
|
|
|
const ANIMATION_DATA_KEY: &str = "Info";
|
|
|
|
const ANIMATION_MOVE_KEY: &str = "Move";
|
|
|
|
const ANIMATION_IDLE_KEY: &str = "Idle";
|
|
|
|
const ANIMATION_CAST_KEY: &str = "Cast";
|
|
|
|
const ANIMATION_ATTACK_KEY: &str = "Attack";
|
|
|
|
|
|
|
|
// sound information keys
|
|
|
|
pub const SOUND_MOVE_KEY: &str = "Move";
|
|
|
|
pub const SOUND_CREATE_KEY: &str = ON_ENABLE_SOUND;
|
|
|
|
pub const SOUND_DESTROY_KEY: &str = ON_DISABLE_SOUND;
|
|
|
|
// TODO: more
|
|
|
|
|
|
|
|
pub fn split_matches(s: &str, c: char) -> (&str, &str) {
|
|
|
|
match s.find(c) {
|
|
|
|
Some(pos) => {
|
|
|
|
let (f, l) = s.split_at(pos);
|
|
|
|
let l = l.trim_start_matches(c);
|
|
|
|
|
|
|
|
(f, l)
|
|
|
|
}
|
|
|
|
None => (s, ""),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Serialize, Deserialize)]
|
|
|
|
pub struct AnimationData {
|
|
|
|
pub index: u32,
|
|
|
|
pub name: String,
|
|
|
|
pub duration: Duration,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for AnimationData {
|
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
let (index, rest) = split_matches(s, ';');
|
|
|
|
let (name, duration) = split_matches(rest, ';');
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
index: index.parse()?,
|
|
|
|
name: name.to_string(),
|
|
|
|
duration: PersistentDuration::from_str(duration)?.into(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for AnimationData {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{};{};{}",
|
|
|
|
self.index,
|
|
|
|
self.name,
|
|
|
|
PersistentDuration::from(self.duration)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct EntityParser {
|
|
|
|
pub enable_hitbox: bool,
|
|
|
|
pub hitbox_radius: f32,
|
|
|
|
pub hitbox_height: f32,
|
|
|
|
pub gltf_file_name: String,
|
|
|
|
pub tags: Vec<EntityTags>,
|
|
|
|
|
|
|
|
pub animations: Vec<AnimationData>,
|
|
|
|
|
|
|
|
pub animation_map: HashMap<AnimationType, Vec<u32>>,
|
|
|
|
|
|
|
|
pub sound_map: HashMap<String, AssetPath>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EntityParser {
|
|
|
|
pub fn parse(file: &AssetPath) -> Result<EntityParser> {
|
|
|
|
let mut entity_parser = EntityParser::default();
|
|
|
|
|
|
|
|
let parsed_entity = ConfigHandler::read_config(&file.full_path())?;
|
|
|
|
|
|
|
|
if let Some(info) = parsed_entity.get(META_KEY) {
|
|
|
|
if let Some(key_value) = info.get(GLTF_FILE_NAME) {
|
|
|
|
entity_parser.gltf_file_name = key_value.to_value()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(HITBOX) {
|
|
|
|
entity_parser.enable_hitbox = key_value.to_value()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(HITBOX_RADIUS) {
|
|
|
|
entity_parser.hitbox_radius = key_value.to_value()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(HITBOX_HEIGHT) {
|
|
|
|
entity_parser.hitbox_height = key_value.to_value()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(ENTITY_TAGS) {
|
|
|
|
let tag_strings: Vec<String> = key_value.to_array()?;
|
|
|
|
|
|
|
|
for tag_string in tag_strings {
|
|
|
|
match tag_string.as_str() {
|
|
|
|
"Ability" => {
|
|
|
|
if !entity_parser.tags.contains(&EntityTags::Ability) {
|
|
|
|
entity_parser.tags.push(EntityTags::Ability);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"WorldObject" => {
|
|
|
|
if !entity_parser.tags.contains(&EntityTags::WorldObject) {
|
|
|
|
entity_parser.tags.push(EntityTags::WorldObject);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"Creature" => {
|
|
|
|
if !entity_parser.tags.contains(&EntityTags::Creature) {
|
|
|
|
entity_parser.tags.push(EntityTags::Creature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(info) = parsed_entity.get(ANIMATION_KEY) {
|
|
|
|
if let Some(key_value) = info.get(ANIMATION_DATA_KEY) {
|
|
|
|
entity_parser.animations = key_value.to_array()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(ANIMATION_MOVE_KEY) {
|
|
|
|
Self::insert_animation(
|
|
|
|
key_value,
|
|
|
|
&mut entity_parser.animation_map,
|
|
|
|
AnimationType::Move,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(ANIMATION_IDLE_KEY) {
|
|
|
|
Self::insert_animation(
|
|
|
|
key_value,
|
|
|
|
&mut entity_parser.animation_map,
|
|
|
|
AnimationType::Idle,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(ANIMATION_ATTACK_KEY) {
|
|
|
|
Self::insert_animation(
|
|
|
|
key_value,
|
|
|
|
&mut entity_parser.animation_map,
|
|
|
|
AnimationType::Attack,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(ANIMATION_CAST_KEY) {
|
|
|
|
Self::insert_animation(
|
|
|
|
key_value,
|
|
|
|
&mut entity_parser.animation_map,
|
|
|
|
AnimationType::Cast,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(info) = parsed_entity.get(AUDIO_KEY) {
|
|
|
|
if let Some(key_value) = info.get(SOUND_MOVE_KEY) {
|
|
|
|
Self::insert_sound(key_value, &mut entity_parser.sound_map, SOUND_MOVE_KEY)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(SOUND_CREATE_KEY) {
|
|
|
|
Self::insert_sound(key_value, &mut entity_parser.sound_map, SOUND_CREATE_KEY)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(key_value) = info.get(SOUND_DESTROY_KEY) {
|
|
|
|
Self::insert_sound(key_value, &mut entity_parser.sound_map, SOUND_DESTROY_KEY)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(entity_parser)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save(&self, file: &str) -> Result<()> {
|
|
|
|
let data = vec![
|
|
|
|
(
|
|
|
|
META_KEY,
|
|
|
|
vec![
|
|
|
|
(GLTF_FILE_NAME, Value::from(&self.gltf_file_name)),
|
|
|
|
(ENTITY_TAGS, Value::from(self.tags.as_slice())),
|
|
|
|
(HITBOX, Value::from(&self.enable_hitbox)),
|
|
|
|
(HITBOX_RADIUS, Value::from(&self.hitbox_radius)),
|
|
|
|
(HITBOX_HEIGHT, Value::from(&self.hitbox_height)),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
ANIMATION_KEY,
|
|
|
|
vec![
|
|
|
|
(ANIMATION_DATA_KEY, Value::from(self.animations.as_slice())),
|
|
|
|
(
|
|
|
|
ANIMATION_MOVE_KEY,
|
|
|
|
match self.animation_map.get(&AnimationType::Move) {
|
|
|
|
Some(indices) => Value::from(indices.as_slice()),
|
|
|
|
None => Value::empty_array(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
ANIMATION_CAST_KEY,
|
|
|
|
match self.animation_map.get(&AnimationType::Cast) {
|
|
|
|
Some(indices) => Value::from(indices.as_slice()),
|
|
|
|
None => Value::empty_array(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
ANIMATION_IDLE_KEY,
|
|
|
|
match self.animation_map.get(&AnimationType::Idle) {
|
|
|
|
Some(indices) => Value::from(indices.as_slice()),
|
|
|
|
None => Value::empty_array(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
ANIMATION_ATTACK_KEY,
|
|
|
|
match self.animation_map.get(&AnimationType::Attack) {
|
|
|
|
Some(indices) => Value::from(indices.as_slice()),
|
|
|
|
None => Value::empty_array(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
AUDIO_KEY,
|
|
|
|
vec![
|
|
|
|
(
|
|
|
|
SOUND_MOVE_KEY,
|
|
|
|
match self.sound_map.get(SOUND_MOVE_KEY) {
|
|
|
|
Some(file_name) => Value::from(&file_name),
|
|
|
|
None => Value::empty(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
SOUND_CREATE_KEY,
|
|
|
|
match self.sound_map.get(SOUND_CREATE_KEY) {
|
|
|
|
Some(file_name) => Value::from(&file_name),
|
|
|
|
None => Value::empty(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
SOUND_DESTROY_KEY,
|
|
|
|
match self.sound_map.get(SOUND_DESTROY_KEY) {
|
|
|
|
Some(file_name) => Value::from(&file_name),
|
|
|
|
None => Value::empty(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
Ok(ConfigHandler::write_config(file, &data)?)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn insert_animation(
|
|
|
|
key_value: &Value,
|
|
|
|
animation_map: &mut HashMap<AnimationType, Vec<u32>>,
|
|
|
|
animation_type: AnimationType,
|
|
|
|
) -> Result<()> {
|
|
|
|
animation_map.insert(animation_type, key_value.to_array()?);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn insert_sound(
|
|
|
|
key_value: &Value,
|
|
|
|
sound_map: &mut HashMap<String, AssetPath>,
|
|
|
|
sound_type: &str,
|
|
|
|
) -> Result<()> {
|
|
|
|
sound_map.insert(sound_type.to_string(), key_value.to_value()?);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for EntityParser {
|
|
|
|
fn default() -> EntityParser {
|
|
|
|
EntityParser {
|
|
|
|
enable_hitbox: false,
|
|
|
|
hitbox_radius: 0.0,
|
|
|
|
hitbox_height: 0.0,
|
|
|
|
gltf_file_name: String::new(),
|
|
|
|
|
|
|
|
animations: Vec::new(),
|
|
|
|
|
|
|
|
animation_map: HashMap::new(),
|
|
|
|
|
|
|
|
sound_map: HashMap::new(),
|
|
|
|
|
|
|
|
tags: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|