1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2005 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "Scriptable/Scriptable.h"
21
22 #include "strrefs.h"
23 #include "ie_cursors.h"
24 #include "voodooconst.h"
25
26 #include "DialogHandler.h"
27 #include "DisplayMessage.h"
28 #include "Game.h"
29 #include "GameData.h"
30 #include "Projectile.h"
31 #include "Spell.h"
32 #include "Sprite2D.h"
33 #include "Video.h"
34 #include "GameScript/GSUtils.h"
35 #include "GameScript/Matching.h" // MatchActor
36 #include "GUI/GameControl.h"
37 #include "GUI/TextSystem/Font.h"
38 #include "RNG.h"
39 #include "Scriptable/InfoPoint.h"
40
41 #include <cmath>
42
43 namespace GemRB {
44
45 // we start this at a non-zero value to make debugging easier
46 static ieDword globalActorCounter = 10000;
47 static bool startActive = false;
48 static bool third = false;
49 static bool pst_flags = false;
50 static unsigned short ClearActionsID = 133; // same for all games
51
52 /***********************
53 * Scriptable Class *
54 ***********************/
Scriptable(ScriptableType type)55 Scriptable::Scriptable(ScriptableType type)
56 {
57 Type = type;
58 for (int i = 0; i < MAX_SCRIPTS; i++) {
59 Scripts[i] = NULL;
60 }
61 overHeadTextPos.empty();
62 overheadTextDisplaying = 0;
63 timeStartDisplaying = 0;
64
65 scriptName[0] = 0;
66 scriptlevel = 0;
67
68 LastAttacker = 0;
69 LastCommander = 0;
70 LastProtector = 0;
71 LastProtectee = 0;
72 LastTargetedBy = 0;
73 LastHitter = 0;
74 LastHelp = 0;
75 LastTrigger = 0;
76 LastSeen = 0;
77 LastTalker = 0;
78 LastHeard = 0;
79 LastSummoner = 0;
80 LastFollowed = 0;
81 LastMarked = 0;
82 LastMarkedSpell = 0;
83
84 DialogName = 0;
85 CurrentAction = NULL;
86 CurrentActionState = 0;
87 CurrentActionTarget = 0;
88 CurrentActionInterruptable = true;
89 CurrentActionTicks = 0;
90 UnselectableTimer = 0;
91 Ticks = 0;
92 AdjustedTicks = 0;
93 ScriptTicks = 0;
94 IdleTicks = 0;
95 AuraTicks = 100;
96 TriggerCountdown = 0;
97 Dialog[0] = 0;
98
99 globalID = ++globalActorCounter;
100 if (globalActorCounter == 0) {
101 error("Scriptable", "GlobalID overflowed, quitting due to too many actors.");
102 }
103
104 WaitCounter = 0;
105 if (Type == ST_ACTOR) {
106 InternalFlags = IF_VISIBLE | IF_USEDSAVE;
107 if (startActive) {
108 InternalFlags |= IF_ACTIVE;
109 }
110 } else {
111 InternalFlags = IF_ACTIVE | IF_VISIBLE | IF_NOINT;
112 }
113 area = 0;
114 Pos.x = 0;
115 Pos.y = 0;
116
117 LastTarget = 0;
118 LastTargetPersistent = 0;
119 LastSpellOnMe = 0xffffffff;
120 ResetCastingState(NULL);
121 InterruptCasting = false;
122 locals = new Variables();
123 locals->SetType( GEM_VARIABLES_INT );
124 locals->ParseKey( 1 );
125 ClearTriggers();
126 AddTrigger(TriggerEntry(trigger_oncreation));
127
128 startActive = core->HasFeature(GF_START_ACTIVE);
129 third = core->HasFeature(GF_3ED_RULES);
130 pst_flags = core->HasFeature(GF_PST_STATE_FLAGS);
131 }
132
~Scriptable(void)133 Scriptable::~Scriptable(void)
134 {
135 if (CurrentAction) {
136 ReleaseCurrentAction();
137 }
138 ClearActions();
139 for (int i = 0; i < MAX_SCRIPTS; i++) {
140 delete Scripts[i];
141 }
142
143 delete( locals );
144 }
145
SetScriptName(const char * text)146 void Scriptable::SetScriptName(const char* text)
147 {
148 //if (text && text[0]) { //this leaves some uninitialized bytes
149 //lets hope this won't break anything
150 if (text) {
151 strnspccpy( scriptName, text, 32 );
152 }
153 }
154
155 /** Gets the DeathVariable */
GetScriptName(void) const156 const char* Scriptable::GetScriptName(void) const
157 {
158 return scriptName;
159 }
160
SetDialog(const char * resref)161 void Scriptable::SetDialog(const char *resref) {
162 if (gamedata->Exists(resref, IE_DLG_CLASS_ID) ) {
163 strnuprcpy(Dialog, resref, 8);
164 }
165 }
166
GetCurrentArea() const167 Map* Scriptable::GetCurrentArea() const
168 {
169 //this could be NULL, always check it
170 return area;
171 }
172
SetMap(Map * map)173 void Scriptable::SetMap(Map *map)
174 {
175 if (map && (map->GetCurrentArea()!=map)) {
176 //a map always points to itself (if it is a real map)
177 error("Scriptable", "Invalid map set!\n");
178 }
179 area = map;
180 }
181
182 //ai is nonzero if this is an actor currently in the party
183 //if the script level is AI_SCRIPT_LEVEL, then we need to
184 //load an AI script (.bs) instead of (.bcs)
SetScript(const ieResRef aScript,int idx,bool ai)185 void Scriptable::SetScript(const ieResRef aScript, int idx, bool ai)
186 {
187 if (idx >= MAX_SCRIPTS) {
188 error("Scriptable", "Invalid script index!\n");
189 }
190 if (Scripts[idx] && Scripts[idx]->running) {
191 Scripts[idx]->dead = true;
192 } else {
193 delete Scripts[idx];
194 }
195 Scripts[idx] = NULL;
196 // NONE is an 'invalid' script name, seldomly used to reset the slot, which we do above
197 // This check is to prevent flooding of the console
198 if (aScript[0] && stricmp(aScript, "NONE")) {
199 if (idx!=AI_SCRIPT_LEVEL) ai = false;
200 Scripts[idx] = new GameScript( aScript, this, idx, ai );
201 }
202 }
203
SetSpellResRef(ieResRef resref)204 void Scriptable::SetSpellResRef(ieResRef resref) {
205 strnuprcpy(SpellResRef, resref, 8);
206 }
207
SetOverheadText(const String & text,bool display)208 void Scriptable::SetOverheadText(const String& text, bool display)
209 {
210 overHeadTextPos.empty();
211 if (!text.empty()) {
212 OverheadText = text;
213 DisplayOverheadText(display);
214 } else {
215 DisplayOverheadText(false);
216 }
217 }
218
DisplayOverheadText(bool show)219 bool Scriptable::DisplayOverheadText(bool show)
220 {
221 if (show) {
222 overheadTextDisplaying = true;
223 timeStartDisplaying = core->GetGame()->Ticks;
224 return true;
225 } else if (!show && overheadTextDisplaying) {
226 overheadTextDisplaying = false;
227 timeStartDisplaying = 0;
228 return true;
229 }
230 return false;
231 }
232
233 /* 'fix' the current overhead text in the current position */
FixHeadTextPos()234 void Scriptable::FixHeadTextPos()
235 {
236 overHeadTextPos = Pos;
237 }
238
239 #define MAX_DELAY 6000
DrawOverheadText()240 void Scriptable::DrawOverheadText()
241 {
242 if (!overheadTextDisplaying)
243 return;
244
245 unsigned long time = core->GetGame()->Ticks;
246 Font::PrintColors color = {core->InfoTextColor, ColorBlack};
247
248 time -= timeStartDisplaying;
249 if (time >= MAX_DELAY) {
250 DisplayOverheadText(false);
251 return;
252 } else {
253 time = (MAX_DELAY-time)/10;
254 if (time<256) {
255 ieByte time2 = time; // shut up narrowing warnings
256 color.fg = Color(time2, time2, time2, time2);
257 }
258 }
259
260 int cs = 100;
261 if (Type == ST_ACTOR) {
262 cs = ((Selectable *) this)->size*50;
263 }
264
265 Point p = (overHeadTextPos.isempty()) ? Pos : overHeadTextPos;
266 Region vp = core->GetGameControl()->Viewport();
267 Region rgn(p - Point(100, cs) - vp.Origin(), Size(200, 400));
268 core->GetTextFont()->Print(rgn, OverheadText, IE_FONT_ALIGN_CENTER | IE_FONT_ALIGN_TOP, color);
269 }
270
DrawingRegion() const271 Region Scriptable::DrawingRegion() const
272 {
273 return BBox;
274 }
275
ImmediateEvent()276 void Scriptable::ImmediateEvent()
277 {
278 InternalFlags |= IF_FORCEUPDATE;
279 }
280
IsPC() const281 bool Scriptable::IsPC() const
282 {
283 if (Type != ST_ACTOR) return false;
284 return ((const Actor *) this)->GetStat(IE_EA) <= EA_CHARMED;
285 }
286
Update()287 void Scriptable::Update()
288 {
289 Ticks++;
290 AdjustedTicks++;
291 AuraTicks++;
292
293 if (UnselectableTimer) {
294 UnselectableTimer--;
295 if (!UnselectableTimer && Type == ST_ACTOR) {
296 Actor *actor = (Actor *) this;
297 actor->SetCircleSize();
298 if (actor->InParty) {
299 core->GetGame()->SelectActor(actor, true, SELECT_QUIET);
300 core->SetEventFlag(EF_PORTRAIT);
301 }
302 }
303 }
304
305 TickScripting();
306
307 ProcessActions();
308
309 InterruptCasting = false;
310 }
311
TickScripting()312 void Scriptable::TickScripting()
313 {
314 // Stagger script updates.
315 if (Ticks % 16 != globalID % 16)
316 return;
317
318 ieDword actorState = 0;
319 if (Type == ST_ACTOR)
320 actorState = ((Actor *)this)->Modified[IE_STATE_ID];
321
322 // Dead actors only get one chance to run a new script.
323 if ( (InternalFlags& (IF_REALLYDIED|IF_JUSTDIED))==IF_REALLYDIED)
324 return;
325
326 ScriptTicks++;
327
328 // If no action is running, we've had triggers set recently or we haven't checked recently, do a script update.
329 bool needsUpdate = (!CurrentAction) || (TriggerCountdown > 0) || (IdleTicks > 15);
330
331 // Also do a script update if one was forced..
332 if (InternalFlags & IF_FORCEUPDATE) {
333 needsUpdate = true;
334 InternalFlags &= ~IF_FORCEUPDATE;
335 }
336 // TODO: force for all on-screen actors
337
338 // Charmed actors don't get frequent updates.
339 if ((actorState & STATE_CHARMED) && (IdleTicks < 5))
340 needsUpdate = false;
341
342 if (!needsUpdate) {
343 IdleTicks++;
344 return;
345 }
346
347 if (triggers.size())
348 TriggerCountdown = 5;
349 IdleTicks = 0;
350 InternalFlags &= ~IF_JUSTDIED;
351 if (TriggerCountdown > 0)
352 TriggerCountdown--;
353 // TODO: set TriggerCountdown once we have real triggers
354
355 ExecuteScript(MAX_SCRIPTS);
356 }
357
ExecuteScript(int scriptCount)358 void Scriptable::ExecuteScript(int scriptCount)
359 {
360 const GameControl *gc = core->GetGameControl();
361
362 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
363 // but not in bg2 (kill Abazigal in ar6005)
364 if (gc->GetScreenFlags() & SF_CUTSCENE) {
365 if (! (core->HasFeature(GF_CUTSCENE_AREASCRIPTS) && Type == ST_AREA)) {
366 return;
367 }
368 }
369
370 // Don't abort if there is a running non-interruptable action.
371 if ((InternalFlags & IF_NOINT) && (CurrentAction || GetNextAction())) {
372 return;
373 }
374 if (!CurrentActionInterruptable) {
375 // sanity check
376 if (!CurrentAction && !GetNextAction()) {
377 error("Scriptable", "No current action and no next action.\n");
378 }
379 return;
380 }
381
382 bool changed = false;
383
384 Actor *act = NULL;
385 if (Type == ST_ACTOR) {
386 act = (Actor *) this;
387 }
388
389 // don't run if the final dialog action queue is still playing out (we're already out of dialog!)
390 // currently limited with GF_CUTSCENE_AREASCRIPTS and to area scripts, to minimize risk into known test cases
391 if (Type == ST_AREA && !core->HasFeature(GF_CUTSCENE_AREASCRIPTS) && gc->GetDialogueFlags() & DF_POSTPONE_SCRIPTS) {
392 return;
393 }
394
395 // don't run scripts if we're in dialog, regardless of DF_FREEZE_SCRIPTS
396 if ((gc->GetDialogueFlags() & DF_IN_DIALOG) && gc->dialoghandler->InDialog(this) &&
397 (!act || act->Modified[IE_IGNOREDIALOGPAUSE] == 0)) {
398 return;
399 }
400
401 if (act) {
402 // if party AI is disabled, don't run non-override scripts
403 if (act->InParty && !(core->GetGame()->ControlStatus & CS_PARTY_AI))
404 scriptCount = 1;
405 // hardcoded action overrides like charm, confusion, panic and berserking
406 // TODO: check why everything else but charm is handled separately and unify if possible
407 changed |= act->OverrideActions();
408 }
409
410 bool continuing = false, done = false;
411 for (scriptlevel = 0;scriptlevel<scriptCount;scriptlevel++) {
412 GameScript *Script = Scripts[scriptlevel];
413 if (Script) {
414 changed |= Script->Update(&continuing, &done);
415 if (Script->dead) {
416 delete Script;
417 }
418 }
419
420 /* scripts are not concurrent, see WAITPC override script for example */
421 if (done) break;
422 }
423
424 if (changed) {
425 ClearTriggers();
426 }
427
428 if (act) {
429 // if nothing is happening, look around, check if we're bored and so on
430 act->IdleActions(CurrentAction!=NULL);
431 }
432 }
433
AddAction(Action * aC)434 void Scriptable::AddAction(Action* aC)
435 {
436 if (!aC) {
437 Log(WARNING, "Scriptable", "AA: NULL action encountered for %s!", scriptName);
438 return;
439 }
440
441 InternalFlags|=IF_ACTIVE;
442 if (startActive) {
443 InternalFlags &= ~IF_IDLE;
444 }
445 aC->IncRef();
446 if (actionflags[aC->actionID] & AF_SCRIPTLEVEL) {
447 aC->int0Parameter = scriptlevel;
448 }
449
450 // attempt to handle 'instant' actions, from instant.ids, which run immediately
451 // when added if the action queue is empty, even on actors which are Held/etc
452 // FIXME: area check hack until fuzzie fixes scripts here
453 if (!CurrentAction && !GetNextAction() && area) {
454 int instant = AF_SCR_INSTANT;
455 if (core->GetGameControl()->GetDialogueFlags() & DF_IN_DIALOG) {
456 instant = AF_DLG_INSTANT;
457 }
458 if (actionflags[aC->actionID] & instant) {
459 CurrentAction = aC;
460 GameScript::ExecuteAction( this, CurrentAction );
461 return;
462 }
463 }
464
465 actionQueue.push_back( aC );
466 }
467
AddActionInFront(Action * aC)468 void Scriptable::AddActionInFront(Action* aC)
469 {
470 if (!aC) {
471 Log(WARNING, "Scriptable", "AAIF: NULL action encountered for %s!", scriptName);
472 return;
473 }
474 InternalFlags|=IF_ACTIVE;
475 actionQueue.push_front( aC );
476 aC->IncRef();
477 }
478
GetNextAction() const479 Action* Scriptable::GetNextAction() const
480 {
481 if (actionQueue.empty()) return nullptr;
482 return actionQueue.front();
483 }
484
PopNextAction()485 Action* Scriptable::PopNextAction()
486 {
487 if (actionQueue.empty()) return nullptr;
488
489 Action* aC = actionQueue.front();
490 actionQueue.pop_front();
491 return aC;
492 }
493
ClearActions()494 void Scriptable::ClearActions()
495 {
496 // pst sometimes uses clearactions in the middle of a cutscene (eg. 1203cd21)
497 // and expect it to clear only the previous actions, not the whole queue
498 if (pst_flags && CurrentAction && CurrentAction->actionID == ClearActionsID) {
499 ReleaseCurrentAction();
500 } else {
501 ReleaseCurrentAction();
502 for (unsigned int i = 0; i < actionQueue.size(); i++) {
503 Action* aC = actionQueue.front();
504 actionQueue.pop_front();
505 aC->Release();
506 }
507 actionQueue.clear();
508 }
509 WaitCounter = 0;
510 LastTarget = 0;
511 LastTargetPos.empty();
512 // intentionally not resetting LastTargetPersistent
513 LastSpellTarget = 0;
514
515 if (Type == ST_ACTOR) {
516 Interrupt();
517 } else {
518 NoInterrupt();
519 }
520 }
521
Stop()522 void Scriptable::Stop()
523 {
524 ClearActions();
525 }
526
ReleaseCurrentAction()527 void Scriptable::ReleaseCurrentAction()
528 {
529 if (CurrentAction) {
530 CurrentAction->Release();
531 CurrentAction = NULL;
532 }
533
534 CurrentActionState = 0;
535 CurrentActionTarget = 0;
536 CurrentActionInterruptable = true;
537 CurrentActionTicks = 0;
538 }
539
ProcessActions()540 void Scriptable::ProcessActions()
541 {
542 if (WaitCounter) {
543 WaitCounter--;
544 if (WaitCounter) return;
545 }
546
547 int lastAction = -1;
548 while (true) {
549 CurrentActionInterruptable = true;
550 if (!CurrentAction) {
551 if (! (CurrentActionTicks == 0 && CurrentActionState == 0)) {
552 Log(ERROR, "Scriptable", "Last action: %d", lastAction);
553 }
554 assert(CurrentActionTicks == 0 && CurrentActionState == 0);
555 CurrentAction = PopNextAction();
556 } else {
557 CurrentActionTicks++;
558 }
559 if (!CurrentAction) {
560 ClearActions();
561 // clear lastAction here if you'll ever need it after exiting the loop
562 break;
563 }
564 lastAction = CurrentAction->actionID;
565 GameScript::ExecuteAction( this, CurrentAction );
566 //break execution in case of a Wait flag
567 if (WaitCounter) {
568 break;
569 }
570 //break execution in case of blocking action
571 if (CurrentAction) {
572 break;
573 }
574 //break execution in case of movement
575 //we should not actually break here, or else fix waypoints
576 if (InMove()) {
577 break;
578 }
579 }
580 // FIXME
581 /*if (InternalFlags&IF_IDLE) {
582 Deactivate();
583 }*/
584 }
585
InMove() const586 bool Scriptable::InMove() const
587 {
588 if (Type!=ST_ACTOR) {
589 return false;
590 }
591 Movable *me = (Movable *) this;
592 return me->GetStep() != NULL;
593 }
594
SetWait(unsigned long time)595 void Scriptable::SetWait(unsigned long time)
596 {
597 WaitCounter = time;
598 }
599
GetWait() const600 unsigned long Scriptable::GetWait() const
601 {
602 return WaitCounter;
603 }
604
LeftDialog()605 void Scriptable::LeftDialog()
606 {
607 AddTrigger(TriggerEntry(trigger_wasindialog));
608 }
609
Hide()610 void Scriptable::Hide()
611 {
612 InternalFlags &= ~IF_VISIBLE;
613 }
614
Unhide()615 void Scriptable::Unhide()
616 {
617 InternalFlags |= IF_VISIBLE;
618 }
619
Interrupt()620 void Scriptable::Interrupt()
621 {
622 InternalFlags &= ~IF_NOINT;
623 }
624
NoInterrupt()625 void Scriptable::NoInterrupt()
626 {
627 InternalFlags |= IF_NOINT;
628 }
629
630 //also turning off the idle flag so it won't run continuously
Deactivate()631 void Scriptable::Deactivate()
632 {
633 InternalFlags &=~(IF_ACTIVE|IF_IDLE);
634 }
635
636 //turning off the not interruptable flag, actions should reenable it themselves
637 //also turning off the idle flag
638 //heh, no, i wonder why did i touch the interruptable flag here
Activate()639 void Scriptable::Activate()
640 {
641 InternalFlags |= IF_ACTIVE;
642 InternalFlags &= ~IF_IDLE;
643 }
644
PartyRested()645 void Scriptable::PartyRested()
646 {
647 //InternalFlags |=IF_PARTYRESTED;
648 AddTrigger(TriggerEntry(trigger_partyrested));
649 }
650
GetInternalFlag() const651 ieDword Scriptable::GetInternalFlag() const
652 {
653 return InternalFlags;
654 }
655
SetInternalFlag(unsigned int value,int mode)656 void Scriptable::SetInternalFlag(unsigned int value, int mode)
657 {
658 SetBits(InternalFlags, value, mode);
659 }
660
ClearTriggers()661 void Scriptable::ClearTriggers()
662 {
663 triggers.clear();
664 }
665
AddTrigger(TriggerEntry trigger)666 void Scriptable::AddTrigger(TriggerEntry trigger)
667 {
668 triggers.push_back(trigger);
669 ImmediateEvent();
670 SetLastTrigger(trigger.triggerID, trigger.param1);
671 }
672
673 // plenty of triggers in svitrobj don't send trigger messages and so never see the code in AddTrigger
SetLastTrigger(ieDword triggerID,ieDword globalID)674 void Scriptable::SetLastTrigger(ieDword triggerID, ieDword globalID)
675 {
676 assert(triggerID < MAX_TRIGGERS);
677 if (triggerflags[triggerID] & TF_SAVED) {
678 //TODO: if LastTrigger is still overwritten by script action blocks, store this in a separate field and copy it back when the block ends
679 const char *name = "none";
680 if (area) {
681 Scriptable *scr = area->GetScriptableByGlobalID(globalID);
682 if (scr) {
683 name = scr->GetScriptName();
684 }
685 }
686 ScriptDebugLog(ID_TRIGGERS, "Scriptable", "%s: Added LastTrigger: %d (%s) for trigger %d\n", scriptName, globalID, name, triggerID);
687 LastTrigger = globalID;
688 }
689 }
690
MatchTrigger(unsigned short id,ieDword param)691 bool Scriptable::MatchTrigger(unsigned short id, ieDword param) {
692 for (std::list<TriggerEntry>::iterator m = triggers.begin(); m != triggers.end (); ++m) {
693 TriggerEntry &trigger = *m;
694 if (trigger.triggerID != id)
695 continue;
696 if (param && trigger.param1 != param)
697 continue;
698 return true;
699 }
700
701 return false;
702 }
703
MatchTriggerWithObject(unsigned short id,const Object * obj,ieDword param) const704 bool Scriptable::MatchTriggerWithObject(unsigned short id, const Object *obj, ieDword param) const
705 {
706 for (auto& trigger : triggers) {
707 if (trigger.triggerID != id) continue;
708 if (param && trigger.param2 != param) continue;
709 if (!MatchActor(this, trigger.param1, obj)) continue;
710 return true;
711 }
712
713 return false;
714 }
715
GetMatchingTrigger(unsigned short id,unsigned int notflags) const716 const TriggerEntry *Scriptable::GetMatchingTrigger(unsigned short id, unsigned int notflags) const
717 {
718 for (auto& trigger : triggers) {
719 if (trigger.triggerID != id) continue;
720 if (notflags & trigger.flags) continue;
721 return &trigger;
722 }
723
724 return NULL;
725 }
726
CreateProjectile(const ieResRef SpellResRef,ieDword tgt,int level,bool fake)727 void Scriptable::CreateProjectile(const ieResRef SpellResRef, ieDword tgt, int level, bool fake)
728 {
729 Spell* spl = gamedata->GetSpell( SpellResRef );
730 Actor *caster = NULL;
731
732 //PST has a weird effect, called Enoll Eva's duplication
733 //it creates every projectile of the affected actor twice
734 int projectileCount = 1;
735 if (Type == ST_ACTOR) {
736 caster = (Actor *) this;
737 if (spl->Flags&SF_HOSTILE) {
738 caster->CureSanctuary();
739 }
740
741 // check if a wild surge ordered us to replicate the projectile
742 projectileCount = caster->wildSurgeMods.num_castings;
743 if (!projectileCount) {
744 projectileCount = 1;
745 }
746 }
747
748 if (pst_flags && (Type == ST_ACTOR)) {
749 if (caster->GetStat(IE_STATE_ID)&STATE_EE_DUPL) {
750 //seriously, wild surges and EE in the same game?
751 //anyway, it would be too many duplications
752 projectileCount = 2;
753 }
754 }
755
756 while(projectileCount --) {
757 Projectile *pro = NULL;
758 // jump through hoops to skip applying selftargeting spells to the caster
759 // if we'll be changing the target
760 int tct = 0;
761 if (caster) {
762 tct = caster->wildSurgeMods.target_change_type;
763 }
764 if (!caster || !tct || tct == WSTC_ADDTYPE || !caster->wildSurgeMods.projectile_id) {
765 pro = spl->GetProjectile(this, SpellHeader, level, LastTargetPos);
766 if (!pro) {
767 return;
768 }
769 pro->SetCaster(GetGlobalID(), level);
770 }
771
772 Point origin = Pos;
773 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
774 // try and make projectiles start from the right trap position
775 // see the traps in the duergar/assassin battle in bg2 dungeon
776 // see also function below - maybe we should fix Pos instead
777 origin = ((InfoPoint *)this)->TrapLaunch;
778 }
779
780 if (caster) {
781 // check for target (type) change
782 int count, i;
783 Actor *newact = NULL;
784 SPLExtHeader *seh = NULL;
785 Effect *fx = NULL;
786 switch (caster->wildSurgeMods.target_change_type) {
787 case WSTC_SETTYPE:
788 seh = &spl->ext_headers[SpellHeader];
789 for (i=0; i < seh->FeatureCount; i++) {
790 seh->features[i].Target = caster->wildSurgeMods.target_type;
791 }
792 // we need to fetch the projectile, so the effect queue is created
793 // (skipped above)
794 delete pro;
795 pro = spl->GetProjectile(this, SpellHeader, level, LastTargetPos);
796 pro->SetCaster(GetGlobalID(), level);
797 break;
798 case WSTC_ADDTYPE:
799 // TODO: unhardcode to allow for mixing all the target types
800 // caster gets selftargeting fx when the projectile is fetched above
801 seh = &spl->ext_headers[SpellHeader];
802 for (i=0; i < seh->FeatureCount; i++) {
803 if (seh->features[i].Target == FX_TARGET_SELF) {
804 seh->features[i].Target = caster->wildSurgeMods.target_type;
805 } else {
806 // also apply to the caster
807 fx = seh->features+i;
808 core->ApplyEffect(fx, caster, caster);
809 }
810 }
811 // we need to refetch the projectile, so the effect queue is created
812 delete pro; // don't leak the original one
813 pro = spl->GetProjectile(this, SpellHeader, level, LastTargetPos);
814 pro->SetCaster(GetGlobalID(), level);
815 break;
816 case WSTC_RANDOMIZE:
817 count = area->GetActorCount(false);
818 newact = area->GetActor(core->Roll(1,count,-1), false);
819 if (count > 1 && newact == caster) {
820 while (newact == caster) {
821 newact = area->GetActor(core->Roll(1,count,-1), false);
822 }
823 }
824 if (tgt) {
825 LastSpellTarget = newact->GetGlobalID();
826 LastTargetPos = newact->Pos;
827 } else {
828 // no better idea; I wonder if the original randomized point targets at all
829 LastTargetPos = newact->Pos;
830 }
831
832 // make it also work for self-targeting spells:
833 // change the payload or this was all in vain
834 seh = &spl->ext_headers[SpellHeader];
835 for (i=0; i < seh->FeatureCount; i++) {
836 if (seh->features[i].Target == FX_TARGET_SELF) {
837 seh->features[i].Target = FX_TARGET_PRESET;
838 }
839 }
840 // we need to fetch the projectile, so the effect queue is created
841 // (skipped above)
842 delete pro;
843 pro = spl->GetProjectile(this, SpellHeader, level, LastTargetPos);
844 pro->SetCaster(GetGlobalID(), level);
845 break;
846 default: //0 - do nothing
847 break;
848 }
849
850 // apply the saving throw mod
851 if (caster->wildSurgeMods.saving_throw_mod) {
852 seh = &spl->ext_headers[SpellHeader];
853 for (i=0; i < seh->FeatureCount; i++) {
854 seh->features[i].SavingThrowBonus += caster->wildSurgeMods.saving_throw_mod;
855 }
856 }
857
858 // change the projectile
859 if (caster->wildSurgeMods.projectile_id) {
860 spl->ext_headers[SpellHeader].ProjectileAnimation = caster->wildSurgeMods.projectile_id;
861 // make it also work for self-targeting spells:
862 // change the payload or this was all in vain
863 seh = &spl->ext_headers[SpellHeader];
864 for (i=0; i < seh->FeatureCount; i++) {
865 if (seh->features[i].Target == FX_TARGET_SELF) {
866 seh->features[i].Target = FX_TARGET_PRESET;
867 }
868 }
869 // we need to refetch the projectile, so the new one is used
870 delete pro; // don't leak the original one
871 pro = spl->GetProjectile(this, SpellHeader, level, LastTargetPos);
872 pro->SetCaster(GetGlobalID(), level);
873 }
874
875 // check for the speed mod
876 if (caster->wildSurgeMods.projectile_speed_mod) {
877 pro->Speed = (pro->Speed * caster->wildSurgeMods.projectile_speed_mod) / 100;
878 if (!pro->Speed) {
879 pro->Speed = 1;
880 }
881 }
882 }
883
884 if (tgt) {
885 area->AddProjectile(pro, origin, tgt, fake);
886 } else {
887 area->AddProjectile(pro, origin, LastTargetPos);
888 }
889 }
890
891 ieDword spellnum=ResolveSpellNumber( SpellResRef );
892 if (spellnum!=0xffffffff) {
893 area->SeeSpellCast(this, spellnum);
894
895 // spellcasting feedback
896 // iwd2: only display it for party friendly creatures - enemies require a successful spellcraft check
897 if (!third || (caster && caster->GetStat(IE_EA) <= EA_CONTROLLABLE)) {
898 DisplaySpellCastMessage(tgt, spl);
899 }
900 }
901 // only trigger the autopause when in combat or buffing gets very annoying
902 if (core->GetGame()->CombatCounter && caster && caster->IsPartyMember()) {
903 core->Autopause(AP_SPELLCAST, this);
904 }
905
906 gamedata->FreeSpell(spl, SpellResRef, false);
907 }
908
DisplaySpellCastMessage(ieDword tgt,Spell * spl)909 void Scriptable::DisplaySpellCastMessage(ieDword tgt, Spell *spl)
910 {
911 if (!core->HasFeedback(FT_CASTING)) return;
912
913 // caster - Casts spellname : target OR
914 // caster - spellname : target (repeating spells)
915 Scriptable *target = NULL;
916 if (tgt) {
917 target = area->GetActorByGlobalID(tgt);
918 if (!target) {
919 target=core->GetGame()->GetActorByGlobalID(tgt);
920 }
921 }
922
923 String* spell = core->GetString(spl->SpellName);
924 if (spell->length() && Type == ST_ACTOR) {
925 wchar_t str[256];
926
927 if (target) {
928 String* msg = core->GetString(displaymsg->GetStringReference(STR_ACTION_CAST), 0);
929 swprintf(str, sizeof(str)/sizeof(str[0]), L"%ls %ls : %s", msg->c_str(), spell->c_str(), target->GetName(-1));
930 delete msg;
931 } else {
932 swprintf(str, sizeof(str)/sizeof(str[0]), L"%ls : %s", spell->c_str(), GetName(-1));
933 }
934 displaymsg->DisplayStringName(str, DMC_WHITE, this);
935 }
936 delete spell;
937 }
938
939 // NOTE: currently includes the sender
SendTriggerToAll(TriggerEntry entry)940 void Scriptable::SendTriggerToAll(TriggerEntry entry)
941 {
942 std::vector<Actor *> nearActors = area->GetAllActorsInRadius(Pos, GA_NO_DEAD|GA_NO_UNSCHEDULED, 15);
943 std::vector<Actor *>::iterator neighbour;
944 for (neighbour = nearActors.begin(); neighbour != nearActors.end(); ++neighbour) {
945 (*neighbour)->AddTrigger(entry);
946 }
947 area->AddTrigger(entry);
948 }
949
ResetCastingState(Actor * caster)950 inline void Scriptable::ResetCastingState(Actor *caster) {
951 SpellHeader = -1;
952 SpellResRef[0] = 0;
953 LastTargetPos.empty();
954 LastSpellTarget = 0;
955 if (caster) {
956 memset(&(caster->wildSurgeMods), 0, sizeof(caster->wildSurgeMods));
957 }
958 }
959
CastSpellPointEnd(int level,int no_stance)960 void Scriptable::CastSpellPointEnd(int level, int no_stance)
961 {
962 Actor *caster = NULL;
963 Spell* spl = gamedata->GetSpell(SpellResRef); // this was checked before we got here
964 if (!spl) {
965 return;
966 }
967 int nSpellType = spl->SpellType;
968 gamedata->FreeSpell(spl, SpellResRef, false);
969
970 if (Type == ST_ACTOR) {
971 caster = ((Actor *) this);
972 if (!no_stance) {
973 caster->SetStance(IE_ANI_CONJURE);
974 }
975 }
976 if (level == 0) {
977 if (caster) {
978 level = caster->GetCasterLevel(nSpellType);
979 } else {
980 //default caster level is 1
981 level = 1;
982 }
983 }
984
985 if (SpellHeader == -1) {
986 LastTargetPos.empty();
987 return;
988 }
989
990 if (LastTargetPos.isempty()) {
991 SpellHeader = -1;
992 return;
993 }
994
995 if (!SpellResRef[0]) {
996 return;
997 }
998 if (!area) {
999 Log(ERROR, "Scriptable", "CastSpellPointEnd: lost area, skipping %s!", SpellResRef);
1000 ResetCastingState(caster);
1001 return;
1002 }
1003
1004 // a candidate for favourite spell? Not if we're forced cast (eg. from fx_cast_spell)
1005 if (caster && caster->PCStats && !no_stance) {
1006 caster->PCStats->RegisterFavourite(SpellResRef, FAV_SPELL);
1007 }
1008
1009 if (!no_stance) {
1010 // yep, the original didn't use the casting channel for this!
1011 core->GetAudioDrv()->Play(spl->CompletionSound, SFX_CHAN_MISSILE, Pos.x, Pos.y);
1012 }
1013
1014 CreateProjectile(SpellResRef, 0, level, false);
1015 //FIXME: this trigger affects actors whom the caster sees, not just the caster itself
1016 // the original engine saves lasttrigger only in case of SpellCast, so we have to differentiate
1017 // NOTE: unused in iwd2, so the fact that it has no stored spelltype is of no consequence
1018 ieDword spellID = ResolveSpellNumber(SpellResRef);
1019 switch (nSpellType) {
1020 case 1:
1021 SendTriggerToAll(TriggerEntry(trigger_spellcast, GetGlobalID(), spellID));
1022 break;
1023 case 2:
1024 SendTriggerToAll(TriggerEntry(trigger_spellcastpriest, GetGlobalID(), spellID));
1025 break;
1026 default:
1027 SendTriggerToAll(TriggerEntry(trigger_spellcastinnate, GetGlobalID(), spellID));
1028 break;
1029 }
1030
1031 Actor *target = area->GetActor(LastTargetPos, GA_NO_UNSCHEDULED|GA_NO_HIDDEN);
1032 if (target) {
1033 target->AddTrigger(TriggerEntry(trigger_spellcastonme, GetGlobalID(), spellID));
1034 target->LastSpellOnMe = spellID;
1035 }
1036
1037 ResetCastingState(caster);
1038 }
1039
CastSpellEnd(int level,int no_stance)1040 void Scriptable::CastSpellEnd(int level, int no_stance)
1041 {
1042 Actor *caster = NULL;
1043 Spell* spl = gamedata->GetSpell(SpellResRef); // this was checked before we got here
1044 if (!spl) {
1045 return;
1046 }
1047 int nSpellType = spl->SpellType;
1048 gamedata->FreeSpell(spl, SpellResRef, false);
1049 if (Type == ST_ACTOR) {
1050 caster = ((Actor *) this);
1051 if (!no_stance) {
1052 caster->SetStance(IE_ANI_CONJURE);
1053 }
1054 }
1055 if (level == 0) {
1056 if (caster) {
1057 level = caster->GetCasterLevel(nSpellType);
1058 } else {
1059 //default caster level is 1
1060 level = 1;
1061 }
1062 }
1063
1064 if (SpellHeader == -1) {
1065 LastSpellTarget = 0;
1066 return;
1067 }
1068 if (!LastSpellTarget) {
1069 SpellHeader = -1;
1070 return;
1071 }
1072 if (!SpellResRef[0]) {
1073 return;
1074 }
1075 if (!area) {
1076 Log(ERROR, "Scriptable", "CastSpellEnd: lost area, skipping %s!", SpellResRef);
1077 ResetCastingState(caster);
1078 return;
1079 }
1080
1081 if (caster && caster->PCStats && !no_stance) {
1082 caster->PCStats->RegisterFavourite(SpellResRef, FAV_SPELL);
1083 }
1084
1085 if (!no_stance) {
1086 core->GetAudioDrv()->Play(spl->CompletionSound, SFX_CHAN_MISSILE, Pos.x, Pos.y);
1087 }
1088
1089 //if the projectile doesn't need to follow the target, then use the target position
1090 CreateProjectile(SpellResRef, LastSpellTarget, level, GetSpellDistance(SpellResRef, this)==0xffffffff);
1091 //FIXME: this trigger affects actors whom the caster sees, not just the caster itself
1092 // the original engine saves lasttrigger only in case of SpellCast, so we have to differentiate
1093 // NOTE: unused in iwd2, so the fact that it has no stored spelltype is of no consequence
1094 ieDword spellID = ResolveSpellNumber(SpellResRef);
1095 switch (nSpellType) {
1096 case 1:
1097 SendTriggerToAll(TriggerEntry(trigger_spellcast, GetGlobalID(), spellID));
1098 break;
1099 case 2:
1100 SendTriggerToAll(TriggerEntry(trigger_spellcastpriest, GetGlobalID(), spellID));
1101 break;
1102 default:
1103 SendTriggerToAll(TriggerEntry(trigger_spellcastinnate, GetGlobalID(), spellID));
1104 break;
1105 }
1106
1107 Actor *target = area->GetActorByGlobalID(LastSpellTarget);
1108 if (target) {
1109 target->AddTrigger(TriggerEntry(trigger_spellcastonme, GetGlobalID(), spellID));
1110 target->LastSpellOnMe = spellID;
1111 }
1112
1113 ResetCastingState(caster);
1114 }
1115
1116 // check for input sanity and good casting conditions
CanCast(const ieResRef SpellRef,bool verbose)1117 int Scriptable::CanCast(const ieResRef SpellRef, bool verbose) {
1118 Spell* spl = gamedata->GetSpell(SpellRef);
1119 if (!spl) {
1120 SpellHeader = -1;
1121 Log(ERROR, "Scriptable", "Spell not found, aborting cast!");
1122 return 0;
1123 }
1124
1125 // check for area dead magic
1126 // tob AR3004 is a dead magic area, but using a script with personal dead magic
1127 if (area->GetInternalFlag()&AF_DEADMAGIC && !(spl->Flags&SF_HLA)) {
1128 displaymsg->DisplayConstantStringName(STR_DEADMAGIC_FAIL, DMC_WHITE, this);
1129 return 0;
1130 }
1131
1132 if (spl->Flags&SF_NOT_INDOORS && !(area->AreaType&AT_OUTDOOR)) {
1133 displaymsg->DisplayConstantStringName(STR_INDOOR_FAIL, DMC_WHITE, this);
1134 return 0;
1135 }
1136
1137 // various individual checks
1138 if (Type != ST_ACTOR) {
1139 return 1;
1140 }
1141 Actor *actor = (Actor *) this;
1142
1143 // check for silence
1144 // only a handful of spells don't have a verbal component -
1145 // the original hardcoded vocalize and a few more
1146 // we (also) ignore tobex modded spells
1147 if (actor->CheckSilenced()) {
1148 if (!(core->GetSpecialSpell(spl->Name)&SP_SILENCE) && !(spl->Flags&SF_IGNORES_SILENCE)) {
1149 Log(WARNING, "Scriptable", "Tried to cast while silenced!");
1150 return 0;
1151 }
1152 }
1153
1154 // check for personal dead magic
1155 if (actor->Modified[IE_DEADMAGIC] && !(spl->Flags&SF_HLA)) {
1156 displaymsg->DisplayConstantStringName(STR_DEADMAGIC_FAIL, DMC_WHITE, this);
1157 return 0;
1158 }
1159
1160 // check for miscast magic and similar
1161 ieDword roll = actor->LuckyRoll(1, 100, 0, 0);
1162 bool failed = false;
1163 ieDword chance = 0;
1164 switch(spl->SpellType)
1165 {
1166 case IE_SPL_PRIEST:
1167 chance = actor->GetSpellFailure(false);
1168 break;
1169 case IE_SPL_WIZARD:
1170 chance = actor->GetSpellFailure(true);
1171 break;
1172 case IE_SPL_INNATE:
1173 chance = actor->Modified[IE_SPELLFAILUREINNATE];
1174 break;
1175 }
1176 if (chance >= roll) {
1177 failed = true;
1178 }
1179 if (verbose && chance && third) {
1180 // ~Spell Failure check: Roll d100 %d vs. Spell failure chance %d~
1181 displaymsg->DisplayRollStringName(40955, DMC_LIGHTGREY, actor, roll, chance);
1182 }
1183 if (failed) {
1184 displaymsg->DisplayConstantStringName(STR_MISCASTMAGIC, DMC_WHITE, this);
1185 return 0;
1186 }
1187
1188 // iwd2: make a concentration check if needed
1189 if (!actor->ConcentrationCheck()) {
1190 return 0;
1191 }
1192
1193 return 1;
1194 }
1195
1196 // checks if a party-friendly actor is nearby and if so, if it recognizes the spell
1197 // this enemy just started casting
SpellcraftCheck(const Actor * caster,const ieResRef SpellRef)1198 void Scriptable::SpellcraftCheck(const Actor *caster, const ieResRef SpellRef)
1199 {
1200 if (!third || !caster || caster->GetStat(IE_EA) <= EA_CONTROLLABLE || !area) {
1201 return;
1202 }
1203
1204 Spell* spl = gamedata->GetSpell(SpellRef);
1205 assert(spl); // only a bad surge could make this fail and we want to catch it
1206 int AdjustedSpellLevel = spl->SpellLevel + 15;
1207 std::vector<Actor *> neighbours = area->GetAllActorsInRadius(caster->Pos, GA_NO_DEAD|GA_NO_ENEMY|GA_NO_SELF|GA_NO_UNSCHEDULED, caster->GetBase(IE_VISUALRANGE), this);
1208 std::vector<Actor *>::iterator neighbour;
1209 for (neighbour = neighbours.begin(); neighbour != neighbours.end(); ++neighbour) {
1210 Actor *detective = *neighbour;
1211 // disallow neutrals from helping the party
1212 if (detective->GetStat(IE_EA) > EA_CONTROLLABLE) {
1213 continue;
1214 }
1215 if (detective->GetSkill(IE_SPELLCRAFT) <= 0) {
1216 continue;
1217 }
1218
1219 // ~Spellcraft check (d20 roll + Spellcraft level + int mod) %d vs. (spell level + 15) = %d. (Int mod = %d)~
1220 int Spellcraft = core->Roll(1, 20, 0) + detective->GetStat(IE_SPELLCRAFT);
1221 int IntMod = detective->GetAbilityBonus(IE_INT);
1222
1223 if ((Spellcraft + IntMod) > AdjustedSpellLevel) {
1224 wchar_t tmpstr[100];
1225 // eg. .:Casts Word of Recall:.
1226 String* castmsg = core->GetString(displaymsg->GetStringReference(STR_CASTS));
1227 String* spellname = core->GetString(spl->SpellName);
1228 swprintf(tmpstr, sizeof(tmpstr)/sizeof(tmpstr[0]), L".:%ls %ls:.", castmsg->c_str(), spellname->c_str());
1229 delete castmsg;
1230 delete spellname;
1231
1232 SetOverheadText(tmpstr);
1233 displaymsg->DisplayRollStringName(39306, DMC_LIGHTGREY, detective, Spellcraft+IntMod, AdjustedSpellLevel, IntMod);
1234 break;
1235 }
1236 }
1237 gamedata->FreeSpell(spl, SpellRef, false);
1238 }
1239
1240 // shortcut for internal use when there is no wait
1241 // if any user needs casting time support, they should use Spell* actions directly
DirectlyCastSpellPoint(const Point & target,ieResRef spellref,int level,int no_stance,bool deplete)1242 void Scriptable::DirectlyCastSpellPoint(const Point &target, ieResRef spellref, int level, int no_stance, bool deplete)
1243 {
1244 if (!gamedata->Exists(spellref, IE_SPL_CLASS_ID)) {
1245 return;
1246 }
1247
1248 // save and restore the casting targets, so we don't interrupt any gui triggered casts with spells like true seeing (repeated fx_cast_spell)
1249 Point TmpPos = LastTargetPos;
1250 ieDword TmpTarget = LastSpellTarget;
1251 int TmpHeader = SpellHeader;
1252
1253 SetSpellResRef(spellref);
1254 CastSpellPoint(target, deplete, true, true);
1255 CastSpellPointEnd(level, no_stance);
1256
1257 LastTargetPos = TmpPos;
1258 LastSpellTarget = TmpTarget;
1259 SpellHeader = TmpHeader;
1260 }
1261
1262 // shortcut for internal use
1263 // if any user needs casting time support, they should use Spell* actions directly
DirectlyCastSpell(Scriptable * target,ieResRef spellref,int level,int no_stance,bool deplete)1264 void Scriptable::DirectlyCastSpell(Scriptable *target, ieResRef spellref, int level, int no_stance, bool deplete)
1265 {
1266 if (!gamedata->Exists(spellref, IE_SPL_CLASS_ID)) {
1267 return;
1268 }
1269
1270 // save and restore the casting targets, so we don't interrupt any gui triggered casts with spells like true seeing (repeated fx_cast_spell)
1271 Point TmpPos = LastTargetPos;
1272 ieDword TmpTarget = LastSpellTarget;
1273 int TmpHeader = SpellHeader;
1274
1275 SetSpellResRef(spellref);
1276 CastSpell(target, deplete, true, true);
1277 CastSpellEnd(level, no_stance);
1278
1279 LastTargetPos = TmpPos;
1280 LastSpellTarget = TmpTarget;
1281 SpellHeader = TmpHeader;
1282 }
1283
1284 //set target as point
1285 //if spell needs to be depleted, do it
1286 //if spell is illegal stop casting
CastSpellPoint(const Point & target,bool deplete,bool instant,bool nointerrupt)1287 int Scriptable::CastSpellPoint( const Point &target, bool deplete, bool instant, bool nointerrupt )
1288 {
1289 LastSpellTarget = 0;
1290 LastTargetPos.empty();
1291 Actor *actor = NULL;
1292 if (Type == ST_ACTOR) {
1293 actor = (Actor *) this;
1294 if (actor->HandleCastingStance(SpellResRef, deplete, instant) ) {
1295 Log(ERROR, "Scriptable", "Spell %s not known or memorized, aborting cast!", SpellResRef);
1296 return -1;
1297 }
1298 }
1299 if(!nointerrupt && !CanCast(SpellResRef)) {
1300 SpellResRef[0] = 0;
1301 if (actor) {
1302 actor->SetStance(IE_ANI_READY);
1303 }
1304 return -1;
1305 }
1306
1307 LastTargetPos = target;
1308
1309 if(!CheckWildSurge()) {
1310 return -1;
1311 }
1312 if (!instant) {
1313 SpellcraftCheck(actor, SpellResRef);
1314 if (actor) actor->CureInvisibility();
1315 }
1316 return SpellCast(instant);
1317 }
1318
1319 //set target as actor (if target isn't actor, use its position)
1320 //if spell needs to be depleted, do it
1321 //if spell is illegal stop casting
CastSpell(Scriptable * target,bool deplete,bool instant,bool nointerrupt)1322 int Scriptable::CastSpell( Scriptable* target, bool deplete, bool instant, bool nointerrupt )
1323 {
1324 LastSpellTarget = 0;
1325 LastTargetPos.empty();
1326 Actor *actor = NULL;
1327 if (Type == ST_ACTOR) {
1328 actor = (Actor *) this;
1329 if (actor->HandleCastingStance(SpellResRef, deplete, instant) ) {
1330 Log(ERROR, "Scriptable", "Spell %s not known or memorized, aborting cast!", SpellResRef);
1331 return -1;
1332 }
1333 }
1334
1335 assert(target);
1336
1337 if(!nointerrupt && !CanCast(SpellResRef)) {
1338 SpellResRef[0] = 0;
1339 if (actor) {
1340 actor->SetStance(IE_ANI_READY);
1341 }
1342 return -1;
1343 }
1344
1345 LastTargetPos = target->Pos;
1346 if (target->Type==ST_ACTOR) {
1347 LastSpellTarget = target->GetGlobalID();
1348 }
1349
1350 if(!CheckWildSurge()) {
1351 return -1;
1352 }
1353
1354 if (!instant) {
1355 SpellcraftCheck(actor, SpellResRef);
1356 if (actor) actor->CureInvisibility();
1357 }
1358 return SpellCast(instant, target);
1359 }
1360
1361 static EffectRef fx_force_surge_modifier_ref = { "ForceSurgeModifier", -1 };
1362 static EffectRef fx_castingspeed_modifier_ref = { "CastingSpeedModifier", -1 };
1363
1364 //start spellcasting (common part)
SpellCast(bool instant,Scriptable * target)1365 int Scriptable::SpellCast(bool instant, Scriptable *target)
1366 {
1367 Spell* spl = gamedata->GetSpell(SpellResRef); // this was checked before we got here
1368 Actor *actor = NULL;
1369 int level = 0;
1370 if (Type == ST_ACTOR) {
1371 actor = (Actor *) this;
1372
1373 //The ext. index is here to calculate the casting time
1374 level = actor->GetCasterLevel(spl->SpellType);
1375 SpellHeader = spl->GetHeaderIndexFromLevel(level);
1376 } else {
1377 SpellHeader = 0;
1378 }
1379
1380 SPLExtHeader *header = spl->GetExtHeader(SpellHeader);
1381 int casting_time = (int)header->CastingTime;
1382 // how does this work for non-actors exactly?
1383 if (actor) {
1384 // The mental speed effect can shorten or lengthen the casting time.
1385 // But first check if a special maximum is set
1386 Effect *fx = actor->fxqueue.HasEffectWithParam(fx_castingspeed_modifier_ref, 2);
1387 int max = 1000;
1388 if (fx) {
1389 max = fx->Parameter1;
1390 }
1391 if (max < 10 && casting_time > max) {
1392 casting_time = max;
1393 } else {
1394 casting_time -= (int)actor->Modified[IE_MENTALSPEED];
1395 }
1396 casting_time = Clamp(casting_time, 0, 10);
1397 }
1398
1399 // this is a guess which seems approximately right so far (same as in the bg2 manual, except that it may be a combat round instead)
1400 int duration = (casting_time*core->Time.round_size) / 10;
1401 if (instant) {
1402 duration = 0;
1403 }
1404 if (actor) {
1405 //cfb
1406 EffectQueue *fxqueue = new EffectQueue();
1407 // casting glow is always on the caster
1408 if (!(actor->Modified[IE_AVATARREMOVAL] || instant)) {
1409 ieDword gender = actor->GetCGGender();
1410 fxqueue->SetOwner(actor);
1411 spl->AddCastingGlow(fxqueue, duration, gender);
1412 fxqueue->AddAllEffects(actor, Point());
1413 }
1414 delete fxqueue;
1415
1416 // actual cfb
1417 fxqueue = spl->GetEffectBlock(this, this->Pos, -1, level);
1418 fxqueue->SetOwner(actor);
1419 if (target && target->Type == ST_ACTOR) {
1420 fxqueue->AddAllEffects((Actor *)target, target->Pos);
1421 } else {
1422 fxqueue->AddAllEffects(actor, actor->Pos);
1423 }
1424 delete fxqueue;
1425 actor->WMLevelMod = 0;
1426 if (actor->Modified[IE_FORCESURGE] == 1) {
1427 // affects only the next spell cast, but since the timing is permanent,
1428 // we have to remove it manually
1429 actor->fxqueue.RemoveAllEffectsWithParam(fx_force_surge_modifier_ref, 1);
1430 }
1431 actor->ResetCommentTime();
1432 }
1433
1434 gamedata->FreeSpell(spl, SpellResRef, false);
1435 return duration;
1436 }
1437
1438 // Anyone with some wildness has 5% chance of getting a wild surge when casting,
1439 // but most innates are excluded, due to being nonmagic.
1440 // A d100 roll is made, some stat boni are added, then either:
1441 // 1. the spell is cast normally (score of 100 or more)
1442 // 2. one or more wild surges happen and something else is cast
1443 // 2.1. this can loop, since some surges cause rerolls
1444 static EffectRef fx_chaosshield_ref = { "ChaosShieldModifier", -1 };
1445
CheckWildSurge()1446 int Scriptable::CheckWildSurge()
1447 {
1448 //no need to check for 3rd ed rules, because surgemod or forcesurge need to be nonzero to get a surge
1449 if (Type != ST_ACTOR) {
1450 return 1;
1451 }
1452 if (core->InCutSceneMode()) {
1453 return 1;
1454 }
1455
1456 Actor *caster = (Actor *) this;
1457
1458 int roll = core->Roll(1, 100, 0);
1459 if ((roll <= 5 && caster->Modified[IE_SURGEMOD]) || caster->Modified[IE_FORCESURGE]) {
1460 ieResRef OldSpellResRef;
1461 memcpy(OldSpellResRef, SpellResRef, sizeof(OldSpellResRef));
1462 Spell *spl = gamedata->GetSpell( OldSpellResRef ); // this was checked before we got here
1463 // ignore non-magic "spells"
1464 if (spl->Flags&(SF_HLA|SF_TRIGGER)) {
1465 gamedata->FreeSpell(spl, OldSpellResRef, false);
1466 return 1;
1467 }
1468
1469 int check = roll + caster->Modified[IE_SURGEMOD];
1470 if (caster->Modified[IE_FORCESURGE] != 7) {
1471 // skip the caster level bonus if we're already in a complicated surge
1472 check += caster->GetCasterLevel(spl->SpellType);
1473 }
1474 if (caster->Modified[IE_CHAOSSHIELD]) {
1475 //avert the surge and decrease the chaos shield counter
1476 check = 0;
1477 caster->fxqueue.DecreaseParam1OfEffect(fx_chaosshield_ref,1);
1478 displaymsg->DisplayConstantStringName(STR_CHAOSSHIELD,DMC_LIGHTGREY,caster);
1479 }
1480
1481 // hundred or more means a normal cast; same for negative values (for absurd antisurge modifiers)
1482 if ((check > 0) && (check < 100) ) {
1483 // display feedback: Wild Surge: bla bla
1484 String* s1 = core->GetString(displaymsg->GetStringReference(STR_WILDSURGE), 0);
1485 String* s2 = core->GetString(core->SurgeSpells[check-1].message, 0);
1486 displaymsg->DisplayStringName(*s1 + L" " + *s2, DMC_WHITE, this);
1487 delete s1;
1488 delete s2;
1489
1490 // lookup the spell in the "check" row of wildmag.2da
1491 ieResRef surgeSpellRef;
1492 CopyResRef(surgeSpellRef, core->SurgeSpells[check-1].spell);
1493
1494 if (!gamedata->Exists(surgeSpellRef, IE_SPL_CLASS_ID)) {
1495 // handle the hardcoded cases - they'll also fail here
1496 if (!HandleHardcodedSurge(surgeSpellRef, spl, caster)) {
1497 //free the spell handle because we need to return
1498 gamedata->FreeSpell(spl, OldSpellResRef, false);
1499 return 0;
1500 }
1501 } else {
1502 // finally change the spell
1503 // the hardcoded bunch does it on its own when needed
1504 CopyResRef(SpellResRef, surgeSpellRef);
1505 }
1506 }
1507
1508 //free the spell handle
1509 gamedata->FreeSpell(spl, OldSpellResRef, false);
1510 }
1511
1512 return 1;
1513 }
1514
HandleHardcodedSurge(ieResRef surgeSpellRef,Spell * spl,Actor * caster)1515 bool Scriptable::HandleHardcodedSurge(ieResRef surgeSpellRef, Spell *spl, Actor *caster)
1516 {
1517 // format: ID or ID.param1 or +SPELLREF
1518 int types = caster->spellbook.GetTypes();
1519 int lvl = spl->SpellLevel-1;
1520 int count, i, tmp, tmp3;
1521 Scriptable *target = NULL;
1522 Point targetpos(-1, -1);
1523 ieResRef newspl;
1524
1525 int level = caster->GetCasterLevel(spl->SpellType);
1526 switch (surgeSpellRef[0]) {
1527 case '+': // cast normally, but also cast SPELLREF first
1528 core->ApplySpell(surgeSpellRef+1, caster, caster, level);
1529 break;
1530 case '0': // cast spell param1 times
1531 strtok(surgeSpellRef,".");
1532 count = strtol(strtok(NULL,"."), NULL, 0);
1533 caster->wildSurgeMods.num_castings = count;
1534 break;
1535 case '1': // change projectile (id) to param1
1536 strtok(surgeSpellRef,".");
1537 count = strtol(strtok(NULL,"."), NULL, 0);
1538 caster->wildSurgeMods.projectile_id = count;
1539 break;
1540 case '2': // also target target type param1
1541 strtok(surgeSpellRef,".");
1542 count = strtol(strtok(NULL,"."), NULL, 0);
1543 caster->wildSurgeMods.target_type = count;
1544 caster->wildSurgeMods.target_change_type = WSTC_ADDTYPE;
1545 break;
1546 case '3': // (wild surge) roll param1 more times
1547 strtok(surgeSpellRef,".");
1548 count = strtol(strtok(NULL,"."), NULL, 0);
1549 // force surge and then cast
1550 // force the surge roll to be < 100, so we cast a spell from the surge table
1551 tmp = caster->Modified[IE_FORCESURGE];
1552 tmp3 = caster->WMLevelMod; // also save caster level; the original didn't reroll the bonus
1553 caster->Modified[IE_FORCESURGE] = 7;
1554 if (LastSpellTarget) {
1555 target = area->GetActorByGlobalID(LastSpellTarget);
1556 if (!target) {
1557 target = core->GetGame()->GetActorByGlobalID(LastSpellTarget);
1558 }
1559 }
1560 if (!LastTargetPos.isempty()) {
1561 targetpos = LastTargetPos;
1562 } else if (target) {
1563 targetpos = target->Pos;
1564 }
1565 // SpellResRef still contains the original spell and we need to keep it that way
1566 // as any of the rerolls could result in a "spell cast normally" (non-surge)
1567 for (i=0; i<count; i++) {
1568 if (target) {
1569 caster->CastSpell(target, false, true);
1570 CopyResRef(newspl, SpellResRef);
1571 caster->WMLevelMod = tmp3;
1572 caster->CastSpellEnd(level, 1);
1573 } else {
1574 caster->CastSpellPoint(targetpos, false, true);
1575 CopyResRef(newspl, SpellResRef);
1576 caster->WMLevelMod = tmp3;
1577 caster->CastSpellPointEnd(level, 1);
1578 }
1579 // reset the ref, since CastSpell*End destroyed it
1580 CopyResRef(SpellResRef, newspl);
1581 }
1582 caster->Modified[IE_FORCESURGE] = tmp;
1583 break;
1584 case '4': // change the target type to param1
1585 strtok(surgeSpellRef,".");
1586 count = strtol(strtok(NULL,"."), NULL, 0);
1587 caster->wildSurgeMods.target_type = count;
1588 caster->wildSurgeMods.target_change_type = WSTC_SETTYPE;
1589 break;
1590 case '5': // change the target to a random actor
1591 caster->wildSurgeMods.target_change_type = WSTC_RANDOMIZE;
1592 break;
1593 case '6': // change saving throw (+param1)
1594 strtok(surgeSpellRef,".");
1595 count = strtol(strtok(NULL,"."), NULL, 0);
1596 caster->wildSurgeMods.saving_throw_mod = count;
1597 break;
1598 case '7': // random spell of the same level (FIXME: make an effect out of this?)
1599 // change this if we ever want the surges to respect the original type
1600 for (i=0; i<types; i++) {
1601 unsigned int spellCount = caster->spellbook.GetKnownSpellsCount(i, lvl);
1602 if (!spellCount) continue;
1603 int id = core->Roll(1, spellCount, -1);
1604 CREKnownSpell *ck = caster->spellbook.GetKnownSpell(i, lvl, id);
1605 if (ck) {
1606 CopyResRef(SpellResRef, ck->SpellResRef);
1607 break;
1608 }
1609 }
1610 break;
1611 case '8': // set projectile speed to param1 %
1612 strtok(surgeSpellRef,".");
1613 count = strtol(strtok(NULL,"."), NULL, 0);
1614 caster->wildSurgeMods.projectile_speed_mod = count;
1615 break;
1616 default:
1617 SpellHeader = -1;
1618 SpellResRef[0] = 0;
1619 Log(ERROR, "Scriptable", "New spell not found, aborting cast mid-surge!");
1620 caster->SetStance(IE_ANI_READY);
1621 return false;
1622 }
1623 return true;
1624 }
1625
AuraPolluted()1626 bool Scriptable::AuraPolluted()
1627 {
1628 if (Type != ST_ACTOR) {
1629 return false;
1630 }
1631
1632 // aura pollution happens automatically
1633 // aura cleansing the usual and magical way
1634 if (AuraTicks >= core->Time.attack_round_size) {
1635 AuraTicks = -1;
1636 return false;
1637 } else if (CurrentActionTicks == 0 && AuraTicks != 1) {
1638 Actor *act = (Actor *) this;
1639 if (act->GetStat(IE_AURACLEANSING)) {
1640 AuraTicks = -1;
1641 if (core->HasFeedback(FT_STATES)) displaymsg->DisplayConstantStringName(STR_AURACLEANSED, DMC_WHITE, this);
1642 return false;
1643 }
1644 }
1645
1646 if (AuraTicks > 0) {
1647 // sorry, you'll have to recover first
1648 return true;
1649 }
1650 return false;
1651 }
1652
TimerActive(ieDword ID)1653 bool Scriptable::TimerActive(ieDword ID)
1654 {
1655 std::map<ieDword,ieDword>::iterator tit = script_timers.find(ID);
1656 if (tit == script_timers.end()) {
1657 return false;
1658 }
1659 return tit->second > core->GetGame()->GameTime;
1660 }
1661
TimerExpired(ieDword ID)1662 bool Scriptable::TimerExpired(ieDword ID)
1663 {
1664 std::map<ieDword,ieDword>::iterator tit = script_timers.find(ID);
1665 if (tit == script_timers.end()) {
1666 return false;
1667 }
1668 if (tit->second <= core->GetGame()->GameTime) {
1669 // expired timers become inactive after being checked
1670 script_timers.erase(tit);
1671 return true;
1672 }
1673 return false;
1674 }
1675
StartTimer(ieDword ID,ieDword expiration)1676 void Scriptable::StartTimer(ieDword ID, ieDword expiration)
1677 {
1678 ieDword newTime = core->GetGame()->GameTime + expiration*AI_UPDATE_TIME;
1679 std::map<ieDword,ieDword>::iterator tit = script_timers.find(ID);
1680 if (tit != script_timers.end()) {
1681 tit->second = newTime;
1682 return;
1683 }
1684 script_timers.insert(std::pair<ieDword,ieDword>(ID, newTime));
1685 }
1686
1687 /********************
1688 * Selectable Class *
1689 ********************/
1690
Selectable(ScriptableType type)1691 Selectable::Selectable(ScriptableType type)
1692 : Scriptable( type )
1693 {
1694 Selected = false;
1695 Over = false;
1696 size = 0;
1697 sizeFactor = 1.0f;
1698 circleBitmap[0] = NULL;
1699 circleBitmap[1] = NULL;
1700 selectedColor = ColorBlack;
1701 overColor = ColorBlack;
1702 }
1703
SetBBox(const Region & newBBox)1704 void Selectable::SetBBox(const Region &newBBox)
1705 {
1706 BBox = newBBox;
1707 }
1708
DrawCircle(const Point & p) const1709 void Selectable::DrawCircle(const Point& p) const
1710 {
1711 /* BG2 colours ground circles as follows:
1712 dark green for unselected party members
1713 bright green for selected party members
1714 flashing green/white for a party member the mouse is over
1715 bright red for enemies
1716 yellow for panicked actors
1717 flashing red/white for enemies the mouse is over
1718 flashing cyan/white for neutrals the mouse is over
1719 */
1720
1721 if (size<=0) {
1722 return;
1723 }
1724
1725 Color mix;
1726 const Color* col = &selectedColor;
1727 Holder<Sprite2D> sprite = circleBitmap[0];
1728
1729 if (Selected && !Over) {
1730 sprite = circleBitmap[1];
1731 } else if (Over) {
1732 mix = GlobalColorCycle.Blend(overColor, selectedColor);
1733 col = &mix;
1734 } else if (IsPC()) {
1735 col = &overColor;
1736 }
1737
1738 if (sprite) {
1739 core->GetVideoDriver()->BlitSprite(sprite, Pos - p);
1740 } else {
1741 // for size >= 2, radii are (size-1)*16, (size-1)*12
1742 // for size == 1, radii are 12, 9
1743 int csize = (size - 1) * 4;
1744 if (csize < 4) csize = 3;
1745
1746 core->GetVideoDriver()->DrawEllipse( Pos - p,
1747 (ieWord) (csize * 4 * sizeFactor), (ieWord) (csize * 3 * sizeFactor), *col);
1748 }
1749 }
1750
1751 // Check if P is over our ground circle
IsOver(const Point & P) const1752 bool Selectable::IsOver(const Point &P) const
1753 {
1754 int csize = size;
1755 if (csize < 2) csize = 2;
1756
1757 int dx = P.x - Pos.x;
1758 int dy = P.y - Pos.y;
1759
1760 // check rectangle first
1761 if (dx < -(csize-1)*16 || dx > (csize-1)*16) return false;
1762 if (dy < -(csize-1)*12 || dy > (csize-1)*12) return false;
1763
1764 // then check ellipse
1765 int r = 9*dx*dx + 16*dy*dy; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
1766
1767 return (r <= 48*48*(csize-1)*(csize-1));
1768 }
1769
IsSelected() const1770 bool Selectable::IsSelected() const
1771 {
1772 return Selected == 1;
1773 }
1774
SetOver(bool over)1775 void Selectable::SetOver(bool over)
1776 {
1777 Over = over;
1778 }
1779
1780 //don't call this function after rendering the cover and before the
1781 //blitting of the sprite or bad things will happen :)
Select(int Value)1782 void Selectable::Select(int Value)
1783 {
1784 if (Selected!=0x80 || Value!=1) {
1785 Selected = (ieWord) Value;
1786 }
1787 }
1788
SetCircle(int circlesize,float factor,const Color & color,Holder<Sprite2D> normal_circle,Holder<Sprite2D> selected_circle)1789 void Selectable::SetCircle(int circlesize, float factor, const Color &color, Holder<Sprite2D> normal_circle, Holder<Sprite2D> selected_circle)
1790 {
1791 size = circlesize;
1792 sizeFactor = factor;
1793 selectedColor = color;
1794 overColor.r = color.r >> 1;
1795 overColor.g = color.g >> 1;
1796 overColor.b = color.b >> 1;
1797 overColor.a = color.a;
1798 circleBitmap[0] = normal_circle;
1799 circleBitmap[1] = selected_circle;
1800 }
1801
1802 /***********************
1803 * Highlightable Class *
1804 ***********************/
1805
Highlightable(ScriptableType type)1806 Highlightable::Highlightable(ScriptableType type)
1807 : Scriptable( type )
1808 {
1809 outline = NULL;
1810 Highlight = false;
1811 Cursor = IE_CURSOR_NORMAL;
1812 KeyResRef[0] = 0;
1813 EnterWav[0] = 0;
1814 outlineColor = ColorBlack;
1815 TrapDetectionDiff = TrapRemovalDiff = Trapped = TrapDetected = 0;
1816 }
1817
IsOver(const Point & Place) const1818 bool Highlightable::IsOver(const Point &Place) const
1819 {
1820 if (!outline) {
1821 return false;
1822 }
1823 return outline->PointIn(Place);
1824 }
1825
DrawOutline(Point origin) const1826 void Highlightable::DrawOutline(Point origin) const
1827 {
1828 if (!outline) {
1829 return;
1830 }
1831 origin = outline->BBox.Origin() - origin;
1832
1833 if (core->HasFeature(GF_PST_STATE_FLAGS)) {
1834 core->GetVideoDriver()->DrawPolygon( outline.get(), origin, outlineColor, true, BlitFlags::MULTIPLY|BlitFlags::HALFTRANS );
1835 } else {
1836 core->GetVideoDriver()->DrawPolygon( outline.get(), origin, outlineColor, true, BlitFlags::BLENDED|BlitFlags::HALFTRANS );
1837 core->GetVideoDriver()->DrawPolygon( outline.get(), origin, outlineColor, false );
1838 }
1839 }
1840
SetCursor(unsigned char CursorIndex)1841 void Highlightable::SetCursor(unsigned char CursorIndex)
1842 {
1843 Cursor = CursorIndex;
1844 }
1845
1846 //trap that will fire now
TriggerTrap(int,ieDword ID)1847 bool Highlightable::TriggerTrap(int /*skill*/, ieDword ID)
1848 {
1849 if (!Trapped) {
1850 return false;
1851 }
1852 //actually this could be script name[0]
1853 if (!Scripts[0] && !EnterWav[0]) {
1854 return false;
1855 }
1856 AddTrigger(TriggerEntry(trigger_entered, ID));
1857 AddTrigger(TriggerEntry(trigger_traptriggered, ID)); // for that one user in bg2
1858
1859 // the second part is a hack to deal with bg2's ar1401 lava floor trap ("muck"), which doesn't have the repeating bit set
1860 // should we always send Entered instead, also when !Trapped? Does not appear so, see history of InfoPoint::TriggerTrap
1861 if (TrapResets()) {
1862 AddTrigger(TriggerEntry(trigger_reset, GetGlobalID()));
1863 } else if (TrapDetectionDiff && TrapRemovalDiff) {
1864 Trapped = false;
1865 }
1866 return true;
1867 }
1868
TryUnlock(Actor * actor,bool removekey)1869 bool Highlightable::TryUnlock(Actor *actor, bool removekey) {
1870 const char *Key = GetKey();
1871 Actor *haskey = NULL;
1872
1873 if (Key && actor->InParty) {
1874 const Game *game = core->GetGame();
1875 //allow unlock when the key is on any partymember
1876 for (int idx = 0; idx < game->GetPartySize(false); idx++) {
1877 Actor *pc = game->FindPC(idx + 1);
1878 if (!pc) continue;
1879
1880 if (pc->inventory.HasItem(Key,0) ) {
1881 haskey = pc;
1882 break;
1883 }
1884 }
1885 } else if (Key) {
1886 //actor is not in party, check only actor
1887 if (actor->inventory.HasItem(Key,0) ) {
1888 haskey = actor;
1889 }
1890 }
1891
1892 if (!haskey) {
1893 return false;
1894 }
1895
1896 if (removekey) {
1897 CREItem *item = NULL;
1898 haskey->inventory.RemoveItem(Key,0,&item);
1899 //the item should always be existing!!!
1900 delete item;
1901 }
1902
1903 return true;
1904 }
1905
1906 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1907 //skill is the all around modified trap detection skill
1908 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
DetectTrap(int skill,ieDword actorID)1909 void Highlightable::DetectTrap(int skill, ieDword actorID)
1910 {
1911 if (!CanDetectTrap()) return;
1912 if (!Scripts[0]) return;
1913 if ((skill>=100) && (skill!=256) ) skill = 100;
1914 int check = 0;
1915 if (third) {
1916 //~Search (detect traps) check. Search skill %d vs. trap's difficulty %d (searcher's %d INT bonus).~
1917 Actor *detective = core->GetGame()->GetActorByGlobalID(actorID);
1918 int bonus = 0;
1919 if (detective) {
1920 bonus = detective->GetAbilityBonus(IE_INT);
1921 displaymsg->DisplayRollStringName(39303, DMC_LIGHTGREY, detective, skill-bonus, TrapDetectionDiff, bonus);
1922 }
1923 check = (skill + bonus)*7;
1924 } else {
1925 check = skill/2 + core->Roll(1, skill/2, 0);
1926 }
1927 if (check > TrapDetectionDiff) {
1928 SetTrapDetected(1); //probably could be set to the player #?
1929 AddTrigger(TriggerEntry(trigger_detected, actorID));
1930 }
1931 }
1932
PossibleToSeeTrap() const1933 bool Highlightable::PossibleToSeeTrap() const
1934 {
1935 return CanDetectTrap();
1936 }
1937
1938 /*****************
1939 * Movable Class *
1940 *****************/
1941
Movable(ScriptableType type)1942 Movable::Movable(ScriptableType type)
1943 : Selectable( type )
1944 {
1945 bumpBackTries = 0;
1946 bumped = false;
1947 oldPos = Pos;
1948 Destination = Pos;
1949 Orientation = 0;
1950 NewOrientation = 0;
1951 StanceID = 0;
1952 path = NULL;
1953 step = NULL;
1954 timeStartStep = 0;
1955 Area[0] = 0;
1956 AttackMovements[0] = 100;
1957 AttackMovements[1] = 0;
1958 AttackMovements[2] = 0;
1959 HomeLocation.x = 0;
1960 HomeLocation.y = 0;
1961 maxWalkDistance = 0;
1962 prevTicks = 0;
1963 pathTries = 0;
1964 randomBackoff = 0;
1965 pathfindingDistance = size;
1966 randomWalkCounter = 0;
1967 pathAbandoned = false;
1968 }
1969
~Movable(void)1970 Movable::~Movable(void)
1971 {
1972 if (path) {
1973 ClearPath(true);
1974 }
1975 }
1976
GetPathLength() const1977 int Movable::GetPathLength() const
1978 {
1979 const PathNode *node = GetNextStep(0);
1980 if (!node) return 0;
1981
1982 int i = 0;
1983 while (node->Next) {
1984 i++;
1985 node = node->Next;
1986 }
1987 return i;
1988 }
1989
GetNextStep(int x) const1990 PathNode *Movable::GetNextStep(int x) const
1991 {
1992 if (!step) {
1993 error("GetNextStep", "Hit with step = null");
1994 }
1995 PathNode *node = step;
1996 while(node && x--) {
1997 node = node->Next;
1998 }
1999 return node;
2000 }
2001
GetMostLikelyPosition() const2002 Point Movable::GetMostLikelyPosition() const
2003 {
2004 if (!path) {
2005 return Pos;
2006 }
2007
2008 //actually, sometimes middle path would be better, if
2009 //we stand in Destination already
2010 int halfway = GetPathLength()/2;
2011 const PathNode *node = GetNextStep(halfway);
2012 if (node) {
2013 return Point((ieWord) ((node->x*16)+8), (ieWord) ((node->y*12)+6) );
2014 }
2015 return Destination;
2016 }
2017
SetStance(unsigned int arg)2018 void Movable::SetStance(unsigned int arg)
2019 {
2020 //don't modify stance from dead back to anything if the actor is dead
2021 if ((StanceID==IE_ANI_TWITCH || StanceID==IE_ANI_DIE) && (arg!=IE_ANI_TWITCH) ) {
2022 if (GetInternalFlag()&IF_REALLYDIED) {
2023 Log(WARNING, "Movable", "Stance overridden by death");
2024 return;
2025 }
2026 }
2027
2028 if (StanceID == IE_ANI_CONJURE && StanceID != arg && Type ==ST_ACTOR) {
2029 Actor *caster = (Actor *) this;
2030 if (caster->casting_sound) {
2031 caster->casting_sound->Stop();
2032 caster->casting_sound.release();
2033 }
2034 }
2035
2036 if (arg >= MAX_ANIMS) {
2037 StanceID = IE_ANI_AWAKE;
2038 Log(ERROR, "Movable", "Tried to set invalid stance id(%u)", arg);
2039 return;
2040 }
2041
2042 StanceID = (unsigned char) arg;
2043
2044 if (StanceID == IE_ANI_ATTACK) {
2045 // Set stance to a random attack animation
2046 int random = RAND(0, 99);
2047 if (random < AttackMovements[0]) {
2048 StanceID = IE_ANI_ATTACK_BACKSLASH;
2049 } else if (random < AttackMovements[0] + AttackMovements[1]) {
2050 StanceID = IE_ANI_ATTACK_SLASH;
2051 } else {
2052 StanceID = IE_ANI_ATTACK_JAB;
2053 }
2054 }
2055
2056 // this doesn't get hit on movement, since movement overrides the stance manually
2057 // but it is needed for the twang/clank when an actor stops moving
2058 // a lot of other stances would get skipped later, since we check we're out of combat
2059 if (Type == ST_ACTOR) {
2060 Actor *actor = (Actor *) this;
2061 actor->PlayArmorSound();
2062 }
2063 }
2064
SetOrientation(int value,bool slow)2065 void Movable::SetOrientation(int value, bool slow) {
2066 //MAX_ORIENT == 16, so we can do this
2067 NewOrientation = (unsigned char) (value&(MAX_ORIENT-1));
2068 if (NewOrientation != Orientation && Type == ST_ACTOR) {
2069 Actor *actor = (Actor *) this;
2070 actor->PlayArmorSound();
2071 }
2072 if (!slow) {
2073 Orientation = NewOrientation;
2074 }
2075 }
2076
SetAttackMoveChances(ieWord * amc)2077 void Movable::SetAttackMoveChances(ieWord *amc)
2078 {
2079 AttackMovements[0]=amc[0];
2080 AttackMovements[1]=amc[1];
2081 AttackMovements[2]=amc[2];
2082 }
2083
2084 //this could be used for WingBuffet as well
MoveLine(int steps,ieDword orient)2085 void Movable::MoveLine(int steps, ieDword orient)
2086 {
2087 if (path || !steps) {
2088 return;
2089 }
2090 // DoStep takes care of stopping on walls if necessary
2091 path = area->GetLine(Pos, steps, orient);
2092 }
2093
GetNextFace()2094 unsigned char Movable::GetNextFace()
2095 {
2096 //slow turning
2097 if (timeStartStep==core->GetGame()->Ticks) {
2098 return Orientation;
2099 }
2100 if (Orientation != NewOrientation) {
2101 if ( ( (NewOrientation-Orientation) & (MAX_ORIENT-1) ) <= MAX_ORIENT/2) {
2102 Orientation++;
2103 } else {
2104 Orientation--;
2105 }
2106 Orientation = Orientation&(MAX_ORIENT-1);
2107 }
2108
2109 return Orientation;
2110 }
2111
2112
Backoff()2113 void Movable::Backoff()
2114 {
2115 StanceID = IE_ANI_READY;
2116 if (InternalFlags & IF_RUNNING) {
2117 randomBackoff = RAND(MAX_PATH_TRIES * 2 / 3, MAX_PATH_TRIES * 4 / 3);
2118 } else {
2119 randomBackoff = RAND(MAX_PATH_TRIES, MAX_PATH_TRIES * 2);
2120 }
2121 }
2122
2123
BumpAway()2124 void Movable::BumpAway()
2125 {
2126 area->ClearSearchMapFor(this);
2127 if (!IsBumped()) oldPos = Pos;
2128 bumped = true;
2129 bumpBackTries = 0;
2130 area->AdjustPositionNavmap(Pos);
2131 }
2132
BumpBack()2133 void Movable::BumpBack()
2134 {
2135 if (Type != ST_ACTOR) return;
2136 Actor *actor = (Actor*)this;
2137 area->ClearSearchMapFor(this);
2138 PathMapFlags oldPosBlockStatus = area->GetBlockedNavmap(oldPos.x, oldPos.y);
2139 if (!(oldPosBlockStatus & PathMapFlags::PASSABLE)) {
2140 // Do bump back if the actor is "blocking" itself
2141 if (!((oldPosBlockStatus & PathMapFlags::ACTOR) == PathMapFlags::ACTOR && area->GetActor(oldPos, GA_NO_DEAD|GA_NO_UNSCHEDULED) == actor)) {
2142 area->BlockSearchMap(Pos, size, actor->IsPartyMember() ? PathMapFlags::PC : PathMapFlags::NPC);
2143 if (actor->GetStat(IE_EA) < EA_GOODCUTOFF) {
2144 bumpBackTries++;
2145 if (bumpBackTries > MAX_BUMP_BACK_TRIES && SquaredDistance(Pos, oldPos) < unsigned(size * 32 * size * 32)) {
2146 oldPos = Pos;
2147 bumped = false;
2148 bumpBackTries = 0;
2149 if (SquaredDistance(Pos, Destination) < unsigned(size * 32 * size * 32)) {
2150 ClearPath(true);
2151 }
2152
2153 }
2154 }
2155 return;
2156 }
2157 }
2158 bumped = false;
2159 MoveTo(oldPos);
2160 bumpBackTries = 0;
2161 }
2162
2163 // Takes care of movement and actor bumping, i.e. gently pushing blocking actors out of the way
2164 // The movement logic is a proportional regulator: the displacement/movement vector has a
2165 // fixed radius, based on actor walk speed, and its direction heads towards the next waypoint.
2166 // The bumping logic checks if there would be a collision if the actor was to move according to this
2167 // displacement vector and then, if that is the case, checks if that actor can be bumped
2168 // In that case, it bumps it and goes on with its step, otherwise it either stops and waits
2169 // for a random time (inspired by network media access control algorithms) or just stops if
2170 // the goal is close enough.
DoStep(unsigned int walkScale,ieDword time)2171 void Movable::DoStep(unsigned int walkScale, ieDword time) {
2172 Actor *actor = nullptr;
2173 if (Type == ST_ACTOR) actor = (Actor*)this;
2174 // Only bump back if not moving
2175 // Actors can be bumped while moving if they are backing off
2176 if (!path) {
2177 if (IsBumped()) {
2178 BumpBack();
2179 }
2180 return;
2181 }
2182 if (!time) time = core->GetGame()->Ticks;
2183 if (!walkScale) {
2184 // zero speed: no movement
2185 StanceID = IE_ANI_READY;
2186 timeStartStep = time;
2187 return;
2188 }
2189 if (!step) {
2190 step = path;
2191 timeStartStep = time;
2192 return;
2193 }
2194
2195 Point nmptStep(step->x, step->y);
2196 double dx = nmptStep.x - Pos.x;
2197 double dy = nmptStep.y - Pos.y;
2198 Map::NormalizeDeltas(dx, dy, double(gamedata->GetStepTime()) / double(walkScale));
2199 if (time > timeStartStep) {
2200 Actor *actorInTheWay = nullptr;
2201 // We can't use GetActorInRadius because we want to only check directly along the way
2202 // and not be blocked by actors who are on the sides
2203 int collisionLookaheadRadius = ((size < 3 ? 3 : size) - 1) * 3;
2204 for (int r = collisionLookaheadRadius; r > 0 && !actorInTheWay; r--) {
2205 double xCollision = Pos.x + dx * r;
2206 double yCollision = Pos.y + dy * r * 0.75;
2207 Point nmptCollision(xCollision, yCollision);
2208 actorInTheWay = area->GetActor(nmptCollision, GA_NO_DEAD|GA_NO_UNSCHEDULED);
2209 }
2210
2211 if (BlocksSearchMap() && actorInTheWay && actorInTheWay != this && actorInTheWay->BlocksSearchMap()) {
2212 // Give up instead of bumping if you are close to the goal
2213 if (!(step->Next) && PersonalDistance(nmptStep, this) < MAX_OPERATING_DISTANCE) {
2214 ClearPath(true);
2215 NewOrientation = Orientation;
2216 // Do not call ReleaseCurrentAction() since other actions
2217 // than MoveToPoint can cause movement
2218 Log(DEBUG, "PathFinderWIP", "Abandoning because I'm close to the goal");
2219 pathAbandoned = true;
2220 return;
2221 }
2222 if (actor && actor->ValidTarget(GA_CAN_BUMP) && actorInTheWay->ValidTarget(GA_ONLY_BUMPABLE)) {
2223 actorInTheWay->BumpAway();
2224 } else {
2225 Backoff();
2226 return;
2227 }
2228 }
2229 // Stop if there's a door in the way
2230 if (BlocksSearchMap() && bool(area->GetBlockedNavmap(Pos.x + dx, Pos.y + dy) & PathMapFlags::SIDEWALL)) {
2231 ClearPath(true);
2232 NewOrientation = Orientation;
2233 return;
2234 }
2235 if (BlocksSearchMap()) {
2236 area->ClearSearchMapFor(this);
2237 }
2238 StanceID = IE_ANI_WALK;
2239 if (InternalFlags & IF_RUNNING) {
2240 StanceID = IE_ANI_RUN;
2241 }
2242 Pos.x += dx;
2243 Pos.y += dy;
2244 oldPos = Pos;
2245 if (actor && BlocksSearchMap()) {
2246 area->BlockSearchMap(Pos, size, actor->IsPartyMember() ? PathMapFlags::PC : PathMapFlags::NPC);
2247 }
2248
2249 SetOrientation(step->orient, false);
2250 timeStartStep = time;
2251 if (Pos == nmptStep) {
2252 if (step->Next) {
2253 step = step->Next;
2254 } else {
2255 ClearPath(true);
2256 NewOrientation = Orientation;
2257 pathfindingDistance = size;
2258 }
2259 }
2260 }
2261 }
2262
AdjustPosition()2263 void Movable::AdjustPosition()
2264 {
2265 area->AdjustPosition(Pos);
2266 ImpedeBumping();
2267 }
2268
AddWayPoint(const Point & Des)2269 void Movable::AddWayPoint(const Point &Des)
2270 {
2271 if (!path) {
2272 WalkTo(Des);
2273 return;
2274 }
2275 Destination = Des;
2276 //it is tempting to use 'step' here, as it could
2277 //be about half of the current path already
2278 PathNode *endNode = path;
2279 while(endNode->Next) {
2280 endNode = endNode->Next;
2281 }
2282 Point p(endNode->x, endNode->y);
2283 area->ClearSearchMapFor(this);
2284 PathNode *path2 = area->FindPath(p, Des, size);
2285 // if the waypoint is too close to the current position, no path is generated
2286 if (!path2) {
2287 if (BlocksSearchMap()) {
2288 area->BlockSearchMap(Pos, size, IsPC() ? PathMapFlags::PC : PathMapFlags::NPC);
2289 }
2290 return;
2291 }
2292 endNode->Next = path2;
2293 //probably it is wise to connect it both ways?
2294 path2->Parent = endNode;
2295 }
2296
2297 // This function is called at each tick if an actor is following another actor
2298 // Therefore it's rate-limited to avoid actors being stuck as they keep pathfinding
WalkTo(const Point & Des,int distance)2299 void Movable::WalkTo(const Point &Des, int distance)
2300 {
2301 // Only rate-limit when moving
2302 if ((GetPath() || InMove()) && prevTicks && Ticks < prevTicks + 2) {
2303 return;
2304 }
2305
2306 Actor *actor = nullptr;
2307 if (Type == ST_ACTOR) actor = (Actor*)this;
2308
2309 prevTicks = Ticks;
2310 Destination = Des;
2311 if (pathAbandoned) {
2312 Log(DEBUG, "WalkTo", "%s: Path was just abandoned", GetName(0));
2313 ClearPath(true);
2314 return;
2315 }
2316
2317 if (Pos.x / 16 == Des.x / 16 && Pos.y / 12 == Des.y / 12) {
2318 ClearPath(true);
2319 return;
2320 }
2321
2322 if (BlocksSearchMap()) area->ClearSearchMapFor(this);
2323 PathNode *newPath = area->FindPath(Pos, Des, size, distance, PF_SIGHT|PF_ACTORS_ARE_BLOCKING, actor);
2324 if (!newPath && actor && actor->ValidTarget(GA_CAN_BUMP)) {
2325 Log(DEBUG, "WalkTo", "%s re-pathing ignoring actors", GetName(0));
2326 newPath = area->FindPath(Pos, Des, size, distance, PF_SIGHT, actor);
2327 }
2328
2329 if (newPath) {
2330 ClearPath(false);
2331 path = newPath;
2332 step = path;
2333 } else {
2334 pathfindingDistance = std::max(size, distance);
2335 if (BlocksSearchMap()) {
2336 area->BlockSearchMap(Pos, size, IsPC() ? PathMapFlags::PC : PathMapFlags::NPC);
2337 }
2338 }
2339 }
2340
RunAwayFrom(const Point & Des,int PathLength,bool noBackAway)2341 void Movable::RunAwayFrom(const Point &Des, int PathLength, bool noBackAway)
2342 {
2343 ClearPath(true);
2344 area->ClearSearchMapFor(this);
2345 path = area->RunAway(Pos, Des, size, PathLength, !noBackAway, Type == ST_ACTOR ? (Actor*)this : NULL);
2346 }
2347
RandomWalk(bool can_stop,bool run)2348 void Movable::RandomWalk(bool can_stop, bool run)
2349 {
2350 if (path) {
2351 return;
2352 }
2353 //if not continous random walk, then stops for a while
2354 if (can_stop) {
2355 Region vp = core->GetGameControl()->Viewport();
2356 if (!vp.PointInside(Pos)) {
2357 SetWait(AI_UPDATE_TIME * core->Roll(1, 40, 0));
2358 return;
2359 }
2360 // a 50/50 chance to move or do a spin (including its own wait)
2361 if (RAND(1, 2) == 1) {
2362 Action *me = ParamCopy(CurrentAction);
2363 Action *turnAction = GenerateAction("RandomTurn()");
2364 // only spin once before relinquishing control back
2365 turnAction->int0Parameter = 3;
2366 // remove and readd ourselves, so the turning gets a chance to run
2367 ReleaseCurrentAction();
2368 AddActionInFront(me);
2369 AddActionInFront(turnAction);
2370 return;
2371 }
2372 }
2373
2374 // handle the RandomWalkTime variants, which only count moves
2375 if (CurrentAction->int0Parameter && !CurrentAction->int1Parameter) {
2376 // first run only
2377 CurrentAction->int1Parameter = 1;
2378 CurrentAction->int0Parameter++;
2379 }
2380 if (CurrentAction->int0Parameter) {
2381 CurrentAction->int0Parameter--;
2382 }
2383 if (CurrentAction->int1Parameter && !CurrentAction->int0Parameter) {
2384 ReleaseCurrentAction();
2385 return;
2386 }
2387
2388 randomWalkCounter++;
2389 if (randomWalkCounter > MAX_RAND_WALK) {
2390 randomWalkCounter = 0;
2391 WalkTo(HomeLocation);
2392 return;
2393 }
2394
2395 if (run) {
2396 InternalFlags|=IF_RUNNING;
2397 }
2398
2399 if (BlocksSearchMap()) {
2400 area->ClearSearchMapFor(this);
2401 }
2402
2403 //the 5th parameter is controlling the orientation of the actor
2404 //0 - back away, 1 - face direction
2405 path = area->RandomWalk(Pos, size, maxWalkDistance ? maxWalkDistance : 5, Type == ST_ACTOR ? (Actor*)this : NULL);
2406 if (BlocksSearchMap()) {
2407 area->BlockSearchMap(Pos, size, IsPC() ? PathMapFlags::PC : PathMapFlags::NPC);
2408 }
2409 if (path) {
2410 Destination = Point(path->x, path->y);
2411 } else {
2412 randomWalkCounter = 0;
2413 WalkTo(HomeLocation);
2414 return;
2415 }
2416
2417 }
2418
MoveTo(const Point & Des)2419 void Movable::MoveTo(const Point &Des)
2420 {
2421 area->ClearSearchMapFor(this);
2422 Pos = Des;
2423 oldPos = Des;
2424 Destination = Des;
2425 if (BlocksSearchMap()) {
2426 area->BlockSearchMap( Pos, size, IsPC()?PathMapFlags::PC:PathMapFlags::NPC);
2427 }
2428 }
2429
Stop()2430 void Movable::Stop()
2431 {
2432 Scriptable::Stop();
2433 ClearPath(true);
2434 }
2435
ClearPath(bool resetDestination)2436 void Movable::ClearPath(bool resetDestination)
2437 {
2438 pathAbandoned = false;
2439
2440 if (resetDestination) {
2441 //this is to make sure attackers come to us
2442 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
2443 //is set before ClearPath
2444 Destination = Pos;
2445
2446 if (StanceID == IE_ANI_WALK || StanceID == IE_ANI_RUN) {
2447 StanceID = IE_ANI_AWAKE;
2448 }
2449 InternalFlags &= ~IF_NORETICLE;
2450 }
2451 PathNode* thisNode = path;
2452 while (thisNode) {
2453 PathNode* nextNode = thisNode->Next;
2454 delete( thisNode );
2455 thisNode = nextNode;
2456 }
2457 path = NULL;
2458 step = NULL;
2459 //don't call ReleaseCurrentAction
2460 }
2461
2462 /**********************
2463 * Tiled Object Class *
2464 **********************/
2465
TileObject()2466 TileObject::TileObject()
2467 {
2468 opentiles = NULL;
2469 opencount = 0;
2470 closedtiles = NULL;
2471 closedcount = 0;
2472 Flags = 0;
2473 }
2474
~TileObject()2475 TileObject::~TileObject()
2476 {
2477 if (opentiles) {
2478 free( opentiles );
2479 }
2480 if (closedtiles) {
2481 free( closedtiles );
2482 }
2483 }
2484
SetOpenTiles(unsigned short * Tiles,int cnt)2485 void TileObject::SetOpenTiles(unsigned short* Tiles, int cnt)
2486 {
2487 if (opentiles) {
2488 free( opentiles );
2489 }
2490 opentiles = Tiles;
2491 opencount = cnt;
2492 }
2493
SetClosedTiles(unsigned short * Tiles,int cnt)2494 void TileObject::SetClosedTiles(unsigned short* Tiles, int cnt)
2495 {
2496 if (closedtiles) {
2497 free( closedtiles );
2498 }
2499 closedtiles = Tiles;
2500 closedcount = cnt;
2501 }
2502
2503
2504 }
2505