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 /***********************
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
);
81 if (Type
== ST_ACTOR
) {
82 InternalFlags
= IF_VISIBLE
| IF_ONCREATION
| IF_USEDSAVE
;
84 InternalFlags
= IF_ACTIVE
| IF_VISIBLE
| IF_ONCREATION
| IF_NOINT
;
91 LastSpellOnMe
= 0xffffffff;
93 LastSpellSeen
= 0xffffffff;
95 LastTargetPos
.empty();
96 locals
= new Variables();
97 locals
->SetType( GEM_VARIABLES_INT
);
98 locals
->ParseKey( 1 );
101 memset( script_timers
,0, sizeof(script_timers
));
104 Scriptable::~Scriptable(void)
107 ReleaseCurrentAction();
110 for (int i
= 0; i
< MAX_SCRIPTS
; i
++) {
112 delete( Scripts
[i
] );
116 core
->FreeString( overHeadText
);
123 void Scriptable::SetScriptName(const char* text
)
125 //if (text && text[0]) { //this leaves some uninitialized bytes
126 //lets hope this won't break anything
128 strnspccpy( scriptName
, text
, 32 );
132 /** Gets the DeathVariable */
133 const char* Scriptable::GetScriptName(void) const
138 Map
* Scriptable::GetCurrentArea() const
140 //this could be NULL, always check it
144 void Scriptable::SetMap(Map
*map
)
146 if (map
&& (map
->GetCurrentArea()!=map
)) {
147 //a map always points to itself (if it is a real map)
148 printMessage("Scriptable","Invalid map set!\n",LIGHT_RED
);
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 // but not in bg2 (kill Abazigal in ar6005)
279 if (core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
) {
280 if (! (core
->HasFeature(GF_CUTSCENE_AREASCRIPTS
) && Type
== ST_AREA
)) {
285 if ((InternalFlags
& IF_NOINT
) && (CurrentAction
|| GetNextAction())) {
289 if (!CurrentActionInterruptable
) {
290 if (!CurrentAction
&& !GetNextAction()) abort();
294 // only allow death scripts to run once, hopefully?
295 // this is probably terrible logic which needs moving elsewhere
296 if ((lastRunTime
!= 0) && (InternalFlags
& IF_JUSTDIED
)) {
300 ieDword thisTime
= core
->GetGame()->Ticks
;
301 if (( thisTime
- lastRunTime
) < 1000) {
305 lastDelay
= lastRunTime
;
306 lastRunTime
= thisTime
;
310 bool continuing
= false, done
= false;
311 for (int i
= 0;i
<scriptCount
;i
++) {
312 //disable AI script level for actors in party when the player disabled them
313 if ((i
== AI_SCRIPT_LEVEL
) && Type
== ST_ACTOR
&& ((Actor
*) this)->InParty
) {
314 if (core
->GetGame()->ControlStatus
&CS_PARTY_AI
) {
319 GameScript
*Script
= Scripts
[i
];
321 alive
|= Script
->Update(&continuing
, &done
);
324 /* scripts are not concurrent, see WAITPC override script for example */
327 if (alive
&& UnselectableTimer
) {
329 if (!UnselectableTimer
) {
330 if (Type
== ST_ACTOR
) {
331 ((Actor
*) this)->SetCircleSize();
335 InternalFlags
&= ~IF_ONCREATION
;
338 void Scriptable::AddAction(Action
* aC
)
341 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
345 InternalFlags
|=IF_ACTIVE
;
348 // attempt to handle 'instant' actions, from instant.ids, which run immediately
349 // when added if the action queue is empty, even on actors which are Held/etc
350 if (!CurrentAction
&& !GetNextAction()) {
351 if (actionflags
[aC
->actionID
] & AF_INSTANT
) {
353 GameScript::ExecuteAction( this, CurrentAction
);
358 actionQueue
.push_back( aC
);
361 void Scriptable::AddActionInFront(Action
* aC
)
364 printf( "[GameScript]: NULL action encountered for %s!\n",scriptName
);
367 InternalFlags
|=IF_ACTIVE
;
368 actionQueue
.push_front( aC
);
372 Action
* Scriptable::GetNextAction() const
374 if (actionQueue
.size() == 0) {
377 return actionQueue
.front();
380 Action
* Scriptable::PopNextAction()
382 if (actionQueue
.size() == 0) {
385 Action
* aC
= actionQueue
.front();
386 actionQueue
.pop_front();
390 void Scriptable::ClearActions()
392 ReleaseCurrentAction();
393 for (unsigned int i
= 0; i
< actionQueue
.size(); i
++) {
394 Action
* aC
= actionQueue
.front();
395 actionQueue
.pop_front();
401 //clear the triggers as fast as possible when queue ended?
404 if (Type
== ST_ACTOR
) {
411 void Scriptable::ReleaseCurrentAction()
414 CurrentAction
->Release();
415 CurrentAction
= NULL
;
418 CurrentActionState
= 0;
419 CurrentActionTarget
= 0;
420 CurrentActionInterruptable
= true;
423 ieWord
Scriptable::GetGlobalID()
425 if (Type
== ST_ACTOR
) {
426 Actor
*actor
= (Actor
*) this;
427 return actor
->globalID
;
432 void Scriptable::ProcessActions(bool force
)
434 unsigned long thisTime
= core
->GetGame()->Ticks
;
436 if (!force
&& (( thisTime
- startTime
) < interval
)) {
439 startTime
= thisTime
;
442 if (WaitCounter
) return;
446 CurrentActionInterruptable
= true;
447 if (!CurrentAction
) {
448 CurrentAction
= PopNextAction();
450 if (!CurrentAction
) {
455 //removing the triggers at the end of the
460 GameScript::ExecuteAction( this, CurrentAction
);
461 //break execution in case of a Wait flag
463 //clear triggers while waiting
467 //break execution in case of blocking action
471 //break execution in case of movement
472 //we should not actually break here, or else fix waypoints
477 //most likely the best place to clear triggers is here
478 //queue is empty, or there is a looong action subject to break
480 if (InternalFlags
&IF_IDLE
) {
485 bool Scriptable::InMove() const
487 if (Type
!=ST_ACTOR
) {
490 Movable
*me
= (Movable
*) this;
491 return me
->GetNextStep()!=NULL
;
494 void Scriptable::SetWait(unsigned long time
)
499 unsigned long Scriptable::GetWait() const
504 Scriptable
*Scriptable::GetCutsceneID() const
509 void Scriptable::LeaveDialog()
511 InternalFlags
|=IF_WASINDIALOG
;
514 //this ends cutscene mode for this Sender
515 void Scriptable::ClearCutsceneID()
518 InternalFlags
&= ~IF_CUTSCENEID
;
521 //if the cutsceneID doesn't exist, we simply skip the action
522 //because the cutscene script executer DOESN'T get hijacked
523 void Scriptable::SetCutsceneID(Scriptable
*csid
)
526 InternalFlags
|= IF_CUTSCENEID
;
529 void Scriptable::Hide()
531 InternalFlags
&=~(IF_VISIBLE
);
534 void Scriptable::Unhide()
536 InternalFlags
|= IF_VISIBLE
;
539 void Scriptable::Interrupt()
541 InternalFlags
&= ~IF_NOINT
;
544 void Scriptable::NoInterrupt()
546 InternalFlags
|= IF_NOINT
;
549 //also turning off the idle flag so it won't run continuously
550 void Scriptable::Deactivate()
552 InternalFlags
&=~(IF_ACTIVE
|IF_IDLE
);
555 //turning off the not interruptable flag, actions should reenable it themselves
556 //also turning off the idle flag
557 //heh, no, i wonder why did i touch the interruptable flag here
558 void Scriptable::Activate()
560 InternalFlags
|= IF_ACTIVE
;
561 InternalFlags
&= ~IF_IDLE
;
564 void Scriptable::PartyRested()
566 InternalFlags
|=IF_PARTYRESTED
;
569 ieDword
Scriptable::GetInternalFlag()
571 return InternalFlags
;
574 void Scriptable::InitTriggers()
580 void Scriptable::ClearTriggers()
582 for (TriggerObjects::iterator m
= tolist
.begin(); m
!= tolist
.end (); m
++) {
588 if (bittriggers
& BT_DIE
) {
589 InternalFlags
&= ~IF_JUSTDIED
;
591 if (bittriggers
& BT_ONCREATION
) {
592 InternalFlags
&= ~IF_ONCREATION
;
594 if (bittriggers
& BT_BECAMEVISIBLE
) {
595 InternalFlags
&= ~IF_BECAMEVISIBLE
;
597 if (bittriggers
& BT_PARTYRESTED
) {
598 InternalFlags
&= ~IF_PARTYRESTED
;
600 if (bittriggers
& BT_WASINDIALOG
) {
601 InternalFlags
&= ~IF_WASINDIALOG
;
603 if (bittriggers
& BT_PARTYRESTED
) {
604 InternalFlags
&= ~IF_PARTYRESTED
;
609 void Scriptable::SetBitTrigger(ieDword bittrigger
)
611 bittriggers
|= bittrigger
;
614 void Scriptable::AddTrigger(ieDword
*actorref
)
616 tolist
.push_back(actorref
);
619 void Scriptable::CastSpellPointEnd( const ieResRef SpellResRef
)
621 if (Type
== ST_ACTOR
) {
622 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
625 if (SpellHeader
== -1) {
626 LastTargetPos
.empty();
630 if (LastTargetPos
.isempty()) {
635 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
636 //create projectile from known spellheader
637 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
640 pro
->SetCaster(GetGlobalID());
642 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
643 // try and make projectiles start from the right trap position
644 // see the traps in the duergar/assassin battle in bg2 dungeon
645 // see also function below - maybe we should fix Pos instead
646 origin
= ((InfoPoint
*)this)->TrapLaunch
;
648 GetCurrentArea()->AddProjectile(pro
, origin
, LastTargetPos
);
651 // caster - Casts spellname
653 const char* msg
= core
->GetString(displaymsg
->GetStringReference(STR_ACTION_CAST
), 0);
654 snprintf(tmp
, sizeof(tmp
), "%s %s", msg
, core
->GetString(spl
->SpellName
));
655 displaymsg
->DisplayStringName(tmp
, 0xffffff, this);
657 core
->Autopause(AP_SPELLCAST
);
660 LastTargetPos
.empty();
663 static EffectRef fx_set_invisible_state_ref
={"State:Invisible",NULL
,-1};
665 void Scriptable::CastSpellEnd( const ieResRef SpellResRef
)
667 if (Type
== ST_ACTOR
) {
668 ((Actor
*) this)->SetStance(IE_ANI_CONJURE
);
671 if (SpellHeader
== -1) {
679 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
680 //create projectile from known spellheader
681 Projectile
*pro
= spl
->GetProjectile(this, SpellHeader
, LastTargetPos
);
683 pro
->SetCaster(GetGlobalID());
685 if (Type
== ST_TRIGGER
|| Type
== ST_PROXIMITY
) {
686 // try and make projectiles start from the right trap position
687 // see the traps in the duergar/assassin battle in bg2 dungeon
688 // see also function above - maybe we should fix Pos instead
689 origin
= ((InfoPoint
*)this)->TrapLaunch
;
692 GetCurrentArea()->AddProjectile(pro
, origin
, LastTarget
);
694 GetCurrentArea()->AddProjectile(pro
, origin
, LastTargetPos
);
697 ieDword spellnum
=ResolveSpellNumber( SpellResRef
);
698 if (spellnum
!=0xffffffff) {
699 area
->SeeSpellCast(this, spellnum
);
701 // caster - Casts spellname : target OR
702 // caster - spellname : target (repeating spells)
703 Scriptable
*target
= NULL
;
705 const char* msg
= core
->GetString(displaymsg
->GetStringReference(STR_ACTION_CAST
), 0);
706 const char* spell
= core
->GetString(spl
->SpellName
);
708 target
= area
->GetActorByGlobalID(LastTarget
);
710 if (stricmp(spell
, "")) {
712 snprintf(tmp
, sizeof(tmp
), "%s %s : %s", msg
, spell
, target
->GetName(-1));
714 snprintf(tmp
, sizeof(tmp
), "%s : %s", spell
, GetName(-1));
716 displaymsg
->DisplayStringName(tmp
, 0xffffff, this);
720 if (target
&& (Type
==ST_ACTOR
) ) {
721 Actor
*me
= (Actor
*) this;
722 target
->LastSpellOnMe
= spellnum
;
723 target
->LastCasterOnMe
= me
->GetID();
724 // don't cure invisibility if this is a self targetting invisibility spell
726 //can't check GetEffectBlock, since it doesn't construct the queue for selftargetting spells
728 unsigned int opcode
= EffectQueue::ResolveEffect(fx_set_invisible_state_ref
);
729 for (unsigned int i
=0; i
< spl
->ext_headers
[SpellHeader
].FeatureCount
; i
++) {
730 if (spl
->GetExtHeader(SpellHeader
)->features
[i
].Opcode
== opcode
) {
735 if (invis
&& spl
->GetExtHeader(SpellHeader
)->Target
== TARGET_SELF
) {
738 me
->CureInvisibility();
740 if (target
!=this) { //FIXME: only dispel it for hostile spells
747 core
->Autopause(AP_SPELLCAST
);
749 gamedata
->FreeSpell(spl
, SpellResRef
, false);
751 LastTargetPos
.empty();
754 //set target as point
755 //if spell needs to be depleted, do it
756 //if spell is illegal stop casting
757 int Scriptable::CastSpellPoint( const ieResRef SpellResRef
, const Point
&target
, bool deplete
, bool instant
)
760 LastTargetPos
.empty();
761 if (Type
== ST_ACTOR
) {
762 Actor
*actor
= (Actor
*) this;
763 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
767 LastTargetPos
= target
;
768 return SpellCast(SpellResRef
, instant
);
771 //set target as actor (if target isn't actor, use its position)
772 //if spell needs to be depleted, do it
773 //if spell is illegal stop casting
774 int Scriptable::CastSpell( const ieResRef SpellResRef
, Scriptable
* target
, bool deplete
, bool instant
)
777 LastTargetPos
.empty();
778 if (Type
== ST_ACTOR
) {
779 Actor
*actor
= (Actor
*) this;
780 if (actor
->HandleCastingStance(SpellResRef
,deplete
) ) {
785 if (!target
) target
= this;
787 LastTargetPos
= target
->Pos
;
788 if (target
->Type
==ST_ACTOR
) {
789 LastTarget
= target
->GetGlobalID();
791 return SpellCast(SpellResRef
, instant
);
794 //start spellcasting (common part)
795 int Scriptable::SpellCast(const ieResRef SpellResRef
, bool instant
)
797 Spell
* spl
= gamedata
->GetSpell( SpellResRef
);
803 if (Type
== ST_ACTOR
) {
804 Actor
*actor
= (Actor
*) this;
805 //The ext. index is here to calculate the casting time
806 int level
= actor
->GetXPLevel(true);
807 //Add casting level bonus/penalty - from stats and LVLMODWM.2da
808 level
+= actor
->CastingLevelBonus(level
, spl
->SpellType
);
809 SpellHeader
= spl
->GetHeaderIndexFromLevel(level
);
814 SPLExtHeader
*header
= spl
->GetExtHeader(SpellHeader
);
815 int casting_time
= (int)header
->CastingTime
;
816 // how does this work for non-actors exactly?
817 if (Type
== ST_ACTOR
) {
818 // The mental speed effect can shorten or lengthen the casting time.
819 casting_time
-= (int)((Actor
*) this)->GetStat(IE_MENTALSPEED
);
820 if (casting_time
< 0) casting_time
= 0;
822 // this is a guess which seems approximately right so far
823 int duration
= (casting_time
*core
->Time
.round_size
) / 10;
828 if (Type
== ST_ACTOR
) {
829 Actor
*actor
= (Actor
*) this;
830 EffectQueue
*fxqueue
= spl
->GetEffectBlock(this, this->Pos
, -1);
831 spl
->AddCastingGlow(fxqueue
, duration
);
832 fxqueue
->SetOwner(actor
);
833 fxqueue
->AddAllEffects(actor
, actor
->Pos
);
837 gamedata
->FreeSpell(spl
, SpellResRef
, false);
841 bool Scriptable::TimerActive(ieDword ID
)
846 if (script_timers
[ID
]) {
852 bool Scriptable::TimerExpired(ieDword ID
)
857 if (script_timers
[ID
] && script_timers
[ID
] < core
->GetGame()->GameTime
) {
858 // expired timers become inactive after being checked
859 script_timers
[ID
] = 0;
865 void Scriptable::StartTimer(ieDword ID
, ieDword expiration
)
868 printMessage("Scriptable", " ", RED
);
869 printf("Timer id %d exceeded MAX_TIMER %d\n", ID
, MAX_TIMER
);
872 script_timers
[ID
]= core
->GetGame()->GameTime
+ expiration
*AI_UPDATE_TIME
;
875 /********************
877 ********************/
879 Selectable::Selectable(ScriptableType type
)
886 circleBitmap
[0] = NULL
;
887 circleBitmap
[1] = NULL
;
890 void Selectable::SetSpriteCover(SpriteCover
* c
)
896 Selectable::~Selectable(void)
901 void Selectable::SetBBox(const Region
&newBBox
)
906 static const unsigned long tp_steps
[8]={3,2,1,0,1,2,3,4};
908 void Selectable::DrawCircle(const Region
&vp
)
910 /* BG2 colours ground circles as follows:
911 dark green for unselected party members
912 bright green for selected party members
913 flashing green/white for a party member the mouse is over
914 bright red for enemies
915 yellow for panicked actors
916 flashing red/white for enemies the mouse is over
917 flashing cyan/white for neutrals the mouse is over
924 Color
* col
= &selectedColor
;
925 Sprite2D
* sprite
= circleBitmap
[0];
928 sprite
= circleBitmap
[1];
930 //doing a time dependent flashing of colors
931 //if it is too fast, increase the 6 to 7
934 step
= tp_steps
[(step
>> 6) & 7];
936 mix
.r
= (overColor
.r
*step
+selectedColor
.r
*(8-step
))/8;
937 mix
.g
= (overColor
.g
*step
+selectedColor
.g
*(8-step
))/8;
938 mix
.b
= (overColor
.b
*step
+selectedColor
.b
*(8-step
))/8;
945 core
->GetVideoDriver()->BlitSprite( sprite
, Pos
.x
- vp
.x
, Pos
.y
- vp
.y
, true );
947 // for size >= 2, radii are (size-1)*16, (size-1)*12
948 // for size == 1, radii are 12, 9
949 int csize
= (size
- 1) * 4;
950 if (csize
< 4) csize
= 3;
951 core
->GetVideoDriver()->DrawEllipse( (ieWord
) (Pos
.x
- vp
.x
), (ieWord
) (Pos
.y
- vp
.y
),
952 (ieWord
) (csize
* 4), (ieWord
) (csize
* 3), *col
);
956 // Check if P is over our ground circle
957 bool Selectable::IsOver(const Point
&P
) const
960 if (csize
< 2) csize
= 2;
962 int dx
= P
.x
- Pos
.x
;
963 int dy
= P
.y
- Pos
.y
;
965 // check rectangle first
966 if (dx
< -(csize
-1)*16 || dx
> (csize
-1)*16) return false;
967 if (dy
< -(csize
-1)*12 || dy
> (csize
-1)*12) return false;
969 // then check ellipse
970 int r
= 9*dx
*dx
+ 16*dy
*dy
; // 48^2 * ( (dx/16)^2 + (dy/12)^2 )
972 return (r
<= 48*48*(csize
-1)*(csize
-1));
975 bool Selectable::IsSelected() const
977 return Selected
== 1;
980 void Selectable::SetOver(bool over
)
985 //don't call this function after rendering the cover and before the
986 //blitting of the sprite or bad things will happen :)
987 void Selectable::Select(int Value
)
989 if (Selected
!=0x80 || Value
!=1) {
990 Selected
= (ieWord
) Value
;
992 //forcing regeneration of the cover
993 SetSpriteCover(NULL
);
996 void Selectable::SetCircle(int circlesize
, const Color
&color
, Sprite2D
* normal_circle
, Sprite2D
* selected_circle
)
999 selectedColor
= color
;
1000 overColor
.r
= color
.r
>> 1;
1001 overColor
.g
= color
.g
>> 1;
1002 overColor
.b
= color
.b
>> 1;
1003 overColor
.a
= color
.a
;
1004 circleBitmap
[0] = normal_circle
;
1005 circleBitmap
[1] = selected_circle
;
1008 //used for creatures
1009 int Selectable::WantDither()
1011 //if dithering is disabled globally, don't do it
1012 if (core
->FogOfWar
&4) {
1015 //if actor is dead, dither it if polygon wants
1016 if (Selected
&0x80) {
1019 //if actor is selected dither it
1026 /***********************
1027 * Highlightable Class *
1028 ***********************/
1030 Highlightable::Highlightable(ScriptableType type
)
1031 : Scriptable( type
)
1035 Cursor
= IE_CURSOR_NORMAL
;
1039 Highlightable::~Highlightable(void)
1046 bool Highlightable::IsOver(const Point
&Pos
) const
1051 return outline
->PointIn( Pos
);
1054 void Highlightable::DrawOutline() const
1059 core
->GetVideoDriver()->DrawPolyline( outline
, outlineColor
, true );
1062 void Highlightable::SetCursor(unsigned char CursorIndex
)
1064 Cursor
= CursorIndex
;
1067 bool Highlightable::TryUnlock(Actor
*actor
, bool removekey
) {
1068 const char *Key
= GetKey();
1069 Actor
*haskey
= NULL
;
1071 if (Key
&& actor
->InParty
) {
1072 Game
*game
= core
->GetGame();
1073 //allow unlock when the key is on any partymember
1074 for (int idx
= 0; idx
< game
->GetPartySize(false); idx
++) {
1075 Actor
*pc
= game
->FindPC(idx
+ 1);
1078 if (pc
->inventory
.HasItem(Key
,0) ) {
1084 //actor is not in party, check only actor
1085 if (actor
->inventory
.HasItem(Key
,0) ) {
1095 CREItem
*item
= NULL
;
1096 haskey
->inventory
.RemoveItem(Key
,0,&item
);
1097 //the item should always be existing!!!
1111 Movable::Movable(ScriptableType type
)
1112 : Selectable( type
)
1123 AttackMovements
[0] = 100;
1124 AttackMovements
[1] = 0;
1125 AttackMovements
[2] = 0;
1128 Movable::~Movable(void)
1135 int Movable::GetPathLength()
1137 PathNode
*node
= GetNextStep(0);
1139 while (node
->Next
) {
1146 PathNode
*Movable::GetNextStep(int x
)
1149 DoStep((unsigned int) ~0);
1151 PathNode
*node
= step
;
1152 while(node
&& x
--) {
1158 Point
Movable::GetMostLikelyPosition()
1164 //actually, sometimes middle path would be better, if
1165 //we stand in Destination already
1166 int halfway
= GetPathLength()/2;
1167 PathNode
*node
= GetNextStep(halfway
);
1169 return Point((ieWord
) ((node
->x
*16)+8), (ieWord
) ((node
->y
*12)+6) );
1174 void Movable::SetStance(unsigned int arg
)
1176 //don't modify stance from dead back to anything if the actor is dead
1177 if ((StanceID
==IE_ANI_TWITCH
|| StanceID
==IE_ANI_DIE
) && (arg
!=IE_ANI_TWITCH
) ) {
1178 if (GetInternalFlag()&IF_REALLYDIED
) {
1179 printMessage("Movable","Stance overridden by death\n", YELLOW
);
1184 if (arg
<MAX_ANIMS
) {
1185 StanceID
=(unsigned char) arg
;
1187 if (StanceID
== IE_ANI_ATTACK
) {
1188 // Set stance to a random attack animation
1190 int random
= rand()%100;
1191 if (random
< AttackMovements
[0]) {
1192 StanceID
= IE_ANI_ATTACK_BACKSLASH
;
1193 } else if (random
< AttackMovements
[0] + AttackMovements
[1]) {
1194 StanceID
= IE_ANI_ATTACK_SLASH
;
1196 StanceID
= IE_ANI_ATTACK_JAB
;
1201 StanceID
=IE_ANI_AWAKE
; //
1202 printf("Tried to set invalid stance id (%u)\n", arg
);
1206 void Movable::SetAttackMoveChances(ieWord
*amc
)
1208 AttackMovements
[0]=amc
[0];
1209 AttackMovements
[1]=amc
[1];
1210 AttackMovements
[2]=amc
[2];
1215 //this could be used for WingBuffet as well
1216 void Movable::MoveLine(int steps
, int Pass
, ieDword orient
)
1218 //remove previous path
1225 path
= area
->GetLine( p
, steps
, orient
, Pass
);
1228 void AdjustPositionTowards(Point
&Pos
, ieDword time_diff
, unsigned int walk_speed
, short srcx
, short srcy
, short destx
, short desty
) {
1230 Pos
.x
+= ( unsigned short )
1231 ( ( ( ( ( destx
* 16 ) + 8 ) - Pos
.x
) * ( time_diff
) ) / walk_speed
);
1233 Pos
.x
-= ( unsigned short )
1234 ( ( ( Pos
.x
- ( ( destx
* 16 ) + 8 ) ) * ( time_diff
) ) / walk_speed
);
1236 Pos
.y
+= ( unsigned short )
1237 ( ( ( ( ( desty
* 12 ) + 6 ) - Pos
.y
) * ( time_diff
) ) / walk_speed
);
1239 Pos
.y
-= ( unsigned short )
1240 ( ( ( Pos
.y
- ( ( desty
* 12 ) + 6 ) ) * ( time_diff
) ) / walk_speed
);
1244 // returns whether we made all pending steps (so, false if we must be called again this tick)
1245 // we can't just do them all here because the caller might have to update searchmap etc
1246 bool Movable::DoStep(unsigned int walk_speed
, ieDword time
)
1251 if (!time
) time
= core
->GetGame()->Ticks
;
1253 // zero speed: no movement
1254 timeStartStep
= time
;
1255 StanceID
= IE_ANI_READY
;
1260 timeStartStep
= time
;
1261 } else if (step
->Next
&& (( time
- timeStartStep
) >= walk_speed
)) {
1262 //printf("[New Step] : Orientation = %d\n", step->orient);
1264 timeStartStep
= timeStartStep
+ walk_speed
;
1266 SetOrientation (step
->orient
, false);
1267 StanceID
= IE_ANI_WALK
;
1268 if ((Type
== ST_ACTOR
) && (InternalFlags
& IF_RUNNING
)) {
1269 StanceID
= IE_ANI_RUN
;
1271 Pos
.x
= ( step
->x
* 16 ) + 8;
1272 Pos
.y
= ( step
->y
* 12 ) + 6;
1274 // we reached our destination, we are done
1276 NewOrientation
= Orientation
;
1277 //since clearpath no longer sets currentaction to NULL
1279 //no we don't, action is responsible for releasing itself
1280 //ReleaseCurrentAction();
1283 if (( time
- timeStartStep
) >= walk_speed
) {
1284 // we didn't finish all pending steps, yet
1287 AdjustPositionTowards(Pos
, time
- timeStartStep
, walk_speed
, step
->x
, step
->y
, step
->Next
->x
, step
->Next
->y
);
1291 void Movable::AddWayPoint(const Point
&Des
)
1298 //it is tempting to use 'step' here, as it could
1299 //be about half of the current path already
1300 PathNode
*endNode
= path
;
1301 while(endNode
->Next
) {
1302 endNode
= endNode
->Next
;
1304 Point
p(endNode
->x
, endNode
->y
);
1305 area
->ClearSearchMapFor(this);
1306 PathNode
*path2
= area
->FindPath( p
, Des
, size
);
1307 endNode
->Next
= path2
;
1308 //probably it is wise to connect it both ways?
1309 path2
->Parent
= endNode
;
1312 void Movable::FixPosition()
1314 if (Type
!=ST_ACTOR
) {
1317 Actor
*actor
= (Actor
*) this;
1318 if (actor
->GetStat(IE_DONOTJUMP
)&DNJ_BIRD
) {
1321 //before fixposition, you should remove own shadow
1322 area
->ClearSearchMapFor(this);
1325 GetCurrentArea()->AdjustPosition(Pos
);
1330 void Movable::WalkTo(const Point
&Des
, int distance
)
1334 // maybe caller should be responsible for this
1335 if ((Des
.x
/16 == Pos
.x
/16) && (Des
.y
/12 == Pos
.y
/12)) {
1340 // the prev_step stuff is a naive attempt to allow re-pathing while moving
1341 PathNode
*prev_step
= NULL
;
1342 unsigned char old_stance
= StanceID
;
1343 if (step
&& step
->Next
) {
1344 // don't interrupt in the middle of a step; path from the next one
1345 prev_step
= new PathNode(*step
);
1346 from
.x
= ( step
->Next
->x
* 16 ) + 8;
1347 from
.y
= ( step
->Next
->y
* 12 ) + 6;
1355 area
->ClearSearchMapFor(this);
1357 path
= area
->FindPathNear( from
, Des
, size
, distance
);
1359 path
= area
->FindPath( from
, Des
, size
, distance
);
1361 //ClearPath sets destination, so Destination must be set after it
1362 //also we should set Destination only if there is a walkable path
1367 // we want to smoothly continue, please
1368 // this all needs more thought! but it seems to work okay
1369 StanceID
= old_stance
;
1372 // this is a terrible hack to make up for the
1373 // pathfinder orienting the first node wrong
1374 // should be fixed in pathfinder and not here!
1376 next
.x
= path
->x
; next
.y
= path
->y
;
1377 follow
.x
= path
->Next
->x
;
1378 follow
.y
= path
->Next
->y
;
1379 path
->orient
= GetOrient(follow
, next
);
1382 // then put the prev_step at the beginning of the path
1383 prev_step
->Next
= path
;
1384 path
->Parent
= prev_step
;
1392 delete( prev_step
);
1398 void Movable::RunAwayFrom(const Point
&Des
, int PathLength
, int flags
)
1401 area
->ClearSearchMapFor(this);
1402 path
= area
->RunAway( Pos
, Des
, size
, PathLength
, flags
);
1405 void Movable::RandomWalk(bool can_stop
, bool run
)
1410 //if not continous random walk, then stops for a while
1411 if (can_stop
&& (rand()&3) ) {
1412 SetWait((rand()&7)+7);
1416 InternalFlags
|=IF_RUNNING
;
1418 //the commenting-out of the clear search map call was removed in 0.4.0
1419 //if you want to put it back for some reason, check
1420 //if the searchmap is not eaten up
1421 area
->ClearSearchMapFor(this);
1424 //selecting points around a circle's edge around actor (didn't work better)
1425 //int x = core->Roll(1,100,-50);
1427 //p.y+=(int) sqrt(100-x*x);
1429 //selecting points in a square around actor
1430 p
.x
+=core
->Roll(1,50,-25);
1431 p
.y
+=core
->Roll(1,50,-25);
1432 //the 5th parameter is controlling the orientation of the actor
1433 //0 - back away, 1 - face direction
1434 path
= area
->RunAway( Pos
, p
, size
, 50, 1 );
1437 void Movable::MoveTo(const Point
&Des
)
1439 area
->ClearSearchMapFor(this);
1442 if (BlocksSearchMap()) {
1443 area
->BlockSearchMap( Pos
, size
, IsPC()?PATH_MAP_PC
:PATH_MAP_NPC
);
1447 void Movable::ClearPath()
1449 //this is to make sure attackers come to us
1450 //make sure ClearPath doesn't screw Destination (in the rare cases Destination
1451 //is set before ClearPath
1453 if (StanceID
==IE_ANI_WALK
|| StanceID
==IE_ANI_RUN
) {
1454 StanceID
= IE_ANI_AWAKE
;
1456 InternalFlags
&=~IF_NORECTICLE
;
1457 PathNode
* thisNode
= path
;
1459 PathNode
* nextNode
= thisNode
->Next
;
1461 thisNode
= nextNode
;
1465 //don't call ReleaseCurrentAction
1468 void Movable::DrawTargetPoint(const Region
&vp
)
1470 if (!path
|| !Selected
|| (InternalFlags
&IF_NORECTICLE
) )
1473 // recticles are never drawn in cutscenes
1474 if ((core
->GetGameControl()->GetScreenFlags()&SF_CUTSCENE
))
1477 // generates "step" from sequence 3 2 1 0 1 2 3 4
1478 // updated each 1/15 sec
1481 step
= tp_steps
[(step
>> 6) & 7];
1484 int csize
= (size
- 1) * 4;
1485 if (csize
< 4) csize
= 3;
1487 /* segments should not go outside selection radius */
1488 unsigned short xradius
= (csize
* 4) - 5;
1489 unsigned short yradius
= (csize
* 3) - 5;
1490 ieWord xcentre
= (ieWord
) (Destination
.x
- vp
.x
);
1491 ieWord ycentre
= (ieWord
) (Destination
.y
- vp
.y
);
1493 // TODO: 0.5 and 0.7 are pretty much random values
1495 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
+ step
, ycentre
, xradius
,
1496 yradius
, selectedColor
, -0.5, 0.5 );
1498 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
- step
, xradius
,
1499 yradius
, selectedColor
, -0.7 - M_PI_2
, 0.7 - M_PI_2
);
1501 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
- step
, ycentre
, xradius
,
1502 yradius
, selectedColor
, -0.5 - M_PI
, 0.5 - M_PI
);
1504 core
->GetVideoDriver()->DrawEllipseSegment( xcentre
, ycentre
+ step
, xradius
,
1505 yradius
, selectedColor
, -0.7 - M_PI
- M_PI_2
, 0.7 - M_PI
- M_PI_2
);
1508 /**********************
1509 * Tiled Object Class *
1510 **********************/
1512 TileObject::TileObject()
1521 TileObject::~TileObject()
1527 free( closedtiles
);
1531 void TileObject::SetOpenTiles(unsigned short* Tiles
, int cnt
)
1540 void TileObject::SetClosedTiles(unsigned short* Tiles
, int cnt
)
1543 free( closedtiles
);
1545 closedtiles
= Tiles
;
1553 Door::Door(TileOverlay
* Overlay
)
1554 : Highlightable( ST_DOOR
)
1571 OpenStrRef
= (ieDword
) -1;
1576 if (Flags
&DOOR_OPEN
) {
1596 void Door::ImpedeBlocks(int count
, Point
*points
, unsigned char value
)
1598 for(int i
= 0;i
<count
;i
++) {
1599 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_NOTDOOR
;
1600 area
->SearchMap
->SetAt( points
[i
].x
, points
[i
].y
, (tmp
|value
) );
1604 void Door::UpdateDoor()
1606 if (Flags
&DOOR_OPEN
) {
1611 // update the Scriptable position
1612 Pos
.x
= outline
->BBox
.x
+ outline
->BBox
.w
/2;
1613 Pos
.y
= outline
->BBox
.y
+ outline
->BBox
.h
/2;
1615 unsigned char oval
, cval
;
1616 oval
= PATH_MAP_IMPASSABLE
;
1617 if (Flags
& DOOR_TRANSPARENT
) {
1618 cval
= PATH_MAP_DOOR_TRANSPARENT
;
1621 cval
= PATH_MAP_DOOR_OPAQUE
;
1623 if (Flags
&DOOR_OPEN
) {
1624 ImpedeBlocks(cibcount
, closed_ib
, 0);
1625 ImpedeBlocks(oibcount
, open_ib
, cval
);
1628 ImpedeBlocks(oibcount
, open_ib
, 0);
1629 ImpedeBlocks(cibcount
, closed_ib
, cval
);
1632 InfoPoint
*ip
= area
->TMap
->GetInfoPoint(LinkedInfo
);
1634 if (Flags
&DOOR_OPEN
) ip
->Flags
&=~INFO_DOOR
;
1635 else ip
->Flags
|=INFO_DOOR
;
1639 void Door::ToggleTiles(int State
, int playsound
)
1645 state
= !closedIndex
;
1646 if (playsound
&& ( OpenSound
[0] != '\0' ))
1647 core
->GetAudioDrv()->Play( OpenSound
);
1649 state
= closedIndex
;
1650 if (playsound
&& ( CloseSound
[0] != '\0' ))
1651 core
->GetAudioDrv()->Play( CloseSound
);
1653 for (i
= 0; i
< tilecount
; i
++) {
1654 overlay
->tiles
[tiles
[i
]]->tileIndex
= (ieByte
) state
;
1657 //set door_open as state
1658 Flags
= (Flags
& ~DOOR_OPEN
) | (State
== !core
->HasFeature(GF_REVERSE_DOOR
) );
1661 //this is the short name (not the scripting name)
1662 void Door::SetName(const char* name
)
1664 strnlwrcpy( ID
, name
, 8 );
1667 void Door::SetTiles(unsigned short* Tiles
, int cnt
)
1676 void Door::SetDoorLocked(int Locked
, int playsound
)
1679 if (Flags
& DOOR_LOCKED
) return;
1681 if (playsound
&& ( LockSound
[0] != '\0' ))
1682 core
->GetAudioDrv()->Play( LockSound
);
1685 if (!(Flags
& DOOR_LOCKED
)) return;
1686 Flags
&=~DOOR_LOCKED
;
1687 if (playsound
&& ( UnLockSound
[0] != '\0' ))
1688 core
->GetAudioDrv()->Play( UnLockSound
);
1692 int Door::IsOpen() const
1694 int ret
= core
->HasFeature(GF_REVERSE_DOOR
);
1695 if (Flags
&DOOR_OPEN
) {
1701 //also mark actors to fix position
1702 bool Door::BlockedOpen(int Open
, int ForceOpen
)
1716 //getting all impeded actors flagged for jump
1720 for(int i
= 0;i
<count
;i
++) {
1722 rgn
.x
= points
[i
].x
*16;
1723 rgn
.y
= points
[i
].y
*12;
1724 unsigned char tmp
= area
->SearchMap
->GetAt( points
[i
].x
, points
[i
].y
) & PATH_MAP_ACTOR
;
1726 int ac
= area
->GetActorInRect(ab
, rgn
, false);
1728 if (ab
[ac
]->GetBase(IE_DONOTJUMP
)) {
1731 ab
[ac
]->SetBase(IE_DONOTJUMP
, DNJ_JUMP
);
1740 if ((Flags
&DOOR_SLIDE
) || ForceOpen
) {
1746 void Door::SetDoorOpen(int Open
, int playsound
, ieDword ID
)
1749 //the door cannot be blocked when opening,
1750 //but the actors will be pushed
1751 //BlockedOpen will mark actors to be pushed
1752 if (BlockedOpen(Open
,0) && !Open
) {
1753 //clear up the blocking actors
1754 area
->JumpActors(false);
1757 area
->JumpActors(true);
1760 LastEntered
= ID
; //used as lastOpener
1762 // in PS:T, opening a door does not unlock it
1763 if (!core
->HasFeature(GF_REVERSE_DOOR
)) {
1764 SetDoorLocked(false,playsound
);
1767 LastTriggerObject
= LastTrigger
= ID
; //used as lastCloser
1769 ToggleTiles(Open
, playsound
);
1770 //synchronising other data with the door state
1772 area
->ActivateWallgroups(open_wg_index
, open_wg_count
, Flags
&DOOR_OPEN
);
1773 area
->ActivateWallgroups(closed_wg_index
, closed_wg_count
, !(Flags
&DOOR_OPEN
));
1776 bool Door::TryUnlock(Actor
*actor
) {
1777 if (!(Flags
&DOOR_LOCKED
)) return true;
1779 // don't remove key in PS:T!
1780 bool removekey
= !core
->HasFeature(GF_REVERSE_DOOR
) && Flags
&DOOR_KEY
;
1781 return Highlightable::TryUnlock(actor
, removekey
);
1784 void Door::TryDetectSecret(int skill
)
1786 if (Type
!= ST_DOOR
) return;
1787 if (Visible()) return;
1788 if (skill
> (signed)DiscoveryDiff
) {
1789 Flags
|= DOOR_FOUND
;
1790 core
->PlaySound(DS_FOUNDSECRET
);
1794 // return true if the door isn't secret or if it is, but was already discovered
1795 bool Door::Visible()
1797 return (!(Flags
& DOOR_SECRET
) || (Flags
& DOOR_FOUND
));
1800 void Door::SetPolygon(bool Open
, Gem_Polygon
* poly
)
1813 void Door::SetNewOverlay(TileOverlay
*Overlay
) {
1815 ToggleTiles(IsOpen(), false);
1818 void Highlightable::SetTrapDetected(int x
)
1820 if(x
== TrapDetected
)
1824 core
->Autopause(AP_TRAP
);
1828 void Highlightable::TryDisarm(Actor
*actor
)
1830 if (!Trapped
|| !TrapDetected
) return;
1832 LastTriggerObject
= LastTrigger
= actor
->GetID();
1833 int skill
= actor
->GetStat(IE_TRAPS
);
1835 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapRemovalDiff
) {
1836 LastDisarmed
= actor
->GetID();
1839 displaymsg
->DisplayConstantStringName(STR_DISARM_DONE
, 0xd7d7be, actor
);
1840 int xp
= actor
->CalculateExperience(XP_DISARM
, actor
->GetXPLevel(1));
1841 Game
*game
= core
->GetGame();
1842 game
->ShareXP(xp
, SX_DIVIDE
);
1844 displaymsg
->DisplayConstantStringName(STR_DISARM_FAIL
, 0xd7d7be, actor
);
1845 TriggerTrap(skill
, LastTrigger
);
1850 void Door::TryPickLock(Actor
*actor
)
1852 if (LockDifficulty
== 100) {
1853 if (OpenStrRef
!= (ieDword
)-1) {
1854 displaymsg
->DisplayStringName(OpenStrRef
, 0xbcefbc, actor
, IE_STR_SOUND
|IE_STR_SPEECH
);
1856 displaymsg
->DisplayConstantStringName(STR_DOOR_NOPICK
, 0xbcefbc, actor
);
1860 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
1861 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
1862 LastPickLockFailed
= actor
->GetID();
1865 SetDoorLocked( false, true);
1866 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
1867 LastUnlocked
= actor
->GetID();
1869 int xp
= actor
->CalculateExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
1870 Game
*game
= core
->GetGame();
1871 game
->ShareXP(xp
, SX_DIVIDE
);
1874 void Door::TryBashLock(Actor
*actor
)
1876 //Get the strength bonus agains lock difficulty
1877 int str
= actor
->GetStat(IE_STR
);
1878 int strEx
= actor
->GetStat(IE_STREXTRA
);
1879 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
1880 unsigned int roll
= actor
->LuckyRoll(1, 10, bonus
, 0);
1882 if(roll
< LockDifficulty
|| LockDifficulty
== 100) {
1883 displaymsg
->DisplayConstantStringName(STR_DOORBASH_FAIL
, 0xbcefbc, actor
);
1887 displaymsg
->DisplayConstantStringName(STR_DOORBASH_DONE
, 0xd7d7be, actor
);
1888 SetDoorLocked(false, true);
1889 //Is this really useful ?
1890 LastUnlocked
= actor
->GetID();
1894 void Door::DebugDump() const
1896 printf( "Debugdump of Door %s:\n", GetScriptName() );
1897 printf( "Door Open: %s\n", YESNO(IsOpen()));
1898 printf( "Door Locked: %s\n", YESNO(Flags
&DOOR_LOCKED
));
1899 printf( "Door Trapped: %s\n", YESNO(Trapped
));
1901 printf( "Trap Permanent: %s Detectable: %s\n", YESNO(Flags
&DOOR_RESET
), YESNO(Flags
&DOOR_DETECTABLE
) );
1903 printf( "Secret door: %s (Found: %s)\n", YESNO(Flags
&DOOR_SECRET
),YESNO(Flags
&DOOR_FOUND
));
1904 const char *Key
= GetKey();
1905 const char *name
= "NONE";
1907 name
= Scripts
[0]->GetName();
1909 printf( "Script: %s, Key (%s) removed: %s, Dialog: %s\n", name
, Key
?Key
:"NONE", YESNO(Flags
&DOOR_KEY
), Dialog
);
1912 /*******************
1914 *******************/
1916 InfoPoint::InfoPoint(void)
1917 : Highlightable( ST_TRIGGER
)
1920 EntranceName
[0] = 0;
1922 TrapDetectionDiff
= 0;
1923 TrapRemovalDiff
= 0;
1929 InfoPoint::~InfoPoint(void)
1933 //checks if the actor may use this travel trigger
1935 //bit 2 : whole team
1936 int InfoPoint::CheckTravel(Actor
*actor
)
1938 if (Flags
&TRAP_DEACTIVATED
) return CT_CANTMOVE
;
1939 if (!actor
->InParty
&& (Flags
&TRAVEL_NONPC
) ) return CT_CANTMOVE
;
1940 if (actor
->InParty
&& (Flags
&TRAVEL_PARTY
) ) {
1941 if (core
->HasFeature(GF_TEAM_MOVEMENT
) || core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
) ) {
1944 return CT_GO_CLOSER
;
1946 if(actor
->IsSelected() ) {
1947 if(core
->GetGame()->EveryoneNearPoint(actor
->GetCurrentArea(), actor
->Pos
, ENP_CANMOVE
|ENP_ONLYSELECT
) ) {
1948 return CT_MOVE_SELECTED
;
1955 //detect this trap, using a skill, skill could be set to 256 for 'sure'
1956 //skill is the all around modified trap detection skill
1957 //a trapdetectiondifficulty of 100 means impossible detection short of a spell
1958 void Highlightable::DetectTrap(int skill
)
1960 if (!CanDetectTrap()) return;
1961 if (!Scripts
[0]) return;
1962 if ((skill
>=100) && (skill
!=256) ) skill
= 100;
1963 if (skill
/2+core
->Roll(1,skill
/2,0)>TrapDetectionDiff
) {
1964 SetTrapDetected(1); //probably could be set to the player #?
1968 bool Highlightable::PossibleToSeeTrap() const
1970 return CanDetectTrap();
1973 bool InfoPoint::PossibleToSeeTrap() const
1975 // Only detectable trap-type infopoints.
1976 return (CanDetectTrap() && (Type
== ST_PROXIMITY
) );
1979 bool InfoPoint::CanDetectTrap() const
1981 // Traps can be detected on all types of infopoint, as long
1982 // as the trap is detectable and isn't deactivated.
1983 return ((Flags
&TRAP_DETECTABLE
) && !(Flags
&TRAP_DEACTIVATED
));
1986 // returns true if the infopoint is a PS:T portal
1987 // GF_REVERSE_DOOR is the closest game feature (exists only in PST, and about area objects)
1988 bool InfoPoint::IsPortal() const
1990 if (Type
!=ST_TRAVEL
) return false;
1991 if (Cursor
!= IE_CURSOR_PORTAL
) return false;
1992 return core
->HasFeature(GF_REVERSE_DOOR
);
1995 //trap that is visible on screen (marked by red)
1996 //if TrapDetected is a bitflag, we could show traps selectively for
1997 //players, really nice for multiplayer
1998 bool Highlightable::VisibleTrap(int see_all
) const
2000 if (!Trapped
) return false;
2001 if (!PossibleToSeeTrap()) return false;
2002 if (!Scripts
[0]) return false;
2003 if (see_all
) return true;
2004 if (TrapDetected
) return true;
2008 //trap that will fire now
2009 bool Highlightable::TriggerTrap(int /*skill*/, ieDword ID
)
2014 //actually this could be script name[0]
2018 LastTriggerObject
= LastTrigger
= LastEntered
= ID
;
2020 if (!TrapResets()) {
2026 //trap that will fire now
2027 bool InfoPoint::TriggerTrap(int skill
, ieDword ID
)
2029 if (Type
!=ST_PROXIMITY
) {
2032 if (Flags
&TRAP_DEACTIVATED
) {
2036 // we have to set Entered somewhere, here seems best..
2039 } else if (Highlightable::TriggerTrap(skill
, ID
)) {
2041 Flags
|=TRAP_DEACTIVATED
;
2043 // ok, so this is a pain. Entered() trigger checks Trapped,
2044 // so it needs to be kept set. how to do this right?
2051 bool InfoPoint::Entered(Actor
*actor
)
2053 if (outline
->PointIn( actor
->Pos
) ) {
2054 //don't trigger again for this actor
2055 if (!(actor
->GetInternalFlag()&IF_INTRAP
)) {
2059 // why is this here? actors which aren't *in* a trap get IF_INTRAP
2060 // repeatedly unset, so this triggers again and again and again.
2061 // i disabled it for ST_PROXIMITY for now..
2062 /*if (Type != ST_PROXIMITY && (PersonalDistance(Pos, actor)<MAX_OPERATING_DISTANCE) ) {
2065 // this method is better (fuzzie, 2009) and also works for the iwd ar6002 northeast exit
2066 if (Type
== ST_TRAVEL
&& PersonalDistance(TrapLaunch
, actor
)<MAX_OPERATING_DISTANCE
) {
2069 // fuzzie can't escape pst's ar1405 without this one, maybe we should really be checking
2070 // for distance from the outline for travel regions instead?
2071 if (Type
== ST_TRAVEL
&& PersonalDistance(TalkPos
, actor
)<MAX_OPERATING_DISTANCE
) {
2074 if (Flags
&TRAP_USEPOINT
) {
2075 if (PersonalDistance(UsePoint
, actor
)<MAX_OPERATING_DISTANCE
) {
2081 if (Type
==ST_TRAVEL
) {
2085 if (actor
->InParty
|| (Flags
&TRAP_NPC
) ) {
2086 //no need to avoid a travel trigger
2089 if (TriggerTrap(0, actor
->GetID()) ) {
2096 void InfoPoint::DebugDump() const
2100 printf( "Debugdump of InfoPoint Region %s:\n", GetScriptName() );
2103 printf( "Debugdump of Trap Region %s:\n", GetScriptName() );
2106 printf( "Debugdump of Travel Region %s:\n", GetScriptName() );
2109 printf( "Debugdump of Unsupported Region %s:\n", GetScriptName() );
2112 printf( "TrapDetected: %d, Trapped: %s\n", TrapDetected
, YESNO(Trapped
));
2113 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2115 const char *name
= "NONE";
2117 name
= Scripts
[0]->GetName();
2119 printf( "Script: %s, Key: %s, Dialog: %s\n", name
, KeyResRef
, Dialog
);
2120 printf( "Active: %s\n", YESNO(InternalFlags
&IF_ACTIVE
));
2123 /*******************
2125 *******************/
2127 Container::Container(void)
2128 : Highlightable( ST_CONTAINER
)
2133 TrapDetectionDiff
= 0;
2134 TrapRemovalDiff
= 0;
2137 inventory
.SetInventoryType(INVENTORY_HEAP
);
2138 // NULL should be 0 for this
2139 memset (groundicons
, 0, sizeof(groundicons
) );
2140 groundiconcover
= 0;
2143 void Container::FreeGroundIcons()
2145 Video
* video
= core
->GetVideoDriver();
2147 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2148 if (groundicons
[i
]) {
2149 video
->FreeSprite( groundicons
[i
] );
2150 groundicons
[i
]=NULL
;
2153 delete groundiconcover
;
2154 groundiconcover
= 0;
2157 Container::~Container()
2162 void Container::DrawPile(bool highlight
, Region screen
, Color tint
)
2164 Video
* video
= core
->GetVideoDriver();
2165 CreateGroundIconCover();
2166 for (int i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2167 if (groundicons
[i
]) {
2168 //draw it with highlight
2169 video
->BlitGameSprite(groundicons
[i
],
2170 screen
.x
+ Pos
.x
, screen
.y
+ Pos
.y
,
2171 BLIT_TINTED
| (highlight
? 0:BLIT_NOSHADOW
),
2172 tint
, groundiconcover
);
2177 // create the SpriteCover for the groundicons
2178 void Container::CreateGroundIconCover()
2186 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2187 if (groundicons
[i
]) {
2188 Sprite2D
& spr
= *groundicons
[i
];
2189 if (xpos
< spr
.XPos
) {
2190 width
+= spr
.XPos
- xpos
;
2193 if (ypos
< spr
.YPos
) {
2194 height
+= spr
.YPos
- ypos
;
2197 if (width
-xpos
< spr
.Width
-spr
.XPos
) {
2198 width
= spr
.Width
-spr
.XPos
+xpos
;
2200 if (height
-ypos
< spr
.Height
-spr
.YPos
) {
2201 height
= spr
.Height
-spr
.YPos
+ypos
;
2206 if (!groundiconcover
||
2207 !groundiconcover
->Covers(Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
))
2209 delete groundiconcover
;
2210 groundiconcover
= 0;
2211 if (width
*height
> 0) {
2212 groundiconcover
= GetCurrentArea()->BuildSpriteCover
2213 (Pos
.x
, Pos
.y
, xpos
, ypos
, width
, height
, WantDither());
2218 // TODO: remove this checking code eventually
2219 for (i
= 0;i
<MAX_GROUND_ICON_DRAWN
;i
++) {
2220 if (groundicons
[i
]) {
2221 Sprite2D
& spr
= *groundicons
[i
];
2222 assert(groundiconcover
->Covers(Pos
.x
, Pos
.y
, spr
.XPos
, spr
.YPos
, spr
.Width
, spr
.Height
));
2228 void Container::SetContainerLocked(bool lock
)
2233 Flags
&=~CONT_LOCKED
;
2237 //This function doesn't exist in the original IE, destroys a container
2238 //turning it to a ground pile
2239 void Container::DestroyContainer()
2241 //it is already a groundpile?
2242 if (Type
== IE_CONTAINER_PILE
)
2244 Type
= IE_CONTAINER_PILE
;
2245 RefreshGroundIcons();
2246 //probably we should stop the script or trigger it, whatever
2249 //Takes an item from the container's inventory and returns its pointer
2250 CREItem
*Container::RemoveItem(unsigned int idx
, unsigned int count
)
2252 CREItem
*ret
= inventory
.RemoveItem(idx
, count
);
2253 //we just took the 3. or less item, groundpile changed
2254 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<3)) {
2255 RefreshGroundIcons();
2260 //Adds an item to the container's inventory
2261 //containers always have enough capacity (so far), thus we always return 2
2262 int Container::AddItem(CREItem
*item
)
2264 inventory
.AddItem(item
);
2265 //we just added a 3. or less item, groundpile changed
2266 if ((Type
== IE_CONTAINER_PILE
) && (inventory
.GetSlotCount()<4)) {
2267 RefreshGroundIcons();
2272 void Container::RefreshGroundIcons()
2274 int i
= inventory
.GetSlotCount();
2275 if (i
>MAX_GROUND_ICON_DRAWN
)
2276 i
= MAX_GROUND_ICON_DRAWN
;
2279 CREItem
*slot
= inventory
.GetSlotItem(i
); //borrowed reference
2280 Item
*itm
= gamedata
->GetItem( slot
->ItemResRef
); //cached reference
2281 //well, this is required in PST, needs more work if some other
2282 //game is broken by not using -1,0
2283 groundicons
[i
] = gamedata
->GetBAMSprite( itm
->GroundIcon
, 0, 0 );
2284 gamedata
->FreeItem( itm
, slot
->ItemResRef
); //decref
2288 //used for ground piles
2289 int Container::WantDither()
2291 //if pile is highlighted, always dither it
2293 return 2; //dither me if you want
2295 //if pile isn't highlighted, dither it if the polygon wants
2299 int Container::IsOpen() const
2301 if (Flags
&CONT_LOCKED
) {
2307 void Container::TryPickLock(Actor
*actor
)
2309 if (LockDifficulty
== 100) {
2310 if (OpenFail
!= (ieDword
)-1) {
2311 displaymsg
->DisplayStringName(OpenFail
, 0xbcefbc, actor
, IE_STR_SOUND
|IE_STR_SPEECH
);
2313 displaymsg
->DisplayConstantStringName(STR_CONT_NOPICK
, 0xbcefbc, actor
);
2317 if (actor
->GetStat(IE_LOCKPICKING
)<LockDifficulty
) {
2318 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_FAILED
, 0xbcefbc, actor
);
2319 LastPickLockFailed
= actor
->GetID();
2322 SetContainerLocked(false);
2323 displaymsg
->DisplayConstantStringName(STR_LOCKPICK_DONE
, 0xd7d7be, actor
);
2324 LastUnlocked
= actor
->GetID();
2326 int xp
= actor
->CalculateExperience(XP_LOCKPICK
, actor
->GetXPLevel(1));
2327 Game
*game
= core
->GetGame();
2328 game
->ShareXP(xp
, SX_DIVIDE
);
2331 void Container::TryBashLock(Actor
*actor
)
2333 //Get the strength bonus agains lock difficulty
2334 int str
= actor
->GetStat(IE_STR
);
2335 int strEx
= actor
->GetStat(IE_STREXTRA
);
2336 unsigned int bonus
= core
->GetStrengthBonus(2, str
, strEx
); //BEND_BARS_LIFT_GATES
2337 unsigned int roll
= actor
->LuckyRoll(1, 10, bonus
, 0);
2339 if(roll
< LockDifficulty
|| LockDifficulty
== 100) {
2340 displaymsg
->DisplayConstantStringName(STR_CONTBASH_FAIL
, 0xbcefbc, actor
);
2344 displaymsg
->DisplayConstantStringName(STR_CONTBASH_DONE
, 0xd7d7be, actor
);
2345 SetContainerLocked(false);
2346 //Is this really useful ?
2347 LastUnlocked
= actor
->GetID();
2351 void Container::DebugDump() const
2353 printf( "Debugdump of Container %s\n", GetScriptName() );
2354 printf( "Type: %d, LockDifficulty: %d\n", Type
, LockDifficulty
);
2355 printf( "Flags: %d, Trapped: %s, Detected: %d\n", Flags
, YESNO(Trapped
), TrapDetected
);
2356 printf( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff
,
2358 const char *name
= "NONE";
2360 name
= Scripts
[0]->GetName();
2362 printf( "Script: %s, Key: %s\n", name
, KeyResRef
);
2363 // FIXME: const_cast
2364 const_cast<Inventory
&>(inventory
).dump();
2367 bool Container::TryUnlock(Actor
*actor
) {
2368 if (!(Flags
&CONT_LOCKED
)) return true;
2370 return Highlightable::TryUnlock(actor
, false);