added autodetecting secret doors
[gemrb.git] / gemrb / core / Scriptable / ActorBlock.cpp
blob2086728c0b5d15bf6aee00b893860764bb633d50
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 /***********************
46 * Scriptable Class *
47 ***********************/
48 Scriptable::Scriptable(ScriptableType type)
50 Type = type;
51 CutSceneId = NULL;
52 for (int i = 0; i < MAX_SCRIPTS; i++) {
53 Scripts[i] = NULL;
55 overHeadText = NULL;
56 overHeadTextPos.empty();
57 textDisplaying = 0;
58 timeStartDisplaying = 0;
59 scriptName[0] = 0;
60 TriggerID = 0; //used by SendTrigger
61 LastTriggerObject = LastTrigger = 0;
62 LastEntered = 0;
63 LastDisarmed = 0;
64 LastDisarmFailed = 0;
65 LastUnlocked = 0;
66 LastOpenFailed = 0;
67 LastPickLockFailed = 0;
68 DialogName = 0;
69 CurrentAction = NULL;
70 CurrentActionState = 0;
71 CurrentActionTarget = 0;
72 CurrentActionInterruptable = true;
73 UnselectableTimer = 0;
74 startTime = 0; //executing scripts
75 lastRunTime = 0; //evaluating scripts
76 lastDelay = 0;
77 Dialog[0] = 0;
79 interval = ( 1000 / AI_UPDATE_TIME );
80 WaitCounter = 0;
81 if (Type == ST_ACTOR) {
82 InternalFlags = IF_VISIBLE | IF_ONCREATION | IF_USEDSAVE;
83 } else {
84 InternalFlags = IF_ACTIVE | IF_VISIBLE | IF_ONCREATION | IF_NOINT;
86 area = 0;
87 Pos.x = 0;
88 Pos.y = 0;
90 LastCasterOnMe = 0;
91 LastSpellOnMe = 0xffffffff;
92 LastCasterSeen = 0;
93 LastSpellSeen = 0xffffffff;
94 SpellHeader = -1;
95 LastTargetPos.empty();
96 locals = new Variables();
97 locals->SetType( GEM_VARIABLES_INT );
98 locals->ParseKey( 1 );
99 InitTriggers();
101 memset( script_timers,0, sizeof(script_timers));
104 Scriptable::~Scriptable(void)
106 if (CurrentAction) {
107 ReleaseCurrentAction();
109 ClearActions();
110 for (int i = 0; i < MAX_SCRIPTS; i++) {
111 if (Scripts[i]) {
112 delete( Scripts[i] );
115 if (overHeadText) {
116 core->FreeString( overHeadText );
118 if (locals) {
119 delete( locals );
123 void Scriptable::SetScriptName(const char* text)
125 //if (text && text[0]) { //this leaves some uninitialized bytes
126 //lets hope this won't break anything
127 if (text) {
128 strnspccpy( scriptName, text, 32 );
132 /** Gets the DeathVariable */
133 const char* Scriptable::GetScriptName(void) const
135 return scriptName;
138 Map* Scriptable::GetCurrentArea() const
140 //this could be NULL, always check it
141 return area;
144 void Scriptable::SetMap(Map *map)
146 if (map && (map->GetCurrentArea()!=map)) {
147 //a map always points to itself (if it is a real map)
148 printMessage("Scriptable","Invalid map set!\n",LIGHT_RED);
149 abort();
151 area = map;
154 //ai is nonzero if this is an actor currently in the party
155 //if the script level is AI_SCRIPT_LEVEL, then we need to
156 //load an AI script (.bs) instead of (.bcs)
157 void Scriptable::SetScript(const ieResRef aScript, int idx, bool ai)
159 if (idx >= MAX_SCRIPTS) {
160 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
161 abort();
163 if (Scripts[idx]) {
164 delete Scripts[idx];
166 Scripts[idx] = NULL;
167 // NONE is an 'invalid' script name, never used seriously
168 // This hack is to prevent flooding of the console
169 if (aScript[0] && stricmp(aScript, "NONE") ) {
170 if (idx!=AI_SCRIPT_LEVEL) ai = false;
171 Scripts[idx] = new GameScript( aScript, this, idx, ai );
175 void Scriptable::SetScript(int index, GameScript* script)
177 if (index >= MAX_SCRIPTS) {
178 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
179 return;
181 if (Scripts[index] ) {
182 delete Scripts[index];
184 Scripts[index] = script;
187 void Scriptable::DisplayHeadText(const char* text)
189 if (overHeadText) {
190 core->FreeString( overHeadText );
192 overHeadText = (char *) text;
193 overHeadTextPos.empty();
194 if (text) {
195 timeStartDisplaying = core->GetGame()->Ticks;
196 textDisplaying = 1;
198 else {
199 timeStartDisplaying = 0;
200 textDisplaying = 0;
204 /* 'fix' the current overhead text in the current position */
205 void Scriptable::FixHeadTextPos()
207 overHeadTextPos = Pos;
210 #define MAX_DELAY 6000
211 static const Color black={0,0,0,0};
213 void Scriptable::DrawOverheadText(const Region &screen)
215 unsigned long time = core->GetGame()->Ticks;
216 Palette *palette = NULL;
218 if (!textDisplaying)
219 return;
221 time -= timeStartDisplaying;
223 Font* font = core->GetFont( 1 );
224 if (time >= MAX_DELAY) {
225 textDisplaying = 0;
226 return;
227 } else {
228 time = (MAX_DELAY-time)/10;
229 if (time<256) {
230 const Color overHeadColor = {time,time,time,time};
231 palette = core->CreatePalette(overHeadColor, black);
235 int cs = 100;
236 if (Type == ST_ACTOR) {
237 cs = ((Selectable *) this)->size*50;
240 short x, y;
241 if (overHeadTextPos.isempty()) {
242 x = Pos.x;
243 y = Pos.y;
244 } else {
245 x = overHeadTextPos.x;
246 y = overHeadTextPos.y;
249 Region rgn( x-100+screen.x, y - cs + screen.y, 200, 400 );
250 font->Print( rgn, ( unsigned char * ) overHeadText,
251 palette?palette:core->InfoTextPalette, IE_FONT_ALIGN_CENTER | IE_FONT_ALIGN_TOP, false );
252 gamedata->FreePalette(palette);
255 void Scriptable::DelayedEvent()
257 lastRunTime = core->GetGame()->Ticks;
260 void Scriptable::ImmediateEvent()
262 lastRunTime = 0;
265 bool Scriptable::IsPC() const
267 if(Type == ST_ACTOR) {
268 if (((Actor *) this)->GetStat(IE_EA) <= EA_CHARMED) {
269 return true;
272 return false;
275 void Scriptable::ExecuteScript(int scriptCount)
277 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
278 // but not in bg2 (kill Abazigal in ar6005)
279 if (core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE) {
280 if (! (core->HasFeature(GF_CUTSCENE_AREASCRIPTS) && Type == ST_AREA)) {
281 return;
285 if ((InternalFlags & IF_NOINT) && (CurrentAction || GetNextAction())) {
286 return;
289 if (!CurrentActionInterruptable) {
290 if (!CurrentAction && !GetNextAction()) abort();
291 return;
294 // only allow death scripts to run once, hopefully?
295 // this is probably terrible logic which needs moving elsewhere
296 if ((lastRunTime != 0) && (InternalFlags & IF_JUSTDIED)) {
297 return;
300 ieDword thisTime = core->GetGame()->Ticks;
301 if (( thisTime - lastRunTime ) < 1000) {
302 return;
305 lastDelay = lastRunTime;
306 lastRunTime = thisTime;
308 bool alive = false;
310 bool continuing = false, done = false;
311 for (int i = 0;i<scriptCount;i++) {
312 //disable AI script level for actors in party when the player disabled them
313 if ((i == AI_SCRIPT_LEVEL) && Type == ST_ACTOR && ((Actor *) this)->InParty) {
314 if (core->GetGame()->ControlStatus&CS_PARTY_AI) {
315 continue;
319 GameScript *Script = Scripts[i];
320 if (Script) {
321 alive |= Script->Update(&continuing, &done);
324 /* scripts are not concurrent, see WAITPC override script for example */
325 if (done) break;
327 if (alive && UnselectableTimer) {
328 UnselectableTimer--;
329 if (!UnselectableTimer) {
330 if (Type == ST_ACTOR) {
331 ((Actor *) this)->SetCircleSize();
335 InternalFlags &= ~IF_ONCREATION;
338 void Scriptable::AddAction(Action* aC)
340 if (!aC) {
341 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
342 return;
345 InternalFlags|=IF_ACTIVE;
346 aC->IncRef();
348 // attempt to handle 'instant' actions, from instant.ids, which run immediately
349 // when added if the action queue is empty, even on actors which are Held/etc
350 if (!CurrentAction && !GetNextAction()) {
351 if (actionflags[aC->actionID] & AF_INSTANT) {
352 CurrentAction = aC;
353 GameScript::ExecuteAction( this, CurrentAction );
354 return;
358 actionQueue.push_back( aC );
361 void Scriptable::AddActionInFront(Action* aC)
363 if (!aC) {
364 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
365 return;
367 InternalFlags|=IF_ACTIVE;
368 actionQueue.push_front( aC );
369 aC->IncRef();
372 Action* Scriptable::GetNextAction() const
374 if (actionQueue.size() == 0) {
375 return NULL;
377 return actionQueue.front();
380 Action* Scriptable::PopNextAction()
382 if (actionQueue.size() == 0) {
383 return NULL;
385 Action* aC = actionQueue.front();
386 actionQueue.pop_front();
387 return aC;
390 void Scriptable::ClearActions()
392 ReleaseCurrentAction();
393 for (unsigned int i = 0; i < actionQueue.size(); i++) {
394 Action* aC = actionQueue.front();
395 actionQueue.pop_front();
396 aC->Release();
398 actionQueue.clear();
399 WaitCounter = 0;
400 LastTarget = 0;
401 //clear the triggers as fast as possible when queue ended?
402 ClearTriggers();
404 if (Type == ST_ACTOR) {
405 Interrupt();
406 } else {
407 NoInterrupt();
411 void Scriptable::ReleaseCurrentAction()
413 if (CurrentAction) {
414 CurrentAction->Release();
415 CurrentAction = NULL;
418 CurrentActionState = 0;
419 CurrentActionTarget = 0;
420 CurrentActionInterruptable = true;
423 ieWord Scriptable::GetGlobalID()
425 if (Type == ST_ACTOR) {
426 Actor *actor = (Actor *) this;
427 return actor->globalID;
429 return 0;
432 void Scriptable::ProcessActions(bool force)
434 unsigned long thisTime = core->GetGame()->Ticks;
436 if (!force && (( thisTime - startTime ) < interval)) {
437 return;
439 startTime = thisTime;
440 if (WaitCounter) {
441 WaitCounter--;
442 if (WaitCounter) return;
445 while (true) {
446 CurrentActionInterruptable = true;
447 if (!CurrentAction) {
448 CurrentAction = PopNextAction();
450 if (!CurrentAction) {
451 ClearActions();
452 if (CutSceneId) {
453 CutSceneId = NULL;
455 //removing the triggers at the end of the
456 //block
457 //ClearTriggers();
458 break;
460 GameScript::ExecuteAction( this, CurrentAction );
461 //break execution in case of a Wait flag
462 if (WaitCounter) {
463 //clear triggers while waiting
464 //ClearTriggers();
465 break;
467 //break execution in case of blocking action
468 if (CurrentAction) {
469 break;
471 //break execution in case of movement
472 //we should not actually break here, or else fix waypoints
473 if (InMove()) {
474 break;
477 //most likely the best place to clear triggers is here
478 //queue is empty, or there is a looong action subject to break
479 ClearTriggers();
480 if (InternalFlags&IF_IDLE) {
481 Deactivate();
485 bool Scriptable::InMove() const
487 if (Type!=ST_ACTOR) {
488 return false;
490 Movable *me = (Movable *) this;
491 return me->GetNextStep()!=NULL;
494 void Scriptable::SetWait(unsigned long time)
496 WaitCounter = time;
499 unsigned long Scriptable::GetWait() const
501 return WaitCounter;
504 Scriptable *Scriptable::GetCutsceneID() const
506 return CutSceneId;
509 void Scriptable::LeaveDialog()
511 InternalFlags |=IF_WASINDIALOG;
514 //this ends cutscene mode for this Sender
515 void Scriptable::ClearCutsceneID()
517 CutSceneId = NULL;
518 InternalFlags &= ~IF_CUTSCENEID;
521 //if the cutsceneID doesn't exist, we simply skip the action
522 //because the cutscene script executer DOESN'T get hijacked
523 void Scriptable::SetCutsceneID(Scriptable *csid)
525 CutSceneId = csid;
526 InternalFlags |= IF_CUTSCENEID;
529 void Scriptable::Hide()
531 InternalFlags &=~(IF_VISIBLE);
534 void Scriptable::Unhide()
536 InternalFlags |= IF_VISIBLE;
539 void Scriptable::Interrupt()
541 InternalFlags &= ~IF_NOINT;
544 void Scriptable::NoInterrupt()
546 InternalFlags |= IF_NOINT;
549 //also turning off the idle flag so it won't run continuously
550 void Scriptable::Deactivate()
552 InternalFlags &=~(IF_ACTIVE|IF_IDLE);
555 //turning off the not interruptable flag, actions should reenable it themselves
556 //also turning off the idle flag
557 //heh, no, i wonder why did i touch the interruptable flag here
558 void Scriptable::Activate()
560 InternalFlags |= IF_ACTIVE;
561 InternalFlags &= ~IF_IDLE;
564 void Scriptable::PartyRested()
566 InternalFlags |=IF_PARTYRESTED;
569 ieDword Scriptable::GetInternalFlag()
571 return InternalFlags;
574 void Scriptable::InitTriggers()
576 tolist.clear();
577 bittriggers = 0;
580 void Scriptable::ClearTriggers()
582 for (TriggerObjects::iterator m = tolist.begin(); m != tolist.end (); m++) {
583 *(*m) = 0;
585 if (!bittriggers) {
586 return;
588 if (bittriggers & BT_DIE) {
589 InternalFlags &= ~IF_JUSTDIED;
591 if (bittriggers & BT_ONCREATION) {
592 InternalFlags &= ~IF_ONCREATION;
594 if (bittriggers & BT_BECAMEVISIBLE) {
595 InternalFlags &= ~IF_BECAMEVISIBLE;
597 if (bittriggers & BT_PARTYRESTED) {
598 InternalFlags &= ~IF_PARTYRESTED;
600 if (bittriggers & BT_WASINDIALOG) {
601 InternalFlags &= ~IF_WASINDIALOG;
603 if (bittriggers & BT_PARTYRESTED) {
604 InternalFlags &= ~IF_PARTYRESTED;
606 InitTriggers();
609 void Scriptable::SetBitTrigger(ieDword bittrigger)
611 bittriggers |= bittrigger;
614 void Scriptable::AddTrigger(ieDword *actorref)
616 tolist.push_back(actorref);
619 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef )
621 if (Type == ST_ACTOR) {
622 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
625 if (SpellHeader == -1) {
626 LastTargetPos.empty();
627 return;
630 if (LastTargetPos.isempty()) {
631 SpellHeader = -1;
632 return;
635 Spell* spl = gamedata->GetSpell( SpellResRef );
636 //create projectile from known spellheader
637 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
638 SpellHeader = -1;
639 if (pro) {
640 pro->SetCaster(GetGlobalID());
641 Point origin = Pos;
642 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
643 // try and make projectiles start from the right trap position
644 // see the traps in the duergar/assassin battle in bg2 dungeon
645 // see also function below - maybe we should fix Pos instead
646 origin = ((InfoPoint *)this)->TrapLaunch;
648 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
651 // caster - Casts spellname
652 char tmp[100];
653 const char* msg = core->GetString(displaymsg->GetStringReference(STR_ACTION_CAST), 0);
654 snprintf(tmp, sizeof(tmp), "%s %s", msg, core->GetString(spl->SpellName));
655 displaymsg->DisplayStringName(tmp, 0xffffff, this);
657 core->Autopause(AP_SPELLCAST);
659 LastTarget = 0;
660 LastTargetPos.empty();
663 static EffectRef fx_set_invisible_state_ref={"State:Invisible",NULL,-1};
665 void Scriptable::CastSpellEnd( const ieResRef SpellResRef )
667 if (Type == ST_ACTOR) {
668 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
671 if (SpellHeader == -1) {
672 LastTarget = 0;
673 return;
675 if (!LastTarget) {
676 SpellHeader = -1;
677 return;
679 Spell* spl = gamedata->GetSpell( SpellResRef );
680 //create projectile from known spellheader
681 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
682 if (pro) {
683 pro->SetCaster(GetGlobalID());
684 Point origin = Pos;
685 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
686 // try and make projectiles start from the right trap position
687 // see the traps in the duergar/assassin battle in bg2 dungeon
688 // see also function above - maybe we should fix Pos instead
689 origin = ((InfoPoint *)this)->TrapLaunch;
691 if (LastTarget) {
692 GetCurrentArea()->AddProjectile(pro, origin, LastTarget);
693 } else {
694 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
697 ieDword spellnum=ResolveSpellNumber( SpellResRef );
698 if (spellnum!=0xffffffff) {
699 area->SeeSpellCast(this, spellnum);
701 // caster - Casts spellname : target OR
702 // caster - spellname : target (repeating spells)
703 Scriptable *target = NULL;
704 char tmp[100];
705 const char* msg = core->GetString(displaymsg->GetStringReference(STR_ACTION_CAST), 0);
706 if (LastTarget) {
707 target = area->GetActorByGlobalID(LastTarget);
708 snprintf(tmp, sizeof(tmp), "%s %s : %s", msg, core->GetString(spl->SpellName), target->GetName(-1));
709 } else {
710 snprintf(tmp, sizeof(tmp), "%s : %s", core->GetString(spl->SpellName), GetName(-1));
712 displaymsg->DisplayStringName(tmp, 0xffffff, this);
714 if(LastTarget) {
715 if (target && (Type==ST_ACTOR) ) {
716 Actor *me = (Actor *) this;
717 target->LastSpellOnMe = spellnum;
718 target->LastCasterOnMe = me->GetID();
719 // don't cure invisibility if this is a self targetting invisibility spell
720 // like shadow door
721 //can't check GetEffectBlock, since it doesn't construct the queue for selftargetting spells
722 bool invis = false;
723 unsigned int opcode = EffectQueue::ResolveEffect(fx_set_invisible_state_ref);
724 for (unsigned int i=0; i < spl->ext_headers[SpellHeader].FeatureCount; i++) {
725 if (spl->GetExtHeader(SpellHeader)->features[i].Opcode == opcode) {
726 invis = true;
727 break;
730 if (invis && spl->GetExtHeader(SpellHeader)->Target == TARGET_SELF) {
731 //pass
732 } else {
733 me->CureInvisibility();
735 if (target!=this) { //FIXME: only dispel it for hostile spells
736 me->CureSanctuary();
742 core->Autopause(AP_SPELLCAST);
744 gamedata->FreeSpell(spl, SpellResRef, false);
745 LastTarget = 0;
746 LastTargetPos.empty();
749 //set target as point
750 //if spell needs to be depleted, do it
751 //if spell is illegal stop casting
752 int Scriptable::CastSpellPoint( const ieResRef SpellResRef, const Point &target, bool deplete, bool instant )
754 LastTarget = 0;
755 LastTargetPos.empty();
756 if (Type == ST_ACTOR) {
757 Actor *actor = (Actor *) this;
758 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
759 return -1;
762 LastTargetPos = target;
763 return SpellCast(SpellResRef, instant);
766 //set target as actor (if target isn't actor, use its position)
767 //if spell needs to be depleted, do it
768 //if spell is illegal stop casting
769 int Scriptable::CastSpell( const ieResRef SpellResRef, Scriptable* target, bool deplete, bool instant )
771 LastTarget = 0;
772 LastTargetPos.empty();
773 if (Type == ST_ACTOR) {
774 Actor *actor = (Actor *) this;
775 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
776 return -1;
780 if (!target) target = this;
782 LastTargetPos = target->Pos;
783 if (target->Type==ST_ACTOR) {
784 LastTarget = target->GetGlobalID();
786 return SpellCast(SpellResRef, instant);
789 //start spellcasting (common part)
790 int Scriptable::SpellCast(const ieResRef SpellResRef, bool instant)
792 Spell* spl = gamedata->GetSpell( SpellResRef );
793 if (!spl) {
794 SpellHeader = -1;
795 return -1;
798 if (Type == ST_ACTOR) {
799 Actor *actor = (Actor *) this;
800 //The ext. index is here to calculate the casting time
801 int level = actor->GetXPLevel(true);
802 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
803 level += actor->CastingLevelBonus(level, spl->SpellType);
804 SpellHeader = spl->GetHeaderIndexFromLevel(level);
805 } else {
806 SpellHeader = 0;
809 SPLExtHeader *header = spl->GetExtHeader(SpellHeader);
810 int casting_time = (int)header->CastingTime;
811 // how does this work for non-actors exactly?
812 if (Type == ST_ACTOR) {
813 // The mental speed effect can shorten or lengthen the casting time.
814 casting_time -= (int)((Actor *) this)->GetStat(IE_MENTALSPEED);
815 if (casting_time < 0) casting_time = 0;
817 // this is a guess which seems approximately right so far
818 int duration = (casting_time*core->Time.round_size) / 10;
819 if (instant)
820 duration = 0;
822 //cfb
823 if (Type == ST_ACTOR) {
824 Actor *actor = (Actor *) this;
825 EffectQueue *fxqueue = spl->GetEffectBlock(this, this->Pos, -1);
826 spl->AddCastingGlow(fxqueue, duration);
827 fxqueue->SetOwner(actor);
828 fxqueue->AddAllEffects(actor, actor->Pos);
829 delete fxqueue;
832 gamedata->FreeSpell(spl, SpellResRef, false);
833 return duration;
836 bool Scriptable::TimerActive(ieDword ID)
838 if (ID>=MAX_TIMER) {
839 return false;
841 if (script_timers[ID]) {
842 return true;
844 return false;
847 bool Scriptable::TimerExpired(ieDword ID)
849 if (ID>=MAX_TIMER) {
850 return false;
852 if (script_timers[ID] && script_timers[ID] < core->GetGame()->GameTime) {
853 // expired timers become inactive after being checked
854 script_timers[ID] = 0;
855 return true;
857 return false;
860 void Scriptable::StartTimer(ieDword ID, ieDword expiration)
862 if (ID>=MAX_TIMER) {
863 printMessage("Scriptable", " ", RED);
864 printf("Timer id %d exceeded MAX_TIMER %d\n", ID, MAX_TIMER);
865 return;
867 script_timers[ID]= core->GetGame()->GameTime + expiration*AI_UPDATE_TIME;
870 /********************
871 * Selectable Class *
872 ********************/
874 Selectable::Selectable(ScriptableType type)
875 : Scriptable( type )
877 Selected = false;
878 Over = false;
879 size = 0;
880 cover = NULL;
881 circleBitmap[0] = NULL;
882 circleBitmap[1] = NULL;
885 void Selectable::SetSpriteCover(SpriteCover* c)
887 delete cover;
888 cover = c;
891 Selectable::~Selectable(void)
893 delete cover;
896 void Selectable::SetBBox(const Region &newBBox)
898 BBox = newBBox;
901 static const unsigned long tp_steps[8]={3,2,1,0,1,2,3,4};
903 void Selectable::DrawCircle(const Region &vp)
905 /* BG2 colours ground circles as follows:
906 dark green for unselected party members
907 bright green for selected party members
908 flashing green/white for a party member the mouse is over
909 bright red for enemies
910 yellow for panicked actors
911 flashing red/white for enemies the mouse is over
912 flashing cyan/white for neutrals the mouse is over
915 if (size<=0) {
916 return;
918 Color mix;
919 Color* col = &selectedColor;
920 Sprite2D* sprite = circleBitmap[0];
922 if (Selected) {
923 sprite = circleBitmap[1];
924 } else if (Over) {
925 //doing a time dependent flashing of colors
926 //if it is too fast, increase the 6 to 7
927 unsigned long step;
928 GetTime( step );
929 step = tp_steps [(step >> 6) & 7];
930 mix.a = overColor.a;
931 mix.r = (overColor.r*step+selectedColor.r*(8-step))/8;
932 mix.g = (overColor.g*step+selectedColor.g*(8-step))/8;
933 mix.b = (overColor.b*step+selectedColor.b*(8-step))/8;
934 col = &mix;
935 } else if (IsPC()) {
936 col = &overColor;
939 if (sprite) {
940 core->GetVideoDriver()->BlitSprite( sprite, Pos.x - vp.x, Pos.y - vp.y, true );
941 } else {
942 // for size >= 2, radii are (size-1)*16, (size-1)*12
943 // for size == 1, radii are 12, 9
944 int csize = (size - 1) * 4;
945 if (csize < 4) csize = 3;
946 core->GetVideoDriver()->DrawEllipse( (ieWord) (Pos.x - vp.x), (ieWord) (Pos.y - vp.y),
947 (ieWord) (csize * 4), (ieWord) (csize * 3), *col );
951 // Check if P is over our ground circle
952 bool Selectable::IsOver(const Point &P) const
954 int csize = size;
955 if (csize < 2) csize = 2;
957 int dx = P.x - Pos.x;
958 int dy = P.y - Pos.y;
960 // check rectangle first
961 if (dx < -(csize-1)*16 || dx > (csize-1)*16) return false;
962 if (dy < -(csize-1)*12 || dy > (csize-1)*12) return false;
964 // then check ellipse
965 int r = 9*dx*dx + 16*dy*dy; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
967 return (r <= 48*48*(csize-1)*(csize-1));
970 bool Selectable::IsSelected() const
972 return Selected == 1;
975 void Selectable::SetOver(bool over)
977 Over = over;
980 //don't call this function after rendering the cover and before the
981 //blitting of the sprite or bad things will happen :)
982 void Selectable::Select(int Value)
984 if (Selected!=0x80 || Value!=1) {
985 Selected = (ieWord) Value;
987 //forcing regeneration of the cover
988 SetSpriteCover(NULL);
991 void Selectable::SetCircle(int circlesize, const Color &color, Sprite2D* normal_circle, Sprite2D* selected_circle)
993 size = circlesize;
994 selectedColor = color;
995 overColor.r = color.r >> 1;
996 overColor.g = color.g >> 1;
997 overColor.b = color.b >> 1;
998 overColor.a = color.a;
999 circleBitmap[0] = normal_circle;
1000 circleBitmap[1] = selected_circle;
1003 //used for creatures
1004 int Selectable::WantDither()
1006 //if dithering is disabled globally, don't do it
1007 if (core->FogOfWar&4) {
1008 return 0;
1010 //if actor is dead, dither it if polygon wants
1011 if (Selected&0x80) {
1012 return 1;
1014 //if actor is selected dither it
1015 if (Selected) {
1016 return 2;
1018 return 1;
1021 /***********************
1022 * Highlightable Class *
1023 ***********************/
1025 Highlightable::Highlightable(ScriptableType type)
1026 : Scriptable( type )
1028 outline = NULL;
1029 Highlight = false;
1030 Cursor = IE_CURSOR_NORMAL;
1031 KeyResRef[0] = 0;
1034 Highlightable::~Highlightable(void)
1036 if (outline) {
1037 delete( outline );
1041 bool Highlightable::IsOver(const Point &Pos) const
1043 if (!outline) {
1044 return false;
1046 return outline->PointIn( Pos );
1049 void Highlightable::DrawOutline() const
1051 if (!outline) {
1052 return;
1054 core->GetVideoDriver()->DrawPolyline( outline, outlineColor, true );
1057 void Highlightable::SetCursor(unsigned char CursorIndex)
1059 Cursor = CursorIndex;
1062 bool Highlightable::TryUnlock(Actor *actor, bool removekey) {
1063 const char *Key = GetKey();
1064 Actor *haskey = NULL;
1066 if (Key && actor->InParty) {
1067 Game *game = core->GetGame();
1068 //allow unlock when the key is on any partymember
1069 for (int idx = 0; idx < game->GetPartySize(false); idx++) {
1070 Actor *pc = game->FindPC(idx + 1);
1071 if (!pc) continue;
1073 if (pc->inventory.HasItem(Key,0) ) {
1074 haskey = pc;
1075 break;
1078 } else if (Key) {
1079 //actor is not in party, check only actor
1080 if (actor->inventory.HasItem(Key,0) ) {
1081 haskey = actor;
1085 if (!haskey) {
1086 return false;
1089 if (removekey) {
1090 CREItem *item = NULL;
1091 haskey->inventory.RemoveItem(Key,0,&item);
1092 //the item should always be existing!!!
1093 if (item) {
1094 delete item;
1098 return true;
1102 /*****************
1103 * Movable Class *
1104 *****************/
1106 Movable::Movable(ScriptableType type)
1107 : Selectable( type )
1109 Destination = Pos;
1110 Orientation = 0;
1111 NewOrientation = 0;
1112 StanceID = 0;
1113 path = NULL;
1114 step = NULL;
1115 timeStartStep = 0;
1116 lastFrame = NULL;
1117 Area[0] = 0;
1118 AttackMovements[0] = 100;
1119 AttackMovements[1] = 0;
1120 AttackMovements[2] = 0;
1123 Movable::~Movable(void)
1125 if (path) {
1126 ClearPath();
1130 int Movable::GetPathLength()
1132 PathNode *node = GetNextStep(0);
1133 int i = 0;
1134 while (node->Next) {
1135 i++;
1136 node = node->Next;
1138 return i;
1141 PathNode *Movable::GetNextStep(int x)
1143 if (!step) {
1144 DoStep((unsigned int) ~0);
1146 PathNode *node = step;
1147 while(node && x--) {
1148 node = node->Next;
1150 return node;
1153 Point Movable::GetMostLikelyPosition()
1155 if (!path) {
1156 return Pos;
1159 //actually, sometimes middle path would be better, if
1160 //we stand in Destination already
1161 int halfway = GetPathLength()/2;
1162 PathNode *node = GetNextStep(halfway);
1163 if (node) {
1164 return Point((ieWord) ((node->x*16)+8), (ieWord) ((node->y*12)+6) );
1166 return Destination;
1169 void Movable::SetStance(unsigned int arg)
1171 //don't modify stance from dead back to anything if the actor is dead
1172 if ((StanceID==IE_ANI_TWITCH || StanceID==IE_ANI_DIE) && (arg!=IE_ANI_TWITCH) ) {
1173 if (GetInternalFlag()&IF_REALLYDIED) {
1174 printMessage("Movable","Stance overridden by death\n", YELLOW);
1175 return;
1179 if (arg<MAX_ANIMS) {
1180 StanceID=(unsigned char) arg;
1182 if (StanceID == IE_ANI_ATTACK) {
1183 // Set stance to a random attack animation
1185 int random = rand()%100;
1186 if (random < AttackMovements[0]) {
1187 StanceID = IE_ANI_ATTACK_BACKSLASH;
1188 } else if (random < AttackMovements[0] + AttackMovements[1]) {
1189 StanceID = IE_ANI_ATTACK_SLASH;
1190 } else {
1191 StanceID = IE_ANI_ATTACK_JAB;
1195 } else {
1196 StanceID=IE_ANI_AWAKE; //
1197 printf("Tried to set invalid stance id (%u)\n", arg);
1201 void Movable::SetAttackMoveChances(ieWord *amc)
1203 AttackMovements[0]=amc[0];
1204 AttackMovements[1]=amc[1];
1205 AttackMovements[2]=amc[2];
1210 //this could be used for WingBuffet as well
1211 void Movable::MoveLine(int steps, int Pass, ieDword orient)
1213 //remove previous path
1214 ClearPath();
1215 if (!steps)
1216 return;
1217 Point p = Pos;
1218 p.x/=16;
1219 p.y/=14;
1220 path = area->GetLine( p, steps, orient, Pass );
1223 void AdjustPositionTowards(Point &Pos, ieDword time_diff, unsigned int walk_speed, short srcx, short srcy, short destx, short desty) {
1224 if (destx > srcx)
1225 Pos.x += ( unsigned short )
1226 ( ( ( ( ( destx * 16 ) + 8 ) - Pos.x ) * ( time_diff ) ) / walk_speed );
1227 else
1228 Pos.x -= ( unsigned short )
1229 ( ( ( Pos.x - ( ( destx * 16 ) + 8 ) ) * ( time_diff ) ) / walk_speed );
1230 if (desty > srcy)
1231 Pos.y += ( unsigned short )
1232 ( ( ( ( ( desty * 12 ) + 6 ) - Pos.y ) * ( time_diff ) ) / walk_speed );
1233 else
1234 Pos.y -= ( unsigned short )
1235 ( ( ( Pos.y - ( ( desty * 12 ) + 6 ) ) * ( time_diff ) ) / walk_speed );
1239 // returns whether we made all pending steps (so, false if we must be called again this tick)
1240 // we can't just do them all here because the caller might have to update searchmap etc
1241 bool Movable::DoStep(unsigned int walk_speed, ieDword time)
1243 if (!path) {
1244 return true;
1246 if (!time) time = core->GetGame()->Ticks;
1247 if (!walk_speed) {
1248 // zero speed: no movement
1249 timeStartStep = time;
1250 StanceID = IE_ANI_READY;
1251 return true;
1253 if (!step) {
1254 step = path;
1255 timeStartStep = time;
1256 } else if (step->Next && (( time - timeStartStep ) >= walk_speed)) {
1257 //printf("[New Step] : Orientation = %d\n", step->orient);
1258 step = step->Next;
1259 timeStartStep = timeStartStep + walk_speed;
1261 SetOrientation (step->orient, false);
1262 StanceID = IE_ANI_WALK;
1263 if ((Type == ST_ACTOR) && (InternalFlags & IF_RUNNING)) {
1264 StanceID = IE_ANI_RUN;
1266 Pos.x = ( step->x * 16 ) + 8;
1267 Pos.y = ( step->y * 12 ) + 6;
1268 if (!step->Next) {
1269 // we reached our destination, we are done
1270 ClearPath();
1271 NewOrientation = Orientation;
1272 //since clearpath no longer sets currentaction to NULL
1273 //we set it here
1274 //no we don't, action is responsible for releasing itself
1275 //ReleaseCurrentAction();
1276 return true;
1278 if (( time - timeStartStep ) >= walk_speed) {
1279 // we didn't finish all pending steps, yet
1280 return false;
1282 AdjustPositionTowards(Pos, time - timeStartStep, walk_speed, step->x, step->y, step->Next->x, step->Next->y);
1283 return true;
1286 void Movable::AddWayPoint(const Point &Des)
1288 if (!path) {
1289 WalkTo(Des);
1290 return;
1292 Destination = Des;
1293 //it is tempting to use 'step' here, as it could
1294 //be about half of the current path already
1295 PathNode *endNode = path;
1296 while(endNode->Next) {
1297 endNode = endNode->Next;
1299 Point p(endNode->x, endNode->y);
1300 area->ClearSearchMapFor(this);
1301 PathNode *path2 = area->FindPath( p, Des, size );
1302 endNode->Next = path2;
1303 //probably it is wise to connect it both ways?
1304 path2->Parent = endNode;
1307 void Movable::FixPosition()
1309 if (Type!=ST_ACTOR) {
1310 return;
1312 Actor *actor = (Actor *) this;
1313 if (actor->GetStat(IE_DONOTJUMP)&DNJ_BIRD ) {
1314 return;
1316 //before fixposition, you should remove own shadow
1317 area->ClearSearchMapFor(this);
1318 Pos.x/=16;
1319 Pos.y/=12;
1320 GetCurrentArea()->AdjustPosition(Pos);
1321 Pos.x=Pos.x*16+8;
1322 Pos.y=Pos.y*12+6;
1325 void Movable::WalkTo(const Point &Des, int distance)
1327 Point from;
1329 // maybe caller should be responsible for this
1330 if ((Des.x/16 == Pos.x/16) && (Des.y/12 == Pos.y/12)) {
1331 ClearPath();
1332 return;
1335 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1336 PathNode *prev_step = NULL;
1337 unsigned char old_stance = StanceID;
1338 if (step && step->Next) {
1339 // don't interrupt in the middle of a step; path from the next one
1340 prev_step = new PathNode(*step);
1341 from.x = ( step->Next->x * 16 ) + 8;
1342 from.y = ( step->Next->y * 12 ) + 6;
1345 ClearPath();
1346 if (!prev_step) {
1347 FixPosition();
1348 from = Pos;
1350 area->ClearSearchMapFor(this);
1351 if (distance) {
1352 path = area->FindPathNear( from, Des, size, distance );
1353 } else {
1354 path = area->FindPath( from, Des, size, distance );
1356 //ClearPath sets destination, so Destination must be set after it
1357 //also we should set Destination only if there is a walkable path
1358 if (path) {
1359 Destination = Des;
1361 if (prev_step) {
1362 // we want to smoothly continue, please
1363 // this all needs more thought! but it seems to work okay
1364 StanceID = old_stance;
1366 if (path->Next) {
1367 // this is a terrible hack to make up for the
1368 // pathfinder orienting the first node wrong
1369 // should be fixed in pathfinder and not here!
1370 Point next, follow;
1371 next.x = path->x; next.y = path->y;
1372 follow.x = path->Next->x;
1373 follow.y = path->Next->y;
1374 path->orient = GetOrient(follow, next);
1377 // then put the prev_step at the beginning of the path
1378 prev_step->Next = path;
1379 path->Parent = prev_step;
1380 path = prev_step;
1382 step = path;
1384 } else {
1385 // pathing failed
1386 if (prev_step) {
1387 delete( prev_step );
1388 FixPosition();
1393 void Movable::RunAwayFrom(const Point &Des, int PathLength, int flags)
1395 ClearPath();
1396 area->ClearSearchMapFor(this);
1397 path = area->RunAway( Pos, Des, size, PathLength, flags );
1400 void Movable::RandomWalk(bool can_stop, bool run)
1402 if (path) {
1403 return;
1405 //if not continous random walk, then stops for a while
1406 if (can_stop && (rand()&3) ) {
1407 SetWait((rand()&7)+7);
1408 return;
1410 if (run) {
1411 InternalFlags|=IF_RUNNING;
1413 //the commenting-out of the clear search map call was removed in 0.4.0
1414 //if you want to put it back for some reason, check
1415 //if the searchmap is not eaten up
1416 area->ClearSearchMapFor(this);
1417 Point p = Pos;
1419 //selecting points around a circle's edge around actor (didn't work better)
1420 //int x = core->Roll(1,100,-50);
1421 //p.x+=x;
1422 //p.y+=(int) sqrt(100-x*x);
1424 //selecting points in a square around actor
1425 p.x+=core->Roll(1,50,-25);
1426 p.y+=core->Roll(1,50,-25);
1427 //the 5th parameter is controlling the orientation of the actor
1428 //0 - back away, 1 - face direction
1429 path = area->RunAway( Pos, p, size, 50, 1 );
1432 void Movable::MoveTo(const Point &Des)
1434 area->ClearSearchMapFor(this);
1435 Pos = Des;
1436 Destination = Des;
1437 if (BlocksSearchMap()) {
1438 area->BlockSearchMap( Pos, size, IsPC()?PATH_MAP_PC:PATH_MAP_NPC);
1442 void Movable::ClearPath()
1444 //this is to make sure attackers come to us
1445 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1446 //is set before ClearPath
1447 Destination = Pos;
1448 if (StanceID==IE_ANI_WALK || StanceID==IE_ANI_RUN) {
1449 StanceID = IE_ANI_AWAKE;
1451 InternalFlags&=~IF_NORECTICLE;
1452 PathNode* thisNode = path;
1453 while (thisNode) {
1454 PathNode* nextNode = thisNode->Next;
1455 delete( thisNode );
1456 thisNode = nextNode;
1458 path = NULL;
1459 step = NULL;
1460 //don't call ReleaseCurrentAction
1463 void Movable::DrawTargetPoint(const Region &vp)
1465 if (!path || !Selected || (InternalFlags&IF_NORECTICLE) )
1466 return;
1468 // recticles are never drawn in cutscenes
1469 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE))
1470 return;
1472 // generates "step" from sequence 3 2 1 0 1 2 3 4
1473 // updated each 1/15 sec
1474 unsigned long step;
1475 GetTime( step );
1476 step = tp_steps [(step >> 6) & 7];
1478 step = step + 1;
1479 int csize = (size - 1) * 4;
1480 if (csize < 4) csize = 3;
1482 /* segments should not go outside selection radius */
1483 unsigned short xradius = (csize * 4) - 5;
1484 unsigned short yradius = (csize * 3) - 5;
1485 ieWord xcentre = (ieWord) (Destination.x - vp.x);
1486 ieWord ycentre = (ieWord) (Destination.y - vp.y);
1488 // TODO: 0.5 and 0.7 are pretty much random values
1489 // right segment
1490 core->GetVideoDriver()->DrawEllipseSegment( xcentre + step, ycentre, xradius,
1491 yradius, selectedColor, -0.5, 0.5 );
1492 // top segment
1493 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre - step, xradius,
1494 yradius, selectedColor, -0.7 - M_PI_2, 0.7 - M_PI_2 );
1495 // left segment
1496 core->GetVideoDriver()->DrawEllipseSegment( xcentre - step, ycentre, xradius,
1497 yradius, selectedColor, -0.5 - M_PI, 0.5 - M_PI );
1498 // bottom segment
1499 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre + step, xradius,
1500 yradius, selectedColor, -0.7 - M_PI - M_PI_2, 0.7 - M_PI - M_PI_2 );
1503 /**********************
1504 * Tiled Object Class *
1505 **********************/
1507 TileObject::TileObject()
1509 opentiles = NULL;
1510 opencount = 0;
1511 closedtiles = NULL;
1512 closedcount = 0;
1513 Flags = 0;
1516 TileObject::~TileObject()
1518 if (opentiles) {
1519 free( opentiles );
1521 if (closedtiles) {
1522 free( closedtiles );
1526 void TileObject::SetOpenTiles(unsigned short* Tiles, int cnt)
1528 if (opentiles) {
1529 free( opentiles );
1531 opentiles = Tiles;
1532 opencount = cnt;
1535 void TileObject::SetClosedTiles(unsigned short* Tiles, int cnt)
1537 if (closedtiles) {
1538 free( closedtiles );
1540 closedtiles = Tiles;
1541 closedcount = cnt;
1544 /**************
1545 * Door Class *
1546 **************/
1548 Door::Door(TileOverlay* Overlay)
1549 : Highlightable( ST_DOOR )
1551 tiles = NULL;
1552 tilecount = 0;
1553 Flags = 0;
1554 open = NULL;
1555 closed = NULL;
1556 open_ib = NULL;
1557 oibcount = 0;
1558 closed_ib = NULL;
1559 cibcount = 0;
1560 OpenSound[0] = 0;
1561 CloseSound[0] = 0;
1562 LockSound[0] = 0;
1563 UnLockSound[0] = 0;
1564 overlay = Overlay;
1565 LinkedInfo[0] = 0;
1566 OpenStrRef = (ieDword) -1;
1569 Door::~Door(void)
1571 if (Flags&DOOR_OPEN) {
1572 if (closed) {
1573 delete( closed );
1575 } else {
1576 if (open) {
1577 delete( open );
1580 if (tiles) {
1581 free( tiles );
1583 if (open_ib) {
1584 free( open_ib );
1586 if (closed_ib) {
1587 free( closed_ib );
1591 void Door::ImpedeBlocks(int count, Point *points, unsigned char value)
1593 for(int i = 0;i<count;i++) {
1594 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_NOTDOOR;
1595 area->SearchMap->SetAt( points[i].x, points[i].y, (tmp|value) );
1599 void Door::UpdateDoor()
1601 if (Flags&DOOR_OPEN) {
1602 outline = open;
1603 } else {
1604 outline = closed;
1606 // update the Scriptable position
1607 Pos.x = outline->BBox.x + outline->BBox.w/2;
1608 Pos.y = outline->BBox.y + outline->BBox.h/2;
1610 unsigned char oval, cval;
1611 oval = PATH_MAP_IMPASSABLE;
1612 if (Flags & DOOR_TRANSPARENT) {
1613 cval = PATH_MAP_DOOR_TRANSPARENT;
1615 else {
1616 cval = PATH_MAP_DOOR_OPAQUE;
1618 if (Flags &DOOR_OPEN) {
1619 ImpedeBlocks(cibcount, closed_ib, 0);
1620 ImpedeBlocks(oibcount, open_ib, cval);
1622 else {
1623 ImpedeBlocks(oibcount, open_ib, 0);
1624 ImpedeBlocks(cibcount, closed_ib, cval);
1627 InfoPoint *ip = area->TMap->GetInfoPoint(LinkedInfo);
1628 if (ip) {
1629 if (Flags&DOOR_OPEN) ip->Flags&=~INFO_DOOR;
1630 else ip->Flags|=INFO_DOOR;
1634 void Door::ToggleTiles(int State, int playsound)
1636 int i;
1637 int state;
1639 if (State) {
1640 state = !closedIndex;
1641 if (playsound && ( OpenSound[0] != '\0' ))
1642 core->GetAudioDrv()->Play( OpenSound );
1643 } else {
1644 state = closedIndex;
1645 if (playsound && ( CloseSound[0] != '\0' ))
1646 core->GetAudioDrv()->Play( CloseSound );
1648 for (i = 0; i < tilecount; i++) {
1649 overlay->tiles[tiles[i]]->tileIndex = (ieByte) state;
1652 //set door_open as state
1653 Flags = (Flags & ~DOOR_OPEN) | (State == !core->HasFeature(GF_REVERSE_DOOR) );
1656 //this is the short name (not the scripting name)
1657 void Door::SetName(const char* name)
1659 strnlwrcpy( ID, name, 8 );
1662 void Door::SetTiles(unsigned short* Tiles, int cnt)
1664 if (tiles) {
1665 free( tiles );
1667 tiles = Tiles;
1668 tilecount = cnt;
1671 void Door::SetDoorLocked(int Locked, int playsound)
1673 if (Locked) {
1674 if (Flags & DOOR_LOCKED) return;
1675 Flags|=DOOR_LOCKED;
1676 if (playsound && ( LockSound[0] != '\0' ))
1677 core->GetAudioDrv()->Play( LockSound );
1679 else {
1680 if (!(Flags & DOOR_LOCKED)) return;
1681 Flags&=~DOOR_LOCKED;
1682 if (playsound && ( UnLockSound[0] != '\0' ))
1683 core->GetAudioDrv()->Play( UnLockSound );
1687 int Door::IsOpen() const
1689 int ret = core->HasFeature(GF_REVERSE_DOOR);
1690 if (Flags&DOOR_OPEN) {
1691 ret = !ret;
1693 return ret;
1696 //also mark actors to fix position
1697 bool Door::BlockedOpen(int Open, int ForceOpen)
1699 bool blocked;
1700 int count;
1701 Point *points;
1703 blocked = false;
1704 if (Open) {
1705 count = oibcount;
1706 points = open_ib;
1707 } else {
1708 count = cibcount;
1709 points = closed_ib;
1711 //getting all impeded actors flagged for jump
1712 Region rgn;
1713 rgn.w = 16;
1714 rgn.h = 12;
1715 for(int i = 0;i<count;i++) {
1716 Actor** ab;
1717 rgn.x = points[i].x*16;
1718 rgn.y = points[i].y*12;
1719 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_ACTOR;
1720 if (tmp) {
1721 int ac = area->GetActorInRect(ab, rgn, false);
1722 while(ac--) {
1723 if (ab[ac]->GetBase(IE_DONOTJUMP)) {
1724 continue;
1726 ab[ac]->SetBase(IE_DONOTJUMP, DNJ_JUMP);
1727 blocked = true;
1729 if (ab) {
1730 free(ab);
1735 if ((Flags&DOOR_SLIDE) || ForceOpen) {
1736 return false;
1738 return blocked;
1741 void Door::SetDoorOpen(int Open, int playsound, ieDword ID)
1743 if (playsound) {
1744 //the door cannot be blocked when opening,
1745 //but the actors will be pushed
1746 //BlockedOpen will mark actors to be pushed
1747 if (BlockedOpen(Open,0) && !Open) {
1748 //clear up the blocking actors
1749 area->JumpActors(false);
1750 return;
1752 area->JumpActors(true);
1754 if (Open) {
1755 LastEntered = ID; //used as lastOpener
1757 // in PS:T, opening a door does not unlock it
1758 if (!core->HasFeature(GF_REVERSE_DOOR)) {
1759 SetDoorLocked(false,playsound);
1761 } else {
1762 LastTriggerObject = LastTrigger = ID; //used as lastCloser
1764 ToggleTiles(Open, playsound);
1765 //synchronising other data with the door state
1766 UpdateDoor();
1767 area->ActivateWallgroups(open_wg_index, open_wg_count, Flags&DOOR_OPEN);
1768 area->ActivateWallgroups(closed_wg_index, closed_wg_count, !(Flags&DOOR_OPEN));
1771 bool Door::TryUnlock(Actor *actor) {
1772 if (!(Flags&DOOR_LOCKED)) return true;
1774 // don't remove key in PS:T!
1775 bool removekey = !core->HasFeature(GF_REVERSE_DOOR) && Flags&DOOR_KEY;
1776 return Highlightable::TryUnlock(actor, removekey);
1779 void Door::TryDetectSecret(int skill)
1781 if (Type != ST_DOOR) return;
1782 if (Visible()) return;
1783 if (skill > (signed)DiscoveryDiff) {
1784 Flags |= DOOR_FOUND;
1785 core->PlaySound(DS_FOUNDSECRET);
1789 // return true if the door isn't secret or if it is, but was already discovered
1790 bool Door::Visible()
1792 return (!(Flags & DOOR_SECRET) || (Flags & DOOR_FOUND));
1795 void Door::SetPolygon(bool Open, Gem_Polygon* poly)
1797 if (Open) {
1798 if (open)
1799 delete( open );
1800 open = poly;
1801 } else {
1802 if (closed)
1803 delete( closed );
1804 closed = poly;
1808 void Door::SetNewOverlay(TileOverlay *Overlay) {
1809 overlay = Overlay;
1810 ToggleTiles(IsOpen(), false);
1813 void Highlightable::SetTrapDetected(int x)
1815 if(x == TrapDetected)
1816 return;
1817 TrapDetected = x;
1818 if(TrapDetected) {
1819 core->Autopause(AP_TRAP);
1823 void Highlightable::TryDisarm(Actor *actor)
1825 if (!Trapped || !TrapDetected) return;
1827 LastTriggerObject = LastTrigger = actor->GetID();
1828 int skill = actor->GetStat(IE_TRAPS);
1830 if (skill/2+core->Roll(1,skill/2,0)>TrapRemovalDiff) {
1831 LastDisarmed = actor->GetID();
1832 //trap removed
1833 Trapped = 0;
1834 displaymsg->DisplayConstantStringName(STR_DISARM_DONE, 0xd7d7be, actor);
1835 int xp = actor->CalculateExperience(XP_DISARM, actor->GetXPLevel(1));
1836 Game *game = core->GetGame();
1837 game->ShareXP(xp, SX_DIVIDE);
1838 } else {
1839 displaymsg->DisplayConstantStringName(STR_DISARM_FAIL, 0xd7d7be, actor);
1840 TriggerTrap(skill, LastTrigger);
1842 ImmediateEvent();
1845 void Door::TryPickLock(Actor *actor)
1847 if (LockDifficulty == 100) {
1848 if (OpenStrRef != (ieDword)-1) {
1849 displaymsg->DisplayStringName(OpenStrRef, 0xbcefbc, actor, IE_STR_SOUND|IE_STR_SPEECH);
1850 } else {
1851 displaymsg->DisplayConstantStringName(STR_DOOR_NOPICK, 0xbcefbc, actor);
1853 return;
1855 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
1856 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
1857 LastPickLockFailed = actor->GetID();
1858 return;
1860 SetDoorLocked( false, true);
1861 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
1862 LastUnlocked = actor->GetID();
1863 ImmediateEvent();
1864 int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
1865 Game *game = core->GetGame();
1866 game->ShareXP(xp, SX_DIVIDE);
1869 void Door::TryBashLock(Actor *actor)
1871 //Get the strength bonus agains lock difficulty
1872 int str = actor->GetStat(IE_STR);
1873 int strEx = actor->GetStat(IE_STREXTRA);
1874 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
1875 unsigned int roll = actor->LuckyRoll(1, 10, bonus, 0);
1877 if(roll < LockDifficulty || LockDifficulty == 100) {
1878 displaymsg->DisplayConstantStringName(STR_DOORBASH_FAIL, 0xbcefbc, actor);
1879 return;
1882 displaymsg->DisplayConstantStringName(STR_DOORBASH_DONE, 0xd7d7be, actor);
1883 SetDoorLocked(false, true);
1884 //Is this really useful ?
1885 LastUnlocked = actor->GetID();
1886 ImmediateEvent();
1889 void Door::DebugDump() const
1891 printf( "Debugdump of Door %s:\n", GetScriptName() );
1892 printf( "Door Open: %s\n", YESNO(IsOpen()));
1893 printf( "Door Locked: %s\n", YESNO(Flags&DOOR_LOCKED));
1894 printf( "Door Trapped: %s\n", YESNO(Trapped));
1895 if (Trapped) {
1896 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags&DOOR_RESET), YESNO(Flags&DOOR_DETECTABLE) );
1898 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags&DOOR_SECRET),YESNO(Flags&DOOR_FOUND));
1899 const char *Key = GetKey();
1900 const char *name = "NONE";
1901 if (Scripts[0]) {
1902 name = Scripts[0]->GetName();
1904 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name, Key?Key:"NONE", YESNO(Flags&DOOR_KEY), Dialog );
1907 /*******************
1908 * InfoPoint Class *
1909 *******************/
1911 InfoPoint::InfoPoint(void)
1912 : Highlightable( ST_TRIGGER )
1914 Destination[0] = 0;
1915 EntranceName[0] = 0;
1916 Flags = 0;
1917 TrapDetectionDiff = 0;
1918 TrapRemovalDiff = 0;
1919 TrapDetected = 0;
1920 TrapLaunch.empty();
1921 Dialog[0] = 0;
1924 InfoPoint::~InfoPoint(void)
1928 //checks if the actor may use this travel trigger
1929 //bit 1 : can use
1930 //bit 2 : whole team
1931 int InfoPoint::CheckTravel(Actor *actor)
1933 if (Flags&TRAP_DEACTIVATED) return CT_CANTMOVE;
1934 if (!actor->InParty && (Flags&TRAVEL_NONPC) ) return CT_CANTMOVE;
1935 if (actor->InParty && (Flags&TRAVEL_PARTY) ) {
1936 if (core->HasFeature(GF_TEAM_MOVEMENT) || core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE) ) {
1937 return CT_WHOLE;
1939 return CT_GO_CLOSER;
1941 if(actor->IsSelected() ) {
1942 if(core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE|ENP_ONLYSELECT) ) {
1943 return CT_MOVE_SELECTED;
1945 return CT_SELECTED;
1947 return CT_ACTIVE;
1950 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1951 //skill is the all around modified trap detection skill
1952 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1953 void Highlightable::DetectTrap(int skill)
1955 if (!CanDetectTrap()) return;
1956 if (!Scripts[0]) return;
1957 if ((skill>=100) && (skill!=256) ) skill = 100;
1958 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1959 SetTrapDetected(1); //probably could be set to the player #?
1963 bool Highlightable::PossibleToSeeTrap() const
1965 return CanDetectTrap();
1968 bool InfoPoint::PossibleToSeeTrap() const
1970 // Only detectable trap-type infopoints.
1971 return (CanDetectTrap() && (Type == ST_PROXIMITY) );
1974 bool InfoPoint::CanDetectTrap() const
1976 // Traps can be detected on all types of infopoint, as long
1977 // as the trap is detectable and isn't deactivated.
1978 return ((Flags&TRAP_DETECTABLE) && !(Flags&TRAP_DEACTIVATED));
1981 // returns true if the infopoint is a PS:T portal
1982 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1983 bool InfoPoint::IsPortal() const
1985 if (Type!=ST_TRAVEL) return false;
1986 if (Cursor != IE_CURSOR_PORTAL) return false;
1987 return core->HasFeature(GF_REVERSE_DOOR);
1990 //trap that is visible on screen (marked by red)
1991 //if TrapDetected is a bitflag, we could show traps selectively for
1992 //players, really nice for multiplayer
1993 bool Highlightable::VisibleTrap(int see_all) const
1995 if (!Trapped) return false;
1996 if (!PossibleToSeeTrap()) return false;
1997 if (!Scripts[0]) return false;
1998 if (see_all) return true;
1999 if (TrapDetected ) return true;
2000 return false;
2003 //trap that will fire now
2004 bool Highlightable::TriggerTrap(int skill, ieDword ID)
2006 if (!Trapped) {
2007 return false;
2009 //actually this could be script name[0]
2010 if (!Scripts[0]) {
2011 return false;
2013 if (CanDetectTrap()) {
2014 // this should probably be party members only
2015 if ((skill>=100) && (skill!=256) ) skill = 100;
2016 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
2017 SetTrapDetected(1); //probably too late :)
2018 //tumble???
2019 return false;
2022 LastTriggerObject = LastTrigger = LastEntered = ID;
2023 ImmediateEvent();
2024 if (!TrapResets()) {
2025 Trapped = false;
2027 return true;
2030 //trap that will fire now
2031 bool InfoPoint::TriggerTrap(int skill, ieDword ID)
2033 if (Type!=ST_PROXIMITY) {
2034 return true;
2036 if (Flags&TRAP_DEACTIVATED) {
2037 return false;
2039 if (!Trapped) {
2040 // we have to set Entered somewhere, here seems best..
2041 LastEntered = ID;
2042 return true;
2043 } else if (Highlightable::TriggerTrap(skill, ID)) {
2044 if (!Trapped) {
2045 Flags|=TRAP_DEACTIVATED;
2047 // ok, so this is a pain. Entered() trigger checks Trapped,
2048 // so it needs to be kept set. how to do this right?
2049 Trapped = true;
2050 return true;
2052 return false;
2055 bool InfoPoint::Entered(Actor *actor)
2057 if (outline->PointIn( actor->Pos ) ) {
2058 //don't trigger again for this actor
2059 if (!(actor->GetInternalFlag()&IF_INTRAP)) {
2060 goto check;
2063 // why is this here? actors which aren't *in* a trap get IF_INTRAP
2064 // repeatedly unset, so this triggers again and again and again.
2065 // i disabled it for ST_PROXIMITY for now..
2066 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
2067 goto check;
2069 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
2070 if (Type == ST_TRAVEL && PersonalDistance(TrapLaunch, actor)<MAX_OPERATING_DISTANCE) {
2071 goto check;
2073 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
2074 // for distance from the outline for travel regions instead?
2075 if (Type == ST_TRAVEL && PersonalDistance(TalkPos, actor)<MAX_OPERATING_DISTANCE) {
2076 goto check;
2078 if (Flags&TRAP_USEPOINT) {
2079 if (PersonalDistance(UsePoint, actor)<MAX_OPERATING_DISTANCE) {
2080 goto check;
2083 return false;
2084 check:
2085 if (Type==ST_TRAVEL) {
2086 return true;
2089 if (actor->InParty || (Flags&TRAP_NPC) ) {
2090 //no need to avoid a travel trigger
2092 //skill?
2093 if (TriggerTrap(0, actor->GetID()) ) {
2094 return true;
2097 return false;
2100 void InfoPoint::DebugDump() const
2102 switch (Type) {
2103 case ST_TRIGGER:
2104 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2105 break;
2106 case ST_PROXIMITY:
2107 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2108 break;
2109 case ST_TRAVEL:
2110 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2111 break;
2112 default:
2113 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2114 break;
2116 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected, YESNO(Trapped));
2117 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2118 TrapRemovalDiff );
2119 const char *name = "NONE";
2120 if (Scripts[0]) {
2121 name = Scripts[0]->GetName();
2123 printf( "Script: %s, Key: %s, Dialog: %s\n", name, KeyResRef, Dialog );
2124 printf( "Active: %s\n", YESNO(InternalFlags&IF_ACTIVE));
2127 /*******************
2128 * Container Class *
2129 *******************/
2131 Container::Container(void)
2132 : Highlightable( ST_CONTAINER )
2134 Type = 0;
2135 LockDifficulty = 0;
2136 Flags = 0;
2137 TrapDetectionDiff = 0;
2138 TrapRemovalDiff = 0;
2139 Trapped = 0;
2140 TrapDetected = 0;
2141 inventory.SetInventoryType(INVENTORY_HEAP);
2142 // NULL should be 0 for this
2143 memset (groundicons, 0, sizeof(groundicons) );
2144 groundiconcover = 0;
2147 void Container::FreeGroundIcons()
2149 Video* video = core->GetVideoDriver();
2151 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2152 if (groundicons[i]) {
2153 video->FreeSprite( groundicons[i] );
2154 groundicons[i]=NULL;
2157 delete groundiconcover;
2158 groundiconcover = 0;
2161 Container::~Container()
2163 FreeGroundIcons();
2166 void Container::DrawPile(bool highlight, Region screen, Color tint)
2168 Video* video = core->GetVideoDriver();
2169 CreateGroundIconCover();
2170 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2171 if (groundicons[i]) {
2172 //draw it with highlight
2173 video->BlitGameSprite(groundicons[i],
2174 screen.x + Pos.x, screen.y + Pos.y,
2175 BLIT_TINTED | (highlight ? 0:BLIT_NOSHADOW),
2176 tint, groundiconcover);
2181 // create the SpriteCover for the groundicons
2182 void Container::CreateGroundIconCover()
2184 int xpos = 0;
2185 int ypos = 0;
2186 int width = 0;
2187 int height = 0;
2189 int i; //msvc6.0
2190 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2191 if (groundicons[i]) {
2192 Sprite2D& spr = *groundicons[i];
2193 if (xpos < spr.XPos) {
2194 width += spr.XPos - xpos;
2195 xpos = spr.XPos;
2197 if (ypos < spr.YPos) {
2198 height += spr.YPos - ypos;
2199 ypos = spr.YPos;
2201 if (width-xpos < spr.Width-spr.XPos) {
2202 width = spr.Width-spr.XPos+xpos;
2204 if (height-ypos < spr.Height-spr.YPos) {
2205 height = spr.Height-spr.YPos+ypos;
2210 if (!groundiconcover ||
2211 !groundiconcover->Covers(Pos.x, Pos.y, xpos, ypos, width, height))
2213 delete groundiconcover;
2214 groundiconcover = 0;
2215 if (width*height > 0) {
2216 groundiconcover = GetCurrentArea()->BuildSpriteCover
2217 (Pos.x, Pos.y, xpos, ypos, width, height, WantDither());
2221 #ifndef NDEBUG
2222 // TODO: remove this checking code eventually
2223 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2224 if (groundicons[i]) {
2225 Sprite2D& spr = *groundicons[i];
2226 assert(groundiconcover->Covers(Pos.x, Pos.y, spr.XPos, spr.YPos, spr.Width, spr.Height));
2229 #endif
2232 void Container::SetContainerLocked(bool lock)
2234 if (lock) {
2235 Flags|=CONT_LOCKED;
2236 } else {
2237 Flags&=~CONT_LOCKED;
2241 //This function doesn't exist in the original IE, destroys a container
2242 //turning it to a ground pile
2243 void Container::DestroyContainer()
2245 //it is already a groundpile?
2246 if (Type == IE_CONTAINER_PILE)
2247 return;
2248 Type = IE_CONTAINER_PILE;
2249 RefreshGroundIcons();
2250 //probably we should stop the script or trigger it, whatever
2253 //Takes an item from the container's inventory and returns its pointer
2254 CREItem *Container::RemoveItem(unsigned int idx, unsigned int count)
2256 CREItem *ret = inventory.RemoveItem(idx, count);
2257 //we just took the 3. or less item, groundpile changed
2258 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<3)) {
2259 RefreshGroundIcons();
2261 return ret;
2264 //Adds an item to the container's inventory
2265 //containers always have enough capacity (so far), thus we always return 2
2266 int Container::AddItem(CREItem *item)
2268 inventory.AddItem(item);
2269 //we just added a 3. or less item, groundpile changed
2270 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<4)) {
2271 RefreshGroundIcons();
2273 return 2;
2276 void Container::RefreshGroundIcons()
2278 int i = inventory.GetSlotCount();
2279 if (i>MAX_GROUND_ICON_DRAWN)
2280 i = MAX_GROUND_ICON_DRAWN;
2281 FreeGroundIcons();
2282 while (i--) {
2283 CREItem *slot = inventory.GetSlotItem(i); //borrowed reference
2284 Item *itm = gamedata->GetItem( slot->ItemResRef ); //cached reference
2285 //well, this is required in PST, needs more work if some other
2286 //game is broken by not using -1,0
2287 groundicons[i] = gamedata->GetBAMSprite( itm->GroundIcon, 0, 0 );
2288 gamedata->FreeItem( itm, slot->ItemResRef ); //decref
2292 //used for ground piles
2293 int Container::WantDither()
2295 //if pile is highlighted, always dither it
2296 if (Highlight) {
2297 return 2; //dither me if you want
2299 //if pile isn't highlighted, dither it if the polygon wants
2300 return 1;
2303 int Container::IsOpen() const
2305 if (Flags&CONT_LOCKED) {
2306 return false;
2308 return true;
2311 void Container::TryPickLock(Actor *actor)
2313 if (LockDifficulty == 100) {
2314 if (OpenFail != (ieDword)-1) {
2315 displaymsg->DisplayStringName(OpenFail, 0xbcefbc, actor, IE_STR_SOUND|IE_STR_SPEECH);
2316 } else {
2317 displaymsg->DisplayConstantStringName(STR_CONT_NOPICK, 0xbcefbc, actor);
2319 return;
2321 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
2322 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
2323 LastPickLockFailed = actor->GetID();
2324 return;
2326 SetContainerLocked(false);
2327 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
2328 LastUnlocked = actor->GetID();
2329 ImmediateEvent();
2330 int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
2331 Game *game = core->GetGame();
2332 game->ShareXP(xp, SX_DIVIDE);
2335 void Container::TryBashLock(Actor *actor)
2337 //Get the strength bonus agains lock difficulty
2338 int str = actor->GetStat(IE_STR);
2339 int strEx = actor->GetStat(IE_STREXTRA);
2340 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
2341 unsigned int roll = actor->LuckyRoll(1, 10, bonus, 0);
2343 if(roll < LockDifficulty || LockDifficulty == 100) {
2344 displaymsg->DisplayConstantStringName(STR_CONTBASH_FAIL, 0xbcefbc, actor);
2345 return;
2348 displaymsg->DisplayConstantStringName(STR_CONTBASH_DONE, 0xd7d7be, actor);
2349 SetContainerLocked(false);
2350 //Is this really useful ?
2351 LastUnlocked = actor->GetID();
2352 ImmediateEvent();
2355 void Container::DebugDump() const
2357 printf( "Debugdump of Container %s\n", GetScriptName() );
2358 printf( "Type: %d, LockDifficulty: %d\n", Type, LockDifficulty );
2359 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags, YESNO(Trapped), TrapDetected );
2360 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2361 TrapRemovalDiff );
2362 const char *name = "NONE";
2363 if (Scripts[0]) {
2364 name = Scripts[0]->GetName();
2366 printf( "Script: %s, Key: %s\n", name, KeyResRef );
2367 // FIXME: const_cast
2368 const_cast<Inventory&>(inventory).dump();
2371 bool Container::TryUnlock(Actor *actor) {
2372 if (!(Flags&CONT_LOCKED)) return true;
2374 return Highlightable::TryUnlock(actor, false);