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