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"
29 #include "Interface.h"
32 #include "Projectile.h"
34 #include "SpriteCover.h"
37 #include "GameScript/GSUtils.h"
38 #include "GUI/GameControl.h"
43 #define YESNO(x) ( (x)?"Yes":"No")
45 // we start this at a non-zero value to make debugging easier
46 static ieDword globalActorCounter
= 10000;
48 /***********************
50 ***********************/
51 Scriptable::Scriptable(ScriptableType type
)
54 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
58 overHeadTextPos
.empty();
60 timeStartDisplaying
= 0;
62 TriggerID
= 0; //used by SendTrigger
63 LastTriggerObject
= LastTrigger
= 0;
69 LastPickLockFailed
= 0;
72 CurrentActionState
= 0;
73 CurrentActionTarget
= 0;
74 CurrentActionInterruptable
= true;
75 UnselectableTimer
= 0;
76 startTime
= 0; //executing scripts
77 lastRunTime
= 0; //evaluating scripts
81 globalID
= ++globalActorCounter
;
83 interval
= ( 1000 / AI_UPDATE_TIME
);
85 if (Type
== ST_ACTOR
) {
86 InternalFlags
= IF_VISIBLE
| IF_ONCREATION
| IF_USEDSAVE
;
88 InternalFlags
= IF_ACTIVE
| IF_VISIBLE
| IF_ONCREATION
| IF_NOINT
;
95 LastSpellOnMe
= 0xffffffff;
97 LastSpellSeen
= 0xffffffff;
99 LastTargetPos
.empty();
100 locals
= new Variables();
101 locals
->SetType( GEM_VARIABLES_INT
);
102 locals
->ParseKey( 1 );
105 memset( script_timers
,0, sizeof(script_timers
));
108 Scriptable::~Scriptable(void)
111 ReleaseCurrentAction();
114 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
116 delete( Scripts
[i
] );
120 core
->FreeString( overHeadText
);
127 void Scriptable::SetScriptName(const char* text
)
129 //if (text && text[0]) { //this leaves some uninitialized bytes
130 //lets hope this won't break anything
132 strnspccpy( scriptName
, text
, 32 );
136 /** Gets the DeathVariable */
137 const char* Scriptable::GetScriptName(void) const
142 Map
* Scriptable::GetCurrentArea() const
144 //this could be NULL, always check it
148 void Scriptable::SetMap(Map
*map
)
150 if (map
&& (map
->GetCurrentArea()!=map
)) {
151 //a map always points to itself (if it is a real map)
152 printMessage("Scriptable","Invalid map set!\n",LIGHT_RED
);
158 //ai is nonzero if this is an actor currently in the party
159 //if the script level is AI_SCRIPT_LEVEL, then we need to
160 //load an AI script (.bs) instead of (.bcs)
161 void Scriptable::SetScript(const ieResRef aScript
, int idx
, bool ai
)
163 if (idx
>= MAX_SCRIPTS
) {
164 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED
);
171 // NONE is an 'invalid' script name, never used seriously
172 // This hack is to prevent flooding of the console
173 if (aScript
[0] && stricmp(aScript
, "NONE") ) {
174 if (idx
!=AI_SCRIPT_LEVEL
) ai
= false;
175 Scripts
[idx
] = new GameScript( aScript
, this, idx
, ai
);
179 void Scriptable::SetScript(int index
, GameScript
* script
)
181 if (index
>= MAX_SCRIPTS
) {
182 printMessage("Scriptable","Invalid script index!\n",LIGHT_RED
);
185 if (Scripts
[index
] ) {
186 delete Scripts
[index
];
188 Scripts
[index
] = script
;
191 void Scriptable::DisplayHeadText(const char* text
)
194 core
->FreeString( overHeadText
);
196 overHeadText
= (char *) text
;
197 overHeadTextPos
.empty();
199 timeStartDisplaying
= core
->GetGame()->Ticks
;
203 timeStartDisplaying
= 0;
208 /* 'fix' the current overhead text in the current position */
209 void Scriptable::FixHeadTextPos()
211 overHeadTextPos
= Pos
;
214 #define MAX_DELAY 6000
215 static const Color black
={0,0,0,0};
217 void Scriptable::DrawOverheadText(const Region
&screen
)
219 unsigned long time
= core
->GetGame()->Ticks
;
220 Palette
*palette
= NULL
;
225 time
-= timeStartDisplaying
;
227 Font
* font
= core
->GetFont( 1 );
228 if (time
>= MAX_DELAY
) {
232 time
= (MAX_DELAY
-time
)/10;
234 const Color overHeadColor
= {time
,time
,time
,time
};
235 palette
= core
->CreatePalette(overHeadColor
, black
);
240 if (Type
== ST_ACTOR
) {
241 cs
= ((Selectable
*) this)->size
*50;
245 if (overHeadTextPos
.isempty()) {
249 x
= overHeadTextPos
.x
;
250 y
= overHeadTextPos
.y
;
253 Region
rgn( x
-100+screen
.x
, y
- cs
+ screen
.y
, 200, 400 );
254 font
->Print( rgn
, ( unsigned char * ) overHeadText
,
255 palette
?palette
:core
->InfoTextPalette
, IE_FONT_ALIGN_CENTER
| IE_FONT_ALIGN_TOP
, false );
256 gamedata
->FreePalette(palette
);
259 void Scriptable::DelayedEvent()
261 lastRunTime
= core
->GetGame()->Ticks
;
264 void Scriptable::ImmediateEvent()
269 bool Scriptable::IsPC() const
271 if(Type
== ST_ACTOR
) {
272 if (((Actor
*) this)->GetStat(IE_EA
) <= EA_CHARMED
) {
279 void Scriptable::ExecuteScript(int scriptCount
)
281 // area scripts still run for at least the current area, in bg1 (see ar2631, confirmed by testing)
282 // but not in bg2 (kill Abazigal in ar6005)
283 if (core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
) {
284 if (! (core
->HasFeature(GF_CUTSCENE_AREASCRIPTS
) && Type
== ST_AREA
)) {
289 if ((InternalFlags
& IF_NOINT
) && (CurrentAction
|| GetNextAction())) {
293 if (!CurrentActionInterruptable
) {
294 if (!CurrentAction
&& !GetNextAction()) abort();
298 // only allow death scripts to run once, hopefully?
299 // this is probably terrible logic which needs moving elsewhere
300 if ((lastRunTime
!= 0) && (InternalFlags
& IF_JUSTDIED
)) {
304 ieDword thisTime
= core
->GetGame()->Ticks
;
305 if (( thisTime
- lastRunTime
) < 1000) {
309 lastDelay
= lastRunTime
;
310 lastRunTime
= thisTime
;
314 bool continuing
= false, done
= false;
315 for (int i
= 0;i
<scriptCount
;i
++) {
316 //disable AI script level for actors in party when the player disabled them
317 if ((i
== AI_SCRIPT_LEVEL
) && Type
== ST_ACTOR
&& ((Actor
*) this)->InParty
) {
318 if (core
->GetGame()->ControlStatus
&CS_PARTY_AI
) {
323 GameScript
*Script
= Scripts
[i
];
325 alive
|= Script
->Update(&continuing
, &done
);
328 /* scripts are not concurrent, see WAITPC override script for example */
331 if (alive
&& UnselectableTimer
) {
333 if (!UnselectableTimer
) {
334 if (Type
== ST_ACTOR
) {
335 ((Actor
*) this)->SetCircleSize();
339 InternalFlags
&= ~IF_ONCREATION
;
342 void Scriptable::AddAction(Action
* aC
)
345 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
349 InternalFlags
|=IF_ACTIVE
;
352 // attempt to handle 'instant' actions, from instant.ids, which run immediately
353 // when added if the action queue is empty, even on actors which are Held/etc
354 if (!CurrentAction
&& !GetNextAction()) {
355 if (actionflags
[aC
->actionID
] & AF_INSTANT
) {
357 GameScript::ExecuteAction( this, CurrentAction
);
362 actionQueue
.push_back( aC
);
365 void Scriptable::AddActionInFront(Action
* aC
)
368 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
371 InternalFlags
|=IF_ACTIVE
;
372 actionQueue
.push_front( aC
);
376 Action
* Scriptable::GetNextAction() const
378 if (actionQueue
.size() == 0) {
381 return actionQueue
.front();
384 Action
* Scriptable::PopNextAction()
386 if (actionQueue
.size() == 0) {
389 Action
* aC
= actionQueue
.front();
390 actionQueue
.pop_front();
394 void Scriptable::ClearActions()
396 ReleaseCurrentAction();
397 for (unsigned int i
= 0; i
< actionQueue
.size(); i
++) {
398 Action
* aC
= actionQueue
.front();
399 actionQueue
.pop_front();
405 //clear the triggers as fast as possible when queue ended?
408 if (Type
== ST_ACTOR
) {
415 void Scriptable::ReleaseCurrentAction()
418 CurrentAction
->Release();
419 CurrentAction
= NULL
;
422 CurrentActionState
= 0;
423 CurrentActionTarget
= 0;
424 CurrentActionInterruptable
= true;
427 void Scriptable::ProcessActions(bool force
)
429 unsigned long thisTime
= core
->GetGame()->Ticks
;
431 if (!force
&& (( thisTime
- startTime
) < interval
)) {
434 startTime
= thisTime
;
437 if (WaitCounter
) return;
441 CurrentActionInterruptable
= true;
442 if (!CurrentAction
) {
443 CurrentAction
= PopNextAction();
445 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 void Scriptable::LeaveDialog()
498 InternalFlags
|=IF_WASINDIALOG
;
501 void Scriptable::Hide()
503 InternalFlags
&=~(IF_VISIBLE
);
506 void Scriptable::Unhide()
508 InternalFlags
|= IF_VISIBLE
;
511 void Scriptable::Interrupt()
513 InternalFlags
&= ~IF_NOINT
;
516 void Scriptable::NoInterrupt()
518 InternalFlags
|= IF_NOINT
;
521 //also turning off the idle flag so it won't run continuously
522 void Scriptable::Deactivate()
524 InternalFlags
&=~(IF_ACTIVE
|IF_IDLE
);
527 //turning off the not interruptable flag, actions should reenable it themselves
528 //also turning off the idle flag
529 //heh, no, i wonder why did i touch the interruptable flag here
530 void Scriptable::Activate()
532 InternalFlags
|= IF_ACTIVE
;
533 InternalFlags
&= ~IF_IDLE
;
536 void Scriptable::PartyRested()
538 InternalFlags
|=IF_PARTYRESTED
;
541 ieDword
Scriptable::GetInternalFlag()
543 return InternalFlags
;
546 void Scriptable::InitTriggers()
552 void Scriptable::ClearTriggers()
554 for (TriggerObjects::iterator m
= tolist
.begin(); m
!= tolist
.end (); m
++) {
560 if (bittriggers
& BT_DIE
) {
561 InternalFlags
&= ~IF_JUSTDIED
;
563 if (bittriggers
& BT_ONCREATION
) {
564 InternalFlags
&= ~IF_ONCREATION
;
566 if (bittriggers
& BT_BECAMEVISIBLE
) {
567 InternalFlags
&= ~IF_BECAMEVISIBLE
;
569 if (bittriggers
& BT_PARTYRESTED
) {
570 InternalFlags
&= ~IF_PARTYRESTED
;
572 if (bittriggers
& BT_WASINDIALOG
) {
573 InternalFlags
&= ~IF_WASINDIALOG
;
575 if (bittriggers
& BT_PARTYRESTED
) {
576 InternalFlags
&= ~IF_PARTYRESTED
;
581 void Scriptable::SetBitTrigger(ieDword bittrigger
)
583 bittriggers
|= bittrigger
;
586 void Scriptable::AddTrigger(ieDword
*actorref
)
588 tolist
.push_back(actorref
);
591 static EffectRef fx_set_invisible_state_ref
={"State:Invisible",NULL
,-1};
593 void Scriptable::CreateProjectile(const ieResRef SpellResRef
, ieDword tgt
, bool fake
)
595 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
597 //PST has a weird effect, called Enoll Eva's duplication
598 //it creates every projectile of the affected actor twice
600 if (core
->HasFeature(GF_PST_STATE_FLAGS
) && (Type
== ST_ACTOR
)) {
601 if ( ((Actor
*)this)->GetStat(IE_STATE_ID
)&STATE_EE_DUPL
) {
606 while(duplicate
--) {
607 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
611 pro
->SetCaster(GetGlobalID());
613 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
614 // try and make projectiles start from the right trap position
615 // see the traps in the duergar/assassin battle in bg2 dungeon
616 // see also function below - maybe we should fix Pos instead
617 origin
= ((InfoPoint
*)this)->TrapLaunch
;
621 area
->AddProjectile(pro
, origin
, LastTarget
, fake
);
623 area
->AddProjectile(pro
, origin
, LastTargetPos
);
627 ieDword spellnum
=ResolveSpellNumber( SpellResRef
);
628 if (spellnum
!=0xffffffff) {
629 area
->SeeSpellCast(this, spellnum
);
631 // caster - Casts spellname : target OR
632 // caster - spellname : target (repeating spells)
633 Scriptable
*target
= NULL
;
635 const char* msg
= core
->GetString(displaymsg
->GetStringReference(STR_ACTION_CAST
), 0);
636 const char* spell
= core
->GetString(spl
->SpellName
);
638 target
= area
->GetActorByGlobalID(LastTarget
);
640 target
=core
->GetGame()->GetActorByGlobalID(LastTarget
);
643 if (stricmp(spell
, "")) {
645 snprintf(tmp
, sizeof(tmp
), "%s %s : %s", msg
, spell
, target
->GetName(-1));
647 snprintf(tmp
, sizeof(tmp
), "%s : %s", spell
, GetName(-1));
649 displaymsg
->DisplayStringName(tmp
, 0xffffff, this);
653 if (target
&& (Type
==ST_ACTOR
) ) {
654 Actor
*me
= (Actor
*) this;
655 target
->LastSpellOnMe
= spellnum
;
656 target
->LastCasterOnMe
= me
->GetGlobalID();
657 // don't cure invisibility if this is a self targetting invisibility spell
659 //can't check GetEffectBlock, since it doesn't construct the queue for selftargetting spells
661 unsigned int opcode
= EffectQueue::ResolveEffect(fx_set_invisible_state_ref
);
662 for (unsigned int i
=0; i
< spl
->ext_headers
[SpellHeader
].FeatureCount
; i
++) {
663 if (spl
->GetExtHeader(SpellHeader
)->features
[i
].Opcode
== opcode
) {
668 if (invis
&& spl
->GetExtHeader(SpellHeader
)->Target
== TARGET_SELF
) {
671 me
->CureInvisibility();
673 // sanctuary ends with all hostile actions or when the caster targets someone else
674 if (target
!= this || spl
->Flags
& SF_HOSTILE
) {
681 core
->Autopause(AP_SPELLCAST
);
683 gamedata
->FreeSpell(spl
, SpellResRef
, false);
687 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef
)
689 if (Type
== ST_ACTOR
) {
690 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
693 if (SpellHeader
== -1) {
694 LastTargetPos
.empty();
698 if (LastTargetPos
.isempty()) {
703 CreateProjectile(SpellResRef
, 0, false);
707 LastTargetPos
.empty();
710 void Scriptable::CastSpellEnd( const ieResRef SpellResRef
)
712 if (Type
== ST_ACTOR
) {
713 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
716 if (SpellHeader
== -1) {
724 //if the projectile doesn't need to follow the target, then use the target position
725 CreateProjectile(SpellResRef
, LastTarget
, GetSpellDistance(SpellResRef
, this)==0xffffffff);
728 LastTargetPos
.empty();
731 //set target as point
732 //if spell needs to be depleted, do it
733 //if spell is illegal stop casting
734 int Scriptable::CastSpellPoint( const ieResRef SpellResRef
, const Point
&target
, bool deplete
, bool instant
)
737 LastTargetPos
.empty();
738 if (Type
== ST_ACTOR
) {
739 Actor
*actor
= (Actor
*) this;
740 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
744 LastTargetPos
= target
;
745 return SpellCast(SpellResRef
, instant
);
748 //set target as actor (if target isn't actor, use its position)
749 //if spell needs to be depleted, do it
750 //if spell is illegal stop casting
751 int Scriptable::CastSpell( const ieResRef SpellResRef
, Scriptable
* target
, bool deplete
, bool instant
)
754 LastTargetPos
.empty();
755 if (Type
== ST_ACTOR
) {
756 Actor
*actor
= (Actor
*) this;
757 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
762 if (!target
) target
= this;
764 LastTargetPos
= target
->Pos
;
765 if (target
->Type
==ST_ACTOR
) {
766 LastTarget
= target
->GetGlobalID();
768 return SpellCast(SpellResRef
, instant
);
771 //start spellcasting (common part)
772 int Scriptable::SpellCast(const ieResRef SpellResRef
, bool instant
)
774 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
780 if (Type
== ST_ACTOR
) {
781 Actor
*actor
= (Actor
*) this;
782 //The ext. index is here to calculate the casting time
783 int level
= actor
->GetXPLevel(true);
784 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
785 level
+= actor
->CastingLevelBonus(level
, spl
->SpellType
);
786 SpellHeader
= spl
->GetHeaderIndexFromLevel(level
);
791 SPLExtHeader
*header
= spl
->GetExtHeader(SpellHeader
);
792 int casting_time
= (int)header
->CastingTime
;
793 // how does this work for non-actors exactly?
794 if (Type
== ST_ACTOR
) {
795 // The mental speed effect can shorten or lengthen the casting time.
796 casting_time
-= (int)((Actor
*) this)->Modified
[IE_MENTALSPEED
];
797 if (casting_time
< 0) casting_time
= 0;
799 // this is a guess which seems approximately right so far
800 int duration
= (casting_time
*core
->Time
.round_size
) / 10;
806 if (Type
== ST_ACTOR
) {
807 Actor
*actor
= (Actor
*) this;
808 EffectQueue
*fxqueue
= spl
->GetEffectBlock(this, this->Pos
, -1);
809 fxqueue
->SetOwner(actor
);
810 if (!actor
->Modified
[IE_AVATARREMOVAL
]) {
811 spl
->AddCastingGlow(fxqueue
, duration
, actor
->Modified
[IE_SEX
]);
813 fxqueue
->AddAllEffects(actor
, actor
->Pos
);
817 gamedata
->FreeSpell(spl
, SpellResRef
, false);
821 bool Scriptable::TimerActive(ieDword ID
)
826 if (script_timers
[ID
]) {
832 bool Scriptable::TimerExpired(ieDword ID
)
837 if (script_timers
[ID
] && script_timers
[ID
] < core
->GetGame()->GameTime
) {
838 // expired timers become inactive after being checked
839 script_timers
[ID
] = 0;
845 void Scriptable::StartTimer(ieDword ID
, ieDword expiration
)
848 printMessage("Scriptable", " ", RED
);
849 printf("Timer id %d exceeded MAX_TIMER %d\n", ID
, MAX_TIMER
);
852 script_timers
[ID
]= core
->GetGame()->GameTime
+ expiration
*AI_UPDATE_TIME
;
855 /********************
857 ********************/
859 Selectable::Selectable(ScriptableType type
)
866 circleBitmap
[0] = NULL
;
867 circleBitmap
[1] = NULL
;
870 void Selectable::SetSpriteCover(SpriteCover
* c
)
876 Selectable::~Selectable(void)
881 void Selectable::SetBBox(const Region
&newBBox
)
886 static const unsigned long tp_steps
[8]={3,2,1,0,1,2,3,4};
888 void Selectable::DrawCircle(const Region
&vp
)
890 /* BG2 colours ground circles as follows:
891 dark green for unselected party members
892 bright green for selected party members
893 flashing green/white for a party member the mouse is over
894 bright red for enemies
895 yellow for panicked actors
896 flashing red/white for enemies the mouse is over
897 flashing cyan/white for neutrals the mouse is over
904 Color
* col
= &selectedColor
;
905 Sprite2D
* sprite
= circleBitmap
[0];
908 sprite
= circleBitmap
[1];
910 //doing a time dependent flashing of colors
911 //if it is too fast, increase the 6 to 7
914 step
= tp_steps
[(step
>> 6) & 7];
916 mix
.r
= (overColor
.r
*step
+selectedColor
.r
*(8-step
))/8;
917 mix
.g
= (overColor
.g
*step
+selectedColor
.g
*(8-step
))/8;
918 mix
.b
= (overColor
.b
*step
+selectedColor
.b
*(8-step
))/8;
925 core
->GetVideoDriver()->BlitSprite( sprite
, Pos
.x
- vp
.x
, Pos
.y
- vp
.y
, true );
927 // for size >= 2, radii are (size-1)*16, (size-1)*12
928 // for size == 1, radii are 12, 9
929 int csize
= (size
- 1) * 4;
930 if (csize
< 4) csize
= 3;
931 core
->GetVideoDriver()->DrawEllipse( (ieWord
) (Pos
.x
- vp
.x
), (ieWord
) (Pos
.y
- vp
.y
),
932 (ieWord
) (csize
* 4), (ieWord
) (csize
* 3), *col
);
936 // Check if P is over our ground circle
937 bool Selectable::IsOver(const Point
&P
) const
940 if (csize
< 2) csize
= 2;
942 int dx
= P
.x
- Pos
.x
;
943 int dy
= P
.y
- Pos
.y
;
945 // check rectangle first
946 if (dx
< -(csize
-1)*16 || dx
> (csize
-1)*16) return false;
947 if (dy
< -(csize
-1)*12 || dy
> (csize
-1)*12) return false;
949 // then check ellipse
950 int r
= 9*dx
*dx
+ 16*dy
*dy
; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
952 return (r
<= 48*48*(csize
-1)*(csize
-1));
955 bool Selectable::IsSelected() const
957 return Selected
== 1;
960 void Selectable::SetOver(bool over
)
965 //don't call this function after rendering the cover and before the
966 //blitting of the sprite or bad things will happen :)
967 void Selectable::Select(int Value
)
969 if (Selected
!=0x80 || Value
!=1) {
970 Selected
= (ieWord
) Value
;
972 //forcing regeneration of the cover
973 SetSpriteCover(NULL
);
976 void Selectable::SetCircle(int circlesize
, const Color
&color
, Sprite2D
* normal_circle
, Sprite2D
* selected_circle
)
979 selectedColor
= color
;
980 overColor
.r
= color
.r
>> 1;
981 overColor
.g
= color
.g
>> 1;
982 overColor
.b
= color
.b
>> 1;
983 overColor
.a
= color
.a
;
984 circleBitmap
[0] = normal_circle
;
985 circleBitmap
[1] = selected_circle
;
989 int Selectable::WantDither()
991 //if dithering is disabled globally, don't do it
992 if (core
->FogOfWar
&4) {
995 //if actor is dead, dither it if polygon wants
999 //if actor is selected dither it
1006 /***********************
1007 * Highlightable Class *
1008 ***********************/
1010 Highlightable::Highlightable(ScriptableType type
)
1011 : Scriptable( type
)
1015 Cursor
= IE_CURSOR_NORMAL
;
1019 Highlightable::~Highlightable(void)
1026 bool Highlightable::IsOver(const Point
&Pos
) const
1031 return outline
->PointIn( Pos
);
1034 void Highlightable::DrawOutline() const
1039 core
->GetVideoDriver()->DrawPolyline( outline
, outlineColor
, true );
1042 void Highlightable::SetCursor(unsigned char CursorIndex
)
1044 Cursor
= CursorIndex
;
1047 bool Highlightable::TryUnlock(Actor
*actor
, bool removekey
) {
1048 const char *Key
= GetKey();
1049 Actor
*haskey
= NULL
;
1051 if (Key
&& actor
->InParty
) {
1052 Game
*game
= core
->GetGame();
1053 //allow unlock when the key is on any partymember
1054 for (int idx
= 0; idx
< game
->GetPartySize(false); idx
++) {
1055 Actor
*pc
= game
->FindPC(idx
+ 1);
1058 if (pc
->inventory
.HasItem(Key
,0) ) {
1064 //actor is not in party, check only actor
1065 if (actor
->inventory
.HasItem(Key
,0) ) {
1075 CREItem
*item
= NULL
;
1076 haskey
->inventory
.RemoveItem(Key
,0,&item
);
1077 //the item should always be existing!!!
1091 Movable::Movable(ScriptableType type
)
1092 : Selectable( type
)
1103 AttackMovements
[0] = 100;
1104 AttackMovements
[1] = 0;
1105 AttackMovements
[2] = 0;
1108 Movable::~Movable(void)
1115 int Movable::GetPathLength()
1117 PathNode
*node
= GetNextStep(0);
1119 while (node
->Next
) {
1126 PathNode
*Movable::GetNextStep(int x
)
1129 DoStep((unsigned int) ~0);
1131 PathNode
*node
= step
;
1132 while(node
&& x
--) {
1138 Point
Movable::GetMostLikelyPosition()
1144 //actually, sometimes middle path would be better, if
1145 //we stand in Destination already
1146 int halfway
= GetPathLength()/2;
1147 PathNode
*node
= GetNextStep(halfway
);
1149 return Point((ieWord
) ((node
->x
*16)+8), (ieWord
) ((node
->y
*12)+6) );
1154 void Movable::SetStance(unsigned int arg
)
1156 //don't modify stance from dead back to anything if the actor is dead
1157 if ((StanceID
==IE_ANI_TWITCH
|| StanceID
==IE_ANI_DIE
) && (arg
!=IE_ANI_TWITCH
) ) {
1158 if (GetInternalFlag()&IF_REALLYDIED
) {
1159 printMessage("Movable","Stance overridden by death\n", YELLOW
);
1164 if (StanceID
== IE_ANI_CONJURE
&& StanceID
!= arg
&& Type
==ST_ACTOR
) {
1165 Actor
*caster
= (Actor
*) this;
1166 if (caster
->casting_sound
) {
1167 caster
->casting_sound
->Stop();
1168 caster
->casting_sound
.release();
1172 if (arg
<MAX_ANIMS
) {
1173 StanceID
=(unsigned char) arg
;
1175 if (StanceID
== IE_ANI_ATTACK
) {
1176 // Set stance to a random attack animation
1178 int random
= rand()%100;
1179 if (random
< AttackMovements
[0]) {
1180 StanceID
= IE_ANI_ATTACK_BACKSLASH
;
1181 } else if (random
< AttackMovements
[0] + AttackMovements
[1]) {
1182 StanceID
= IE_ANI_ATTACK_SLASH
;
1184 StanceID
= IE_ANI_ATTACK_JAB
;
1189 StanceID
=IE_ANI_AWAKE
; //
1190 printf("Tried to set invalid stance id (%u)\n", arg
);
1194 void Movable::SetAttackMoveChances(ieWord
*amc
)
1196 AttackMovements
[0]=amc
[0];
1197 AttackMovements
[1]=amc
[1];
1198 AttackMovements
[2]=amc
[2];
1203 //this could be used for WingBuffet as well
1204 void Movable::MoveLine(int steps
, int Pass
, ieDword orient
)
1206 //remove previous path
1213 path
= area
->GetLine( p
, steps
, orient
, Pass
);
1216 void AdjustPositionTowards(Point
&Pos
, ieDword time_diff
, unsigned int walk_speed
, short srcx
, short srcy
, short destx
, short desty
) {
1218 Pos
.x
+= ( unsigned short )
1219 ( ( ( ( ( destx
* 16 ) + 8 ) - Pos
.x
) * ( time_diff
) ) / walk_speed
);
1221 Pos
.x
-= ( unsigned short )
1222 ( ( ( Pos
.x
- ( ( destx
* 16 ) + 8 ) ) * ( time_diff
) ) / walk_speed
);
1224 Pos
.y
+= ( unsigned short )
1225 ( ( ( ( ( desty
* 12 ) + 6 ) - Pos
.y
) * ( time_diff
) ) / walk_speed
);
1227 Pos
.y
-= ( unsigned short )
1228 ( ( ( Pos
.y
- ( ( desty
* 12 ) + 6 ) ) * ( time_diff
) ) / walk_speed
);
1231 // returns whether we made all pending steps (so, false if we must be called again this tick)
1232 // we can't just do them all here because the caller might have to update searchmap etc
1233 bool Movable::DoStep(unsigned int walk_speed
, ieDword time
)
1238 if (!time
) time
= core
->GetGame()->Ticks
;
1240 // zero speed: no movement
1241 timeStartStep
= time
;
1242 StanceID
= IE_ANI_READY
;
1247 timeStartStep
= time
;
1248 } else if (step
->Next
&& (( time
- timeStartStep
) >= walk_speed
)) {
1249 //printf("[New Step] : Orientation = %d\n", step->orient);
1251 timeStartStep
= timeStartStep
+ walk_speed
;
1253 SetOrientation (step
->orient
, false);
1254 StanceID
= IE_ANI_WALK
;
1255 if ((Type
== ST_ACTOR
) && (InternalFlags
& IF_RUNNING
)) {
1256 StanceID
= IE_ANI_RUN
;
1258 Pos
.x
= ( step
->x
* 16 ) + 8;
1259 Pos
.y
= ( step
->y
* 12 ) + 6;
1261 // we reached our destination, we are done
1263 NewOrientation
= Orientation
;
1264 //since clearpath no longer sets currentaction to NULL
1266 //no we don't, action is responsible for releasing itself
1267 //ReleaseCurrentAction();
1270 if (( time
- timeStartStep
) >= walk_speed
) {
1271 // we didn't finish all pending steps, yet
1274 AdjustPositionTowards(Pos
, time
- timeStartStep
, walk_speed
, step
->x
, step
->y
, step
->Next
->x
, step
->Next
->y
);
1278 void Movable::AddWayPoint(const Point
&Des
)
1285 //it is tempting to use 'step' here, as it could
1286 //be about half of the current path already
1287 PathNode
*endNode
= path
;
1288 while(endNode
->Next
) {
1289 endNode
= endNode
->Next
;
1291 Point
p(endNode
->x
, endNode
->y
);
1292 area
->ClearSearchMapFor(this);
1293 PathNode
*path2
= area
->FindPath( p
, Des
, size
);
1294 endNode
->Next
= path2
;
1295 //probably it is wise to connect it both ways?
1296 path2
->Parent
= endNode
;
1299 void Movable::FixPosition()
1301 if (Type
!=ST_ACTOR
) {
1304 Actor
*actor
= (Actor
*) this;
1305 if (actor
->GetStat(IE_DONOTJUMP
)&DNJ_BIRD
) {
1308 //before fixposition, you should remove own shadow
1309 area
->ClearSearchMapFor(this);
1312 GetCurrentArea()->AdjustPosition(Pos
);
1317 void Movable::WalkTo(const Point
&Des
, int distance
)
1321 // maybe caller should be responsible for this
1322 if ((Des
.x
/16 == Pos
.x
/16) && (Des
.y
/12 == Pos
.y
/12)) {
1327 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1328 PathNode
*prev_step
= NULL
;
1329 unsigned char old_stance
= StanceID
;
1330 if (step
&& step
->Next
) {
1331 // don't interrupt in the middle of a step; path from the next one
1332 prev_step
= new PathNode(*step
);
1333 from
.x
= ( step
->Next
->x
* 16 ) + 8;
1334 from
.y
= ( step
->Next
->y
* 12 ) + 6;
1342 area
->ClearSearchMapFor(this);
1344 path
= area
->FindPathNear( from
, Des
, size
, distance
);
1346 path
= area
->FindPath( from
, Des
, size
, distance
);
1348 //ClearPath sets destination, so Destination must be set after it
1349 //also we should set Destination only if there is a walkable path
1354 // we want to smoothly continue, please
1355 // this all needs more thought! but it seems to work okay
1356 StanceID
= old_stance
;
1359 // this is a terrible hack to make up for the
1360 // pathfinder orienting the first node wrong
1361 // should be fixed in pathfinder and not here!
1363 next
.x
= path
->x
; next
.y
= path
->y
;
1364 follow
.x
= path
->Next
->x
;
1365 follow
.y
= path
->Next
->y
;
1366 path
->orient
= GetOrient(follow
, next
);
1369 // then put the prev_step at the beginning of the path
1370 prev_step
->Next
= path
;
1371 path
->Parent
= prev_step
;
1379 delete( prev_step
);
1385 void Movable::RunAwayFrom(const Point
&Des
, int PathLength
, int flags
)
1388 area
->ClearSearchMapFor(this);
1389 path
= area
->RunAway( Pos
, Des
, size
, PathLength
, flags
);
1392 void Movable::RandomWalk(bool can_stop
, bool run
)
1397 //if not continous random walk, then stops for a while
1398 if (can_stop
&& (rand()&3) ) {
1399 SetWait((rand()&7)+7);
1403 InternalFlags
|=IF_RUNNING
;
1405 //the commenting-out of the clear search map call was removed in 0.4.0
1406 //if you want to put it back for some reason, check
1407 //if the searchmap is not eaten up
1408 area
->ClearSearchMapFor(this);
1411 //selecting points around a circle's edge around actor (didn't work better)
1412 //int x = core->Roll(1,100,-50);
1414 //p.y+=(int) sqrt(100-x*x);
1416 //selecting points in a square around actor
1417 p
.x
+=core
->Roll(1,50,-25);
1418 p
.y
+=core
->Roll(1,50,-25);
1419 //the 5th parameter is controlling the orientation of the actor
1420 //0 - back away, 1 - face direction
1421 path
= area
->RunAway( Pos
, p
, size
, 50, 1 );
1424 void Movable::MoveTo(const Point
&Des
)
1426 area
->ClearSearchMapFor(this);
1429 if (BlocksSearchMap()) {
1430 area
->BlockSearchMap( Pos
, size
, IsPC()?PATH_MAP_PC
:PATH_MAP_NPC
);
1434 void Movable::ClearPath()
1436 //this is to make sure attackers come to us
1437 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1438 //is set before ClearPath
1440 if (StanceID
==IE_ANI_WALK
|| StanceID
==IE_ANI_RUN
) {
1441 StanceID
= IE_ANI_AWAKE
;
1443 InternalFlags
&=~IF_NORECTICLE
;
1444 PathNode
* thisNode
= path
;
1446 PathNode
* nextNode
= thisNode
->Next
;
1448 thisNode
= nextNode
;
1452 //don't call ReleaseCurrentAction
1455 void Movable::DrawTargetPoint(const Region
&vp
)
1457 if (!path
|| !Selected
|| (InternalFlags
&IF_NORECTICLE
) )
1460 // recticles are never drawn in cutscenes
1461 if ((core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
))
1464 // generates "step" from sequence 3 2 1 0 1 2 3 4
1465 // updated each 1/15 sec
1468 step
= tp_steps
[(step
>> 6) & 7];
1471 int csize
= (size
- 1) * 4;
1472 if (csize
< 4) csize
= 3;
1474 /* segments should not go outside selection radius */
1475 unsigned short xradius
= (csize
* 4) - 5;
1476 unsigned short yradius
= (csize
* 3) - 5;
1477 ieWord xcentre
= (ieWord
) (Destination
.x
- vp
.x
);
1478 ieWord ycentre
= (ieWord
) (Destination
.y
- vp
.y
);
1480 // TODO: 0.5 and 0.7 are pretty much random values
1482 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
+ step
, ycentre
, xradius
,
1483 yradius
, selectedColor
, -0.5, 0.5 );
1485 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
- step
, xradius
,
1486 yradius
, selectedColor
, -0.7 - M_PI_2
, 0.7 - M_PI_2
);
1488 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
- step
, ycentre
, xradius
,
1489 yradius
, selectedColor
, -0.5 - M_PI
, 0.5 - M_PI
);
1491 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
+ step
, xradius
,
1492 yradius
, selectedColor
, -0.7 - M_PI
- M_PI_2
, 0.7 - M_PI
- M_PI_2
);
1495 /**********************
1496 * Tiled Object Class *
1497 **********************/
1499 TileObject::TileObject()
1508 TileObject::~TileObject()
1514 free( closedtiles
);
1518 void TileObject::SetOpenTiles(unsigned short* Tiles
, int cnt
)
1527 void TileObject::SetClosedTiles(unsigned short* Tiles
, int cnt
)
1530 free( closedtiles
);
1532 closedtiles
= Tiles
;
1540 Door::Door(TileOverlay
* Overlay
)
1541 : Highlightable( ST_DOOR
)
1558 OpenStrRef
= (ieDword
) -1;
1563 if (Flags
&DOOR_OPEN
) {
1583 void Door::ImpedeBlocks(int count
, Point
*points
, unsigned char value
)
1585 for(int i
= 0;i
<count
;i
++) {
1586 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_NOTDOOR
;
1587 area
->SearchMap
->SetAt( points
[i
].x
, points
[i
].y
, (tmp
|value
) );
1591 void Door::UpdateDoor()
1593 if (Flags
&DOOR_OPEN
) {
1598 // update the Scriptable position
1599 Pos
.x
= outline
->BBox
.x
+ outline
->BBox
.w
/2;
1600 Pos
.y
= outline
->BBox
.y
+ outline
->BBox
.h
/2;
1602 unsigned char oval
, cval
;
1603 oval
= PATH_MAP_IMPASSABLE
;
1604 if (Flags
& DOOR_TRANSPARENT
) {
1605 cval
= PATH_MAP_DOOR_TRANSPARENT
;
1608 cval
= PATH_MAP_DOOR_OPAQUE
;
1610 if (Flags
&DOOR_OPEN
) {
1611 ImpedeBlocks(cibcount
, closed_ib
, 0);
1612 ImpedeBlocks(oibcount
, open_ib
, cval
);
1615 ImpedeBlocks(oibcount
, open_ib
, 0);
1616 ImpedeBlocks(cibcount
, closed_ib
, cval
);
1619 InfoPoint
*ip
= area
->TMap
->GetInfoPoint(LinkedInfo
);
1621 if (Flags
&DOOR_OPEN
) ip
->Flags
&=~INFO_DOOR
;
1622 else ip
->Flags
|=INFO_DOOR
;
1626 void Door::ToggleTiles(int State
, int playsound
)
1632 state
= !closedIndex
;
1633 if (playsound
&& ( OpenSound
[0] != '\0' ))
1634 core
->GetAudioDrv()->Play( OpenSound
);
1636 state
= closedIndex
;
1637 if (playsound
&& ( CloseSound
[0] != '\0' ))
1638 core
->GetAudioDrv()->Play( CloseSound
);
1640 for (i
= 0; i
< tilecount
; i
++) {
1641 overlay
->tiles
[tiles
[i
]]->tileIndex
= (ieByte
) state
;
1644 //set door_open as state
1645 Flags
= (Flags
& ~DOOR_OPEN
) | (State
== !core
->HasFeature(GF_REVERSE_DOOR
) );
1648 //this is the short name (not the scripting name)
1649 void Door::SetName(const char* name
)
1651 strnlwrcpy( ID
, name
, 8 );
1654 void Door::SetTiles(unsigned short* Tiles
, int cnt
)
1663 void Door::SetDoorLocked(int Locked
, int playsound
)
1666 if (Flags
& DOOR_LOCKED
) return;
1668 if (playsound
&& ( LockSound
[0] != '\0' ))
1669 core
->GetAudioDrv()->Play( LockSound
);
1672 if (!(Flags
& DOOR_LOCKED
)) return;
1673 Flags
&=~DOOR_LOCKED
;
1674 if (playsound
&& ( UnLockSound
[0] != '\0' ))
1675 core
->GetAudioDrv()->Play( UnLockSound
);
1679 int Door::IsOpen() const
1681 int ret
= core
->HasFeature(GF_REVERSE_DOOR
);
1682 if (Flags
&DOOR_OPEN
) {
1688 //also mark actors to fix position
1689 bool Door::BlockedOpen(int Open
, int ForceOpen
)
1703 //getting all impeded actors flagged for jump
1707 for(int i
= 0;i
<count
;i
++) {
1709 rgn
.x
= points
[i
].x
*16;
1710 rgn
.y
= points
[i
].y
*12;
1711 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_ACTOR
;
1713 int ac
= area
->GetActorInRect(ab
, rgn
, false);
1715 if (ab
[ac
]->GetBase(IE_DONOTJUMP
)) {
1718 ab
[ac
]->SetBase(IE_DONOTJUMP
, DNJ_JUMP
);
1727 if ((Flags
&DOOR_SLIDE
) || ForceOpen
) {
1733 void Door::SetDoorOpen(int Open
, int playsound
, ieDword ID
)
1736 //the door cannot be blocked when opening,
1737 //but the actors will be pushed
1738 //BlockedOpen will mark actors to be pushed
1739 if (BlockedOpen(Open
,0) && !Open
) {
1740 //clear up the blocking actors
1741 area
->JumpActors(false);
1744 area
->JumpActors(true);
1747 LastEntered
= ID
; //used as lastOpener
1749 // in PS:T, opening a door does not unlock it
1750 if (!core
->HasFeature(GF_REVERSE_DOOR
)) {
1751 SetDoorLocked(false,playsound
);
1754 LastTriggerObject
= LastTrigger
= ID
; //used as lastCloser
1756 ToggleTiles(Open
, playsound
);
1757 //synchronising other data with the door state
1759 area
->ActivateWallgroups(open_wg_index
, open_wg_count
, Flags
&DOOR_OPEN
);
1760 area
->ActivateWallgroups(closed_wg_index
, closed_wg_count
, !(Flags
&DOOR_OPEN
));
1763 bool Door::TryUnlock(Actor
*actor
) {
1764 if (!(Flags
&DOOR_LOCKED
)) return true;
1766 // don't remove key in PS:T!
1767 bool removekey
= !core
->HasFeature(GF_REVERSE_DOOR
) && Flags
&DOOR_KEY
;
1768 return Highlightable::TryUnlock(actor
, removekey
);
1771 void Door::TryDetectSecret(int skill
)
1773 if (Type
!= ST_DOOR
) return;
1774 if (Visible()) return;
1775 if (skill
> (signed)DiscoveryDiff
) {
1776 Flags
|= DOOR_FOUND
;
1777 core
->PlaySound(DS_FOUNDSECRET
);
1781 // return true if the door isn't secret or if it is, but was already discovered
1782 bool Door::Visible()
1784 return (!(Flags
& DOOR_SECRET
) || (Flags
& DOOR_FOUND
));
1787 void Door::SetPolygon(bool Open
, Gem_Polygon
* poly
)
1800 void Door::SetNewOverlay(TileOverlay
*Overlay
) {
1802 ToggleTiles(IsOpen(), false);
1805 void Highlightable::SetTrapDetected(int x
)
1807 if(x
== TrapDetected
)
1811 core
->Autopause(AP_TRAP
);
1815 void Highlightable::TryDisarm(Actor
*actor
)
1817 if (!Trapped
|| !TrapDetected
) return;
1819 LastTriggerObject
= LastTrigger
= actor
->GetGlobalID();
1820 int skill
= actor
->GetStat(IE_TRAPS
);
1822 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapRemovalDiff
) {
1823 LastDisarmed
= actor
->GetGlobalID();
1826 displaymsg
->DisplayConstantStringName(STR_DISARM_DONE
, 0xd7d7be, actor
);
1827 int xp
= actor
->CalculateExperience(XP_DISARM
, actor
->GetXPLevel(1));
1828 Game
*game
= core
->GetGame();
1829 game
->ShareXP(xp
, SX_DIVIDE
);
1831 displaymsg
->DisplayConstantStringName(STR_DISARM_FAIL
, 0xd7d7be, actor
);
1832 TriggerTrap(skill
, LastTrigger
);
1837 void Door::TryPickLock(Actor
*actor
)
1839 if (LockDifficulty
== 100) {
1840 if (OpenStrRef
!= (ieDword
)-1) {
1841 displaymsg
->DisplayStringName(OpenStrRef
, 0xbcefbc, actor
, IE_STR_SOUND
|IE_STR_SPEECH
);
1843 displaymsg
->DisplayConstantStringName(STR_DOOR_NOPICK
, 0xbcefbc, actor
);
1847 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
1848 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
1849 LastPickLockFailed
= actor
->GetGlobalID();
1852 SetDoorLocked( false, true);
1853 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
1854 LastUnlocked
= actor
->GetGlobalID();
1856 int xp
= actor
->CalculateExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
1857 Game
*game
= core
->GetGame();
1858 game
->ShareXP(xp
, SX_DIVIDE
);
1861 void Door::TryBashLock(Actor
*actor
)
1863 //Get the strength bonus agains lock difficulty
1864 int str
= actor
->GetStat(IE_STR
);
1865 int strEx
= actor
->GetStat(IE_STREXTRA
);
1866 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
1867 unsigned int roll
= actor
->LuckyRoll(1, 10, bonus
, 0);
1869 if(roll
< LockDifficulty
|| LockDifficulty
== 100) {
1870 displaymsg
->DisplayConstantStringName(STR_DOORBASH_FAIL
, 0xbcefbc, actor
);
1874 displaymsg
->DisplayConstantStringName(STR_DOORBASH_DONE
, 0xd7d7be, actor
);
1875 SetDoorLocked(false, true);
1876 //Is this really useful ?
1877 LastUnlocked
= actor
->GetGlobalID();
1881 void Door::DebugDump() const
1883 printf( "Debugdump of Door %s:\n", GetScriptName() );
1884 printf( "Door Global ID: %d\n", GetGlobalID());
1885 printf( "Position: %d.%d\n", Pos
.x
, Pos
.y
);
1886 printf( "Door Open: %s\n", YESNO(IsOpen()));
1887 printf( "Door Locked: %s\n", YESNO(Flags
&DOOR_LOCKED
));
1888 printf( "Door Trapped: %s\n", YESNO(Trapped
));
1890 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags
&DOOR_RESET
), YESNO(Flags
&DOOR_DETECTABLE
) );
1892 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags
&DOOR_SECRET
),YESNO(Flags
&DOOR_FOUND
));
1893 const char *Key
= GetKey();
1894 const char *name
= "NONE";
1896 name
= Scripts
[0]->GetName();
1898 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name
, Key
?Key
:"NONE", YESNO(Flags
&DOOR_KEY
), Dialog
);
1901 /*******************
1903 *******************/
1905 InfoPoint::InfoPoint(void)
1906 : Highlightable( ST_TRIGGER
)
1909 EntranceName
[0] = 0;
1911 TrapDetectionDiff
= 0;
1912 TrapRemovalDiff
= 0;
1918 InfoPoint::~InfoPoint(void)
1922 //checks if the actor may use this travel trigger
1924 //bit 2 : whole team
1925 int InfoPoint::CheckTravel(Actor
*actor
)
1927 if (Flags
&TRAP_DEACTIVATED
) return CT_CANTMOVE
;
1928 if (!actor
->InParty
&& (Flags
&TRAVEL_NONPC
) ) return CT_CANTMOVE
;
1929 if (actor
->InParty
&& (Flags
&TRAVEL_PARTY
) ) {
1930 if (core
->HasFeature(GF_TEAM_MOVEMENT
) || core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
) ) {
1933 return CT_GO_CLOSER
;
1935 if(actor
->IsSelected() ) {
1936 if(core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
|ENP_ONLYSELECT
) ) {
1937 return CT_MOVE_SELECTED
;
1944 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1945 //skill is the all around modified trap detection skill
1946 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1947 void Highlightable::DetectTrap(int skill
)
1949 if (!CanDetectTrap()) return;
1950 if (!Scripts
[0]) return;
1951 if ((skill
>=100) && (skill
!=256) ) skill
= 100;
1952 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapDetectionDiff
) {
1953 SetTrapDetected(1); //probably could be set to the player #?
1957 bool Highlightable::PossibleToSeeTrap() const
1959 return CanDetectTrap();
1962 bool InfoPoint::PossibleToSeeTrap() const
1964 // Only detectable trap-type infopoints.
1965 return (CanDetectTrap() && (Type
== ST_PROXIMITY
) );
1968 bool InfoPoint::CanDetectTrap() const
1970 // Traps can be detected on all types of infopoint, as long
1971 // as the trap is detectable and isn't deactivated.
1972 return ((Flags
&TRAP_DETECTABLE
) && !(Flags
&TRAP_DEACTIVATED
));
1975 // returns true if the infopoint is a PS:T portal
1976 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1977 bool InfoPoint::IsPortal() const
1979 if (Type
!=ST_TRAVEL
) return false;
1980 if (Cursor
!= IE_CURSOR_PORTAL
) return false;
1981 return core
->HasFeature(GF_REVERSE_DOOR
);
1984 //trap that is visible on screen (marked by red)
1985 //if TrapDetected is a bitflag, we could show traps selectively for
1986 //players, really nice for multiplayer
1987 bool Highlightable::VisibleTrap(int see_all
) const
1989 if (!Trapped
) return false;
1990 if (!PossibleToSeeTrap()) return false;
1991 if (!Scripts
[0]) return false;
1992 if (see_all
) return true;
1993 if (TrapDetected
) return true;
1997 //trap that will fire now
1998 bool Highlightable::TriggerTrap(int /*skill*/, ieDword ID
)
2003 //actually this could be script name[0]
2007 LastTriggerObject
= LastTrigger
= LastEntered
= ID
;
2009 if (!TrapResets()) {
2015 //trap that will fire now
2016 bool InfoPoint::TriggerTrap(int skill
, ieDword ID
)
2018 if (Type
!=ST_PROXIMITY
) {
2021 if (Flags
&TRAP_DEACTIVATED
) {
2025 // we have to set Entered somewhere, here seems best..
2028 } else if (Highlightable::TriggerTrap(skill
, ID
)) {
2030 Flags
|=TRAP_DEACTIVATED
;
2032 // ok, so this is a pain. Entered() trigger checks Trapped,
2033 // so it needs to be kept set. how to do this right?
2040 bool InfoPoint::Entered(Actor
*actor
)
2042 if (outline
->PointIn( actor
->Pos
) ) {
2043 //don't trigger again for this actor
2044 if (!(actor
->GetInternalFlag()&IF_INTRAP
)) {
2048 // why is this here? actors which aren't *in* a trap get IF_INTRAP
2049 // repeatedly unset, so this triggers again and again and again.
2050 // i disabled it for ST_PROXIMITY for now..
2051 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
2054 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
2055 if (Type
== ST_TRAVEL
&& PersonalDistance(TrapLaunch
, actor
)<MAX_OPERATING_DISTANCE
) {
2058 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
2059 // for distance from the outline for travel regions instead?
2060 if (Type
== ST_TRAVEL
&& PersonalDistance(TalkPos
, actor
)<MAX_OPERATING_DISTANCE
) {
2063 if (Flags
&TRAP_USEPOINT
) {
2064 if (PersonalDistance(UsePoint
, actor
)<MAX_OPERATING_DISTANCE
) {
2070 if (Type
==ST_TRAVEL
) {
2074 if (actor
->InParty
|| (Flags
&TRAP_NPC
) ) {
2075 //no need to avoid a travel trigger
2078 if (TriggerTrap(0, actor
->GetGlobalID()) ) {
2085 void InfoPoint::DebugDump() const
2089 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2092 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2095 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2098 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2101 printf( "Region Global ID: %d\n", GetGlobalID());
2102 printf( "Position: %d.%d\n", Pos
.x
, Pos
.y
);
2103 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected
, YESNO(Trapped
));
2104 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2106 const char *name
= "NONE";
2108 name
= Scripts
[0]->GetName();
2110 printf( "Script: %s, Key: %s, Dialog: %s\n", name
, KeyResRef
, Dialog
);
2111 printf( "Active: %s\n", YESNO(InternalFlags
&IF_ACTIVE
));
2114 /*******************
2116 *******************/
2118 Container::Container(void)
2119 : Highlightable( ST_CONTAINER
)
2124 TrapDetectionDiff
= 0;
2125 TrapRemovalDiff
= 0;
2128 inventory
.SetInventoryType(INVENTORY_HEAP
);
2129 // NULL should be 0 for this
2130 memset (groundicons
, 0, sizeof(groundicons
) );
2131 groundiconcover
= 0;
2134 void Container::FreeGroundIcons()
2136 Video
* video
= core
->GetVideoDriver();
2138 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2139 if (groundicons
[i
]) {
2140 video
->FreeSprite( groundicons
[i
] );
2141 groundicons
[i
]=NULL
;
2144 delete groundiconcover
;
2145 groundiconcover
= 0;
2148 Container::~Container()
2153 void Container::DrawPile(bool highlight
, Region screen
, Color tint
)
2155 Video
* video
= core
->GetVideoDriver();
2156 CreateGroundIconCover();
2157 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2158 if (groundicons
[i
]) {
2159 //draw it with highlight
2160 video
->BlitGameSprite(groundicons
[i
],
2161 screen
.x
+ Pos
.x
, screen
.y
+ Pos
.y
,
2162 BLIT_TINTED
| (highlight
? 0:BLIT_NOSHADOW
),
2163 tint
, groundiconcover
);
2168 // create the SpriteCover for the groundicons
2169 void Container::CreateGroundIconCover()
2177 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2178 if (groundicons
[i
]) {
2179 Sprite2D
& spr
= *groundicons
[i
];
2180 if (xpos
< spr
.XPos
) {
2181 width
+= spr
.XPos
- xpos
;
2184 if (ypos
< spr
.YPos
) {
2185 height
+= spr
.YPos
- ypos
;
2188 if (width
-xpos
< spr
.Width
-spr
.XPos
) {
2189 width
= spr
.Width
-spr
.XPos
+xpos
;
2191 if (height
-ypos
< spr
.Height
-spr
.YPos
) {
2192 height
= spr
.Height
-spr
.YPos
+ypos
;
2197 if (!groundiconcover
||
2198 !groundiconcover
->Covers(Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
))
2200 delete groundiconcover
;
2201 groundiconcover
= 0;
2202 if (width
*height
> 0) {
2203 groundiconcover
= GetCurrentArea()->BuildSpriteCover
2204 (Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
, WantDither());
2209 // TODO: remove this checking code eventually
2210 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2211 if (groundicons
[i
]) {
2212 Sprite2D
& spr
= *groundicons
[i
];
2213 assert(groundiconcover
->Covers(Pos
.x
, Pos
.y
, spr
.XPos
, spr
.YPos
, spr
.Width
, spr
.Height
));
2219 void Container::SetContainerLocked(bool lock
)
2224 Flags
&=~CONT_LOCKED
;
2228 //This function doesn't exist in the original IE, destroys a container
2229 //turning it to a ground pile
2230 void Container::DestroyContainer()
2232 //it is already a groundpile?
2233 if (Type
== IE_CONTAINER_PILE
)
2235 Type
= IE_CONTAINER_PILE
;
2236 RefreshGroundIcons();
2237 //probably we should stop the script or trigger it, whatever
2240 //Takes an item from the container's inventory and returns its pointer
2241 CREItem
*Container::RemoveItem(unsigned int idx
, unsigned int count
)
2243 CREItem
*ret
= inventory
.RemoveItem(idx
, count
);
2244 //we just took the 3. or less item, groundpile changed
2245 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<3)) {
2246 RefreshGroundIcons();
2251 //Adds an item to the container's inventory
2252 //containers always have enough capacity (so far), thus we always return 2
2253 int Container::AddItem(CREItem
*item
)
2255 inventory
.AddItem(item
);
2256 //we just added a 3. or less item, groundpile changed
2257 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<4)) {
2258 RefreshGroundIcons();
2263 void Container::RefreshGroundIcons()
2265 int i
= inventory
.GetSlotCount();
2266 if (i
>MAX_GROUND_ICON_DRAWN
)
2267 i
= MAX_GROUND_ICON_DRAWN
;
2270 CREItem
*slot
= inventory
.GetSlotItem(i
); //borrowed reference
2271 Item
*itm
= gamedata
->GetItem( slot
->ItemResRef
); //cached reference
2272 //well, this is required in PST, needs more work if some other
2273 //game is broken by not using -1,0
2274 groundicons
[i
] = gamedata
->GetBAMSprite( itm
->GroundIcon
, 0, 0 );
2275 gamedata
->FreeItem( itm
, slot
->ItemResRef
); //decref
2279 //used for ground piles
2280 int Container::WantDither()
2282 //if pile is highlighted, always dither it
2284 return 2; //dither me if you want
2286 //if pile isn't highlighted, dither it if the polygon wants
2290 int Container::IsOpen() const
2292 if (Flags
&CONT_LOCKED
) {
2298 void Container::TryPickLock(Actor
*actor
)
2300 if (LockDifficulty
== 100) {
2301 if (OpenFail
!= (ieDword
)-1) {
2302 displaymsg
->DisplayStringName(OpenFail
, 0xbcefbc, actor
, IE_STR_SOUND
|IE_STR_SPEECH
);
2304 displaymsg
->DisplayConstantStringName(STR_CONT_NOPICK
, 0xbcefbc, actor
);
2308 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
2309 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
2310 LastPickLockFailed
= actor
->GetGlobalID();
2313 SetContainerLocked(false);
2314 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
2315 LastUnlocked
= actor
->GetGlobalID();
2317 int xp
= actor
->CalculateExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
2318 Game
*game
= core
->GetGame();
2319 game
->ShareXP(xp
, SX_DIVIDE
);
2322 void Container::TryBashLock(Actor
*actor
)
2324 //Get the strength bonus agains lock difficulty
2325 int str
= actor
->GetStat(IE_STR
);
2326 int strEx
= actor
->GetStat(IE_STREXTRA
);
2327 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
2328 unsigned int roll
= actor
->LuckyRoll(1, 10, bonus
, 0);
2330 if(roll
< LockDifficulty
|| LockDifficulty
== 100) {
2331 displaymsg
->DisplayConstantStringName(STR_CONTBASH_FAIL
, 0xbcefbc, actor
);
2335 displaymsg
->DisplayConstantStringName(STR_CONTBASH_DONE
, 0xd7d7be, actor
);
2336 SetContainerLocked(false);
2337 //Is this really useful ?
2338 LastUnlocked
= actor
->GetGlobalID();
2342 void Container::DebugDump() const
2344 printf( "Debugdump of Container %s\n", GetScriptName() );
2345 printf( "Container Global ID: %d\n", GetGlobalID());
2346 printf( "Position: %d.%d\n", Pos
.x
, Pos
.y
);
2347 printf( "Type: %d, Locked: %s, LockDifficulty: %d\n", Type
, YESNO(Flags
&CONT_LOCKED
), LockDifficulty
);
2348 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags
, YESNO(Trapped
), TrapDetected
);
2349 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2351 const char *name
= "NONE";
2353 name
= Scripts
[0]->GetName();
2355 printf( "Script: %s, Key: %s\n", name
, KeyResRef
);
2356 // FIXME: const_cast
2357 const_cast<Inventory
&>(inventory
).dump();
2360 bool Container::TryUnlock(Actor
*actor
) {
2361 if (!(Flags
&CONT_LOCKED
)) return true;
2363 return Highlightable::TryUnlock(actor
, false);