diff --git a/source/main/CMakeLists.txt b/source/main/CMakeLists.txt index ea415de197..815f6063ed 100644 --- a/source/main/CMakeLists.txt +++ b/source/main/CMakeLists.txt @@ -192,6 +192,7 @@ set(SOURCE_FILES resources/skin_fileformat/SkinFileFormat.{h,cpp} resources/terrn2_fileformat/Terrn2FileFormat.{h,cpp} resources/tobj_fileformat/TObjFileFormat.{h,cpp} + resources/character_def_fileformat/CharacterDefFileFormat.h system/AppCommandLine.cpp system/AppConfig.cpp system/Console.{h,cpp} @@ -334,6 +335,7 @@ target_include_directories(${BINNAME} PRIVATE physics/utils physics/water resources + resources/character_def_fileformat resources/odef_fileformat/ resources/otc_fileformat/ resources/rig_def_fileformat diff --git a/source/main/gameplay/Character.cpp b/source/main/gameplay/Character.cpp index c37efcece7..2a9424596d 100644 --- a/source/main/gameplay/Character.cpp +++ b/source/main/gameplay/Character.cpp @@ -39,7 +39,7 @@ using namespace Ogre; using namespace RoR; -Character::Character(int source, unsigned int streamid, UTFString player_name, int color_number, bool is_remote) : +Character::Character(CharacterDefPtr def, int source, unsigned int streamid, UTFString player_name, int color_number, bool is_remote) : m_actor_coupling(nullptr) , m_character_rotation(0.0f) , m_character_h_speed(2.0f) @@ -50,6 +50,7 @@ Character::Character(int source, unsigned int streamid, UTFString player_name, i , m_is_remote(is_remote) , m_source_id(source) , m_stream_id(streamid) + , m_character_def(def) { static int id_counter = 0; m_instance_name = "Character" + TOSTRING(id_counter); diff --git a/source/main/gameplay/Character.h b/source/main/gameplay/Character.h index 2674ae2d05..fa7b6f4cfe 100644 --- a/source/main/gameplay/Character.h +++ b/source/main/gameplay/Character.h @@ -21,6 +21,7 @@ #pragma once +#include "CharacterDefFileFormat.h" #include "ForwardDeclarations.h" #include "SimBuffers.h" @@ -36,6 +37,7 @@ namespace RoR { /// @addtogroup Character /// @{ +/// /// Character uses simplified physics and occupies single point in space. /// Note on animations: @@ -66,7 +68,7 @@ class Character static const BitMask_t SITUATION_IN_AIR = BITMASK(4); static const BitMask_t SITUATION_DRIVING = BITMASK(5); - Character(int source = -1, unsigned int streamid = 0, Ogre::UTFString playerName = "", int color_number = 0, bool is_remote = true); + Character(CharacterDefPtr def, int source = -1, unsigned int streamid = 0, Ogre::UTFString playerName = "", int color_number = 0, bool is_remote = true); ~Character(); // get state @@ -78,7 +80,6 @@ class Character void setRotation(Ogre::Radian rotation); void move(Ogre::Vector3 offset); void updateLocal(float dt); - void upateRemote(float dt); void updateCharacterRotation(); void SetActorCoupling(bool enabled, Actor* actor); @@ -97,6 +98,7 @@ class Character void SendStreamSetup(); // attributes + CharacterDefPtr m_character_def; std::string m_instance_name; // transforms diff --git a/source/main/gameplay/CharacterFactory.cpp b/source/main/gameplay/CharacterFactory.cpp index 5934d0f9b2..6174ec9c96 100644 --- a/source/main/gameplay/CharacterFactory.cpp +++ b/source/main/gameplay/CharacterFactory.cpp @@ -28,6 +28,175 @@ using namespace RoR; +CharacterFactory::CharacterFactory() +{ + // set up definitions + // NOTE each anim is evaluated separately, there is no either-or relation, + // so you must set each anim's conditions to avoid conflicts. + + CharacterDefPtr rorbot = std::make_shared(); + rorbot->mesh_name = "character.mesh"; + rorbot->name = "Classic RORBot"; + + { // driving + CharacterAnimDef def; + BITMASK_SET_1(def.for_situations, Character::SITUATION_DRIVING); + def.anim_name = "Driving"; + def.playback_time_ratio = 0.f; + def.playback_steering_ratio = -1.f; + def.anim_continuous = false; + def.source_percentual = true; + def.anim_neutral_mid = true; + def.playback_trim = 0.01f; + rorbot->anims.push_back(def); + } + + { // swimming + CharacterAnimDef def; + BITMASK_SET_1(def.for_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.for_actions, Character::ACTION_MOVE_FORWARD); + def.anim_name = "Swim_loop"; + def.playback_h_speed_ratio = 1.f; + def.playback_time_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // floating in water + CharacterAnimDef def; + BITMASK_SET_1(def.for_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + def.anim_name = "Spot_swim"; + def.playback_time_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // running + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.for_actions, Character::ACTION_RUN); + def.anim_name = "Run"; + def.playback_time_ratio = 1.f; + def.playback_h_speed_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // walking forward + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Walk"; + def.playback_time_ratio = 1.f; + def.playback_h_speed_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // walking backward + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_MOVE_BACKWARD); + def.anim_name = "Walk"; + def.playback_time_ratio = -1.f; + def.playback_h_speed_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // side stepping left (-time) + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_TURN_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_BACKWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Side_step"; + def.playback_time_ratio = -1.f; + rorbot->anims.push_back(def); + } + + { // side stepping right (+time) + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_SIDESTEP_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_BACKWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_TURN_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_TURN_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Side_step"; + def.playback_time_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // side stepping left (-time) + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_SIDESTEP_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_BACKWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_TURN_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_TURN_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Side_step"; + def.playback_time_ratio = -1.f; + rorbot->anims.push_back(def); + } + + { // turning left (+time) + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_TURN_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_SIDESTEP_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_SIDESTEP_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_BACKWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Turn"; + def.playback_time_ratio = 1.f; + rorbot->anims.push_back(def); + } + + { // turning right (-time) + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.for_actions, Character::ACTION_TURN_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_SIDESTEP_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_SIDESTEP_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_BACKWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Turn"; + def.playback_time_ratio = -1.f; + rorbot->anims.push_back(def); + } + + { // idle + CharacterAnimDef def; + BITMASK_SET_1(def.except_situations, Character::SITUATION_IN_DEEP_WATER); + BITMASK_SET_1(def.except_situations, Character::SITUATION_DRIVING); + BITMASK_SET_1(def.except_actions, Character::ACTION_TURN_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_TURN_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_SIDESTEP_LEFT); + BITMASK_SET_1(def.except_actions, Character::ACTION_SIDESTEP_RIGHT); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_FORWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_MOVE_BACKWARD); + BITMASK_SET_1(def.except_actions, Character::ACTION_RUN); + def.anim_name = "Idle_sway"; + def.playback_time_ratio = 1.f; + rorbot->anims.push_back(def); + } + + m_character_defs.push_back(rorbot); +} + Character* CharacterFactory::CreateLocalCharacter() { int colourNum = -1; @@ -42,7 +211,7 @@ Character* CharacterFactory::CreateLocalCharacter() } #endif // USE_SOCKETW - m_local_character = std::unique_ptr(new Character(-1, 0, playerName, colourNum, false)); + m_local_character = std::unique_ptr(new Character(m_character_defs[0], -1, 0, playerName, colourNum, false)); App::GetGfxScene()->RegisterGfxCharacter(m_local_character.get()); return m_local_character.get(); } @@ -57,7 +226,7 @@ void CharacterFactory::createRemoteInstance(int sourceid, int streamid) LOG(" new character for " + TOSTRING(sourceid) + ":" + TOSTRING(streamid) + ", colour: " + TOSTRING(colour)); - Character* ch = new Character(sourceid, streamid, name, colour, true); + Character* ch = new Character(m_character_defs[0], sourceid, streamid, name, colour, true); App::GetGfxScene()->RegisterGfxCharacter(ch); m_remote_characters.push_back(std::unique_ptr(ch)); #endif // USE_SOCKETW diff --git a/source/main/gameplay/CharacterFactory.h b/source/main/gameplay/CharacterFactory.h index 765bcf5cbf..28fa2e1cd3 100644 --- a/source/main/gameplay/CharacterFactory.h +++ b/source/main/gameplay/CharacterFactory.h @@ -24,6 +24,7 @@ #include "Application.h" #include "Character.h" +#include "CharacterDefFileFormat.h" #include "Network.h" #include @@ -39,7 +40,7 @@ namespace RoR { class CharacterFactory { public: - CharacterFactory() {} + CharacterFactory(); Character* CreateLocalCharacter(); Character* GetLocalCharacter() { return m_local_character.get(); } void DeleteAllCharacters(); @@ -51,6 +52,8 @@ class CharacterFactory private: + std::vector m_character_defs; + std::unique_ptr m_local_character; std::vector> m_remote_characters; diff --git a/source/main/gfx/GfxCharacter.cpp b/source/main/gfx/GfxCharacter.cpp index f34a1e9a7f..eb7c47ddbc 100644 --- a/source/main/gfx/GfxCharacter.cpp +++ b/source/main/gfx/GfxCharacter.cpp @@ -186,115 +186,84 @@ void RoR::GfxCharacter::UpdateCharacterInScene(float dt) #endif // USE_SOCKETW } -void GfxCharacter::UpdateAnimations(float dt) +void GfxCharacter::EvaluateAnimDef(CharacterAnimDef const& def, float dt) { - // Reset all anims + // Test if applicable. + if (//(def.for_situations != 0 && BITMASK_IS_0(xc_simbuf.simbuf_situation_flags, def.for_situations)) || // some situations are specified, but none of the situations matches + (!BITMASK_IS_1(xc_simbuf.simbuf_situation_flags, def.for_situations)) || // not all situation flags are satisified + (xc_simbuf.simbuf_situation_flags & def.except_situations) || // any of the forbidden situation matches + //(def.for_actions != 0 && BITMASK_IS_0(xc_simbuf.simbuf_action_flags, def.for_actions)) || // some actions are specified, but none of the actions matches + (!BITMASK_IS_1(xc_simbuf.simbuf_action_flags, def.for_actions)) || // not all action flags are satisfied + (xc_simbuf.simbuf_action_flags & def.except_actions)) // any of the forbidden situation matches + { + return; + } + Ogre::Entity* entity = static_cast(xc_scenenode->getAttachedObject(0)); - AnimationStateSet* stateset = entity->getAllAnimationStates(); - for (auto& state_pair : stateset->getAnimationStates()) + AnimationState* as = entity->getAnimationState(def.anim_name); + + // Query data sources. + float timepos = 1.f; + if (def.playback_time_ratio != 0.f) { - AnimationState* as = state_pair.second; - as->setEnabled(false); - as->setWeight(0); + timepos *= (def.playback_time_ratio * dt); + } + if (def.playback_h_speed_ratio != 0.f) + { + timepos *= (def.playback_h_speed_ratio * xc_simbuf.simbuf_character_h_speed); + } + if (def.playback_steering_ratio != 0.f && xc_simbuf.simbuf_actor_coupling) + { + timepos *= (def.playback_steering_ratio * xc_simbuf.simbuf_actor_coupling->ar_hydro_dir_wheel_display); } - if (BITMASK_IS_1(xc_simbuf.simbuf_situation_flags, Character::SITUATION_DRIVING)) + // Transform the anim pos. + if (def.source_percentual) { - AnimationState* as = entity->getAnimationState("Driving"); - float angle = xc_simbuf.simbuf_actor_coupling->ar_hydro_dir_wheel_display * -1.0f; // not getSteeringAngle(), but this, as its smoothed - float anim_time_pos = ((angle + 1.0f) * 0.5f) * as->getLength(); - // prevent animation flickering on the borders: - if (anim_time_pos < 0.01f) + if (def.anim_neutral_mid) { - anim_time_pos = 0.01f; + timepos = (timepos + 1.0f) * 0.5f; } - if (anim_time_pos > as->getLength() - 0.01f) - { - anim_time_pos = as->getLength() - 0.01f; - } - - as->setTimePosition(anim_time_pos); - as->setWeight(1.f); - as->setEnabled(true); + timepos *= as->getLength(); } - else if (BITMASK_IS_1(xc_simbuf.simbuf_situation_flags, Character::SITUATION_IN_DEEP_WATER)) + if (def.playback_trim > 0.f) { - if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_MOVE_FORWARD) || - BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_MOVE_BACKWARD)) + // prevent animation flickering on the borders: + if (timepos < def.playback_trim) { - AnimationState* as = entity->getAnimationState("Swim_loop"); - as->setTimePosition(as->getTimePosition() + (dt * xc_simbuf.simbuf_character_h_speed)); - as->setWeight(1.f); - as->setEnabled(true); + timepos = def.playback_trim; } - else + if (timepos > as->getLength() - def.playback_trim) { - AnimationState* as = entity->getAnimationState("Spot_swim"); - as->setTimePosition(as->getTimePosition() + dt); - as->setWeight(1.f); - as->setEnabled(true); + timepos = as->getLength() - def.playback_trim; } } - else // solid ground or jumping + if (def.anim_continuous) { - if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_MOVE_FORWARD) || - BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_MOVE_BACKWARD)) - { - AnimationState* as = nullptr; - if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_RUN)) - as = entity->getAnimationState("Run"); - else - as = entity->getAnimationState("Walk"); - - float time = dt * xc_simbuf.simbuf_character_h_speed; - as->setTimePosition(as->getTimePosition() + time); -as->setWeight(1.f); - as->setEnabled(true); - } - else - { - if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_TURN_LEFT)) - { - AnimationState* as = entity->getAnimationState("Turn"); - float time = dt; - if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_SLOW_TURN)) - time *= 1.2f; - as->setTimePosition(as->getTimePosition() + time); - as->setWeight(1.f); - as->setEnabled(true); - } - else if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_TURN_RIGHT)) - { - AnimationState* as = entity->getAnimationState("Turn"); - float time = -dt; - if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_SLOW_TURN)) - time *= 1.2f; - as->setTimePosition(as->getTimePosition() + time); - as->setWeight(1.f); - as->setEnabled(true); - } - else if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_SIDESTEP_RIGHT)) - { - AnimationState* as = entity->getAnimationState("Side_step"); - as->setTimePosition(as->getTimePosition() + dt); - as->setWeight(1.f); - as->setEnabled(true); - } - else if (BITMASK_IS_1(xc_simbuf.simbuf_action_flags, Character::ACTION_SIDESTEP_LEFT)) - { - AnimationState* as = entity->getAnimationState("Side_step"); - as->setTimePosition(as->getTimePosition() + -dt); - as->setWeight(1.f); - as->setEnabled(true); - } - else - { - AnimationState* as = entity->getAnimationState("Idle_sway"); - as->setTimePosition(as->getTimePosition() + dt); - as->setWeight(1.f); - as->setEnabled(true); - } - } + timepos += as->getTimePosition(); + } + + // Update the OGRE object + as->setTimePosition(timepos); + as->setWeight(def.weight); + as->setEnabled(true); +} + +void GfxCharacter::UpdateAnimations(float dt) +{ + // Reset all anims + Ogre::Entity* entity = static_cast(xc_scenenode->getAttachedObject(0)); + AnimationStateSet* stateset = entity->getAllAnimationStates(); + for (auto& state_pair : stateset->getAnimationStates()) + { + AnimationState* as = state_pair.second; + as->setEnabled(false); + as->setWeight(0); + } + + for (CharacterAnimDef const& def : xc_character->m_character_def->anims) + { + this->EvaluateAnimDef(def, dt); } } diff --git a/source/main/gfx/GfxCharacter.h b/source/main/gfx/GfxCharacter.h index efb7a77380..f1ca90c0e2 100644 --- a/source/main/gfx/GfxCharacter.h +++ b/source/main/gfx/GfxCharacter.h @@ -21,6 +21,7 @@ #pragma once +#include "CharacterDefFileFormat.h" #include "ForwardDeclarations.h" #include "SimBuffers.h" @@ -44,6 +45,7 @@ struct GfxCharacter void DisableAnim(Ogre::AnimationState* anim_state); void EnableAnim(Ogre::AnimationState* anim_state, float time); void UpdateAnimations(float dt); + void EvaluateAnimDef(CharacterAnimDef const& def, float dt); Ogre::SceneNode* xc_scenenode; CharacterSB xc_simbuf; diff --git a/source/main/resources/character_def_fileformat/CharacterDefFileFormat.h b/source/main/resources/character_def_fileformat/CharacterDefFileFormat.h new file mode 100644 index 0000000000..daa4881688 --- /dev/null +++ b/source/main/resources/character_def_fileformat/CharacterDefFileFormat.h @@ -0,0 +1,74 @@ +/* + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2022 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Rigs of Rods. If not, see . +*/ + +#pragma once + +#include "BitFlags.h" + +#include +#include +#include + +namespace RoR { + +/// @addtogroup Gameplay +/// @{ + +/// @addtogroup Character +/// @{ + +struct CharacterAnimDef +{ + std::string anim_name; //!< Name of the skeletal animation from OGRE's *.skeleton file. + std::string description; //!< Internal game name. + + // Conditions + BitMask_t for_situations = 0; //!< Character::SITUATION_, all must be satisfied. + BitMask_t except_situations = 0; //!< Character::SITUATION_, none must be satisfied. + BitMask_t for_actions = 0; //!< Character::ACTION_, all must be satisfied. + BitMask_t except_actions = 0; //!< Character::ACTION_, none must be satisfied. + + // Anim position calculation + float playback_time_ratio = 0.f; //!< How much elapsed time affects animation position. + float playback_h_speed_ratio = 0.f; //!< How much horizontal movement speed affects animation position. + float playback_steering_ratio = 0.f; //!< How much vehicle steering angle affects animation position. + bool anim_continuous = true; //!< Should animation keep advancing and looping, or should it be set to exact position? + bool anim_neutral_mid = false; //!< Does the anim have the 'neutral' position in it's middle (such as steering left/right) instead of at start? Only effective together with "percentual". + bool source_percentual = false; //!< Is the position source value a percentage of animation length? + float playback_trim = 0.0f; //!< How much to trim the animation position. Useful for i.e. steering animation to avoid flickering. + + // Anim blending weight + float weight = 1.0f; +}; + +struct CharacterDef +{ + std::string name; + std::string mesh_name; + std::vector anims; +}; + +typedef std::shared_ptr CharacterDefPtr; + +/// @} // addtogroup Character +/// @} // addtogroup Gameplay + +} // namespace RoR \ No newline at end of file