Move EventMgr to GUI.
[gemrb.git] / gemrb / core / Scriptable / ActorBlock.cpp
blob344831e69e9c295508018901f9007c27c08d4071
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 "GSUtils.h"
28 #include "Game.h"
29 #include "GameData.h"
30 #include "Interface.h"
31 #include "Item.h"
32 #include "Map.h"
33 #include "Projectile.h"
34 #include "Spell.h"
35 #include "SpriteCover.h"
36 #include "TileMap.h"
37 #include "Video.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 playDeadCounter = 0;
82 if (Type == ST_ACTOR) {
83 InternalFlags = IF_VISIBLE | IF_ONCREATION | IF_USEDSAVE;
84 } else {
85 InternalFlags = IF_ACTIVE | IF_VISIBLE | IF_ONCREATION | IF_NOINT;
87 area = 0;
88 Pos.x = 0;
89 Pos.y = 0;
91 LastCasterOnMe = 0;
92 LastSpellOnMe = 0xffffffff;
93 LastCasterSeen = 0;
94 LastSpellSeen = 0xffffffff;
95 SpellHeader = -1;
96 LastTargetPos.empty();
97 locals = new Variables();
98 locals->SetType( GEM_VARIABLES_INT );
99 locals->ParseKey( 1 );
100 InitTriggers();
102 memset( script_timers,0, sizeof(script_timers));
105 Scriptable::~Scriptable(void)
107 if (CurrentAction) {
108 ReleaseCurrentAction();
110 ClearActions();
111 for (int i = 0; i < MAX_SCRIPTS; i++) {
112 if (Scripts[i]) {
113 delete( Scripts[i] );
116 if (overHeadText) {
117 core->FreeString( overHeadText );
119 if (locals) {
120 delete( locals );
124 void Scriptable::SetScriptName(const char* text)
126 //if (text && text[0]) { //this leaves some uninitialized bytes
127 //lets hope this won't break anything
128 if (text) {
129 strnspccpy( scriptName, text, 32 );
133 /** Gets the DeathVariable */
134 const char* Scriptable::GetScriptName(void) const
136 return scriptName;
139 Map* Scriptable::GetCurrentArea() const
141 //this could be NULL, always check it
142 return area;
145 void Scriptable::SetMap(Map *map)
147 if (!map) {
148 printMessage("Scriptable","Null 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 if ((Type != ST_AREA) && (core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE)) {
279 return;
282 if ((InternalFlags & IF_NOINT) && (CurrentAction || GetNextAction())) {
283 return;
286 if (!CurrentActionInterruptable) {
287 if (!CurrentAction && !GetNextAction()) abort();
288 return;
291 // only allow death scripts to run once, hopefully?
292 // this is probably terrible logic which needs moving elsewhere
293 if ((lastRunTime != 0) && (InternalFlags & IF_JUSTDIED)) {
294 return;
297 ieDword thisTime = core->GetGame()->Ticks;
298 if (( thisTime - lastRunTime ) < 1000) {
299 return;
302 lastDelay = lastRunTime;
303 lastRunTime = thisTime;
305 bool alive = false;
307 bool continuing = false, done = false;
308 for (int i = 0;i<scriptCount;i++) {
309 //disable AI script level for actors in party when the player disabled them
310 if ((i == AI_SCRIPT_LEVEL) && Type == ST_ACTOR && ((Actor *) this)->InParty) {
311 if (core->GetGame()->ControlStatus&CS_PARTY_AI) {
312 continue;
316 GameScript *Script = Scripts[i];
317 if (Script) {
318 alive |= Script->Update(&continuing, &done);
321 /* scripts are not concurrent, see WAITPC override script for example */
322 if (done) break;
324 if (alive && UnselectableTimer) {
325 UnselectableTimer--;
326 if (!UnselectableTimer) {
327 if (Type == ST_ACTOR) {
328 ((Actor *) this)->SetCircleSize();
334 void Scriptable::AddAction(Action* aC)
336 if (!aC) {
337 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
338 return;
340 InternalFlags|=IF_ACTIVE;
341 actionQueue.push_back( aC );
342 aC->IncRef();
345 void Scriptable::AddActionInFront(Action* aC)
347 if (!aC) {
348 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
349 return;
351 InternalFlags|=IF_ACTIVE;
352 actionQueue.push_front( aC );
353 aC->IncRef();
356 Action* Scriptable::GetNextAction() const
358 if (actionQueue.size() == 0) {
359 return NULL;
361 return actionQueue.front();
364 Action* Scriptable::PopNextAction()
366 if (actionQueue.size() == 0) {
367 return NULL;
369 Action* aC = actionQueue.front();
370 actionQueue.pop_front();
371 return aC;
374 void Scriptable::ClearActions()
376 ReleaseCurrentAction();
377 for (unsigned int i = 0; i < actionQueue.size(); i++) {
378 Action* aC = actionQueue.front();
379 actionQueue.pop_front();
380 aC->Release();
382 actionQueue.clear();
383 WaitCounter = 0;
384 playDeadCounter = 0; // i'm not sure about this
385 LastTarget = 0;
386 //clear the triggers as fast as possible when queue ended?
387 ClearTriggers();
389 if (Type == ST_ACTOR) {
390 Interrupt();
391 } else {
392 NoInterrupt();
396 void Scriptable::ReleaseCurrentAction()
398 if (CurrentAction) {
399 CurrentAction->Release();
400 CurrentAction = NULL;
403 CurrentActionState = 0;
404 CurrentActionTarget = 0;
405 CurrentActionInterruptable = true;
408 ieWord Scriptable::GetGlobalID()
410 if (Type == ST_ACTOR) {
411 Actor *actor = (Actor *) this;
412 return actor->globalID;
414 return 0;
417 void Scriptable::ProcessActions(bool force)
419 unsigned long thisTime = core->GetGame()->Ticks;
421 if (!force && (( thisTime - startTime ) < interval)) {
422 return;
424 startTime = thisTime;
425 if (playDeadCounter) {
426 playDeadCounter--;
427 if (!playDeadCounter) {
428 Movable* mov = ( Movable* ) this;
429 mov->SetStance( IE_ANI_GET_UP );
432 if (WaitCounter) {
433 WaitCounter--;
434 if (WaitCounter) return;
437 while (true) {
438 CurrentActionInterruptable = true;
439 if (!CurrentAction) {
440 CurrentAction = PopNextAction();
442 if (!CurrentAction) {
443 ClearActions();
444 if (CutSceneId) {
445 CutSceneId = NULL;
447 //removing the triggers at the end of the
448 //block
449 //ClearTriggers();
450 break;
452 GameScript::ExecuteAction( this, CurrentAction );
453 //break execution in case of a Wait flag
454 if (WaitCounter) {
455 //clear triggers while waiting
456 //ClearTriggers();
457 break;
459 //break execution in case of blocking action
460 if (CurrentAction) {
461 break;
463 //break execution in case of movement
464 //we should not actually break here, or else fix waypoints
465 if (InMove()) {
466 break;
469 //most likely the best place to clear triggers is here
470 //queue is empty, or there is a looong action subject to break
471 ClearTriggers();
472 if (InternalFlags&IF_IDLE) {
473 Deactivate();
477 bool Scriptable::InMove() const
479 if (Type!=ST_ACTOR) {
480 return false;
482 Movable *me = (Movable *) this;
483 return me->GetNextStep()!=NULL;
486 void Scriptable::SetWait(unsigned long time)
488 WaitCounter = time;
491 unsigned long Scriptable::GetWait() const
493 return WaitCounter;
496 Scriptable *Scriptable::GetCutsceneID() const
498 return CutSceneId;
501 void Scriptable::LeaveDialog()
503 InternalFlags |=IF_WASINDIALOG;
506 //this ends cutscene mode for this Sender
507 void Scriptable::ClearCutsceneID()
509 CutSceneId = NULL;
510 InternalFlags &= ~IF_CUTSCENEID;
513 //if the cutsceneID doesn't exist, we simply skip the action
514 //because the cutscene script executer DOESN'T get hijacked
515 void Scriptable::SetCutsceneID(Scriptable *csid)
517 CutSceneId = csid;
518 InternalFlags |= IF_CUTSCENEID;
521 void Scriptable::Hide()
523 InternalFlags &=~(IF_VISIBLE);
526 void Scriptable::Unhide()
528 InternalFlags |= IF_VISIBLE;
531 void Scriptable::Interrupt()
533 InternalFlags &= ~IF_NOINT;
536 void Scriptable::NoInterrupt()
538 InternalFlags |= IF_NOINT;
541 //also turning off the idle flag so it won't run continuously
542 void Scriptable::Deactivate()
544 InternalFlags &=~(IF_ACTIVE|IF_IDLE);
547 //turning off the not interruptable flag, actions should reenable it themselves
548 //also turning off the idle flag
549 //heh, no, i wonder why did i touch the interruptable flag here
550 void Scriptable::Activate()
552 InternalFlags |= IF_ACTIVE;
553 InternalFlags &= ~IF_IDLE;
556 void Scriptable::PartyRested()
558 InternalFlags |=IF_PARTYRESTED;
561 ieDword Scriptable::GetInternalFlag()
563 return InternalFlags;
566 void Scriptable::InitTriggers()
568 tolist.clear();
569 bittriggers = 0;
572 void Scriptable::ClearTriggers()
574 for (TriggerObjects::iterator m = tolist.begin(); m != tolist.end (); m++) {
575 *(*m) = 0;
577 if (!bittriggers) {
578 return;
580 if (bittriggers & BT_DIE) {
581 InternalFlags &= ~IF_JUSTDIED;
583 if (bittriggers & BT_ONCREATION) {
584 InternalFlags &= ~IF_ONCREATION;
586 if (bittriggers & BT_BECAMEVISIBLE) {
587 InternalFlags &= ~IF_BECAMEVISIBLE;
589 if (bittriggers & BT_PARTYRESTED) {
590 InternalFlags &= ~IF_PARTYRESTED;
592 if (bittriggers & BT_WASINDIALOG) {
593 InternalFlags &= ~IF_WASINDIALOG;
595 if (bittriggers & BT_PARTYRESTED) {
596 InternalFlags &= ~IF_PARTYRESTED;
598 InitTriggers();
601 void Scriptable::SetBitTrigger(ieDword bittrigger)
603 bittriggers |= bittrigger;
606 void Scriptable::AddTrigger(ieDword *actorref)
608 tolist.push_back(actorref);
611 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef )
613 if (Type == ST_ACTOR) {
614 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
617 if (SpellHeader == -1) {
618 LastTargetPos.empty();
619 return;
622 if (LastTargetPos.isempty()) {
623 SpellHeader = -1;
624 return;
627 Spell* spl = gamedata->GetSpell( SpellResRef );
628 //create projectile from known spellheader
629 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
630 SpellHeader = -1;
631 if (pro) {
632 pro->SetCaster(GetGlobalID());
633 Point origin = Pos;
634 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
635 // try and make projectiles start from the right trap position
636 // see the traps in the duergar/assassin battle in bg2 dungeon
637 // see also function below - maybe we should fix Pos instead
638 origin = ((InfoPoint *)this)->TrapLaunch;
640 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
642 LastTarget = 0;
643 LastTargetPos.empty();
646 void Scriptable::CastSpellEnd( const ieResRef SpellResRef )
648 if (Type == ST_ACTOR) {
649 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
652 if (SpellHeader == -1) {
653 LastTarget = 0;
654 return;
656 if (!LastTarget) {
657 SpellHeader = -1;
658 return;
660 Spell* spl = gamedata->GetSpell( SpellResRef );
661 //create projectile from known spellheader
662 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
663 if (pro) {
664 pro->SetCaster(GetGlobalID());
665 Point origin = Pos;
666 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
667 // try and make projectiles start from the right trap position
668 // see the traps in the duergar/assassin battle in bg2 dungeon
669 // see also function above - maybe we should fix Pos instead
670 origin = ((InfoPoint *)this)->TrapLaunch;
672 if (LastTarget) {
673 GetCurrentArea()->AddProjectile(pro, origin, LastTarget);
674 } else {
675 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
678 ieDword spellnum=ResolveSpellNumber( SpellResRef );
679 if (spellnum!=0xffffffff) {
680 area->SeeSpellCast(this, spellnum);
681 if(LastTarget) {
682 Scriptable *target = area->GetActorByGlobalID(LastTarget);
683 if (target && (Type==ST_ACTOR) ) {
684 Actor *me = (Actor *) this;
685 target->LastSpellOnMe = spellnum;
686 target->LastCasterOnMe = me->GetID();
687 me->CureInvisibility();
688 if (target!=this) {
689 me->CureSanctuary();
695 gamedata->FreeSpell(spl, SpellResRef, false);
696 LastTarget = 0;
697 LastTargetPos.empty();
700 //set target as point
701 //if spell needs to be depleted, do it
702 //if spell is illegal stop casting
703 int Scriptable::CastSpellPoint( const ieResRef SpellResRef, const Point &target, bool deplete, bool instant )
705 LastTarget = 0;
706 LastTargetPos.empty();
707 if (Type == ST_ACTOR) {
708 Actor *actor = (Actor *) this;
709 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
710 return -1;
713 LastTargetPos = target;
714 return SpellCast(SpellResRef, instant);
717 //set target as actor (if target isn't actor, use its position)
718 //if spell needs to be depleted, do it
719 //if spell is illegal stop casting
720 int Scriptable::CastSpell( const ieResRef SpellResRef, Scriptable* target, bool deplete, bool instant )
722 LastTarget = 0;
723 LastTargetPos.empty();
724 if (Type == ST_ACTOR) {
725 Actor *actor = (Actor *) this;
726 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
727 return -1;
731 if (!target) target = this;
733 LastTargetPos = target->Pos;
734 if (target->Type==ST_ACTOR) {
735 LastTarget = target->GetGlobalID();
737 return SpellCast(SpellResRef, instant);
740 //start spellcasting (common part)
741 int Scriptable::SpellCast(const ieResRef SpellResRef, bool instant)
743 Spell* spl = gamedata->GetSpell( SpellResRef );
744 if (!spl) {
745 SpellHeader = -1;
746 return -1;
749 if (Type == ST_ACTOR) {
750 Actor *actor = (Actor *) this;
751 //The ext. index is here to calculate the casting time
752 int level = actor->GetXPLevel(true);
753 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
754 level += actor->CastingLevelBonus(level, spl->SpellType);
755 SpellHeader = spl->GetHeaderIndexFromLevel(level);
756 } else {
757 SpellHeader = 0;
760 SPLExtHeader *header = spl->GetExtHeader(SpellHeader);
761 int casting_time = (int)header->CastingTime;
762 // how does this work for non-actors exactly?
763 if (Type == ST_ACTOR) {
764 // The mental speed effect can shorten or lengthen the casting time.
765 casting_time -= (int)((Actor *) this)->GetStat(IE_MENTALSPEED);
766 if (casting_time < 0) casting_time = 0;
768 // this is a guess which seems approximately right so far
769 int duration = (casting_time*ROUND_SIZE) / 10;
770 if (instant)
771 duration = 0;
773 //cfb
774 if (Type == ST_ACTOR) {
775 Actor *actor = (Actor *) this;
776 EffectQueue *fxqueue = spl->GetEffectBlock(this, this->Pos, -1);
777 spl->AddCastingGlow(fxqueue, duration);
778 fxqueue->SetOwner(actor);
779 fxqueue->AddAllEffects(actor, actor->Pos);
780 delete fxqueue;
783 gamedata->FreeSpell(spl, SpellResRef, false);
784 return duration;
787 bool Scriptable::TimerActive(ieDword ID)
789 if (ID>=MAX_TIMER) {
790 return false;
792 if (script_timers[ID]) {
793 return true;
795 return false;
798 bool Scriptable::TimerExpired(ieDword ID)
800 if (ID>=MAX_TIMER) {
801 return false;
803 if (script_timers[ID] && script_timers[ID] < core->GetGame()->GameTime) {
804 // expired timers become inactive after being checked
805 script_timers[ID] = 0;
806 return true;
808 return false;
811 void Scriptable::StartTimer(ieDword ID, ieDword expiration)
813 if (ID>=MAX_TIMER) {
814 printMessage("Scriptable", " ", RED);
815 printf("Timer id %d exceeded MAX_TIMER %d\n", ID, MAX_TIMER);
816 return;
818 script_timers[ID]= core->GetGame()->GameTime + expiration*AI_UPDATE_TIME;
821 /********************
822 * Selectable Class *
823 ********************/
825 Selectable::Selectable(ScriptableType type)
826 : Scriptable( type )
828 Selected = false;
829 Over = false;
830 size = 0;
831 cover = NULL;
832 circleBitmap[0] = NULL;
833 circleBitmap[1] = NULL;
836 void Selectable::SetSpriteCover(SpriteCover* c)
838 delete cover;
839 cover = c;
842 Selectable::~Selectable(void)
844 delete cover;
847 void Selectable::SetBBox(const Region &newBBox)
849 BBox = newBBox;
852 static const unsigned long tp_steps[8]={3,2,1,0,1,2,3,4};
854 void Selectable::DrawCircle(const Region &vp)
856 /* BG2 colours ground circles as follows:
857 dark green for unselected party members
858 bright green for selected party members
859 flashing green/white for a party member the mouse is over
860 bright red for enemies
861 yellow for panicked actors
862 flashing red/white for enemies the mouse is over
863 flashing cyan/white for neutrals the mouse is over
866 if (size<=0) {
867 return;
869 Color mix;
870 Color* col = &selectedColor;
871 Sprite2D* sprite = circleBitmap[0];
873 if (Selected) {
874 sprite = circleBitmap[1];
875 } else if (Over) {
876 //doing a time dependent flashing of colors
877 //if it is too fast, increase the 6 to 7
878 unsigned long step;
879 GetTime( step );
880 step = tp_steps [(step >> 6) & 7];
881 mix.a = overColor.a;
882 mix.r = (overColor.r*step+selectedColor.r*(8-step))/8;
883 mix.g = (overColor.g*step+selectedColor.g*(8-step))/8;
884 mix.b = (overColor.b*step+selectedColor.b*(8-step))/8;
885 col = &mix;
886 } else if (IsPC()) {
887 col = &overColor;
890 if (sprite) {
891 core->GetVideoDriver()->BlitSprite( sprite, Pos.x - vp.x, Pos.y - vp.y, true );
892 } else {
893 // for size >= 2, radii are (size-1)*16, (size-1)*12
894 // for size == 1, radii are 12, 9
895 int csize = (size - 1) * 4;
896 if (csize < 4) csize = 3;
897 core->GetVideoDriver()->DrawEllipse( (ieWord) (Pos.x - vp.x), (ieWord) (Pos.y - vp.y),
898 (ieWord) (csize * 4), (ieWord) (csize * 3), *col );
902 // Check if P is over our ground circle
903 bool Selectable::IsOver(const Point &P) const
905 int csize = size;
906 if (csize < 2) csize = 2;
908 int dx = P.x - Pos.x;
909 int dy = P.y - Pos.y;
911 // check rectangle first
912 if (dx < -(csize-1)*16 || dx > (csize-1)*16) return false;
913 if (dy < -(csize-1)*12 || dy > (csize-1)*12) return false;
915 // then check ellipse
916 int r = 9*dx*dx + 16*dy*dy; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
918 return (r <= 48*48*(csize-1)*(csize-1));
921 bool Selectable::IsSelected() const
923 return Selected == 1;
926 void Selectable::SetOver(bool over)
928 Over = over;
931 //don't call this function after rendering the cover and before the
932 //blitting of the sprite or bad things will happen :)
933 void Selectable::Select(int Value)
935 if (Selected!=0x80 || Value!=1) {
936 Selected = (ieWord) Value;
938 //forcing regeneration of the cover
939 SetSpriteCover(NULL);
942 void Selectable::SetCircle(int circlesize, const Color &color, Sprite2D* normal_circle, Sprite2D* selected_circle)
944 size = circlesize;
945 selectedColor = color;
946 overColor.r = color.r >> 1;
947 overColor.g = color.g >> 1;
948 overColor.b = color.b >> 1;
949 overColor.a = color.a;
950 circleBitmap[0] = normal_circle;
951 circleBitmap[1] = selected_circle;
954 //used for creatures
955 int Selectable::WantDither()
957 //if dithering is disabled globally, don't do it
958 if (core->FogOfWar&4) {
959 return 0;
961 //if actor is dead, dither it if polygon wants
962 if (Selected&0x80) {
963 return 1;
965 //if actor is selected dither it
966 if (Selected) {
967 return 2;
969 return 1;
972 /***********************
973 * Highlightable Class *
974 ***********************/
976 Highlightable::Highlightable(ScriptableType type)
977 : Scriptable( type )
979 outline = NULL;
980 Highlight = false;
981 Cursor = IE_CURSOR_NORMAL;
982 KeyResRef[0] = 0;
985 Highlightable::~Highlightable(void)
987 if (outline) {
988 delete( outline );
992 bool Highlightable::IsOver(const Point &Pos) const
994 if (!outline) {
995 return false;
997 return outline->PointIn( Pos );
1000 void Highlightable::DrawOutline() const
1002 if (!outline) {
1003 return;
1005 core->GetVideoDriver()->DrawPolyline( outline, outlineColor, true );
1008 void Highlightable::SetCursor(unsigned char CursorIndex)
1010 Cursor = CursorIndex;
1013 bool Highlightable::TryUnlock(Actor *actor, bool removekey) {
1014 const char *Key = GetKey();
1015 Actor *haskey = NULL;
1017 if (Key && actor->InParty) {
1018 Game *game = core->GetGame();
1019 //allow unlock when the key is on any partymember
1020 for (int idx = 0; idx < game->GetPartySize(false); idx++) {
1021 Actor *pc = game->FindPC(idx + 1);
1022 if (!pc) continue;
1024 if (pc->inventory.HasItem(Key,0) ) {
1025 haskey = pc;
1026 break;
1029 } else if (Key) {
1030 //actor is not in party, check only actor
1031 if (actor->inventory.HasItem(Key,0) ) {
1032 haskey = actor;
1036 if (!haskey) {
1037 return false;
1040 if (removekey) {
1041 CREItem *item = NULL;
1042 haskey->inventory.RemoveItem(Key,0,&item);
1043 //the item should always be existing!!!
1044 if (item) {
1045 delete item;
1049 return true;
1053 /*****************
1054 * Movable Class *
1055 *****************/
1057 Movable::Movable(ScriptableType type)
1058 : Selectable( type )
1060 Destination = Pos;
1061 Orientation = 0;
1062 NewOrientation = 0;
1063 StanceID = 0;
1064 path = NULL;
1065 step = NULL;
1066 timeStartStep = 0;
1067 lastFrame = NULL;
1068 Area[0] = 0;
1069 AttackMovements[0] = 100;
1070 AttackMovements[1] = 0;
1071 AttackMovements[2] = 0;
1074 Movable::~Movable(void)
1076 if (path) {
1077 ClearPath();
1081 int Movable::GetPathLength()
1083 PathNode *node = GetNextStep(0);
1084 int i = 0;
1085 while (node->Next) {
1086 i++;
1087 node = node->Next;
1089 return i;
1092 PathNode *Movable::GetNextStep(int x)
1094 if (!step) {
1095 DoStep((unsigned int) ~0);
1097 PathNode *node = step;
1098 while(node && x--) {
1099 node = node->Next;
1101 return node;
1104 Point Movable::GetMostLikelyPosition()
1106 if (!path) {
1107 return Pos;
1110 //actually, sometimes middle path would be better, if
1111 //we stand in Destination already
1112 int halfway = GetPathLength()/2;
1113 PathNode *node = GetNextStep(halfway);
1114 if (node) {
1115 return Point((ieWord) ((node->x*16)+8), (ieWord) ((node->y*12)+6) );
1117 return Destination;
1120 void Movable::SetStance(unsigned int arg)
1122 //don't modify stance from dead back to anything if the actor is dead
1123 if ((StanceID==IE_ANI_TWITCH || StanceID==IE_ANI_DIE) && (arg!=IE_ANI_TWITCH) ) {
1124 if (GetInternalFlag()&IF_REALLYDIED) {
1125 printMessage("Movable","Stance overridden by death\n", YELLOW);
1126 return;
1130 if (arg<MAX_ANIMS) {
1131 StanceID=(unsigned char) arg;
1133 if (StanceID == IE_ANI_ATTACK) {
1134 // Set stance to a random attack animation
1136 int random = rand()%100;
1137 if (random < AttackMovements[0]) {
1138 StanceID = IE_ANI_ATTACK_BACKSLASH;
1139 } else if (random < AttackMovements[0] + AttackMovements[1]) {
1140 StanceID = IE_ANI_ATTACK_SLASH;
1141 } else {
1142 StanceID = IE_ANI_ATTACK_JAB;
1146 } else {
1147 StanceID=IE_ANI_AWAKE; //
1148 printf("Tried to set invalid stance id (%u)\n", arg);
1152 void Movable::SetAttackMoveChances(ieWord *amc)
1154 AttackMovements[0]=amc[0];
1155 AttackMovements[1]=amc[1];
1156 AttackMovements[2]=amc[2];
1161 //this could be used for WingBuffet as well
1162 void Movable::MoveLine(int steps, int Pass, ieDword orient)
1164 //remove previous path
1165 ClearPath();
1166 if (!steps)
1167 return;
1168 Point p = Pos;
1169 p.x/=16;
1170 p.y/=14;
1171 path = area->GetLine( p, steps, orient, Pass );
1174 void AdjustPositionTowards(Point &Pos, ieDword time_diff, unsigned int walk_speed, short srcx, short srcy, short destx, short desty) {
1175 if (destx > srcx)
1176 Pos.x += ( unsigned short )
1177 ( ( ( ( ( destx * 16 ) + 8 ) - Pos.x ) * ( time_diff ) ) / walk_speed );
1178 else
1179 Pos.x -= ( unsigned short )
1180 ( ( ( Pos.x - ( ( destx * 16 ) + 8 ) ) * ( time_diff ) ) / walk_speed );
1181 if (desty > srcy)
1182 Pos.y += ( unsigned short )
1183 ( ( ( ( ( desty * 12 ) + 6 ) - Pos.y ) * ( time_diff ) ) / walk_speed );
1184 else
1185 Pos.y -= ( unsigned short )
1186 ( ( ( Pos.y - ( ( desty * 12 ) + 6 ) ) * ( time_diff ) ) / walk_speed );
1190 // returns whether we made all pending steps (so, false if we must be called again this tick)
1191 // we can't just do them all here because the caller might have to update searchmap etc
1192 bool Movable::DoStep(unsigned int walk_speed, ieDword time)
1194 if (!path) {
1195 return true;
1197 if (!time) time = core->GetGame()->Ticks;
1198 if (!walk_speed) {
1199 // zero speed: no movement
1200 timeStartStep = time;
1201 StanceID = IE_ANI_READY;
1202 return true;
1204 if (!step) {
1205 step = path;
1206 timeStartStep = time;
1207 } else if (step->Next && (( time - timeStartStep ) >= walk_speed)) {
1208 //printf("[New Step] : Orientation = %d\n", step->orient);
1209 step = step->Next;
1210 timeStartStep = timeStartStep + walk_speed;
1212 SetOrientation (step->orient, false);
1213 StanceID = IE_ANI_WALK;
1214 if ((Type == ST_ACTOR) && (InternalFlags & IF_RUNNING)) {
1215 StanceID = IE_ANI_RUN;
1217 Pos.x = ( step->x * 16 ) + 8;
1218 Pos.y = ( step->y * 12 ) + 6;
1219 if (!step->Next) {
1220 // we reached our destination, we are done
1221 ClearPath();
1222 NewOrientation = Orientation;
1223 //since clearpath no longer sets currentaction to NULL
1224 //we set it here
1225 //no we don't, action is responsible for releasing itself
1226 //ReleaseCurrentAction();
1227 return true;
1229 if (( time - timeStartStep ) >= walk_speed) {
1230 // we didn't finish all pending steps, yet
1231 return false;
1233 AdjustPositionTowards(Pos, time - timeStartStep, walk_speed, step->x, step->y, step->Next->x, step->Next->y);
1234 return true;
1237 void Movable::AddWayPoint(const Point &Des)
1239 if (!path) {
1240 WalkTo(Des);
1241 return;
1243 Destination = Des;
1244 //it is tempting to use 'step' here, as it could
1245 //be about half of the current path already
1246 PathNode *endNode = path;
1247 while(endNode->Next) {
1248 endNode = endNode->Next;
1250 Point p(endNode->x, endNode->y);
1251 area->ClearSearchMapFor(this);
1252 PathNode *path2 = area->FindPath( p, Des, size );
1253 endNode->Next = path2;
1254 //probably it is wise to connect it both ways?
1255 path2->Parent = endNode;
1258 void Movable::FixPosition()
1260 if (Type!=ST_ACTOR) {
1261 return;
1263 Actor *actor = (Actor *) this;
1264 if (actor->GetStat(IE_DONOTJUMP)&DNJ_BIRD ) {
1265 return;
1267 //before fixposition, you should remove own shadow
1268 area->ClearSearchMapFor(this);
1269 Pos.x/=16;
1270 Pos.y/=12;
1271 GetCurrentArea()->AdjustPosition(Pos);
1272 Pos.x=Pos.x*16+8;
1273 Pos.y=Pos.y*12+6;
1276 void Movable::WalkTo(const Point &Des, int distance)
1278 Point from;
1280 // maybe caller should be responsible for this
1281 if ((Des.x/16 == Pos.x/16) && (Des.y/12 == Pos.y/12)) {
1282 ClearPath();
1283 return;
1286 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1287 PathNode *prev_step = NULL;
1288 unsigned char old_stance = StanceID;
1289 if (step && step->Next) {
1290 // don't interrupt in the middle of a step; path from the next one
1291 prev_step = new PathNode(*step);
1292 from.x = ( step->Next->x * 16 ) + 8;
1293 from.y = ( step->Next->y * 12 ) + 6;
1296 ClearPath();
1297 if (!prev_step) {
1298 FixPosition();
1299 from = Pos;
1301 area->ClearSearchMapFor(this);
1302 if (distance) {
1303 path = area->FindPathNear( from, Des, size, distance );
1304 } else {
1305 path = area->FindPath( from, Des, size, distance );
1307 //ClearPath sets destination, so Destination must be set after it
1308 //also we should set Destination only if there is a walkable path
1309 if (path) {
1310 Destination = Des;
1312 if (prev_step) {
1313 // we want to smoothly continue, please
1314 // this all needs more thought! but it seems to work okay
1315 StanceID = old_stance;
1317 if (path->Next) {
1318 // this is a terrible hack to make up for the
1319 // pathfinder orienting the first node wrong
1320 // should be fixed in pathfinder and not here!
1321 Point next, follow;
1322 next.x = path->x; next.y = path->y;
1323 follow.x = path->Next->x;
1324 follow.y = path->Next->y;
1325 path->orient = GetOrient(follow, next);
1328 // then put the prev_step at the beginning of the path
1329 prev_step->Next = path;
1330 path->Parent = prev_step;
1331 path = prev_step;
1333 step = path;
1335 } else {
1336 // pathing failed
1337 if (prev_step) {
1338 delete( prev_step );
1339 FixPosition();
1344 void Movable::RunAwayFrom(const Point &Des, int PathLength, int flags)
1346 ClearPath();
1347 area->ClearSearchMapFor(this);
1348 path = area->RunAway( Pos, Des, size, PathLength, flags );
1351 void Movable::RandomWalk(bool can_stop, bool run)
1353 if (path) {
1354 return;
1356 //if not continous random walk, then stops for a while
1357 if (can_stop && (rand()&3) ) {
1358 SetWait((rand()&7)+7);
1359 return;
1361 if (run) {
1362 InternalFlags|=IF_RUNNING;
1364 //the commenting-out of the clear search map call was removed in 0.4.0
1365 //if you want to put it back for some reason, check
1366 //if the searchmap is not eaten up
1367 area->ClearSearchMapFor(this);
1368 Point p = Pos;
1370 //selecting points around a circle's edge around actor (didn't work better)
1371 //int x = core->Roll(1,100,-50);
1372 //p.x+=x;
1373 //p.y+=(int) sqrt(100-x*x);
1375 //selecting points in a square around actor
1376 p.x+=core->Roll(1,50,-25);
1377 p.y+=core->Roll(1,50,-25);
1378 //the 5th parameter is controlling the orientation of the actor
1379 //0 - back away, 1 - face direction
1380 path = area->RunAway( Pos, p, size, 50, 1 );
1383 void Movable::MoveTo(const Point &Des)
1385 area->ClearSearchMapFor(this);
1386 Pos = Des;
1387 Destination = Des;
1388 if (BlocksSearchMap()) {
1389 area->BlockSearchMap( Pos, size, IsPC()?PATH_MAP_PC:PATH_MAP_NPC);
1393 void Movable::ClearPath()
1395 //this is to make sure attackers come to us
1396 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1397 //is set before ClearPath
1398 Destination = Pos;
1399 if (StanceID==IE_ANI_WALK || StanceID==IE_ANI_RUN) {
1400 StanceID = IE_ANI_AWAKE;
1402 InternalFlags&=~IF_NORECTICLE;
1403 PathNode* thisNode = path;
1404 while (thisNode) {
1405 PathNode* nextNode = thisNode->Next;
1406 delete( thisNode );
1407 thisNode = nextNode;
1409 path = NULL;
1410 step = NULL;
1411 //don't call ReleaseCurrentAction
1414 void Movable::DrawTargetPoint(const Region &vp)
1416 if (!path || !Selected || (InternalFlags&IF_NORECTICLE) )
1417 return;
1419 // recticles are never drawn in cutscenes
1420 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE))
1421 return;
1423 // generates "step" from sequence 3 2 1 0 1 2 3 4
1424 // updated each 1/15 sec
1425 unsigned long step;
1426 GetTime( step );
1427 step = tp_steps [(step >> 6) & 7];
1429 step = step + 1;
1430 int csize = (size - 1) * 4;
1431 if (csize < 4) csize = 3;
1433 /* segments should not go outside selection radius */
1434 unsigned short xradius = (csize * 4) - 5;
1435 unsigned short yradius = (csize * 3) - 5;
1436 ieWord xcentre = (ieWord) (Destination.x - vp.x);
1437 ieWord ycentre = (ieWord) (Destination.y - vp.y);
1439 // TODO: 0.5 and 0.7 are pretty much random values
1440 // right segment
1441 core->GetVideoDriver()->DrawEllipseSegment( xcentre + step, ycentre, xradius,
1442 yradius, selectedColor, -0.5, 0.5 );
1443 // top segment
1444 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre - step, xradius,
1445 yradius, selectedColor, -0.7 - M_PI_2, 0.7 - M_PI_2 );
1446 // left segment
1447 core->GetVideoDriver()->DrawEllipseSegment( xcentre - step, ycentre, xradius,
1448 yradius, selectedColor, -0.5 - M_PI, 0.5 - M_PI );
1449 // bottom segment
1450 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre + step, xradius,
1451 yradius, selectedColor, -0.7 - M_PI - M_PI_2, 0.7 - M_PI - M_PI_2 );
1454 /**********************
1455 * Tiled Object Class *
1456 **********************/
1458 TileObject::TileObject()
1460 opentiles = NULL;
1461 opencount = 0;
1462 closedtiles = NULL;
1463 closedcount = 0;
1464 Flags = 0;
1467 TileObject::~TileObject()
1469 if (opentiles) {
1470 free( opentiles );
1472 if (closedtiles) {
1473 free( closedtiles );
1477 void TileObject::SetOpenTiles(unsigned short* Tiles, int cnt)
1479 if (opentiles) {
1480 free( opentiles );
1482 opentiles = Tiles;
1483 opencount = cnt;
1486 void TileObject::SetClosedTiles(unsigned short* Tiles, int cnt)
1488 if (closedtiles) {
1489 free( closedtiles );
1491 closedtiles = Tiles;
1492 closedcount = cnt;
1495 /**************
1496 * Door Class *
1497 **************/
1499 Door::Door(TileOverlay* Overlay)
1500 : Highlightable( ST_DOOR )
1502 tiles = NULL;
1503 tilecount = 0;
1504 Flags = 0;
1505 open = NULL;
1506 closed = NULL;
1507 open_ib = NULL;
1508 oibcount = 0;
1509 closed_ib = NULL;
1510 cibcount = 0;
1511 OpenSound[0] = 0;
1512 CloseSound[0] = 0;
1513 LockSound[0] = 0;
1514 UnLockSound[0] = 0;
1515 overlay = Overlay;
1516 LinkedInfo[0] = 0;
1517 OpenStrRef = (ieDword) -1;
1520 Door::~Door(void)
1522 if (Flags&DOOR_OPEN) {
1523 if (closed) {
1524 delete( closed );
1526 } else {
1527 if (open) {
1528 delete( open );
1531 if (tiles) {
1532 free( tiles );
1534 if (open_ib) {
1535 free( open_ib );
1537 if (closed_ib) {
1538 free( closed_ib );
1542 void Door::ImpedeBlocks(int count, Point *points, unsigned char value)
1544 for(int i = 0;i<count;i++) {
1545 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_NOTDOOR;
1546 area->SearchMap->SetAt( points[i].x, points[i].y, (tmp|value) );
1550 void Door::UpdateDoor()
1552 if (Flags&DOOR_OPEN) {
1553 outline = open;
1554 } else {
1555 outline = closed;
1557 unsigned char oval, cval;
1559 oval = PATH_MAP_IMPASSABLE;
1560 if (Flags & DOOR_TRANSPARENT) {
1561 cval = PATH_MAP_DOOR_TRANSPARENT;
1563 else {
1564 cval = PATH_MAP_DOOR_OPAQUE;
1566 if (Flags &DOOR_OPEN) {
1567 ImpedeBlocks(cibcount, closed_ib, 0);
1568 ImpedeBlocks(oibcount, open_ib, cval);
1570 else {
1571 ImpedeBlocks(oibcount, open_ib, 0);
1572 ImpedeBlocks(cibcount, closed_ib, cval);
1575 InfoPoint *ip = area->TMap->GetInfoPoint(LinkedInfo);
1576 if (ip) {
1577 if (Flags&DOOR_OPEN) ip->Flags&=~INFO_DOOR;
1578 else ip->Flags|=INFO_DOOR;
1582 void Door::ToggleTiles(int State, int playsound)
1584 int i;
1585 int state;
1587 if (State) {
1588 state = !closedIndex;
1589 if (playsound && ( OpenSound[0] != '\0' ))
1590 core->GetAudioDrv()->Play( OpenSound );
1591 } else {
1592 state = closedIndex;
1593 if (playsound && ( CloseSound[0] != '\0' ))
1594 core->GetAudioDrv()->Play( CloseSound );
1596 for (i = 0; i < tilecount; i++) {
1597 overlay->tiles[tiles[i]]->tileIndex = (ieByte) state;
1600 //set door_open as state
1601 Flags = (Flags & ~DOOR_OPEN) | (State == !core->HasFeature(GF_REVERSE_DOOR) );
1604 //this is the short name (not the scripting name)
1605 void Door::SetName(const char* name)
1607 strnlwrcpy( ID, name, 8 );
1610 void Door::SetTiles(unsigned short* Tiles, int cnt)
1612 if (tiles) {
1613 free( tiles );
1615 tiles = Tiles;
1616 tilecount = cnt;
1619 void Door::SetDoorLocked(int Locked, int playsound)
1621 if (Locked) {
1622 if (Flags & DOOR_LOCKED) return;
1623 Flags|=DOOR_LOCKED;
1624 if (playsound && ( LockSound[0] != '\0' ))
1625 core->GetAudioDrv()->Play( LockSound );
1627 else {
1628 if (!(Flags & DOOR_LOCKED)) return;
1629 Flags&=~DOOR_LOCKED;
1630 if (playsound && ( UnLockSound[0] != '\0' ))
1631 core->GetAudioDrv()->Play( UnLockSound );
1635 int Door::IsOpen() const
1637 int ret = core->HasFeature(GF_REVERSE_DOOR);
1638 if (Flags&DOOR_OPEN) {
1639 ret = !ret;
1641 return ret;
1644 //also mark actors to fix position
1645 bool Door::BlockedOpen(int Open, int ForceOpen)
1647 bool blocked;
1648 int count;
1649 Point *points;
1651 blocked = false;
1652 if (Open) {
1653 count = oibcount;
1654 points = open_ib;
1655 } else {
1656 count = cibcount;
1657 points = closed_ib;
1659 //getting all impeded actors flagged for jump
1660 Region rgn;
1661 rgn.w = 16;
1662 rgn.h = 12;
1663 for(int i = 0;i<count;i++) {
1664 Actor** ab;
1665 rgn.x = points[i].x*16;
1666 rgn.y = points[i].y*12;
1667 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_ACTOR;
1668 if (tmp) {
1669 int ac = area->GetActorInRect(ab, rgn, false);
1670 while(ac--) {
1671 if (ab[ac]->GetBase(IE_DONOTJUMP)) {
1672 continue;
1674 ab[ac]->SetBase(IE_DONOTJUMP, DNJ_JUMP);
1675 blocked = true;
1677 if (ab) {
1678 free(ab);
1683 if ((Flags&DOOR_SLIDE) || ForceOpen) {
1684 return false;
1686 return blocked;
1689 void Door::SetDoorOpen(int Open, int playsound, ieDword ID)
1691 if (playsound) {
1692 //the door cannot be blocked when opening,
1693 //but the actors will be pushed
1694 //BlockedOpen will mark actors to be pushed
1695 if (BlockedOpen(Open,0) && !Open) {
1696 //clear up the blocking actors
1697 area->JumpActors(false);
1698 return;
1700 area->JumpActors(true);
1702 if (Open) {
1703 LastEntered = ID; //used as lastOpener
1705 // in PS:T, opening a door does not unlock it
1706 if (!core->HasFeature(GF_REVERSE_DOOR)) {
1707 SetDoorLocked(false,playsound);
1709 } else {
1710 LastTriggerObject = LastTrigger = ID; //used as lastCloser
1712 ToggleTiles(Open, playsound);
1713 //synchronising other data with the door state
1714 UpdateDoor();
1715 area->ActivateWallgroups(open_wg_index, open_wg_count, Flags&DOOR_OPEN);
1716 area->ActivateWallgroups(closed_wg_index, closed_wg_count, !(Flags&DOOR_OPEN));
1719 bool Door::TryUnlock(Actor *actor) {
1720 if (!(Flags&DOOR_LOCKED)) return true;
1722 // don't remove key in PS:T!
1723 bool removekey = !core->HasFeature(GF_REVERSE_DOOR) && Flags&DOOR_KEY;
1724 return Highlightable::TryUnlock(actor, removekey);
1727 void Door::SetPolygon(bool Open, Gem_Polygon* poly)
1729 if (Open) {
1730 if (open)
1731 delete( open );
1732 open = poly;
1733 } else {
1734 if (closed)
1735 delete( closed );
1736 closed = poly;
1740 void Door::SetNewOverlay(TileOverlay *Overlay) {
1741 overlay = Overlay;
1742 ToggleTiles(IsOpen(), false);
1745 void Highlightable::SetTrapDetected(int x)
1747 if(x == TrapDetected)
1748 return;
1749 TrapDetected = x;
1750 if(TrapDetected) {
1751 core->Autopause(AP_TRAP);
1755 void Highlightable::TryDisarm(Actor *actor)
1757 if (!Trapped || !TrapDetected) return;
1759 LastTriggerObject = LastTrigger = actor->GetID();
1760 int skill = actor->GetStat(IE_TRAPS);
1762 if (skill/2+core->Roll(1,skill/2,0)>TrapRemovalDiff) {
1763 LastDisarmed = actor->GetID();
1764 //trap removed
1765 Trapped = 0;
1766 displaymsg->DisplayConstantStringName(STR_DISARM_DONE, 0xd7d7be, actor);
1767 actor->AddExperience(XP_DISARM, actor->GetXPLevel(1));
1768 } else {
1769 displaymsg->DisplayConstantStringName(STR_DISARM_FAIL, 0xd7d7be, actor);
1770 TriggerTrap(skill, LastTrigger);
1772 ImmediateEvent();
1775 void Door::TryPickLock(Actor *actor)
1777 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
1778 if (LockDifficulty == 100) {
1779 displaymsg->DisplayConstantStringName(STR_DOOR_NOPICK, 0xbcefbc, actor);
1780 } else {
1781 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
1782 LastPickLockFailed = actor->GetID();
1784 return;
1786 SetDoorLocked( false, true);
1787 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
1788 LastUnlocked = actor->GetID();
1789 ImmediateEvent();
1790 actor->AddExperience(XP_LOCKPICK, actor->GetXPLevel(1));
1793 void Door::TryBashLock(Actor *actor)
1795 //Get the strength bonus agains lock difficulty
1796 int str = actor->GetStat(IE_STR);
1797 int strEx = actor->GetStat(IE_STREXTRA);
1798 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
1800 //bonus will never reach 100
1801 if(bonus < LockDifficulty) {
1802 displaymsg->DisplayConstantStringName(STR_DOORBASH_FAIL, 0xbcefbc, actor);
1803 return;
1806 displaymsg->DisplayConstantStringName(STR_DOORBASH_DONE, 0xd7d7be, actor);
1807 SetDoorLocked(false, true);
1808 //Is this really useful ?
1809 LastUnlocked = actor->GetID();
1810 ImmediateEvent();
1813 void Door::DebugDump() const
1815 printf( "Debugdump of Door %s:\n", GetScriptName() );
1816 printf( "Door Open: %s\n", YESNO(IsOpen()));
1817 printf( "Door Locked: %s\n", YESNO(Flags&DOOR_LOCKED));
1818 printf( "Door Trapped: %s\n", YESNO(Trapped));
1819 if (Trapped) {
1820 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags&DOOR_RESET), YESNO(Flags&DOOR_DETECTABLE) );
1822 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags&DOOR_SECRET),YESNO(Flags&DOOR_FOUND));
1823 const char *Key = GetKey();
1824 const char *name = "NONE";
1825 if (Scripts[0]) {
1826 name = Scripts[0]->GetName();
1828 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name, Key?Key:"NONE", YESNO(Flags&DOOR_KEY), Dialog );
1831 /*******************
1832 * InfoPoint Class *
1833 *******************/
1835 InfoPoint::InfoPoint(void)
1836 : Highlightable( ST_TRIGGER )
1838 Destination[0] = 0;
1839 EntranceName[0] = 0;
1840 Flags = 0;
1841 TrapDetectionDiff = 0;
1842 TrapRemovalDiff = 0;
1843 TrapDetected = 0;
1844 TrapLaunch.empty();
1845 Dialog[0] = 0;
1848 InfoPoint::~InfoPoint(void)
1852 //checks if the actor may use this travel trigger
1853 //bit 1 : can use
1854 //bit 2 : whole team
1855 int InfoPoint::CheckTravel(Actor *actor)
1857 if (Flags&TRAP_DEACTIVATED) return CT_CANTMOVE;
1858 if (!actor->InParty && (Flags&TRAVEL_NONPC) ) return CT_CANTMOVE;
1859 if (actor->InParty && (Flags&TRAVEL_PARTY) ) {
1860 if (core->HasFeature(GF_TEAM_MOVEMENT) || core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE) ) {
1861 return CT_WHOLE;
1863 return CT_GO_CLOSER;
1865 if(actor->IsSelected() ) {
1866 if(core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE|ENP_ONLYSELECT) ) {
1867 return CT_MOVE_SELECTED;
1869 return CT_SELECTED;
1871 return CT_ACTIVE;
1874 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1875 //skill is the all around modified trap detection skill
1876 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1877 void Highlightable::DetectTrap(int skill)
1879 if (!CanDetectTrap()) return;
1880 if (!Scripts[0]) return;
1881 if ((skill>=100) && (skill!=256) ) skill = 100;
1882 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1883 SetTrapDetected(1); //probably could be set to the player #?
1887 bool Highlightable::PossibleToSeeTrap() const
1889 return CanDetectTrap();
1892 bool InfoPoint::PossibleToSeeTrap() const
1894 // Only detectable trap-type infopoints.
1895 return (CanDetectTrap() && (Type == ST_PROXIMITY) );
1898 bool InfoPoint::CanDetectTrap() const
1900 // Traps can be detected on all types of infopoint, as long
1901 // as the trap is detectable and isn't deactivated.
1902 return ((Flags&TRAP_DETECTABLE) && !(Flags&TRAP_DEACTIVATED));
1905 // returns true if the infopoint is a PS:T portal
1906 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1907 bool InfoPoint::IsPortal() const
1909 if (Type!=ST_TRAVEL) return false;
1910 if (Cursor != IE_CURSOR_PORTAL) return false;
1911 return core->HasFeature(GF_REVERSE_DOOR);
1914 //trap that is visible on screen (marked by red)
1915 //if TrapDetected is a bitflag, we could show traps selectively for
1916 //players, really nice for multiplayer
1917 bool Highlightable::VisibleTrap(int see_all) const
1919 if (!Trapped) return false;
1920 if (!PossibleToSeeTrap()) return false;
1921 if (!Scripts[0]) return false;
1922 if (see_all) return true;
1923 if (TrapDetected ) return true;
1924 return false;
1927 //trap that will fire now
1928 bool Highlightable::TriggerTrap(int skill, ieDword ID)
1930 if (!Trapped) {
1931 return false;
1933 //actually this could be script name[0]
1934 if (!Scripts[0]) {
1935 return false;
1937 if (CanDetectTrap()) {
1938 // this should probably be party members only
1939 if ((skill>=100) && (skill!=256) ) skill = 100;
1940 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1941 SetTrapDetected(1); //probably too late :)
1942 //tumble???
1943 return false;
1946 LastTriggerObject = LastTrigger = LastEntered = ID;
1947 ImmediateEvent();
1948 if (!TrapResets()) {
1949 Trapped = false;
1951 return true;
1954 //trap that will fire now
1955 bool InfoPoint::TriggerTrap(int skill, ieDword ID)
1957 if (Type!=ST_PROXIMITY) {
1958 return true;
1960 if (Flags&TRAP_DEACTIVATED) {
1961 return false;
1963 if (!Trapped) {
1964 // we have to set Entered somewhere, here seems best..
1965 LastEntered = ID;
1966 return true;
1967 } else if (Highlightable::TriggerTrap(skill, ID)) {
1968 if (!Trapped) {
1969 Flags|=TRAP_DEACTIVATED;
1971 // ok, so this is a pain. Entered() trigger checks Trapped,
1972 // so it needs to be kept set. how to do this right?
1973 Trapped = true;
1974 return true;
1976 return false;
1979 bool InfoPoint::Entered(Actor *actor)
1981 if (outline->PointIn( actor->Pos ) ) {
1982 //don't trigger again for this actor
1983 if (!(actor->GetInternalFlag()&IF_INTRAP)) {
1984 goto check;
1987 // why is this here? actors which aren't *in* a trap get IF_INTRAP
1988 // repeatedly unset, so this triggers again and again and again.
1989 // i disabled it for ST_PROXIMITY for now..
1990 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
1991 goto check;
1993 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
1994 if (Type == ST_TRAVEL && PersonalDistance(TrapLaunch, actor)<MAX_OPERATING_DISTANCE) {
1995 goto check;
1997 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
1998 // for distance from the outline for travel regions instead?
1999 if (Type == ST_TRAVEL && PersonalDistance(TalkPos, actor)<MAX_OPERATING_DISTANCE) {
2000 goto check;
2002 if (Flags&TRAP_USEPOINT) {
2003 if (PersonalDistance(UsePoint, actor)<MAX_OPERATING_DISTANCE) {
2004 goto check;
2007 return false;
2008 check:
2009 if (Type==ST_TRAVEL) {
2010 return true;
2013 if (actor->InParty || (Flags&TRAP_NPC) ) {
2014 //no need to avoid a travel trigger
2016 //skill?
2017 if (TriggerTrap(0, actor->GetID()) ) {
2018 return true;
2021 return false;
2024 void InfoPoint::DebugDump() const
2026 switch (Type) {
2027 case ST_TRIGGER:
2028 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2029 break;
2030 case ST_PROXIMITY:
2031 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2032 break;
2033 case ST_TRAVEL:
2034 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2035 break;
2036 default:
2037 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2038 break;
2040 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected, YESNO(Trapped));
2041 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2042 TrapRemovalDiff );
2043 const char *name = "NONE";
2044 if (Scripts[0]) {
2045 name = Scripts[0]->GetName();
2047 printf( "Script: %s, Key: %s, Dialog: %s\n", name, KeyResRef, Dialog );
2048 printf( "Active: %s\n", YESNO(InternalFlags&IF_ACTIVE));
2051 /*******************
2052 * Container Class *
2053 *******************/
2055 Container::Container(void)
2056 : Highlightable( ST_CONTAINER )
2058 Type = 0;
2059 LockDifficulty = 0;
2060 Flags = 0;
2061 TrapDetectionDiff = 0;
2062 TrapRemovalDiff = 0;
2063 Trapped = 0;
2064 TrapDetected = 0;
2065 inventory.SetInventoryType(INVENTORY_HEAP);
2066 // NULL should be 0 for this
2067 memset (groundicons, 0, sizeof(groundicons) );
2068 groundiconcover = 0;
2071 void Container::FreeGroundIcons()
2073 Video* video = core->GetVideoDriver();
2075 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2076 if (groundicons[i]) {
2077 video->FreeSprite( groundicons[i] );
2078 groundicons[i]=NULL;
2081 delete groundiconcover;
2082 groundiconcover = 0;
2085 Container::~Container()
2087 FreeGroundIcons();
2090 void Container::DrawPile(bool highlight, Region screen, Color tint)
2092 Video* video = core->GetVideoDriver();
2093 CreateGroundIconCover();
2094 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2095 if (groundicons[i]) {
2096 //draw it with highlight
2097 video->BlitGameSprite(groundicons[i],
2098 screen.x + Pos.x, screen.y + Pos.y,
2099 BLIT_TINTED | (highlight ? 0:BLIT_NOSHADOW),
2100 tint, groundiconcover);
2105 // create the SpriteCover for the groundicons
2106 void Container::CreateGroundIconCover()
2108 int xpos = 0;
2109 int ypos = 0;
2110 int width = 0;
2111 int height = 0;
2113 int i; //msvc6.0
2114 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2115 if (groundicons[i]) {
2116 Sprite2D& spr = *groundicons[i];
2117 if (xpos < spr.XPos) {
2118 width += spr.XPos - xpos;
2119 xpos = spr.XPos;
2121 if (ypos < spr.YPos) {
2122 height += spr.YPos - ypos;
2123 ypos = spr.YPos;
2125 if (width-xpos < spr.Width-spr.XPos) {
2126 width = spr.Width-spr.XPos+xpos;
2128 if (height-ypos < spr.Height-spr.YPos) {
2129 height = spr.Height-spr.YPos+ypos;
2134 if (!groundiconcover ||
2135 !groundiconcover->Covers(Pos.x, Pos.y, xpos, ypos, width, height))
2137 delete groundiconcover;
2138 groundiconcover = 0;
2139 if (width*height > 0) {
2140 groundiconcover = GetCurrentArea()->BuildSpriteCover
2141 (Pos.x, Pos.y, xpos, ypos, width, height, WantDither());
2145 #ifndef NDEBUG
2146 // TODO: remove this checking code eventually
2147 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2148 if (groundicons[i]) {
2149 Sprite2D& spr = *groundicons[i];
2150 assert(groundiconcover->Covers(Pos.x, Pos.y, spr.XPos, spr.YPos, spr.Width, spr.Height));
2153 #endif
2156 void Container::SetContainerLocked(bool lock)
2158 if (lock) {
2159 Flags|=CONT_LOCKED;
2160 } else {
2161 Flags&=~CONT_LOCKED;
2165 //This function doesn't exist in the original IE, destroys a container
2166 //turning it to a ground pile
2167 void Container::DestroyContainer()
2169 //it is already a groundpile?
2170 if (Type == IE_CONTAINER_PILE)
2171 return;
2172 Type = IE_CONTAINER_PILE;
2173 RefreshGroundIcons();
2174 //probably we should stop the script or trigger it, whatever
2177 //Takes an item from the container's inventory and returns its pointer
2178 CREItem *Container::RemoveItem(unsigned int idx, unsigned int count)
2180 CREItem *ret = inventory.RemoveItem(idx, count);
2181 //we just took the 3. or less item, groundpile changed
2182 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<3)) {
2183 RefreshGroundIcons();
2185 return ret;
2188 //Adds an item to the container's inventory
2189 //containers always have enough capacity (so far), thus we always return 2
2190 int Container::AddItem(CREItem *item)
2192 inventory.AddItem(item);
2193 //we just added a 3. or less item, groundpile changed
2194 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<4)) {
2195 RefreshGroundIcons();
2197 return 2;
2200 void Container::RefreshGroundIcons()
2202 int i = inventory.GetSlotCount();
2203 if (i>MAX_GROUND_ICON_DRAWN)
2204 i = MAX_GROUND_ICON_DRAWN;
2205 FreeGroundIcons();
2206 while (i--) {
2207 CREItem *slot = inventory.GetSlotItem(i); //borrowed reference
2208 Item *itm = gamedata->GetItem( slot->ItemResRef ); //cached reference
2209 //well, this is required in PST, needs more work if some other
2210 //game is broken by not using -1,0
2211 groundicons[i] = gamedata->GetBAMSprite( itm->GroundIcon, 0, 0 );
2212 gamedata->FreeItem( itm, slot->ItemResRef ); //decref
2216 //used for ground piles
2217 int Container::WantDither()
2219 //if pile is highlighted, always dither it
2220 if (Highlight) {
2221 return 2; //dither me if you want
2223 //if pile isn't highlighted, dither it if the polygon wants
2224 return 1;
2227 int Container::IsOpen() const
2229 if (Flags&CONT_LOCKED) {
2230 return false;
2232 return true;
2235 void Container::TryPickLock(Actor *actor)
2237 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
2238 if (LockDifficulty == 100) {
2239 displaymsg->DisplayConstantStringName(STR_CONT_NOPICK, 0xbcefbc, actor);
2240 } else {
2241 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
2242 LastPickLockFailed = actor->GetID();
2244 return;
2246 SetContainerLocked(false);
2247 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
2248 LastUnlocked = actor->GetID();
2249 ImmediateEvent();
2250 actor->AddExperience(XP_LOCKPICK, actor->GetXPLevel(1));
2253 void Container::TryBashLock(Actor *actor)
2255 //Get the strength bonus agains lock difficulty
2256 int str = actor->GetStat(IE_STR);
2257 int strEx = actor->GetStat(IE_STREXTRA);
2258 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
2260 //bonus will never reach 100
2261 if(bonus < LockDifficulty) {
2262 displaymsg->DisplayConstantStringName(STR_CONTBASH_FAIL, 0xbcefbc, actor);
2263 return;
2266 displaymsg->DisplayConstantStringName(STR_CONTBASH_DONE, 0xd7d7be, actor);
2267 SetContainerLocked(false);
2268 //Is this really useful ?
2269 LastUnlocked = actor->GetID();
2270 ImmediateEvent();
2273 void Container::DebugDump() const
2275 printf( "Debugdump of Container %s\n", GetScriptName() );
2276 printf( "Type: %d, LockDifficulty: %d\n", Type, LockDifficulty );
2277 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags, YESNO(Trapped), TrapDetected );
2278 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2279 TrapRemovalDiff );
2280 const char *name = "NONE";
2281 if (Scripts[0]) {
2282 name = Scripts[0]->GetName();
2284 printf( "Script: %s, Key: %s\n", name, KeyResRef );
2285 // FIXME: const_cast
2286 const_cast<Inventory&>(inventory).dump();
2289 bool Container::TryUnlock(Actor *actor) {
2290 if (!(Flags&CONT_LOCKED)) return true;
2292 return Highlightable::TryUnlock(actor, false);