fixed the last commit to set and check the target more often, thanks fuzzie
[gemrb.git] / gemrb / core / Scriptable / ActorBlock.cpp
blobf57cdd12ee5ab0a7dd535ee45ee8216562c9e754
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2005 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "Scriptable/ActorBlock.h"
22 #include "strrefs.h"
23 #include "win32def.h"
25 #include "Audio.h"
26 #include "DisplayMessage.h"
27 #include "Game.h"
28 #include "GameData.h"
29 #include "Interface.h"
30 #include "Item.h"
31 #include "Map.h"
32 #include "Projectile.h"
33 #include "Spell.h"
34 #include "SpriteCover.h"
35 #include "TileMap.h"
36 #include "Video.h"
37 #include "GameScript/GSUtils.h"
38 #include "GUI/GameControl.h"
40 #include <cassert>
41 #include <cmath>
43 #define YESNO(x) ( (x)?"Yes":"No")
45 /***********************
46 * Scriptable Class *
47 ***********************/
48 Scriptable::Scriptable(ScriptableType type)
50 Type = type;
51 CutSceneId = NULL;
52 for (int i = 0; i < MAX_SCRIPTS; i++) {
53 Scripts[i] = NULL;
55 overHeadText = NULL;
56 overHeadTextPos.empty();
57 textDisplaying = 0;
58 timeStartDisplaying = 0;
59 scriptName[0] = 0;
60 TriggerID = 0; //used by SendTrigger
61 LastTriggerObject = LastTrigger = 0;
62 LastEntered = 0;
63 LastDisarmed = 0;
64 LastDisarmFailed = 0;
65 LastUnlocked = 0;
66 LastOpenFailed = 0;
67 LastPickLockFailed = 0;
68 DialogName = 0;
69 CurrentAction = NULL;
70 CurrentActionState = 0;
71 CurrentActionTarget = 0;
72 CurrentActionInterruptable = true;
73 UnselectableTimer = 0;
74 startTime = 0; //executing scripts
75 lastRunTime = 0; //evaluating scripts
76 lastDelay = 0;
77 Dialog[0] = 0;
79 interval = ( 1000 / AI_UPDATE_TIME );
80 WaitCounter = 0;
81 if (Type == ST_ACTOR) {
82 InternalFlags = IF_VISIBLE | IF_ONCREATION | IF_USEDSAVE;
83 } else {
84 InternalFlags = IF_ACTIVE | IF_VISIBLE | IF_ONCREATION | IF_NOINT;
86 area = 0;
87 Pos.x = 0;
88 Pos.y = 0;
90 LastCasterOnMe = 0;
91 LastSpellOnMe = 0xffffffff;
92 LastCasterSeen = 0;
93 LastSpellSeen = 0xffffffff;
94 SpellHeader = -1;
95 LastTargetPos.empty();
96 locals = new Variables();
97 locals->SetType( GEM_VARIABLES_INT );
98 locals->ParseKey( 1 );
99 InitTriggers();
101 memset( script_timers,0, sizeof(script_timers));
104 Scriptable::~Scriptable(void)
106 if (CurrentAction) {
107 ReleaseCurrentAction();
109 ClearActions();
110 for (int i = 0; i < MAX_SCRIPTS; i++) {
111 if (Scripts[i]) {
112 delete( Scripts[i] );
115 if (overHeadText) {
116 core->FreeString( overHeadText );
118 if (locals) {
119 delete( locals );
123 void Scriptable::SetScriptName(const char* text)
125 //if (text && text[0]) { //this leaves some uninitialized bytes
126 //lets hope this won't break anything
127 if (text) {
128 strnspccpy( scriptName, text, 32 );
132 /** Gets the DeathVariable */
133 const char* Scriptable::GetScriptName(void) const
135 return scriptName;
138 Map* Scriptable::GetCurrentArea() const
140 //this could be NULL, always check it
141 return area;
144 void Scriptable::SetMap(Map *map)
146 if (map && (map->GetCurrentArea()!=map)) {
147 //a map always points to itself (if it is a real map)
148 printMessage("Scriptable","Invalid map set!\n",LIGHT_RED);
149 abort();
151 area = map;
154 //ai is nonzero if this is an actor currently in the party
155 //if the script level is AI_SCRIPT_LEVEL, then we need to
156 //load an AI script (.bs) instead of (.bcs)
157 void Scriptable::SetScript(const ieResRef aScript, int idx, bool ai)
159 if (idx >= MAX_SCRIPTS) {
160 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
161 abort();
163 if (Scripts[idx]) {
164 delete Scripts[idx];
166 Scripts[idx] = NULL;
167 // NONE is an 'invalid' script name, never used seriously
168 // This hack is to prevent flooding of the console
169 if (aScript[0] && stricmp(aScript, "NONE") ) {
170 if (idx!=AI_SCRIPT_LEVEL) ai = false;
171 Scripts[idx] = new GameScript( aScript, this, idx, ai );
175 void Scriptable::SetScript(int index, GameScript* script)
177 if (index >= MAX_SCRIPTS) {
178 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED);
179 return;
181 if (Scripts[index] ) {
182 delete Scripts[index];
184 Scripts[index] = script;
187 void Scriptable::DisplayHeadText(const char* text)
189 if (overHeadText) {
190 core->FreeString( overHeadText );
192 overHeadText = (char *) text;
193 overHeadTextPos.empty();
194 if (text) {
195 timeStartDisplaying = core->GetGame()->Ticks;
196 textDisplaying = 1;
198 else {
199 timeStartDisplaying = 0;
200 textDisplaying = 0;
204 /* 'fix' the current overhead text in the current position */
205 void Scriptable::FixHeadTextPos()
207 overHeadTextPos = Pos;
210 #define MAX_DELAY 6000
211 static const Color black={0,0,0,0};
213 void Scriptable::DrawOverheadText(const Region &screen)
215 unsigned long time = core->GetGame()->Ticks;
216 Palette *palette = NULL;
218 if (!textDisplaying)
219 return;
221 time -= timeStartDisplaying;
223 Font* font = core->GetFont( 1 );
224 if (time >= MAX_DELAY) {
225 textDisplaying = 0;
226 return;
227 } else {
228 time = (MAX_DELAY-time)/10;
229 if (time<256) {
230 const Color overHeadColor = {time,time,time,time};
231 palette = core->CreatePalette(overHeadColor, black);
235 int cs = 100;
236 if (Type == ST_ACTOR) {
237 cs = ((Selectable *) this)->size*50;
240 short x, y;
241 if (overHeadTextPos.isempty()) {
242 x = Pos.x;
243 y = Pos.y;
244 } else {
245 x = overHeadTextPos.x;
246 y = overHeadTextPos.y;
249 Region rgn( x-100+screen.x, y - cs + screen.y, 200, 400 );
250 font->Print( rgn, ( unsigned char * ) overHeadText,
251 palette?palette:core->InfoTextPalette, IE_FONT_ALIGN_CENTER | IE_FONT_ALIGN_TOP, false );
252 gamedata->FreePalette(palette);
255 void Scriptable::DelayedEvent()
257 lastRunTime = core->GetGame()->Ticks;
260 void Scriptable::ImmediateEvent()
262 lastRunTime = 0;
265 bool Scriptable::IsPC() const
267 if(Type == ST_ACTOR) {
268 if (((Actor *) this)->GetStat(IE_EA) <= EA_CHARMED) {
269 return true;
272 return false;
275 void Scriptable::ExecuteScript(int scriptCount)
277 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
278 // but not in bg2 (kill Abazigal in ar6005)
279 if (core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE) {
280 if (! (core->HasFeature(GF_CUTSCENE_AREASCRIPTS) && Type == ST_AREA)) {
281 return;
285 if ((InternalFlags & IF_NOINT) && (CurrentAction || GetNextAction())) {
286 return;
289 if (!CurrentActionInterruptable) {
290 if (!CurrentAction && !GetNextAction()) abort();
291 return;
294 // only allow death scripts to run once, hopefully?
295 // this is probably terrible logic which needs moving elsewhere
296 if ((lastRunTime != 0) && (InternalFlags & IF_JUSTDIED)) {
297 return;
300 ieDword thisTime = core->GetGame()->Ticks;
301 if (( thisTime - lastRunTime ) < 1000) {
302 return;
305 lastDelay = lastRunTime;
306 lastRunTime = thisTime;
308 bool alive = false;
310 bool continuing = false, done = false;
311 for (int i = 0;i<scriptCount;i++) {
312 //disable AI script level for actors in party when the player disabled them
313 if ((i == AI_SCRIPT_LEVEL) && Type == ST_ACTOR && ((Actor *) this)->InParty) {
314 if (core->GetGame()->ControlStatus&CS_PARTY_AI) {
315 continue;
319 GameScript *Script = Scripts[i];
320 if (Script) {
321 alive |= Script->Update(&continuing, &done);
324 /* scripts are not concurrent, see WAITPC override script for example */
325 if (done) break;
327 if (alive && UnselectableTimer) {
328 UnselectableTimer--;
329 if (!UnselectableTimer) {
330 if (Type == ST_ACTOR) {
331 ((Actor *) this)->SetCircleSize();
335 InternalFlags &= ~IF_ONCREATION;
338 void Scriptable::AddAction(Action* aC)
340 if (!aC) {
341 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
342 return;
345 InternalFlags|=IF_ACTIVE;
346 aC->IncRef();
348 // attempt to handle 'instant' actions, from instant.ids, which run immediately
349 // when added if the action queue is empty, even on actors which are Held/etc
350 if (!CurrentAction && !GetNextAction()) {
351 if (actionflags[aC->actionID] & AF_INSTANT) {
352 CurrentAction = aC;
353 GameScript::ExecuteAction( this, CurrentAction );
354 return;
358 actionQueue.push_back( aC );
361 void Scriptable::AddActionInFront(Action* aC)
363 if (!aC) {
364 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName );
365 return;
367 InternalFlags|=IF_ACTIVE;
368 actionQueue.push_front( aC );
369 aC->IncRef();
372 Action* Scriptable::GetNextAction() const
374 if (actionQueue.size() == 0) {
375 return NULL;
377 return actionQueue.front();
380 Action* Scriptable::PopNextAction()
382 if (actionQueue.size() == 0) {
383 return NULL;
385 Action* aC = actionQueue.front();
386 actionQueue.pop_front();
387 return aC;
390 void Scriptable::ClearActions()
392 ReleaseCurrentAction();
393 for (unsigned int i = 0; i < actionQueue.size(); i++) {
394 Action* aC = actionQueue.front();
395 actionQueue.pop_front();
396 aC->Release();
398 actionQueue.clear();
399 WaitCounter = 0;
400 LastTarget = 0;
401 //clear the triggers as fast as possible when queue ended?
402 ClearTriggers();
404 if (Type == ST_ACTOR) {
405 Interrupt();
406 } else {
407 NoInterrupt();
411 void Scriptable::ReleaseCurrentAction()
413 if (CurrentAction) {
414 CurrentAction->Release();
415 CurrentAction = NULL;
418 CurrentActionState = 0;
419 CurrentActionTarget = 0;
420 CurrentActionInterruptable = true;
423 ieWord Scriptable::GetGlobalID()
425 if (Type == ST_ACTOR) {
426 Actor *actor = (Actor *) this;
427 return actor->globalID;
429 return 0;
432 void Scriptable::ProcessActions(bool force)
434 unsigned long thisTime = core->GetGame()->Ticks;
436 if (!force && (( thisTime - startTime ) < interval)) {
437 return;
439 startTime = thisTime;
440 if (WaitCounter) {
441 WaitCounter--;
442 if (WaitCounter) return;
445 while (true) {
446 CurrentActionInterruptable = true;
447 if (!CurrentAction) {
448 CurrentAction = PopNextAction();
450 if (!CurrentAction) {
451 ClearActions();
452 if (CutSceneId) {
453 CutSceneId = NULL;
455 //removing the triggers at the end of the
456 //block
457 //ClearTriggers();
458 break;
460 GameScript::ExecuteAction( this, CurrentAction );
461 //break execution in case of a Wait flag
462 if (WaitCounter) {
463 //clear triggers while waiting
464 //ClearTriggers();
465 break;
467 //break execution in case of blocking action
468 if (CurrentAction) {
469 break;
471 //break execution in case of movement
472 //we should not actually break here, or else fix waypoints
473 if (InMove()) {
474 break;
477 //most likely the best place to clear triggers is here
478 //queue is empty, or there is a looong action subject to break
479 ClearTriggers();
480 if (InternalFlags&IF_IDLE) {
481 Deactivate();
485 bool Scriptable::InMove() const
487 if (Type!=ST_ACTOR) {
488 return false;
490 Movable *me = (Movable *) this;
491 return me->GetNextStep()!=NULL;
494 void Scriptable::SetWait(unsigned long time)
496 WaitCounter = time;
499 unsigned long Scriptable::GetWait() const
501 return WaitCounter;
504 Scriptable *Scriptable::GetCutsceneID() const
506 return CutSceneId;
509 void Scriptable::LeaveDialog()
511 InternalFlags |=IF_WASINDIALOG;
514 //this ends cutscene mode for this Sender
515 void Scriptable::ClearCutsceneID()
517 CutSceneId = NULL;
518 InternalFlags &= ~IF_CUTSCENEID;
521 //if the cutsceneID doesn't exist, we simply skip the action
522 //because the cutscene script executer DOESN'T get hijacked
523 void Scriptable::SetCutsceneID(Scriptable *csid)
525 CutSceneId = csid;
526 InternalFlags |= IF_CUTSCENEID;
529 void Scriptable::Hide()
531 InternalFlags &=~(IF_VISIBLE);
534 void Scriptable::Unhide()
536 InternalFlags |= IF_VISIBLE;
539 void Scriptable::Interrupt()
541 InternalFlags &= ~IF_NOINT;
544 void Scriptable::NoInterrupt()
546 InternalFlags |= IF_NOINT;
549 //also turning off the idle flag so it won't run continuously
550 void Scriptable::Deactivate()
552 InternalFlags &=~(IF_ACTIVE|IF_IDLE);
555 //turning off the not interruptable flag, actions should reenable it themselves
556 //also turning off the idle flag
557 //heh, no, i wonder why did i touch the interruptable flag here
558 void Scriptable::Activate()
560 InternalFlags |= IF_ACTIVE;
561 InternalFlags &= ~IF_IDLE;
564 void Scriptable::PartyRested()
566 InternalFlags |=IF_PARTYRESTED;
569 ieDword Scriptable::GetInternalFlag()
571 return InternalFlags;
574 void Scriptable::InitTriggers()
576 tolist.clear();
577 bittriggers = 0;
580 void Scriptable::ClearTriggers()
582 for (TriggerObjects::iterator m = tolist.begin(); m != tolist.end (); m++) {
583 *(*m) = 0;
585 if (!bittriggers) {
586 return;
588 if (bittriggers & BT_DIE) {
589 InternalFlags &= ~IF_JUSTDIED;
591 if (bittriggers & BT_ONCREATION) {
592 InternalFlags &= ~IF_ONCREATION;
594 if (bittriggers & BT_BECAMEVISIBLE) {
595 InternalFlags &= ~IF_BECAMEVISIBLE;
597 if (bittriggers & BT_PARTYRESTED) {
598 InternalFlags &= ~IF_PARTYRESTED;
600 if (bittriggers & BT_WASINDIALOG) {
601 InternalFlags &= ~IF_WASINDIALOG;
603 if (bittriggers & BT_PARTYRESTED) {
604 InternalFlags &= ~IF_PARTYRESTED;
606 InitTriggers();
609 void Scriptable::SetBitTrigger(ieDword bittrigger)
611 bittriggers |= bittrigger;
614 void Scriptable::AddTrigger(ieDword *actorref)
616 tolist.push_back(actorref);
619 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef )
621 if (Type == ST_ACTOR) {
622 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
625 if (SpellHeader == -1) {
626 LastTargetPos.empty();
627 return;
630 if (LastTargetPos.isempty()) {
631 SpellHeader = -1;
632 return;
635 Spell* spl = gamedata->GetSpell( SpellResRef );
636 //create projectile from known spellheader
637 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
638 SpellHeader = -1;
639 if (pro) {
640 pro->SetCaster(GetGlobalID());
641 Point origin = Pos;
642 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
643 // try and make projectiles start from the right trap position
644 // see the traps in the duergar/assassin battle in bg2 dungeon
645 // see also function below - maybe we should fix Pos instead
646 origin = ((InfoPoint *)this)->TrapLaunch;
648 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
651 // caster - Casts spellname
652 char tmp[100];
653 const char* msg = core->GetString(displaymsg->GetStringReference(STR_ACTION_CAST), 0);
654 snprintf(tmp, sizeof(tmp), "%s %s", msg, core->GetString(spl->SpellName));
655 displaymsg->DisplayStringName(tmp, 0xffffff, this);
657 core->Autopause(AP_SPELLCAST);
659 LastTarget = 0;
660 LastTargetPos.empty();
663 static EffectRef fx_set_invisible_state_ref={"State:Invisible",NULL,-1};
665 void Scriptable::CastSpellEnd( const ieResRef SpellResRef )
667 if (Type == ST_ACTOR) {
668 ((Actor *) this)->SetStance(IE_ANI_CONJURE);
671 if (SpellHeader == -1) {
672 LastTarget = 0;
673 return;
675 if (!LastTarget) {
676 SpellHeader = -1;
677 return;
679 Spell* spl = gamedata->GetSpell( SpellResRef );
680 //create projectile from known spellheader
681 Projectile *pro = spl->GetProjectile(this, SpellHeader, LastTargetPos);
682 if (pro) {
683 pro->SetCaster(GetGlobalID());
684 Point origin = Pos;
685 if (Type == ST_TRIGGER || Type == ST_PROXIMITY) {
686 // try and make projectiles start from the right trap position
687 // see the traps in the duergar/assassin battle in bg2 dungeon
688 // see also function above - maybe we should fix Pos instead
689 origin = ((InfoPoint *)this)->TrapLaunch;
691 if (LastTarget) {
692 GetCurrentArea()->AddProjectile(pro, origin, LastTarget);
693 } else {
694 GetCurrentArea()->AddProjectile(pro, origin, LastTargetPos);
697 ieDword spellnum=ResolveSpellNumber( SpellResRef );
698 if (spellnum!=0xffffffff) {
699 area->SeeSpellCast(this, spellnum);
701 // caster - Casts spellname : target OR
702 // caster - spellname : target (repeating spells)
703 Scriptable *target = NULL;
704 char tmp[100];
705 const char* msg = core->GetString(displaymsg->GetStringReference(STR_ACTION_CAST), 0);
706 const char* spell = core->GetString(spl->SpellName);
707 if (LastTarget) {
708 target = area->GetActorByGlobalID(LastTarget);
710 if (stricmp(spell, "")) {
711 if (target) {
712 snprintf(tmp, sizeof(tmp), "%s %s : %s", msg, spell, target->GetName(-1));
713 } else {
714 snprintf(tmp, sizeof(tmp), "%s : %s", spell, GetName(-1));
716 displaymsg->DisplayStringName(tmp, 0xffffff, this);
719 if(LastTarget) {
720 if (target && (Type==ST_ACTOR) ) {
721 Actor *me = (Actor *) this;
722 target->LastSpellOnMe = spellnum;
723 target->LastCasterOnMe = me->GetID();
724 // don't cure invisibility if this is a self targetting invisibility spell
725 // like shadow door
726 //can't check GetEffectBlock, since it doesn't construct the queue for selftargetting spells
727 bool invis = false;
728 unsigned int opcode = EffectQueue::ResolveEffect(fx_set_invisible_state_ref);
729 for (unsigned int i=0; i < spl->ext_headers[SpellHeader].FeatureCount; i++) {
730 if (spl->GetExtHeader(SpellHeader)->features[i].Opcode == opcode) {
731 invis = true;
732 break;
735 if (invis && spl->GetExtHeader(SpellHeader)->Target == TARGET_SELF) {
736 //pass
737 } else {
738 me->CureInvisibility();
740 if (target!=this) { //FIXME: only dispel it for hostile spells
741 me->CureSanctuary();
747 core->Autopause(AP_SPELLCAST);
749 gamedata->FreeSpell(spl, SpellResRef, false);
750 LastTarget = 0;
751 LastTargetPos.empty();
754 //set target as point
755 //if spell needs to be depleted, do it
756 //if spell is illegal stop casting
757 int Scriptable::CastSpellPoint( const ieResRef SpellResRef, const Point &target, bool deplete, bool instant )
759 LastTarget = 0;
760 LastTargetPos.empty();
761 if (Type == ST_ACTOR) {
762 Actor *actor = (Actor *) this;
763 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
764 return -1;
767 LastTargetPos = target;
768 return SpellCast(SpellResRef, instant);
771 //set target as actor (if target isn't actor, use its position)
772 //if spell needs to be depleted, do it
773 //if spell is illegal stop casting
774 int Scriptable::CastSpell( const ieResRef SpellResRef, Scriptable* target, bool deplete, bool instant )
776 LastTarget = 0;
777 LastTargetPos.empty();
778 if (Type == ST_ACTOR) {
779 Actor *actor = (Actor *) this;
780 if (actor->HandleCastingStance(SpellResRef,deplete) ) {
781 return -1;
785 if (!target) target = this;
787 LastTargetPos = target->Pos;
788 if (target->Type==ST_ACTOR) {
789 LastTarget = target->GetGlobalID();
791 return SpellCast(SpellResRef, instant);
794 //start spellcasting (common part)
795 int Scriptable::SpellCast(const ieResRef SpellResRef, bool instant)
797 Spell* spl = gamedata->GetSpell( SpellResRef );
798 if (!spl) {
799 SpellHeader = -1;
800 return -1;
803 if (Type == ST_ACTOR) {
804 Actor *actor = (Actor *) this;
805 //The ext. index is here to calculate the casting time
806 int level = actor->GetXPLevel(true);
807 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
808 level += actor->CastingLevelBonus(level, spl->SpellType);
809 SpellHeader = spl->GetHeaderIndexFromLevel(level);
810 } else {
811 SpellHeader = 0;
814 SPLExtHeader *header = spl->GetExtHeader(SpellHeader);
815 int casting_time = (int)header->CastingTime;
816 // how does this work for non-actors exactly?
817 if (Type == ST_ACTOR) {
818 // The mental speed effect can shorten or lengthen the casting time.
819 casting_time -= (int)((Actor *) this)->GetStat(IE_MENTALSPEED);
820 if (casting_time < 0) casting_time = 0;
822 // this is a guess which seems approximately right so far
823 int duration = (casting_time*core->Time.round_size) / 10;
824 if (instant)
825 duration = 0;
827 //cfb
828 if (Type == ST_ACTOR) {
829 Actor *actor = (Actor *) this;
830 EffectQueue *fxqueue = spl->GetEffectBlock(this, this->Pos, -1);
831 spl->AddCastingGlow(fxqueue, duration);
832 fxqueue->SetOwner(actor);
833 fxqueue->AddAllEffects(actor, actor->Pos);
834 delete fxqueue;
837 gamedata->FreeSpell(spl, SpellResRef, false);
838 return duration;
841 bool Scriptable::TimerActive(ieDword ID)
843 if (ID>=MAX_TIMER) {
844 return false;
846 if (script_timers[ID]) {
847 return true;
849 return false;
852 bool Scriptable::TimerExpired(ieDword ID)
854 if (ID>=MAX_TIMER) {
855 return false;
857 if (script_timers[ID] && script_timers[ID] < core->GetGame()->GameTime) {
858 // expired timers become inactive after being checked
859 script_timers[ID] = 0;
860 return true;
862 return false;
865 void Scriptable::StartTimer(ieDword ID, ieDword expiration)
867 if (ID>=MAX_TIMER) {
868 printMessage("Scriptable", " ", RED);
869 printf("Timer id %d exceeded MAX_TIMER %d\n", ID, MAX_TIMER);
870 return;
872 script_timers[ID]= core->GetGame()->GameTime + expiration*AI_UPDATE_TIME;
875 /********************
876 * Selectable Class *
877 ********************/
879 Selectable::Selectable(ScriptableType type)
880 : Scriptable( type )
882 Selected = false;
883 Over = false;
884 size = 0;
885 cover = NULL;
886 circleBitmap[0] = NULL;
887 circleBitmap[1] = NULL;
890 void Selectable::SetSpriteCover(SpriteCover* c)
892 delete cover;
893 cover = c;
896 Selectable::~Selectable(void)
898 delete cover;
901 void Selectable::SetBBox(const Region &newBBox)
903 BBox = newBBox;
906 static const unsigned long tp_steps[8]={3,2,1,0,1,2,3,4};
908 void Selectable::DrawCircle(const Region &vp)
910 /* BG2 colours ground circles as follows:
911 dark green for unselected party members
912 bright green for selected party members
913 flashing green/white for a party member the mouse is over
914 bright red for enemies
915 yellow for panicked actors
916 flashing red/white for enemies the mouse is over
917 flashing cyan/white for neutrals the mouse is over
920 if (size<=0) {
921 return;
923 Color mix;
924 Color* col = &selectedColor;
925 Sprite2D* sprite = circleBitmap[0];
927 if (Selected) {
928 sprite = circleBitmap[1];
929 } else if (Over) {
930 //doing a time dependent flashing of colors
931 //if it is too fast, increase the 6 to 7
932 unsigned long step;
933 GetTime( step );
934 step = tp_steps [(step >> 6) & 7];
935 mix.a = overColor.a;
936 mix.r = (overColor.r*step+selectedColor.r*(8-step))/8;
937 mix.g = (overColor.g*step+selectedColor.g*(8-step))/8;
938 mix.b = (overColor.b*step+selectedColor.b*(8-step))/8;
939 col = &mix;
940 } else if (IsPC()) {
941 col = &overColor;
944 if (sprite) {
945 core->GetVideoDriver()->BlitSprite( sprite, Pos.x - vp.x, Pos.y - vp.y, true );
946 } else {
947 // for size >= 2, radii are (size-1)*16, (size-1)*12
948 // for size == 1, radii are 12, 9
949 int csize = (size - 1) * 4;
950 if (csize < 4) csize = 3;
951 core->GetVideoDriver()->DrawEllipse( (ieWord) (Pos.x - vp.x), (ieWord) (Pos.y - vp.y),
952 (ieWord) (csize * 4), (ieWord) (csize * 3), *col );
956 // Check if P is over our ground circle
957 bool Selectable::IsOver(const Point &P) const
959 int csize = size;
960 if (csize < 2) csize = 2;
962 int dx = P.x - Pos.x;
963 int dy = P.y - Pos.y;
965 // check rectangle first
966 if (dx < -(csize-1)*16 || dx > (csize-1)*16) return false;
967 if (dy < -(csize-1)*12 || dy > (csize-1)*12) return false;
969 // then check ellipse
970 int r = 9*dx*dx + 16*dy*dy; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
972 return (r <= 48*48*(csize-1)*(csize-1));
975 bool Selectable::IsSelected() const
977 return Selected == 1;
980 void Selectable::SetOver(bool over)
982 Over = over;
985 //don't call this function after rendering the cover and before the
986 //blitting of the sprite or bad things will happen :)
987 void Selectable::Select(int Value)
989 if (Selected!=0x80 || Value!=1) {
990 Selected = (ieWord) Value;
992 //forcing regeneration of the cover
993 SetSpriteCover(NULL);
996 void Selectable::SetCircle(int circlesize, const Color &color, Sprite2D* normal_circle, Sprite2D* selected_circle)
998 size = circlesize;
999 selectedColor = color;
1000 overColor.r = color.r >> 1;
1001 overColor.g = color.g >> 1;
1002 overColor.b = color.b >> 1;
1003 overColor.a = color.a;
1004 circleBitmap[0] = normal_circle;
1005 circleBitmap[1] = selected_circle;
1008 //used for creatures
1009 int Selectable::WantDither()
1011 //if dithering is disabled globally, don't do it
1012 if (core->FogOfWar&4) {
1013 return 0;
1015 //if actor is dead, dither it if polygon wants
1016 if (Selected&0x80) {
1017 return 1;
1019 //if actor is selected dither it
1020 if (Selected) {
1021 return 2;
1023 return 1;
1026 /***********************
1027 * Highlightable Class *
1028 ***********************/
1030 Highlightable::Highlightable(ScriptableType type)
1031 : Scriptable( type )
1033 outline = NULL;
1034 Highlight = false;
1035 Cursor = IE_CURSOR_NORMAL;
1036 KeyResRef[0] = 0;
1039 Highlightable::~Highlightable(void)
1041 if (outline) {
1042 delete( outline );
1046 bool Highlightable::IsOver(const Point &Pos) const
1048 if (!outline) {
1049 return false;
1051 return outline->PointIn( Pos );
1054 void Highlightable::DrawOutline() const
1056 if (!outline) {
1057 return;
1059 core->GetVideoDriver()->DrawPolyline( outline, outlineColor, true );
1062 void Highlightable::SetCursor(unsigned char CursorIndex)
1064 Cursor = CursorIndex;
1067 bool Highlightable::TryUnlock(Actor *actor, bool removekey) {
1068 const char *Key = GetKey();
1069 Actor *haskey = NULL;
1071 if (Key && actor->InParty) {
1072 Game *game = core->GetGame();
1073 //allow unlock when the key is on any partymember
1074 for (int idx = 0; idx < game->GetPartySize(false); idx++) {
1075 Actor *pc = game->FindPC(idx + 1);
1076 if (!pc) continue;
1078 if (pc->inventory.HasItem(Key,0) ) {
1079 haskey = pc;
1080 break;
1083 } else if (Key) {
1084 //actor is not in party, check only actor
1085 if (actor->inventory.HasItem(Key,0) ) {
1086 haskey = actor;
1090 if (!haskey) {
1091 return false;
1094 if (removekey) {
1095 CREItem *item = NULL;
1096 haskey->inventory.RemoveItem(Key,0,&item);
1097 //the item should always be existing!!!
1098 if (item) {
1099 delete item;
1103 return true;
1107 /*****************
1108 * Movable Class *
1109 *****************/
1111 Movable::Movable(ScriptableType type)
1112 : Selectable( type )
1114 Destination = Pos;
1115 Orientation = 0;
1116 NewOrientation = 0;
1117 StanceID = 0;
1118 path = NULL;
1119 step = NULL;
1120 timeStartStep = 0;
1121 lastFrame = NULL;
1122 Area[0] = 0;
1123 AttackMovements[0] = 100;
1124 AttackMovements[1] = 0;
1125 AttackMovements[2] = 0;
1128 Movable::~Movable(void)
1130 if (path) {
1131 ClearPath();
1135 int Movable::GetPathLength()
1137 PathNode *node = GetNextStep(0);
1138 int i = 0;
1139 while (node->Next) {
1140 i++;
1141 node = node->Next;
1143 return i;
1146 PathNode *Movable::GetNextStep(int x)
1148 if (!step) {
1149 DoStep((unsigned int) ~0);
1151 PathNode *node = step;
1152 while(node && x--) {
1153 node = node->Next;
1155 return node;
1158 Point Movable::GetMostLikelyPosition()
1160 if (!path) {
1161 return Pos;
1164 //actually, sometimes middle path would be better, if
1165 //we stand in Destination already
1166 int halfway = GetPathLength()/2;
1167 PathNode *node = GetNextStep(halfway);
1168 if (node) {
1169 return Point((ieWord) ((node->x*16)+8), (ieWord) ((node->y*12)+6) );
1171 return Destination;
1174 void Movable::SetStance(unsigned int arg)
1176 //don't modify stance from dead back to anything if the actor is dead
1177 if ((StanceID==IE_ANI_TWITCH || StanceID==IE_ANI_DIE) && (arg!=IE_ANI_TWITCH) ) {
1178 if (GetInternalFlag()&IF_REALLYDIED) {
1179 printMessage("Movable","Stance overridden by death\n", YELLOW);
1180 return;
1184 if (arg<MAX_ANIMS) {
1185 StanceID=(unsigned char) arg;
1187 if (StanceID == IE_ANI_ATTACK) {
1188 // Set stance to a random attack animation
1190 int random = rand()%100;
1191 if (random < AttackMovements[0]) {
1192 StanceID = IE_ANI_ATTACK_BACKSLASH;
1193 } else if (random < AttackMovements[0] + AttackMovements[1]) {
1194 StanceID = IE_ANI_ATTACK_SLASH;
1195 } else {
1196 StanceID = IE_ANI_ATTACK_JAB;
1200 } else {
1201 StanceID=IE_ANI_AWAKE; //
1202 printf("Tried to set invalid stance id (%u)\n", arg);
1206 void Movable::SetAttackMoveChances(ieWord *amc)
1208 AttackMovements[0]=amc[0];
1209 AttackMovements[1]=amc[1];
1210 AttackMovements[2]=amc[2];
1215 //this could be used for WingBuffet as well
1216 void Movable::MoveLine(int steps, int Pass, ieDword orient)
1218 //remove previous path
1219 ClearPath();
1220 if (!steps)
1221 return;
1222 Point p = Pos;
1223 p.x/=16;
1224 p.y/=14;
1225 path = area->GetLine( p, steps, orient, Pass );
1228 void AdjustPositionTowards(Point &Pos, ieDword time_diff, unsigned int walk_speed, short srcx, short srcy, short destx, short desty) {
1229 if (destx > srcx)
1230 Pos.x += ( unsigned short )
1231 ( ( ( ( ( destx * 16 ) + 8 ) - Pos.x ) * ( time_diff ) ) / walk_speed );
1232 else
1233 Pos.x -= ( unsigned short )
1234 ( ( ( Pos.x - ( ( destx * 16 ) + 8 ) ) * ( time_diff ) ) / walk_speed );
1235 if (desty > srcy)
1236 Pos.y += ( unsigned short )
1237 ( ( ( ( ( desty * 12 ) + 6 ) - Pos.y ) * ( time_diff ) ) / walk_speed );
1238 else
1239 Pos.y -= ( unsigned short )
1240 ( ( ( Pos.y - ( ( desty * 12 ) + 6 ) ) * ( time_diff ) ) / walk_speed );
1244 // returns whether we made all pending steps (so, false if we must be called again this tick)
1245 // we can't just do them all here because the caller might have to update searchmap etc
1246 bool Movable::DoStep(unsigned int walk_speed, ieDword time)
1248 if (!path) {
1249 return true;
1251 if (!time) time = core->GetGame()->Ticks;
1252 if (!walk_speed) {
1253 // zero speed: no movement
1254 timeStartStep = time;
1255 StanceID = IE_ANI_READY;
1256 return true;
1258 if (!step) {
1259 step = path;
1260 timeStartStep = time;
1261 } else if (step->Next && (( time - timeStartStep ) >= walk_speed)) {
1262 //printf("[New Step] : Orientation = %d\n", step->orient);
1263 step = step->Next;
1264 timeStartStep = timeStartStep + walk_speed;
1266 SetOrientation (step->orient, false);
1267 StanceID = IE_ANI_WALK;
1268 if ((Type == ST_ACTOR) && (InternalFlags & IF_RUNNING)) {
1269 StanceID = IE_ANI_RUN;
1271 Pos.x = ( step->x * 16 ) + 8;
1272 Pos.y = ( step->y * 12 ) + 6;
1273 if (!step->Next) {
1274 // we reached our destination, we are done
1275 ClearPath();
1276 NewOrientation = Orientation;
1277 //since clearpath no longer sets currentaction to NULL
1278 //we set it here
1279 //no we don't, action is responsible for releasing itself
1280 //ReleaseCurrentAction();
1281 return true;
1283 if (( time - timeStartStep ) >= walk_speed) {
1284 // we didn't finish all pending steps, yet
1285 return false;
1287 AdjustPositionTowards(Pos, time - timeStartStep, walk_speed, step->x, step->y, step->Next->x, step->Next->y);
1288 return true;
1291 void Movable::AddWayPoint(const Point &Des)
1293 if (!path) {
1294 WalkTo(Des);
1295 return;
1297 Destination = Des;
1298 //it is tempting to use 'step' here, as it could
1299 //be about half of the current path already
1300 PathNode *endNode = path;
1301 while(endNode->Next) {
1302 endNode = endNode->Next;
1304 Point p(endNode->x, endNode->y);
1305 area->ClearSearchMapFor(this);
1306 PathNode *path2 = area->FindPath( p, Des, size );
1307 endNode->Next = path2;
1308 //probably it is wise to connect it both ways?
1309 path2->Parent = endNode;
1312 void Movable::FixPosition()
1314 if (Type!=ST_ACTOR) {
1315 return;
1317 Actor *actor = (Actor *) this;
1318 if (actor->GetStat(IE_DONOTJUMP)&DNJ_BIRD ) {
1319 return;
1321 //before fixposition, you should remove own shadow
1322 area->ClearSearchMapFor(this);
1323 Pos.x/=16;
1324 Pos.y/=12;
1325 GetCurrentArea()->AdjustPosition(Pos);
1326 Pos.x=Pos.x*16+8;
1327 Pos.y=Pos.y*12+6;
1330 void Movable::WalkTo(const Point &Des, int distance)
1332 Point from;
1334 // maybe caller should be responsible for this
1335 if ((Des.x/16 == Pos.x/16) && (Des.y/12 == Pos.y/12)) {
1336 ClearPath();
1337 return;
1340 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1341 PathNode *prev_step = NULL;
1342 unsigned char old_stance = StanceID;
1343 if (step && step->Next) {
1344 // don't interrupt in the middle of a step; path from the next one
1345 prev_step = new PathNode(*step);
1346 from.x = ( step->Next->x * 16 ) + 8;
1347 from.y = ( step->Next->y * 12 ) + 6;
1350 ClearPath();
1351 if (!prev_step) {
1352 FixPosition();
1353 from = Pos;
1355 area->ClearSearchMapFor(this);
1356 if (distance) {
1357 path = area->FindPathNear( from, Des, size, distance );
1358 } else {
1359 path = area->FindPath( from, Des, size, distance );
1361 //ClearPath sets destination, so Destination must be set after it
1362 //also we should set Destination only if there is a walkable path
1363 if (path) {
1364 Destination = Des;
1366 if (prev_step) {
1367 // we want to smoothly continue, please
1368 // this all needs more thought! but it seems to work okay
1369 StanceID = old_stance;
1371 if (path->Next) {
1372 // this is a terrible hack to make up for the
1373 // pathfinder orienting the first node wrong
1374 // should be fixed in pathfinder and not here!
1375 Point next, follow;
1376 next.x = path->x; next.y = path->y;
1377 follow.x = path->Next->x;
1378 follow.y = path->Next->y;
1379 path->orient = GetOrient(follow, next);
1382 // then put the prev_step at the beginning of the path
1383 prev_step->Next = path;
1384 path->Parent = prev_step;
1385 path = prev_step;
1387 step = path;
1389 } else {
1390 // pathing failed
1391 if (prev_step) {
1392 delete( prev_step );
1393 FixPosition();
1398 void Movable::RunAwayFrom(const Point &Des, int PathLength, int flags)
1400 ClearPath();
1401 area->ClearSearchMapFor(this);
1402 path = area->RunAway( Pos, Des, size, PathLength, flags );
1405 void Movable::RandomWalk(bool can_stop, bool run)
1407 if (path) {
1408 return;
1410 //if not continous random walk, then stops for a while
1411 if (can_stop && (rand()&3) ) {
1412 SetWait((rand()&7)+7);
1413 return;
1415 if (run) {
1416 InternalFlags|=IF_RUNNING;
1418 //the commenting-out of the clear search map call was removed in 0.4.0
1419 //if you want to put it back for some reason, check
1420 //if the searchmap is not eaten up
1421 area->ClearSearchMapFor(this);
1422 Point p = Pos;
1424 //selecting points around a circle's edge around actor (didn't work better)
1425 //int x = core->Roll(1,100,-50);
1426 //p.x+=x;
1427 //p.y+=(int) sqrt(100-x*x);
1429 //selecting points in a square around actor
1430 p.x+=core->Roll(1,50,-25);
1431 p.y+=core->Roll(1,50,-25);
1432 //the 5th parameter is controlling the orientation of the actor
1433 //0 - back away, 1 - face direction
1434 path = area->RunAway( Pos, p, size, 50, 1 );
1437 void Movable::MoveTo(const Point &Des)
1439 area->ClearSearchMapFor(this);
1440 Pos = Des;
1441 Destination = Des;
1442 if (BlocksSearchMap()) {
1443 area->BlockSearchMap( Pos, size, IsPC()?PATH_MAP_PC:PATH_MAP_NPC);
1447 void Movable::ClearPath()
1449 //this is to make sure attackers come to us
1450 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1451 //is set before ClearPath
1452 Destination = Pos;
1453 if (StanceID==IE_ANI_WALK || StanceID==IE_ANI_RUN) {
1454 StanceID = IE_ANI_AWAKE;
1456 InternalFlags&=~IF_NORECTICLE;
1457 PathNode* thisNode = path;
1458 while (thisNode) {
1459 PathNode* nextNode = thisNode->Next;
1460 delete( thisNode );
1461 thisNode = nextNode;
1463 path = NULL;
1464 step = NULL;
1465 //don't call ReleaseCurrentAction
1468 void Movable::DrawTargetPoint(const Region &vp)
1470 if (!path || !Selected || (InternalFlags&IF_NORECTICLE) )
1471 return;
1473 // recticles are never drawn in cutscenes
1474 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE))
1475 return;
1477 // generates "step" from sequence 3 2 1 0 1 2 3 4
1478 // updated each 1/15 sec
1479 unsigned long step;
1480 GetTime( step );
1481 step = tp_steps [(step >> 6) & 7];
1483 step = step + 1;
1484 int csize = (size - 1) * 4;
1485 if (csize < 4) csize = 3;
1487 /* segments should not go outside selection radius */
1488 unsigned short xradius = (csize * 4) - 5;
1489 unsigned short yradius = (csize * 3) - 5;
1490 ieWord xcentre = (ieWord) (Destination.x - vp.x);
1491 ieWord ycentre = (ieWord) (Destination.y - vp.y);
1493 // TODO: 0.5 and 0.7 are pretty much random values
1494 // right segment
1495 core->GetVideoDriver()->DrawEllipseSegment( xcentre + step, ycentre, xradius,
1496 yradius, selectedColor, -0.5, 0.5 );
1497 // top segment
1498 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre - step, xradius,
1499 yradius, selectedColor, -0.7 - M_PI_2, 0.7 - M_PI_2 );
1500 // left segment
1501 core->GetVideoDriver()->DrawEllipseSegment( xcentre - step, ycentre, xradius,
1502 yradius, selectedColor, -0.5 - M_PI, 0.5 - M_PI );
1503 // bottom segment
1504 core->GetVideoDriver()->DrawEllipseSegment( xcentre, ycentre + step, xradius,
1505 yradius, selectedColor, -0.7 - M_PI - M_PI_2, 0.7 - M_PI - M_PI_2 );
1508 /**********************
1509 * Tiled Object Class *
1510 **********************/
1512 TileObject::TileObject()
1514 opentiles = NULL;
1515 opencount = 0;
1516 closedtiles = NULL;
1517 closedcount = 0;
1518 Flags = 0;
1521 TileObject::~TileObject()
1523 if (opentiles) {
1524 free( opentiles );
1526 if (closedtiles) {
1527 free( closedtiles );
1531 void TileObject::SetOpenTiles(unsigned short* Tiles, int cnt)
1533 if (opentiles) {
1534 free( opentiles );
1536 opentiles = Tiles;
1537 opencount = cnt;
1540 void TileObject::SetClosedTiles(unsigned short* Tiles, int cnt)
1542 if (closedtiles) {
1543 free( closedtiles );
1545 closedtiles = Tiles;
1546 closedcount = cnt;
1549 /**************
1550 * Door Class *
1551 **************/
1553 Door::Door(TileOverlay* Overlay)
1554 : Highlightable( ST_DOOR )
1556 tiles = NULL;
1557 tilecount = 0;
1558 Flags = 0;
1559 open = NULL;
1560 closed = NULL;
1561 open_ib = NULL;
1562 oibcount = 0;
1563 closed_ib = NULL;
1564 cibcount = 0;
1565 OpenSound[0] = 0;
1566 CloseSound[0] = 0;
1567 LockSound[0] = 0;
1568 UnLockSound[0] = 0;
1569 overlay = Overlay;
1570 LinkedInfo[0] = 0;
1571 OpenStrRef = (ieDword) -1;
1574 Door::~Door(void)
1576 if (Flags&DOOR_OPEN) {
1577 if (closed) {
1578 delete( closed );
1580 } else {
1581 if (open) {
1582 delete( open );
1585 if (tiles) {
1586 free( tiles );
1588 if (open_ib) {
1589 free( open_ib );
1591 if (closed_ib) {
1592 free( closed_ib );
1596 void Door::ImpedeBlocks(int count, Point *points, unsigned char value)
1598 for(int i = 0;i<count;i++) {
1599 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_NOTDOOR;
1600 area->SearchMap->SetAt( points[i].x, points[i].y, (tmp|value) );
1604 void Door::UpdateDoor()
1606 if (Flags&DOOR_OPEN) {
1607 outline = open;
1608 } else {
1609 outline = closed;
1611 // update the Scriptable position
1612 Pos.x = outline->BBox.x + outline->BBox.w/2;
1613 Pos.y = outline->BBox.y + outline->BBox.h/2;
1615 unsigned char oval, cval;
1616 oval = PATH_MAP_IMPASSABLE;
1617 if (Flags & DOOR_TRANSPARENT) {
1618 cval = PATH_MAP_DOOR_TRANSPARENT;
1620 else {
1621 cval = PATH_MAP_DOOR_OPAQUE;
1623 if (Flags &DOOR_OPEN) {
1624 ImpedeBlocks(cibcount, closed_ib, 0);
1625 ImpedeBlocks(oibcount, open_ib, cval);
1627 else {
1628 ImpedeBlocks(oibcount, open_ib, 0);
1629 ImpedeBlocks(cibcount, closed_ib, cval);
1632 InfoPoint *ip = area->TMap->GetInfoPoint(LinkedInfo);
1633 if (ip) {
1634 if (Flags&DOOR_OPEN) ip->Flags&=~INFO_DOOR;
1635 else ip->Flags|=INFO_DOOR;
1639 void Door::ToggleTiles(int State, int playsound)
1641 int i;
1642 int state;
1644 if (State) {
1645 state = !closedIndex;
1646 if (playsound && ( OpenSound[0] != '\0' ))
1647 core->GetAudioDrv()->Play( OpenSound );
1648 } else {
1649 state = closedIndex;
1650 if (playsound && ( CloseSound[0] != '\0' ))
1651 core->GetAudioDrv()->Play( CloseSound );
1653 for (i = 0; i < tilecount; i++) {
1654 overlay->tiles[tiles[i]]->tileIndex = (ieByte) state;
1657 //set door_open as state
1658 Flags = (Flags & ~DOOR_OPEN) | (State == !core->HasFeature(GF_REVERSE_DOOR) );
1661 //this is the short name (not the scripting name)
1662 void Door::SetName(const char* name)
1664 strnlwrcpy( ID, name, 8 );
1667 void Door::SetTiles(unsigned short* Tiles, int cnt)
1669 if (tiles) {
1670 free( tiles );
1672 tiles = Tiles;
1673 tilecount = cnt;
1676 void Door::SetDoorLocked(int Locked, int playsound)
1678 if (Locked) {
1679 if (Flags & DOOR_LOCKED) return;
1680 Flags|=DOOR_LOCKED;
1681 if (playsound && ( LockSound[0] != '\0' ))
1682 core->GetAudioDrv()->Play( LockSound );
1684 else {
1685 if (!(Flags & DOOR_LOCKED)) return;
1686 Flags&=~DOOR_LOCKED;
1687 if (playsound && ( UnLockSound[0] != '\0' ))
1688 core->GetAudioDrv()->Play( UnLockSound );
1692 int Door::IsOpen() const
1694 int ret = core->HasFeature(GF_REVERSE_DOOR);
1695 if (Flags&DOOR_OPEN) {
1696 ret = !ret;
1698 return ret;
1701 //also mark actors to fix position
1702 bool Door::BlockedOpen(int Open, int ForceOpen)
1704 bool blocked;
1705 int count;
1706 Point *points;
1708 blocked = false;
1709 if (Open) {
1710 count = oibcount;
1711 points = open_ib;
1712 } else {
1713 count = cibcount;
1714 points = closed_ib;
1716 //getting all impeded actors flagged for jump
1717 Region rgn;
1718 rgn.w = 16;
1719 rgn.h = 12;
1720 for(int i = 0;i<count;i++) {
1721 Actor** ab;
1722 rgn.x = points[i].x*16;
1723 rgn.y = points[i].y*12;
1724 unsigned char tmp = area->SearchMap->GetAt( points[i].x, points[i].y ) & PATH_MAP_ACTOR;
1725 if (tmp) {
1726 int ac = area->GetActorInRect(ab, rgn, false);
1727 while(ac--) {
1728 if (ab[ac]->GetBase(IE_DONOTJUMP)) {
1729 continue;
1731 ab[ac]->SetBase(IE_DONOTJUMP, DNJ_JUMP);
1732 blocked = true;
1734 if (ab) {
1735 free(ab);
1740 if ((Flags&DOOR_SLIDE) || ForceOpen) {
1741 return false;
1743 return blocked;
1746 void Door::SetDoorOpen(int Open, int playsound, ieDword ID)
1748 if (playsound) {
1749 //the door cannot be blocked when opening,
1750 //but the actors will be pushed
1751 //BlockedOpen will mark actors to be pushed
1752 if (BlockedOpen(Open,0) && !Open) {
1753 //clear up the blocking actors
1754 area->JumpActors(false);
1755 return;
1757 area->JumpActors(true);
1759 if (Open) {
1760 LastEntered = ID; //used as lastOpener
1762 // in PS:T, opening a door does not unlock it
1763 if (!core->HasFeature(GF_REVERSE_DOOR)) {
1764 SetDoorLocked(false,playsound);
1766 } else {
1767 LastTriggerObject = LastTrigger = ID; //used as lastCloser
1769 ToggleTiles(Open, playsound);
1770 //synchronising other data with the door state
1771 UpdateDoor();
1772 area->ActivateWallgroups(open_wg_index, open_wg_count, Flags&DOOR_OPEN);
1773 area->ActivateWallgroups(closed_wg_index, closed_wg_count, !(Flags&DOOR_OPEN));
1776 bool Door::TryUnlock(Actor *actor) {
1777 if (!(Flags&DOOR_LOCKED)) return true;
1779 // don't remove key in PS:T!
1780 bool removekey = !core->HasFeature(GF_REVERSE_DOOR) && Flags&DOOR_KEY;
1781 return Highlightable::TryUnlock(actor, removekey);
1784 void Door::TryDetectSecret(int skill)
1786 if (Type != ST_DOOR) return;
1787 if (Visible()) return;
1788 if (skill > (signed)DiscoveryDiff) {
1789 Flags |= DOOR_FOUND;
1790 core->PlaySound(DS_FOUNDSECRET);
1794 // return true if the door isn't secret or if it is, but was already discovered
1795 bool Door::Visible()
1797 return (!(Flags & DOOR_SECRET) || (Flags & DOOR_FOUND));
1800 void Door::SetPolygon(bool Open, Gem_Polygon* poly)
1802 if (Open) {
1803 if (open)
1804 delete( open );
1805 open = poly;
1806 } else {
1807 if (closed)
1808 delete( closed );
1809 closed = poly;
1813 void Door::SetNewOverlay(TileOverlay *Overlay) {
1814 overlay = Overlay;
1815 ToggleTiles(IsOpen(), false);
1818 void Highlightable::SetTrapDetected(int x)
1820 if(x == TrapDetected)
1821 return;
1822 TrapDetected = x;
1823 if(TrapDetected) {
1824 core->Autopause(AP_TRAP);
1828 void Highlightable::TryDisarm(Actor *actor)
1830 if (!Trapped || !TrapDetected) return;
1832 LastTriggerObject = LastTrigger = actor->GetID();
1833 int skill = actor->GetStat(IE_TRAPS);
1835 if (skill/2+core->Roll(1,skill/2,0)>TrapRemovalDiff) {
1836 LastDisarmed = actor->GetID();
1837 //trap removed
1838 Trapped = 0;
1839 displaymsg->DisplayConstantStringName(STR_DISARM_DONE, 0xd7d7be, actor);
1840 int xp = actor->CalculateExperience(XP_DISARM, actor->GetXPLevel(1));
1841 Game *game = core->GetGame();
1842 game->ShareXP(xp, SX_DIVIDE);
1843 } else {
1844 displaymsg->DisplayConstantStringName(STR_DISARM_FAIL, 0xd7d7be, actor);
1845 TriggerTrap(skill, LastTrigger);
1847 ImmediateEvent();
1850 void Door::TryPickLock(Actor *actor)
1852 if (LockDifficulty == 100) {
1853 if (OpenStrRef != (ieDword)-1) {
1854 displaymsg->DisplayStringName(OpenStrRef, 0xbcefbc, actor, IE_STR_SOUND|IE_STR_SPEECH);
1855 } else {
1856 displaymsg->DisplayConstantStringName(STR_DOOR_NOPICK, 0xbcefbc, actor);
1858 return;
1860 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
1861 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
1862 LastPickLockFailed = actor->GetID();
1863 return;
1865 SetDoorLocked( false, true);
1866 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
1867 LastUnlocked = actor->GetID();
1868 ImmediateEvent();
1869 int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
1870 Game *game = core->GetGame();
1871 game->ShareXP(xp, SX_DIVIDE);
1874 void Door::TryBashLock(Actor *actor)
1876 //Get the strength bonus agains lock difficulty
1877 int str = actor->GetStat(IE_STR);
1878 int strEx = actor->GetStat(IE_STREXTRA);
1879 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
1880 unsigned int roll = actor->LuckyRoll(1, 10, bonus, 0);
1882 if(roll < LockDifficulty || LockDifficulty == 100) {
1883 displaymsg->DisplayConstantStringName(STR_DOORBASH_FAIL, 0xbcefbc, actor);
1884 return;
1887 displaymsg->DisplayConstantStringName(STR_DOORBASH_DONE, 0xd7d7be, actor);
1888 SetDoorLocked(false, true);
1889 //Is this really useful ?
1890 LastUnlocked = actor->GetID();
1891 ImmediateEvent();
1894 void Door::DebugDump() const
1896 printf( "Debugdump of Door %s:\n", GetScriptName() );
1897 printf( "Door Open: %s\n", YESNO(IsOpen()));
1898 printf( "Door Locked: %s\n", YESNO(Flags&DOOR_LOCKED));
1899 printf( "Door Trapped: %s\n", YESNO(Trapped));
1900 if (Trapped) {
1901 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags&DOOR_RESET), YESNO(Flags&DOOR_DETECTABLE) );
1903 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags&DOOR_SECRET),YESNO(Flags&DOOR_FOUND));
1904 const char *Key = GetKey();
1905 const char *name = "NONE";
1906 if (Scripts[0]) {
1907 name = Scripts[0]->GetName();
1909 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name, Key?Key:"NONE", YESNO(Flags&DOOR_KEY), Dialog );
1912 /*******************
1913 * InfoPoint Class *
1914 *******************/
1916 InfoPoint::InfoPoint(void)
1917 : Highlightable( ST_TRIGGER )
1919 Destination[0] = 0;
1920 EntranceName[0] = 0;
1921 Flags = 0;
1922 TrapDetectionDiff = 0;
1923 TrapRemovalDiff = 0;
1924 TrapDetected = 0;
1925 TrapLaunch.empty();
1926 Dialog[0] = 0;
1929 InfoPoint::~InfoPoint(void)
1933 //checks if the actor may use this travel trigger
1934 //bit 1 : can use
1935 //bit 2 : whole team
1936 int InfoPoint::CheckTravel(Actor *actor)
1938 if (Flags&TRAP_DEACTIVATED) return CT_CANTMOVE;
1939 if (!actor->InParty && (Flags&TRAVEL_NONPC) ) return CT_CANTMOVE;
1940 if (actor->InParty && (Flags&TRAVEL_PARTY) ) {
1941 if (core->HasFeature(GF_TEAM_MOVEMENT) || core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE) ) {
1942 return CT_WHOLE;
1944 return CT_GO_CLOSER;
1946 if(actor->IsSelected() ) {
1947 if(core->GetGame()->EveryoneNearPoint(actor->GetCurrentArea(), actor->Pos, ENP_CANMOVE|ENP_ONLYSELECT) ) {
1948 return CT_MOVE_SELECTED;
1950 return CT_SELECTED;
1952 return CT_ACTIVE;
1955 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1956 //skill is the all around modified trap detection skill
1957 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1958 void Highlightable::DetectTrap(int skill)
1960 if (!CanDetectTrap()) return;
1961 if (!Scripts[0]) return;
1962 if ((skill>=100) && (skill!=256) ) skill = 100;
1963 if (skill/2+core->Roll(1,skill/2,0)>TrapDetectionDiff) {
1964 SetTrapDetected(1); //probably could be set to the player #?
1968 bool Highlightable::PossibleToSeeTrap() const
1970 return CanDetectTrap();
1973 bool InfoPoint::PossibleToSeeTrap() const
1975 // Only detectable trap-type infopoints.
1976 return (CanDetectTrap() && (Type == ST_PROXIMITY) );
1979 bool InfoPoint::CanDetectTrap() const
1981 // Traps can be detected on all types of infopoint, as long
1982 // as the trap is detectable and isn't deactivated.
1983 return ((Flags&TRAP_DETECTABLE) && !(Flags&TRAP_DEACTIVATED));
1986 // returns true if the infopoint is a PS:T portal
1987 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1988 bool InfoPoint::IsPortal() const
1990 if (Type!=ST_TRAVEL) return false;
1991 if (Cursor != IE_CURSOR_PORTAL) return false;
1992 return core->HasFeature(GF_REVERSE_DOOR);
1995 //trap that is visible on screen (marked by red)
1996 //if TrapDetected is a bitflag, we could show traps selectively for
1997 //players, really nice for multiplayer
1998 bool Highlightable::VisibleTrap(int see_all) const
2000 if (!Trapped) return false;
2001 if (!PossibleToSeeTrap()) return false;
2002 if (!Scripts[0]) return false;
2003 if (see_all) return true;
2004 if (TrapDetected ) return true;
2005 return false;
2008 //trap that will fire now
2009 bool Highlightable::TriggerTrap(int /*skill*/, ieDword ID)
2011 if (!Trapped) {
2012 return false;
2014 //actually this could be script name[0]
2015 if (!Scripts[0]) {
2016 return false;
2018 LastTriggerObject = LastTrigger = LastEntered = ID;
2019 ImmediateEvent();
2020 if (!TrapResets()) {
2021 Trapped = false;
2023 return true;
2026 //trap that will fire now
2027 bool InfoPoint::TriggerTrap(int skill, ieDword ID)
2029 if (Type!=ST_PROXIMITY) {
2030 return true;
2032 if (Flags&TRAP_DEACTIVATED) {
2033 return false;
2035 if (!Trapped) {
2036 // we have to set Entered somewhere, here seems best..
2037 LastEntered = ID;
2038 return true;
2039 } else if (Highlightable::TriggerTrap(skill, ID)) {
2040 if (!Trapped) {
2041 Flags|=TRAP_DEACTIVATED;
2043 // ok, so this is a pain. Entered() trigger checks Trapped,
2044 // so it needs to be kept set. how to do this right?
2045 Trapped = true;
2046 return true;
2048 return false;
2051 bool InfoPoint::Entered(Actor *actor)
2053 if (outline->PointIn( actor->Pos ) ) {
2054 //don't trigger again for this actor
2055 if (!(actor->GetInternalFlag()&IF_INTRAP)) {
2056 goto check;
2059 // why is this here? actors which aren't *in* a trap get IF_INTRAP
2060 // repeatedly unset, so this triggers again and again and again.
2061 // i disabled it for ST_PROXIMITY for now..
2062 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
2063 goto check;
2065 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
2066 if (Type == ST_TRAVEL && PersonalDistance(TrapLaunch, actor)<MAX_OPERATING_DISTANCE) {
2067 goto check;
2069 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
2070 // for distance from the outline for travel regions instead?
2071 if (Type == ST_TRAVEL && PersonalDistance(TalkPos, actor)<MAX_OPERATING_DISTANCE) {
2072 goto check;
2074 if (Flags&TRAP_USEPOINT) {
2075 if (PersonalDistance(UsePoint, actor)<MAX_OPERATING_DISTANCE) {
2076 goto check;
2079 return false;
2080 check:
2081 if (Type==ST_TRAVEL) {
2082 return true;
2085 if (actor->InParty || (Flags&TRAP_NPC) ) {
2086 //no need to avoid a travel trigger
2088 //skill?
2089 if (TriggerTrap(0, actor->GetID()) ) {
2090 return true;
2093 return false;
2096 void InfoPoint::DebugDump() const
2098 switch (Type) {
2099 case ST_TRIGGER:
2100 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2101 break;
2102 case ST_PROXIMITY:
2103 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2104 break;
2105 case ST_TRAVEL:
2106 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2107 break;
2108 default:
2109 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2110 break;
2112 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected, YESNO(Trapped));
2113 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2114 TrapRemovalDiff );
2115 const char *name = "NONE";
2116 if (Scripts[0]) {
2117 name = Scripts[0]->GetName();
2119 printf( "Script: %s, Key: %s, Dialog: %s\n", name, KeyResRef, Dialog );
2120 printf( "Active: %s\n", YESNO(InternalFlags&IF_ACTIVE));
2123 /*******************
2124 * Container Class *
2125 *******************/
2127 Container::Container(void)
2128 : Highlightable( ST_CONTAINER )
2130 Type = 0;
2131 LockDifficulty = 0;
2132 Flags = 0;
2133 TrapDetectionDiff = 0;
2134 TrapRemovalDiff = 0;
2135 Trapped = 0;
2136 TrapDetected = 0;
2137 inventory.SetInventoryType(INVENTORY_HEAP);
2138 // NULL should be 0 for this
2139 memset (groundicons, 0, sizeof(groundicons) );
2140 groundiconcover = 0;
2143 void Container::FreeGroundIcons()
2145 Video* video = core->GetVideoDriver();
2147 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2148 if (groundicons[i]) {
2149 video->FreeSprite( groundicons[i] );
2150 groundicons[i]=NULL;
2153 delete groundiconcover;
2154 groundiconcover = 0;
2157 Container::~Container()
2159 FreeGroundIcons();
2162 void Container::DrawPile(bool highlight, Region screen, Color tint)
2164 Video* video = core->GetVideoDriver();
2165 CreateGroundIconCover();
2166 for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2167 if (groundicons[i]) {
2168 //draw it with highlight
2169 video->BlitGameSprite(groundicons[i],
2170 screen.x + Pos.x, screen.y + Pos.y,
2171 BLIT_TINTED | (highlight ? 0:BLIT_NOSHADOW),
2172 tint, groundiconcover);
2177 // create the SpriteCover for the groundicons
2178 void Container::CreateGroundIconCover()
2180 int xpos = 0;
2181 int ypos = 0;
2182 int width = 0;
2183 int height = 0;
2185 int i; //msvc6.0
2186 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2187 if (groundicons[i]) {
2188 Sprite2D& spr = *groundicons[i];
2189 if (xpos < spr.XPos) {
2190 width += spr.XPos - xpos;
2191 xpos = spr.XPos;
2193 if (ypos < spr.YPos) {
2194 height += spr.YPos - ypos;
2195 ypos = spr.YPos;
2197 if (width-xpos < spr.Width-spr.XPos) {
2198 width = spr.Width-spr.XPos+xpos;
2200 if (height-ypos < spr.Height-spr.YPos) {
2201 height = spr.Height-spr.YPos+ypos;
2206 if (!groundiconcover ||
2207 !groundiconcover->Covers(Pos.x, Pos.y, xpos, ypos, width, height))
2209 delete groundiconcover;
2210 groundiconcover = 0;
2211 if (width*height > 0) {
2212 groundiconcover = GetCurrentArea()->BuildSpriteCover
2213 (Pos.x, Pos.y, xpos, ypos, width, height, WantDither());
2217 #ifndef NDEBUG
2218 // TODO: remove this checking code eventually
2219 for (i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
2220 if (groundicons[i]) {
2221 Sprite2D& spr = *groundicons[i];
2222 assert(groundiconcover->Covers(Pos.x, Pos.y, spr.XPos, spr.YPos, spr.Width, spr.Height));
2225 #endif
2228 void Container::SetContainerLocked(bool lock)
2230 if (lock) {
2231 Flags|=CONT_LOCKED;
2232 } else {
2233 Flags&=~CONT_LOCKED;
2237 //This function doesn't exist in the original IE, destroys a container
2238 //turning it to a ground pile
2239 void Container::DestroyContainer()
2241 //it is already a groundpile?
2242 if (Type == IE_CONTAINER_PILE)
2243 return;
2244 Type = IE_CONTAINER_PILE;
2245 RefreshGroundIcons();
2246 //probably we should stop the script or trigger it, whatever
2249 //Takes an item from the container's inventory and returns its pointer
2250 CREItem *Container::RemoveItem(unsigned int idx, unsigned int count)
2252 CREItem *ret = inventory.RemoveItem(idx, count);
2253 //we just took the 3. or less item, groundpile changed
2254 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<3)) {
2255 RefreshGroundIcons();
2257 return ret;
2260 //Adds an item to the container's inventory
2261 //containers always have enough capacity (so far), thus we always return 2
2262 int Container::AddItem(CREItem *item)
2264 inventory.AddItem(item);
2265 //we just added a 3. or less item, groundpile changed
2266 if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<4)) {
2267 RefreshGroundIcons();
2269 return 2;
2272 void Container::RefreshGroundIcons()
2274 int i = inventory.GetSlotCount();
2275 if (i>MAX_GROUND_ICON_DRAWN)
2276 i = MAX_GROUND_ICON_DRAWN;
2277 FreeGroundIcons();
2278 while (i--) {
2279 CREItem *slot = inventory.GetSlotItem(i); //borrowed reference
2280 Item *itm = gamedata->GetItem( slot->ItemResRef ); //cached reference
2281 //well, this is required in PST, needs more work if some other
2282 //game is broken by not using -1,0
2283 groundicons[i] = gamedata->GetBAMSprite( itm->GroundIcon, 0, 0 );
2284 gamedata->FreeItem( itm, slot->ItemResRef ); //decref
2288 //used for ground piles
2289 int Container::WantDither()
2291 //if pile is highlighted, always dither it
2292 if (Highlight) {
2293 return 2; //dither me if you want
2295 //if pile isn't highlighted, dither it if the polygon wants
2296 return 1;
2299 int Container::IsOpen() const
2301 if (Flags&CONT_LOCKED) {
2302 return false;
2304 return true;
2307 void Container::TryPickLock(Actor *actor)
2309 if (LockDifficulty == 100) {
2310 if (OpenFail != (ieDword)-1) {
2311 displaymsg->DisplayStringName(OpenFail, 0xbcefbc, actor, IE_STR_SOUND|IE_STR_SPEECH);
2312 } else {
2313 displaymsg->DisplayConstantStringName(STR_CONT_NOPICK, 0xbcefbc, actor);
2315 return;
2317 if (actor->GetStat(IE_LOCKPICKING)<LockDifficulty) {
2318 displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, 0xbcefbc, actor);
2319 LastPickLockFailed = actor->GetID();
2320 return;
2322 SetContainerLocked(false);
2323 displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, 0xd7d7be, actor);
2324 LastUnlocked = actor->GetID();
2325 ImmediateEvent();
2326 int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
2327 Game *game = core->GetGame();
2328 game->ShareXP(xp, SX_DIVIDE);
2331 void Container::TryBashLock(Actor *actor)
2333 //Get the strength bonus agains lock difficulty
2334 int str = actor->GetStat(IE_STR);
2335 int strEx = actor->GetStat(IE_STREXTRA);
2336 unsigned int bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
2337 unsigned int roll = actor->LuckyRoll(1, 10, bonus, 0);
2339 if(roll < LockDifficulty || LockDifficulty == 100) {
2340 displaymsg->DisplayConstantStringName(STR_CONTBASH_FAIL, 0xbcefbc, actor);
2341 return;
2344 displaymsg->DisplayConstantStringName(STR_CONTBASH_DONE, 0xd7d7be, actor);
2345 SetContainerLocked(false);
2346 //Is this really useful ?
2347 LastUnlocked = actor->GetID();
2348 ImmediateEvent();
2351 void Container::DebugDump() const
2353 printf( "Debugdump of Container %s\n", GetScriptName() );
2354 printf( "Type: %d, LockDifficulty: %d\n", Type, LockDifficulty );
2355 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags, YESNO(Trapped), TrapDetected );
2356 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
2357 TrapRemovalDiff );
2358 const char *name = "NONE";
2359 if (Scripts[0]) {
2360 name = Scripts[0]->GetName();
2362 printf( "Script: %s, Key: %s\n", name, KeyResRef );
2363 // FIXME: const_cast
2364 const_cast<Inventory&>(inventory).dump();
2367 bool Container::TryUnlock(Actor *actor) {
2368 if (!(Flags&CONT_LOCKED)) return true;
2370 return Highlightable::TryUnlock(actor, false);