OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
effect.cpp
Go to the documentation of this file.
1#include "effect.h"
2
3#include <Tempest/Log>
4
6#include "world/objects/npc.h"
7#include "world/world.h"
8#include "visualfx.h"
9#include "gothic.h"
10
11using namespace Tempest;
12
13Effect::Effect(PfxEmitter&& pfx, std::string_view node)
14 :pfx(std::move(pfx)), nodeSlot(node) {
15 pos.identity();
16 }
17
18Effect::Effect(const VisualFx& vfx, World& owner, const Npc& src, SpellFxKey key)
19 :Effect(vfx, owner, src.position(), key) {
20 }
21
22Effect::Effect(const VisualFx& v, World& owner, const Vec3& inPos, SpellFxKey k) {
23 root = &v;
24 nodeSlot = root->emTrjOriginNode;
25 pos.identity();
26 pos.translate(inPos);
27 setKey(owner,k);
28 }
29
32 sfx.setLooping(false);
33 }
34
35void Effect::setupLight(World& owner) {
36 auto* lightPresetName = &root->lightPresetName;
37 if(key!=nullptr && !key->lightPresetName.empty())
38 lightPresetName = &key->lightPresetName;
39
40 if(lightPresetName->empty()) {
41 light = LightGroup::Light();
42 return;
43 }
44
45 Vec3 pos3 = {pos.at(3,0),pos.at(3,1),pos.at(3,2)};
46 light = owner.addLight(*lightPresetName);
47 light.setPosition(pos3);
48 if(key!=nullptr && key->lightRange>0)
49 light.setRange(key->lightRange);
50 }
51
52void Effect::setupPfx(World& owner) {
53 if(root->visName_S=="time.slw" ||
54 root->visName_S=="morph.fov" ||
55 root->visName_S=="screenblend.scx" ||
56 root->visName_S=="earthquake.eqk") {
57 uint64_t emFXLifeSpan = root->emFXLifeSpan;
58 if(key!=nullptr && key->emFXLifeSpan!=0)
59 emFXLifeSpan = key->emFXLifeSpan;
60 gfx = owner.addGlobalEffect(root->visName_S,emFXLifeSpan,root->userString, zenkit::IEffectBase::user_string_count);
61 return;
62 }
63
64 const ParticleFx* pfxDecl = Gothic::inst().loadParticleFx(root->visName_S);
65 if(key!=nullptr && key->visName!=nullptr)
66 pfxDecl = key->visName;
67
68 pfxDecl = Gothic::inst().loadParticleFx(pfxDecl,key);
69 if(pfxDecl==nullptr)
70 return;
71
72 pfx = PfxEmitter(owner,pfxDecl);
73 if(root->isMeshEmmiter())
74 pfx.setMesh(meshEmitter,pose);
75 pfx.setActive(active);
76 pfx.setLooped(looped);
77 }
78
79void Effect::setupSfx(World& owner) {
80 auto sfxID = root->sfxID;
81 auto sfxIsAmbient = root->sfxIsAmbient;
82 if(key!=nullptr && !key->sfxID.empty()) {
83 sfxID = key->sfxID;
84 sfxIsAmbient = key->sfxIsAmbient;
85 }
86
87 if(!sfxID.empty()) {
88 Vec3 pos3 = {pos.at(3,0),pos.at(3,1),pos.at(3,2)};
89 sfx = ::Sound(owner,::Sound::T_Regular,sfxID,pos3,2500.f,false);
90 sfx.setAmbient(sfxIsAmbient);
91 sfx.play();
92 }
93 }
94
95bool Effect::is(const VisualFx& vfx) const {
96 return root==&vfx;
97 }
98
99void Effect::tick(uint64_t dt) {
100 if(next!=nullptr)
101 next->tick(dt);
102
103 if(root==nullptr)
104 return;
105
106 Vec3 vel = root->emSelfRotVel;
107 if(key!=nullptr)
108 vel = key->emSelfRotVel.value_or(vel);
109
110 selfRotation += (vel*float(dt)/1000.f);
111 if(vel!=Vec3())
112 syncAttachesSingle(pos);
113 }
114
115void Effect::setActive(bool e) {
116 active = e;
117 if(next!=nullptr)
118 next->setActive(e);
119 pfx.setActive(e);
120 sfx.setActive(e);
121 }
122
123void Effect::setLooped(bool l) {
124 if(looped==l)
125 return;
126 looped = l;
127 if(next!=nullptr)
128 next->setLooped(l);
129 pfx.setLooped(l);
130 }
131
132void Effect::setTarget(const Npc* tg) {
133 if(next!=nullptr)
134 next->setTarget(tg);
135 target = tg;
136 pfx.setTarget(tg);
137 syncAttachesSingle(pos);
138 }
139
141 if(next!=nullptr)
142 next->setOrigin(npc);
143 origin = npc;
144 syncAttachesSingle(pos);
145 setupCollision(npc->world());
146 }
147
148void Effect::setObjMatrix(const Tempest::Matrix4x4& mt) {
149 syncAttaches(mt);
150 }
151
152void Effect::syncAttaches(const Matrix4x4& inPos) {
153 if(next!=nullptr)
154 next->syncAttaches(inPos);
155 syncAttachesSingle(inPos);
156 }
157
158void Effect::syncAttachesSingle(const Matrix4x4& inPos) {
159 pos = inPos;
160
161 auto emTrjMode = VisualFx::TrajectoryNone;
162 float emTrjTargetElev = 0;
163 Vec3 emSelfRotVel;
164
165 if(root!=nullptr) {
166 emTrjMode = root->emTrjMode;
167 emSelfRotVel = root->emSelfRotVel;
168 emTrjTargetElev = root->emTrjTargetElev;
169 if(key!=nullptr) {
170 if(key->emTrjMode.has_value())
171 emTrjMode = key->emTrjMode.value();
172 if(key->emSelfRotVel.has_value())
173 emSelfRotVel = key->emSelfRotVel.value();
174 }
175 }
176
177 auto p = inPos;
178 if((emTrjMode & VisualFx::Trajectory::Target)==VisualFx::Trajectory::Target && target!=nullptr) {
179 // NOTE: needed for shrink-spell, light-spell
180 p.identity();
181 p.translate(target->mapBone(nodeSlot));
182 }
183 else if(true || emTrjMode != VisualFx::Trajectory::TrajectoryNone) {
184 if(pose!=nullptr && boneId<pose->boneCount())
185 p = pose->bone(boneId);
186 else if(target!=nullptr)
187 p = target->transform();
188 }
189 else {
190 p = inPos;
191 }
192
193 if(selfRotation!=Vec3() && false) {
194 // FIXME
195 Matrix4x4 m;
196 m.rotateOX(selfRotation.x);
197 m.rotateOY(selfRotation.y);
198 m.rotateOZ(selfRotation.z);
199 p.mul(m);
200 }
201
202 p.set(3,1, p.at(3,1)+emTrjTargetElev);
203 Vec3 pos3 = {p.at(3,0),p.at(3,1),p.at(3,2)};
204 pfx .setObjMatrix(p);
205 light.setPosition(pos3);
206 sfx .setPosition(pos3);
207 }
208
209void Effect::setKey(World& owner, SpellFxKey k, int32_t keyLvl) {
210 if(next!=nullptr)
211 next->setKey(owner,k);
212
213 if(root==nullptr)
214 return;
215
216 key = root->key(k,keyLvl);
217
218 const VisualFx* vfx = root->emFXCreate;
219 if(key!=nullptr && key->emCreateFXID!=nullptr)
220 vfx = key->emCreateFXID;
221
222 if(vfx!=nullptr && !(next!=nullptr && next->is(*vfx))) {
223 Vec3 pos3 = {pos.at(3,0),pos.at(3,1),pos.at(3,2)};
224 auto ex = Effect(*vfx,owner,pos3,k);
225 ex.setActive(active);
226 ex.setLooped(looped);
227 if(pose!=nullptr && skeleton!=nullptr)
228 ex.bindAttaches(*pose,*skeleton);
229 next.reset(new Effect(std::move(ex)));
230 }
231 else if(vfx==nullptr) {
232 next.reset(nullptr);
233 }
234
235 setupPfx (owner);
236 setupLight (owner);
237 setupSfx (owner);
238 setupCollision(owner);
239 syncAttachesSingle(pos);
240 }
241
243 if(next!=nullptr)
244 next->setMesh(mesh);
245 meshEmitter = mesh;
246 if(root!=nullptr && root->isMeshEmmiter())
247 pfx.setMesh(meshEmitter,pose);
248 }
249
251 if(next!=nullptr)
252 next->setBullet(b,owner);
253 bullet = b;
254 setupCollision(owner);
255 }
256
257void Effect::setSpellId(int32_t s, World& owner) {
258 if(next!=nullptr)
259 next->setSpellId(s,owner);
260 splId = s;
261 setupCollision(owner);
262 }
263
265 uint64_t ret = next==nullptr ? 0 : next->effectPrefferedTime();
266
267 ret = std::max(ret, root==nullptr ? 0 : root->effectPrefferedTime());
268 ret = std::max(ret, pfx .effectPrefferedTime());
269 // ret = std::max(ret, sfx .effectPrefferedTime());
270 ret = std::max(ret, gfx .effectPrefferedTime());
271 // ret = std::max(ret, light.effectPrefferedTime());
272 return ret;
273 }
274
275bool Effect::isAlive() const {
276 if(pfx.isAlive())
277 return true;
278 return (next!=nullptr && next->isAlive());
279 }
280
282 noPhysics = true;
283 pfx.setPhysicsDisable();
284 }
285
286void Effect::bindAttaches(const Pose& p, const Skeleton& to) {
287 if(next!=nullptr)
288 next->bindAttaches(p,to);
289
290 skeleton = &to;
291 pose = &p;
292 boneId = to.findNode(nodeSlot);
293 if(boneId==size_t(-1)) {
294 // case: VOB_MAGICBURN
295 boneId = to.findRootNode();
296 }
297 if(root!=nullptr && root->isMeshEmmiter())
298 pfx.setMesh(meshEmitter,pose);
299 }
300
301void Effect::onCollide(World& world, const VisualFx* root, const Vec3& pos, Npc* npc, Npc* other, int32_t splId) {
302 if(root==nullptr)
303 return;
304
305 const VisualFx* vfx = root->emFXCollStat;
306 if(npc!=nullptr)
307 vfx = root->emFXCollDyn;
308
309 // Gothic 1 uses emFXCollDyn->sendAssessMagic as check for PERC_ASSESSMAGIC
310 // Gothic 2 instead introduced new vfx emFXCollDynPerc specific for this purpose
311 const bool g2 = world.version().game==2;
312 const auto sender = g2 ? root->emFXCollDynPerc : vfx;
313 if(sender!=nullptr && sender->sendAssessMagic && npc!=nullptr) {
314 auto oth = other==nullptr ? npc : other;
315 npc->perceptionProcess(*oth,npc,0,PERC_ASSESSMAGIC);
316 }
317
318 if(vfx!=nullptr) {
319 Effect eff(*vfx,world,pos,SpellFxKey::Collide);
320 eff.setSpellId(splId,world);
321 eff.setOrigin(other);
322 eff.setActive(true);
323 if(npc!=nullptr) {
324 npc ->runEffect(std::move(eff));
325 } else {
326 world.runEffect(std::move(eff));
327 }
328 }
329
330 if(npc!=nullptr && root->emFXCollDynPerc!=nullptr) {
331 const VisualFx* vfxDyn = root->emFXCollDynPerc;
332 Effect eff(*vfxDyn,world,pos,SpellFxKey::Collide);
333 eff.setActive(true);
334 npc->runEffect(std::move(eff));
335 }
336 }
337
338void Effect::setupCollision(World& owner) {
339 if(root==nullptr) {
340 pfx.setPhysicsDisable();
341 return;
342 }
343
344 bool emCheckCollision = root->emCheckCollision;
345 if(key!=nullptr)
346 emCheckCollision = key->emCheckCollision;
347 (void)emCheckCollision;
348
349 bool physics = false;
350 if(root->emFXCollDyn!=nullptr || root->emFXCollDynPerc!=nullptr)
351 physics = true;
352 if(emCheckCollision)
353 physics = true;
354 if(noPhysics)
355 physics = false;
356
357 if(!pfx.isEmpty() && physics) {
358 auto vfx = root;
359 auto n = origin;
360 auto sId = splId;
361 auto b = bullet;
362 pfx.setPhysicsEnable(owner,[vfx,n,sId,b](Npc& npc){
363 if(n!=&npc) {
364 auto src = (n!=nullptr ? n : &npc);
365 npc.takeDamage(*src,b,vfx,sId);
366 if(b!=nullptr)
367 b->setFlags(Bullet::Stopped);
368 }
369 });
370 }
371 }
@ Stopped
Definition bullet.h:25
void setMesh(const MeshObjects::Mesh *mesh)
Definition effect.cpp:242
void setActive(bool e)
Definition effect.cpp:115
void setTarget(const Npc *npc)
Definition effect.cpp:132
static void onCollide(World &owner, const VisualFx *root, const Tempest::Vec3 &pos, Npc *npc, Npc *other, int32_t splId)
Definition effect.cpp:301
void tick(uint64_t dt)
Definition effect.cpp:99
void bindAttaches(const Pose &pose, const Skeleton &to)
Definition effect.cpp:286
bool is(const VisualFx &vfx) const
Definition effect.cpp:95
void setOrigin(Npc *npc)
Definition effect.cpp:140
void setKey(World &owner, SpellFxKey key, int32_t keyLvl=0)
Definition effect.cpp:209
bool isAlive() const
Definition effect.cpp:275
uint64_t effectPrefferedTime() const
Definition effect.cpp:264
Effect()=default
void setLooped(bool l)
Definition effect.cpp:123
void setObjMatrix(const Tempest::Matrix4x4 &mt)
Definition effect.cpp:148
~Effect()
Definition effect.cpp:30
void setBullet(Bullet *b, World &owner)
Definition effect.cpp:250
void setPhysicsDisable()
Definition effect.cpp:281
void setSpellId(int32_t splId, World &owner)
Definition effect.cpp:257
auto loadParticleFx(std::string_view name, bool relaxed=false) -> const ParticleFx *
Definition gothic.cpp:387
static Gothic & inst()
Definition gothic.cpp:249
void setRange(float r)
void setPosition(float x, float y, float z)
Definition npc.h:25
auto mapBone(std::string_view bone) const -> Tempest::Vec3
Definition npc.cpp:3382
void takeDamage(Npc &other, const Bullet *b)
Definition npc.cpp:1868
auto transform() const -> Tempest::Matrix4x4
Definition npc.cpp:632
void runEffect(Effect &&e)
Definition npc.cpp:2980
auto world() -> World &
Definition npc.cpp:624
bool perceptionProcess(Npc &pl)
Definition npc.cpp:4023
bool isEmpty() const
Definition pfxemitter.h:28
void setLooped(bool loop)
void setObjMatrix(const Tempest::Matrix4x4 &mt)
bool isAlive() const
void setPhysicsEnable(World &physic, std::function< void(Npc &npc)> cb)
void setMesh(const MeshObjects::Mesh *mesh, const Pose *pose)
void setPhysicsDisable()
void setActive(bool act)
void setTarget(const Npc *tg)
Definition pose.h:16
auto bone(size_t id) const -> const Tempest::Matrix4x4 &
Definition pose.cpp:804
size_t findNode(std::string_view name, size_t def=size_t(-1)) const
Definition skeleton.cpp:52
size_t findRootNode() const
Definition skeleton.cpp:61
Definition sound.h:5
@ T_Regular
Definition sound.h:8
void play()
Definition sound.cpp:126
void setAmbient(bool a)
Definition sound.cpp:116
void setLooping(bool l)
Definition sound.cpp:111
void setActive(bool a)
Definition sound.cpp:121
void setPosition(const Tempest::Vec3 &pos)
Definition sound.cpp:99
std::string sfxID
Definition visualfx.h:90
OptTrajectory emTrjMode
Definition visualfx.h:96
std::string lightPresetName
Definition visualfx.h:88
int sfxIsAmbient
Definition visualfx.h:91
float lightRange
Definition visualfx.h:89
bool emCheckCollision
Definition visualfx.h:98
const VisualFx * emCreateFXID
Definition visualfx.h:92
uint64_t emFXLifeSpan
Definition visualfx.h:99
const ParticleFx * visName
Definition visualfx.h:63
OptVec3 emSelfRotVel
Definition visualfx.h:95
Trajectory emTrjMode
Definition visualfx.h:111
uint64_t emFXLifeSpan
Definition visualfx.h:140
std::string userString[zenkit::IEffectBase::user_string_count]
Definition visualfx.h:147
const VisualFx * emFXCollDynPerc
Definition visualfx.h:137
Tempest::Vec3 emSelfRotVel
Definition visualfx.h:146
std::string sfxID
Definition visualfx.h:149
std::string lightPresetName
Definition visualfx.h:148
const VisualFx * emFXCreate
Definition visualfx.h:128
const VisualFx * emFXCollStat
Definition visualfx.h:135
std::string visName_S
Definition visualfx.h:104
bool isMeshEmmiter() const
Definition visualfx.h:155
std::string emTrjOriginNode
Definition visualfx.h:112
float emTrjTargetElev
Definition visualfx.h:116
bool emCheckCollision
Definition visualfx.h:142
uint64_t effectPrefferedTime() const
Definition visualfx.cpp:141
bool sfxIsAmbient
Definition visualfx.h:150
const VisualFx * emFXCollDyn
Definition visualfx.h:136
const Key * key(SpellFxKey type, int32_t keyLvl=0) const
Definition visualfx.cpp:149
@ TrajectoryNone
Definition visualfx.h:33
Definition world.h:31
auto version() const -> const VersionInfo &
Definition world.cpp:1023
void runEffect(Effect &&e)
Definition world.cpp:234
LightGroup::Light addLight(const zenkit::VLight &vob)
Definition world.cpp:283
GlobalFx addGlobalEffect(std::string_view what, uint64_t len, const std::string *argv, size_t argc)
Definition world.cpp:247
SpellFxKey
Definition constants.h:268
@ PERC_ASSESSMAGIC
Definition constants.h:426