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.
24 #include "ActorBlock.h"
25 #include "Interface.h"
26 #include "SpriteCover.h"
34 #include "GameControl.h"
35 #include "Projectile.h"
39 #define YESNO(x) ( (x)?"Yes":"No")
41 /***********************
43 ***********************/
44 Scriptable::Scriptable(ScriptableType type
)
48 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
52 overHeadTextPos
.empty();
54 timeStartDisplaying
= 0;
56 TriggerID
= 0; //used by SendTrigger
57 LastTriggerObject
= LastTrigger
= 0;
63 LastPickLockFailed
= 0;
66 CurrentActionState
= 0;
67 CurrentActionTarget
= 0;
68 CurrentActionInterruptable
= true;
69 UnselectableTimer
= 0;
70 startTime
= 0; //executing scripts
71 lastRunTime
= 0; //evaluating scripts
75 interval
= ( 1000 / AI_UPDATE_TIME
);
78 if (Type
== ST_ACTOR
) {
79 InternalFlags
= IF_VISIBLE
| IF_ONCREATION
| IF_USEDSAVE
;
81 InternalFlags
= IF_ACTIVE
| IF_VISIBLE
| IF_ONCREATION
| IF_NOINT
;
88 LastSpellOnMe
= 0xffffffff;
90 LastSpellSeen
= 0xffffffff;
92 LastTargetPos
.empty();
93 locals
= new Variables();
94 locals
->SetType( GEM_VARIABLES_INT
);
95 locals
->ParseKey( 1 );
98 memset( script_timers
,0, sizeof(script_timers
));
101 Scriptable::~Scriptable(void)
104 ReleaseCurrentAction();
107 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
109 delete( Scripts
[i
] );
113 core
->FreeString( overHeadText
);
120 void Scriptable::SetScriptName(const char* text
)
122 //if (text && text[0]) { //this leaves some uninitialized bytes
123 //lets hope this won't break anything
125 strnspccpy( scriptName
, text
, 32 );
129 /** Gets the DeathVariable */
130 const char* Scriptable::GetScriptName(void) const
135 Map
* Scriptable::GetCurrentArea() const
137 //this could be NULL, always check it
141 void Scriptable::SetMap(Map
*map
)
144 printMessage("Scriptable","Null map set!\n",LIGHT_RED
);
150 //ai is nonzero if this is an actor currently in the party
151 //if the script level is AI_SCRIPT_LEVEL, then we need to
152 //load an AI script (.bs) instead of (.bcs)
153 void Scriptable::SetScript(const ieResRef aScript
, int idx
, bool ai
)
155 if (idx
>= MAX_SCRIPTS
) {
156 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED
);
163 // NONE is an 'invalid' script name, never used seriously
164 // This hack is to prevent flooding of the console
165 if (aScript
[0] && stricmp(aScript
, "NONE") ) {
166 if (idx
!=AI_SCRIPT_LEVEL
) ai
= false;
167 Scripts
[idx
] = new GameScript( aScript
, this, idx
, ai
);
171 void Scriptable::SetScript(int index
, GameScript
* script
)
173 if (index
>= MAX_SCRIPTS
) {
174 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED
);
177 if (Scripts
[index
] ) {
178 delete Scripts
[index
];
180 Scripts
[index
] = script
;
183 void Scriptable::DisplayHeadText(const char* text
)
186 core
->FreeString( overHeadText
);
188 overHeadText
= (char *) text
;
189 overHeadTextPos
.empty();
191 timeStartDisplaying
= core
->GetGame()->Ticks
;
195 timeStartDisplaying
= 0;
200 /* 'fix' the current overhead text in the current position */
201 void Scriptable::FixHeadTextPos()
203 overHeadTextPos
= Pos
;
206 #define MAX_DELAY 6000
207 static const Color black
={0,0,0,0};
209 void Scriptable::DrawOverheadText(const Region
&screen
)
211 unsigned long time
= core
->GetGame()->Ticks
;
212 Palette
*palette
= NULL
;
217 time
-= timeStartDisplaying
;
219 Font
* font
= core
->GetFont( 1 );
220 if (time
>= MAX_DELAY
) {
224 time
= (MAX_DELAY
-time
)/10;
226 const Color overHeadColor
= {time
,time
,time
,time
};
227 palette
= core
->CreatePalette(overHeadColor
, black
);
232 if (Type
== ST_ACTOR
) {
233 cs
= ((Selectable
*) this)->size
*50;
237 if (overHeadTextPos
.isempty()) {
241 x
= overHeadTextPos
.x
;
242 y
= overHeadTextPos
.y
;
245 Region
rgn( x
-100+screen
.x
, y
- cs
+ screen
.y
, 200, 400 );
246 font
->Print( rgn
, ( unsigned char * ) overHeadText
,
247 palette
?palette
:core
->InfoTextPalette
, IE_FONT_ALIGN_CENTER
| IE_FONT_ALIGN_TOP
, false );
248 gamedata
->FreePalette(palette
);
251 void Scriptable::DelayedEvent()
253 lastRunTime
= core
->GetGame()->Ticks
;
256 void Scriptable::ImmediateEvent()
261 bool Scriptable::IsPC() const
263 if(Type
== ST_ACTOR
) {
264 if (((Actor
*) this)->GetStat(IE_EA
) <= EA_CHARMED
) {
271 void Scriptable::ExecuteScript(int scriptCount
)
273 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
274 if ((Type
!= ST_AREA
) && (core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
)) {
278 if ((InternalFlags
& IF_NOINT
) && (CurrentAction
|| GetNextAction())) {
282 if (!CurrentActionInterruptable
) {
283 if (!CurrentAction
&& !GetNextAction()) abort();
287 // only allow death scripts to run once, hopefully?
288 // this is probably terrible logic which needs moving elsewhere
289 if ((lastRunTime
!= 0) && (InternalFlags
& IF_JUSTDIED
)) {
293 ieDword thisTime
= core
->GetGame()->Ticks
;
294 if (( thisTime
- lastRunTime
) < 1000) {
298 lastDelay
= lastRunTime
;
299 lastRunTime
= thisTime
;
303 bool continuing
= false, done
= false;
304 for (int i
= 0;i
<scriptCount
;i
++) {
305 //disable AI script level for actors in party when the player disabled them
306 if ((i
== AI_SCRIPT_LEVEL
) && Type
== ST_ACTOR
&& ((Actor
*) this)->InParty
) {
307 if (core
->GetGame()->ControlStatus
&CS_PARTY_AI
) {
312 GameScript
*Script
= Scripts
[i
];
314 alive
|= Script
->Update(&continuing
, &done
);
317 /* scripts are not concurrent, see WAITPC override script for example */
320 if (alive
&& UnselectableTimer
) {
322 if (!UnselectableTimer
) {
323 if (Type
== ST_ACTOR
) {
324 ((Actor
*) this)->SetCircleSize();
330 void Scriptable::AddAction(Action
* aC
)
333 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
336 InternalFlags
|=IF_ACTIVE
;
337 actionQueue
.push_back( aC
);
341 void Scriptable::AddActionInFront(Action
* aC
)
344 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
347 InternalFlags
|=IF_ACTIVE
;
348 actionQueue
.push_front( aC
);
352 Action
* Scriptable::GetNextAction() const
354 if (actionQueue
.size() == 0) {
357 return actionQueue
.front();
360 Action
* Scriptable::PopNextAction()
362 if (actionQueue
.size() == 0) {
365 Action
* aC
= actionQueue
.front();
366 actionQueue
.pop_front();
370 void Scriptable::ClearActions()
372 ReleaseCurrentAction();
373 for (unsigned int i
= 0; i
< actionQueue
.size(); i
++) {
374 Action
* aC
= actionQueue
.front();
375 actionQueue
.pop_front();
380 playDeadCounter
= 0; // i'm not sure about this
382 //clear the triggers as fast as possible when queue ended?
385 if (Type
== ST_ACTOR
) {
392 void Scriptable::ReleaseCurrentAction()
395 CurrentAction
->Release();
396 CurrentAction
= NULL
;
399 CurrentActionState
= 0;
400 CurrentActionTarget
= 0;
401 CurrentActionInterruptable
= true;
404 ieWord
Scriptable::GetGlobalID()
406 if (Type
== ST_ACTOR
) {
407 Actor
*actor
= (Actor
*) this;
408 return actor
->globalID
;
413 void Scriptable::ProcessActions(bool force
)
415 unsigned long thisTime
= core
->GetGame()->Ticks
;
417 if (!force
&& (( thisTime
- startTime
) < interval
)) {
420 startTime
= thisTime
;
421 if (playDeadCounter
) {
423 if (!playDeadCounter
) {
424 Movable
* mov
= ( Movable
* ) this;
425 mov
->SetStance( IE_ANI_GET_UP
);
430 if (WaitCounter
) return;
434 CurrentActionInterruptable
= true;
435 if (!CurrentAction
) {
436 CurrentAction
= PopNextAction();
438 if (!CurrentAction
) {
443 //removing the triggers at the end of the
448 GameScript::ExecuteAction( this, CurrentAction
);
449 //break execution in case of a Wait flag
451 //clear triggers while waiting
455 //break execution in case of blocking action
459 //break execution in case of movement
460 //we should not actually break here, or else fix waypoints
465 //most likely the best place to clear triggers is here
466 //queue is empty, or there is a looong action subject to break
468 if (InternalFlags
&IF_IDLE
) {
473 bool Scriptable::InMove() const
475 if (Type
!=ST_ACTOR
) {
478 Movable
*me
= (Movable
*) this;
479 return me
->GetNextStep()!=NULL
;
482 void Scriptable::SetWait(unsigned long time
)
487 unsigned long Scriptable::GetWait() const
492 Scriptable
*Scriptable::GetCutsceneID() const
497 void Scriptable::LeaveDialog()
499 InternalFlags
|=IF_WASINDIALOG
;
502 //this ends cutscene mode for this Sender
503 void Scriptable::ClearCutsceneID()
506 InternalFlags
&= ~IF_CUTSCENEID
;
509 //if the cutsceneID doesn't exist, we simply skip the action
510 //because the cutscene script executer DOESN'T get hijacked
511 void Scriptable::SetCutsceneID(Scriptable
*csid
)
514 InternalFlags
|= IF_CUTSCENEID
;
517 void Scriptable::Hide()
519 InternalFlags
&=~(IF_VISIBLE
);
522 void Scriptable::Unhide()
524 InternalFlags
|= IF_VISIBLE
;
527 void Scriptable::Interrupt()
529 InternalFlags
&= ~IF_NOINT
;
532 void Scriptable::NoInterrupt()
534 InternalFlags
|= IF_NOINT
;
537 //also turning off the idle flag so it won't run continuously
538 void Scriptable::Deactivate()
540 InternalFlags
&=~(IF_ACTIVE
|IF_IDLE
);
543 //turning off the not interruptable flag, actions should reenable it themselves
544 //also turning off the idle flag
545 //heh, no, i wonder why did i touch the interruptable flag here
546 void Scriptable::Activate()
548 InternalFlags
|= IF_ACTIVE
;
549 InternalFlags
&= ~IF_IDLE
;
552 void Scriptable::PartyRested()
554 InternalFlags
|=IF_PARTYRESTED
;
557 ieDword
Scriptable::GetInternalFlag()
559 return InternalFlags
;
562 void Scriptable::InitTriggers()
568 void Scriptable::ClearTriggers()
570 for (TriggerObjects::iterator m
= tolist
.begin(); m
!= tolist
.end (); m
++) {
576 if (bittriggers
& BT_DIE
) {
577 InternalFlags
&= ~IF_JUSTDIED
;
579 if (bittriggers
& BT_ONCREATION
) {
580 InternalFlags
&= ~IF_ONCREATION
;
582 if (bittriggers
& BT_BECAMEVISIBLE
) {
583 InternalFlags
&= ~IF_BECAMEVISIBLE
;
585 if (bittriggers
& BT_PARTYRESTED
) {
586 InternalFlags
&= ~IF_PARTYRESTED
;
588 if (bittriggers
& BT_WASINDIALOG
) {
589 InternalFlags
&= ~IF_WASINDIALOG
;
591 if (bittriggers
& BT_PARTYRESTED
) {
592 InternalFlags
&= ~IF_PARTYRESTED
;
597 void Scriptable::SetBitTrigger(ieDword bittrigger
)
599 bittriggers
|= bittrigger
;
602 void Scriptable::AddTrigger(ieDword
*actorref
)
604 tolist
.push_back(actorref
);
607 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef
)
609 if (Type
== ST_ACTOR
) {
610 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
613 if (SpellHeader
== -1) {
614 LastTargetPos
.empty();
618 if (LastTargetPos
.isempty()) {
623 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
624 //create projectile from known spellheader
625 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
628 pro
->SetCaster(GetGlobalID());
630 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
631 // try and make projectiles start from the right trap position
632 // see the traps in the duergar/assassin battle in bg2 dungeon
633 // see also function below - maybe we should fix Pos instead
634 origin
= ((InfoPoint
*)this)->TrapLaunch
;
636 GetCurrentArea()->AddProjectile(pro
, origin
, LastTargetPos
);
639 LastTargetPos
.empty();
642 void Scriptable::CastSpellEnd( const ieResRef SpellResRef
)
644 if (Type
== ST_ACTOR
) {
645 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
648 if (SpellHeader
== -1) {
656 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
657 //create projectile from known spellheader
658 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
660 pro
->SetCaster(GetGlobalID());
662 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
663 // try and make projectiles start from the right trap position
664 // see the traps in the duergar/assassin battle in bg2 dungeon
665 // see also function above - maybe we should fix Pos instead
666 origin
= ((InfoPoint
*)this)->TrapLaunch
;
669 GetCurrentArea()->AddProjectile(pro
, origin
, LastTarget
);
671 GetCurrentArea()->AddProjectile(pro
, origin
, LastTargetPos
);
674 ieDword spellnum
=ResolveSpellNumber( SpellResRef
);
675 if (spellnum
!=0xffffffff) {
676 area
->SeeSpellCast(this, spellnum
);
678 Scriptable
*target
= area
->GetActorByGlobalID(LastTarget
);
679 if (target
&& (Type
==ST_ACTOR
) ) {
680 Actor
*me
= (Actor
*) this;
681 target
->LastSpellOnMe
= spellnum
;
682 target
->LastCasterOnMe
= me
->GetID();
683 me
->CureInvisibility();
691 gamedata
->FreeSpell(spl
, SpellResRef
, false);
693 LastTargetPos
.empty();
696 //set target as point
697 //if spell needs to be depleted, do it
698 //if spell is illegal stop casting
699 int Scriptable::CastSpellPoint( const ieResRef SpellResRef
, const Point
&target
, bool deplete
, bool instant
)
702 LastTargetPos
.empty();
703 if (Type
== ST_ACTOR
) {
704 Actor
*actor
= (Actor
*) this;
705 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
709 LastTargetPos
= target
;
710 return SpellCast(SpellResRef
, instant
);
713 //set target as actor (if target isn't actor, use its position)
714 //if spell needs to be depleted, do it
715 //if spell is illegal stop casting
716 int Scriptable::CastSpell( const ieResRef SpellResRef
, Scriptable
* target
, bool deplete
, bool instant
)
719 LastTargetPos
.empty();
720 if (Type
== ST_ACTOR
) {
721 Actor
*actor
= (Actor
*) this;
722 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
727 if (!target
) target
= this;
729 LastTargetPos
= target
->Pos
;
730 if (target
->Type
==ST_ACTOR
) {
731 LastTarget
= target
->GetGlobalID();
733 return SpellCast(SpellResRef
, instant
);
736 //start spellcasting (common part)
737 int Scriptable::SpellCast(const ieResRef SpellResRef
, bool instant
)
739 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
745 if (Type
== ST_ACTOR
) {
746 Actor
*actor
= (Actor
*) this;
747 //The ext. index is here to calculate the casting time
748 int level
= actor
->GetXPLevel(true);
749 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
750 level
+= actor
->CastingLevelBonus(level
, spl
->SpellType
);
751 SpellHeader
= spl
->GetHeaderIndexFromLevel(level
);
756 SPLExtHeader
*header
= spl
->GetExtHeader(SpellHeader
);
757 int casting_time
= (int)header
->CastingTime
;
758 // how does this work for non-actors exactly?
759 if (Type
== ST_ACTOR
) {
760 // The mental speed effect can shorten or lengthen the casting time.
761 casting_time
-= (int)((Actor
*) this)->GetStat(IE_MENTALSPEED
);
762 if (casting_time
< 0) casting_time
= 0;
764 // this is a guess which seems approximately right so far
765 int duration
= (casting_time
*ROUND_SIZE
) / 10;
770 if (Type
== ST_ACTOR
) {
771 Actor
*actor
= (Actor
*) this;
772 EffectQueue
*fxqueue
= spl
->GetEffectBlock(this, this->Pos
, -1);
773 spl
->AddCastingGlow(fxqueue
, duration
);
774 fxqueue
->SetOwner(actor
);
775 fxqueue
->AddAllEffects(actor
, actor
->Pos
);
779 gamedata
->FreeSpell(spl
, SpellResRef
, false);
783 bool Scriptable::TimerActive(ieDword ID
)
788 if (script_timers
[ID
]) {
794 bool Scriptable::TimerExpired(ieDword ID
)
799 if (script_timers
[ID
] && script_timers
[ID
] < core
->GetGame()->GameTime
) {
800 // expired timers become inactive after being checked
801 script_timers
[ID
] = 0;
807 void Scriptable::StartTimer(ieDword ID
, ieDword expiration
)
810 printMessage("Scriptable", " ", RED
);
811 printf("Timer id %d exceeded MAX_TIMER %d\n", ID
, MAX_TIMER
);
814 script_timers
[ID
]= core
->GetGame()->GameTime
+ expiration
*AI_UPDATE_TIME
;
817 /********************
819 ********************/
821 Selectable::Selectable(ScriptableType type
)
828 circleBitmap
[0] = NULL
;
829 circleBitmap
[1] = NULL
;
832 void Selectable::SetSpriteCover(SpriteCover
* c
)
838 Selectable::~Selectable(void)
843 void Selectable::SetBBox(const Region
&newBBox
)
848 static const unsigned long tp_steps
[8]={3,2,1,0,1,2,3,4};
850 void Selectable::DrawCircle(const Region
&vp
)
852 /* BG2 colours ground circles as follows:
853 dark green for unselected party members
854 bright green for selected party members
855 flashing green/white for a party member the mouse is over
856 bright red for enemies
857 yellow for panicked actors
858 flashing red/white for enemies the mouse is over
859 flashing cyan/white for neutrals the mouse is over
866 Color
* col
= &selectedColor
;
867 Sprite2D
* sprite
= circleBitmap
[0];
870 sprite
= circleBitmap
[1];
872 //doing a time dependent flashing of colors
873 //if it is too fast, increase the 6 to 7
876 step
= tp_steps
[(step
>> 6) & 7];
878 mix
.r
= (overColor
.r
*step
+selectedColor
.r
*(8-step
))/8;
879 mix
.g
= (overColor
.g
*step
+selectedColor
.g
*(8-step
))/8;
880 mix
.b
= (overColor
.b
*step
+selectedColor
.b
*(8-step
))/8;
887 core
->GetVideoDriver()->BlitSprite( sprite
, Pos
.x
- vp
.x
, Pos
.y
- vp
.y
, true );
889 // for size >= 2, radii are (size-1)*16, (size-1)*12
890 // for size == 1, radii are 12, 9
891 int csize
= (size
- 1) * 4;
892 if (csize
< 4) csize
= 3;
893 core
->GetVideoDriver()->DrawEllipse( (ieWord
) (Pos
.x
- vp
.x
), (ieWord
) (Pos
.y
- vp
.y
),
894 (ieWord
) (csize
* 4), (ieWord
) (csize
* 3), *col
);
898 // Check if P is over our ground circle
899 bool Selectable::IsOver(const Point
&P
) const
902 if (csize
< 2) csize
= 2;
904 int dx
= P
.x
- Pos
.x
;
905 int dy
= P
.y
- Pos
.y
;
907 // check rectangle first
908 if (dx
< -(csize
-1)*16 || dx
> (csize
-1)*16) return false;
909 if (dy
< -(csize
-1)*12 || dy
> (csize
-1)*12) return false;
911 // then check ellipse
912 int r
= 9*dx
*dx
+ 16*dy
*dy
; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
914 return (r
<= 48*48*(csize
-1)*(csize
-1));
917 bool Selectable::IsSelected() const
919 return Selected
== 1;
922 void Selectable::SetOver(bool over
)
927 //don't call this function after rendering the cover and before the
928 //blitting of the sprite or bad things will happen :)
929 void Selectable::Select(int Value
)
931 if (Selected
!=0x80 || Value
!=1) {
932 Selected
= (ieWord
) Value
;
934 //forcing regeneration of the cover
935 SetSpriteCover(NULL
);
938 void Selectable::SetCircle(int circlesize
, const Color
&color
, Sprite2D
* normal_circle
, Sprite2D
* selected_circle
)
941 selectedColor
= color
;
942 overColor
.r
= color
.r
>> 1;
943 overColor
.g
= color
.g
>> 1;
944 overColor
.b
= color
.b
>> 1;
945 overColor
.a
= color
.a
;
946 circleBitmap
[0] = normal_circle
;
947 circleBitmap
[1] = selected_circle
;
951 int Selectable::WantDither()
953 //if dithering is disabled globally, don't do it
954 if (core
->FogOfWar
&4) {
957 //if actor is dead, dither it if polygon wants
961 //if actor is selected dither it
968 /***********************
969 * Highlightable Class *
970 ***********************/
972 Highlightable::Highlightable(ScriptableType type
)
977 Cursor
= IE_CURSOR_NORMAL
;
981 Highlightable::~Highlightable(void)
988 bool Highlightable::IsOver(const Point
&Pos
) const
993 return outline
->PointIn( Pos
);
996 void Highlightable::DrawOutline() const
1001 core
->GetVideoDriver()->DrawPolyline( outline
, outlineColor
, true );
1004 void Highlightable::SetCursor(unsigned char CursorIndex
)
1006 Cursor
= CursorIndex
;
1009 bool Highlightable::TryUnlock(Actor
*actor
, bool removekey
) {
1010 const char *Key
= GetKey();
1011 Actor
*haskey
= NULL
;
1013 if (Key
&& actor
->InParty
) {
1014 Game
*game
= core
->GetGame();
1015 //allow unlock when the key is on any partymember
1016 for (int idx
= 0; idx
< game
->GetPartySize(false); idx
++) {
1017 Actor
*pc
= game
->FindPC(idx
+ 1);
1020 if (pc
->inventory
.HasItem(Key
,0) ) {
1026 //actor is not in party, check only actor
1027 if (actor
->inventory
.HasItem(Key
,0) ) {
1037 CREItem
*item
= NULL
;
1038 haskey
->inventory
.RemoveItem(Key
,0,&item
);
1039 //the item should always be existing!!!
1053 Movable::Movable(ScriptableType type
)
1054 : Selectable( type
)
1065 AttackMovements
[0] = 100;
1066 AttackMovements
[1] = 0;
1067 AttackMovements
[2] = 0;
1070 Movable::~Movable(void)
1077 int Movable::GetPathLength()
1079 PathNode
*node
= GetNextStep(0);
1081 while (node
->Next
) {
1088 PathNode
*Movable::GetNextStep(int x
)
1091 DoStep((unsigned int) ~0);
1093 PathNode
*node
= step
;
1094 while(node
&& x
--) {
1100 Point
Movable::GetMostLikelyPosition()
1106 //actually, sometimes middle path would be better, if
1107 //we stand in Destination already
1108 int halfway
= GetPathLength()/2;
1109 PathNode
*node
= GetNextStep(halfway
);
1111 return Point((ieWord
) ((node
->x
*16)+8), (ieWord
) ((node
->y
*12)+6) );
1116 void Movable::SetStance(unsigned int arg
)
1118 //don't modify stance from dead back to anything if the actor is dead
1119 if ((StanceID
==IE_ANI_TWITCH
|| StanceID
==IE_ANI_DIE
) && (arg
!=IE_ANI_TWITCH
) ) {
1120 if (GetInternalFlag()&IF_REALLYDIED
) {
1121 printMessage("Movable","Stance overridden by death\n", YELLOW
);
1126 if (arg
<MAX_ANIMS
) {
1127 StanceID
=(unsigned char) arg
;
1129 if (StanceID
== IE_ANI_ATTACK
) {
1130 // Set stance to a random attack animation
1132 int random
= rand()%100;
1133 if (random
< AttackMovements
[0]) {
1134 StanceID
= IE_ANI_ATTACK_BACKSLASH
;
1135 } else if (random
< AttackMovements
[0] + AttackMovements
[1]) {
1136 StanceID
= IE_ANI_ATTACK_SLASH
;
1138 StanceID
= IE_ANI_ATTACK_JAB
;
1143 StanceID
=IE_ANI_AWAKE
; //
1144 printf("Tried to set invalid stance id (%u)\n", arg
);
1148 void Movable::SetAttackMoveChances(ieWord
*amc
)
1150 AttackMovements
[0]=amc
[0];
1151 AttackMovements
[1]=amc
[1];
1152 AttackMovements
[2]=amc
[2];
1157 //this could be used for WingBuffet as well
1158 void Movable::MoveLine(int steps
, int Pass
, ieDword orient
)
1160 //remove previous path
1167 path
= area
->GetLine( p
, steps
, orient
, Pass
);
1170 void AdjustPositionTowards(Point
&Pos
, ieDword time_diff
, unsigned int walk_speed
, short srcx
, short srcy
, short destx
, short desty
) {
1172 Pos
.x
+= ( unsigned short )
1173 ( ( ( ( ( destx
* 16 ) + 8 ) - Pos
.x
) * ( time_diff
) ) / walk_speed
);
1175 Pos
.x
-= ( unsigned short )
1176 ( ( ( Pos
.x
- ( ( destx
* 16 ) + 8 ) ) * ( time_diff
) ) / walk_speed
);
1178 Pos
.y
+= ( unsigned short )
1179 ( ( ( ( ( desty
* 12 ) + 6 ) - Pos
.y
) * ( time_diff
) ) / walk_speed
);
1181 Pos
.y
-= ( unsigned short )
1182 ( ( ( Pos
.y
- ( ( desty
* 12 ) + 6 ) ) * ( time_diff
) ) / walk_speed
);
1186 // returns whether we made all pending steps (so, false if we must be called again this tick)
1187 // we can't just do them all here because the caller might have to update searchmap etc
1188 bool Movable::DoStep(unsigned int walk_speed
, ieDword time
)
1193 if (!time
) time
= core
->GetGame()->Ticks
;
1195 // zero speed: no movement
1196 timeStartStep
= time
;
1197 StanceID
= IE_ANI_READY
;
1202 timeStartStep
= time
;
1203 } else if (step
->Next
&& (( time
- timeStartStep
) >= walk_speed
)) {
1204 //printf("[New Step] : Orientation = %d\n", step->orient);
1206 timeStartStep
= timeStartStep
+ walk_speed
;
1208 SetOrientation (step
->orient
, false);
1209 StanceID
= IE_ANI_WALK
;
1210 if ((Type
== ST_ACTOR
) && (InternalFlags
& IF_RUNNING
)) {
1211 StanceID
= IE_ANI_RUN
;
1213 Pos
.x
= ( step
->x
* 16 ) + 8;
1214 Pos
.y
= ( step
->y
* 12 ) + 6;
1216 // we reached our destination, we are done
1218 NewOrientation
= Orientation
;
1219 //since clearpath no longer sets currentaction to NULL
1221 //no we don't, action is responsible for releasing itself
1222 //ReleaseCurrentAction();
1225 if (( time
- timeStartStep
) >= walk_speed
) {
1226 // we didn't finish all pending steps, yet
1229 AdjustPositionTowards(Pos
, time
- timeStartStep
, walk_speed
, step
->x
, step
->y
, step
->Next
->x
, step
->Next
->y
);
1233 void Movable::AddWayPoint(const Point
&Des
)
1240 //it is tempting to use 'step' here, as it could
1241 //be about half of the current path already
1242 PathNode
*endNode
= path
;
1243 while(endNode
->Next
) {
1244 endNode
= endNode
->Next
;
1246 Point
p(endNode
->x
, endNode
->y
);
1247 area
->ClearSearchMapFor(this);
1248 PathNode
*path2
= area
->FindPath( p
, Des
, size
);
1249 endNode
->Next
= path2
;
1250 //probably it is wise to connect it both ways?
1251 path2
->Parent
= endNode
;
1254 void Movable::FixPosition()
1256 if (Type
!=ST_ACTOR
) {
1259 Actor
*actor
= (Actor
*) this;
1260 if (actor
->GetStat(IE_DONOTJUMP
)&DNJ_BIRD
) {
1263 //before fixposition, you should remove own shadow
1264 area
->ClearSearchMapFor(this);
1267 GetCurrentArea()->AdjustPosition(Pos
);
1272 void Movable::WalkTo(const Point
&Des
, int distance
)
1276 // maybe caller should be responsible for this
1277 if ((Des
.x
/16 == Pos
.x
/16) && (Des
.y
/12 == Pos
.y
/12)) {
1282 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1283 PathNode
*prev_step
= NULL
;
1284 unsigned char old_stance
= StanceID
;
1285 if (step
&& step
->Next
) {
1286 // don't interrupt in the middle of a step; path from the next one
1287 prev_step
= new PathNode(*step
);
1288 from
.x
= ( step
->Next
->x
* 16 ) + 8;
1289 from
.y
= ( step
->Next
->y
* 12 ) + 6;
1297 area
->ClearSearchMapFor(this);
1299 path
= area
->FindPathNear( from
, Des
, size
, distance
);
1301 path
= area
->FindPath( from
, Des
, size
, distance
);
1303 //ClearPath sets destination, so Destination must be set after it
1304 //also we should set Destination only if there is a walkable path
1309 // we want to smoothly continue, please
1310 // this all needs more thought! but it seems to work okay
1311 StanceID
= old_stance
;
1314 // this is a terrible hack to make up for the
1315 // pathfinder orienting the first node wrong
1316 // should be fixed in pathfinder and not here!
1318 next
.x
= path
->x
; next
.y
= path
->y
;
1319 follow
.x
= path
->Next
->x
;
1320 follow
.y
= path
->Next
->y
;
1321 path
->orient
= GetOrient(follow
, next
);
1324 // then put the prev_step at the beginning of the path
1325 prev_step
->Next
= path
;
1326 path
->Parent
= prev_step
;
1334 delete( prev_step
);
1340 void Movable::RunAwayFrom(const Point
&Des
, int PathLength
, int flags
)
1343 area
->ClearSearchMapFor(this);
1344 path
= area
->RunAway( Pos
, Des
, size
, PathLength
, flags
);
1347 void Movable::RandomWalk(bool can_stop
, bool run
)
1352 //if not continous random walk, then stops for a while
1353 if (can_stop
&& (rand()&3) ) {
1354 SetWait((rand()&7)+7);
1358 InternalFlags
|=IF_RUNNING
;
1360 //the commenting-out of the clear search map call was removed in 0.4.0
1361 //if you want to put it back for some reason, check
1362 //if the searchmap is not eaten up
1363 area
->ClearSearchMapFor(this);
1366 //selecting points around a circle's edge around actor (didn't work better)
1367 //int x = core->Roll(1,100,-50);
1369 //p.y+=(int) sqrt(100-x*x);
1371 //selecting points in a square around actor
1372 p
.x
+=core
->Roll(1,50,-25);
1373 p
.y
+=core
->Roll(1,50,-25);
1374 //the 5th parameter is controlling the orientation of the actor
1375 //0 - back away, 1 - face direction
1376 path
= area
->RunAway( Pos
, p
, size
, 50, 1 );
1379 void Movable::MoveTo(const Point
&Des
)
1381 area
->ClearSearchMapFor(this);
1384 if (BlocksSearchMap()) {
1385 area
->BlockSearchMap( Pos
, size
, IsPC()?PATH_MAP_PC
:PATH_MAP_NPC
);
1389 void Movable::ClearPath()
1391 //this is to make sure attackers come to us
1392 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1393 //is set before ClearPath
1395 if (StanceID
==IE_ANI_WALK
|| StanceID
==IE_ANI_RUN
) {
1396 StanceID
= IE_ANI_AWAKE
;
1398 InternalFlags
&=~IF_NORECTICLE
;
1399 PathNode
* thisNode
= path
;
1401 PathNode
* nextNode
= thisNode
->Next
;
1403 thisNode
= nextNode
;
1407 //don't call ReleaseCurrentAction
1410 void Movable::DrawTargetPoint(const Region
&vp
)
1412 if (!path
|| !Selected
|| (InternalFlags
&IF_NORECTICLE
) )
1415 // recticles are never drawn in cutscenes
1416 if ((core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
))
1419 // generates "step" from sequence 3 2 1 0 1 2 3 4
1420 // updated each 1/15 sec
1423 step
= tp_steps
[(step
>> 6) & 7];
1426 int csize
= (size
- 1) * 4;
1427 if (csize
< 4) csize
= 3;
1429 /* segments should not go outside selection radius */
1430 unsigned short xradius
= (csize
* 4) - 5;
1431 unsigned short yradius
= (csize
* 3) - 5;
1432 ieWord xcentre
= (ieWord
) (Destination
.x
- vp
.x
);
1433 ieWord ycentre
= (ieWord
) (Destination
.y
- vp
.y
);
1435 // TODO: 0.5 and 0.7 are pretty much random values
1437 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
+ step
, ycentre
, xradius
,
1438 yradius
, selectedColor
, -0.5, 0.5 );
1440 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
- step
, xradius
,
1441 yradius
, selectedColor
, -0.7 - M_PI_2
, 0.7 - M_PI_2
);
1443 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
- step
, ycentre
, xradius
,
1444 yradius
, selectedColor
, -0.5 - M_PI
, 0.5 - M_PI
);
1446 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
+ step
, xradius
,
1447 yradius
, selectedColor
, -0.7 - M_PI
- M_PI_2
, 0.7 - M_PI
- M_PI_2
);
1450 /**********************
1451 * Tiled Object Class *
1452 **********************/
1454 TileObject::TileObject()
1463 TileObject::~TileObject()
1469 free( closedtiles
);
1473 void TileObject::SetOpenTiles(unsigned short* Tiles
, int cnt
)
1482 void TileObject::SetClosedTiles(unsigned short* Tiles
, int cnt
)
1485 free( closedtiles
);
1487 closedtiles
= Tiles
;
1495 Door::Door(TileOverlay
* Overlay
)
1496 : Highlightable( ST_DOOR
)
1513 OpenStrRef
= (ieDword
) -1;
1518 if (Flags
&DOOR_OPEN
) {
1538 void Door::ImpedeBlocks(int count
, Point
*points
, unsigned char value
)
1540 for(int i
= 0;i
<count
;i
++) {
1541 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_NOTDOOR
;
1542 area
->SearchMap
->SetAt( points
[i
].x
, points
[i
].y
, (tmp
|value
) );
1546 void Door::UpdateDoor()
1548 if (Flags
&DOOR_OPEN
) {
1553 unsigned char oval
, cval
;
1555 oval
= PATH_MAP_IMPASSABLE
;
1556 if (Flags
& DOOR_TRANSPARENT
) {
1557 cval
= PATH_MAP_DOOR_TRANSPARENT
;
1560 cval
= PATH_MAP_DOOR_OPAQUE
;
1562 if (Flags
&DOOR_OPEN
) {
1563 ImpedeBlocks(cibcount
, closed_ib
, 0);
1564 ImpedeBlocks(oibcount
, open_ib
, cval
);
1567 ImpedeBlocks(oibcount
, open_ib
, 0);
1568 ImpedeBlocks(cibcount
, closed_ib
, cval
);
1571 InfoPoint
*ip
= area
->TMap
->GetInfoPoint(LinkedInfo
);
1573 if (Flags
&DOOR_OPEN
) ip
->Flags
&=~INFO_DOOR
;
1574 else ip
->Flags
|=INFO_DOOR
;
1578 void Door::ToggleTiles(int State
, int playsound
)
1584 state
= !closedIndex
;
1585 if (playsound
&& ( OpenSound
[0] != '\0' ))
1586 core
->GetAudioDrv()->Play( OpenSound
);
1588 state
= closedIndex
;
1589 if (playsound
&& ( CloseSound
[0] != '\0' ))
1590 core
->GetAudioDrv()->Play( CloseSound
);
1592 for (i
= 0; i
< tilecount
; i
++) {
1593 overlay
->tiles
[tiles
[i
]]->tileIndex
= (ieByte
) state
;
1596 //set door_open as state
1597 Flags
= (Flags
& ~DOOR_OPEN
) | (State
== !core
->HasFeature(GF_REVERSE_DOOR
) );
1600 //this is the short name (not the scripting name)
1601 void Door::SetName(const char* name
)
1603 strnlwrcpy( ID
, name
, 8 );
1606 void Door::SetTiles(unsigned short* Tiles
, int cnt
)
1615 void Door::SetDoorLocked(int Locked
, int playsound
)
1618 if (Flags
& DOOR_LOCKED
) return;
1620 if (playsound
&& ( LockSound
[0] != '\0' ))
1621 core
->GetAudioDrv()->Play( LockSound
);
1624 if (!(Flags
& DOOR_LOCKED
)) return;
1625 Flags
&=~DOOR_LOCKED
;
1626 if (playsound
&& ( UnLockSound
[0] != '\0' ))
1627 core
->GetAudioDrv()->Play( UnLockSound
);
1631 int Door::IsOpen() const
1633 int ret
= core
->HasFeature(GF_REVERSE_DOOR
);
1634 if (Flags
&DOOR_OPEN
) {
1640 //also mark actors to fix position
1641 bool Door::BlockedOpen(int Open
, int ForceOpen
)
1655 //getting all impeded actors flagged for jump
1659 for(int i
= 0;i
<count
;i
++) {
1661 rgn
.x
= points
[i
].x
*16;
1662 rgn
.y
= points
[i
].y
*12;
1663 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_ACTOR
;
1665 int ac
= area
->GetActorInRect(ab
, rgn
, false);
1667 if (ab
[ac
]->GetBase(IE_DONOTJUMP
)) {
1670 ab
[ac
]->SetBase(IE_DONOTJUMP
, DNJ_JUMP
);
1679 if ((Flags
&DOOR_SLIDE
) || ForceOpen
) {
1685 void Door::SetDoorOpen(int Open
, int playsound
, ieDword ID
)
1688 //the door cannot be blocked when opening,
1689 //but the actors will be pushed
1690 //BlockedOpen will mark actors to be pushed
1691 if (BlockedOpen(Open
,0) && !Open
) {
1692 //clear up the blocking actors
1693 area
->JumpActors(false);
1696 area
->JumpActors(true);
1699 LastEntered
= ID
; //used as lastOpener
1701 // in PS:T, opening a door does not unlock it
1702 if (!core
->HasFeature(GF_REVERSE_DOOR
)) {
1703 SetDoorLocked(false,playsound
);
1706 LastTriggerObject
= LastTrigger
= ID
; //used as lastCloser
1708 ToggleTiles(Open
, playsound
);
1709 //synchronising other data with the door state
1711 area
->ActivateWallgroups(open_wg_index
, open_wg_count
, Flags
&DOOR_OPEN
);
1712 area
->ActivateWallgroups(closed_wg_index
, closed_wg_count
, !(Flags
&DOOR_OPEN
));
1715 bool Door::TryUnlock(Actor
*actor
) {
1716 if (!(Flags
&DOOR_LOCKED
)) return true;
1718 // don't remove key in PS:T!
1719 bool removekey
= !core
->HasFeature(GF_REVERSE_DOOR
) && Flags
&DOOR_KEY
;
1720 return Highlightable::TryUnlock(actor
, removekey
);
1723 void Door::SetPolygon(bool Open
, Gem_Polygon
* poly
)
1736 void Door::SetNewOverlay(TileOverlay
*Overlay
) {
1738 ToggleTiles(IsOpen(), false);
1741 void Highlightable::SetTrapDetected(int x
)
1743 if(x
== TrapDetected
)
1747 core
->Autopause(AP_TRAP
);
1751 void Highlightable::TryDisarm(Actor
*actor
)
1753 if (!Trapped
|| !TrapDetected
) return;
1755 LastTriggerObject
= LastTrigger
= actor
->GetID();
1756 int skill
= actor
->GetStat(IE_TRAPS
);
1758 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapRemovalDiff
) {
1759 LastDisarmed
= actor
->GetID();
1762 core
->DisplayConstantStringName(STR_DISARM_DONE
, 0xd7d7be, actor
);
1763 actor
->AddExperience(XP_DISARM
, actor
->GetXPLevel(1));
1765 core
->DisplayConstantStringName(STR_DISARM_FAIL
, 0xd7d7be, actor
);
1766 TriggerTrap(skill
, LastTrigger
);
1771 void Door::TryPickLock(Actor
*actor
)
1773 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
1774 if (LockDifficulty
== 100) {
1775 core
->DisplayConstantStringName(STR_DOOR_NOPICK
, 0xbcefbc, actor
);
1777 core
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
1778 LastPickLockFailed
= actor
->GetID();
1782 SetDoorLocked( false, true);
1783 core
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
1784 LastUnlocked
= actor
->GetID();
1786 actor
->AddExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
1789 void Door::TryBashLock(Actor
*actor
)
1791 //Get the strength bonus agains lock difficulty
1792 int str
= actor
->GetStat(IE_STR
);
1793 int strEx
= actor
->GetStat(IE_STREXTRA
);
1794 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
1796 //bonus will never reach 100
1797 if(bonus
< LockDifficulty
) {
1798 core
->DisplayConstantStringName(STR_DOORBASH_FAIL
, 0xbcefbc, actor
);
1802 core
->DisplayConstantStringName(STR_DOORBASH_DONE
, 0xd7d7be, actor
);
1803 SetDoorLocked(false, true);
1804 //Is this really useful ?
1805 LastUnlocked
= actor
->GetID();
1809 void Door::DebugDump() const
1811 printf( "Debugdump of Door %s:\n", GetScriptName() );
1812 printf( "Door Open: %s\n", YESNO(IsOpen()));
1813 printf( "Door Locked: %s\n", YESNO(Flags
&DOOR_LOCKED
));
1814 printf( "Door Trapped: %s\n", YESNO(Trapped
));
1816 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags
&DOOR_RESET
), YESNO(Flags
&DOOR_DETECTABLE
) );
1818 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags
&DOOR_SECRET
),YESNO(Flags
&DOOR_FOUND
));
1819 const char *Key
= GetKey();
1820 const char *name
= "NONE";
1822 name
= Scripts
[0]->GetName();
1824 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name
, Key
?Key
:"NONE", YESNO(Flags
&DOOR_KEY
), Dialog
);
1827 /*******************
1829 *******************/
1831 InfoPoint::InfoPoint(void)
1832 : Highlightable( ST_TRIGGER
)
1835 EntranceName
[0] = 0;
1837 TrapDetectionDiff
= 0;
1838 TrapRemovalDiff
= 0;
1844 InfoPoint::~InfoPoint(void)
1848 //checks if the actor may use this travel trigger
1850 //bit 2 : whole team
1851 int InfoPoint::CheckTravel(Actor
*actor
)
1853 if (Flags
&TRAP_DEACTIVATED
) return CT_CANTMOVE
;
1854 if (!actor
->InParty
&& (Flags
&TRAVEL_NONPC
) ) return CT_CANTMOVE
;
1855 if (actor
->InParty
&& (Flags
&TRAVEL_PARTY
) ) {
1856 if (core
->HasFeature(GF_TEAM_MOVEMENT
) || core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
) ) {
1859 return CT_GO_CLOSER
;
1861 if(actor
->IsSelected() ) {
1862 if(core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
|ENP_ONLYSELECT
) ) {
1863 return CT_MOVE_SELECTED
;
1870 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1871 //skill is the all around modified trap detection skill
1872 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1873 void Highlightable::DetectTrap(int skill
)
1875 if (!CanDetectTrap()) return;
1876 if (!Scripts
[0]) return;
1877 if ((skill
>=100) && (skill
!=256) ) skill
= 100;
1878 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapDetectionDiff
) {
1879 SetTrapDetected(1); //probably could be set to the player #?
1883 bool Highlightable::PossibleToSeeTrap() const
1885 return CanDetectTrap();
1888 bool InfoPoint::PossibleToSeeTrap() const
1890 // Only detectable trap-type infopoints.
1891 return (CanDetectTrap() && (Type
== ST_PROXIMITY
) );
1894 bool InfoPoint::CanDetectTrap() const
1896 // Traps can be detected on all types of infopoint, as long
1897 // as the trap is detectable and isn't deactivated.
1898 return ((Flags
&TRAP_DETECTABLE
) && !(Flags
&TRAP_DEACTIVATED
));
1901 // returns true if the infopoint is a PS:T portal
1902 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1903 bool InfoPoint::IsPortal() const
1905 if (Type
!=ST_TRAVEL
) return false;
1906 if (Cursor
!= IE_CURSOR_PORTAL
) return false;
1907 return core
->HasFeature(GF_REVERSE_DOOR
);
1910 //trap that is visible on screen (marked by red)
1911 //if TrapDetected is a bitflag, we could show traps selectively for
1912 //players, really nice for multiplayer
1913 bool Highlightable::VisibleTrap(int see_all
) const
1915 if (!Trapped
) return false;
1916 if (!PossibleToSeeTrap()) return false;
1917 if (!Scripts
[0]) return false;
1918 if (see_all
) return true;
1919 if (TrapDetected
) return true;
1923 //trap that will fire now
1924 bool Highlightable::TriggerTrap(int skill
, ieDword ID
)
1929 //actually this could be script name[0]
1933 if (CanDetectTrap()) {
1934 // this should probably be party members only
1935 if ((skill
>=100) && (skill
!=256) ) skill
= 100;
1936 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapDetectionDiff
) {
1937 SetTrapDetected(1); //probably too late :)
1942 LastTriggerObject
= LastTrigger
= LastEntered
= ID
;
1944 if (!TrapResets()) {
1950 //trap that will fire now
1951 bool InfoPoint::TriggerTrap(int skill
, ieDword ID
)
1953 if (Type
!=ST_PROXIMITY
) {
1956 if (Flags
&TRAP_DEACTIVATED
) {
1960 // we have to set Entered somewhere, here seems best..
1963 } else if (Highlightable::TriggerTrap(skill
, ID
)) {
1965 Flags
|=TRAP_DEACTIVATED
;
1967 // ok, so this is a pain. Entered() trigger checks Trapped,
1968 // so it needs to be kept set. how to do this right?
1975 bool InfoPoint::Entered(Actor
*actor
)
1977 if (outline
->PointIn( actor
->Pos
) ) {
1978 //don't trigger again for this actor
1979 if (!(actor
->GetInternalFlag()&IF_INTRAP
)) {
1983 // why is this here? actors which aren't *in* a trap get IF_INTRAP
1984 // repeatedly unset, so this triggers again and again and again.
1985 // i disabled it for ST_PROXIMITY for now..
1986 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
1989 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
1990 if (Type
== ST_TRAVEL
&& PersonalDistance(TrapLaunch
, actor
)<MAX_OPERATING_DISTANCE
) {
1993 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
1994 // for distance from the outline for travel regions instead?
1995 if (Type
== ST_TRAVEL
&& PersonalDistance(TalkPos
, actor
)<MAX_OPERATING_DISTANCE
) {
1998 if (Flags
&TRAP_USEPOINT
) {
1999 if (PersonalDistance(UsePoint
, actor
)<MAX_OPERATING_DISTANCE
) {
2005 if (Type
==ST_TRAVEL
) {
2009 if (actor
->InParty
|| (Flags
&TRAP_NPC
) ) {
2010 //no need to avoid a travel trigger
2013 if (TriggerTrap(0, actor
->GetID()) ) {
2020 void InfoPoint::DebugDump() const
2024 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2027 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2030 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2033 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2036 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected
, YESNO(Trapped
));
2037 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2039 const char *name
= "NONE";
2041 name
= Scripts
[0]->GetName();
2043 printf( "Script: %s, Key: %s, Dialog: %s\n", name
, KeyResRef
, Dialog
);
2044 printf( "Active: %s\n", YESNO(InternalFlags
&IF_ACTIVE
));
2047 /*******************
2049 *******************/
2051 Container::Container(void)
2052 : Highlightable( ST_CONTAINER
)
2057 TrapDetectionDiff
= 0;
2058 TrapRemovalDiff
= 0;
2061 inventory
.SetInventoryType(INVENTORY_HEAP
);
2062 // NULL should be 0 for this
2063 memset (groundicons
, 0, sizeof(groundicons
) );
2064 groundiconcover
= 0;
2067 void Container::FreeGroundIcons()
2069 Video
* video
= core
->GetVideoDriver();
2071 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2072 if (groundicons
[i
]) {
2073 video
->FreeSprite( groundicons
[i
] );
2074 groundicons
[i
]=NULL
;
2077 delete groundiconcover
;
2078 groundiconcover
= 0;
2081 Container::~Container()
2086 void Container::DrawPile(bool highlight
, Region screen
, Color tint
)
2088 Video
* video
= core
->GetVideoDriver();
2089 CreateGroundIconCover();
2090 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2091 if (groundicons
[i
]) {
2092 //draw it with highlight
2093 video
->BlitGameSprite(groundicons
[i
],
2094 screen
.x
+ Pos
.x
, screen
.y
+ Pos
.y
,
2095 BLIT_TINTED
| (highlight
? 0:BLIT_NOSHADOW
),
2096 tint
, groundiconcover
);
2101 // create the SpriteCover for the groundicons
2102 void Container::CreateGroundIconCover()
2110 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2111 if (groundicons
[i
]) {
2112 Sprite2D
& spr
= *groundicons
[i
];
2113 if (xpos
< spr
.XPos
) {
2114 width
+= spr
.XPos
- xpos
;
2117 if (ypos
< spr
.YPos
) {
2118 height
+= spr
.YPos
- ypos
;
2121 if (width
-xpos
< spr
.Width
-spr
.XPos
) {
2122 width
= spr
.Width
-spr
.XPos
+xpos
;
2124 if (height
-ypos
< spr
.Height
-spr
.YPos
) {
2125 height
= spr
.Height
-spr
.YPos
+ypos
;
2130 if (!groundiconcover
||
2131 !groundiconcover
->Covers(Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
))
2133 delete groundiconcover
;
2134 groundiconcover
= 0;
2135 if (width
*height
> 0) {
2136 groundiconcover
= GetCurrentArea()->BuildSpriteCover
2137 (Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
, WantDither());
2142 // TODO: remove this checking code eventually
2143 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2144 if (groundicons
[i
]) {
2145 Sprite2D
& spr
= *groundicons
[i
];
2146 assert(groundiconcover
->Covers(Pos
.x
, Pos
.y
, spr
.XPos
, spr
.YPos
, spr
.Width
, spr
.Height
));
2152 void Container::SetContainerLocked(bool lock
)
2157 Flags
&=~CONT_LOCKED
;
2161 //This function doesn't exist in the original IE, destroys a container
2162 //turning it to a ground pile
2163 void Container::DestroyContainer()
2165 //it is already a groundpile?
2166 if (Type
== IE_CONTAINER_PILE
)
2168 Type
= IE_CONTAINER_PILE
;
2169 RefreshGroundIcons();
2170 //probably we should stop the script or trigger it, whatever
2173 //Takes an item from the container's inventory and returns its pointer
2174 CREItem
*Container::RemoveItem(unsigned int idx
, unsigned int count
)
2176 CREItem
*ret
= inventory
.RemoveItem(idx
, count
);
2177 //we just took the 3. or less item, groundpile changed
2178 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<3)) {
2179 RefreshGroundIcons();
2184 //Adds an item to the container's inventory
2185 //containers always have enough capacity (so far), thus we always return 2
2186 int Container::AddItem(CREItem
*item
)
2188 inventory
.AddItem(item
);
2189 //we just added a 3. or less item, groundpile changed
2190 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<4)) {
2191 RefreshGroundIcons();
2196 void Container::RefreshGroundIcons()
2198 int i
= inventory
.GetSlotCount();
2199 if (i
>MAX_GROUND_ICON_DRAWN
)
2200 i
= MAX_GROUND_ICON_DRAWN
;
2203 CREItem
*slot
= inventory
.GetSlotItem(i
); //borrowed reference
2204 Item
*itm
= gamedata
->GetItem( slot
->ItemResRef
); //cached reference
2205 //well, this is required in PST, needs more work if some other
2206 //game is broken by not using -1,0
2207 groundicons
[i
] = gamedata
->GetBAMSprite( itm
->GroundIcon
, 0, 0 );
2208 gamedata
->FreeItem( itm
, slot
->ItemResRef
); //decref
2212 //used for ground piles
2213 int Container::WantDither()
2215 //if pile is highlighted, always dither it
2217 return 2; //dither me if you want
2219 //if pile isn't highlighted, dither it if the polygon wants
2223 int Container::IsOpen() const
2225 if (Flags
&CONT_LOCKED
) {
2231 void Container::TryPickLock(Actor
*actor
)
2233 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
2234 if (LockDifficulty
== 100) {
2235 core
->DisplayConstantStringName(STR_CONT_NOPICK
, 0xbcefbc, actor
);
2237 core
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
2238 LastPickLockFailed
= actor
->GetID();
2242 SetContainerLocked(false);
2243 core
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
2244 LastUnlocked
= actor
->GetID();
2246 actor
->AddExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
2249 void Container::TryBashLock(Actor
*actor
)
2251 //Get the strength bonus agains lock difficulty
2252 int str
= actor
->GetStat(IE_STR
);
2253 int strEx
= actor
->GetStat(IE_STREXTRA
);
2254 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
2256 //bonus will never reach 100
2257 if(bonus
< LockDifficulty
) {
2258 core
->DisplayConstantStringName(STR_CONTBASH_FAIL
, 0xbcefbc, actor
);
2262 core
->DisplayConstantStringName(STR_CONTBASH_DONE
, 0xd7d7be, actor
);
2263 SetContainerLocked(false);
2264 //Is this really useful ?
2265 LastUnlocked
= actor
->GetID();
2269 void Container::DebugDump() const
2271 printf( "Debugdump of Container %s\n", GetScriptName() );
2272 printf( "Type: %d, LockDifficulty: %d\n", Type
, LockDifficulty
);
2273 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags
, YESNO(Trapped
), TrapDetected
);
2274 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2276 const char *name
= "NONE";
2278 name
= Scripts
[0]->GetName();
2280 printf( "Script: %s, Key: %s\n", name
, KeyResRef
);
2281 // FIXME: const_cast
2282 const_cast<Inventory
&>(inventory
).dump();
2285 bool Container::TryUnlock(Actor
*actor
) {
2286 if (!(Flags
&CONT_LOCKED
)) return true;
2288 return Highlightable::TryUnlock(actor
, false);