3#include <Tempest/Painter>
6#include <zenkit/vobs/MovableObject.hh>
25 return Npc::Anim::InteractIn;
29 :
Vob(parent,world,vob,flags) {
31 vobName = vob.vob_name;
33 bbox[0] = {vob.bbox.min.x, vob.bbox.min.y, vob.bbox.min.z};
34 bbox[1] = {vob.bbox.max.x, vob.bbox.max.y, vob.bbox.max.z};
36 focOver = vob.focus_override;
37 showVisual = vob.show_visual;
40 displayOffset = Tempest::Vec3(0,bbox[1].y-p.y,0);
42 if(vob.type != zenkit::VirtualObjectType::oCMOB) {
44 auto& inter =
reinterpret_cast<const zenkit::VInteractiveObject&
>(vob);
45 stateNum = inter.state;
46 triggerTarget = inter.target;
47 useWithItem = inter.item;
48 conditionFunc = inter.condition_function;
49 onStateFunc = inter.on_state_change_function;
50 rewind = inter.rewind;
54 i =
char(std::toupper(i));
56 if(
vobType==zenkit::VirtualObjectType::oCMobDoor) {
57 auto& door =
reinterpret_cast<const zenkit::VDoor&
>(vob);
59 keyInstance = door.key;
60 pickLockStr = door.pick_string;
61 isLockCracked = !door.locked;
65 auto& container =
reinterpret_cast<const zenkit::VContainer&
>(vob);
66 locked = container.locked;
67 keyInstance = container.key;
68 pickLockStr = container.pick_string;
69 isLockCracked = !container.locked;
71 auto items = std::move(container.contents);
90 mdlVisual = vob.visual->name;
92 if(
isLadder() && !mdlVisual.empty()) {
94 size_t at = mdlVisual.size()-1;
95 while(at>0 && !std::isdigit(mdlVisual[at]))
97 while(at>0 && std::isdigit(mdlVisual[at-1]))
99 stepsCount = std::atoi(mdlVisual.c_str()+at);
100 stateNum = stepsCount;
112 fin.
read(vobName,focName,mdlVisual);
113 fin.
read(bbox[0],bbox[1],owner);
114 fin.
read(focOver,showVisual);
116 fin.
read(stateNum,triggerTarget,useWithItem,conditionFunc,onStateFunc);
117 fin.
read(locked,keyInstance,pickLockStr);
118 fin.
read(state,reverseState,loopState,isLockCracked);
122 for(
size_t i=0; i<sz; ++i) {
125 bool attachMode =
false;
126 Phase started = NotStarted;
128 fin.
read(name,user,attachMode,
reinterpret_cast<uint8_t&
>(started));
133 a.attachMode = attachMode;
143 visual.
load(fin, *
this);
151 fout.
write(vobName,focName,mdlVisual);
152 fout.
write(bbox[0],bbox[1],owner);
153 fout.
write(focOver,showVisual);
155 fout.
write(stateNum,triggerTarget,useWithItem,conditionFunc,onStateFunc);
156 fout.
write(locked,keyInstance,pickLockStr);
157 fout.
write(state,reverseState,loopState,isLockCracked);
159 fout.
write(uint32_t(attPos.size()));
160 for(
auto& i:attPos) {
161 fout.
write(i.name,i.user,i.attachMode,uint8_t(i.started));
170 visual.
save(fout,*
this);
175 if(i.user!=
nullptr && i.user->interactive()!=
this)
183 if(i.user!=
nullptr && i.user->isPlayer())
194void Interactive::setVisual(
const zenkit::VirtualObject& vob) {
200 attPos.resize(mesh->pos.size());
201 for(
size_t i=0;i<attPos.size();++i){
202 attPos[i].name = mesh->pos[i].name;
203 attPos[i].pos = mesh->pos[i].transform;
204 attPos[i].node = mesh->pos[i].node;
227 for(
auto& i:attPos) {
228 if(i.user!=
nullptr) {
236 const int destSt = -1;
237 for(
auto& i:attPos) {
238 if(destSt!=state && (i.started==Quit || rewind)) {
242 i.started = NotStarted;
248 if(p->user==
nullptr && (state==-1 && !p->attachMode))
250 if(p->user==
nullptr && (state==stateNum && p->attachMode))
253 if(
isLadder() && p->started==Started && p->user!=
nullptr && p->user->isAiQueueEmpty())
263 for(
auto& i:attPos) {
264 if(i.user!=
nullptr) {
271 auto& npc = *p->user;
272 assert(npc.isPlayer());
275 npc.setInteraction(
nullptr);
288void Interactive::implTick(Pos& p) {
292 static bool dbg =
false;
294 if(dbg && !p.user->isPlayer() && p.user->handle().id!=kId)
298 const bool attach = (p.attachMode^reverseState);
300 if(p.started==NotStarted) {
301 const bool omit = (!
isLadder() && reverseState && state>0);
312 p.attachMode =
false;
317 setState(std::min(stateNum,state+1));
else
318 setState(std::max(0,state-1));
324 if(p.started==Quit) {
328 p.started = NotStarted;
333 if(stateNum==state &&
attach) {
334 invokeStateFunc(npc);
337 if(0==state && !p.attachMode) {
338 invokeStateFunc(npc);
356 if(state==stateNum &&
attach)
360 if(!setAnim(&npc, dir))
364 if(state==0 && p.attachMode) {
369 if(state==stateNum && p.attachMode && reverseState) {
375 invokeStateFunc(npc);
378 const int prev = state;
380 setState(std::min(stateNum,state+1));
else
381 setState(std::max(0,state-1));
382 loopState = (prev==state);
385void Interactive::implQuitInteract(Interactive::Pos &p) {
420 return p+displayOffset;
438 return s->get_string();
461void Interactive::invokeStateFunc(
Npc& npc) {
462 if(onStateFunc.empty() || state<0)
469 auto& sc = npc.
world().script();
470 sc.useInteractive(npc.
handlePtr(), func);
474 if(triggerTarget.empty())
476 const TriggerEvent evt(triggerTarget,vobName,waitAnim,type);
488 Tempest::Log::i(
"unable to recognize mobsi{",focName,
", ",mdlVisual,
"}");
494 if(i.user!=
nullptr) {
501 return vobType==zenkit::VirtualObjectType::oCMobContainer;
505 return vobType==zenkit::VirtualObjectType::oCMobDoor;
511 for(
int i=1; i<=stateNum; ++i)
518 return vobType==zenkit::VirtualObjectType::oCMobLadder;
523 if(keyInst!=
size_t(-1) && pl.
inventory().itemCount(keyInst)>0)
525 return !(pickLockStr.empty() || isLockCracked);
543 auto pos = nodePosition(npc,i);
549 if(attPos.size()==0){
558 if(
auto p = findNearest(to))
563template<
class P,
class Inter>
564P* Interactive::findNearest(Inter& in,
const Npc& to) {
567 for(
auto& i:in.attPos) {
568 if(i.user || !i.isAttachPoint())
570 float d = in.qDistTo(to,i);
571 if(d<dist || p==
nullptr) {
579const Interactive::Pos* Interactive::findNearest(
const Npc& to)
const {
580 return findNearest<const Interactive::Pos, const Interactive>(*
this,to);
583Interactive::Pos* Interactive::findNearest(
const Npc& to) {
584 return findNearest<Interactive::Pos, Interactive>(*
this,to);
587void Interactive::implAddItem(std::string_view name) {
588 size_t sep = name.find(
':');
589 if(sep!=std::string::npos) {
590 auto itm = name.substr(0,sep);
591 long count = std::strtol(name.data()+sep+1,
nullptr,10);
599void Interactive::autoDetachNpc() {
601 if(i.user!=nullptr) {
602 if(!i.user->world().isInDialog())
603 i.user->setInteraction(
nullptr);
607bool Interactive::checkUseConditions(
Npc& npc) {
608 const bool isPlayer = npc.
isPlayer();
614 const size_t ItKE_lockpick = sc.lockPickId();
615 const size_t lockPickCnt = npc.
inventory().itemCount(ItKE_lockpick);
618 const size_t keyInst = keyInstance.empty() ? size_t(-1) : sc.findSymbolIndex(keyInstance);
619 const bool needToPicklock = (pickLockStr.size()>0);
621 if(keyInst!=
size_t(-1) && (isLockCracked || npc.
itemCount(keyInst)>0)) {
622 isLockCracked =
true;
625 if(needToPicklock && (isLockCracked || canLockPick)) {
629 if(keyInst!=
size_t(-1) && needToPicklock) {
630 sc.printMobMissingKeyOrLockpick(npc);
633 else if(keyInst!=
size_t(-1)) {
634 sc.printMobMissingKey(npc);
637 else if(needToPicklock) {
638 sc.printMobMissingLockpick(npc);
642 if(!conditionFunc.empty()) {
643 const int check = sc.invokeCond(npc,conditionFunc);
648 if(!useWithItem.empty()) {
649 size_t it = sc.findSymbolIndex(useWithItem);
650 if(it!=
size_t(-1) && npc.
itemCount(it)==0) {
651 sc.printMobMissingItem(npc);
659const Interactive::Pos *Interactive::findFreePos()
const {
661 if(i.user==nullptr && i.isAttachPoint()) {
667Interactive::Pos *Interactive::findFreePos() {
669 if(i.user==nullptr && i.isAttachPoint()) {
675Tempest::Vec3 Interactive::worldPos(
const Interactive::Pos &to)
const {
678 return Tempest::Vec3();
681 auto pos = mesh->mapToRoot(to.node);
684 Tempest::Vec3 ret = {};
693 return findFreePos()!=
nullptr;
698 if(i.user!=
nullptr) {
699 if(i.attachMode && i.started==Started)
709 return !(i.attachMode ^ reverseState);
710 return !reverseState;
722 anim =
string_frm(
"T_",scheme,
"_S",state,
"_2_STAND");
else
723 anim =
string_frm(
"T_",scheme,
"_",pos,
"_S",state,
"_2_STAND");
728 return state==stateNum && reverseState;
732 assert(to.user==
nullptr);
738 const Tempest::Vec3 mv = {x,y-npc.
translateY(),z};
742 auto& sc = npc.
world().script();
743 sc.printMobTooFar(npc);
749 if(!checkUseConditions(npc))
752 if(!useWithItem.empty()) {
762 if(
vobType==zenkit::VirtualObjectType::oCMobLadder) {
770 reverseState = (state>0);
772 reverseState =
false;
777 to.started = NotStarted;
778 to.attachMode =
true;
788 if(npc.
isPlayer() && !attPos.empty())
793 auto p = findNearest(npc);
797 if(npc.
isPlayer() && !attPos.empty())
803 for(
auto& i:attPos) {
804 if(i.user==&npc && i.attachMode) {
812 i.started = (
canQuitAtState(npc,state) || !quick) ? NotStarted : Quit;
813 i.attachMode =
false;
819 i.attachMode =
false;
837bool Interactive::setPos(
Npc &npc,
const Tempest::Vec3& pos) {
848void Interactive::setDir(
Npc &npc,
const Tempest::Matrix4x4 &mat) {
849 float x0=0,y0=0,z0=0;
850 float x1=0,y1=0,z1=1;
852 mat.project(x0,y0,z0);
853 mat.project(x1,y1,z1);
858float Interactive::qDistTo(
const Npc &npc,
const Interactive::Pos &to)
const {
859 auto p = worldPos(to);
866 return Tempest::Matrix4x4();
868 auto nodeId = mesh->findNode(p.name);
871 Tempest::Matrix4x4 npos;
872 if(nodeId!=
size_t(-1)) {
873 npos = visual.
bone(nodeId);
877 float nodeX = npos.at(3,0) - pos.x;
878 float nodeY = npos.at(3,1) - pos.y;
879 float nodeZ = npos.at(3,2) - pos.z;
880 float dist = std::sqrt(nodeX*nodeX + nodeZ*nodeZ);
882 float npcX = npc.
position().x - pos.x;
883 float npcZ = npc.
position().z - pos.z;
884 float npcA = 180.f*std::atan2(npcZ,npcX)/float(M_PI);
887 npos.rotateOY(-npcA);
888 npos.translate(dist,nodeY,0);
891 float x = pos.x+npos.at(3,0);
892 float y = pos.y+npos.at(3,1);
893 float z = pos.z+npos.at(3,2);
900 if(nodeId!=
size_t(-1))
901 return visual.
bone(nodeId);
906Tempest::Vec3 Interactive::nodePosition(
const Npc &npc,
const Pos &p)
const {
908 float x = mat.at(3,0);
909 float y = mat.at(3,1);
910 float z = mat.at(3,2);
916 if(mesh==
nullptr || mesh->skeleton==
nullptr)
917 return Tempest::Matrix4x4();
919 auto id = mesh->skeleton->findNode(nodeName);
922 ret = visual.
bone(
id);
928 int st[] = {state,state+dir};
931 st[1] = std::max(0,std::min(st[1],stateNum));
934 for(
int i=0;i<2;++i) {
936 std::snprintf(ss[i],
sizeof(ss[i]),
"S0");
else
937 std::snprintf(ss[i],
sizeof(ss[i]),
"S%d",st[i]);
940 if(st[0]<0 || st[1]<0)
941 std::snprintf(buf,
sizeof(buf),
"S_S0");
else
943 std::snprintf(buf,
sizeof(buf),
"S_%s",ss[0]);
else
944 std::snprintf(buf,
sizeof(buf),
"T_%s_2_%s",ss[0],ss[1]);
949bool Interactive::setAnim(
Npc* npc, Anim dir) {
957 if(sqNpc==
nullptr && !(
vobType==zenkit::VirtualObjectType::oCMobInter && dir==
Anim::Out))
960 sqMob = setAnim(dir);
961 if(sqMob==
nullptr && sqNpc==
nullptr && dir!=
Anim::Out)
964 uint64_t aniT = sqNpc==
nullptr ? 0 : uint64_t(sqNpc->
totalTime());
970 aniT = sqMob==
nullptr ? 0 : uint64_t(sqMob->
totalTime());
978void Interactive::setState(
int st) {
985 int st[] = {state,state+t};
992 st[1] = state<1 ? 0 : stateNum - 1;
995 st[0] = state<1 ? 0 : state;
1000 if(i.user!=
nullptr) {
1004 st[1] = std::max(-1,std::min(st[1],stateNum));
1006 for(
int i=0;i<2;++i) {
1008 ss[i] =
"STAND";
else
1013 for(
auto pt:{std::string_view(point),std::string_view()}) {
1017 if(
auto ret = solver.
solveFrm(buf))
1024 p.
setBrush(Tempest::Color(1.0,0,0,1));
1026 for(
auto& m:attPos) {
1027 auto pos = worldPos(m);
1032 p.
mvp.project(x,y,z);
1034 x = (0.5f*x+0.5f)*
float(p.
w);
1035 y = (0.5f*y+0.5f)*
float(p.
h);
1037 p.
painter.drawRect(
int(x),int(y),1,1);
1041 if(attPos.size()==0) {
1047 p.
mvp.project(x,y,z);
1049 x = (0.5f*x+0.5f)*
float(p.
w);
1050 y = (0.5f*y+0.5f)*
float(p.
h);
1052 p.
painter.drawRect(
int(x),int(y),1,1);
1063 float x = bbox[1].x-bbox[0].x;
1064 float y = bbox[1].y-bbox[0].y;
1065 float z = bbox[1].z-bbox[0].z;
1067 return std::max(x,std::max(y,z));
1070std::string_view Interactive::Pos::posTag()
const {
1071 if(name.rfind(
"_FRONT")==name.size()-6)
1073 if(name.rfind(
"_BACK")==name.size()-5)
1078bool Interactive::Pos::isAttachPoint()
const {
1084 return name.find(
"ZS_POS")==0;
1087bool Interactive::Pos::isDistPos()
const {
1088 return name.rfind(
"_DIST")==name.size()-5;
const Animation::Sequence * solveFrm(std::string_view format) const
void setBrush(const Tempest::Brush &brush)
void drawText(int x, int y, std::string_view txt)
Tempest::Painter & painter
const Tempest::Matrix4x4 mvp
size_t findSymbolIndex(std::string_view s)
void printMobAnotherIsUsing(Npc &npc)
void fixNpcPosition(Npc &npc, float angle0, float distBias)
zenkit::DaedalusSymbol * findSymbol(std::string_view s)
BodyState schemeToBodystate(std::string_view sc)
auto version() const -> const VersionInfo &
bool setMobState(std::string_view scheme, int32_t st) override
Interactive(Vob *parent, World &world, const zenkit::VMovableObject &vob, Flags flags)
bool canSeeNpc(const Npc &npc, bool freeLos) const
float extendedSearchRadius() const override
std::string_view tag() const
bool checkMobName(std::string_view dest) const
std::string_view ownerName() const
Tempest::Vec3 nearestPoint(const Npc &to) const
std::string_view posSchemeName() const
Tempest::Matrix4x4 nodeTranform(std::string_view nodeName) const
bool canQuitAtState(const Npc &npc, int32_t state) const
bool isDetachState(const Npc &npc) const
virtual void onStateChanged()
void resetPositionToTA(int32_t state)
bool overrideFocus() const
void setSlotItem(MeshObjects::Mesh &&itm, std::string_view slot)
bool isTrueDoor(const Npc &npc) const
void emitSoundEffect(std::string_view sound, float range, bool freeSlot)
void onKeyInput(KeyCodec::Action act)
void emitTriggerEvent(TriggerEvent::Type type) const
bool detach(Npc &npc, bool quick)
uint32_t stateMask() const
std::string_view schemeName() const
auto animNpc(const AnimationSolver &solver, Anim t) const -> const Animation::Sequence *
bool isStaticState() const
std::string_view focusName() const
Tempest::Vec3 displayPosition() const
void load(Serialize &fin) override
bool isAttached(const Npc &to)
void save(Serialize &fout) const override
void updateAnimation(uint64_t dt)
std::string_view displayName() const
void moveEvent() override
bool needToLockpick(const Npc &pl) const
auto bBox() const -> const Tempest::Vec3 *
void marchInteractives(DbgPainter &p) const
void clear(GameScript &vm, Npc &owner, bool includeMissionItm=false)
Item * addItem(std::unique_ptr< Item > &&p)
void load(Serialize &s, Npc &owner)
void save(Serialize &s) const
float qDistTo(const Tempest::Vec3 pos) const
const std::shared_ptr< zenkit::INpc > & handlePtr() const
bool canSeeNpc(const Npc &oth, bool freeLos) const
size_t itemCount(size_t id) const
void setDirection(const Tempest::Vec3 &pos)
int32_t talentSkill(Talent t) const
bool setPosition(float x, float y, float z)
bool hasAnim(std::string_view scheme) const
auto setAnimAngGet(Anim a) -> const Animation::Sequence *
auto inventory() const -> const Inventory &
auto interactive() const -> Interactive *
auto position() const -> Tempest::Vec3
void setCurrentItem(size_t item)
bool hasCollision() const
void setObjMatrix(const Tempest::Matrix4x4 &obj)
void save(Serialize &fout, const Interactive &mob) const
void load(Serialize &fin, Interactive &mob)
void setSlotItem(MeshObjects::Mesh &&itm, std::string_view slot)
bool updateAnimation(Npc *npc, Interactive *mobsi, World &world, uint64_t dt, bool force)
bool isAnimExist(std::string_view name) const
void setVisual(const zenkit::IItem &visual, World &world, bool staticDraw)
void setInteractive(Interactive *it)
const ProtoMesh * protoMesh() const
const Animation::Sequence * startAnimAndGet(std::string_view name, uint64_t tickCount, bool force=false)
void processLayers(World &world)
const Tempest::Matrix4x4 & bone(size_t i) const
void write(const Arg &... a)
bool setEntry(const Args &... args)
std::string_view worldName() const
virtual void save(Serialize &fout) const
virtual bool setMobState(std::string_view scheme, int32_t st)
zenkit::VirtualObjectType vobType
static std::unique_ptr< Vob > load(Vob *parent, World &world, const zenkit::VirtualObject &vob, Flags flags)
auto transform() const -> const Tempest::Matrix4x4 &
Tempest::Vec3 position() const
void triggerEvent(const TriggerEvent &e)
uint64_t tickCount() const
GameScript & script() const
void addInteractive(Interactive *inter)
static Npc::Anim toNpcAnim(Interactive::Anim dir)