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"
26 #include "DisplayMessage.h"
30 #include "Interface.h"
33 #include "Projectile.h"
35 #include "SpriteCover.h"
38 #include "GUI/GameControl.h"
43 #define YESNO(x) ( (x)?"Yes":"No")
45 /***********************
47 ***********************/
48 Scriptable::Scriptable(ScriptableType type
)
52 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
56 overHeadTextPos
.empty();
58 timeStartDisplaying
= 0;
60 TriggerID
= 0; //used by SendTrigger
61 LastTriggerObject
= LastTrigger
= 0;
67 LastPickLockFailed
= 0;
70 CurrentActionState
= 0;
71 CurrentActionTarget
= 0;
72 CurrentActionInterruptable
= true;
73 UnselectableTimer
= 0;
74 startTime
= 0; //executing scripts
75 lastRunTime
= 0; //evaluating scripts
79 interval
= ( 1000 / AI_UPDATE_TIME
);
82 if (Type
== ST_ACTOR
) {
83 InternalFlags
= IF_VISIBLE
| IF_ONCREATION
| IF_USEDSAVE
;
85 InternalFlags
= IF_ACTIVE
| IF_VISIBLE
| IF_ONCREATION
| IF_NOINT
;
92 LastSpellOnMe
= 0xffffffff;
94 LastSpellSeen
= 0xffffffff;
96 LastTargetPos
.empty();
97 locals
= new Variables();
98 locals
->SetType( GEM_VARIABLES_INT
);
99 locals
->ParseKey( 1 );
102 memset( script_timers
,0, sizeof(script_timers
));
105 Scriptable::~Scriptable(void)
108 ReleaseCurrentAction();
111 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
113 delete( Scripts
[i
] );
117 core
->FreeString( overHeadText
);
124 void Scriptable::SetScriptName(const char* text
)
126 //if (text && text[0]) { //this leaves some uninitialized bytes
127 //lets hope this won't break anything
129 strnspccpy( scriptName
, text
, 32 );
133 /** Gets the DeathVariable */
134 const char* Scriptable::GetScriptName(void) const
139 Map
* Scriptable::GetCurrentArea() const
141 //this could be NULL, always check it
145 void Scriptable::SetMap(Map
*map
)
148 printMessage("Scriptable","Null map set!\n",LIGHT_RED
);
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
);
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
);
181 if (Scripts
[index
] ) {
182 delete Scripts
[index
];
184 Scripts
[index
] = script
;
187 void Scriptable::DisplayHeadText(const char* text
)
190 core
->FreeString( overHeadText
);
192 overHeadText
= (char *) text
;
193 overHeadTextPos
.empty();
195 timeStartDisplaying
= core
->GetGame()->Ticks
;
199 timeStartDisplaying
= 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
;
221 time
-= timeStartDisplaying
;
223 Font
* font
= core
->GetFont( 1 );
224 if (time
>= MAX_DELAY
) {
228 time
= (MAX_DELAY
-time
)/10;
230 const Color overHeadColor
= {time
,time
,time
,time
};
231 palette
= core
->CreatePalette(overHeadColor
, black
);
236 if (Type
== ST_ACTOR
) {
237 cs
= ((Selectable
*) this)->size
*50;
241 if (overHeadTextPos
.isempty()) {
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()
265 bool Scriptable::IsPC() const
267 if(Type
== ST_ACTOR
) {
268 if (((Actor
*) this)->GetStat(IE_EA
) <= EA_CHARMED
) {
275 void Scriptable::ExecuteScript(int scriptCount
)
277 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
278 if ((Type
!= ST_AREA
) && (core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
)) {
282 if ((InternalFlags
& IF_NOINT
) && (CurrentAction
|| GetNextAction())) {
286 if (!CurrentActionInterruptable
) {
287 if (!CurrentAction
&& !GetNextAction()) abort();
291 // only allow death scripts to run once, hopefully?
292 // this is probably terrible logic which needs moving elsewhere
293 if ((lastRunTime
!= 0) && (InternalFlags
& IF_JUSTDIED
)) {
297 ieDword thisTime
= core
->GetGame()->Ticks
;
298 if (( thisTime
- lastRunTime
) < 1000) {
302 lastDelay
= lastRunTime
;
303 lastRunTime
= thisTime
;
307 bool continuing
= false, done
= false;
308 for (int i
= 0;i
<scriptCount
;i
++) {
309 //disable AI script level for actors in party when the player disabled them
310 if ((i
== AI_SCRIPT_LEVEL
) && Type
== ST_ACTOR
&& ((Actor
*) this)->InParty
) {
311 if (core
->GetGame()->ControlStatus
&CS_PARTY_AI
) {
316 GameScript
*Script
= Scripts
[i
];
318 alive
|= Script
->Update(&continuing
, &done
);
321 /* scripts are not concurrent, see WAITPC override script for example */
324 if (alive
&& UnselectableTimer
) {
326 if (!UnselectableTimer
) {
327 if (Type
== ST_ACTOR
) {
328 ((Actor
*) this)->SetCircleSize();
334 void Scriptable::AddAction(Action
* aC
)
337 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
340 InternalFlags
|=IF_ACTIVE
;
341 actionQueue
.push_back( aC
);
345 void Scriptable::AddActionInFront(Action
* aC
)
348 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
351 InternalFlags
|=IF_ACTIVE
;
352 actionQueue
.push_front( aC
);
356 Action
* Scriptable::GetNextAction() const
358 if (actionQueue
.size() == 0) {
361 return actionQueue
.front();
364 Action
* Scriptable::PopNextAction()
366 if (actionQueue
.size() == 0) {
369 Action
* aC
= actionQueue
.front();
370 actionQueue
.pop_front();
374 void Scriptable::ClearActions()
376 ReleaseCurrentAction();
377 for (unsigned int i
= 0; i
< actionQueue
.size(); i
++) {
378 Action
* aC
= actionQueue
.front();
379 actionQueue
.pop_front();
384 playDeadCounter
= 0; // i'm not sure about this
386 //clear the triggers as fast as possible when queue ended?
389 if (Type
== ST_ACTOR
) {
396 void Scriptable::ReleaseCurrentAction()
399 CurrentAction
->Release();
400 CurrentAction
= NULL
;
403 CurrentActionState
= 0;
404 CurrentActionTarget
= 0;
405 CurrentActionInterruptable
= true;
408 ieWord
Scriptable::GetGlobalID()
410 if (Type
== ST_ACTOR
) {
411 Actor
*actor
= (Actor
*) this;
412 return actor
->globalID
;
417 void Scriptable::ProcessActions(bool force
)
419 unsigned long thisTime
= core
->GetGame()->Ticks
;
421 if (!force
&& (( thisTime
- startTime
) < interval
)) {
424 startTime
= thisTime
;
425 if (playDeadCounter
) {
427 if (!playDeadCounter
) {
428 Movable
* mov
= ( Movable
* ) this;
429 mov
->SetStance( IE_ANI_GET_UP
);
434 if (WaitCounter
) return;
438 CurrentActionInterruptable
= true;
439 if (!CurrentAction
) {
440 CurrentAction
= PopNextAction();
442 if (!CurrentAction
) {
447 //removing the triggers at the end of the
452 GameScript::ExecuteAction( this, CurrentAction
);
453 //break execution in case of a Wait flag
455 //clear triggers while waiting
459 //break execution in case of blocking action
463 //break execution in case of movement
464 //we should not actually break here, or else fix waypoints
469 //most likely the best place to clear triggers is here
470 //queue is empty, or there is a looong action subject to break
472 if (InternalFlags
&IF_IDLE
) {
477 bool Scriptable::InMove() const
479 if (Type
!=ST_ACTOR
) {
482 Movable
*me
= (Movable
*) this;
483 return me
->GetNextStep()!=NULL
;
486 void Scriptable::SetWait(unsigned long time
)
491 unsigned long Scriptable::GetWait() const
496 Scriptable
*Scriptable::GetCutsceneID() const
501 void Scriptable::LeaveDialog()
503 InternalFlags
|=IF_WASINDIALOG
;
506 //this ends cutscene mode for this Sender
507 void Scriptable::ClearCutsceneID()
510 InternalFlags
&= ~IF_CUTSCENEID
;
513 //if the cutsceneID doesn't exist, we simply skip the action
514 //because the cutscene script executer DOESN'T get hijacked
515 void Scriptable::SetCutsceneID(Scriptable
*csid
)
518 InternalFlags
|= IF_CUTSCENEID
;
521 void Scriptable::Hide()
523 InternalFlags
&=~(IF_VISIBLE
);
526 void Scriptable::Unhide()
528 InternalFlags
|= IF_VISIBLE
;
531 void Scriptable::Interrupt()
533 InternalFlags
&= ~IF_NOINT
;
536 void Scriptable::NoInterrupt()
538 InternalFlags
|= IF_NOINT
;
541 //also turning off the idle flag so it won't run continuously
542 void Scriptable::Deactivate()
544 InternalFlags
&=~(IF_ACTIVE
|IF_IDLE
);
547 //turning off the not interruptable flag, actions should reenable it themselves
548 //also turning off the idle flag
549 //heh, no, i wonder why did i touch the interruptable flag here
550 void Scriptable::Activate()
552 InternalFlags
|= IF_ACTIVE
;
553 InternalFlags
&= ~IF_IDLE
;
556 void Scriptable::PartyRested()
558 InternalFlags
|=IF_PARTYRESTED
;
561 ieDword
Scriptable::GetInternalFlag()
563 return InternalFlags
;
566 void Scriptable::InitTriggers()
572 void Scriptable::ClearTriggers()
574 for (TriggerObjects::iterator m
= tolist
.begin(); m
!= tolist
.end (); m
++) {
580 if (bittriggers
& BT_DIE
) {
581 InternalFlags
&= ~IF_JUSTDIED
;
583 if (bittriggers
& BT_ONCREATION
) {
584 InternalFlags
&= ~IF_ONCREATION
;
586 if (bittriggers
& BT_BECAMEVISIBLE
) {
587 InternalFlags
&= ~IF_BECAMEVISIBLE
;
589 if (bittriggers
& BT_PARTYRESTED
) {
590 InternalFlags
&= ~IF_PARTYRESTED
;
592 if (bittriggers
& BT_WASINDIALOG
) {
593 InternalFlags
&= ~IF_WASINDIALOG
;
595 if (bittriggers
& BT_PARTYRESTED
) {
596 InternalFlags
&= ~IF_PARTYRESTED
;
601 void Scriptable::SetBitTrigger(ieDword bittrigger
)
603 bittriggers
|= bittrigger
;
606 void Scriptable::AddTrigger(ieDword
*actorref
)
608 tolist
.push_back(actorref
);
611 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef
)
613 if (Type
== ST_ACTOR
) {
614 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
617 if (SpellHeader
== -1) {
618 LastTargetPos
.empty();
622 if (LastTargetPos
.isempty()) {
627 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
628 //create projectile from known spellheader
629 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
632 pro
->SetCaster(GetGlobalID());
634 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
635 // try and make projectiles start from the right trap position
636 // see the traps in the duergar/assassin battle in bg2 dungeon
637 // see also function below - maybe we should fix Pos instead
638 origin
= ((InfoPoint
*)this)->TrapLaunch
;
640 GetCurrentArea()->AddProjectile(pro
, origin
, LastTargetPos
);
643 LastTargetPos
.empty();
646 void Scriptable::CastSpellEnd( const ieResRef SpellResRef
)
648 if (Type
== ST_ACTOR
) {
649 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
652 if (SpellHeader
== -1) {
660 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
661 //create projectile from known spellheader
662 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
664 pro
->SetCaster(GetGlobalID());
666 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
667 // try and make projectiles start from the right trap position
668 // see the traps in the duergar/assassin battle in bg2 dungeon
669 // see also function above - maybe we should fix Pos instead
670 origin
= ((InfoPoint
*)this)->TrapLaunch
;
673 GetCurrentArea()->AddProjectile(pro
, origin
, LastTarget
);
675 GetCurrentArea()->AddProjectile(pro
, origin
, LastTargetPos
);
678 ieDword spellnum
=ResolveSpellNumber( SpellResRef
);
679 if (spellnum
!=0xffffffff) {
680 area
->SeeSpellCast(this, spellnum
);
682 Scriptable
*target
= area
->GetActorByGlobalID(LastTarget
);
683 if (target
&& (Type
==ST_ACTOR
) ) {
684 Actor
*me
= (Actor
*) this;
685 target
->LastSpellOnMe
= spellnum
;
686 target
->LastCasterOnMe
= me
->GetID();
687 me
->CureInvisibility();
695 gamedata
->FreeSpell(spl
, SpellResRef
, false);
697 LastTargetPos
.empty();
700 //set target as point
701 //if spell needs to be depleted, do it
702 //if spell is illegal stop casting
703 int Scriptable::CastSpellPoint( const ieResRef SpellResRef
, const Point
&target
, bool deplete
, bool instant
)
706 LastTargetPos
.empty();
707 if (Type
== ST_ACTOR
) {
708 Actor
*actor
= (Actor
*) this;
709 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
713 LastTargetPos
= target
;
714 return SpellCast(SpellResRef
, instant
);
717 //set target as actor (if target isn't actor, use its position)
718 //if spell needs to be depleted, do it
719 //if spell is illegal stop casting
720 int Scriptable::CastSpell( const ieResRef SpellResRef
, Scriptable
* target
, bool deplete
, bool instant
)
723 LastTargetPos
.empty();
724 if (Type
== ST_ACTOR
) {
725 Actor
*actor
= (Actor
*) this;
726 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
731 if (!target
) target
= this;
733 LastTargetPos
= target
->Pos
;
734 if (target
->Type
==ST_ACTOR
) {
735 LastTarget
= target
->GetGlobalID();
737 return SpellCast(SpellResRef
, instant
);
740 //start spellcasting (common part)
741 int Scriptable::SpellCast(const ieResRef SpellResRef
, bool instant
)
743 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
749 if (Type
== ST_ACTOR
) {
750 Actor
*actor
= (Actor
*) this;
751 //The ext. index is here to calculate the casting time
752 int level
= actor
->GetXPLevel(true);
753 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
754 level
+= actor
->CastingLevelBonus(level
, spl
->SpellType
);
755 SpellHeader
= spl
->GetHeaderIndexFromLevel(level
);
760 SPLExtHeader
*header
= spl
->GetExtHeader(SpellHeader
);
761 int casting_time
= (int)header
->CastingTime
;
762 // how does this work for non-actors exactly?
763 if (Type
== ST_ACTOR
) {
764 // The mental speed effect can shorten or lengthen the casting time.
765 casting_time
-= (int)((Actor
*) this)->GetStat(IE_MENTALSPEED
);
766 if (casting_time
< 0) casting_time
= 0;
768 // this is a guess which seems approximately right so far
769 int duration
= (casting_time
*ROUND_SIZE
) / 10;
774 if (Type
== ST_ACTOR
) {
775 Actor
*actor
= (Actor
*) this;
776 EffectQueue
*fxqueue
= spl
->GetEffectBlock(this, this->Pos
, -1);
777 spl
->AddCastingGlow(fxqueue
, duration
);
778 fxqueue
->SetOwner(actor
);
779 fxqueue
->AddAllEffects(actor
, actor
->Pos
);
783 gamedata
->FreeSpell(spl
, SpellResRef
, false);
787 bool Scriptable::TimerActive(ieDword ID
)
792 if (script_timers
[ID
]) {
798 bool Scriptable::TimerExpired(ieDword ID
)
803 if (script_timers
[ID
] && script_timers
[ID
] < core
->GetGame()->GameTime
) {
804 // expired timers become inactive after being checked
805 script_timers
[ID
] = 0;
811 void Scriptable::StartTimer(ieDword ID
, ieDword expiration
)
814 printMessage("Scriptable", " ", RED
);
815 printf("Timer id %d exceeded MAX_TIMER %d\n", ID
, MAX_TIMER
);
818 script_timers
[ID
]= core
->GetGame()->GameTime
+ expiration
*AI_UPDATE_TIME
;
821 /********************
823 ********************/
825 Selectable::Selectable(ScriptableType type
)
832 circleBitmap
[0] = NULL
;
833 circleBitmap
[1] = NULL
;
836 void Selectable::SetSpriteCover(SpriteCover
* c
)
842 Selectable::~Selectable(void)
847 void Selectable::SetBBox(const Region
&newBBox
)
852 static const unsigned long tp_steps
[8]={3,2,1,0,1,2,3,4};
854 void Selectable::DrawCircle(const Region
&vp
)
856 /* BG2 colours ground circles as follows:
857 dark green for unselected party members
858 bright green for selected party members
859 flashing green/white for a party member the mouse is over
860 bright red for enemies
861 yellow for panicked actors
862 flashing red/white for enemies the mouse is over
863 flashing cyan/white for neutrals the mouse is over
870 Color
* col
= &selectedColor
;
871 Sprite2D
* sprite
= circleBitmap
[0];
874 sprite
= circleBitmap
[1];
876 //doing a time dependent flashing of colors
877 //if it is too fast, increase the 6 to 7
880 step
= tp_steps
[(step
>> 6) & 7];
882 mix
.r
= (overColor
.r
*step
+selectedColor
.r
*(8-step
))/8;
883 mix
.g
= (overColor
.g
*step
+selectedColor
.g
*(8-step
))/8;
884 mix
.b
= (overColor
.b
*step
+selectedColor
.b
*(8-step
))/8;
891 core
->GetVideoDriver()->BlitSprite( sprite
, Pos
.x
- vp
.x
, Pos
.y
- vp
.y
, true );
893 // for size >= 2, radii are (size-1)*16, (size-1)*12
894 // for size == 1, radii are 12, 9
895 int csize
= (size
- 1) * 4;
896 if (csize
< 4) csize
= 3;
897 core
->GetVideoDriver()->DrawEllipse( (ieWord
) (Pos
.x
- vp
.x
), (ieWord
) (Pos
.y
- vp
.y
),
898 (ieWord
) (csize
* 4), (ieWord
) (csize
* 3), *col
);
902 // Check if P is over our ground circle
903 bool Selectable::IsOver(const Point
&P
) const
906 if (csize
< 2) csize
= 2;
908 int dx
= P
.x
- Pos
.x
;
909 int dy
= P
.y
- Pos
.y
;
911 // check rectangle first
912 if (dx
< -(csize
-1)*16 || dx
> (csize
-1)*16) return false;
913 if (dy
< -(csize
-1)*12 || dy
> (csize
-1)*12) return false;
915 // then check ellipse
916 int r
= 9*dx
*dx
+ 16*dy
*dy
; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
918 return (r
<= 48*48*(csize
-1)*(csize
-1));
921 bool Selectable::IsSelected() const
923 return Selected
== 1;
926 void Selectable::SetOver(bool over
)
931 //don't call this function after rendering the cover and before the
932 //blitting of the sprite or bad things will happen :)
933 void Selectable::Select(int Value
)
935 if (Selected
!=0x80 || Value
!=1) {
936 Selected
= (ieWord
) Value
;
938 //forcing regeneration of the cover
939 SetSpriteCover(NULL
);
942 void Selectable::SetCircle(int circlesize
, const Color
&color
, Sprite2D
* normal_circle
, Sprite2D
* selected_circle
)
945 selectedColor
= color
;
946 overColor
.r
= color
.r
>> 1;
947 overColor
.g
= color
.g
>> 1;
948 overColor
.b
= color
.b
>> 1;
949 overColor
.a
= color
.a
;
950 circleBitmap
[0] = normal_circle
;
951 circleBitmap
[1] = selected_circle
;
955 int Selectable::WantDither()
957 //if dithering is disabled globally, don't do it
958 if (core
->FogOfWar
&4) {
961 //if actor is dead, dither it if polygon wants
965 //if actor is selected dither it
972 /***********************
973 * Highlightable Class *
974 ***********************/
976 Highlightable::Highlightable(ScriptableType type
)
981 Cursor
= IE_CURSOR_NORMAL
;
985 Highlightable::~Highlightable(void)
992 bool Highlightable::IsOver(const Point
&Pos
) const
997 return outline
->PointIn( Pos
);
1000 void Highlightable::DrawOutline() const
1005 core
->GetVideoDriver()->DrawPolyline( outline
, outlineColor
, true );
1008 void Highlightable::SetCursor(unsigned char CursorIndex
)
1010 Cursor
= CursorIndex
;
1013 bool Highlightable::TryUnlock(Actor
*actor
, bool removekey
) {
1014 const char *Key
= GetKey();
1015 Actor
*haskey
= NULL
;
1017 if (Key
&& actor
->InParty
) {
1018 Game
*game
= core
->GetGame();
1019 //allow unlock when the key is on any partymember
1020 for (int idx
= 0; idx
< game
->GetPartySize(false); idx
++) {
1021 Actor
*pc
= game
->FindPC(idx
+ 1);
1024 if (pc
->inventory
.HasItem(Key
,0) ) {
1030 //actor is not in party, check only actor
1031 if (actor
->inventory
.HasItem(Key
,0) ) {
1041 CREItem
*item
= NULL
;
1042 haskey
->inventory
.RemoveItem(Key
,0,&item
);
1043 //the item should always be existing!!!
1057 Movable::Movable(ScriptableType type
)
1058 : Selectable( type
)
1069 AttackMovements
[0] = 100;
1070 AttackMovements
[1] = 0;
1071 AttackMovements
[2] = 0;
1074 Movable::~Movable(void)
1081 int Movable::GetPathLength()
1083 PathNode
*node
= GetNextStep(0);
1085 while (node
->Next
) {
1092 PathNode
*Movable::GetNextStep(int x
)
1095 DoStep((unsigned int) ~0);
1097 PathNode
*node
= step
;
1098 while(node
&& x
--) {
1104 Point
Movable::GetMostLikelyPosition()
1110 //actually, sometimes middle path would be better, if
1111 //we stand in Destination already
1112 int halfway
= GetPathLength()/2;
1113 PathNode
*node
= GetNextStep(halfway
);
1115 return Point((ieWord
) ((node
->x
*16)+8), (ieWord
) ((node
->y
*12)+6) );
1120 void Movable::SetStance(unsigned int arg
)
1122 //don't modify stance from dead back to anything if the actor is dead
1123 if ((StanceID
==IE_ANI_TWITCH
|| StanceID
==IE_ANI_DIE
) && (arg
!=IE_ANI_TWITCH
) ) {
1124 if (GetInternalFlag()&IF_REALLYDIED
) {
1125 printMessage("Movable","Stance overridden by death\n", YELLOW
);
1130 if (arg
<MAX_ANIMS
) {
1131 StanceID
=(unsigned char) arg
;
1133 if (StanceID
== IE_ANI_ATTACK
) {
1134 // Set stance to a random attack animation
1136 int random
= rand()%100;
1137 if (random
< AttackMovements
[0]) {
1138 StanceID
= IE_ANI_ATTACK_BACKSLASH
;
1139 } else if (random
< AttackMovements
[0] + AttackMovements
[1]) {
1140 StanceID
= IE_ANI_ATTACK_SLASH
;
1142 StanceID
= IE_ANI_ATTACK_JAB
;
1147 StanceID
=IE_ANI_AWAKE
; //
1148 printf("Tried to set invalid stance id (%u)\n", arg
);
1152 void Movable::SetAttackMoveChances(ieWord
*amc
)
1154 AttackMovements
[0]=amc
[0];
1155 AttackMovements
[1]=amc
[1];
1156 AttackMovements
[2]=amc
[2];
1161 //this could be used for WingBuffet as well
1162 void Movable::MoveLine(int steps
, int Pass
, ieDword orient
)
1164 //remove previous path
1171 path
= area
->GetLine( p
, steps
, orient
, Pass
);
1174 void AdjustPositionTowards(Point
&Pos
, ieDword time_diff
, unsigned int walk_speed
, short srcx
, short srcy
, short destx
, short desty
) {
1176 Pos
.x
+= ( unsigned short )
1177 ( ( ( ( ( destx
* 16 ) + 8 ) - Pos
.x
) * ( time_diff
) ) / walk_speed
);
1179 Pos
.x
-= ( unsigned short )
1180 ( ( ( Pos
.x
- ( ( destx
* 16 ) + 8 ) ) * ( time_diff
) ) / walk_speed
);
1182 Pos
.y
+= ( unsigned short )
1183 ( ( ( ( ( desty
* 12 ) + 6 ) - Pos
.y
) * ( time_diff
) ) / walk_speed
);
1185 Pos
.y
-= ( unsigned short )
1186 ( ( ( Pos
.y
- ( ( desty
* 12 ) + 6 ) ) * ( time_diff
) ) / walk_speed
);
1190 // returns whether we made all pending steps (so, false if we must be called again this tick)
1191 // we can't just do them all here because the caller might have to update searchmap etc
1192 bool Movable::DoStep(unsigned int walk_speed
, ieDword time
)
1197 if (!time
) time
= core
->GetGame()->Ticks
;
1199 // zero speed: no movement
1200 timeStartStep
= time
;
1201 StanceID
= IE_ANI_READY
;
1206 timeStartStep
= time
;
1207 } else if (step
->Next
&& (( time
- timeStartStep
) >= walk_speed
)) {
1208 //printf("[New Step] : Orientation = %d\n", step->orient);
1210 timeStartStep
= timeStartStep
+ walk_speed
;
1212 SetOrientation (step
->orient
, false);
1213 StanceID
= IE_ANI_WALK
;
1214 if ((Type
== ST_ACTOR
) && (InternalFlags
& IF_RUNNING
)) {
1215 StanceID
= IE_ANI_RUN
;
1217 Pos
.x
= ( step
->x
* 16 ) + 8;
1218 Pos
.y
= ( step
->y
* 12 ) + 6;
1220 // we reached our destination, we are done
1222 NewOrientation
= Orientation
;
1223 //since clearpath no longer sets currentaction to NULL
1225 //no we don't, action is responsible for releasing itself
1226 //ReleaseCurrentAction();
1229 if (( time
- timeStartStep
) >= walk_speed
) {
1230 // we didn't finish all pending steps, yet
1233 AdjustPositionTowards(Pos
, time
- timeStartStep
, walk_speed
, step
->x
, step
->y
, step
->Next
->x
, step
->Next
->y
);
1237 void Movable::AddWayPoint(const Point
&Des
)
1244 //it is tempting to use 'step' here, as it could
1245 //be about half of the current path already
1246 PathNode
*endNode
= path
;
1247 while(endNode
->Next
) {
1248 endNode
= endNode
->Next
;
1250 Point
p(endNode
->x
, endNode
->y
);
1251 area
->ClearSearchMapFor(this);
1252 PathNode
*path2
= area
->FindPath( p
, Des
, size
);
1253 endNode
->Next
= path2
;
1254 //probably it is wise to connect it both ways?
1255 path2
->Parent
= endNode
;
1258 void Movable::FixPosition()
1260 if (Type
!=ST_ACTOR
) {
1263 Actor
*actor
= (Actor
*) this;
1264 if (actor
->GetStat(IE_DONOTJUMP
)&DNJ_BIRD
) {
1267 //before fixposition, you should remove own shadow
1268 area
->ClearSearchMapFor(this);
1271 GetCurrentArea()->AdjustPosition(Pos
);
1276 void Movable::WalkTo(const Point
&Des
, int distance
)
1280 // maybe caller should be responsible for this
1281 if ((Des
.x
/16 == Pos
.x
/16) && (Des
.y
/12 == Pos
.y
/12)) {
1286 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1287 PathNode
*prev_step
= NULL
;
1288 unsigned char old_stance
= StanceID
;
1289 if (step
&& step
->Next
) {
1290 // don't interrupt in the middle of a step; path from the next one
1291 prev_step
= new PathNode(*step
);
1292 from
.x
= ( step
->Next
->x
* 16 ) + 8;
1293 from
.y
= ( step
->Next
->y
* 12 ) + 6;
1301 area
->ClearSearchMapFor(this);
1303 path
= area
->FindPathNear( from
, Des
, size
, distance
);
1305 path
= area
->FindPath( from
, Des
, size
, distance
);
1307 //ClearPath sets destination, so Destination must be set after it
1308 //also we should set Destination only if there is a walkable path
1313 // we want to smoothly continue, please
1314 // this all needs more thought! but it seems to work okay
1315 StanceID
= old_stance
;
1318 // this is a terrible hack to make up for the
1319 // pathfinder orienting the first node wrong
1320 // should be fixed in pathfinder and not here!
1322 next
.x
= path
->x
; next
.y
= path
->y
;
1323 follow
.x
= path
->Next
->x
;
1324 follow
.y
= path
->Next
->y
;
1325 path
->orient
= GetOrient(follow
, next
);
1328 // then put the prev_step at the beginning of the path
1329 prev_step
->Next
= path
;
1330 path
->Parent
= prev_step
;
1338 delete( prev_step
);
1344 void Movable::RunAwayFrom(const Point
&Des
, int PathLength
, int flags
)
1347 area
->ClearSearchMapFor(this);
1348 path
= area
->RunAway( Pos
, Des
, size
, PathLength
, flags
);
1351 void Movable::RandomWalk(bool can_stop
, bool run
)
1356 //if not continous random walk, then stops for a while
1357 if (can_stop
&& (rand()&3) ) {
1358 SetWait((rand()&7)+7);
1362 InternalFlags
|=IF_RUNNING
;
1364 //the commenting-out of the clear search map call was removed in 0.4.0
1365 //if you want to put it back for some reason, check
1366 //if the searchmap is not eaten up
1367 area
->ClearSearchMapFor(this);
1370 //selecting points around a circle's edge around actor (didn't work better)
1371 //int x = core->Roll(1,100,-50);
1373 //p.y+=(int) sqrt(100-x*x);
1375 //selecting points in a square around actor
1376 p
.x
+=core
->Roll(1,50,-25);
1377 p
.y
+=core
->Roll(1,50,-25);
1378 //the 5th parameter is controlling the orientation of the actor
1379 //0 - back away, 1 - face direction
1380 path
= area
->RunAway( Pos
, p
, size
, 50, 1 );
1383 void Movable::MoveTo(const Point
&Des
)
1385 area
->ClearSearchMapFor(this);
1388 if (BlocksSearchMap()) {
1389 area
->BlockSearchMap( Pos
, size
, IsPC()?PATH_MAP_PC
:PATH_MAP_NPC
);
1393 void Movable::ClearPath()
1395 //this is to make sure attackers come to us
1396 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1397 //is set before ClearPath
1399 if (StanceID
==IE_ANI_WALK
|| StanceID
==IE_ANI_RUN
) {
1400 StanceID
= IE_ANI_AWAKE
;
1402 InternalFlags
&=~IF_NORECTICLE
;
1403 PathNode
* thisNode
= path
;
1405 PathNode
* nextNode
= thisNode
->Next
;
1407 thisNode
= nextNode
;
1411 //don't call ReleaseCurrentAction
1414 void Movable::DrawTargetPoint(const Region
&vp
)
1416 if (!path
|| !Selected
|| (InternalFlags
&IF_NORECTICLE
) )
1419 // recticles are never drawn in cutscenes
1420 if ((core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
))
1423 // generates "step" from sequence 3 2 1 0 1 2 3 4
1424 // updated each 1/15 sec
1427 step
= tp_steps
[(step
>> 6) & 7];
1430 int csize
= (size
- 1) * 4;
1431 if (csize
< 4) csize
= 3;
1433 /* segments should not go outside selection radius */
1434 unsigned short xradius
= (csize
* 4) - 5;
1435 unsigned short yradius
= (csize
* 3) - 5;
1436 ieWord xcentre
= (ieWord
) (Destination
.x
- vp
.x
);
1437 ieWord ycentre
= (ieWord
) (Destination
.y
- vp
.y
);
1439 // TODO: 0.5 and 0.7 are pretty much random values
1441 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
+ step
, ycentre
, xradius
,
1442 yradius
, selectedColor
, -0.5, 0.5 );
1444 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
- step
, xradius
,
1445 yradius
, selectedColor
, -0.7 - M_PI_2
, 0.7 - M_PI_2
);
1447 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
- step
, ycentre
, xradius
,
1448 yradius
, selectedColor
, -0.5 - M_PI
, 0.5 - M_PI
);
1450 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
+ step
, xradius
,
1451 yradius
, selectedColor
, -0.7 - M_PI
- M_PI_2
, 0.7 - M_PI
- M_PI_2
);
1454 /**********************
1455 * Tiled Object Class *
1456 **********************/
1458 TileObject::TileObject()
1467 TileObject::~TileObject()
1473 free( closedtiles
);
1477 void TileObject::SetOpenTiles(unsigned short* Tiles
, int cnt
)
1486 void TileObject::SetClosedTiles(unsigned short* Tiles
, int cnt
)
1489 free( closedtiles
);
1491 closedtiles
= Tiles
;
1499 Door::Door(TileOverlay
* Overlay
)
1500 : Highlightable( ST_DOOR
)
1517 OpenStrRef
= (ieDword
) -1;
1522 if (Flags
&DOOR_OPEN
) {
1542 void Door::ImpedeBlocks(int count
, Point
*points
, unsigned char value
)
1544 for(int i
= 0;i
<count
;i
++) {
1545 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_NOTDOOR
;
1546 area
->SearchMap
->SetAt( points
[i
].x
, points
[i
].y
, (tmp
|value
) );
1550 void Door::UpdateDoor()
1552 if (Flags
&DOOR_OPEN
) {
1557 unsigned char oval
, cval
;
1559 oval
= PATH_MAP_IMPASSABLE
;
1560 if (Flags
& DOOR_TRANSPARENT
) {
1561 cval
= PATH_MAP_DOOR_TRANSPARENT
;
1564 cval
= PATH_MAP_DOOR_OPAQUE
;
1566 if (Flags
&DOOR_OPEN
) {
1567 ImpedeBlocks(cibcount
, closed_ib
, 0);
1568 ImpedeBlocks(oibcount
, open_ib
, cval
);
1571 ImpedeBlocks(oibcount
, open_ib
, 0);
1572 ImpedeBlocks(cibcount
, closed_ib
, cval
);
1575 InfoPoint
*ip
= area
->TMap
->GetInfoPoint(LinkedInfo
);
1577 if (Flags
&DOOR_OPEN
) ip
->Flags
&=~INFO_DOOR
;
1578 else ip
->Flags
|=INFO_DOOR
;
1582 void Door::ToggleTiles(int State
, int playsound
)
1588 state
= !closedIndex
;
1589 if (playsound
&& ( OpenSound
[0] != '\0' ))
1590 core
->GetAudioDrv()->Play( OpenSound
);
1592 state
= closedIndex
;
1593 if (playsound
&& ( CloseSound
[0] != '\0' ))
1594 core
->GetAudioDrv()->Play( CloseSound
);
1596 for (i
= 0; i
< tilecount
; i
++) {
1597 overlay
->tiles
[tiles
[i
]]->tileIndex
= (ieByte
) state
;
1600 //set door_open as state
1601 Flags
= (Flags
& ~DOOR_OPEN
) | (State
== !core
->HasFeature(GF_REVERSE_DOOR
) );
1604 //this is the short name (not the scripting name)
1605 void Door::SetName(const char* name
)
1607 strnlwrcpy( ID
, name
, 8 );
1610 void Door::SetTiles(unsigned short* Tiles
, int cnt
)
1619 void Door::SetDoorLocked(int Locked
, int playsound
)
1622 if (Flags
& DOOR_LOCKED
) return;
1624 if (playsound
&& ( LockSound
[0] != '\0' ))
1625 core
->GetAudioDrv()->Play( LockSound
);
1628 if (!(Flags
& DOOR_LOCKED
)) return;
1629 Flags
&=~DOOR_LOCKED
;
1630 if (playsound
&& ( UnLockSound
[0] != '\0' ))
1631 core
->GetAudioDrv()->Play( UnLockSound
);
1635 int Door::IsOpen() const
1637 int ret
= core
->HasFeature(GF_REVERSE_DOOR
);
1638 if (Flags
&DOOR_OPEN
) {
1644 //also mark actors to fix position
1645 bool Door::BlockedOpen(int Open
, int ForceOpen
)
1659 //getting all impeded actors flagged for jump
1663 for(int i
= 0;i
<count
;i
++) {
1665 rgn
.x
= points
[i
].x
*16;
1666 rgn
.y
= points
[i
].y
*12;
1667 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_ACTOR
;
1669 int ac
= area
->GetActorInRect(ab
, rgn
, false);
1671 if (ab
[ac
]->GetBase(IE_DONOTJUMP
)) {
1674 ab
[ac
]->SetBase(IE_DONOTJUMP
, DNJ_JUMP
);
1683 if ((Flags
&DOOR_SLIDE
) || ForceOpen
) {
1689 void Door::SetDoorOpen(int Open
, int playsound
, ieDword ID
)
1692 //the door cannot be blocked when opening,
1693 //but the actors will be pushed
1694 //BlockedOpen will mark actors to be pushed
1695 if (BlockedOpen(Open
,0) && !Open
) {
1696 //clear up the blocking actors
1697 area
->JumpActors(false);
1700 area
->JumpActors(true);
1703 LastEntered
= ID
; //used as lastOpener
1705 // in PS:T, opening a door does not unlock it
1706 if (!core
->HasFeature(GF_REVERSE_DOOR
)) {
1707 SetDoorLocked(false,playsound
);
1710 LastTriggerObject
= LastTrigger
= ID
; //used as lastCloser
1712 ToggleTiles(Open
, playsound
);
1713 //synchronising other data with the door state
1715 area
->ActivateWallgroups(open_wg_index
, open_wg_count
, Flags
&DOOR_OPEN
);
1716 area
->ActivateWallgroups(closed_wg_index
, closed_wg_count
, !(Flags
&DOOR_OPEN
));
1719 bool Door::TryUnlock(Actor
*actor
) {
1720 if (!(Flags
&DOOR_LOCKED
)) return true;
1722 // don't remove key in PS:T!
1723 bool removekey
= !core
->HasFeature(GF_REVERSE_DOOR
) && Flags
&DOOR_KEY
;
1724 return Highlightable::TryUnlock(actor
, removekey
);
1727 void Door::SetPolygon(bool Open
, Gem_Polygon
* poly
)
1740 void Door::SetNewOverlay(TileOverlay
*Overlay
) {
1742 ToggleTiles(IsOpen(), false);
1745 void Highlightable::SetTrapDetected(int x
)
1747 if(x
== TrapDetected
)
1751 core
->Autopause(AP_TRAP
);
1755 void Highlightable::TryDisarm(Actor
*actor
)
1757 if (!Trapped
|| !TrapDetected
) return;
1759 LastTriggerObject
= LastTrigger
= actor
->GetID();
1760 int skill
= actor
->GetStat(IE_TRAPS
);
1762 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapRemovalDiff
) {
1763 LastDisarmed
= actor
->GetID();
1766 displaymsg
->DisplayConstantStringName(STR_DISARM_DONE
, 0xd7d7be, actor
);
1767 actor
->AddExperience(XP_DISARM
, actor
->GetXPLevel(1));
1769 displaymsg
->DisplayConstantStringName(STR_DISARM_FAIL
, 0xd7d7be, actor
);
1770 TriggerTrap(skill
, LastTrigger
);
1775 void Door::TryPickLock(Actor
*actor
)
1777 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
1778 if (LockDifficulty
== 100) {
1779 displaymsg
->DisplayConstantStringName(STR_DOOR_NOPICK
, 0xbcefbc, actor
);
1781 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
1782 LastPickLockFailed
= actor
->GetID();
1786 SetDoorLocked( false, true);
1787 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
1788 LastUnlocked
= actor
->GetID();
1790 actor
->AddExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
1793 void Door::TryBashLock(Actor
*actor
)
1795 //Get the strength bonus agains lock difficulty
1796 int str
= actor
->GetStat(IE_STR
);
1797 int strEx
= actor
->GetStat(IE_STREXTRA
);
1798 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
1800 //bonus will never reach 100
1801 if(bonus
< LockDifficulty
) {
1802 displaymsg
->DisplayConstantStringName(STR_DOORBASH_FAIL
, 0xbcefbc, actor
);
1806 displaymsg
->DisplayConstantStringName(STR_DOORBASH_DONE
, 0xd7d7be, actor
);
1807 SetDoorLocked(false, true);
1808 //Is this really useful ?
1809 LastUnlocked
= actor
->GetID();
1813 void Door::DebugDump() const
1815 printf( "Debugdump of Door %s:\n", GetScriptName() );
1816 printf( "Door Open: %s\n", YESNO(IsOpen()));
1817 printf( "Door Locked: %s\n", YESNO(Flags
&DOOR_LOCKED
));
1818 printf( "Door Trapped: %s\n", YESNO(Trapped
));
1820 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags
&DOOR_RESET
), YESNO(Flags
&DOOR_DETECTABLE
) );
1822 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags
&DOOR_SECRET
),YESNO(Flags
&DOOR_FOUND
));
1823 const char *Key
= GetKey();
1824 const char *name
= "NONE";
1826 name
= Scripts
[0]->GetName();
1828 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name
, Key
?Key
:"NONE", YESNO(Flags
&DOOR_KEY
), Dialog
);
1831 /*******************
1833 *******************/
1835 InfoPoint::InfoPoint(void)
1836 : Highlightable( ST_TRIGGER
)
1839 EntranceName
[0] = 0;
1841 TrapDetectionDiff
= 0;
1842 TrapRemovalDiff
= 0;
1848 InfoPoint::~InfoPoint(void)
1852 //checks if the actor may use this travel trigger
1854 //bit 2 : whole team
1855 int InfoPoint::CheckTravel(Actor
*actor
)
1857 if (Flags
&TRAP_DEACTIVATED
) return CT_CANTMOVE
;
1858 if (!actor
->InParty
&& (Flags
&TRAVEL_NONPC
) ) return CT_CANTMOVE
;
1859 if (actor
->InParty
&& (Flags
&TRAVEL_PARTY
) ) {
1860 if (core
->HasFeature(GF_TEAM_MOVEMENT
) || core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
) ) {
1863 return CT_GO_CLOSER
;
1865 if(actor
->IsSelected() ) {
1866 if(core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
|ENP_ONLYSELECT
) ) {
1867 return CT_MOVE_SELECTED
;
1874 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1875 //skill is the all around modified trap detection skill
1876 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1877 void Highlightable::DetectTrap(int skill
)
1879 if (!CanDetectTrap()) return;
1880 if (!Scripts
[0]) return;
1881 if ((skill
>=100) && (skill
!=256) ) skill
= 100;
1882 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapDetectionDiff
) {
1883 SetTrapDetected(1); //probably could be set to the player #?
1887 bool Highlightable::PossibleToSeeTrap() const
1889 return CanDetectTrap();
1892 bool InfoPoint::PossibleToSeeTrap() const
1894 // Only detectable trap-type infopoints.
1895 return (CanDetectTrap() && (Type
== ST_PROXIMITY
) );
1898 bool InfoPoint::CanDetectTrap() const
1900 // Traps can be detected on all types of infopoint, as long
1901 // as the trap is detectable and isn't deactivated.
1902 return ((Flags
&TRAP_DETECTABLE
) && !(Flags
&TRAP_DEACTIVATED
));
1905 // returns true if the infopoint is a PS:T portal
1906 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1907 bool InfoPoint::IsPortal() const
1909 if (Type
!=ST_TRAVEL
) return false;
1910 if (Cursor
!= IE_CURSOR_PORTAL
) return false;
1911 return core
->HasFeature(GF_REVERSE_DOOR
);
1914 //trap that is visible on screen (marked by red)
1915 //if TrapDetected is a bitflag, we could show traps selectively for
1916 //players, really nice for multiplayer
1917 bool Highlightable::VisibleTrap(int see_all
) const
1919 if (!Trapped
) return false;
1920 if (!PossibleToSeeTrap()) return false;
1921 if (!Scripts
[0]) return false;
1922 if (see_all
) return true;
1923 if (TrapDetected
) return true;
1927 //trap that will fire now
1928 bool Highlightable::TriggerTrap(int skill
, ieDword ID
)
1933 //actually this could be script name[0]
1937 if (CanDetectTrap()) {
1938 // this should probably be party members only
1939 if ((skill
>=100) && (skill
!=256) ) skill
= 100;
1940 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapDetectionDiff
) {
1941 SetTrapDetected(1); //probably too late :)
1946 LastTriggerObject
= LastTrigger
= LastEntered
= ID
;
1948 if (!TrapResets()) {
1954 //trap that will fire now
1955 bool InfoPoint::TriggerTrap(int skill
, ieDword ID
)
1957 if (Type
!=ST_PROXIMITY
) {
1960 if (Flags
&TRAP_DEACTIVATED
) {
1964 // we have to set Entered somewhere, here seems best..
1967 } else if (Highlightable::TriggerTrap(skill
, ID
)) {
1969 Flags
|=TRAP_DEACTIVATED
;
1971 // ok, so this is a pain. Entered() trigger checks Trapped,
1972 // so it needs to be kept set. how to do this right?
1979 bool InfoPoint::Entered(Actor
*actor
)
1981 if (outline
->PointIn( actor
->Pos
) ) {
1982 //don't trigger again for this actor
1983 if (!(actor
->GetInternalFlag()&IF_INTRAP
)) {
1987 // why is this here? actors which aren't *in* a trap get IF_INTRAP
1988 // repeatedly unset, so this triggers again and again and again.
1989 // i disabled it for ST_PROXIMITY for now..
1990 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
1993 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
1994 if (Type
== ST_TRAVEL
&& PersonalDistance(TrapLaunch
, actor
)<MAX_OPERATING_DISTANCE
) {
1997 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
1998 // for distance from the outline for travel regions instead?
1999 if (Type
== ST_TRAVEL
&& PersonalDistance(TalkPos
, actor
)<MAX_OPERATING_DISTANCE
) {
2002 if (Flags
&TRAP_USEPOINT
) {
2003 if (PersonalDistance(UsePoint
, actor
)<MAX_OPERATING_DISTANCE
) {
2009 if (Type
==ST_TRAVEL
) {
2013 if (actor
->InParty
|| (Flags
&TRAP_NPC
) ) {
2014 //no need to avoid a travel trigger
2017 if (TriggerTrap(0, actor
->GetID()) ) {
2024 void InfoPoint::DebugDump() const
2028 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2031 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2034 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2037 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2040 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected
, YESNO(Trapped
));
2041 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2043 const char *name
= "NONE";
2045 name
= Scripts
[0]->GetName();
2047 printf( "Script: %s, Key: %s, Dialog: %s\n", name
, KeyResRef
, Dialog
);
2048 printf( "Active: %s\n", YESNO(InternalFlags
&IF_ACTIVE
));
2051 /*******************
2053 *******************/
2055 Container::Container(void)
2056 : Highlightable( ST_CONTAINER
)
2061 TrapDetectionDiff
= 0;
2062 TrapRemovalDiff
= 0;
2065 inventory
.SetInventoryType(INVENTORY_HEAP
);
2066 // NULL should be 0 for this
2067 memset (groundicons
, 0, sizeof(groundicons
) );
2068 groundiconcover
= 0;
2071 void Container::FreeGroundIcons()
2073 Video
* video
= core
->GetVideoDriver();
2075 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2076 if (groundicons
[i
]) {
2077 video
->FreeSprite( groundicons
[i
] );
2078 groundicons
[i
]=NULL
;
2081 delete groundiconcover
;
2082 groundiconcover
= 0;
2085 Container::~Container()
2090 void Container::DrawPile(bool highlight
, Region screen
, Color tint
)
2092 Video
* video
= core
->GetVideoDriver();
2093 CreateGroundIconCover();
2094 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2095 if (groundicons
[i
]) {
2096 //draw it with highlight
2097 video
->BlitGameSprite(groundicons
[i
],
2098 screen
.x
+ Pos
.x
, screen
.y
+ Pos
.y
,
2099 BLIT_TINTED
| (highlight
? 0:BLIT_NOSHADOW
),
2100 tint
, groundiconcover
);
2105 // create the SpriteCover for the groundicons
2106 void Container::CreateGroundIconCover()
2114 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2115 if (groundicons
[i
]) {
2116 Sprite2D
& spr
= *groundicons
[i
];
2117 if (xpos
< spr
.XPos
) {
2118 width
+= spr
.XPos
- xpos
;
2121 if (ypos
< spr
.YPos
) {
2122 height
+= spr
.YPos
- ypos
;
2125 if (width
-xpos
< spr
.Width
-spr
.XPos
) {
2126 width
= spr
.Width
-spr
.XPos
+xpos
;
2128 if (height
-ypos
< spr
.Height
-spr
.YPos
) {
2129 height
= spr
.Height
-spr
.YPos
+ypos
;
2134 if (!groundiconcover
||
2135 !groundiconcover
->Covers(Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
))
2137 delete groundiconcover
;
2138 groundiconcover
= 0;
2139 if (width
*height
> 0) {
2140 groundiconcover
= GetCurrentArea()->BuildSpriteCover
2141 (Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
, WantDither());
2146 // TODO: remove this checking code eventually
2147 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2148 if (groundicons
[i
]) {
2149 Sprite2D
& spr
= *groundicons
[i
];
2150 assert(groundiconcover
->Covers(Pos
.x
, Pos
.y
, spr
.XPos
, spr
.YPos
, spr
.Width
, spr
.Height
));
2156 void Container::SetContainerLocked(bool lock
)
2161 Flags
&=~CONT_LOCKED
;
2165 //This function doesn't exist in the original IE, destroys a container
2166 //turning it to a ground pile
2167 void Container::DestroyContainer()
2169 //it is already a groundpile?
2170 if (Type
== IE_CONTAINER_PILE
)
2172 Type
= IE_CONTAINER_PILE
;
2173 RefreshGroundIcons();
2174 //probably we should stop the script or trigger it, whatever
2177 //Takes an item from the container's inventory and returns its pointer
2178 CREItem
*Container::RemoveItem(unsigned int idx
, unsigned int count
)
2180 CREItem
*ret
= inventory
.RemoveItem(idx
, count
);
2181 //we just took the 3. or less item, groundpile changed
2182 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<3)) {
2183 RefreshGroundIcons();
2188 //Adds an item to the container's inventory
2189 //containers always have enough capacity (so far), thus we always return 2
2190 int Container::AddItem(CREItem
*item
)
2192 inventory
.AddItem(item
);
2193 //we just added a 3. or less item, groundpile changed
2194 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<4)) {
2195 RefreshGroundIcons();
2200 void Container::RefreshGroundIcons()
2202 int i
= inventory
.GetSlotCount();
2203 if (i
>MAX_GROUND_ICON_DRAWN
)
2204 i
= MAX_GROUND_ICON_DRAWN
;
2207 CREItem
*slot
= inventory
.GetSlotItem(i
); //borrowed reference
2208 Item
*itm
= gamedata
->GetItem( slot
->ItemResRef
); //cached reference
2209 //well, this is required in PST, needs more work if some other
2210 //game is broken by not using -1,0
2211 groundicons
[i
] = gamedata
->GetBAMSprite( itm
->GroundIcon
, 0, 0 );
2212 gamedata
->FreeItem( itm
, slot
->ItemResRef
); //decref
2216 //used for ground piles
2217 int Container::WantDither()
2219 //if pile is highlighted, always dither it
2221 return 2; //dither me if you want
2223 //if pile isn't highlighted, dither it if the polygon wants
2227 int Container::IsOpen() const
2229 if (Flags
&CONT_LOCKED
) {
2235 void Container::TryPickLock(Actor
*actor
)
2237 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
2238 if (LockDifficulty
== 100) {
2239 displaymsg
->DisplayConstantStringName(STR_CONT_NOPICK
, 0xbcefbc, actor
);
2241 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
2242 LastPickLockFailed
= actor
->GetID();
2246 SetContainerLocked(false);
2247 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
2248 LastUnlocked
= actor
->GetID();
2250 actor
->AddExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
2253 void Container::TryBashLock(Actor
*actor
)
2255 //Get the strength bonus agains lock difficulty
2256 int str
= actor
->GetStat(IE_STR
);
2257 int strEx
= actor
->GetStat(IE_STREXTRA
);
2258 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
2260 //bonus will never reach 100
2261 if(bonus
< LockDifficulty
) {
2262 displaymsg
->DisplayConstantStringName(STR_CONTBASH_FAIL
, 0xbcefbc, actor
);
2266 displaymsg
->DisplayConstantStringName(STR_CONTBASH_DONE
, 0xd7d7be, actor
);
2267 SetContainerLocked(false);
2268 //Is this really useful ?
2269 LastUnlocked
= actor
->GetID();
2273 void Container::DebugDump() const
2275 printf( "Debugdump of Container %s\n", GetScriptName() );
2276 printf( "Type: %d, LockDifficulty: %d\n", Type
, LockDifficulty
);
2277 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags
, YESNO(Trapped
), TrapDetected
);
2278 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2280 const char *name
= "NONE";
2282 name
= Scripts
[0]->GetName();
2284 printf( "Script: %s, Key: %s\n", name
, KeyResRef
);
2285 // FIXME: const_cast
2286 const_cast<Inventory
&>(inventory
).dump();
2289 bool Container::TryUnlock(Actor
*actor
) {
2290 if (!(Flags
&CONT_LOCKED
)) return true;
2292 return Highlightable::TryUnlock(actor
, false);