Better narration processing

This commit is contained in:
2026-01-22 17:15:19 +03:00
parent 4b24d85123
commit cd91174f5d
23 changed files with 1301 additions and 367 deletions

View File

@@ -9,7 +9,7 @@ find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
set(COPY_DIRECTORIES resources skybox water resources/buildings/parts)
set(COPY_DIRECTORIES characters resources skybox water resources/buildings/parts)
set(INSTALL_DEPS ${CMAKE_CURRENT_BINARY_DIR}/resources.cfg)
foreach(DIR_NAME ${COPY_DIRECTORIES})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}

View File

@@ -22,38 +22,28 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured && ch.mSkeleton) {
if (!anim.configured) {
int i, j;
e.set<EventData>({});
ch.mSkeleton->setBlendMode(
ch.mBodyEnt->getSkeleton()->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::String animNames[] = {
"idle",
"walking",
"running",
"treading_water",
"swimming",
"hanging-idle",
"hanging-climb",
"swimming-hold-edge",
"swimming-edge-climb",
"character-talk",
"pass-character",
"idle-act",
"sitting-chair",
"sitting-ground"
};
int state_count = sizeof(animNames) /
sizeof(animNames[0]);
anim.mAnimationSystem =
new AnimationSystem(false);
for (i = 0; i < state_count; i++) {
Ogre::AnimationStateSet *animStateSet =
ch.mBodyEnt->getAllAnimationStates();
const Ogre::AnimationStateMap &animMap =
animStateSet->getAnimationStates();
anim.mAnimationSystem =
new AnimationSystem(false);
ch.mBodyEnt->getSkeleton()
->getBone("Root")
->removeAllChildren();
for (auto it = animMap.begin();
it != animMap.end(); it++) {
Animation *animation = new Animation(
ch.mSkeleton,
ch.mBodyEnt->getAnimationState(
animNames[i]),
ch.mSkeleton->getAnimation(
animNames[i]),
ch.mBodyEnt->getSkeleton(),
it->second,
ch.mBodyEnt->getSkeleton()
->getAnimation(
it->first),
e);
#ifdef VDEBUG
std::cout
@@ -62,7 +52,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
#endif
animation->setLoop(true);
anim.mAnimationSystem->add_animation(
animNames[i], animation);
it->first, animation);
}
anim.mAnimationSystem
->builder()
@@ -157,10 +147,39 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
bool result = anim.mAnimationSystem->addTime(delta);
if (!ch.mRootBone)
if (!anim.mAnimationSystem)
return;
// The value we get is interpolated value. When result is true it is new step
#if 0
ch.mBodyEnt->getSkeleton()->getBone("Root")->reset();
ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
Ogre::Vector3::ZERO);
#endif
bool result = anim.mAnimationSystem->addTime(delta);
Ogre::Vector3 rootMotion =
anim.mAnimationSystem->getRootMotionDelta();
ch.mBonePrevMotion = ch.mBoneMotion;
ch.mBoneMotion = rootMotion;
ch.mBodyEnt->_updateAnimation();
ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
Ogre::Vector3::ZERO);
#if 0
Ogre::Vector3 deltaMotion;
ch.mBodyEnt->_updateAnimation();
std::cout << "motion: " << ch.mBoneMotion << " "
<< rootMotion << " " << ch.mBonePrevMotion
<< std::endl;
rootMotion = ch.mBodyEnt->getSkeleton()
->getBone("Root")
->getPosition();
if (rootMotion.squaredLength() <
ch.mBoneMotion.squaredLength())
deltaMotion = rootMotion;
else
deltaMotion = rootMotion - ch.mBoneMotion;
ch.mBonePrevMotion = ch.mBoneMotion;
ch.mBoneMotion = deltaMotion;
#endif
// The value we get is interpolated value. When result is true it is new step
#if 0
Ogre::Vector3 offset = ch.mRootBone->getPosition();
ch.mBoneMotion = static_cast<RootMotionListener *>(
@@ -302,54 +321,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
Ogre::Vector3 colNormal;
bool is_on_floor = false;
bool penetration = false;
#if 0
if (eng.startupDelay < 0.0f) {
if (body.mController) {
Ogre::Vector3 rotMotion =
v.velocity * eng.delta;
rotMotion.x = Ogre::Math::Clamp(
rotMotion.x, -0.04f, 0.04f);
rotMotion.y = Ogre::Math::Clamp(
rotMotion.y, -0.025f, 0.1f);
rotMotion.z = Ogre::Math::Clamp(
rotMotion.z, -0.04f, 0.04f);
btVector3 currentPosition =
body.mGhostObject
->getWorldTransform()
.getOrigin();
is_on_floor =
body.mController->isOnFloor();
penetration = body.mController
->isPenetrating();
if (is_on_floor)
v.gvelocity =
Ogre::Vector3::ZERO;
btTransform from(
Ogre::Bullet::convert(
ch.mBodyNode
->getOrientation()),
Ogre::Bullet::convert(
ch.mBodyNode
->getPosition()));
ch.mBodyNode->_setDerivedPosition(
ch.mBodyNode
->_getDerivedPosition() +
rotMotion);
}
}
#endif
});
#if 0
ecs.system<CharacterVelocity, CharacterBase>("HandleRootMotionEnd")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterVelocity &v,
CharacterBase &ch) {
// zero the velocity;
// v.velocity = Ogre::Vector3::ZERO;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
});
#endif
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandleNPCAnimations")
@@ -360,6 +332,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
@@ -405,6 +379,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
AnimationControl &anim) {
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
@@ -568,15 +544,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
act.prevMotion.z = input.motion.z;
}
#if 0
if (!controls_idle) {
if (Ogre::Math::Abs(input.motion.z - act.prevMotion.z) > 0.001f) {
if (input.motion.z < 0)
ECS::get_mut<LuaData>().call_handler("actuator_forward", e);
}
ECS::get_mut<LuaData>().call_handler("actuator_controls_update");
}
#endif
act.prevMotion = input.motion;
});
ecs.system<EventData>("UpdateEvents")
@@ -616,87 +583,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
<< "h=" << ch.mBodyNode->_getDerivedPosition().y
<< std::endl;
});
#endif
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterBody>(
"UpdateBodyCast")
.kind(flecs::OnUpdate)
.without<CharacterInActuator>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterBody &body) {
struct ResultCallback
: public btCollisionWorld::RayResultCallback {
btCollisionObject *m_me;
btVector3 m_from, m_to, m_hitNormalWorld,
m_hitPointWorld;
ResultCallback(btCollisionObject *me,
const btVector3 &from,
const btVector3 &to)
: m_me(me)
, m_from(from)
, m_to(to)
{
}
btScalar addSingleResult(
btCollisionWorld::LocalRayResult
&rayResult,
bool normalInWorldSpace) override
{
if (rayResult.m_collisionObject == m_me)
return 1.0f;
if (!btPairCachingGhostObject::upcast(
rayResult.m_collisionObject))
return 1.0f;
if (!(rayResult.m_collisionObject
->getCollisionFlags() &
btCollisionObject::
CF_CHARACTER_OBJECT))
return 1.0f;
m_closestHitFraction =
rayResult.m_hitFraction;
m_collisionObject =
rayResult.m_collisionObject;
if (normalInWorldSpace)
m_hitNormalWorld =
rayResult
.m_hitNormalLocal;
else
m_hitNormalWorld =
m_collisionObject
->getWorldTransform()
.getBasis() *
rayResult
.m_hitNormalLocal;
m_hitPointWorld.setInterpolate3(
m_from, m_to,
rayResult.m_hitFraction);
return rayResult.m_hitFraction;
}
};
Ogre::Vector3 offset(0.0f, 0.5f, 0.0f);
float dist = 0.5f;
btVector3 a = Ogre::Bullet::convert(
ch.mBodyNode->getPosition() + offset),
b(Ogre::Bullet::convert(
ch.mBodyNode->getPosition() +
ch.mBodyNode->getOrientation() *
Ogre::Vector3(0, 0, dist) +
offset));
ResultCallback result(body.mGhostObject, a, b);
// body.mGhostObject->rayTest(a, b, result);
eng.mWorld->getBtWorld()->rayTest(a, b, result);
if (result.hasHit()) {
std::cout << "Hit!!! " << result.m_hitPointWorld
<< std::endl;
e.set<CharacterInActuator>(
{ "idle", { 0, 0, 0 } });
ECS::get<LuaBase>().mLua->call_handler(
"character_enter", e,
ECS::get<Body2Entity>().entities.at(
const_cast<btCollisionObject *>(
result.m_collisionObject)));
}
});
#endif
struct AnimationSetCommand : public GameWorld::Command {
int operator()(const std::vector<GameWorld::Parameter *> &args)

View File

@@ -9,22 +9,16 @@
namespace ECS
{
class RootMotionListener : public Ogre::NodeAnimationTrack::Listener {
Ogre::Vector3 prevTranslation;
mutable Ogre::Vector3 deltaMotion;
flecs::entity e;
public:
RootMotionListener(flecs::entity e)
: Ogre::NodeAnimationTrack::Listener()
, e(e)
, prevTranslation(Ogre::Vector3::ZERO)
, deltaMotion(Ogre::Vector3::ZERO)
{
}
bool getInterpolatedKeyFrame(const Ogre::AnimationTrack *t,
const Ogre::TimeIndex &timeIndex,
Ogre::KeyFrame *kf) override
{
#if 0
Ogre::TransformKeyFrame *vkf =
static_cast<Ogre::TransformKeyFrame *>(kf);
Ogre::KeyFrame *kf1, *kf2;
@@ -36,14 +30,12 @@ public:
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
if (tm == 0.0f) {
Ogre::Vector3 deltaMotion;
Ogre::Vector3 prevMotion;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
deltaMotion = translation;
// vkf->setRotation(k1->getRotation());
// vkf->setTranslate(k1->getTranslate());
// vkf->setScale(k1->getScale());
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation(), k2->getRotation(), true);
@@ -55,14 +47,7 @@ public:
translation.squaredLength())
deltaMotion = translation;
}
#if 0
std::cout << "time: " << tm
<< " Position: " << deltaMotion;
std::cout << " Quaternion: " << rotation;
std::cout << std::endl;
#endif
vkf->setTranslate(deltaMotion);
// vkf->setTranslate(translation);
vkf->setRotation(rotation);
vkf->setScale(Ogre::Vector3(1, 1, 1));
prevTranslation = translation;
@@ -70,7 +55,9 @@ public:
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
return true;
}
#endif
return false;
}
};
struct AnimationTrigger;
struct AnimationTriggerSubscriber {
@@ -126,46 +113,66 @@ struct AnimationTrigger {
{
}
};
struct SetupTracks {
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
Ogre::Vector3 mRootTranslation;
SetupTracks(flecs::entity e, Ogre::Skeleton *skeleton,
Ogre::Animation *animation)
: mListener(OGRE_NEW RootMotionListener(e))
{
mHipsTrack = nullptr;
mRootTrack = nullptr;
for (const auto &it : animation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTrack = track;
} else if (trackName == "Root") {
mRootTrack = track;
// mRootTracks[i]->removeAllKeyFrames();
}
}
if (!mRootTrack) {
Ogre::Bone *bone = skeleton->getBone("Root");
mRootTrack = animation->createNodeTrack(
bone->getHandle(), bone);
Ogre::TransformKeyFrame *kf =
mRootTrack->createNodeKeyFrame(0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
// if (e.has<Player>()) // FIXME
// mRootTrack->setListener(mListener);
Ogre::TransformKeyFrame *tkfBeg =
(Ogre::TransformKeyFrame *)mRootTrack->getKeyFrame(0);
Ogre::TransformKeyFrame *tkfEnd =
(Ogre::TransformKeyFrame *)mRootTrack->getKeyFrame(
mRootTrack->getNumKeyFrames() - 1);
mRootTranslation =
tkfEnd->getTranslate() - tkfBeg->getTranslate();
}
};
struct Animation {
Ogre::AnimationState *mAnimationState;
Ogre::Animation *mSkelAnimation;
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
SetupTracks *mTracks;
float m_weight;
float m_accWeight;
flecs::entity e;
Ogre::Skeleton *mSkeleton;
Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation, flecs::entity e)
: mAnimationState(animState)
: mTracks(OGRE_NEW SetupTracks(e, skeleton, skelAnimation))
, mAnimationState(animState)
, mSkelAnimation(skelAnimation)
, mListener(OGRE_NEW RootMotionListener(e))
, m_weight(0)
, m_accWeight(0)
, e(e)
, mSkeleton(skeleton)
{
int j;
mRootTrack = nullptr;
mHipsTrack = nullptr;
for (const auto &it : mSkelAnimation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTrack = track;
} else if (trackName == "Root") {
mRootTrack = track;
// mRootTracks[i]->removeAllKeyFrames();
}
}
if (!mRootTrack) {
Ogre::Bone *bone = skeleton->getBone("Root");
mRootTrack = mSkelAnimation->createNodeTrack(
bone->getHandle(), bone);
Ogre::TransformKeyFrame *kf =
mRootTrack->createNodeKeyFrame(0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
mRootTrack->setListener(mListener);
}
Ogre::String getName()
{
@@ -198,22 +205,134 @@ struct Animation {
{
return m_weight;
}
bool addTime(float time)
Ogre::Vector3 rootMotion;
Ogre::Vector3 getRootMotionDelta()
{
#if 0
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
Ogre::Real timePos = mAnimationState->getTimePosition();
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
float tm = mTracks->mRootTrack->getKeyFramesAtTime(
index, &kf1, &kf2, &firstKeyIndex);
k1 = static_cast<Ogre::TransformKeyFrame *>(kf1);
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation(),
k2->getRotation(), true);
translation = k1->getTranslate() +
(k2->getTranslate() -
k1->getTranslate()) *
tm;
}
return translation * mAnimationState->getWeight();
#endif
if (mAnimationState->getEnabled())
return rootMotion * mAnimationState->getWeight();
else
return Ogre::Vector3(0, 0, 0);
}
#if 0
void updateRootMotion(Ogre::Real timePos)
{
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
mSkeleton->getBone("Root")->setManuallyControlled(true);
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
float tm = mTracks->mRootTrack->getKeyFramesAtTime(
index, &kf1, &kf2, &firstKeyIndex);
k1 = static_cast<Ogre::TransformKeyFrame *>(kf1);
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
Ogre::Vector3 deltaMotion =
e.get_mut<CharacterBase>().mBoneMotion;
Ogre::Vector3 prevTranslation =
e.get_mut<CharacterBase>().mBonePrevMotion;
if (tm == 0.0f) {
rotation = k1->getRotation() * m_weight;
translation = k1->getTranslate() * m_weight;
deltaMotion = translation;
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation() * m_weight,
k2->getRotation() * m_weight, true);
translation = k1->getTranslate() * m_weight +
(k2->getTranslate() * m_weight -
k1->getTranslate() * m_weight) *
tm;
deltaMotion = translation - prevTranslation;
if (deltaMotion.squaredLength() >
translation.squaredLength())
deltaMotion = translation;
}
e.get_mut<CharacterBase>().mBoneMotion = deltaMotion;
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
#if 1
if (timePos > 0.5f && m_weight > 0.2 && translation.squaredLength() > 0.0f) {
std::cout << timePos << " " << m_weight << " "
<< e.get<CharacterBase>()
.mBodyEnt->getMesh()
->getName()
<< " " << deltaMotion << " "
<< prevTranslation << std::endl;
std::cout << translation << " " << rotation << std::endl;
// OgreAssert(false, "updateRootMotion");
}
#endif
mTracks->mRootTrack->getAssociatedNode()->setPosition(
Ogre::Vector3());
mSkeleton->getBone("Root")->reset();
}
#endif
void getKeyframeIndices(Ogre::Real timePos, unsigned short *pindex)
{
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
Ogre::KeyFrame *kf1, *kf2;
mTracks->mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, pindex);
}
bool addTime(float time)
{
bool result = mAnimationState->getEnabled();
if (!result)
return result;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
Ogre::KeyFrame *kf1, *kf2;
unsigned short prev_index, next_index;
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &prev_index);
unsigned short prev_index, next_index;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(mAnimationState->getTimePosition());
getKeyframeIndices(mAnimationState->getTimePosition(), &prev_index);
unsigned int previous_frame = index.getKeyIndex();
float lastTime = mAnimationState->getTimePosition();
mAnimationState->addTime(time);
index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &next_index);
return prev_index != next_index;
float thisTime = mAnimationState->getTimePosition();
float length = mAnimationState->getLength();
bool loop = mAnimationState->getLoop();
int loops = loop ? (int)std::round((lastTime + time - thisTime) / length) : 0;
Ogre::TransformKeyFrame tkf(0, 0);
mTracks->mRootTrack->getInterpolatedKeyFrame(lastTime, &tkf);
Ogre::Vector3 lastRootPos = tkf.getTranslate();
mTracks->mRootTrack->getInterpolatedKeyFrame(thisTime, &tkf);
Ogre::Vector3 thisRootPos = tkf.getTranslate();
#if 0
if (thisTime > lastTime)
rootMotion = thisRootPos - lastRootPos;
else
rootMotion = mTracks->mRootTranslation + thisRootPos - lastRootPos;
#else
rootMotion = (thisRootPos - lastRootPos) + (loops * mTracks->mRootTranslation);
#endif
getKeyframeIndices(mAnimationState->getTimePosition(), &next_index);
// updateRootMotion(mAnimationState->getTimePosition());
return prev_index != next_index;
}
void reset()
{
@@ -827,6 +946,14 @@ struct AnimationSystem : AnimationNode {
}
};
AnimationSystemBuilder m_builder;
Ogre::Vector3 getRootMotionDelta()
{
Ogre::Vector3 motionDelta(0, 0, 0);
int i;
for (i = 0; i < vanimation_list.size(); i++)
motionDelta += vanimation_list[i]->getRootMotionDelta();
return motionDelta;
}
bool addTime(float time)
{
int i;

View File

@@ -4,15 +4,190 @@
#include "Components.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "StaticGeometryModule.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
#include "items.h"
#include "CharacterManagerModule.h"
namespace ECS
{
struct TownNPCs {
struct NPCData {
flecs::entity e;
nlohmann::json props;
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String model;
};
std::map<int, NPCData> npcs;
};
struct LivesIn {};
void createNPCActionNodes(flecs::entity town, flecs::entity e, int index)
{
NPCActionNodes &anodes = e.get_mut<NPCActionNodes>();
const CharacterBase &ch = e.get<CharacterBase>();
Ogre::Vector3 characterPos = ch.mBodyNode->_getDerivedPosition();
Ogre::Quaternion characterRot = ch.mBodyNode->_getDerivedOrientation();
if (anodes.anodes.size() > 0) {
int i;
for (i = 0; i < anodes.anodes.size(); i++) {
auto &anode = anodes.anodes[i];
Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.3f +
Ogre::Vector3::UNIT_Y;
if (i == 1)
offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.3f +
Ogre::Vector3::UNIT_Y;
anode.position = characterPos + characterRot * offset;
anode.rotation = characterRot;
to_json(anode.props["position"], anode.position);
to_json(anode.props["rotation"], anode.rotation);
}
e.modified<NPCActionNodes>();
return;
}
{
ActionNodeList::ActionNode anode;
anode.props["action"] = "talk";
anode.props["action_text"] = "Talk";
anode.action = "talk";
anode.action_text = "Talk";
Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.25f +
Ogre::Vector3::UNIT_Y * 1.5f;
anode.position = characterPos + characterRot * offset;
anode.rotation = characterRot;
anode.radius = 0.6f;
anode.height = 1.0f;
to_json(anode.props["position"], anode.position);
to_json(anode.props["rotation"], anode.rotation);
anode.props["radius"] = anode.radius;
anode.props["height"] = anode.height;
anode.props["town"] = town.id();
anode.props["index"] = index;
anodes.anodes.push_back(anode);
}
{
ActionNodeList::ActionNode anode;
anode.props["action"] = "action";
anode.props["action_text"] = "Action";
anode.action = "action";
anode.action_text = "Action";
Ogre::Vector3 offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.2f +
Ogre::Vector3::UNIT_Y;
anode.position = characterPos + characterRot * offset;
anode.rotation = characterRot;
anode.radius = 0.3f;
anode.height = 1.0f;
to_json(anode.props["position"], anode.position);
to_json(anode.props["rotation"], anode.rotation);
anode.props["radius"] = anode.radius;
anode.props["height"] = anode.height;
anode.props["town"] = town.id();
anode.props["index"] = index;
anodes.anodes.push_back(anode);
}
e.modified<NPCActionNodes>();
}
CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
{
ecs.module<CharacterManagerModule>();
ecs.import <CharacterModule>();
ecs.import <CharacterAnimationModule>();
ecs.import <PhysicsModule>();
ecs.import <PlayerActionModule>();
ecs.component<TownCharacterHolder>();
ecs.component<TownNPCs>();
ecs.component<LivesIn>();
ecs.component<NPCActionNodes>().on_add(
[](flecs::entity e, NPCActionNodes &anodes) {
anodes.anodes.clear();
});
ecs.system<TerrainItem, TownNPCs>("UpdateCharacters")
.immediate()
.kind(flecs::OnUpdate)
.interval(1.0f)
.write<CharacterBase>()
.write<NPCActionNodes>()
.write<CharacterLocation>()
.write<CharacterConf>()
.write<Character>()
.write<LivesIn>()
.each([this](flecs::entity town, TerrainItem &item,
TownNPCs &npcs) {
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
ECS::get().defer_suspend();
Ogre::Vector3 cameraPos =
player.get<CharacterBase>()
.mBodyNode->_getDerivedPosition();
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition = data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(npcPosition) <
10000.0f) {
if (!data.e.is_valid()) {
data.e = createCharacterData(
data.model,
data.position,
data.orientation);
data.e.add<LivesIn>(town);
break;
}
}
if (cameraPos.squaredDistance(npcPosition) >
22500.0f) {
if (data.e.is_valid()) {
data.e.destruct();
data.e = flecs::entity();
break;
}
}
}
ECS::get().defer_resume();
});
ecs.system<TerrainItem, TownNPCs>("UpdateCharacters2")
.immediate()
.kind(flecs::OnUpdate)
.write<CharacterBase>()
.write<NPCActionNodes>()
.write<CharacterLocation>()
.write<CharacterConf>()
.write<Character>()
.write<LivesIn>()
.each([this](flecs::entity town, TerrainItem &item,
TownNPCs &npcs) {
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
Ogre::Vector3 cameraPos =
player.get<CharacterBase>()
.mBodyNode->_getDerivedPosition();
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition = data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(npcPosition) <
10000.0f) {
if (data.e.is_valid()) {
if (data.e.has<CharacterBase>() &&
data.e.has<LivesIn>(town))
createNPCActionNodes(
town, data.e,
index);
}
}
}
});
}
flecs::entity
CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
@@ -43,7 +218,44 @@ CharacterManagerModule::createCharacterData(const Ogre::String model,
.entity()
.set<CharacterLocation>({ rotation, position })
.set<CharacterConf>({ model })
.add<Character>();
return e;
.add<Character>()
.add<NPCActionNodes>();
return e;
}
void CharacterManagerModule::registerTownCharacters(flecs::entity town)
{
Ogre::MeshManager::getSingleton().load("normal-male.glb", "General");
Ogre::MeshManager::getSingleton().load("normal-female.glb", "General");
Ogre::String props = StaticGeometryModule::getItemProperties(town);
nlohmann::json j = nlohmann::json::parse(props);
nlohmann::json npcs = nlohmann::json::array();
if (town.has<TownNPCs>())
return;
if (j.find("npcs") != j.end())
npcs = j["npcs"];
std::cout << npcs.dump(4) << std::endl;
int index = 0;
std::map<int, TownNPCs::NPCData> npcMap;
for (auto &npc : npcs) {
const char *models[] = { "normal-male.glb",
"normal-female.glb" };
int sex = npc["sex"].get<int>();
Ogre::Vector3 npcPosition;
Ogre::Quaternion npcOrientation;
from_json(npc["position"], npcPosition);
from_json(npc["orientation"], npcOrientation);
Ogre::String model = models[sex];
TownNPCs::NPCData npcData;
npcData.e = flecs::entity();
npcData.model = model;
npcData.orientation = npcOrientation;
npcData.position = npcPosition;
npcData.props = npc;
npcMap[index] = npcData;
index++;
}
town.set<TownNPCs>({ npcMap });
}
}

View File

@@ -3,6 +3,7 @@
#include <flecs.h>
namespace ECS
{
struct TownCharacterHolder{int index;};
struct CharacterManagerModule {
std::set<flecs::entity> characters;
flecs::entity player;
@@ -17,6 +18,10 @@ struct CharacterManagerModule {
{
return player;
}
void registerTownCharacters(flecs::entity town);
void setTownCharacter(flecs::entity town, int index, bool enable);
CharacterManagerModule(CharacterManagerModule &&) = delete;
CharacterManagerModule &operator=(CharacterManagerModule&&) = delete;
};
}
#endif

View File

@@ -282,13 +282,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ch.mBodyNode->setOrientation(loc.orientation);
ch.mBodyNode->setPosition(loc.position);
ch.mBodyNode->attachObject(ch.mBodyEnt);
ch.mSkeleton = ch.mBodyEnt->getSkeleton();
OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"),
"No root bone");
OgreAssert(ch.mSkeleton->hasBone("Root"),
"No root bone");
ch.mRootBone = ch.mSkeleton->getBone("Root");
OgreAssert(ch.mRootBone, "No root bone");
ch.mBoneMotion = Ogre::Vector3::ZERO;
ch.mBonePrevMotion = Ogre::Vector3::ZERO;
});

View File

@@ -21,8 +21,6 @@ struct CharacterBase {
float mTimer;
Ogre::SceneNode *mBodyNode;
Ogre::Entity *mBodyEnt;
Ogre::Skeleton *mSkeleton;
Ogre::Node *mRootBone;
Ogre::Vector3 mBoneMotion;
Ogre::Vector3 mBonePrevMotion;
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space

View File

@@ -22,7 +22,10 @@
#include "EditorGizmoModule.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
#include "CharacterModule.h"
#include "CharacterManagerModule.h"
#include "items.h"
#include "physics.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
namespace ECS
@@ -263,60 +266,105 @@ struct GUIListener : public Ogre::RenderTargetListener {
"This game does not autosave. Please use save function to keep your state");
ImGui::PopFont();
ImGui::End();
} else if (ECS::get().get<GUI>().enabled) {
if (ECS::get().get<GUI>().narrationBox) {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0,
size.y * 0.75f),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x,
size.y * 0.25f),
ImGuiCond_Always);
ImGui::Begin("Narration...", NULL,
ImGuiWindowFlags_NoTitleBar);
ImGui::PushFont(midFont);
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::TextWrapped(
"%s", ECS::get()
.get<GUI>()
.narrationText.c_str());
if (ECS::get().get<GUI>().choices.size() == 0) {
ImGui::SetCursorScreenPos(p);
if (ImGui::InvisibleButton(
"Background",
ImGui::GetWindowSize()))
ECS::get<LuaBase>().mLua->call_handler(
"narration_progress");
} else {
int i;
for (i = 0; i < ECS::get()
.get<GUI>()
.choices.size();
i++) {
if (ImGui::Button(
ECS::get()
.get<GUI>()
.choices[i]
.c_str())) {
ECS::get()
.get_mut<GUI>()
.narration_answer =
i + 1;
std::cout << "answer: "
<< i + 1
<< std::endl;
ECS::modified<GUI>();
ECS::get<LuaBase>()
.mLua
->call_handler(
"narration_answered");
}
}
}
ImGui::Spacing();
ImGui::PopFont();
ImGui::End();
} else if (ECS::get().get<GUI>().mainMenu) {
} else if (ECS::get<GUI>().narrationHandlers.size() > 0) {
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
ECS::get_mut<GUI>().enabled = true;
ECS::get_mut<GUI>().narrationBox = true;
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, size.y * 0.75f),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x, size.y * 0.25f),
ImGuiCond_Always);
ImGui::Begin("Narration...", NULL,
ImGuiWindowFlags_NoTitleBar);
ImGui::PushFont(midFont);
ImVec2 p = ImGui::GetCursorScreenPos();
GUI::NarrationHandler *narration =
ECS::get<GUI>().narrationHandlers.front();
if (!narration->is_active())
narration->_event("narration_activate");
ImGui::TextWrapped(
"%s", narration->getNarrationText().c_str());
if (narration->getChoices().size() == 0) {
ImGui::SetCursorScreenPos(p);
if (ImGui::InvisibleButton(
"Background",
ImGui::GetWindowSize()))
narration->progress();
} else {
int i;
for (i = 0; i < narration->getChoices().size();
i++) {
if (ImGui::Button(
narration->getChoices()[i]
.c_str())) {
narration->setNarrationAnswer(
i + 1);
std::cout << "answer: " << i + 1
<< std::endl;
}
}
}
if (narration->is_complete()) {
ECS::get_mut<GUI>().enabled = false;
ECS::get_mut<GUI>().narrationBox = false;
ECS::get_mut<GUI>().grab = true;
ECS::get_mut<GUI>().grabChanged = true;
ECS::get_mut<GUI>().removeNarrationHandler(
narration);
delete narration;
}
ImGui::Spacing();
ImGui::PopFont();
ImGui::End();
ECS::modified<GUI>();
} else if (ECS::get().get<GUI>().narrationBox) {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, size.y * 0.75f),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x, size.y * 0.25f),
ImGuiCond_Always);
ImGui::Begin("Narration...", NULL,
ImGuiWindowFlags_NoTitleBar);
ImGui::PushFont(midFont);
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::TextWrapped(
"%s",
ECS::get().get<GUI>().narrationText.c_str());
if (ECS::get().get<GUI>().choices.size() == 0) {
ImGui::SetCursorScreenPos(p);
if (ImGui::InvisibleButton(
"Background",
ImGui::GetWindowSize()))
ECS::get<LuaBase>().mLua->call_handler(
"narration_progress");
} else {
int i;
for (i = 0;
i < ECS::get().get<GUI>().choices.size();
i++) {
if (ImGui::Button(ECS::get()
.get<GUI>()
.choices[i]
.c_str())) {
ECS::get()
.get_mut<GUI>()
.narration_answer =
i + 1;
std::cout << "answer: " << i + 1
<< std::endl;
ECS::modified<GUI>();
ECS::get<LuaBase>().mLua->call_handler(
"narration_answered");
}
}
}
ImGui::Spacing();
ImGui::PopFont();
ImGui::End();
} else if (ECS::get().get<GUI>().enabled) {
if (ECS::get().get<GUI>().mainMenu) {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, 0),
ImGuiCond_Always);
@@ -495,27 +543,103 @@ struct GUIListener : public Ogre::RenderTargetListener {
} else {
ECS::ActionNodeList &list =
ECS::get_mut<ECS::ActionNodeList>();
if (list.nodes.size() > 0) {
Ogre::Vector3 queryPos =
ECS::get<Camera>()
.mCameraNode
->_getDerivedPosition();
std::vector<size_t> points;
list.query(queryPos, points);
for (auto &p : points) {
std::cout << p << std::endl
<< list.nodes[p].props.dump()
<< std::endl;
if (list.dynamicNodes.size() > 0) {
int j;
Ogre::Vector3 queryPos;
std::vector<size_t> points = list.points;
std::vector<float> distances = list.distances;
Ogre::SceneNode *cameraNode =
ECS::get<Camera>().mCameraNode;
Ogre::Vector3 cameraPos =
cameraNode->_getDerivedPosition();
float minDistance = 25.0f;
int i;
list.selected = -1;
for (i = 0; i < points.size(); i++) {
size_t p = points[i];
float distance = distances[i];
float radius = list.dynamicNodes[p]
.props["radius"]
.get<float>();
float height = list.dynamicNodes[p]
.props["height"]
.get<float>();
float actDistance = radius * radius +
height * height;
float textDistance =
actDistance + 2.0f * 2.0f;
Ogre::Vector2 screenPos =
projectToScreen(
list.nodes[p].position);
list.dynamicNodes[p]
.position);
if (screenPos.x < 0)
continue;
std::cout << list.nodes[p].position
<< " " << screenPos
<< std::endl;
ImGui::SetNextWindowPos(ImVec2(
screenPos.x, screenPos.y));
// FIXME: move this to system to filter points there
Ogre::Vector3 hitPosition;
JPH::BodyID hitBody;
bool hit =
JoltPhysicsWrapper::getSingleton()
.raycastQuery(
list.dynamicNodes[p]
.position,
cameraPos,
hitPosition,
hitBody);
if (hit)
continue;
if (list.selected == -1) {
if (distance < actDistance)
list.selected = p;
}
ImDrawList *drawList =
ImGui::GetBackgroundDrawList();
ImVec2 center = ImVec2(screenPos.x,
screenPos.y);
float circleRadius = 8.0f;
ImColor circleColor(
ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
if (p == list.selected) {
circleRadius = 16.0f;
circleColor = ImColor(
ImVec4(0, 0, 1, 1));
}
drawList->AddCircleFilled(center,
circleRadius,
circleColor);
drawList->AddCircle(
center, circleRadius,
IM_COL32(64, 64, 255, 255));
ImVec2 textSize = ImGui::CalcTextSize(
list.dynamicNodes[p]
.action_text.c_str());
ImVec2 textPos = ImVec2(
center.x - (textSize.x * 0.5f),
center.y + circleRadius + 4.0f);
if (distance < textDistance) {
drawList->AddText(
ImVec2(textPos.x + 1,
textPos.y + 1),
IM_COL32(0, 0, 0, 200),
list.dynamicNodes[p]
.action_text
.c_str());
drawList->AddText(
ImVec2(textPos.x,
textPos.y),
IM_COL32(255, 32, 64,
255),
list.dynamicNodes[p]
.action_text
.c_str());
}
#if 0
ImGui::SetNextWindowPos(
ImVec2(screenPos.x,
screenPos.y),
ImGuiCond_Always,
ImVec2(0.5f, 0.5f));
ImGui::Begin(
("SensorLabel##" +
Ogre::StringConverter::toString(
@@ -524,10 +648,27 @@ struct GUIListener : public Ogre::RenderTargetListener {
nullptr,
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoInputs);
ImGui::TextColored(ImVec4(1, 0, 0, 1),
"SENSOR TRIGGERED");
ImDrawList *drawList =
ImGui::GetWindowDrawList();
ImVec2 cursor =
ImGui::GetCursorScreenPos();
drawList->AddCircleFilled(
ImVec2(cursor.x, cursor.y),
16.0f,
IM_COL32(0, 0, 255, 255));
drawList->AddCircle(
ImVec2(cursor.x, cursor.y),
16.0f,
IM_COL32(32, 32, 255, 255));
ImGui::TextColored(
ImVec4(1, 0, 0, 1), "%s",
list.nodes[p]
.action_text.c_str());
ImGui::End();
#endif
}
}
}

View File

@@ -1,5 +1,9 @@
#ifndef __GUIMODULECOMMON_H__
#define __GUIMODULECOMMON_H__
#include <iostream>
#include <Ogre.h>
#include "Components.h"
#include "GameData.h"
namespace ECS
{
@@ -12,6 +16,83 @@ struct GUI {
Ogre::String narrationText;
std::vector<Ogre::String> choices;
int narration_answer;
struct NarrationHandler {
private:
Ogre::String mnarrationText;
std::vector<Ogre::String> mchoices;
int narration_answer;
private:
bool complete;
bool active;
public:
bool is_complete()
{
return complete;
}
bool is_active()
{
return active;
}
const Ogre::String &getNarrationText() const
{
return mnarrationText;
}
const std::vector<Ogre::String> &getChoices() const
{
return mchoices;
}
void progress()
{
_event("narration_progress");
}
void setNarrationAnswer(int answer)
{
narration_answer = answer;
_event("narration_answered");
}
int getNarrationAnswer() const
{
return narration_answer;
}
NarrationHandler(): complete(false), active(false) {}
private:
virtual void finish() = 0;
virtual void activate() = 0;
virtual void event(const Ogre::String &event) = 0;
protected:
void _activate()
{
activate();
active = true;
}
void _finish()
{
finish();
complete = true;
}
void _narration(const Ogre::String &text, const std::vector<Ogre::String> &choices)
{
mnarrationText = text;
mchoices = choices;
}
void _clear_narration()
{
mnarrationText = "";
mchoices.clear();
}
public:
void _event(const Ogre::String &ev)
{
if (!active && !complete)
_activate();
event(ev);
}
virtual ~NarrationHandler() {}
};
static void setWindowGrab(bool g = true)
{
ECS::GUI &gui = ECS::get().get_mut<GUI>();
@@ -30,6 +111,16 @@ struct GUI {
ECS::get().modified<ECS::GUI>();
setWindowGrab(true);
}
std::vector<NarrationHandler *> narrationHandlers;
void addNarrationHandler(struct NarrationHandler *handler)
{
narrationHandlers.push_back(handler);
}
void removeNarrationHandler(struct NarrationHandler *handler)
{
auto it = std::find(narrationHandlers.begin(), narrationHandlers.end(), handler);
narrationHandlers.erase(it);
}
};
}

View File

@@ -190,6 +190,8 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.import <LuaModule>();
// ecs.import <WorldMapModule>();
ecs.import <CharacterAnimationModule>();
ecs.import <PlayerActionModule>();
ecs.add<ActionNodeList>();
ecs.system<EngineData>("UpdateDelta")
.kind(flecs::OnUpdate)

View File

@@ -11,6 +11,7 @@
#include "EventTriggerModule.h"
#include "SlotsModule.h"
#include "world-build.h"
#include "PlayerActionModule.h"
#include "LuaData.h"
#include "luaaa.hpp"
extern "C" {
@@ -299,7 +300,20 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "setup_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TFUNCTION);
ECS::get_mut<PlayerActionModule>().setupLuaActionHandler(L);
ECS::modified<PlayerActionModule>();
return 0;
});
lua_setglobal(L, "setup_action_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
// FIXME
return 0;
});
lua_setglobal(L, "setup_narration_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
int args = lua_gettop(L);
if (args < 1)
return 0;
@@ -846,6 +860,7 @@ LuaModule::LuaModule(flecs::world &ecs)
ecs.module<LuaModule>();
ecs.import <SlotsModule>();
ecs.import <VehicleManagerModule>();
ecs.import <PlayerActionModule>();
ecs.component<LuaChildEventTrigger>();
ecs.component<LuaBase>()
.on_add([](LuaBase &lua) {

View File

@@ -11,7 +11,7 @@ struct LuaData {
lua_State *L;
std::vector<int> setup_handlers;
int setup_handler();
int call_handler(const Ogre::String &event);
int call_handler(const Ogre::String &event);
int call_handler(const Ogre::String &event, flecs::entity e,
flecs::entity o);

View File

@@ -172,14 +172,18 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.event(flecs::OnRemove)
.each([&](flecs::entity e, const JPH::BodyID &id) {
JoltPhysicsWrapper::getSingleton().removeBody(id);
JoltPhysicsWrapper::getSingleton().destroyBody(id);
if (e.has<CharacterBase>() || e.has<Character>())
return;
JoltPhysicsWrapper::getSingleton().destroyBody(id);
std::cout << "body destroyed" << std::endl;
});
ecs.observer<const EngineData, const CharacterBase>("SetupCharacterPh")
.event(flecs::OnSet)
ecs.system<const EngineData, const CharacterBase>("SetupCharacterPh")
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterBody>()
.write<CharacterBody>()
.without<CharacterBody>()
.without<JPH::BodyID>()
.write<CharacterBody>()
.write<JPH::BodyID>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &base) {
CharacterBody &b = e.ensure<CharacterBody>();

View File

@@ -1,8 +1,16 @@
#include <nanoflann.hpp>
#include <vector>
#include <iostream>
#include <flecs.h>
#include <nlohmann/json.hpp>
#include "Components.h"
#include "GameData.h"
#include "CharacterManagerModule.h"
#include "CharacterModule.h"
#include "items.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "LuaData.h"
#include "PlayerActionModule.h"
namespace ECS
@@ -51,13 +59,69 @@ struct ActionNodeList::indexObject {
}
};
struct TestNarrativeHandler : GUI::NarrationHandler {
int count;
TestNarrativeHandler()
: GUI::NarrationHandler()
, count(0)
{
}
void finish() override
{
_clear_narration();
}
void activate() override
{
_narration("Dialogue...", {});
count = 0;
}
void event(const Ogre::String &evt) override
{
if (evt == "narration_progress" ||
evt == "narration_answered") {
count++;
if (count == 1) {
_narration(
"Question..." +
Ogre::StringConverter::toString(
count),
{ "Answer1", "Answer2" });
} else {
_narration(
"Whatever..." +
Ogre::StringConverter::toString(
count),
{});
}
if (count > 5)
_finish();
}
if (evt == "narration_answered")
std::cout << "answer: " << getNarrationAnswer()
<< std::endl;
}
};
struct SimpleWordHandler : PlayerActionModule::ActionWordHandler {
void operator()(flecs::entity town, int index,
const Ogre::String &word) override
{
TestNarrativeHandler *handle = OGRE_NEW TestNarrativeHandler();
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
}
};
PlayerActionModule::PlayerActionModule(flecs::world &ecs)
{
ecs.module<PlayerActionModule>();
ecs.import <CharacterManagerModule>();
ecs.component<ActionNodeList>()
.on_add([](flecs::entity e, ActionNodeList &alist) {
alist.dirty = true;
alist.nodes.reserve(1000);
alist.dynamicNodes.reserve(1000);
alist.selected = -1;
alist.busy = false;
})
.add(flecs::Singleton);
#if 0
@@ -79,25 +143,166 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
}
});
#endif
ecs.system<ActionNodeList>("updateNodeList")
.kind(flecs::OnUpdate)
.each([](ActionNodeList &list) {
if (list.busy)
return;
if (list.nodes.size() > 0) {
Ogre::SceneNode *cameraNode =
ECS::get<Camera>().mCameraNode;
Ogre::Vector3 cameraPos =
cameraNode->_getDerivedPosition();
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (player.is_valid()) {
Ogre::Vector3 playerPos =
player.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
list.query(playerPos, list.points,
list.distances);
} else {
list.query(cameraPos, list.points,
list.distances);
}
}
});
ecs.system<ActionNodeList, const Input>("ActivateActionNode")
.kind(flecs::OnUpdate)
.each([this](ActionNodeList &list, const Input &input) {
if (input.control & 32)
std::cout << "act pressed" << std::endl;
if (list.busy)
return;
if (input.act_pressed && list.selected >= 0) {
std::cout << list.dynamicNodes[list.selected]
.props.dump(4)
<< std::endl;
flecs::entity_t townid =
list.dynamicNodes[list.selected]
.props["town"]
.get<flecs::entity_t>();
flecs::entity town = ECS::get().entity(townid);
int index = list.dynamicNodes[list.selected]
.props["index"]
.get<int>();
for (auto it = actionWords.begin();
it != actionWords.end(); it++) {
if (it->first ==
list.dynamicNodes[list.selected]
.action) {
(*it->second)(
town, index,
list.dynamicNodes
[list.selected]
.action);
list.busy = true;
}
}
}
if (!ECS::get<GUI>().enabled)
list.busy = false;
});
SimpleWordHandler *handler = OGRE_NEW SimpleWordHandler;
addWordHandler("talk", handler);
}
void PlayerActionModule::addWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
actionWords.insert({ word, handler });
}
void PlayerActionModule::removeWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
for (auto it = actionWords.begin(); it != actionWords.end();) {
if (it->first == word && it->second == handler)
it = actionWords.erase(it);
else
it++;
}
}
struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
lua_State *L;
int ref;
void operator()(flecs::entity town, int index,
const Ogre::String &word) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushinteger(L, town.id());
lua_pushinteger(L, index);
lua_pushstring(L, word.c_str());
if (lua_pcall(L, 3, 0, 0)) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
};
void PlayerActionModule::addLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
struct LuaWordHandler *handler = OGRE_NEW LuaWordHandler;
handler->L = L;
handler->ref = ref;
addWordHandler(word, handler);
}
void PlayerActionModule::removeLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
for (auto it = actionWords.begin(); it != actionWords.end();) {
LuaWordHandler *handler =
static_cast<LuaWordHandler *>(it->second);
if (it->first == word && handler->L == L && handler->ref == ref)
it = actionWords.erase(it);
else
it++;
}
}
int PlayerActionModule::setupLuaActionHandler(lua_State *L)
{
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TFUNCTION);
Ogre::String word = lua_tostring(L, 1);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
addLuaWordHandler(word, L, ref);
return 0;
}
void ActionNodeList::build()
{
indexObj = std::make_shared<ActionNodeList::indexObject>(nodes);
dynamicNodes.clear();
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(), nodes.end());
ECS::get().query_builder<const NPCActionNodes>().each(
[&](flecs::entity e, const NPCActionNodes &anodes) {
dynamicNodes.insert(dynamicNodes.end(),
anodes.anodes.begin(),
anodes.anodes.end());
});
indexObj = std::make_shared<ActionNodeList::indexObject>(dynamicNodes);
indexObj->index.buildIndex();
dirty = false;
std::cout << "index built" << std::endl;
}
bool ActionNodeList::query(const Ogre::Vector3 &position,
std::vector<size_t> &points)
std::vector<size_t> &points,
std::vector<float> &distances)
{
if (dirty)
build();
build();
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
points.clear();
points.reserve(4);
distances.clear();
distances.reserve(4);
tmppoints.resize(4);
tmpdistances.resize(4);
nanoflann::KNNResultSet<float> resultSet(4);
@@ -106,9 +311,10 @@ bool ActionNodeList::query(const Ogre::Vector3 &position,
nanoflann::SearchParameters());
int i;
for (i = 0; i < resultSet.size(); i++)
if (tmpdistances[i] < 25.0f)
if (tmpdistances[i] < 25.0f) {
points.push_back(tmppoints[i]);
distances.push_back(tmpdistances[i]);
}
return ret;
}
}

View File

@@ -2,6 +2,7 @@
#define PLAYERACTIONMODULE_H
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <lua.h>
#include <Ogre.h>
namespace ECS {
@@ -13,13 +14,19 @@ struct ActionNodeList {
Ogre::String action_text;
Ogre::Vector3 position;
Ogre::Quaternion rotation;
float height;
float radius;
nlohmann::json props;
};
std::vector<ActionNode> nodes;
std::vector<ActionNode> nodes, dynamicNodes;
std::shared_ptr<indexObject> indexObj;
std::vector<size_t> points;
std::vector<float> distances;
int selected;
bool dirty;
bool busy;
void build();
bool query(const Ogre::Vector3 &position, std::vector<size_t> &points);
bool query(const Ogre::Vector3 &position, std::vector<size_t> &points, std::vector<float> &distances);
int addNode(struct ActionNodeList::ActionNode &node)
{
int index = nodes.size();
@@ -32,10 +39,24 @@ struct ActionNodeList {
nodes.erase(nodes.begin() + index);
}
};
struct NPCActionNodes {
std::vector<ActionNodeList::ActionNode> anodes;
};
struct PlayerActionModule {
struct ActionWordHandler {
virtual void operator()(flecs::entity town, int index,
const Ogre::String &word) = 0;
};
std::multimap<Ogre::String, ActionWordHandler *>
actionWords;
struct PlayerActionModule
{
PlayerActionModule(flecs::world &ecs);
void addWordHandler(const Ogre::String &word, ActionWordHandler *handler);
void removeWordHandler(const Ogre::String &word, ActionWordHandler *handler);
void addLuaWordHandler(const Ogre::String &word, lua_State *L, int ref);
void removeLuaWordHandler(const Ogre::String &word, lua_State *L, int ref);
int setupLuaActionHandler(lua_State *L);
};
}

View File

@@ -18,6 +18,7 @@
#include "PhysicsModule.h"
#include "LuaData.h"
#include "PlayerActionModule.h"
#include "CharacterManagerModule.h"
#include "items.h"
#include "town.h"
@@ -2088,6 +2089,65 @@ void runAllScriptsForTown(flecs::entity e)
j["districts"] = districts;
StaticGeometryModule::setItemProperties(e, j.dump());
}
bool editNPCs(nlohmann::json &npcs)
{
bool changed = false;
ImGui::Text("NPC");
int id = 0;
for (auto &npc : npcs) {
ImGui::Text("%s", npc["lastName"].get<Ogre::String>().c_str());
if (ImGui::SmallButton(
("Spawn##" + Ogre::StringConverter::toString(id))
.c_str())) {
int sex = npc["sex"].get<int>();
const char *models[] = { "normal-male.glb",
"normal-female.glb" };
Ogre::Vector3 npcPosition;
Ogre::Quaternion npcOrientation;
from_json(npc["position"], npcPosition);
from_json(npc["orientation"], npcOrientation);
// FIXME: create TownCharacterManager and register NPCs through there
ECS::get_mut<CharacterManagerModule>()
.createCharacterData(models[sex], npcPosition,
npcOrientation);
}
if (ImGui::SmallButton(
("Delete##" + Ogre::StringConverter::toString(id))
.c_str())) {
npcs.erase(id);
changed = true;
break;
}
id++;
}
ImGui::Text("New NPC");
static char lastName[64] = { 0 };
ImGui::InputText("Last name", lastName, sizeof(lastName));
static char firstName[64] = { 0 };
ImGui::InputText("First name", firstName, sizeof(firstName));
static char tags[256] = { 0 };
ImGui::InputText("Tags", tags, sizeof(firstName));
static int selection = 0;
const char *items[] = { "Male", "Female" };
ImGui::Combo("Sex", &selection, items, 2);
if (ImGui::SmallButton("Add NPC")) {
nlohmann::json npc;
npc["lastName"] = Ogre::String(lastName);
Ogre::Vector3 npcPosition =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
Ogre::Quaternion npcOrientation =
ECS::get<EditorGizmo>()
.sceneNode->_getDerivedOrientation();
to_json(npc["position"], npcPosition);
to_json(npc["orientation"], npcOrientation);
npc["sex"] = selection;
npcs.push_back(npc);
changed = true;
}
ImGui::Separator();
return changed;
}
void createTownPopup(const std::pair<flecs::entity, Ogre::String> item)
{
Ogre::String prop = StaticGeometryModule::getItemProperties(item.first);
@@ -2124,6 +2184,11 @@ void createTownPopup(const std::pair<flecs::entity, Ogre::String> item)
changed = changed || editColorRects(colorRects);
ImGui::Separator();
}
nlohmann::json npcs = nlohmann::json::array();
if (j.find("npcs") != j.end())
npcs = j["npcs"];
changed = changed || editNPCs(npcs);
nlohmann::json districts = nlohmann::json::array();
for (auto &district : j["districts"])
districts.push_back(district);
@@ -2168,6 +2233,7 @@ void createTownPopup(const std::pair<flecs::entity, Ogre::String> item)
ImGui::Separator();
ImGui::Text("%s", j.dump(4).c_str());
if (changed) {
j["npcs"] = npcs;
j["districts"] = districts;
j["colorRects"] = colorRects;
j["lotTemplates"] = lotTemplates;
@@ -5458,6 +5524,7 @@ struct TownDecorateFurniture : TownTask {
.child_of(e)
.set<FurnitureInstance>(
{ node });
#if 0
if (furniture.find(
"sensors") !=
furniture.end()) {
@@ -5599,6 +5666,93 @@ struct TownDecorateFurniture : TownTask {
#endif
}
}
#endif
if (furniture.find(
"actions") !=
furniture.end()) {
for (const auto &
action :
furniture["actions"]) {
std::cout
<< "SENSOR: "
<< action.dump()
<< std::endl;
std::cout
<< furniture
.dump()
<< std::endl;
Ogre::Vector3
actionPosition;
actionPosition
.x =
action["position_x"]
.get<float>();
actionPosition
.y =
action["position_y"]
.get<float>();
actionPosition
.z =
action["position_z"]
.get<float>();
Ogre::Quaternion worldSensorRotation =
worldCenterOrientation *
Ogre::Quaternion(
Ogre::Degree(
90.0f *
(float)rotation),
Ogre::Vector3::
UNIT_Y);
Ogre::Vector3 worldSensorPosition =
worldCenterPosition +
offsetX +
offsetZ +
offsetY +
offset +
worldSensorRotation *
actionPosition;
float height =
action["height"]
.get<float>();
float radius =
action["radius"]
.get<float>();
if (ECS::get()
.has<ActionNodeList>()) {
ActionNodeList::ActionNode
anode;
anode.action =
action["action"]
.get<Ogre::String>();
anode.action_text =
action["action_text"]
.get<Ogre::String>();
anode.radius =
action["radius"]
.get<float>();
anode.height =
action["height"]
.get<float>();
anode.props =
action;
anode.position =
worldSensorPosition;
anode.rotation =
worldSensorRotation;
ECS::get_mut<
ActionNodeList>()
.addNode(
anode);
std::cout
<< "action: "
<< action.dump(
4)
<< std::endl;
ECS::modified<
ActionNodeList>();
}
}
}
}
}
}
@@ -5620,6 +5774,11 @@ struct TownDecorateFurniture : TownTask {
}
};
void registerTownNPCs(flecs::entity e)
{
ECS::get_mut<CharacterManagerModule>().registerTownCharacters(e);
ECS::modified<CharacterManagerModule>();
}
void createTown(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo)
{
@@ -5693,6 +5852,7 @@ void createTown(flecs::entity e, Ogre::SceneNode *sceneNode,
geo->build();
});
});
registerTownNPCs(e);
}
}
}