TickHook: Fix crash when TickHook isn't set.
[gemrb.git] / gemrb / core / Scriptable / ActorBlock.cpp
blobff1b5da3ea59d89082bb8a4872265c10dda53cfa
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2005 The GemRB Project
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.
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.
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.
20 #include "Scriptable/ActorBlock.h"
22 #include "strrefs.h"
23 #include "win32def.h"
25 #include "Audio.h"
26 #include "DisplayMessage.h"
27 #include "Game.h"
28 #include "GameData.h"
29 #include "Interface.h"
30 #include "Item.h"
31 #include "Map.h"
32 #include "Projectile.h"
33 #include "Spell.h"
34 #include "SpriteCover.h"
35 #include "TileMap.h"
36 #include "Video.h"
37 #include "GameScript/GSUtils.h"
38 #include "GUI/GameControl.h"
40 #include <cassert>
41 #include <cmath>
43 #define YESNO(x) ( (x)?"Yes":"No")
45 // we start this at a non-zero value to make debugging easier
46 static ieDword globalActorCounter = 10000;
48 /***********************
49 * Scriptable Class *
50 ***********************/
51 Scriptable::Scriptable(ScriptableType type)
53 Type = type;
54 for (int i = 0; i < MAX_SCRIPTS; i++) {
55 Scripts[i] = NULL;
57 overHeadText = NULL;
58 overHeadTextPos.empty();
59 textDisplaying = 0;
60 timeStartDisplaying = 0;
61 scriptName[0] = 0;
62 TriggerID = 0; //used by SendTrigger
63 LastTriggerObject = LastTrigger = 0;
64 LastEntered = 0;
65 LastDisarmed = 0;
66 LastDisarmFailed = 0;
67 LastUnlocked = 0;
68 LastOpenFailed = 0;
69 LastPickLockFailed = 0;
70 DialogName = 0;
71 CurrentAction = NULL;
72 CurrentActionState = 0;
73 CurrentActionTarget = 0;
74 CurrentActionInterruptable = true;
75 UnselectableTimer = 0;
76 startTime = 0; //executing scripts
77 lastRunTime = 0; //evaluating scripts
78 lastDelay = 0;
79 Dialog[0] = 0;
81 globalID = ++globalActorCounter;
83 interval = ( 1000 / AI_UPDATE_TIME );
84 WaitCounter = 0;
85 if (Type == ST_ACTOR) {
86 InternalFlags = IF_VISIBLE | IF_ONCREATION | IF_USEDSAVE;
87 } else {
88 InternalFlags = IF_ACTIVE | IF_VISIBLE | IF_ONCREATION | IF_NOINT;
90 area = 0;
91 Pos.x = 0;
92 Pos.y = 0;
94 LastCasterOnMe = 0;
95 LastSpellOnMe = 0xffffffff;
96 LastCasterSeen = 0;
97 LastSpellSeen = 0xffffffff;
98 SpellHeader = -1;
99 LastTargetPos.empty();
100 locals = new Variables();
101 locals->SetType( GEM_VARIABLES_INT );
102 locals->ParseKey( 1 );
103 InitTriggers();
105 memset( script_timers,0, sizeof(script_timers));
108 Scriptable::~Scriptable(void)
110 if (CurrentAction) {
111 ReleaseCurrentAction();
113 ClearActions();
114 for (int i = 0; i < MAX_SCRIPTS; i++) {
115 if (Scripts[i]) {
116 delete( Scripts[i] );
119 if (overHeadText) {
120 core->FreeString( overHeadText );
122 if (locals) {
123 delete( locals );
127 void Scriptable::SetScriptName(const char* text)
129 //if (text && text[0]) { //this leaves some uninitialized bytes
130 //lets hope this won't break anything
131 if (text) {
132 strnspccpy( scriptName, text, 32 );
136 /** Gets the DeathVariable */
137 const char* Scriptable::GetScriptName(void) const
139 return scriptName;
142 Map* Scriptable::GetCurrentArea() const
144 //this could be NULL, always check it
145 return area;
148 void Scriptable::SetMap(Map *map)
150 if (map && (map->GetCurrentArea()!=map)) {
151 //a map always points to itself (if it is a real map)
152 printMessage("Scriptable","Invalid map set!\n",LIGHT_RED);
153 abort();
155 area = map;
158 //ai is nonzero if this is an actor currently in the party
159 //if the script level is AI_SCRIPT_LEVEL, then we need to
160 //load an AI script (.bs) instead of (.bcs)
161 void Scriptable::SetScript(const ieResRef aScript, int idx, bool ai)
163 if (idx >= MAX_SCRIPTS) {
164 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
165 abort();
167 if (Scripts[idx]) {
168 delete Scripts[idx];
170 Scripts[idx] = NULL;
171 // NONE is an 'invalid' script name, never used seriously
172 // This hack is to prevent flooding of the console
173 if (aScript[0] && stricmp(aScript, "NONE") ) {
174 if (idx!=AI_SCRIPT_LEVEL) ai = false;
175 Scripts[idx] = new GameScript( aScript, this, idx, ai );
179 void Scriptable::SetScript(int index, GameScript* script)
181 if (index >= MAX_SCRIPTS) {
182 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
183 return;
185 if (Scripts[index] ) {
186 delete Scripts[index];
188 Scripts[index] = script;
191 void Scriptable::DisplayHeadText(const char* text)
193 if (overHeadText) {
194 core->FreeString( overHeadText );
196 overHeadText = (char *) text;
197 overHeadTextPos.empty();
198 if (text) {
199 timeStartDisplaying = core->GetGame()->Ticks;
200 textDisplaying = 1;
202 else {
203 timeStartDisplaying = 0;
204 textDisplaying = 0;
208 /* 'fix' the current overhead text in the current position */
209 void Scriptable::FixHeadTextPos()
211 overHeadTextPos = Pos;
214 #define MAX_DELAY 6000
215 static const Color black={0,0,0,0};
217 void Scriptable::DrawOverheadText(const Region &screen)
219 unsigned long time = core->GetGame()->Ticks;
220 Palette *palette = NULL;
222 if (!textDisplaying)
223 return;
225 time -= timeStartDisplaying;
227 Font* font = core->GetFont( 1 );
228 if (time >= MAX_DELAY) {
229 textDisplaying = 0;
230 return;
231 } else {
232 time = (MAX_DELAY-time)/10;
233 if (time<256) {
234 const Color overHeadColor = {time,time,time,time};
235 palette = core->CreatePalette(overHeadColor, black);
239 int cs = 100;
240 if (Type == ST_ACTOR) {
241 cs = ((Selectable *) this)->size*50;
244 short x, y;
245 if (overHeadTextPos.isempty()) {
246 x = Pos.x;
247 y = Pos.y;
248 } else {
249 x = overHeadTextPos.x;
250 y = overHeadTextPos.y;
253 Region rgn( x-100+screen.x, y - cs + screen.y, 200, 400 );
254 font->Print( rgn, ( unsigned char * ) overHeadText,
255 palette?palette:core->InfoTextPalette, IE_FONT_ALIGN_CENTER | IE_FONT_ALIGN_TOP, false );
256 gamedata->FreePalette(palette);
259 void Scriptable::DelayedEvent()
261 lastRunTime = core->GetGame()->Ticks;
264 void Scriptable::ImmediateEvent()
266 lastRunTime = 0;
269 bool Scriptable::IsPC() const
271 if(Type == ST_ACTOR) {
272 if (((Actor *) this)->GetStat(IE_EA) <= EA_CHARMED) {
273 return true;
276 return false;
279 void Scriptable::ExecuteScript(int scriptCount)
281 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
282 // but not in bg2 (kill Abazigal in ar6005)
283 if (core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE) {
284 if (! (core->HasFeature(GF_CUTSCENE_AREASCRIPTS) && Type == ST_AREA)) {
285 return;
289 if ((InternalFlags & IF_NOINT) && (CurrentAction || GetNextAction())) {
290 return;
293 if (!CurrentActionInterruptable) {
294 if (!CurrentAction && !GetNextAction()) abort();
295 return;
298 // only allow death scripts to run once, hopefully?
299 // this is probably terrible logic which needs moving elsewhere
300 if ((lastRunTime != 0) && (InternalFlags & IF_JUSTDIED)) {
301 return;
304 ieDword thisTime = core->GetGame()->Ticks;
305 if (( thisTime - lastRunTime ) < 1000) {
306 return;
309 lastDelay = lastRunTime;
310 lastRunTime = thisTime;
312 bool alive = false;
314 bool continuing = false, done = false;
315 for (int i = 0;i<scriptCount;i++) {
316 //disable AI script level for actors in party when the player disabled them
317 if ((i == AI_SCRIPT_LEVEL) && Type == ST_ACTOR && ((Actor *) this)->InParty) {
318 if (core->GetGame()->ControlStatus&CS_PARTY_AI) {
319 continue;
323 GameScript *Script = Scripts[i];
324 if (Script) {
325 alive |= Script->Update(&continuing, &done);
328 /* scripts are not concurrent, see WAITPC override script for example */
329 if (done) break;
331 if (alive && UnselectableTimer) {
332 UnselectableTimer--;
333 if (!UnselectableTimer) {
334 if (Type == ST_ACTOR) {
335 ((Actor *) this)->SetCircleSize();
339 InternalFlags &= ~IF_ONCREATION;
342 void Scriptable::AddAction(Action* aC)
344 if (!aC) {
345 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
346 return;
349 InternalFlags|=IF_ACTIVE;
350 aC->IncRef();
352 // attempt to handle 'instant' actions, from instant.ids, which run immediately
353 // when added if the action queue is empty, even on actors which are Held/etc
354 if (!CurrentAction && !GetNextAction()) {
355 if (actionflags[aC->actionID] & AF_INSTANT) {
356 CurrentAction = aC;
357 GameScript::ExecuteAction( this, CurrentAction );
358 return;
362 actionQueue.push_back( aC );
365 void Scriptable::AddActionInFront(Action* aC)
367 if (!aC) {
368 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
369 return;
371 InternalFlags|=IF_ACTIVE;
372 actionQueue.push_front( aC );
373 aC->IncRef();
376 Action* Scriptable::GetNextAction() const
378 if (actionQueue.size() == 0) {
379 return NULL;
381 return actionQueue.front();
384 Action* Scriptable::PopNextAction()
386 if (actionQueue.size() == 0) {
387 return NULL;
389 Action* aC = actionQueue.front();
390 actionQueue.pop_front();
391 return aC;
394 void Scriptable::ClearActions()
396 ReleaseCurrentAction();
397 for (unsigned int i = 0; i < actionQueue.size(); i++) {
398 Action* aC = actionQueue.front();
399 actionQueue.pop_front();
400 aC->Release();
402 actionQueue.clear();
403 WaitCounter = 0;
404 LastTarget = 0;
405 //clear the triggers as fast as possible when queue ended?
406 ClearTriggers();
408 if (Type == ST_ACTOR) {
409 Interrupt();
410 } else {
411 NoInterrupt();
415 void Scriptable::ReleaseCurrentAction()
417 if (CurrentAction) {
418 CurrentAction->Release();
419 CurrentAction = NULL;
422 CurrentActionState = 0;
423 CurrentActionTarget = 0;
424 CurrentActionInterruptable = true;
427 void Scriptable::ProcessActions(bool force)
429 unsigned long thisTime = core->GetGame()->Ticks;
431 if (!force && (( thisTime - startTime ) < interval)) {
432 return;
434 startTime = thisTime;
435 if (WaitCounter) {
436 WaitCounter--;
437 if (WaitCounter) return;
440 while (true) {
441 CurrentActionInterruptable = true;
442 if (!CurrentAction) {
443 CurrentAction = PopNextAction();
445 if (!CurrentAction) {
446 ClearActions();
447 //removing the triggers at the end of the
448 //block
449 //ClearTriggers();
450 break;
452 GameScript::ExecuteAction( this, CurrentAction );
453 //break execution in case of a Wait flag
454 if (WaitCounter) {
455 //clear triggers while waiting
456 //ClearTriggers();
457 break;
459 //break execution in case of blocking action
460 if (CurrentAction) {
461 break;
463 //break execution in case of movement
464 //we should not actually break here, or else fix waypoints
465 if (InMove()) {
466 break;
469 //most likely the best place to clear triggers is here
470 //queue is empty, or there is a looong action subject to break
471 ClearTriggers();
472 if (InternalFlags&IF_IDLE) {
473 Deactivate();
477 bool Scriptable::InMove() const
479 if (Type!=ST_ACTOR) {
480 return false;
482 Movable *me = (Movable *) this;
483 return me->GetNextStep()!=NULL;
486 void Scriptable::SetWait(unsigned long time)
488 WaitCounter = time;
491 unsigned long Scriptable::GetWait() const
493 return WaitCounter;
496 void Scriptable::LeaveDialog()
498 InternalFlags |=IF_WASINDIALOG;
501 void Scriptable::Hide()
503 InternalFlags &=~(IF_VISIBLE);
506 void Scriptable::Unhide()
508 InternalFlags |= IF_VISIBLE;
511 void Scriptable::Interrupt()
513 InternalFlags &= ~IF_NOINT;
516 void Scriptable::NoInterrupt()
518 InternalFlags |= IF_NOINT;
521 //also turning off the idle flag so it won't run continuously
522 void Scriptable::Deactivate()
524 InternalFlags &=~(IF_ACTIVE|IF_IDLE);
527 //turning off the not interruptable flag, actions should reenable it themselves
528 //also turning off the idle flag
529 //heh, no, i wonder why did i touch the interruptable flag here
530 void Scriptable::Activate()
532 InternalFlags |= IF_ACTIVE;
533 InternalFlags &= ~IF_IDLE;
536 void Scriptable::PartyRested()
538 InternalFlags |=IF_PARTYRESTED;
541 ieDword Scriptable::GetInternalFlag()
543 return InternalFlags;
546 void Scriptable::InitTriggers()
548 tolist.clear();
549 bittriggers = 0;
552 void Scriptable::ClearTriggers()
554 for (TriggerObjects::iterator m = tolist.begin(); m != tolist.end (); m++) {
555 *(*m) = 0;
557 if (!bittriggers) {
558 return;
560 if (bittriggers & BT_DIE) {
561 InternalFlags &= ~IF_JUSTDIED;
563 if (bittriggers & BT_ONCREATION) {
564 InternalFlags &= ~IF_ONCREATION;
566 if (bittriggers & BT_BECAMEVISIBLE) {
567 InternalFlags &= ~IF_BECAMEVISIBLE;
569 if (bittriggers & BT_PARTYRESTED) {
570 InternalFlags &= ~IF_PARTYRESTED;
572 if (bittriggers & BT_WASINDIALOG) {
573 InternalFlags &= ~IF_WASINDIALOG;
575 if (bittriggers & BT_PARTYRESTED) {
576 InternalFlags &= ~IF_PARTYRESTED;
578 InitTriggers();
581 void Scriptable::SetBitTrigger(ieDword bittrigger)
583 bittriggers |= bittrigger;
586 void Scriptable::AddTrigger(ieDword *actorref)
588 tolist.push_back(actorref);
591 static EffectRef fx_set_invisible_state_ref={"State:Invisible",NULL,-1};
593 void Scriptable::CreateProjectile(const ieResRef SpellResRef, ieDword tgt, bool fake)
595 Spell* spl = gamedata->GetSpell( SpellResRef );
597 //PST has a weird effect, called Enoll Eva's duplication
598 //it creates every projectile of the affected actor twice
599 int duplicate = 1;
600 if (core->HasFeature(GF_PST_STATE_FLAGS) && (Type == ST_ACTOR)) {
601 if ( ((Actor *)this)->GetStat(IE_STATE_ID)&STATE_EE_DUPL) {
602 duplicate = 2;
606 while(duplicate --) {
607 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
608 if (!pro) {
609 return;
611 pro->SetCaster(GetGlobalID());
612 Point origin = Pos;
613 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
614 // try and make projectiles start from the right trap position
615 // see the traps in the duergar/assassin battle in bg2 dungeon
616 // see also function below - maybe we should fix Pos instead
617 origin = ((InfoPoint *)this)->TrapLaunch;
620 if (tgt) {
621 area->AddProjectile(pro, origin, LastTarget, fake);
622 } else {
623 area->AddProjectile(pro, origin, LastTargetPos);
627 ieDword spellnum=ResolveSpellNumber( SpellResRef );
628 if (spellnum!=0xffffffff) {
629 area->SeeSpellCast(this, spellnum);
631 // caster - Casts spellname : target OR
632 // caster - spellname : target (repeating spells)
633 Scriptable *target = NULL;
634 char tmp[100];
635 const char* msg = core->GetString(displaymsg->GetStringReference(STR_ACTION_CAST), 0);
636 const char* spell = core->GetString(spl->SpellName);
637 if (LastTarget) {
638 target = area->GetActorByGlobalID(LastTarget);
639 if (!target) {
640 target=core->GetGame()->GetActorByGlobalID(LastTarget);
643 if (stricmp(spell, "")) {
644 if (target) {
645 snprintf(tmp, sizeof(tmp), "%s %s : %s", msg, spell, target->GetName(-1));
646 } else {
647 snprintf(tmp, sizeof(tmp), "%s : %s", spell, GetName(-1));
649 displaymsg->DisplayStringName(tmp, 0xffffff, this);
652 if(LastTarget) {
653 if (target && (Type==ST_ACTOR) ) {
654 Actor *me = (Actor *) this;
655 target->LastSpellOnMe = spellnum;
656 target->LastCasterOnMe = me->GetGlobalID();
657 // don't cure invisibility if this is a self targetting invisibility spell
658 // like shadow door
659 //can't check GetEffectBlock, since it doesn't construct the queue for selftargetting spells
660 bool invis = false;
661 unsigned int opcode = EffectQueue::ResolveEffect(fx_set_invisible_state_ref);
662 for (unsigned int i=0; i < spl->ext_headers[SpellHeader].FeatureCount; i++) {
663 if (spl->GetExtHeader(SpellHeader)->features[i].Opcode == opcode) {
664 invis = true;
665 break;
668 if (invis && spl->GetExtHeader(SpellHeader)->Target == TARGET_SELF) {
669 //pass
670 } else {
671 me->CureInvisibility();
673 // sanctuary ends with all hostile actions or when the caster targets someone else
674 if (target != this || spl->Flags & SF_HOSTILE) {
675 me->CureSanctuary();
681 core->Autopause(AP_SPELLCAST);
683 gamedata->FreeSpell(spl, SpellResRef, false);
687 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef )
689 if (Type == ST_ACTOR) {
690 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
693 if (SpellHeader == -1) {
694 LastTargetPos.empty();
695 return;
698 if (LastTargetPos.isempty()) {
699 SpellHeader = -1;
700 return;
703 CreateProjectile(SpellResRef, 0, false);
705 SpellHeader = -1;
706 LastTarget = 0;
707 LastTargetPos.empty();
710 void Scriptable::CastSpellEnd( const ieResRef SpellResRef )
712 if (Type == ST_ACTOR) {
713 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
716 if (SpellHeader == -1) {
717 LastTarget = 0;
718 return;
720 if (!LastTarget) {
721 SpellHeader = -1;
722 return;
724 //if the projectile doesn't need to follow the target, then use the target position
725 CreateProjectile(SpellResRef, LastTarget, GetSpellDistance(SpellResRef, this)==0xffffffff);
726 SpellHeader = -1;
727 LastTarget = 0;
728 LastTargetPos.empty();
731 //set target as point
732 //if spell needs to be depleted, do it
733 //if spell is illegal stop casting
734 int Scriptable::CastSpellPoint( const ieResRef SpellResRef, const Point &target, bool deplete, bool instant )
736 LastTarget = 0;
737 LastTargetPos.empty();
738 if (Type == ST_ACTOR) {
739 Actor *actor = (Actor *) this;
740 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
741 return -1;
744 LastTargetPos = target;
745 return SpellCast(SpellResRef, instant);
748 //set target as actor (if target isn't actor, use its position)
749 //if spell needs to be depleted, do it
750 //if spell is illegal stop casting
751 int Scriptable::CastSpell( const ieResRef SpellResRef, Scriptable* target, bool deplete, bool instant )
753 LastTarget = 0;
754 LastTargetPos.empty();
755 if (Type == ST_ACTOR) {
756 Actor *actor = (Actor *) this;
757 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
758 return -1;
762 if (!target) target = this;
764 LastTargetPos = target->Pos;
765 if (target->Type==ST_ACTOR) {
766 LastTarget = target->GetGlobalID();
768 return SpellCast(SpellResRef, instant);
771 //start spellcasting (common part)
772 int Scriptable::SpellCast(const ieResRef SpellResRef, bool instant)
774 Spell* spl = gamedata->GetSpell( SpellResRef );
775 if (!spl) {
776 SpellHeader = -1;
777 return -1;
780 if (Type == ST_ACTOR) {
781 Actor *actor = (Actor *) this;
782 //The ext. index is here to calculate the casting time
783 int level = actor->GetXPLevel(true);
784 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
785 level += actor->CastingLevelBonus(level, spl->SpellType);
786 SpellHeader = spl->GetHeaderIndexFromLevel(level);
787 } else {
788 SpellHeader = 0;
791 SPLExtHeader *header = spl->GetExtHeader(SpellHeader);
792 int casting_time = (int)header->CastingTime;
793 // how does this work for non-actors exactly?
794 if (Type == ST_ACTOR) {
795 // The mental speed effect can shorten or lengthen the casting time.
796 casting_time -= (int)((Actor *) this)->Modified[IE_MENTALSPEED];
797 if (casting_time < 0) casting_time = 0;
799 // this is a guess which seems approximately right so far
800 int duration = (casting_time*core->Time.round_size) / 10;
801 if (instant) {
802 duration = 0;
805 //cfb
806 if (Type == ST_ACTOR) {
807 Actor *actor = (Actor *) this;
808 EffectQueue *fxqueue = spl->GetEffectBlock(this, this->Pos, -1);
809 fxqueue->SetOwner(actor);
810 if (!actor->Modified[IE_AVATARREMOVAL]) {
811 spl->AddCastingGlow(fxqueue, duration, actor->Modified[IE_SEX]);
813 fxqueue->AddAllEffects(actor, actor->Pos);
814 delete fxqueue;
817 gamedata->FreeSpell(spl, SpellResRef, false);
818 return duration;
821 bool Scriptable::TimerActive(ieDword ID)
823 if (ID>=MAX_TIMER) {
824 return false;
826 if (script_timers[ID]) {
827 return true;
829 return false;
832 bool Scriptable::TimerExpired(ieDword ID)
834 if (ID>=MAX_TIMER) {
835 return false;
837 if (script_timers[ID] && script_timers[ID] < core->GetGame()->GameTime) {
838 // expired timers become inactive after being checked
839 script_timers[ID] = 0;
840 return true;
842 return false;
845 void Scriptable::StartTimer(ieDword ID, ieDword expiration)
847 if (ID>=MAX_TIMER) {
848 printMessage("Scriptable", " ", RED);
849 printf("Timer id %d exceeded MAX_TIMER %d\n", ID, MAX_TIMER);
850 return;
852 script_timers[ID]= core->GetGame()->GameTime + expiration*AI_UPDATE_TIME;
855 /********************
856 * Selectable Class *
857 ********************/
859 Selectable::Selectable(ScriptableType type)
860 : Scriptable( type )
862 Selected = false;
863 Over = false;
864 size = 0;
865 cover = NULL;
866 circleBitmap[0] = NULL;
867 circleBitmap[1] = NULL;
870 void Selectable::SetSpriteCover(SpriteCover* c)
872 delete cover;
873 cover = c;
876 Selectable::~Selectable(void)
878 delete cover;
881 void Selectable::SetBBox(const Region &newBBox)
883 BBox = newBBox;
886 static const unsigned long tp_steps[8]={3,2,1,0,1,2,3,4};
888 void Selectable::DrawCircle(const Region &vp)
890 /* BG2 colours ground circles as follows:
891 dark green for unselected party members
892 bright green for selected party members
893 flashing green/white for a party member the mouse is over
894 bright red for enemies
895 yellow for panicked actors
896 flashing red/white for enemies the mouse is over
897 flashing cyan/white for neutrals the mouse is over
900 if (size<=0) {
901 return;
903 Color mix;
904 Color* col = &selectedColor;
905 Sprite2D* sprite = circleBitmap[0];
907 if (Selected) {
908 sprite = circleBitmap[1];
909 } else if (Over) {
910 //doing a time dependent flashing of colors
911 //if it is too fast, increase the 6 to 7
912 unsigned long step;
913 GetTime( step );
914 step = tp_steps [(step >> 6) & 7];
915 mix.a = overColor.a;
916 mix.r = (overColor.r*step+selectedColor.r*(8-step))/8;
917 mix.g = (overColor.g*step+selectedColor.g*(8-step))/8;
918 mix.b = (overColor.b*step+selectedColor.b*(8-step))/8;
919 col = &mix;
920 } else if (IsPC()) {
921 col = &overColor;
924 if (sprite) {
925 core->GetVideoDriver()->BlitSprite( sprite, Pos.x - vp.x, Pos.y - vp.y, true );
926 } else {
927 // for size >= 2, radii are (size-1)*16, (size-1)*12
928 // for size == 1, radii are 12, 9
929 int csize = (size - 1) * 4;
930 if (csize < 4) csize = 3;
931 core->GetVideoDriver()->DrawEllipse( (ieWord) (Pos.x - vp.x), (ieWord) (Pos.y - vp.y),
932 (ieWord) (csize * 4), (ieWord) (csize * 3), *col );
936 // Check if P is over our ground circle
937 bool Selectable::IsOver(const Point &P) const
939 int csize = size;
940 if (csize < 2) csize = 2;
942 int dx = P.x - Pos.x;
943 int dy = P.y - Pos.y;
945 // check rectangle first
946 if (dx < -(csize-1)*16 || dx > (csize-1)*16) return false;
947 if (dy < -(csize-1)*12 || dy > (csize-1)*12) return false;
949 // then check ellipse
950 int r = 9*dx*dx + 16*dy*dy; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
952 return (r <= 48*48*(csize-1)*(csize-1));
955 bool Selectable::IsSelected() const
957 return Selected == 1;
960 void Selectable::SetOver(bool over)
962 Over = over;
965 //don't call this function after rendering the cover and before the
966 //blitting of the sprite or bad things will happen :)
967 void Selectable::Select(int Value)
969 if (Selected!=0x80 || Value!=1) {
970 Selected = (ieWord) Value;
972 //forcing regeneration of the cover
973 SetSpriteCover(NULL);
976 void Selectable::SetCircle(int circlesize, const Color &color, Sprite2D* normal_circle, Sprite2D* selected_circle)
978 size = circlesize;
979 selectedColor = color;
980 overColor.r = color.r >> 1;
981 overColor.g = color.g >> 1;
982 overColor.b = color.b >> 1;
983 overColor.a = color.a;
984 circleBitmap[0] = normal_circle;
985 circleBitmap[1] = selected_circle;
988 //used for creatures
989 int Selectable::WantDither()
991 //if dithering is disabled globally, don't do it
992 if (core->FogOfWar&4) {
993 return 0;
995 //if actor is dead, dither it if polygon wants
996 if (Selected&0x80) {
997 return 1;
999 //if actor is selected dither it
1000 if (Selected) {
1001 return 2;
1003 return 1;
1006 /***********************
1007 * Highlightable Class *
1008 ***********************/
1010 Highlightable::Highlightable(ScriptableType type)
1011 : Scriptable( type )
1013 outline = NULL;
1014 Highlight = false;
1015 Cursor = IE_CURSOR_NORMAL;
1016 KeyResRef[0] = 0;
1019 Highlightable::~Highlightable(void)
1021 if (outline) {
1022 delete( outline );
1026 bool Highlightable::IsOver(const Point &Pos) const
1028 if (!outline) {
1029 return false;
1031 return outline->PointIn( Pos );
1034 void Highlightable::DrawOutline() const
1036 if (!outline) {
1037 return;
1039 core->GetVideoDriver()->DrawPolyline( outline, outlineColor, true );
1042 void Highlightable::SetCursor(unsigned char CursorIndex)
1044 Cursor = CursorIndex;
1047 bool Highlightable::TryUnlock(Actor *actor, bool removekey) {
1048 const char *Key = GetKey();
1049 Actor *haskey = NULL;
1051 if (Key && actor->InParty) {
1052 Game *game = core->GetGame();
1053 //allow unlock when the key is on any partymember
1054 for (int idx = 0; idx < game->GetPartySize(false); idx++) {
1055 Actor *pc = game->FindPC(idx + 1);
1056 if (!pc) continue;
1058 if (pc->inventory.HasItem(Key,0) ) {
1059 haskey = pc;
1060 break;
1063 } else if (Key) {
1064 //actor is not in party, check only actor
1065 if (actor->inventory.HasItem(Key,0) ) {
1066 haskey = actor;
1070 if (!haskey) {
1071 return false;
1074 if (removekey) {
1075 CREItem *item = NULL;
1076 haskey->inventory.RemoveItem(Key,0,&item);
1077 //the item should always be existing!!!
1078 if (item) {
1079 delete item;
1083 return true;
1087 /*****************
1088 * Movable Class *
1089 *****************/
1091 Movable::Movable(ScriptableType type)
1092 : Selectable( type )
1094 Destination = Pos;
1095 Orientation = 0;
1096 NewOrientation = 0;
1097 StanceID = 0;
1098 path = NULL;
1099 step = NULL;
1100 timeStartStep = 0;
1101 lastFrame = NULL;
1102 Area[0] = 0;
1103 AttackMovements[0] = 100;
1104 AttackMovements[1] = 0;
1105 AttackMovements[2] = 0;
1108 Movable::~Movable(void)
1110 if (path) {
1111 ClearPath();
1115 int Movable::GetPathLength()
1117 PathNode *node = GetNextStep(0);
1118 int i = 0;
1119 while (node->Next) {
1120 i++;
1121 node = node->Next;
1123 return i;
1126 PathNode *Movable::GetNextStep(int x)
1128 if (!step) {
1129 DoStep((unsigned int) ~0);
1131 PathNode *node = step;
1132 while(node && x--) {
1133 node = node->Next;
1135 return node;
1138 Point Movable::GetMostLikelyPosition()
1140 if (!path) {
1141 return Pos;
1144 //actually, sometimes middle path would be better, if
1145 //we stand in Destination already
1146 int halfway = GetPathLength()/2;
1147 PathNode *node = GetNextStep(halfway);
1148 if (node) {
1149 return Point((ieWord) ((node->x*16)+8), (ieWord) ((node->y*12)+6) );
1151 return Destination;
1154 void Movable::SetStance(unsigned int arg)
1156 //don't modify stance from dead back to anything if the actor is dead
1157 if ((StanceID==IE_ANI_TWITCH || StanceID==IE_ANI_DIE) && (arg!=IE_ANI_TWITCH) ) {
1158 if (GetInternalFlag()&IF_REALLYDIED) {
1159 printMessage("Movable","Stance overridden by death\n", YELLOW);
1160 return;
1164 if (StanceID == IE_ANI_CONJURE && StanceID != arg && Type ==ST_ACTOR) {
1165 Actor *caster = (Actor *) this;
1166 if (caster->casting_sound) {
1167 caster->casting_sound->Stop();
1168 caster->casting_sound.release();
1172 if (arg<MAX_ANIMS) {
1173 StanceID=(unsigned char) arg;
1175 if (StanceID == IE_ANI_ATTACK) {
1176 // Set stance to a random attack animation
1178 int random = rand()%100;
1179 if (random < AttackMovements[0]) {
1180 StanceID = IE_ANI_ATTACK_BACKSLASH;
1181 } else if (random < AttackMovements[0] + AttackMovements[1]) {
1182 StanceID = IE_ANI_ATTACK_SLASH;
1183 } else {
1184 StanceID = IE_ANI_ATTACK_JAB;
1188 } else {
1189 StanceID=IE_ANI_AWAKE; //
1190 printf("Tried to set invalid stance id (%u)\n", arg);
1194 void Movable::SetAttackMoveChances(ieWord *amc)
1196 AttackMovements[0]=amc[0];
1197 AttackMovements[1]=amc[1];
1198 AttackMovements[2]=amc[2];
1203 //this could be used for WingBuffet as well
1204 void Movable::MoveLine(int steps, int Pass, ieDword orient)
1206 //remove previous path
1207 ClearPath();
1208 if (!steps)
1209 return;
1210 Point p = Pos;
1211 p.x/=16;
1212 p.y/=14;
1213 path = area->GetLine( p, steps, orient, Pass );
1216 void AdjustPositionTowards(Point &Pos, ieDword time_diff, unsigned int walk_speed, short srcx, short srcy, short destx, short desty) {
1217 if (destx > srcx)
1218 Pos.x += ( unsigned short )
1219 ( ( ( ( ( destx * 16 ) + 8 ) - Pos.x ) * ( time_diff ) ) / walk_speed );
1220 else
1221 Pos.x -= ( unsigned short )
1222 ( ( ( Pos.x - ( ( destx * 16 ) + 8 ) ) * ( time_diff ) ) / walk_speed );
1223 if (desty > srcy)
1224 Pos.y += ( unsigned short )
1225 ( ( ( ( ( desty * 12 ) + 6 ) - Pos.y ) * ( time_diff ) ) / walk_speed );
1226 else
1227 Pos.y -= ( unsigned short )
1228 ( ( ( Pos.y - ( ( desty * 12 ) + 6 ) ) * ( time_diff ) ) / walk_speed );
1231 // returns whether we made all pending steps (so, false if we must be called again this tick)
1232 // we can't just do them all here because the caller might have to update searchmap etc
1233 bool Movable::DoStep(unsigned int walk_speed, ieDword time)
1235 if (!path) {
1236 return true;
1238 if (!time) time = core->GetGame()->Ticks;
1239 if (!walk_speed) {
1240 // zero speed: no movement
1241 timeStartStep = time;
1242 StanceID = IE_ANI_READY;
1243 return true;
1245 if (!step) {
1246 step = path;
1247 timeStartStep = time;
1248 } else if (step->Next && (( time - timeStartStep ) >= walk_speed)) {
1249 //printf("[New Step] : Orientation = %d\n", step->orient);
1250 step = step->Next;
1251 timeStartStep = timeStartStep + walk_speed;
1253 SetOrientation (step->orient, false);
1254 StanceID = IE_ANI_WALK;
1255 if ((Type == ST_ACTOR) && (InternalFlags & IF_RUNNING)) {
1256 StanceID = IE_ANI_RUN;
1258 Pos.x = ( step->x * 16 ) + 8;
1259 Pos.y = ( step->y * 12 ) + 6;
1260 if (!step->Next) {
1261 // we reached our destination, we are done
1262 ClearPath();
1263 NewOrientation = Orientation;
1264 //since clearpath no longer sets currentaction to NULL
1265 //we set it here
1266 //no we don't, action is responsible for releasing itself
1267 //ReleaseCurrentAction();
1268 return true;
1270 if (( time - timeStartStep ) >= walk_speed) {
1271 // we didn't finish all pending steps, yet
1272 return false;
1274 AdjustPositionTowards(Pos, time - timeStartStep, walk_speed, step->x, step->y, step->Next->x, step->Next->y);
1275 return true;
1278 void Movable::AddWayPoint(const Point &Des)
1280 if (!path) {
1281 WalkTo(Des);
1282 return;
1284 Destination = Des;
1285 //it is tempting to use 'step' here, as it could
1286 //be about half of the current path already
1287 PathNode *endNode = path;
1288 while(endNode->Next) {
1289 endNode = endNode->Next;
1291 Point p(endNode->x, endNode->y);
1292 area->ClearSearchMapFor(this);
1293 PathNode *path2 = area->FindPath( p, Des, size );
1294 endNode->Next = path2;
1295 //probably it is wise to connect it both ways?
1296 path2->Parent = endNode;
1299 void Movable::FixPosition()
1301 if (Type!=ST_ACTOR) {
1302 return;
1304 Actor *actor = (Actor *) this;
1305 if (actor->GetStat(IE_DONOTJUMP)&DNJ_BIRD ) {
1306 return;
1308 //before fixposition, you should remove own shadow
1309 area->ClearSearchMapFor(this);
1310 Pos.x/=16;
1311 Pos.y/=12;
1312 GetCurrentArea()->AdjustPosition(Pos);
1313 Pos.x=Pos.x*16+8;
1314 Pos.y=Pos.y*12+6;
1317 void Movable::WalkTo(const Point &Des, int distance)
1319 Point from;
1321 // maybe caller should be responsible for this
1322 if ((Des.x/16 == Pos.x/16) && (Des.y/12 == Pos.y/12)) {
1323 ClearPath();
1324 return;
1327 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1328 PathNode *prev_step = NULL;
1329 unsigned char old_stance = StanceID;
1330 if (step && step->Next) {
1331 // don't interrupt in the middle of a step; path from the next one
1332 prev_step = new PathNode(*step);
1333 from.x = ( step->Next->x * 16 ) + 8;
1334 from.y = ( step->Next->y * 12 ) + 6;
1337 ClearPath();
1338 if (!prev_step) {
1339 FixPosition();
1340 from = Pos;
1342 area->ClearSearchMapFor(this);
1343 if (distance) {
1344 path = area->FindPathNear( from, Des, size, distance );
1345 } else {
1346 path = area->FindPath( from, Des, size, distance );
1348 //ClearPath sets destination, so Destination must be set after it
1349 //also we should set Destination only if there is a walkable path
1350 if (path) {
1351 Destination = Des;
1353 if (prev_step) {
1354 // we want to smoothly continue, please
1355 // this all needs more thought! but it seems to work okay
1356 StanceID = old_stance;
1358 if (path->Next) {
1359 // this is a terrible hack to make up for the
1360 // pathfinder orienting the first node wrong
1361 // should be fixed in pathfinder and not here!
1362 Point next, follow;
1363 next.x = path->x; next.y = path->y;
1364 follow.x = path->Next->x;
1365 follow.y = path->Next->y;
1366 path->orient = GetOrient(follow, next);
1369 // then put the prev_step at the beginning of the path
1370 prev_step->Next = path;
1371 path->Parent = prev_step;
1372 path = prev_step;
1374 step = path;
1376 } else {
1377 // pathing failed
1378 if (prev_step) {
1379 delete( prev_step );
1380 FixPosition();
1385 void Movable::RunAwayFrom(const Point &Des, int PathLength, int flags)
1387 ClearPath();
1388 area->ClearSearchMapFor(this);
1389 path = area->RunAway( Pos, Des, size, PathLength, flags );
1392 void Movable::RandomWalk(bool can_stop, bool run)
1394 if (path) {
1395 return;
1397 //if not continous random walk, then stops for a while
1398 if (can_stop && (rand()&3) ) {
1399 SetWait((rand()&7)+7);
1400 return;
1402 if (run) {
1403 InternalFlags|=IF_RUNNING;
1405 //the commenting-out of the clear search map call was removed in 0.4.0
1406 //if you want to put it back for some reason, check
1407 //if the searchmap is not eaten up
1408 area->ClearSearchMapFor(this);
1409 Point p = Pos;
1411 //selecting points around a circle's edge around actor (didn't work better)
1412 //int x = core->Roll(1,100,-50);
1413 //p.x+=x;
1414 //p.y+=(int) sqrt(100-x*x);
1416 //selecting points in a square around actor
1417 p.x+=core->Roll(1,50,-25);
1418 p.y+=core->Roll(1,50,-25);
1419 //the 5th parameter is controlling the orientation of the actor
1420 //0 - back away, 1 - face direction
1421 path = area->RunAway( Pos, p, size, 50, 1 );
1424 void Movable::MoveTo(const Point &Des)
1426 area->ClearSearchMapFor(this);
1427 Pos = Des;
1428 Destination = Des;
1429 if (BlocksSearchMap()) {
1430 area->BlockSearchMap( Pos, size, IsPC()?PATH_MAP_PC:PATH_MAP_NPC);
1434 void Movable::ClearPath()
1436 //this is to make sure attackers come to us
1437 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1438 //is set before ClearPath
1439 Destination = Pos;
1440 if (StanceID==IE_ANI_WALK || StanceID==IE_ANI_RUN) {
1441 StanceID = IE_ANI_AWAKE;
1443 InternalFlags&=~IF_NORECTICLE;
1444 PathNode* thisNode = path;
1445 while (thisNode) {
1446 PathNode* nextNode = thisNode->Next;
1447 delete( thisNode );
1448 thisNode = nextNode;
1450 path = NULL;
1451 step = NULL;
1452 //don't call ReleaseCurrentAction
1455 void Movable::DrawTargetPoint(const Region &vp)
1457 if (!path || !Selected || (InternalFlags&IF_NORECTICLE) )
1458 return;
1460 // recticles are never drawn in cutscenes
1461 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE))
1462 return;
1464 // generates "step" from sequence 3 2 1 0 1 2 3 4
1465 // updated each 1/15 sec
1466 unsigned long step;
1467 GetTime( step );
1468 step = tp_steps [(step >> 6) & 7];
1470 step = step + 1;
1471 int csize = (size - 1) * 4;
1472 if (csize < 4) csize = 3;
1474 /* segments should not go outside selection radius */
1475 unsigned short xradius = (csize * 4) - 5;
1476 unsigned short yradius = (csize * 3) - 5;
1477 ieWord xcentre = (ieWord) (Destination.x - vp.x);
1478 ieWord ycentre = (ieWord) (Destination.y - vp.y);
1480 // TODO: 0.5 and 0.7 are pretty much random values
1481 // right segment
1482 core->GetVideoDriver()->DrawEllipseSegment( xcentre + step, ycentre, xradius,
1483 yradius, selectedColor, -0.5, 0.5 );
1484 // top segment
1485 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre - step, xradius,
1486 yradius, selectedColor, -0.7 - M_PI_2, 0.7 - M_PI_2 );
1487 // left segment
1488 core->GetVideoDriver()->DrawEllipseSegment( xcentre - step, ycentre, xradius,
1489 yradius, selectedColor, -0.5 - M_PI, 0.5 - M_PI );
1490 // bottom segment
1491 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre + step, xradius,
1492 yradius, selectedColor, -0.7 - M_PI - M_PI_2, 0.7 - M_PI - M_PI_2 );
1495 /**********************
1496 * Tiled Object Class *
1497 **********************/
1499 TileObject::TileObject()
1501 opentiles = NULL;
1502 opencount = 0;
1503 closedtiles = NULL;
1504 closedcount = 0;
1505 Flags = 0;
1508 TileObject::~TileObject()
1510 if (opentiles) {
1511 free( opentiles );
1513 if (closedtiles) {
1514 free( closedtiles );
1518 void TileObject::SetOpenTiles(unsigned short* Tiles, int cnt)
1520 if (opentiles) {
1521 free( opentiles );
1523 opentiles = Tiles;
1524 opencount = cnt;
1527 void TileObject::SetClosedTiles(unsigned short* Tiles, int cnt)
1529 if (closedtiles) {
1530 free( closedtiles );
1532 closedtiles = Tiles;
1533 closedcount = cnt;
1536 /**************
1537 * Door Class *
1538 **************/
1540 Door::Door(TileOverlay* Overlay)
1541 : Highlightable( ST_DOOR )
1543 tiles = NULL;
1544 tilecount = 0;
1545 Flags = 0;
1546 open = NULL;
1547 closed = NULL;
1548 open_ib = NULL;
1549 oibcount = 0;
1550 closed_ib = NULL;
1551 cibcount = 0;
1552 OpenSound[0] = 0;
1553 CloseSound[0] = 0;
1554 LockSound[0] = 0;
1555 UnLockSound[0] = 0;
1556 overlay = Overlay;
1557 LinkedInfo[0] = 0;
1558 OpenStrRef = (ieDword) -1;
1561 Door::~Door(void)
1563 if (Flags&DOOR_OPEN) {
1564 if (closed) {
1565 delete( closed );
1567 } else {
1568 if (open) {
1569 delete( open );
1572 if (tiles) {
1573 free( tiles );
1575 if (open_ib) {
1576 free( open_ib );
1578 if (closed_ib) {
1579 free( closed_ib );
1583 void Door::ImpedeBlocks(int count, Point *points, unsigned char value)
1585 for(int i = 0;i<count;i++) {
1586 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_NOTDOOR;
1587 area->SearchMap->SetAt( points[i].x, points[i].y, (tmp|value) );
1591 void Door::UpdateDoor()
1593 if (Flags&DOOR_OPEN) {
1594 outline = open;
1595 } else {
1596 outline = closed;
1598 // update the Scriptable position
1599 Pos.x = outline->BBox.x + outline->BBox.w/2;
1600 Pos.y = outline->BBox.y + outline->BBox.h/2;
1602 unsigned char oval, cval;
1603 oval = PATH_MAP_IMPASSABLE;
1604 if (Flags & DOOR_TRANSPARENT) {
1605 cval = PATH_MAP_DOOR_TRANSPARENT;
1607 else {
1608 cval = PATH_MAP_DOOR_OPAQUE;
1610 if (Flags &DOOR_OPEN) {
1611 ImpedeBlocks(cibcount, closed_ib, 0);
1612 ImpedeBlocks(oibcount, open_ib, cval);
1614 else {
1615 ImpedeBlocks(oibcount, open_ib, 0);
1616 ImpedeBlocks(cibcount, closed_ib, cval);
1619 InfoPoint *ip = area->TMap->GetInfoPoint(LinkedInfo);
1620 if (ip) {
1621 if (Flags&DOOR_OPEN) ip->Flags&=~INFO_DOOR;
1622 else ip->Flags|=INFO_DOOR;
1626 void Door::ToggleTiles(int State, int playsound)
1628 int i;
1629 int state;
1631 if (State) {
1632 state = !closedIndex;
1633 if (playsound && ( OpenSound[0] != '\0' ))
1634 core->GetAudioDrv()->Play( OpenSound );
1635 } else {
1636 state = closedIndex;
1637 if (playsound && ( CloseSound[0] != '\0' ))
1638 core->GetAudioDrv()->Play( CloseSound );
1640 for (i = 0; i < tilecount; i++) {
1641 overlay->tiles[tiles[i]]->tileIndex = (ieByte) state;
1644 //set door_open as state
1645 Flags = (Flags & ~DOOR_OPEN) | (State == !core->HasFeature(GF_REVERSE_DOOR) );
1648 //this is the short name (not the scripting name)
1649 void Door::SetName(const char* name)
1651 strnlwrcpy( ID, name, 8 );
1654 void Door::SetTiles(unsigned short* Tiles, int cnt)
1656 if (tiles) {
1657 free( tiles );
1659 tiles = Tiles;
1660 tilecount = cnt;
1663 void Door::SetDoorLocked(int Locked, int playsound)
1665 if (Locked) {
1666 if (Flags & DOOR_LOCKED) return;
1667 Flags|=DOOR_LOCKED;
1668 if (playsound && ( LockSound[0] != '\0' ))
1669 core->GetAudioDrv()->Play( LockSound );
1671 else {
1672 if (!(Flags & DOOR_LOCKED)) return;
1673 Flags&=~DOOR_LOCKED;
1674 if (playsound && ( UnLockSound[0] != '\0' ))
1675 core->GetAudioDrv()->Play( UnLockSound );
1679 int Door::IsOpen() const
1681 int ret = core->HasFeature(GF_REVERSE_DOOR);
1682 if (Flags&DOOR_OPEN) {
1683 ret = !ret;
1685 return ret;
1688 //also mark actors to fix position
1689 bool Door::BlockedOpen(int Open, int ForceOpen)
1691 bool blocked;
1692 int count;
1693 Point *points;
1695 blocked = false;
1696 if (Open) {
1697 count = oibcount;
1698 points = open_ib;
1699 } else {
1700 count = cibcount;
1701 points = closed_ib;
1703 //getting all impeded actors flagged for jump
1704 Region rgn;
1705 rgn.w = 16;
1706 rgn.h = 12;
1707 for(int i = 0;i<count;i++) {
1708 Actor** ab;
1709 rgn.x = points[i].x*16;
1710 rgn.y = points[i].y*12;
1711 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_ACTOR;
1712 if (tmp) {
1713 int ac = area->GetActorInRect(ab, rgn, false);
1714 while(ac--) {
1715 if (ab[ac]->GetBase(IE_DONOTJUMP)) {
1716 continue;
1718 ab[ac]->SetBase(IE_DONOTJUMP, DNJ_JUMP);
1719 blocked = true;
1721 if (ab) {
1722 free(ab);
1727 if ((Flags&DOOR_SLIDE) || ForceOpen) {
1728 return false;
1730 return blocked;
1733 void Door::SetDoorOpen(int Open, int playsound, ieDword ID)
1735 if (playsound) {
1736 //the door cannot be blocked when opening,
1737 //but the actors will be pushed
1738 //BlockedOpen will mark actors to be pushed
1739 if (BlockedOpen(Open,0) && !Open) {
1740 //clear up the blocking actors
1741 area->JumpActors(false);
1742 return;
1744 area->JumpActors(true);
1746 if (Open) {
1747 LastEntered = ID; //used as lastOpener
1749 // in PS:T, opening a door does not unlock it
1750 if (!core->HasFeature(GF_REVERSE_DOOR)) {
1751 SetDoorLocked(false,playsound);
1753 } else {
1754 LastTriggerObject = LastTrigger = ID; //used as lastCloser
1756 ToggleTiles(Open, playsound);
1757 //synchronising other data with the door state
1758 UpdateDoor();
1759 area->ActivateWallgroups(open_wg_index, open_wg_count, Flags&DOOR_OPEN);
1760 area->ActivateWallgroups(closed_wg_index, closed_wg_count, !(Flags&DOOR_OPEN));
1763 bool Door::TryUnlock(Actor *actor) {
1764 if (!(Flags&DOOR_LOCKED)) return true;
1766 // don't remove key in PS:T!
1767 bool removekey = !core->HasFeature(GF_REVERSE_DOOR) && Flags&DOOR_KEY;
1768 return Highlightable::TryUnlock(actor, removekey);
1771 void Door::TryDetectSecret(int skill)
1773 if (Type != ST_DOOR) return;
1774 if (Visible()) return;
1775 if (skill > (signed)DiscoveryDiff) {
1776 Flags |= DOOR_FOUND;
1777 core->PlaySound(DS_FOUNDSECRET);
1781 // return true if the door isn't secret or if it is, but was already discovered
1782 bool Door::Visible()
1784 return (!(Flags & DOOR_SECRET) || (Flags & DOOR_FOUND));
1787 void Door::SetPolygon(bool Open, Gem_Polygon* poly)
1789 if (Open) {
1790 if (open)
1791 delete( open );
1792 open = poly;
1793 } else {
1794 if (closed)
1795 delete( closed );
1796 closed = poly;
1800 void Door::SetNewOverlay(TileOverlay *Overlay) {
1801 overlay = Overlay;
1802 ToggleTiles(IsOpen(), false);
1805 void Highlightable::SetTrapDetected(int x)
1807 if(x == TrapDetected)
1808 return;
1809 TrapDetected = x;
1810 if(TrapDetected) {
1811 core->Autopause(AP_TRAP);
1815 void Highlightable::TryDisarm(Actor *actor)
1817 if (!Trapped || !TrapDetected) return;
1819 LastTriggerObject = LastTrigger = actor->GetGlobalID();
1820 int skill = actor->GetStat(IE_TRAPS);
1822 if (skill/2+core->Roll(1,skill/2,0)>TrapRemovalDiff) {
1823 LastDisarmed = actor->GetGlobalID();
1824 //trap removed
1825 Trapped = 0;
1826 displaymsg->DisplayConstantStringName(STR_DISARM_DONE, 0xd7d7be, actor);
1827 int xp = actor->CalculateExperience(XP_DISARM, actor->GetXPLevel(1));
1828 Game *game = core->GetGame();
1829 game->ShareXP(xp, SX_DIVIDE);
1830 } else {
1831 displaymsg->DisplayConstantStringName(STR_DISARM_FAIL, 0xd7d7be, actor);
1832 TriggerTrap(skill, LastTrigger);
1834 ImmediateEvent();
1837 void Door::TryPickLock(Actor *actor)
1839 if (LockDifficulty == 100) {
1840 if (OpenStrRef != (ieDword)-1) {
1841 displaymsg->DisplayStringName(OpenStrRef, 0xbcefbc, actor, IE_STR_SOUND|IE_STR_SPEECH);
1842 } else {
1843 displaymsg->DisplayConstantStringName(STR_DOOR_NOPICK, 0xbcefbc, actor);
1845 return;
1847 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
1848 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
1849 LastPickLockFailed = actor->GetGlobalID();
1850 return;
1852 SetDoorLocked( false, true);
1853 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
1854 LastUnlocked = actor->GetGlobalID();
1855 ImmediateEvent();
1856 int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
1857 Game *game = core->GetGame();
1858 game->ShareXP(xp, SX_DIVIDE);
1861 void Door::TryBashLock(Actor *actor)
1863 //Get the strength bonus agains lock difficulty
1864 int str = actor->GetStat(IE_STR);
1865 int strEx = actor->GetStat(IE_STREXTRA);
1866 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
1867 unsigned int roll = actor->LuckyRoll(1, 10, bonus, 0);
1869 if(roll < LockDifficulty || LockDifficulty == 100) {
1870 displaymsg->DisplayConstantStringName(STR_DOORBASH_FAIL, 0xbcefbc, actor);
1871 return;
1874 displaymsg->DisplayConstantStringName(STR_DOORBASH_DONE, 0xd7d7be, actor);
1875 SetDoorLocked(false, true);
1876 //Is this really useful ?
1877 LastUnlocked = actor->GetGlobalID();
1878 ImmediateEvent();
1881 void Door::DebugDump() const
1883 printf( "Debugdump of Door %s:\n", GetScriptName() );
1884 printf( "Door Global ID: %d\n", GetGlobalID());
1885 printf( "Position: %d.%d\n", Pos.x, Pos.y);
1886 printf( "Door Open: %s\n", YESNO(IsOpen()));
1887 printf( "Door Locked: %s\n", YESNO(Flags&DOOR_LOCKED));
1888 printf( "Door Trapped: %s\n", YESNO(Trapped));
1889 if (Trapped) {
1890 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags&DOOR_RESET), YESNO(Flags&DOOR_DETECTABLE) );
1892 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags&DOOR_SECRET),YESNO(Flags&DOOR_FOUND));
1893 const char *Key = GetKey();
1894 const char *name = "NONE";
1895 if (Scripts[0]) {
1896 name = Scripts[0]->GetName();
1898 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name, Key?Key:"NONE", YESNO(Flags&DOOR_KEY), Dialog );
1901 /*******************
1902 * InfoPoint Class *
1903 *******************/
1905 InfoPoint::InfoPoint(void)
1906 : Highlightable( ST_TRIGGER )
1908 Destination[0] = 0;
1909 EntranceName[0] = 0;
1910 Flags = 0;
1911 TrapDetectionDiff = 0;
1912 TrapRemovalDiff = 0;
1913 TrapDetected = 0;
1914 TrapLaunch.empty();
1915 Dialog[0] = 0;
1918 InfoPoint::~InfoPoint(void)
1922 //checks if the actor may use this travel trigger
1923 //bit 1 : can use
1924 //bit 2 : whole team
1925 int InfoPoint::CheckTravel(Actor *actor)
1927 if (Flags&TRAP_DEACTIVATED) return CT_CANTMOVE;
1928 if (!actor->InParty && (Flags&TRAVEL_NONPC) ) return CT_CANTMOVE;
1929 if (actor->InParty && (Flags&TRAVEL_PARTY) ) {
1930 if (core->HasFeature(GF_TEAM_MOVEMENT) || core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE) ) {
1931 return CT_WHOLE;
1933 return CT_GO_CLOSER;
1935 if(actor->IsSelected() ) {
1936 if(core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE|ENP_ONLYSELECT) ) {
1937 return CT_MOVE_SELECTED;
1939 return CT_SELECTED;
1941 return CT_ACTIVE;
1944 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1945 //skill is the all around modified trap detection skill
1946 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1947 void Highlightable::DetectTrap(int skill)
1949 if (!CanDetectTrap()) return;
1950 if (!Scripts[0]) return;
1951 if ((skill>=100) && (skill!=256) ) skill = 100;
1952 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1953 SetTrapDetected(1); //probably could be set to the player #?
1957 bool Highlightable::PossibleToSeeTrap() const
1959 return CanDetectTrap();
1962 bool InfoPoint::PossibleToSeeTrap() const
1964 // Only detectable trap-type infopoints.
1965 return (CanDetectTrap() && (Type == ST_PROXIMITY) );
1968 bool InfoPoint::CanDetectTrap() const
1970 // Traps can be detected on all types of infopoint, as long
1971 // as the trap is detectable and isn't deactivated.
1972 return ((Flags&TRAP_DETECTABLE) && !(Flags&TRAP_DEACTIVATED));
1975 // returns true if the infopoint is a PS:T portal
1976 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1977 bool InfoPoint::IsPortal() const
1979 if (Type!=ST_TRAVEL) return false;
1980 if (Cursor != IE_CURSOR_PORTAL) return false;
1981 return core->HasFeature(GF_REVERSE_DOOR);
1984 //trap that is visible on screen (marked by red)
1985 //if TrapDetected is a bitflag, we could show traps selectively for
1986 //players, really nice for multiplayer
1987 bool Highlightable::VisibleTrap(int see_all) const
1989 if (!Trapped) return false;
1990 if (!PossibleToSeeTrap()) return false;
1991 if (!Scripts[0]) return false;
1992 if (see_all) return true;
1993 if (TrapDetected ) return true;
1994 return false;
1997 //trap that will fire now
1998 bool Highlightable::TriggerTrap(int /*skill*/, ieDword ID)
2000 if (!Trapped) {
2001 return false;
2003 //actually this could be script name[0]
2004 if (!Scripts[0]) {
2005 return false;
2007 LastTriggerObject = LastTrigger = LastEntered = ID;
2008 ImmediateEvent();
2009 if (!TrapResets()) {
2010 Trapped = false;
2012 return true;
2015 //trap that will fire now
2016 bool InfoPoint::TriggerTrap(int skill, ieDword ID)
2018 if (Type!=ST_PROXIMITY) {
2019 return true;
2021 if (Flags&TRAP_DEACTIVATED) {
2022 return false;
2024 if (!Trapped) {
2025 // we have to set Entered somewhere, here seems best..
2026 LastEntered = ID;
2027 return true;
2028 } else if (Highlightable::TriggerTrap(skill, ID)) {
2029 if (!Trapped) {
2030 Flags|=TRAP_DEACTIVATED;
2032 // ok, so this is a pain. Entered() trigger checks Trapped,
2033 // so it needs to be kept set. how to do this right?
2034 Trapped = true;
2035 return true;
2037 return false;
2040 bool InfoPoint::Entered(Actor *actor)
2042 if (outline->PointIn( actor->Pos ) ) {
2043 //don't trigger again for this actor
2044 if (!(actor->GetInternalFlag()&IF_INTRAP)) {
2045 goto check;
2048 // why is this here? actors which aren't *in* a trap get IF_INTRAP
2049 // repeatedly unset, so this triggers again and again and again.
2050 // i disabled it for ST_PROXIMITY for now..
2051 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
2052 goto check;
2054 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
2055 if (Type == ST_TRAVEL && PersonalDistance(TrapLaunch, actor)<MAX_OPERATING_DISTANCE) {
2056 goto check;
2058 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
2059 // for distance from the outline for travel regions instead?
2060 if (Type == ST_TRAVEL && PersonalDistance(TalkPos, actor)<MAX_OPERATING_DISTANCE) {
2061 goto check;
2063 if (Flags&TRAP_USEPOINT) {
2064 if (PersonalDistance(UsePoint, actor)<MAX_OPERATING_DISTANCE) {
2065 goto check;
2068 return false;
2069 check:
2070 if (Type==ST_TRAVEL) {
2071 return true;
2074 if (actor->InParty || (Flags&TRAP_NPC) ) {
2075 //no need to avoid a travel trigger
2077 //skill?
2078 if (TriggerTrap(0, actor->GetGlobalID()) ) {
2079 return true;
2082 return false;
2085 void InfoPoint::DebugDump() const
2087 switch (Type) {
2088 case ST_TRIGGER:
2089 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2090 break;
2091 case ST_PROXIMITY:
2092 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2093 break;
2094 case ST_TRAVEL:
2095 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2096 break;
2097 default:
2098 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2099 break;
2101 printf( "Region Global ID: %d\n", GetGlobalID());
2102 printf( "Position: %d.%d\n", Pos.x, Pos.y);
2103 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected, YESNO(Trapped));
2104 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2105 TrapRemovalDiff );
2106 const char *name = "NONE";
2107 if (Scripts[0]) {
2108 name = Scripts[0]->GetName();
2110 printf( "Script: %s, Key: %s, Dialog: %s\n", name, KeyResRef, Dialog );
2111 printf( "Active: %s\n", YESNO(InternalFlags&IF_ACTIVE));
2114 /*******************
2115 * Container Class *
2116 *******************/
2118 Container::Container(void)
2119 : Highlightable( ST_CONTAINER )
2121 Type = 0;
2122 LockDifficulty = 0;
2123 Flags = 0;
2124 TrapDetectionDiff = 0;
2125 TrapRemovalDiff = 0;
2126 Trapped = 0;
2127 TrapDetected = 0;
2128 inventory.SetInventoryType(INVENTORY_HEAP);
2129 // NULL should be 0 for this
2130 memset (groundicons, 0, sizeof(groundicons) );
2131 groundiconcover = 0;
2134 void Container::FreeGroundIcons()
2136 Video* video = core->GetVideoDriver();
2138 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2139 if (groundicons[i]) {
2140 video->FreeSprite( groundicons[i] );
2141 groundicons[i]=NULL;
2144 delete groundiconcover;
2145 groundiconcover = 0;
2148 Container::~Container()
2150 FreeGroundIcons();
2153 void Container::DrawPile(bool highlight, Region screen, Color tint)
2155 Video* video = core->GetVideoDriver();
2156 CreateGroundIconCover();
2157 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2158 if (groundicons[i]) {
2159 //draw it with highlight
2160 video->BlitGameSprite(groundicons[i],
2161 screen.x + Pos.x, screen.y + Pos.y,
2162 BLIT_TINTED | (highlight ? 0:BLIT_NOSHADOW),
2163 tint, groundiconcover);
2168 // create the SpriteCover for the groundicons
2169 void Container::CreateGroundIconCover()
2171 int xpos = 0;
2172 int ypos = 0;
2173 int width = 0;
2174 int height = 0;
2176 int i; //msvc6.0
2177 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2178 if (groundicons[i]) {
2179 Sprite2D& spr = *groundicons[i];
2180 if (xpos < spr.XPos) {
2181 width += spr.XPos - xpos;
2182 xpos = spr.XPos;
2184 if (ypos < spr.YPos) {
2185 height += spr.YPos - ypos;
2186 ypos = spr.YPos;
2188 if (width-xpos < spr.Width-spr.XPos) {
2189 width = spr.Width-spr.XPos+xpos;
2191 if (height-ypos < spr.Height-spr.YPos) {
2192 height = spr.Height-spr.YPos+ypos;
2197 if (!groundiconcover ||
2198 !groundiconcover->Covers(Pos.x, Pos.y, xpos, ypos, width, height))
2200 delete groundiconcover;
2201 groundiconcover = 0;
2202 if (width*height > 0) {
2203 groundiconcover = GetCurrentArea()->BuildSpriteCover
2204 (Pos.x, Pos.y, xpos, ypos, width, height, WantDither());
2208 #ifndef NDEBUG
2209 // TODO: remove this checking code eventually
2210 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2211 if (groundicons[i]) {
2212 Sprite2D& spr = *groundicons[i];
2213 assert(groundiconcover->Covers(Pos.x, Pos.y, spr.XPos, spr.YPos, spr.Width, spr.Height));
2216 #endif
2219 void Container::SetContainerLocked(bool lock)
2221 if (lock) {
2222 Flags|=CONT_LOCKED;
2223 } else {
2224 Flags&=~CONT_LOCKED;
2228 //This function doesn't exist in the original IE, destroys a container
2229 //turning it to a ground pile
2230 void Container::DestroyContainer()
2232 //it is already a groundpile?
2233 if (Type == IE_CONTAINER_PILE)
2234 return;
2235 Type = IE_CONTAINER_PILE;
2236 RefreshGroundIcons();
2237 //probably we should stop the script or trigger it, whatever
2240 //Takes an item from the container's inventory and returns its pointer
2241 CREItem *Container::RemoveItem(unsigned int idx, unsigned int count)
2243 CREItem *ret = inventory.RemoveItem(idx, count);
2244 //we just took the 3. or less item, groundpile changed
2245 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<3)) {
2246 RefreshGroundIcons();
2248 return ret;
2251 //Adds an item to the container's inventory
2252 //containers always have enough capacity (so far), thus we always return 2
2253 int Container::AddItem(CREItem *item)
2255 inventory.AddItem(item);
2256 //we just added a 3. or less item, groundpile changed
2257 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<4)) {
2258 RefreshGroundIcons();
2260 return 2;
2263 void Container::RefreshGroundIcons()
2265 int i = inventory.GetSlotCount();
2266 if (i>MAX_GROUND_ICON_DRAWN)
2267 i = MAX_GROUND_ICON_DRAWN;
2268 FreeGroundIcons();
2269 while (i--) {
2270 CREItem *slot = inventory.GetSlotItem(i); //borrowed reference
2271 Item *itm = gamedata->GetItem( slot->ItemResRef ); //cached reference
2272 //well, this is required in PST, needs more work if some other
2273 //game is broken by not using -1,0
2274 groundicons[i] = gamedata->GetBAMSprite( itm->GroundIcon, 0, 0 );
2275 gamedata->FreeItem( itm, slot->ItemResRef ); //decref
2279 //used for ground piles
2280 int Container::WantDither()
2282 //if pile is highlighted, always dither it
2283 if (Highlight) {
2284 return 2; //dither me if you want
2286 //if pile isn't highlighted, dither it if the polygon wants
2287 return 1;
2290 int Container::IsOpen() const
2292 if (Flags&CONT_LOCKED) {
2293 return false;
2295 return true;
2298 void Container::TryPickLock(Actor *actor)
2300 if (LockDifficulty == 100) {
2301 if (OpenFail != (ieDword)-1) {
2302 displaymsg->DisplayStringName(OpenFail, 0xbcefbc, actor, IE_STR_SOUND|IE_STR_SPEECH);
2303 } else {
2304 displaymsg->DisplayConstantStringName(STR_CONT_NOPICK, 0xbcefbc, actor);
2306 return;
2308 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
2309 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
2310 LastPickLockFailed = actor->GetGlobalID();
2311 return;
2313 SetContainerLocked(false);
2314 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
2315 LastUnlocked = actor->GetGlobalID();
2316 ImmediateEvent();
2317 int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
2318 Game *game = core->GetGame();
2319 game->ShareXP(xp, SX_DIVIDE);
2322 void Container::TryBashLock(Actor *actor)
2324 //Get the strength bonus agains lock difficulty
2325 int str = actor->GetStat(IE_STR);
2326 int strEx = actor->GetStat(IE_STREXTRA);
2327 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
2328 unsigned int roll = actor->LuckyRoll(1, 10, bonus, 0);
2330 if(roll < LockDifficulty || LockDifficulty == 100) {
2331 displaymsg->DisplayConstantStringName(STR_CONTBASH_FAIL, 0xbcefbc, actor);
2332 return;
2335 displaymsg->DisplayConstantStringName(STR_CONTBASH_DONE, 0xd7d7be, actor);
2336 SetContainerLocked(false);
2337 //Is this really useful ?
2338 LastUnlocked = actor->GetGlobalID();
2339 ImmediateEvent();
2342 void Container::DebugDump() const
2344 printf( "Debugdump of Container %s\n", GetScriptName() );
2345 printf( "Container Global ID: %d\n", GetGlobalID());
2346 printf( "Position: %d.%d\n", Pos.x, Pos.y);
2347 printf( "Type: %d, Locked: %s, LockDifficulty: %d\n", Type, YESNO(Flags&CONT_LOCKED), LockDifficulty );
2348 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags, YESNO(Trapped), TrapDetected );
2349 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2350 TrapRemovalDiff );
2351 const char *name = "NONE";
2352 if (Scripts[0]) {
2353 name = Scripts[0]->GetName();
2355 printf( "Script: %s, Key: %s\n", name, KeyResRef );
2356 // FIXME: const_cast
2357 const_cast<Inventory&>(inventory).dump();
2360 bool Container::TryUnlock(Actor *actor) {
2361 if (!(Flags&CONT_LOCKED)) return true;
2363 return Highlightable::TryUnlock(actor, false);