Constificiation.
[gemrb.git] / gemrb / core / ActorBlock.cpp
blob6570c858291675ea77d3670f17ff252a1cf2ddca
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.
19 #include <cassert>
20 #include <cmath>
22 #include "win32def.h"
23 #include "strrefs.h"
24 #include "ActorBlock.h"
25 #include "Interface.h"
26 #include "SpriteCover.h"
27 #include "TileMap.h"
28 #include "Video.h"
29 #include "Audio.h"
30 #include "Item.h"
31 #include "Spell.h"
32 #include "Map.h"
33 #include "Game.h"
34 #include "GameControl.h"
35 #include "Projectile.h"
36 #include "GSUtils.h"
37 #include "GameData.h"
39 #define YESNO(x) ( (x)?"Yes":"No")
41 /***********************
42 * Scriptable Class *
43 ***********************/
44 Scriptable::Scriptable(ScriptableType type)
46 Type = type;
47 CutSceneId = NULL;
48 for (int i = 0; i < MAX_SCRIPTS; i++) {
49 Scripts[i] = NULL;
51 overHeadText = NULL;
52 overHeadTextPos.empty();
53 textDisplaying = 0;
54 timeStartDisplaying = 0;
55 scriptName[0] = 0;
56 TriggerID = 0; //used by SendTrigger
57 LastTriggerObject = LastTrigger = 0;
58 LastEntered = 0;
59 LastDisarmed = 0;
60 LastDisarmFailed = 0;
61 LastUnlocked = 0;
62 LastOpenFailed = 0;
63 LastPickLockFailed = 0;
64 DialogName = 0;
65 CurrentAction = NULL;
66 CurrentActionState = 0;
67 CurrentActionTarget = 0;
68 CurrentActionInterruptable = true;
69 UnselectableTimer = 0;
70 startTime = 0; //executing scripts
71 lastRunTime = 0; //evaluating scripts
72 lastDelay = 0;
73 Dialog[0] = 0;
75 interval = ( 1000 / AI_UPDATE_TIME );
76 WaitCounter = 0;
77 playDeadCounter = 0;
78 if (Type == ST_ACTOR) {
79 InternalFlags = IF_VISIBLE | IF_ONCREATION | IF_USEDSAVE;
80 } else {
81 InternalFlags = IF_ACTIVE | IF_VISIBLE | IF_ONCREATION | IF_NOINT;
83 area = 0;
84 Pos.x = 0;
85 Pos.y = 0;
87 LastCasterOnMe = 0;
88 LastSpellOnMe = 0xffffffff;
89 LastCasterSeen = 0;
90 LastSpellSeen = 0xffffffff;
91 SpellHeader = -1;
92 LastTargetPos.empty();
93 locals = new Variables();
94 locals->SetType( GEM_VARIABLES_INT );
95 locals->ParseKey( 1 );
96 InitTriggers();
98 memset( script_timers,0, sizeof(script_timers));
101 Scriptable::~Scriptable(void)
103 if (CurrentAction) {
104 ReleaseCurrentAction();
106 ClearActions();
107 for (int i = 0; i < MAX_SCRIPTS; i++) {
108 if (Scripts[i]) {
109 delete( Scripts[i] );
112 if (overHeadText) {
113 core->FreeString( overHeadText );
115 if (locals) {
116 delete( locals );
120 void Scriptable::SetScriptName(const char* text)
122 //if (text && text[0]) { //this leaves some uninitialized bytes
123 //lets hope this won't break anything
124 if (text) {
125 strnspccpy( scriptName, text, 32 );
129 /** Gets the DeathVariable */
130 const char* Scriptable::GetScriptName(void) const
132 return scriptName;
135 Map* Scriptable::GetCurrentArea() const
137 //this could be NULL, always check it
138 return area;
141 void Scriptable::SetMap(Map *map)
143 if (!map) {
144 printMessage("Scriptable","Null map set!\n",LIGHT_RED);
145 abort();
147 area = map;
150 //ai is nonzero if this is an actor currently in the party
151 //if the script level is AI_SCRIPT_LEVEL, then we need to
152 //load an AI script (.bs) instead of (.bcs)
153 void Scriptable::SetScript(const ieResRef aScript, int idx, bool ai)
155 if (idx >= MAX_SCRIPTS) {
156 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
157 abort();
159 if (Scripts[idx]) {
160 delete Scripts[idx];
162 Scripts[idx] = NULL;
163 // NONE is an 'invalid' script name, never used seriously
164 // This hack is to prevent flooding of the console
165 if (aScript[0] && stricmp(aScript, "NONE") ) {
166 if (idx!=AI_SCRIPT_LEVEL) ai = false;
167 Scripts[idx] = new GameScript( aScript, this, idx, ai );
171 void Scriptable::SetScript(int index, GameScript* script)
173 if (index >= MAX_SCRIPTS) {
174 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
175 return;
177 if (Scripts[index] ) {
178 delete Scripts[index];
180 Scripts[index] = script;
183 void Scriptable::DisplayHeadText(const char* text)
185 if (overHeadText) {
186 core->FreeString( overHeadText );
188 overHeadText = (char *) text;
189 overHeadTextPos.empty();
190 if (text) {
191 timeStartDisplaying = core->GetGame()->Ticks;
192 textDisplaying = 1;
194 else {
195 timeStartDisplaying = 0;
196 textDisplaying = 0;
200 /* 'fix' the current overhead text in the current position */
201 void Scriptable::FixHeadTextPos()
203 overHeadTextPos = Pos;
206 #define MAX_DELAY 6000
207 static const Color black={0,0,0,0};
209 void Scriptable::DrawOverheadText(const Region &screen)
211 unsigned long time = core->GetGame()->Ticks;
212 Palette *palette = NULL;
214 if (!textDisplaying)
215 return;
217 time -= timeStartDisplaying;
219 Font* font = core->GetFont( 1 );
220 if (time >= MAX_DELAY) {
221 textDisplaying = 0;
222 return;
223 } else {
224 time = (MAX_DELAY-time)/10;
225 if (time<256) {
226 const Color overHeadColor = {time,time,time,time};
227 palette = core->CreatePalette(overHeadColor, black);
231 int cs = 100;
232 if (Type == ST_ACTOR) {
233 cs = ((Selectable *) this)->size*50;
236 short x, y;
237 if (overHeadTextPos.isempty()) {
238 x = Pos.x;
239 y = Pos.y;
240 } else {
241 x = overHeadTextPos.x;
242 y = overHeadTextPos.y;
245 Region rgn( x-100+screen.x, y - cs + screen.y, 200, 400 );
246 font->Print( rgn, ( unsigned char * ) overHeadText,
247 palette?palette:core->InfoTextPalette, IE_FONT_ALIGN_CENTER | IE_FONT_ALIGN_TOP, false );
248 gamedata->FreePalette(palette);
251 void Scriptable::DelayedEvent()
253 lastRunTime = core->GetGame()->Ticks;
256 void Scriptable::ImmediateEvent()
258 lastRunTime = 0;
261 bool Scriptable::IsPC() const
263 if(Type == ST_ACTOR) {
264 if (((Actor *) this)->GetStat(IE_EA) <= EA_CHARMED) {
265 return true;
268 return false;
271 void Scriptable::ExecuteScript(int scriptCount)
273 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
274 if ((Type != ST_AREA) && (core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE)) {
275 return;
278 if ((InternalFlags & IF_NOINT) && (CurrentAction || GetNextAction())) {
279 return;
282 if (!CurrentActionInterruptable) {
283 if (!CurrentAction && !GetNextAction()) abort();
284 return;
287 // only allow death scripts to run once, hopefully?
288 // this is probably terrible logic which needs moving elsewhere
289 if ((lastRunTime != 0) && (InternalFlags & IF_JUSTDIED)) {
290 return;
293 ieDword thisTime = core->GetGame()->Ticks;
294 if (( thisTime - lastRunTime ) < 1000) {
295 return;
298 lastDelay = lastRunTime;
299 lastRunTime = thisTime;
301 bool alive = false;
303 bool continuing = false, done = false;
304 for (int i = 0;i<scriptCount;i++) {
305 //disable AI script level for actors in party when the player disabled them
306 if ((i == AI_SCRIPT_LEVEL) && Type == ST_ACTOR && ((Actor *) this)->InParty) {
307 if (core->GetGame()->ControlStatus&CS_PARTY_AI) {
308 continue;
312 GameScript *Script = Scripts[i];
313 if (Script) {
314 alive |= Script->Update(&continuing, &done);
317 /* scripts are not concurrent, see WAITPC override script for example */
318 if (done) break;
320 if (alive && UnselectableTimer) {
321 UnselectableTimer--;
322 if (!UnselectableTimer) {
323 if (Type == ST_ACTOR) {
324 ((Actor *) this)->SetCircleSize();
330 void Scriptable::AddAction(Action* aC)
332 if (!aC) {
333 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
334 return;
336 InternalFlags|=IF_ACTIVE;
337 actionQueue.push_back( aC );
338 aC->IncRef();
341 void Scriptable::AddActionInFront(Action* aC)
343 if (!aC) {
344 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
345 return;
347 InternalFlags|=IF_ACTIVE;
348 actionQueue.push_front( aC );
349 aC->IncRef();
352 Action* Scriptable::GetNextAction() const
354 if (actionQueue.size() == 0) {
355 return NULL;
357 return actionQueue.front();
360 Action* Scriptable::PopNextAction()
362 if (actionQueue.size() == 0) {
363 return NULL;
365 Action* aC = actionQueue.front();
366 actionQueue.pop_front();
367 return aC;
370 void Scriptable::ClearActions()
372 ReleaseCurrentAction();
373 for (unsigned int i = 0; i < actionQueue.size(); i++) {
374 Action* aC = actionQueue.front();
375 actionQueue.pop_front();
376 aC->Release();
378 actionQueue.clear();
379 WaitCounter = 0;
380 playDeadCounter = 0; // i'm not sure about this
381 LastTarget = 0;
382 //clear the triggers as fast as possible when queue ended?
383 ClearTriggers();
385 if (Type == ST_ACTOR) {
386 Interrupt();
387 } else {
388 NoInterrupt();
392 void Scriptable::ReleaseCurrentAction()
394 if (CurrentAction) {
395 CurrentAction->Release();
396 CurrentAction = NULL;
399 CurrentActionState = 0;
400 CurrentActionTarget = 0;
401 CurrentActionInterruptable = true;
404 ieWord Scriptable::GetGlobalID()
406 if (Type == ST_ACTOR) {
407 Actor *actor = (Actor *) this;
408 return actor->globalID;
410 return 0;
413 void Scriptable::ProcessActions(bool force)
415 unsigned long thisTime = core->GetGame()->Ticks;
417 if (!force && (( thisTime - startTime ) < interval)) {
418 return;
420 startTime = thisTime;
421 if (playDeadCounter) {
422 playDeadCounter--;
423 if (!playDeadCounter) {
424 Movable* mov = ( Movable* ) this;
425 mov->SetStance( IE_ANI_GET_UP );
428 if (WaitCounter) {
429 WaitCounter--;
430 if (WaitCounter) return;
433 while (true) {
434 CurrentActionInterruptable = true;
435 if (!CurrentAction) {
436 CurrentAction = PopNextAction();
438 if (!CurrentAction) {
439 ClearActions();
440 if (CutSceneId) {
441 CutSceneId = NULL;
443 //removing the triggers at the end of the
444 //block
445 //ClearTriggers();
446 break;
448 GameScript::ExecuteAction( this, CurrentAction );
449 //break execution in case of a Wait flag
450 if (WaitCounter) {
451 //clear triggers while waiting
452 //ClearTriggers();
453 break;
455 //break execution in case of blocking action
456 if (CurrentAction) {
457 break;
459 //break execution in case of movement
460 //we should not actually break here, or else fix waypoints
461 if (InMove()) {
462 break;
465 //most likely the best place to clear triggers is here
466 //queue is empty, or there is a looong action subject to break
467 ClearTriggers();
468 if (InternalFlags&IF_IDLE) {
469 Deactivate();
473 bool Scriptable::InMove() const
475 if (Type!=ST_ACTOR) {
476 return false;
478 Movable *me = (Movable *) this;
479 return me->GetNextStep()!=NULL;
482 void Scriptable::SetWait(unsigned long time)
484 WaitCounter = time;
487 unsigned long Scriptable::GetWait() const
489 return WaitCounter;
492 Scriptable *Scriptable::GetCutsceneID() const
494 return CutSceneId;
497 void Scriptable::LeaveDialog()
499 InternalFlags |=IF_WASINDIALOG;
502 //this ends cutscene mode for this Sender
503 void Scriptable::ClearCutsceneID()
505 CutSceneId = NULL;
506 InternalFlags &= ~IF_CUTSCENEID;
509 //if the cutsceneID doesn't exist, we simply skip the action
510 //because the cutscene script executer DOESN'T get hijacked
511 void Scriptable::SetCutsceneID(Scriptable *csid)
513 CutSceneId = csid;
514 InternalFlags |= IF_CUTSCENEID;
517 void Scriptable::Hide()
519 InternalFlags &=~(IF_VISIBLE);
522 void Scriptable::Unhide()
524 InternalFlags |= IF_VISIBLE;
527 void Scriptable::Interrupt()
529 InternalFlags &= ~IF_NOINT;
532 void Scriptable::NoInterrupt()
534 InternalFlags |= IF_NOINT;
537 //also turning off the idle flag so it won't run continuously
538 void Scriptable::Deactivate()
540 InternalFlags &=~(IF_ACTIVE|IF_IDLE);
543 //turning off the not interruptable flag, actions should reenable it themselves
544 //also turning off the idle flag
545 //heh, no, i wonder why did i touch the interruptable flag here
546 void Scriptable::Activate()
548 InternalFlags |= IF_ACTIVE;
549 InternalFlags &= ~IF_IDLE;
552 void Scriptable::PartyRested()
554 InternalFlags |=IF_PARTYRESTED;
557 ieDword Scriptable::GetInternalFlag()
559 return InternalFlags;
562 void Scriptable::InitTriggers()
564 tolist.clear();
565 bittriggers = 0;
568 void Scriptable::ClearTriggers()
570 for (TriggerObjects::iterator m = tolist.begin(); m != tolist.end (); m++) {
571 *(*m) = 0;
573 if (!bittriggers) {
574 return;
576 if (bittriggers & BT_DIE) {
577 InternalFlags &= ~IF_JUSTDIED;
579 if (bittriggers & BT_ONCREATION) {
580 InternalFlags &= ~IF_ONCREATION;
582 if (bittriggers & BT_BECAMEVISIBLE) {
583 InternalFlags &= ~IF_BECAMEVISIBLE;
585 if (bittriggers & BT_PARTYRESTED) {
586 InternalFlags &= ~IF_PARTYRESTED;
588 if (bittriggers & BT_WASINDIALOG) {
589 InternalFlags &= ~IF_WASINDIALOG;
591 if (bittriggers & BT_PARTYRESTED) {
592 InternalFlags &= ~IF_PARTYRESTED;
594 InitTriggers();
597 void Scriptable::SetBitTrigger(ieDword bittrigger)
599 bittriggers |= bittrigger;
602 void Scriptable::AddTrigger(ieDword *actorref)
604 tolist.push_back(actorref);
607 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef )
609 if (Type == ST_ACTOR) {
610 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
613 if (SpellHeader == -1) {
614 LastTargetPos.empty();
615 return;
618 if (LastTargetPos.isempty()) {
619 SpellHeader = -1;
620 return;
623 Spell* spl = gamedata->GetSpell( SpellResRef );
624 //create projectile from known spellheader
625 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
626 SpellHeader = -1;
627 if (pro) {
628 pro->SetCaster(GetGlobalID());
629 Point origin = Pos;
630 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
631 // try and make projectiles start from the right trap position
632 // see the traps in the duergar/assassin battle in bg2 dungeon
633 // see also function below - maybe we should fix Pos instead
634 origin = ((InfoPoint *)this)->TrapLaunch;
636 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
638 LastTarget = 0;
639 LastTargetPos.empty();
642 void Scriptable::CastSpellEnd( const ieResRef SpellResRef )
644 if (Type == ST_ACTOR) {
645 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
648 if (SpellHeader == -1) {
649 LastTarget = 0;
650 return;
652 if (!LastTarget) {
653 SpellHeader = -1;
654 return;
656 Spell* spl = gamedata->GetSpell( SpellResRef );
657 //create projectile from known spellheader
658 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
659 if (pro) {
660 pro->SetCaster(GetGlobalID());
661 Point origin = Pos;
662 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
663 // try and make projectiles start from the right trap position
664 // see the traps in the duergar/assassin battle in bg2 dungeon
665 // see also function above - maybe we should fix Pos instead
666 origin = ((InfoPoint *)this)->TrapLaunch;
668 if (LastTarget) {
669 GetCurrentArea()->AddProjectile(pro, origin, LastTarget);
670 } else {
671 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
674 ieDword spellnum=ResolveSpellNumber( SpellResRef );
675 if (spellnum!=0xffffffff) {
676 area->SeeSpellCast(this, spellnum);
677 if(LastTarget) {
678 Scriptable *target = area->GetActorByGlobalID(LastTarget);
679 if (target && (Type==ST_ACTOR) ) {
680 Actor *me = (Actor *) this;
681 target->LastSpellOnMe = spellnum;
682 target->LastCasterOnMe = me->GetID();
683 me->CureInvisibility();
684 if (target!=this) {
685 me->CureSanctuary();
691 gamedata->FreeSpell(spl, SpellResRef, false);
692 LastTarget = 0;
693 LastTargetPos.empty();
696 //set target as point
697 //if spell needs to be depleted, do it
698 //if spell is illegal stop casting
699 int Scriptable::CastSpellPoint( const ieResRef SpellResRef, const Point &target, bool deplete, bool instant )
701 LastTarget = 0;
702 LastTargetPos.empty();
703 if (Type == ST_ACTOR) {
704 Actor *actor = (Actor *) this;
705 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
706 return -1;
709 LastTargetPos = target;
710 return SpellCast(SpellResRef, instant);
713 //set target as actor (if target isn't actor, use its position)
714 //if spell needs to be depleted, do it
715 //if spell is illegal stop casting
716 int Scriptable::CastSpell( const ieResRef SpellResRef, Scriptable* target, bool deplete, bool instant )
718 LastTarget = 0;
719 LastTargetPos.empty();
720 if (Type == ST_ACTOR) {
721 Actor *actor = (Actor *) this;
722 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
723 return -1;
727 if (!target) target = this;
729 LastTargetPos = target->Pos;
730 if (target->Type==ST_ACTOR) {
731 LastTarget = target->GetGlobalID();
733 return SpellCast(SpellResRef, instant);
736 //start spellcasting (common part)
737 int Scriptable::SpellCast(const ieResRef SpellResRef, bool instant)
739 Spell* spl = gamedata->GetSpell( SpellResRef );
740 if (!spl) {
741 SpellHeader = -1;
742 return -1;
745 if (Type == ST_ACTOR) {
746 Actor *actor = (Actor *) this;
747 //The ext. index is here to calculate the casting time
748 int level = actor->GetXPLevel(true);
749 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
750 level += actor->CastingLevelBonus(level, spl->SpellType);
751 SpellHeader = spl->GetHeaderIndexFromLevel(level);
752 } else {
753 SpellHeader = 0;
756 SPLExtHeader *header = spl->GetExtHeader(SpellHeader);
757 int casting_time = (int)header->CastingTime;
758 // how does this work for non-actors exactly?
759 if (Type == ST_ACTOR) {
760 // The mental speed effect can shorten or lengthen the casting time.
761 casting_time -= (int)((Actor *) this)->GetStat(IE_MENTALSPEED);
762 if (casting_time < 0) casting_time = 0;
764 // this is a guess which seems approximately right so far
765 int duration = (casting_time*ROUND_SIZE) / 10;
766 if (instant)
767 duration = 0;
769 //cfb
770 if (Type == ST_ACTOR) {
771 Actor *actor = (Actor *) this;
772 EffectQueue *fxqueue = spl->GetEffectBlock(this, this->Pos, -1);
773 spl->AddCastingGlow(fxqueue, duration);
774 fxqueue->SetOwner(actor);
775 fxqueue->AddAllEffects(actor, actor->Pos);
776 delete fxqueue;
779 gamedata->FreeSpell(spl, SpellResRef, false);
780 return duration;
783 bool Scriptable::TimerActive(ieDword ID)
785 if (ID>=MAX_TIMER) {
786 return false;
788 if (script_timers[ID]) {
789 return true;
791 return false;
794 bool Scriptable::TimerExpired(ieDword ID)
796 if (ID>=MAX_TIMER) {
797 return false;
799 if (script_timers[ID] && script_timers[ID] < core->GetGame()->GameTime) {
800 // expired timers become inactive after being checked
801 script_timers[ID] = 0;
802 return true;
804 return false;
807 void Scriptable::StartTimer(ieDword ID, ieDword expiration)
809 if (ID>=MAX_TIMER) {
810 printMessage("Scriptable", " ", RED);
811 printf("Timer id %d exceeded MAX_TIMER %d\n", ID, MAX_TIMER);
812 return;
814 script_timers[ID]= core->GetGame()->GameTime + expiration*AI_UPDATE_TIME;
817 /********************
818 * Selectable Class *
819 ********************/
821 Selectable::Selectable(ScriptableType type)
822 : Scriptable( type )
824 Selected = false;
825 Over = false;
826 size = 0;
827 cover = NULL;
828 circleBitmap[0] = NULL;
829 circleBitmap[1] = NULL;
832 void Selectable::SetSpriteCover(SpriteCover* c)
834 delete cover;
835 cover = c;
838 Selectable::~Selectable(void)
840 delete cover;
843 void Selectable::SetBBox(const Region &newBBox)
845 BBox = newBBox;
848 static const unsigned long tp_steps[8]={3,2,1,0,1,2,3,4};
850 void Selectable::DrawCircle(const Region &vp)
852 /* BG2 colours ground circles as follows:
853 dark green for unselected party members
854 bright green for selected party members
855 flashing green/white for a party member the mouse is over
856 bright red for enemies
857 yellow for panicked actors
858 flashing red/white for enemies the mouse is over
859 flashing cyan/white for neutrals the mouse is over
862 if (size<=0) {
863 return;
865 Color mix;
866 Color* col = &selectedColor;
867 Sprite2D* sprite = circleBitmap[0];
869 if (Selected) {
870 sprite = circleBitmap[1];
871 } else if (Over) {
872 //doing a time dependent flashing of colors
873 //if it is too fast, increase the 6 to 7
874 unsigned long step;
875 GetTime( step );
876 step = tp_steps [(step >> 6) & 7];
877 mix.a = overColor.a;
878 mix.r = (overColor.r*step+selectedColor.r*(8-step))/8;
879 mix.g = (overColor.g*step+selectedColor.g*(8-step))/8;
880 mix.b = (overColor.b*step+selectedColor.b*(8-step))/8;
881 col = &mix;
882 } else if (IsPC()) {
883 col = &overColor;
886 if (sprite) {
887 core->GetVideoDriver()->BlitSprite( sprite, Pos.x - vp.x, Pos.y - vp.y, true );
888 } else {
889 // for size >= 2, radii are (size-1)*16, (size-1)*12
890 // for size == 1, radii are 12, 9
891 int csize = (size - 1) * 4;
892 if (csize < 4) csize = 3;
893 core->GetVideoDriver()->DrawEllipse( (ieWord) (Pos.x - vp.x), (ieWord) (Pos.y - vp.y),
894 (ieWord) (csize * 4), (ieWord) (csize * 3), *col );
898 // Check if P is over our ground circle
899 bool Selectable::IsOver(const Point &P) const
901 int csize = size;
902 if (csize < 2) csize = 2;
904 int dx = P.x - Pos.x;
905 int dy = P.y - Pos.y;
907 // check rectangle first
908 if (dx < -(csize-1)*16 || dx > (csize-1)*16) return false;
909 if (dy < -(csize-1)*12 || dy > (csize-1)*12) return false;
911 // then check ellipse
912 int r = 9*dx*dx + 16*dy*dy; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
914 return (r <= 48*48*(csize-1)*(csize-1));
917 bool Selectable::IsSelected() const
919 return Selected == 1;
922 void Selectable::SetOver(bool over)
924 Over = over;
927 //don't call this function after rendering the cover and before the
928 //blitting of the sprite or bad things will happen :)
929 void Selectable::Select(int Value)
931 if (Selected!=0x80 || Value!=1) {
932 Selected = (ieWord) Value;
934 //forcing regeneration of the cover
935 SetSpriteCover(NULL);
938 void Selectable::SetCircle(int circlesize, const Color &color, Sprite2D* normal_circle, Sprite2D* selected_circle)
940 size = circlesize;
941 selectedColor = color;
942 overColor.r = color.r >> 1;
943 overColor.g = color.g >> 1;
944 overColor.b = color.b >> 1;
945 overColor.a = color.a;
946 circleBitmap[0] = normal_circle;
947 circleBitmap[1] = selected_circle;
950 //used for creatures
951 int Selectable::WantDither()
953 //if dithering is disabled globally, don't do it
954 if (core->FogOfWar&4) {
955 return 0;
957 //if actor is dead, dither it if polygon wants
958 if (Selected&0x80) {
959 return 1;
961 //if actor is selected dither it
962 if (Selected) {
963 return 2;
965 return 1;
968 /***********************
969 * Highlightable Class *
970 ***********************/
972 Highlightable::Highlightable(ScriptableType type)
973 : Scriptable( type )
975 outline = NULL;
976 Highlight = false;
977 Cursor = IE_CURSOR_NORMAL;
978 KeyResRef[0] = 0;
981 Highlightable::~Highlightable(void)
983 if (outline) {
984 delete( outline );
988 bool Highlightable::IsOver(const Point &Pos) const
990 if (!outline) {
991 return false;
993 return outline->PointIn( Pos );
996 void Highlightable::DrawOutline() const
998 if (!outline) {
999 return;
1001 core->GetVideoDriver()->DrawPolyline( outline, outlineColor, true );
1004 void Highlightable::SetCursor(unsigned char CursorIndex)
1006 Cursor = CursorIndex;
1009 bool Highlightable::TryUnlock(Actor *actor, bool removekey) {
1010 const char *Key = GetKey();
1011 Actor *haskey = NULL;
1013 if (Key && actor->InParty) {
1014 Game *game = core->GetGame();
1015 //allow unlock when the key is on any partymember
1016 for (int idx = 0; idx < game->GetPartySize(false); idx++) {
1017 Actor *pc = game->FindPC(idx + 1);
1018 if (!pc) continue;
1020 if (pc->inventory.HasItem(Key,0) ) {
1021 haskey = pc;
1022 break;
1025 } else if (Key) {
1026 //actor is not in party, check only actor
1027 if (actor->inventory.HasItem(Key,0) ) {
1028 haskey = actor;
1032 if (!haskey) {
1033 return false;
1036 if (removekey) {
1037 CREItem *item = NULL;
1038 haskey->inventory.RemoveItem(Key,0,&item);
1039 //the item should always be existing!!!
1040 if (item) {
1041 delete item;
1045 return true;
1049 /*****************
1050 * Movable Class *
1051 *****************/
1053 Movable::Movable(ScriptableType type)
1054 : Selectable( type )
1056 Destination = Pos;
1057 Orientation = 0;
1058 NewOrientation = 0;
1059 StanceID = 0;
1060 path = NULL;
1061 step = NULL;
1062 timeStartStep = 0;
1063 lastFrame = NULL;
1064 Area[0] = 0;
1065 AttackMovements[0] = 100;
1066 AttackMovements[1] = 0;
1067 AttackMovements[2] = 0;
1070 Movable::~Movable(void)
1072 if (path) {
1073 ClearPath();
1077 int Movable::GetPathLength()
1079 PathNode *node = GetNextStep(0);
1080 int i = 0;
1081 while (node->Next) {
1082 i++;
1083 node = node->Next;
1085 return i;
1088 PathNode *Movable::GetNextStep(int x)
1090 if (!step) {
1091 DoStep((unsigned int) ~0);
1093 PathNode *node = step;
1094 while(node && x--) {
1095 node = node->Next;
1097 return node;
1100 Point Movable::GetMostLikelyPosition()
1102 if (!path) {
1103 return Pos;
1106 //actually, sometimes middle path would be better, if
1107 //we stand in Destination already
1108 int halfway = GetPathLength()/2;
1109 PathNode *node = GetNextStep(halfway);
1110 if (node) {
1111 return Point((ieWord) ((node->x*16)+8), (ieWord) ((node->y*12)+6) );
1113 return Destination;
1116 void Movable::SetStance(unsigned int arg)
1118 //don't modify stance from dead back to anything if the actor is dead
1119 if ((StanceID==IE_ANI_TWITCH || StanceID==IE_ANI_DIE) && (arg!=IE_ANI_TWITCH) ) {
1120 if (GetInternalFlag()&IF_REALLYDIED) {
1121 printMessage("Movable","Stance overridden by death\n", YELLOW);
1122 return;
1126 if (arg<MAX_ANIMS) {
1127 StanceID=(unsigned char) arg;
1129 if (StanceID == IE_ANI_ATTACK) {
1130 // Set stance to a random attack animation
1132 int random = rand()%100;
1133 if (random < AttackMovements[0]) {
1134 StanceID = IE_ANI_ATTACK_BACKSLASH;
1135 } else if (random < AttackMovements[0] + AttackMovements[1]) {
1136 StanceID = IE_ANI_ATTACK_SLASH;
1137 } else {
1138 StanceID = IE_ANI_ATTACK_JAB;
1142 } else {
1143 StanceID=IE_ANI_AWAKE; //
1144 printf("Tried to set invalid stance id (%u)\n", arg);
1148 void Movable::SetAttackMoveChances(ieWord *amc)
1150 AttackMovements[0]=amc[0];
1151 AttackMovements[1]=amc[1];
1152 AttackMovements[2]=amc[2];
1157 //this could be used for WingBuffet as well
1158 void Movable::MoveLine(int steps, int Pass, ieDword orient)
1160 //remove previous path
1161 ClearPath();
1162 if (!steps)
1163 return;
1164 Point p = Pos;
1165 p.x/=16;
1166 p.y/=14;
1167 path = area->GetLine( p, steps, orient, Pass );
1170 void AdjustPositionTowards(Point &Pos, ieDword time_diff, unsigned int walk_speed, short srcx, short srcy, short destx, short desty) {
1171 if (destx > srcx)
1172 Pos.x += ( unsigned short )
1173 ( ( ( ( ( destx * 16 ) + 8 ) - Pos.x ) * ( time_diff ) ) / walk_speed );
1174 else
1175 Pos.x -= ( unsigned short )
1176 ( ( ( Pos.x - ( ( destx * 16 ) + 8 ) ) * ( time_diff ) ) / walk_speed );
1177 if (desty > srcy)
1178 Pos.y += ( unsigned short )
1179 ( ( ( ( ( desty * 12 ) + 6 ) - Pos.y ) * ( time_diff ) ) / walk_speed );
1180 else
1181 Pos.y -= ( unsigned short )
1182 ( ( ( Pos.y - ( ( desty * 12 ) + 6 ) ) * ( time_diff ) ) / walk_speed );
1186 // returns whether we made all pending steps (so, false if we must be called again this tick)
1187 // we can't just do them all here because the caller might have to update searchmap etc
1188 bool Movable::DoStep(unsigned int walk_speed, ieDword time)
1190 if (!path) {
1191 return true;
1193 if (!time) time = core->GetGame()->Ticks;
1194 if (!walk_speed) {
1195 // zero speed: no movement
1196 timeStartStep = time;
1197 StanceID = IE_ANI_READY;
1198 return true;
1200 if (!step) {
1201 step = path;
1202 timeStartStep = time;
1203 } else if (step->Next && (( time - timeStartStep ) >= walk_speed)) {
1204 //printf("[New Step] : Orientation = %d\n", step->orient);
1205 step = step->Next;
1206 timeStartStep = timeStartStep + walk_speed;
1208 SetOrientation (step->orient, false);
1209 StanceID = IE_ANI_WALK;
1210 if ((Type == ST_ACTOR) && (InternalFlags & IF_RUNNING)) {
1211 StanceID = IE_ANI_RUN;
1213 Pos.x = ( step->x * 16 ) + 8;
1214 Pos.y = ( step->y * 12 ) + 6;
1215 if (!step->Next) {
1216 // we reached our destination, we are done
1217 ClearPath();
1218 NewOrientation = Orientation;
1219 //since clearpath no longer sets currentaction to NULL
1220 //we set it here
1221 //no we don't, action is responsible for releasing itself
1222 //ReleaseCurrentAction();
1223 return true;
1225 if (( time - timeStartStep ) >= walk_speed) {
1226 // we didn't finish all pending steps, yet
1227 return false;
1229 AdjustPositionTowards(Pos, time - timeStartStep, walk_speed, step->x, step->y, step->Next->x, step->Next->y);
1230 return true;
1233 void Movable::AddWayPoint(const Point &Des)
1235 if (!path) {
1236 WalkTo(Des);
1237 return;
1239 Destination = Des;
1240 //it is tempting to use 'step' here, as it could
1241 //be about half of the current path already
1242 PathNode *endNode = path;
1243 while(endNode->Next) {
1244 endNode = endNode->Next;
1246 Point p(endNode->x, endNode->y);
1247 area->ClearSearchMapFor(this);
1248 PathNode *path2 = area->FindPath( p, Des, size );
1249 endNode->Next = path2;
1250 //probably it is wise to connect it both ways?
1251 path2->Parent = endNode;
1254 void Movable::FixPosition()
1256 if (Type!=ST_ACTOR) {
1257 return;
1259 Actor *actor = (Actor *) this;
1260 if (actor->GetStat(IE_DONOTJUMP)&DNJ_BIRD ) {
1261 return;
1263 //before fixposition, you should remove own shadow
1264 area->ClearSearchMapFor(this);
1265 Pos.x/=16;
1266 Pos.y/=12;
1267 GetCurrentArea()->AdjustPosition(Pos);
1268 Pos.x=Pos.x*16+8;
1269 Pos.y=Pos.y*12+6;
1272 void Movable::WalkTo(const Point &Des, int distance)
1274 Point from;
1276 // maybe caller should be responsible for this
1277 if ((Des.x/16 == Pos.x/16) && (Des.y/12 == Pos.y/12)) {
1278 ClearPath();
1279 return;
1282 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1283 PathNode *prev_step = NULL;
1284 unsigned char old_stance = StanceID;
1285 if (step && step->Next) {
1286 // don't interrupt in the middle of a step; path from the next one
1287 prev_step = new PathNode(*step);
1288 from.x = ( step->Next->x * 16 ) + 8;
1289 from.y = ( step->Next->y * 12 ) + 6;
1292 ClearPath();
1293 if (!prev_step) {
1294 FixPosition();
1295 from = Pos;
1297 area->ClearSearchMapFor(this);
1298 if (distance) {
1299 path = area->FindPathNear( from, Des, size, distance );
1300 } else {
1301 path = area->FindPath( from, Des, size, distance );
1303 //ClearPath sets destination, so Destination must be set after it
1304 //also we should set Destination only if there is a walkable path
1305 if (path) {
1306 Destination = Des;
1308 if (prev_step) {
1309 // we want to smoothly continue, please
1310 // this all needs more thought! but it seems to work okay
1311 StanceID = old_stance;
1313 if (path->Next) {
1314 // this is a terrible hack to make up for the
1315 // pathfinder orienting the first node wrong
1316 // should be fixed in pathfinder and not here!
1317 Point next, follow;
1318 next.x = path->x; next.y = path->y;
1319 follow.x = path->Next->x;
1320 follow.y = path->Next->y;
1321 path->orient = GetOrient(follow, next);
1324 // then put the prev_step at the beginning of the path
1325 prev_step->Next = path;
1326 path->Parent = prev_step;
1327 path = prev_step;
1329 step = path;
1331 } else {
1332 // pathing failed
1333 if (prev_step) {
1334 delete( prev_step );
1335 FixPosition();
1340 void Movable::RunAwayFrom(const Point &Des, int PathLength, int flags)
1342 ClearPath();
1343 area->ClearSearchMapFor(this);
1344 path = area->RunAway( Pos, Des, size, PathLength, flags );
1347 void Movable::RandomWalk(bool can_stop, bool run)
1349 if (path) {
1350 return;
1352 //if not continous random walk, then stops for a while
1353 if (can_stop && (rand()&3) ) {
1354 SetWait((rand()&7)+7);
1355 return;
1357 if (run) {
1358 InternalFlags|=IF_RUNNING;
1360 //the commenting-out of the clear search map call was removed in 0.4.0
1361 //if you want to put it back for some reason, check
1362 //if the searchmap is not eaten up
1363 area->ClearSearchMapFor(this);
1364 Point p = Pos;
1366 //selecting points around a circle's edge around actor (didn't work better)
1367 //int x = core->Roll(1,100,-50);
1368 //p.x+=x;
1369 //p.y+=(int) sqrt(100-x*x);
1371 //selecting points in a square around actor
1372 p.x+=core->Roll(1,50,-25);
1373 p.y+=core->Roll(1,50,-25);
1374 //the 5th parameter is controlling the orientation of the actor
1375 //0 - back away, 1 - face direction
1376 path = area->RunAway( Pos, p, size, 50, 1 );
1379 void Movable::MoveTo(const Point &Des)
1381 area->ClearSearchMapFor(this);
1382 Pos = Des;
1383 Destination = Des;
1384 if (BlocksSearchMap()) {
1385 area->BlockSearchMap( Pos, size, IsPC()?PATH_MAP_PC:PATH_MAP_NPC);
1389 void Movable::ClearPath()
1391 //this is to make sure attackers come to us
1392 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1393 //is set before ClearPath
1394 Destination = Pos;
1395 if (StanceID==IE_ANI_WALK || StanceID==IE_ANI_RUN) {
1396 StanceID = IE_ANI_AWAKE;
1398 InternalFlags&=~IF_NORECTICLE;
1399 PathNode* thisNode = path;
1400 while (thisNode) {
1401 PathNode* nextNode = thisNode->Next;
1402 delete( thisNode );
1403 thisNode = nextNode;
1405 path = NULL;
1406 step = NULL;
1407 //don't call ReleaseCurrentAction
1410 void Movable::DrawTargetPoint(const Region &vp)
1412 if (!path || !Selected || (InternalFlags&IF_NORECTICLE) )
1413 return;
1415 // recticles are never drawn in cutscenes
1416 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE))
1417 return;
1419 // generates "step" from sequence 3 2 1 0 1 2 3 4
1420 // updated each 1/15 sec
1421 unsigned long step;
1422 GetTime( step );
1423 step = tp_steps [(step >> 6) & 7];
1425 step = step + 1;
1426 int csize = (size - 1) * 4;
1427 if (csize < 4) csize = 3;
1429 /* segments should not go outside selection radius */
1430 unsigned short xradius = (csize * 4) - 5;
1431 unsigned short yradius = (csize * 3) - 5;
1432 ieWord xcentre = (ieWord) (Destination.x - vp.x);
1433 ieWord ycentre = (ieWord) (Destination.y - vp.y);
1435 // TODO: 0.5 and 0.7 are pretty much random values
1436 // right segment
1437 core->GetVideoDriver()->DrawEllipseSegment( xcentre + step, ycentre, xradius,
1438 yradius, selectedColor, -0.5, 0.5 );
1439 // top segment
1440 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre - step, xradius,
1441 yradius, selectedColor, -0.7 - M_PI_2, 0.7 - M_PI_2 );
1442 // left segment
1443 core->GetVideoDriver()->DrawEllipseSegment( xcentre - step, ycentre, xradius,
1444 yradius, selectedColor, -0.5 - M_PI, 0.5 - M_PI );
1445 // bottom segment
1446 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre + step, xradius,
1447 yradius, selectedColor, -0.7 - M_PI - M_PI_2, 0.7 - M_PI - M_PI_2 );
1450 /**********************
1451 * Tiled Object Class *
1452 **********************/
1454 TileObject::TileObject()
1456 opentiles = NULL;
1457 opencount = 0;
1458 closedtiles = NULL;
1459 closedcount = 0;
1460 Flags = 0;
1463 TileObject::~TileObject()
1465 if (opentiles) {
1466 free( opentiles );
1468 if (closedtiles) {
1469 free( closedtiles );
1473 void TileObject::SetOpenTiles(unsigned short* Tiles, int cnt)
1475 if (opentiles) {
1476 free( opentiles );
1478 opentiles = Tiles;
1479 opencount = cnt;
1482 void TileObject::SetClosedTiles(unsigned short* Tiles, int cnt)
1484 if (closedtiles) {
1485 free( closedtiles );
1487 closedtiles = Tiles;
1488 closedcount = cnt;
1491 /**************
1492 * Door Class *
1493 **************/
1495 Door::Door(TileOverlay* Overlay)
1496 : Highlightable( ST_DOOR )
1498 tiles = NULL;
1499 tilecount = 0;
1500 Flags = 0;
1501 open = NULL;
1502 closed = NULL;
1503 open_ib = NULL;
1504 oibcount = 0;
1505 closed_ib = NULL;
1506 cibcount = 0;
1507 OpenSound[0] = 0;
1508 CloseSound[0] = 0;
1509 LockSound[0] = 0;
1510 UnLockSound[0] = 0;
1511 overlay = Overlay;
1512 LinkedInfo[0] = 0;
1513 OpenStrRef = (ieDword) -1;
1516 Door::~Door(void)
1518 if (Flags&DOOR_OPEN) {
1519 if (closed) {
1520 delete( closed );
1522 } else {
1523 if (open) {
1524 delete( open );
1527 if (tiles) {
1528 free( tiles );
1530 if (open_ib) {
1531 free( open_ib );
1533 if (closed_ib) {
1534 free( closed_ib );
1538 void Door::ImpedeBlocks(int count, Point *points, unsigned char value)
1540 for(int i = 0;i<count;i++) {
1541 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_NOTDOOR;
1542 area->SearchMap->SetAt( points[i].x, points[i].y, (tmp|value) );
1546 void Door::UpdateDoor()
1548 if (Flags&DOOR_OPEN) {
1549 outline = open;
1550 } else {
1551 outline = closed;
1553 unsigned char oval, cval;
1555 oval = PATH_MAP_IMPASSABLE;
1556 if (Flags & DOOR_TRANSPARENT) {
1557 cval = PATH_MAP_DOOR_TRANSPARENT;
1559 else {
1560 cval = PATH_MAP_DOOR_OPAQUE;
1562 if (Flags &DOOR_OPEN) {
1563 ImpedeBlocks(cibcount, closed_ib, 0);
1564 ImpedeBlocks(oibcount, open_ib, cval);
1566 else {
1567 ImpedeBlocks(oibcount, open_ib, 0);
1568 ImpedeBlocks(cibcount, closed_ib, cval);
1571 InfoPoint *ip = area->TMap->GetInfoPoint(LinkedInfo);
1572 if (ip) {
1573 if (Flags&DOOR_OPEN) ip->Flags&=~INFO_DOOR;
1574 else ip->Flags|=INFO_DOOR;
1578 void Door::ToggleTiles(int State, int playsound)
1580 int i;
1581 int state;
1583 if (State) {
1584 state = !closedIndex;
1585 if (playsound && ( OpenSound[0] != '\0' ))
1586 core->GetAudioDrv()->Play( OpenSound );
1587 } else {
1588 state = closedIndex;
1589 if (playsound && ( CloseSound[0] != '\0' ))
1590 core->GetAudioDrv()->Play( CloseSound );
1592 for (i = 0; i < tilecount; i++) {
1593 overlay->tiles[tiles[i]]->tileIndex = (ieByte) state;
1596 //set door_open as state
1597 Flags = (Flags & ~DOOR_OPEN) | (State == !core->HasFeature(GF_REVERSE_DOOR) );
1600 //this is the short name (not the scripting name)
1601 void Door::SetName(const char* name)
1603 strnlwrcpy( ID, name, 8 );
1606 void Door::SetTiles(unsigned short* Tiles, int cnt)
1608 if (tiles) {
1609 free( tiles );
1611 tiles = Tiles;
1612 tilecount = cnt;
1615 void Door::SetDoorLocked(int Locked, int playsound)
1617 if (Locked) {
1618 if (Flags & DOOR_LOCKED) return;
1619 Flags|=DOOR_LOCKED;
1620 if (playsound && ( LockSound[0] != '\0' ))
1621 core->GetAudioDrv()->Play( LockSound );
1623 else {
1624 if (!(Flags & DOOR_LOCKED)) return;
1625 Flags&=~DOOR_LOCKED;
1626 if (playsound && ( UnLockSound[0] != '\0' ))
1627 core->GetAudioDrv()->Play( UnLockSound );
1631 int Door::IsOpen() const
1633 int ret = core->HasFeature(GF_REVERSE_DOOR);
1634 if (Flags&DOOR_OPEN) {
1635 ret = !ret;
1637 return ret;
1640 //also mark actors to fix position
1641 bool Door::BlockedOpen(int Open, int ForceOpen)
1643 bool blocked;
1644 int count;
1645 Point *points;
1647 blocked = false;
1648 if (Open) {
1649 count = oibcount;
1650 points = open_ib;
1651 } else {
1652 count = cibcount;
1653 points = closed_ib;
1655 //getting all impeded actors flagged for jump
1656 Region rgn;
1657 rgn.w = 16;
1658 rgn.h = 12;
1659 for(int i = 0;i<count;i++) {
1660 Actor** ab;
1661 rgn.x = points[i].x*16;
1662 rgn.y = points[i].y*12;
1663 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_ACTOR;
1664 if (tmp) {
1665 int ac = area->GetActorInRect(ab, rgn, false);
1666 while(ac--) {
1667 if (ab[ac]->GetBase(IE_DONOTJUMP)) {
1668 continue;
1670 ab[ac]->SetBase(IE_DONOTJUMP, DNJ_JUMP);
1671 blocked = true;
1673 if (ab) {
1674 free(ab);
1679 if ((Flags&DOOR_SLIDE) || ForceOpen) {
1680 return false;
1682 return blocked;
1685 void Door::SetDoorOpen(int Open, int playsound, ieDword ID)
1687 if (playsound) {
1688 //the door cannot be blocked when opening,
1689 //but the actors will be pushed
1690 //BlockedOpen will mark actors to be pushed
1691 if (BlockedOpen(Open,0) && !Open) {
1692 //clear up the blocking actors
1693 area->JumpActors(false);
1694 return;
1696 area->JumpActors(true);
1698 if (Open) {
1699 LastEntered = ID; //used as lastOpener
1701 // in PS:T, opening a door does not unlock it
1702 if (!core->HasFeature(GF_REVERSE_DOOR)) {
1703 SetDoorLocked(false,playsound);
1705 } else {
1706 LastTriggerObject = LastTrigger = ID; //used as lastCloser
1708 ToggleTiles(Open, playsound);
1709 //synchronising other data with the door state
1710 UpdateDoor();
1711 area->ActivateWallgroups(open_wg_index, open_wg_count, Flags&DOOR_OPEN);
1712 area->ActivateWallgroups(closed_wg_index, closed_wg_count, !(Flags&DOOR_OPEN));
1715 bool Door::TryUnlock(Actor *actor) {
1716 if (!(Flags&DOOR_LOCKED)) return true;
1718 // don't remove key in PS:T!
1719 bool removekey = !core->HasFeature(GF_REVERSE_DOOR) && Flags&DOOR_KEY;
1720 return Highlightable::TryUnlock(actor, removekey);
1723 void Door::SetPolygon(bool Open, Gem_Polygon* poly)
1725 if (Open) {
1726 if (open)
1727 delete( open );
1728 open = poly;
1729 } else {
1730 if (closed)
1731 delete( closed );
1732 closed = poly;
1736 void Door::SetNewOverlay(TileOverlay *Overlay) {
1737 overlay = Overlay;
1738 ToggleTiles(IsOpen(), false);
1741 void Highlightable::SetTrapDetected(int x)
1743 if(x == TrapDetected)
1744 return;
1745 TrapDetected = x;
1746 if(TrapDetected) {
1747 core->Autopause(AP_TRAP);
1751 void Highlightable::TryDisarm(Actor *actor)
1753 if (!Trapped || !TrapDetected) return;
1755 LastTriggerObject = LastTrigger = actor->GetID();
1756 int skill = actor->GetStat(IE_TRAPS);
1758 if (skill/2+core->Roll(1,skill/2,0)>TrapRemovalDiff) {
1759 LastDisarmed = actor->GetID();
1760 //trap removed
1761 Trapped = 0;
1762 core->DisplayConstantStringName(STR_DISARM_DONE, 0xd7d7be, actor);
1763 actor->AddExperience(XP_DISARM, actor->GetXPLevel(1));
1764 } else {
1765 core->DisplayConstantStringName(STR_DISARM_FAIL, 0xd7d7be, actor);
1766 TriggerTrap(skill, LastTrigger);
1768 ImmediateEvent();
1771 void Door::TryPickLock(Actor *actor)
1773 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
1774 if (LockDifficulty == 100) {
1775 core->DisplayConstantStringName(STR_DOOR_NOPICK, 0xbcefbc, actor);
1776 } else {
1777 core->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
1778 LastPickLockFailed = actor->GetID();
1780 return;
1782 SetDoorLocked( false, true);
1783 core->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
1784 LastUnlocked = actor->GetID();
1785 ImmediateEvent();
1786 actor->AddExperience(XP_LOCKPICK, actor->GetXPLevel(1));
1789 void Door::TryBashLock(Actor *actor)
1791 //Get the strength bonus agains lock difficulty
1792 int str = actor->GetStat(IE_STR);
1793 int strEx = actor->GetStat(IE_STREXTRA);
1794 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
1796 //bonus will never reach 100
1797 if(bonus < LockDifficulty) {
1798 core->DisplayConstantStringName(STR_DOORBASH_FAIL, 0xbcefbc, actor);
1799 return;
1802 core->DisplayConstantStringName(STR_DOORBASH_DONE, 0xd7d7be, actor);
1803 SetDoorLocked(false, true);
1804 //Is this really useful ?
1805 LastUnlocked = actor->GetID();
1806 ImmediateEvent();
1809 void Door::DebugDump() const
1811 printf( "Debugdump of Door %s:\n", GetScriptName() );
1812 printf( "Door Open: %s\n", YESNO(IsOpen()));
1813 printf( "Door Locked: %s\n", YESNO(Flags&DOOR_LOCKED));
1814 printf( "Door Trapped: %s\n", YESNO(Trapped));
1815 if (Trapped) {
1816 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags&DOOR_RESET), YESNO(Flags&DOOR_DETECTABLE) );
1818 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags&DOOR_SECRET),YESNO(Flags&DOOR_FOUND));
1819 const char *Key = GetKey();
1820 const char *name = "NONE";
1821 if (Scripts[0]) {
1822 name = Scripts[0]->GetName();
1824 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name, Key?Key:"NONE", YESNO(Flags&DOOR_KEY), Dialog );
1827 /*******************
1828 * InfoPoint Class *
1829 *******************/
1831 InfoPoint::InfoPoint(void)
1832 : Highlightable( ST_TRIGGER )
1834 Destination[0] = 0;
1835 EntranceName[0] = 0;
1836 Flags = 0;
1837 TrapDetectionDiff = 0;
1838 TrapRemovalDiff = 0;
1839 TrapDetected = 0;
1840 TrapLaunch.empty();
1841 Dialog[0] = 0;
1844 InfoPoint::~InfoPoint(void)
1848 //checks if the actor may use this travel trigger
1849 //bit 1 : can use
1850 //bit 2 : whole team
1851 int InfoPoint::CheckTravel(Actor *actor)
1853 if (Flags&TRAP_DEACTIVATED) return CT_CANTMOVE;
1854 if (!actor->InParty && (Flags&TRAVEL_NONPC) ) return CT_CANTMOVE;
1855 if (actor->InParty && (Flags&TRAVEL_PARTY) ) {
1856 if (core->HasFeature(GF_TEAM_MOVEMENT) || core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE) ) {
1857 return CT_WHOLE;
1859 return CT_GO_CLOSER;
1861 if(actor->IsSelected() ) {
1862 if(core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE|ENP_ONLYSELECT) ) {
1863 return CT_MOVE_SELECTED;
1865 return CT_SELECTED;
1867 return CT_ACTIVE;
1870 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1871 //skill is the all around modified trap detection skill
1872 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1873 void Highlightable::DetectTrap(int skill)
1875 if (!CanDetectTrap()) return;
1876 if (!Scripts[0]) return;
1877 if ((skill>=100) && (skill!=256) ) skill = 100;
1878 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1879 SetTrapDetected(1); //probably could be set to the player #?
1883 bool Highlightable::PossibleToSeeTrap() const
1885 return CanDetectTrap();
1888 bool InfoPoint::PossibleToSeeTrap() const
1890 // Only detectable trap-type infopoints.
1891 return (CanDetectTrap() && (Type == ST_PROXIMITY) );
1894 bool InfoPoint::CanDetectTrap() const
1896 // Traps can be detected on all types of infopoint, as long
1897 // as the trap is detectable and isn't deactivated.
1898 return ((Flags&TRAP_DETECTABLE) && !(Flags&TRAP_DEACTIVATED));
1901 // returns true if the infopoint is a PS:T portal
1902 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1903 bool InfoPoint::IsPortal() const
1905 if (Type!=ST_TRAVEL) return false;
1906 if (Cursor != IE_CURSOR_PORTAL) return false;
1907 return core->HasFeature(GF_REVERSE_DOOR);
1910 //trap that is visible on screen (marked by red)
1911 //if TrapDetected is a bitflag, we could show traps selectively for
1912 //players, really nice for multiplayer
1913 bool Highlightable::VisibleTrap(int see_all) const
1915 if (!Trapped) return false;
1916 if (!PossibleToSeeTrap()) return false;
1917 if (!Scripts[0]) return false;
1918 if (see_all) return true;
1919 if (TrapDetected ) return true;
1920 return false;
1923 //trap that will fire now
1924 bool Highlightable::TriggerTrap(int skill, ieDword ID)
1926 if (!Trapped) {
1927 return false;
1929 //actually this could be script name[0]
1930 if (!Scripts[0]) {
1931 return false;
1933 if (CanDetectTrap()) {
1934 // this should probably be party members only
1935 if ((skill>=100) && (skill!=256) ) skill = 100;
1936 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1937 SetTrapDetected(1); //probably too late :)
1938 //tumble???
1939 return false;
1942 LastTriggerObject = LastTrigger = LastEntered = ID;
1943 ImmediateEvent();
1944 if (!TrapResets()) {
1945 Trapped = false;
1947 return true;
1950 //trap that will fire now
1951 bool InfoPoint::TriggerTrap(int skill, ieDword ID)
1953 if (Type!=ST_PROXIMITY) {
1954 return true;
1956 if (Flags&TRAP_DEACTIVATED) {
1957 return false;
1959 if (!Trapped) {
1960 // we have to set Entered somewhere, here seems best..
1961 LastEntered = ID;
1962 return true;
1963 } else if (Highlightable::TriggerTrap(skill, ID)) {
1964 if (!Trapped) {
1965 Flags|=TRAP_DEACTIVATED;
1967 // ok, so this is a pain. Entered() trigger checks Trapped,
1968 // so it needs to be kept set. how to do this right?
1969 Trapped = true;
1970 return true;
1972 return false;
1975 bool InfoPoint::Entered(Actor *actor)
1977 if (outline->PointIn( actor->Pos ) ) {
1978 //don't trigger again for this actor
1979 if (!(actor->GetInternalFlag()&IF_INTRAP)) {
1980 goto check;
1983 // why is this here? actors which aren't *in* a trap get IF_INTRAP
1984 // repeatedly unset, so this triggers again and again and again.
1985 // i disabled it for ST_PROXIMITY for now..
1986 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
1987 goto check;
1989 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
1990 if (Type == ST_TRAVEL && PersonalDistance(TrapLaunch, actor)<MAX_OPERATING_DISTANCE) {
1991 goto check;
1993 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
1994 // for distance from the outline for travel regions instead?
1995 if (Type == ST_TRAVEL && PersonalDistance(TalkPos, actor)<MAX_OPERATING_DISTANCE) {
1996 goto check;
1998 if (Flags&TRAP_USEPOINT) {
1999 if (PersonalDistance(UsePoint, actor)<MAX_OPERATING_DISTANCE) {
2000 goto check;
2003 return false;
2004 check:
2005 if (Type==ST_TRAVEL) {
2006 return true;
2009 if (actor->InParty || (Flags&TRAP_NPC) ) {
2010 //no need to avoid a travel trigger
2012 //skill?
2013 if (TriggerTrap(0, actor->GetID()) ) {
2014 return true;
2017 return false;
2020 void InfoPoint::DebugDump() const
2022 switch (Type) {
2023 case ST_TRIGGER:
2024 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2025 break;
2026 case ST_PROXIMITY:
2027 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2028 break;
2029 case ST_TRAVEL:
2030 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2031 break;
2032 default:
2033 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2034 break;
2036 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected, YESNO(Trapped));
2037 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2038 TrapRemovalDiff );
2039 const char *name = "NONE";
2040 if (Scripts[0]) {
2041 name = Scripts[0]->GetName();
2043 printf( "Script: %s, Key: %s, Dialog: %s\n", name, KeyResRef, Dialog );
2044 printf( "Active: %s\n", YESNO(InternalFlags&IF_ACTIVE));
2047 /*******************
2048 * Container Class *
2049 *******************/
2051 Container::Container(void)
2052 : Highlightable( ST_CONTAINER )
2054 Type = 0;
2055 LockDifficulty = 0;
2056 Flags = 0;
2057 TrapDetectionDiff = 0;
2058 TrapRemovalDiff = 0;
2059 Trapped = 0;
2060 TrapDetected = 0;
2061 inventory.SetInventoryType(INVENTORY_HEAP);
2062 // NULL should be 0 for this
2063 memset (groundicons, 0, sizeof(groundicons) );
2064 groundiconcover = 0;
2067 void Container::FreeGroundIcons()
2069 Video* video = core->GetVideoDriver();
2071 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2072 if (groundicons[i]) {
2073 video->FreeSprite( groundicons[i] );
2074 groundicons[i]=NULL;
2077 delete groundiconcover;
2078 groundiconcover = 0;
2081 Container::~Container()
2083 FreeGroundIcons();
2086 void Container::DrawPile(bool highlight, Region screen, Color tint)
2088 Video* video = core->GetVideoDriver();
2089 CreateGroundIconCover();
2090 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2091 if (groundicons[i]) {
2092 //draw it with highlight
2093 video->BlitGameSprite(groundicons[i],
2094 screen.x + Pos.x, screen.y + Pos.y,
2095 BLIT_TINTED | (highlight ? 0:BLIT_NOSHADOW),
2096 tint, groundiconcover);
2101 // create the SpriteCover for the groundicons
2102 void Container::CreateGroundIconCover()
2104 int xpos = 0;
2105 int ypos = 0;
2106 int width = 0;
2107 int height = 0;
2109 int i; //msvc6.0
2110 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2111 if (groundicons[i]) {
2112 Sprite2D& spr = *groundicons[i];
2113 if (xpos < spr.XPos) {
2114 width += spr.XPos - xpos;
2115 xpos = spr.XPos;
2117 if (ypos < spr.YPos) {
2118 height += spr.YPos - ypos;
2119 ypos = spr.YPos;
2121 if (width-xpos < spr.Width-spr.XPos) {
2122 width = spr.Width-spr.XPos+xpos;
2124 if (height-ypos < spr.Height-spr.YPos) {
2125 height = spr.Height-spr.YPos+ypos;
2130 if (!groundiconcover ||
2131 !groundiconcover->Covers(Pos.x, Pos.y, xpos, ypos, width, height))
2133 delete groundiconcover;
2134 groundiconcover = 0;
2135 if (width*height > 0) {
2136 groundiconcover = GetCurrentArea()->BuildSpriteCover
2137 (Pos.x, Pos.y, xpos, ypos, width, height, WantDither());
2141 #ifndef NDEBUG
2142 // TODO: remove this checking code eventually
2143 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2144 if (groundicons[i]) {
2145 Sprite2D& spr = *groundicons[i];
2146 assert(groundiconcover->Covers(Pos.x, Pos.y, spr.XPos, spr.YPos, spr.Width, spr.Height));
2149 #endif
2152 void Container::SetContainerLocked(bool lock)
2154 if (lock) {
2155 Flags|=CONT_LOCKED;
2156 } else {
2157 Flags&=~CONT_LOCKED;
2161 //This function doesn't exist in the original IE, destroys a container
2162 //turning it to a ground pile
2163 void Container::DestroyContainer()
2165 //it is already a groundpile?
2166 if (Type == IE_CONTAINER_PILE)
2167 return;
2168 Type = IE_CONTAINER_PILE;
2169 RefreshGroundIcons();
2170 //probably we should stop the script or trigger it, whatever
2173 //Takes an item from the container's inventory and returns its pointer
2174 CREItem *Container::RemoveItem(unsigned int idx, unsigned int count)
2176 CREItem *ret = inventory.RemoveItem(idx, count);
2177 //we just took the 3. or less item, groundpile changed
2178 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<3)) {
2179 RefreshGroundIcons();
2181 return ret;
2184 //Adds an item to the container's inventory
2185 //containers always have enough capacity (so far), thus we always return 2
2186 int Container::AddItem(CREItem *item)
2188 inventory.AddItem(item);
2189 //we just added a 3. or less item, groundpile changed
2190 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<4)) {
2191 RefreshGroundIcons();
2193 return 2;
2196 void Container::RefreshGroundIcons()
2198 int i = inventory.GetSlotCount();
2199 if (i>MAX_GROUND_ICON_DRAWN)
2200 i = MAX_GROUND_ICON_DRAWN;
2201 FreeGroundIcons();
2202 while (i--) {
2203 CREItem *slot = inventory.GetSlotItem(i); //borrowed reference
2204 Item *itm = gamedata->GetItem( slot->ItemResRef ); //cached reference
2205 //well, this is required in PST, needs more work if some other
2206 //game is broken by not using -1,0
2207 groundicons[i] = gamedata->GetBAMSprite( itm->GroundIcon, 0, 0 );
2208 gamedata->FreeItem( itm, slot->ItemResRef ); //decref
2212 //used for ground piles
2213 int Container::WantDither()
2215 //if pile is highlighted, always dither it
2216 if (Highlight) {
2217 return 2; //dither me if you want
2219 //if pile isn't highlighted, dither it if the polygon wants
2220 return 1;
2223 int Container::IsOpen() const
2225 if (Flags&CONT_LOCKED) {
2226 return false;
2228 return true;
2231 void Container::TryPickLock(Actor *actor)
2233 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
2234 if (LockDifficulty == 100) {
2235 core->DisplayConstantStringName(STR_CONT_NOPICK, 0xbcefbc, actor);
2236 } else {
2237 core->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
2238 LastPickLockFailed = actor->GetID();
2240 return;
2242 SetContainerLocked(false);
2243 core->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
2244 LastUnlocked = actor->GetID();
2245 ImmediateEvent();
2246 actor->AddExperience(XP_LOCKPICK, actor->GetXPLevel(1));
2249 void Container::TryBashLock(Actor *actor)
2251 //Get the strength bonus agains lock difficulty
2252 int str = actor->GetStat(IE_STR);
2253 int strEx = actor->GetStat(IE_STREXTRA);
2254 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
2256 //bonus will never reach 100
2257 if(bonus < LockDifficulty) {
2258 core->DisplayConstantStringName(STR_CONTBASH_FAIL, 0xbcefbc, actor);
2259 return;
2262 core->DisplayConstantStringName(STR_CONTBASH_DONE, 0xd7d7be, actor);
2263 SetContainerLocked(false);
2264 //Is this really useful ?
2265 LastUnlocked = actor->GetID();
2266 ImmediateEvent();
2269 void Container::DebugDump() const
2271 printf( "Debugdump of Container %s\n", GetScriptName() );
2272 printf( "Type: %d, LockDifficulty: %d\n", Type, LockDifficulty );
2273 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags, YESNO(Trapped), TrapDetected );
2274 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2275 TrapRemovalDiff );
2276 const char *name = "NONE";
2277 if (Scripts[0]) {
2278 name = Scripts[0]->GetName();
2280 printf( "Script: %s, Key: %s\n", name, KeyResRef );
2281 // FIXME: const_cast
2282 const_cast<Inventory&>(inventory).dump();
2285 bool Container::TryUnlock(Actor *actor) {
2286 if (!(Flags&CONT_LOCKED)) return true;
2288 return Highlightable::TryUnlock(actor, false);