1 /*
2 Copyright (C) 2007, 2010 - Bit-Blot
3
4 This file is part of Aquaria.
5
6 Aquaria is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21 #include "ScriptedEntity.h"
22 #include "DSQ.h"
23 #include "Game.h"
24 #include "Avatar.h"
25 #include "Shot.h"
26
27 bool ScriptedEntity::runningActivation = false;
28
ScriptedEntity(const std::string & scriptName,Vector position,EntityType et)29 ScriptedEntity::ScriptedEntity(const std::string &scriptName, Vector position, EntityType et) : CollideEntity(), Segmented(2, 26)
30 {
31 addType(SCO_SCRIPTED_ENTITY);
32 crushDelay = 0;
33 script = 0;
34 songNoteFunction = songNoteDoneFunction = true;
35 addChild(&pullEmitter, PM_STATIC);
36
37 hair = 0;
38 becomeSolidDelay = false;
39 strandSpacing = 10;
40 animKeyFunc = true;
41 canShotHitFunc = true;
42 //runningActivation = false;
43
44 setEntityType(et);
45 myTimer = 0;
46 layer = LR_ENTITIES;
47 surfaceMoveDir = 1;
48 this->position = position;
49 numSegments = 0;
50 reverseSegments = false;
51 manaBallAmount = 1;
52 this->name = scriptName;
53
54 std::string file;
55 if (!scriptName.empty())
56 {
57 if (scriptName[0]=='@' && dsq->mod.isActive())
58 {
59 file = dsq->mod.getPath() + "scripts/" + scriptName.substr(1, scriptName.size()) + ".lua";
60 this->name = scriptName.substr(1, scriptName.size());
61 }
62 else if (dsq->mod.isActive())
63 {
64 file = dsq->mod.getPath() + "scripts/" + scriptName + ".lua";
65
66 if (!exists(file))
67 {
68 file = "scripts/entities/" + scriptName + ".lua";
69 }
70 }
71 else
72 {
73 file = "scripts/entities/" + scriptName + ".lua";
74 }
75 }
76 script = dsq->scriptInterface.openScript(file);
77 if (!script)
78 {
79 debugLog("Could not load script [" + file + "]");
80 }
81 }
82
setAutoSkeletalUpdate(bool v)83 void ScriptedEntity::setAutoSkeletalUpdate(bool v)
84 {
85 skeletalSprite.ignoreUpdate = !v;
86 }
87
message(const std::string & msg,int v)88 void ScriptedEntity::message(const std::string &msg, int v)
89 {
90 if (script)
91 {
92 if (!script->call("msg", this, msg.c_str(), v))
93 debugLog(name + " : msg : " + script->getLastError());
94 }
95 Entity::message(msg, v);
96 }
97
messageVariadic(lua_State * L,int nparams)98 int ScriptedEntity::messageVariadic(lua_State *L, int nparams)
99 {
100 if (script)
101 {
102 int res = script->callVariadic("msg", L, nparams, this);
103 if (res < 0)
104 luaDebugMsg("msg", script->getLastError());
105 else
106 return res;
107 }
108 return Entity::messageVariadic(L, nparams);
109 }
110
warpSegments()111 void ScriptedEntity::warpSegments()
112 {
113 Segmented::warpSegments(position);
114 }
115
init()116 void ScriptedEntity::init()
117 {
118 if (script)
119 {
120 if (!script->call("init", this))
121 luaDebugMsg("init", script->getLastError());
122 }
123
124 Entity::init();
125 }
126
postInit()127 void ScriptedEntity::postInit()
128 {
129 if (script)
130 {
131 if (!script->call("postInit", this))
132 luaDebugMsg("postInit", script->getLastError());
133 }
134
135 Entity::postInit();
136 }
137
initEmitter(int emit,const std::string & file)138 void ScriptedEntity::initEmitter(int emit, const std::string &file)
139 {
140 if (emitters.size() <= emit)
141 {
142 emitters.resize(emit+1);
143 }
144 if (emitters[emit] != 0)
145 {
146 errorLog("Trying to init emitter being used");
147 return;
148 }
149 emitters[emit] = new ParticleEffect;
150 addChild(emitters[emit], PM_POINTER);
151 emitters[emit]->load(file);
152 }
153
startEmitter(int emit)154 void ScriptedEntity::startEmitter(int emit)
155 {
156 if(emit >= emitters.size())
157 return;
158
159 if (emitters[emit])
160 {
161 emitters[emit]->start();
162 }
163 }
164
stopEmitter(int emit)165 void ScriptedEntity::stopEmitter(int emit)
166 {
167 if(emit >= emitters.size())
168 return;
169
170 if (emitters[emit])
171 {
172 emitters[emit]->stop();
173 }
174 }
175
getEmitter(int emit)176 ParticleEffect *ScriptedEntity::getEmitter(int emit)
177 {
178 return (size_t(emit) < emitters.size()) ? emitters[emit] : NULL;
179 }
180
getNumEmitters() const181 int ScriptedEntity::getNumEmitters() const
182 {
183 return emitters.size();
184 }
185
registerNewPart(RenderObject * r,const std::string & name)186 void ScriptedEntity::registerNewPart(RenderObject *r, const std::string &name)
187 {
188 partMap[name] = r;
189 }
190
initSegments(int numSegments,int minDist,int maxDist,std::string bodyTex,std::string tailTex,int w,int h,float taper,bool reverseSegments)191 void ScriptedEntity::initSegments(int numSegments, int minDist, int maxDist, std::string bodyTex, std::string tailTex, int w, int h, float taper, bool reverseSegments)
192 {
193 this->reverseSegments = reverseSegments;
194 this->numSegments = numSegments;
195 this->minDist = minDist;
196 this->maxDist = maxDist;
197 segments.resize(numSegments);
198 for (int i = segments.size()-1; i >= 0 ; i--)
199 {
200 Quad *q = new Quad;
201 if (i == segments.size()-1)
202 q->setTexture(tailTex);
203 else
204 q->setTexture(bodyTex);
205 q->setWidthHeight(w, h);
206
207 if (i > 0 && i < segments.size()-1 && taper !=0)
208 q->scale = Vector(1.0f-(i*taper), 1-(i*taper));
209 dsq->game->addRenderObject(q, LR_ENTITIES);
210 segments[i] = q;
211 }
212 Segmented::initSegments(position);
213 }
214
setupEntity(const std::string & tex,int lcode)215 void ScriptedEntity::setupEntity(const std::string &tex, int lcode)
216 {
217 setEntityType(ET_NEUTRAL);
218 if (!tex.empty())
219 setTexture(tex);
220
221 updateCull = -1;
222 manaBallAmount = 0;
223 setState(STATE_IDLE);
224
225 this->layer = dsq->getEntityLayerToLayer(lcode);
226 }
227
setupBasicEntity(const std::string & texture,int health,int manaBall,int exp,int money,float collideRadius,int state,int w,int h,int expType,bool hitEntity,int updateCull,int layer)228 void ScriptedEntity::setupBasicEntity(const std::string& texture, int health, int manaBall, int exp, int money, float collideRadius, int state, int w, int h, int expType, bool hitEntity, int updateCull, int layer)
229 {
230 //this->updateCull = updateCull;
231 updateCull = -1;
232
233 if (texture.empty())
234 renderQuad = false;
235 else
236 setTexture(texture);
237 this->health = maxHealth = health;
238 this->collideRadius = collideRadius;
239 setState(state);
240 this->manaBallAmount = manaBall;
241 width = w;
242 height = h;
243
244 setEntityLayer(layer);
245 }
246
setEntityLayer(int lcode)247 void ScriptedEntity::setEntityLayer(int lcode)
248 {
249 this->layer = dsq->getEntityLayerToLayer(lcode);
250 }
251
initStrands(int num,int segs,int dist,int strandSpacing,Vector color)252 void ScriptedEntity::initStrands(int num, int segs, int dist, int strandSpacing, Vector color)
253 {
254 this->strandSpacing = strandSpacing;
255 strands.resize(num);
256 for (int i = 0; i < strands.size(); i++)
257 {
258 strands[i] = new Strand(position, segs, dist);
259 strands[i]->color = color;
260 dsq->game->addRenderObject(strands[i], this->layer);
261 }
262 updateStrands(0);
263 }
264
onAlwaysUpdate(float dt)265 void ScriptedEntity::onAlwaysUpdate(float dt)
266 {
267 Entity::onAlwaysUpdate(dt);
268
269 updateStrands(dt);
270
271 if (!isEntityDead() && getState() != STATE_DEAD && getState() != STATE_DEATHSCENE && isPresent())
272 {
273 if (frozenTimer > 0)
274 {
275 pullEmitter.update(dt);
276
277 doFriction(dt, 50);
278 updateCurrents(dt);
279 updateMovement(dt);
280
281 if (hair)
282 {
283 setHairHeadPosition(position);
284 updateHair(dt);
285 }
286
287 if (skeletalSprite.isLoaded())
288 dsq->game->handleShotCollisionsSkeletal(this);
289 else
290 dsq->game->handleShotCollisions(this);
291 }
292
293 if (isPullable() && !fillGridFromQuad)
294 {
295 bool doCrush = false;
296 crushDelay -= dt;
297 if (crushDelay < 0)
298 {
299 crushDelay = 0.2;
300 doCrush = true;
301 }
302 FOR_ENTITIES(i)
303 {
304 Entity *e = *i;
305 if (e && e != this && e->life == 1 && e->ridingOnEntity != this)
306 {
307 if ((e->position - this->position).isLength2DIn(collideRadius + e->collideRadius))
308 {
309 if (this->isEntityProperty(EP_BLOCKER) && doCrush)
310 {
311 bool doit = !vel.isLength2DIn(64) || (e->position.y > position.y && vel.y > 0);
312 if (doit)
313 {
314 if (e->getEntityType() == ET_ENEMY && e->isDamageTarget(DT_CRUSH))
315 {
316 DamageData d;
317 d.damageType = DT_CRUSH;
318 d.attacker = this;
319 d.damage = 1;
320 if (e->damage(d))
321 {
322 e->sound("RockHit");
323 dsq->spawnParticleEffect("rockhit", e->position, 0, 0);
324 }
325 //e->push(vel, 0.2, 500, 0);
326 Vector add = vel;
327 add.setLength2D(5000*dt);
328 e->vel += add;
329 }
330 }
331 }
332 Vector add = e->position - this->position;
333 add.capLength2D(10000 * dt);
334 e->vel += add;
335 e->doCollisionAvoidance(dt, 3, 1);
336 }
337 }
338 }
339 }
340
341 if (isPullable())
342 {
343 Entity *followEntity = dsq->game->avatar;
344 if (followEntity && dsq->game->avatar->pullTarget == this)
345 {
346 Vector dist = followEntity->position - this->position;
347 if (dist.isLength2DIn(followEntity->collideRadius + collideRadius + 16))
348 {
349 vel = 0;
350 }
351 else if (!dist.isLength2DIn(800))
352 {
353 // break;
354 vel.setZero();
355 dsq->game->avatar->pullTarget->stopPull();
356 dsq->game->avatar->pullTarget = 0;
357 }
358 else if (!dist.isLength2DIn(128))
359 {
360 Vector v = dist;
361 int moveSpeed = 1000;
362 moveSpeed = 4000;
363 v.setLength2D(moveSpeed);
364 vel += v*dt;
365 setMaxSpeed(dsq->game->avatar->getMaxSpeed());
366 }
367 else
368 {
369 if (!vel.isZero())
370 {
371 Vector sub = vel;
372 sub.setLength2D(getMaxSpeed()*maxSpeedLerp.x*dt);
373 vel -= sub;
374 if (vel.isLength2DIn(100))
375 vel = 0;
376 }
377 }
378 doCollisionAvoidance(dt, 2, 0.5);
379 }
380 }
381 }
382 }
383
updateStrands(float dt)384 void ScriptedEntity::updateStrands(float dt)
385 {
386 if (strands.empty()) return;
387 float angle = rotation.z;
388 angle = (PI*(360-(angle-90)))/180.0;
389 //angle = (180*angle)/PI;
390 float sz = (strands.size()/2);
391 for (int i = 0; i < strands.size(); i++)
392 {
393 float diff = (i-sz)*strandSpacing;
394 if (diff < 0)
395 strands[i]->position = position - Vector(sinf(angle)*fabsf(diff), cosf(angle)*fabsf(diff));
396 else
397 strands[i]->position = position + Vector(sinf(angle)*diff, cosf(angle)*diff);
398 if (dt > 0)
399 strands[i]->update(dt);
400 }
401 }
402
destroy()403 void ScriptedEntity::destroy()
404 {
405 CollideEntity::destroy();
406
407 if (script)
408 {
409 dsq->scriptInterface.closeScript(script);
410 script = 0;
411 }
412 }
413
song(SongType songType)414 void ScriptedEntity::song(SongType songType)
415 {
416 if (script)
417 {
418 if (!script->call("song", this, int(songType)))
419 debugLog(name + " : " + script->getLastError());
420 }
421 }
422
shiftWorlds(WorldType lastWorld,WorldType worldType)423 void ScriptedEntity::shiftWorlds(WorldType lastWorld, WorldType worldType)
424 {
425 if (script)
426 {
427 if (!script->call("shiftWorlds", this, int(lastWorld), int(worldType)))
428 debugLog(name + " : " + script->getLastError() + " shiftWorlds");
429 }
430 }
431
startPull()432 void ScriptedEntity::startPull()
433 {
434 Entity::startPull();
435 beforePullMaxSpeed = getMaxSpeed();
436 becomeSolidDelay = false;
437 debugLog("HERE!");
438 if (isEntityProperty(EP_BLOCKER))
439 {
440 fillGridFromQuad = false;
441 dsq->game->reconstructEntityGrid();
442 }
443 pullEmitter.load("Pulled");
444 pullEmitter.start();
445
446 // HACK: move this to the lower level at some point
447
448 if (isEntityProperty(EP_BLOCKER))
449 {
450 FOR_ENTITIES(i)
451 {
452 Entity *e = *i;
453 if (e != this && e->getEntityType() != ET_AVATAR && e->isv(EV_CRAWLING, 1))
454 {
455 if ((e->position - position).isLength2DIn(collideRadius+e->collideRadius+32))
456 {
457 debugLog(e->name + ": is now riding on : " + name);
458 e->ridingOnEntity = this;
459 e->ridingOnEntityOffset = e->position - position;
460 e->ridingOnEntityOffset.setLength2D(collideRadius);
461 }
462 }
463 }
464 }
465 }
466
sporesDropped(const Vector & pos,int type)467 void ScriptedEntity::sporesDropped(const Vector &pos, int type)
468 {
469 if (script)
470 {
471 script->call("sporesDropped", this, pos.x, pos.y, type);
472 }
473 }
474
stopPull()475 void ScriptedEntity::stopPull()
476 {
477 Entity::stopPull();
478 pullEmitter.stop();
479 setMaxSpeed(beforePullMaxSpeed);
480 }
481
onUpdate(float dt)482 void ScriptedEntity::onUpdate(float dt)
483 {
484 BBGE_PROF(ScriptedEntity_onUpdate);
485
486 CollideEntity::onUpdate(dt);
487
488 if (becomeSolidDelay)
489 {
490 if (vel.isLength2DIn(5))
491 {
492 if (!isEntityInside())
493 {
494 becomeSolid();
495 becomeSolidDelay = false;
496 }
497 }
498 }
499
500
501 if (life != 1 || isEntityDead()) return;
502
503
504 if (myTimer > 0)
505 {
506 myTimer -= dt;
507 if (myTimer <= 0)
508 {
509 myTimer = 0;
510 onExitTimer();
511 }
512 }
513
514 if (this->isEntityDead() || this->getState() == STATE_DEATHSCENE || this->getState() == STATE_DEAD)
515 {
516 return;
517 }
518 if (script)
519 {
520 if (!script->call("update", this, dt))
521 debugLog(name + " : update : " + script->getLastError());
522 }
523
524 if (numSegments > 0)
525 {
526 updateSegments(position, reverseSegments);
527 updateAlpha(alpha.x);
528 }
529 }
530
resetTimer(float t)531 void ScriptedEntity::resetTimer(float t)
532 {
533 myTimer = t;
534 }
535
stopTimer()536 void ScriptedEntity::stopTimer()
537 {
538 myTimer = 0;
539 }
540
onExitTimer()541 void ScriptedEntity::onExitTimer()
542 {
543 if (script)
544 {
545 if (!script->call("exitTimer", this))
546 debugLog(this->name + " : " + script->getLastError() + " exitTimer");
547 }
548 }
549
onAnimationKeyPassed(int key)550 void ScriptedEntity::onAnimationKeyPassed(int key)
551 {
552 if (script && animKeyFunc)
553 {
554 if (!script->call("animationKey", this, key))
555 {
556 debugLog(this->name + " : " + script->getLastError() + " animationKey");
557 animKeyFunc = false;
558 }
559 }
560
561 Entity::onAnimationKeyPassed(key);
562 }
563
lightFlare()564 void ScriptedEntity::lightFlare()
565 {
566 if (script && !isEntityDead())
567 {
568 script->call("lightFlare", this);
569 }
570 }
571
canShotHit(const DamageData & d)572 bool ScriptedEntity::canShotHit(const DamageData &d)
573 {
574 bool doDefault = true;
575 if (script && canShotHitFunc)
576 {
577 if (!script->call("canShotHit", this, d.attacker, d.bone, int(d.damageType), d.damage, d.hitPos.x, d.hitPos.y, d.shot, &doDefault))
578 {
579 debugLog(name + ": canShotHit function failed");
580 canShotHitFunc = false;
581 }
582 }
583
584 if (doDefault)
585 {
586 return Entity::canShotHit(d);
587 }
588
589 return false;
590 }
591
damage(const DamageData & d)592 bool ScriptedEntity::damage(const DamageData &d)
593 {
594 if (d.damageType == DT_NONE) return false;
595 bool doDefault = true;
596 if (script)
597 {
598 if (!script->call("damage", this, d.attacker, d.bone, int(d.damageType), d.damage, d.hitPos.x, d.hitPos.y, d.shot, &doDefault))
599 {
600 debugLog(name + ": damage function failed");
601 }
602 }
603
604 if (doDefault)
605 {
606 return Entity::damage(d);
607 }
608
609 return false;
610 }
611
songNote(int note)612 void ScriptedEntity::songNote(int note)
613 {
614 Entity::songNote(note);
615
616 if (script && songNoteFunction)
617 {
618 if (!script->call("songNote", this, note))
619 {
620 songNoteFunction = false;
621 debugLog(this->name + " : " + script->getLastError() + " songNote");
622 }
623 }
624 }
625
songNoteDone(int note,float len)626 void ScriptedEntity::songNoteDone(int note, float len)
627 {
628 Entity::songNoteDone(note, len);
629 if (script && songNoteDoneFunction)
630 {
631 if (!script->call("songNoteDone", this, note, len))
632 {
633 songNoteDoneFunction = false;
634 debugLog(this->name + " : " + script->getLastError() + " songNoteDone");
635 }
636 }
637 }
638
becomeSolid()639 void ScriptedEntity::becomeSolid()
640 {
641 //vel = 0;
642 float oldRot = 0;
643 bool doRot=false;
644 Vector n = dsq->game->getWallNormal(position);
645 if (!n.isZero())
646 {
647 oldRot = rotation.z;
648 rotateToVec(n, 0);
649 doRot = true;
650 }
651 fillGridFromQuad = true;
652 dsq->game->reconstructEntityGrid();
653
654 FOR_ENTITIES(i)
655 {
656 Entity *e = *i;
657 if (e->ridingOnEntity == this)
658 {
659 e->ridingOnEntity = 0;
660 e->moveOutOfWall();
661 // if can't get the rider out of the wall, kill it
662 if (dsq->game->isObstructed(TileVector(e->position)))
663 {
664 e->setState(STATE_DEAD);
665 }
666 }
667 }
668
669 if (doRot)
670 {
671 rotation.z = oldRot;
672 rotateToVec(n, 0.01);
673 }
674 }
675
onHitWall()676 void ScriptedEntity::onHitWall()
677 {
678 if (isEntityProperty(EP_BLOCKER) && !fillGridFromQuad && dsq->game->avatar->pullTarget != this)
679 {
680 becomeSolidDelay = true;
681 }
682
683 if (isEntityProperty(EP_BLOCKER) && !fillGridFromQuad)
684 {
685 Vector n = dsq->game->getWallNormal(position);
686 if (!n.isZero())
687 {
688 rotateToVec(n, 0.2);
689 }
690 }
691
692 CollideEntity::onHitWall();
693
694 if (script)
695 {
696 if (!script->call("hitSurface", this))
697 debugLog(this->name + " : " + script->getLastError() + " hitSurface");
698 }
699 }
700
activate()701 void ScriptedEntity::activate()
702 {
703 if (runningActivation) return;
704 Entity::activate();
705
706 runningActivation = true;
707 if (script)
708 {
709 if (!script->call("activate", this))
710 luaDebugMsg("activate", script->getLastError());
711 }
712 runningActivation = false;
713 }
714
shotHitEntity(Entity * hit,Shot * shot,Bone * bone)715 void ScriptedEntity::shotHitEntity(Entity *hit, Shot *shot, Bone *bone)
716 {
717 Entity::shotHitEntity(hit, shot, bone);
718
719 if (script)
720 {
721 script->call("shotHitEntity", this, hit, shot, bone);
722 }
723 }
724
entityDied(Entity * e)725 void ScriptedEntity::entityDied(Entity *e)
726 {
727 CollideEntity::entityDied(e);
728
729 if (script)
730 {
731 script->call("entityDied", this, e);
732 }
733 }
734
luaDebugMsg(const std::string & func,const std::string & msg)735 void ScriptedEntity::luaDebugMsg(const std::string &func, const std::string &msg)
736 {
737 debugLog("luaScriptError: " + name + " : " + func + " : " + msg);
738 }
739
onDieNormal()740 void ScriptedEntity::onDieNormal()
741 {
742 Entity::onDieNormal();
743 if (script)
744 {
745 script->call("dieNormal", this);
746 }
747 }
748
onDieEaten()749 void ScriptedEntity::onDieEaten()
750 {
751 Entity::onDieEaten();
752 if (script)
753 {
754 script->call("dieEaten", this);
755 }
756 }
757
onEnterState(int action)758 void ScriptedEntity::onEnterState(int action)
759 {
760 CollideEntity::onEnterState(action);
761
762 if (script)
763 {
764 if (!script->call("enterState", this))
765 luaDebugMsg("enterState", script->getLastError());
766 }
767 switch(action)
768 {
769 case STATE_DEAD:
770 if (!isGoingToBeEaten())
771 {
772 doDeathEffects(manaBallAmount);
773 dsq->spawnParticleEffect(deathParticleEffect, position);
774 onDieNormal();
775 }
776 else
777 {
778 // eaten
779 doDeathEffects(0);
780 onDieEaten();
781 }
782 destroySegments(1);
783
784
785 for (int i = 0; i < strands.size(); i++)
786 {
787 strands[i]->safeKill();
788 }
789 strands.clear();
790
791 // BASE ENTITY CLASS WILL HANDLE CLEANING UP HAIR
792 break;
793 }
794 }
795
onExitState(int action)796 void ScriptedEntity::onExitState(int action)
797 {
798
799 if (script)
800 {
801 if (!script->call("exitState", this))
802 luaDebugMsg("exitState", script->getLastError());
803 }
804
805 CollideEntity::onExitState(action);
806 }
807
deathNotify(RenderObject * r)808 void ScriptedEntity::deathNotify(RenderObject *r)
809 {
810 if (script)
811 {
812 if (!script->call("deathNotify", this, r))
813 luaDebugMsg("deathNotify", script->getLastError());
814 }
815 CollideEntity::deathNotify(r);
816 }
817
818