1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2007 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.
21 // This class handles the special spawn structures of planescape torment
22 // (stored in .ini format)
30 #include "Interface.h"
32 #include "GameScript/GSUtils.h"
33 #include "GameScript/Matching.h"
34 #include "Scriptable/Actor.h"
36 static const int StatValues
[9]={
37 IE_EA
, IE_FACTION
, IE_TEAM
, IE_GENERAL
, IE_RACE
, IE_CLASS
, IE_SPECIFIC
,
38 IE_SEX
, IE_ALIGNMENT
};
40 IniSpawn::IniSpawn(Map
*owner
)
43 NamelessSpawnArea
[0] = 0;
61 Holder
<DataFileMgr
> GetIniFile(const ieResRef DefaultArea
)
63 //the lack of spawn ini files is not a serious problem, happens all the time
64 if (!gamedata
->Exists( DefaultArea
, IE_INI_CLASS_ID
)) {
68 DataStream
* inifile
= gamedata
->GetResource( DefaultArea
, IE_INI_CLASS_ID
);
72 if (!core
->IsAvailable( IE_INI_CLASS_ID
)) {
73 printStatus( "ERROR", LIGHT_RED
);
74 printMessage( "IniSpawn","No INI Importer Available.\n",LIGHT_RED
);
78 PluginHolder
<DataFileMgr
> ini(IE_INI_CLASS_ID
);
79 ini
->Open(inifile
, true ); //autofree
83 /*** initializations ***/
85 inline int CountElements(const char *s
, char separator
)
89 if (*s
==separator
) ret
++;
95 inline void GetElements(const char *s
, ieResRef
*storage
, int count
)
98 ieResRef
*field
= storage
+count
;
99 strnuprcpy(*field
, s
, sizeof(ieResRef
)-1);
100 for(size_t i
=0;i
<sizeof(ieResRef
) && (*field
)[i
];i
++) {
101 if ((*field
)[i
]==',') {
107 while(*s
&& *s
!=',') s
++;
109 if (*s
==' ') s
++; //this is because there is one single screwed up entry in ar1100.ini
113 inline void GetElements(const char *s
, ieVariable
*storage
, int count
)
116 ieVariable
*field
= storage
+count
;
117 strnuprcpy(*field
, s
, sizeof(ieVariable
)-1);
118 for(size_t i
=0;i
<sizeof(ieVariable
) && (*field
)[i
];i
++) {
119 if ((*field
)[i
]==',') {
124 while(*s
&& *s
!=',') s
++;
129 // possible values implemented in DiffMode, but not needed here
130 // BINARY_LESS_OR_EQUALS 6 //(left has only bits in right)
131 // BINARY_MORE_OR_EQUALS 7 //(left has equal or more bits than right)
132 // BINARY_INTERSECT 8 //(left and right has at least one common bit)
133 // BINARY_NOT_INTERSECT 9 //(no common bits)
134 // BINARY_MORE 10 //left has more bits than right
135 // BINARY_LESS 11 //left has less bits than right
137 int IniSpawn::GetDiffMode(const char *keyword
)
139 if (!keyword
) return NO_OPERATION
; //-1
140 if (keyword
[0]==0) return NO_OPERATION
; //-1
141 if (!stricmp(keyword
,"less_or_equal_to") ) return LESS_OR_EQUALS
; //0 (gemrb ext)
142 if (!stricmp(keyword
,"equal_to") ) return EQUALS
; // 1
143 if (!stricmp(keyword
,"less_than") ) return LESS_THAN
; // 2
144 if (!stricmp(keyword
,"greater_than") ) return GREATER_THAN
; //3
145 if (!stricmp(keyword
,"greater_or_equal_to") ) return GREATER_THAN
; //4 (gemrb ext)
146 if (!stricmp(keyword
,"not_equal_to") ) return NOT_EQUALS
; //5
150 //unimplemented tags:
152 // good_mod, law_mod, lady_mod, murder_mod
157 // check_by_view_port
160 // hold_selected_point_key
161 // inc_spawn_point_index
168 void IniSpawn::ReadCreature(DataFileMgr
*inifile
, const char *crittername
, CritterEntry
&critter
)
173 memset(&critter
,0,sizeof(critter
));
175 //all specvars are using global, but sometimes it is explicitly given
176 s
= inifile
->GetKeyAsString(crittername
,"spec_var",NULL
);
178 if ((strlen(s
)>9) && s
[6]==':' && s
[7]==':') {
179 strnuprcpy(critter
.SpecContext
, s
, 6);
180 strnlwrcpy(critter
.SpecVar
, s
+8, 32);
182 strnuprcpy(critter
.SpecContext
, "GLOBAL", 6);
183 strnlwrcpy(critter
.SpecVar
, s
, 32);
187 //add this to specvar at each spawn
188 ps
= inifile
->GetKeyAsInt(crittername
,"spec_var_inc", 0);
189 critter
.SpecVarInc
=ps
;
191 //use this value with spec_var_operation to determine spawn
192 ps
= inifile
->GetKeyAsInt(crittername
,"spec_var_value",0);
193 critter
.SpecVarValue
=ps
;
194 //this operation uses DiffCore
195 s
= inifile
->GetKeyAsString(crittername
,"spec_var_operation","");
196 critter
.SpecVarOperator
=GetDiffMode(s
);
197 //the amount of critters to spawn
198 critter
.TotalQuantity
= inifile
->GetKeyAsInt(crittername
,"spec_qty",1);
199 critter
.SpawnCount
= inifile
->GetKeyAsInt(crittername
,"create_qty",critter
.TotalQuantity
);
201 //the creature resource(s)
202 s
= inifile
->GetKeyAsString(crittername
,"cre_file",NULL
);
204 critter
.creaturecount
= CountElements(s
,',');
205 critter
.CreFile
=new ieResRef
[critter
.creaturecount
];
206 GetElements(s
, critter
.CreFile
, critter
.creaturecount
);
208 printMessage( "IniSpawn"," ", LIGHT_RED
);
209 printf("Invalid spawn entry: %s\n", crittername
);
212 s
= inifile
->GetKeyAsString(crittername
,"point_select",NULL
);
220 s
= inifile
->GetKeyAsString(crittername
,"spawn_point",NULL
);
222 //expect more than one spawnpoint
224 //select one of the spawnpoints randomly
225 int count
= core
->Roll(1,CountElements(s
,']'),-1);
226 //go to the selected spawnpoint
231 //parse the selected spawnpoint
233 if (sscanf(s
,"[%d.%d:%d]", &x
, &y
, &o
)==3) {
234 critter
.SpawnPoint
.x
=(short) x
;
235 critter
.SpawnPoint
.y
=(short) y
;
236 critter
.Orientation
=o
;
238 if (sscanf(s
,"[%d.%d]", &x
, &y
)==2) {
239 critter
.SpawnPoint
.x
=(short) x
;
240 critter
.SpawnPoint
.y
=(short) y
;
241 critter
.Orientation
=core
->Roll(1,16,-1);
246 //store or retrieve spawn point
247 s
= inifile
->GetKeyAsString(crittername
,"spawn_point_global", NULL
);
251 critter
.SpawnPoint
.fromDword(CheckVariable(map
, s
+8,s
));
254 //see save_selected_point
255 //SetVariable(map, s+8, s, critter.SpawnPoint.asDword());
260 //take facing from variable
261 s
= inifile
->GetKeyAsString(crittername
,"spawn_facing_global", NULL
);
265 critter
.Orientation
=(int) CheckVariable(map
, s
+8,s
);
268 //see save_selected_point
269 //SetVariable(map, s+8, s, (ieDword) critter.Orientation);
274 s
= inifile
->GetKeyAsString(crittername
,"save_selected_point",NULL
);
276 if ((strlen(s
)>9) && s
[6]==':' && s
[7]==':') {
277 SetVariable(map
, s
+8, s
, critter
.SpawnPoint
.asDword());
279 SetVariable(map
, s
, "GLOBAL", critter
.SpawnPoint
.asDword());
282 s
= inifile
->GetKeyAsString(crittername
,"save_selected_facing",NULL
);
284 if ((strlen(s
)>9) && s
[6]==':' && s
[7]==':') {
285 SetVariable(map
, s
+8, s
, (ieDword
) critter
.Orientation
);
287 SetVariable(map
, s
, "GLOBAL", (ieDword
) critter
.Orientation
);
291 //sometimes only the orientation is given, the point is stored in a variable
292 ps
= inifile
->GetKeyAsInt(crittername
,"facing",-1);
293 if (ps
!=-1) critter
.Orientation
= ps
;
294 ps
= inifile
->GetKeyAsInt(crittername
, "ai_ea",-1);
295 if (ps
!=-1) critter
.SetSpec
[AI_EA
] = (ieByte
) ps
;
296 ps
= inifile
->GetKeyAsInt(crittername
, "ai_team",-1);
297 if (ps
!=-1) critter
.SetSpec
[AI_TEAM
] = (ieByte
) ps
;
298 ps
= inifile
->GetKeyAsInt(crittername
, "ai_general",-1);
299 if (ps
!=-1) critter
.SetSpec
[AI_GENERAL
] = (ieByte
) ps
;
300 ps
= inifile
->GetKeyAsInt(crittername
, "ai_race",-1);
301 if (ps
!=-1) critter
.SetSpec
[AI_RACE
] = (ieByte
) ps
;
302 ps
= inifile
->GetKeyAsInt(crittername
, "ai_class",-1);
303 if (ps
!=-1) critter
.SetSpec
[AI_CLASS
] = (ieByte
) ps
;
304 ps
= inifile
->GetKeyAsInt(crittername
, "ai_specifics",-1);
305 if (ps
!=-1) critter
.SetSpec
[AI_SPECIFICS
] = (ieByte
) ps
;
306 ps
= inifile
->GetKeyAsInt(crittername
, "ai_gender",-1);
307 if (ps
!=-1) critter
.SetSpec
[AI_GENDER
] = (ieByte
) ps
;
308 ps
= inifile
->GetKeyAsInt(crittername
, "ai_alignment",-1);
309 if (ps
!=-1) critter
.SetSpec
[AI_ALIGNMENT
] = (ieByte
) ps
;
311 s
= inifile
->GetKeyAsString(crittername
,"spec",NULL
);
315 ps
= sscanf(s
,"[%d.%d.%d.%d.%d.%d.%d.%d.%d]", x
, x
+1, x
+2, x
+3, x
+4, x
+5,
318 strnuprcpy(critter
.ScriptName
, s
, 32);
319 critter
.Flags
|=CF_CHECK_NAME
;
320 memset(critter
.Spec
,-1,sizeof(critter
.Spec
));
323 critter
.Spec
[ps
]=(ieByte
) x
[ps
];
328 s
= inifile
->GetKeyAsString(crittername
,"script_name",NULL
);
330 strnuprcpy(critter
.ScriptName
, s
, 32);
333 //iwd2 script names (override remains the same)
335 s
= inifile
->GetKeyAsString(crittername
,"script_special_1",NULL
);
337 strnuprcpy(critter
.AreaScript
,s
, 8);
340 s
= inifile
->GetKeyAsString(crittername
,"script_special_2",NULL
);
342 strnuprcpy(critter
.ClassScript
,s
, 8);
344 //special 3 == general
345 s
= inifile
->GetKeyAsString(crittername
,"script_special_3",NULL
);
347 strnuprcpy(critter
.GeneralScript
,s
, 8);
350 s
= inifile
->GetKeyAsString(crittername
,"script_team",NULL
);
352 strnuprcpy(critter
.SpecificScript
,s
, 8);
356 s
= inifile
->GetKeyAsString(crittername
,"script_combat",NULL
);
358 strnuprcpy(critter
.RaceScript
,s
, 8);
360 //movement == default
361 s
= inifile
->GetKeyAsString(crittername
,"script_movement",NULL
);
363 strnuprcpy(critter
.DefaultScript
,s
, 8);
367 s
= inifile
->GetKeyAsString(crittername
,"script_override",NULL
);
369 strnuprcpy(critter
.OverrideScript
,s
, 8);
371 s
= inifile
->GetKeyAsString(crittername
,"script_class",NULL
);
373 strnuprcpy(critter
.ClassScript
,s
, 8);
375 s
= inifile
->GetKeyAsString(crittername
,"script_race",NULL
);
377 strnuprcpy(critter
.RaceScript
,s
, 8);
379 s
= inifile
->GetKeyAsString(crittername
,"script_general",NULL
);
381 strnuprcpy(critter
.GeneralScript
,s
, 8);
383 s
= inifile
->GetKeyAsString(crittername
,"script_default",NULL
);
385 strnuprcpy(critter
.DefaultScript
,s
, 8);
387 s
= inifile
->GetKeyAsString(crittername
,"script_area",NULL
);
389 strnuprcpy(critter
.AreaScript
,s
, 8);
391 s
= inifile
->GetKeyAsString(crittername
,"script_specifics",NULL
);
393 strnuprcpy(critter
.SpecificScript
,s
, 8);
395 s
= inifile
->GetKeyAsString(crittername
,"dialog",NULL
);
397 strnuprcpy(critter
.Dialog
,s
, 8);
401 if (inifile
->GetKeyAsBool(crittername
,"death_scriptname",false)) {
402 critter
.Flags
|=CF_DEATHVAR
;
404 //don't spawn when spawnpoint is visible
405 if (inifile
->GetKeyAsBool(crittername
,"ignore_can_see",false)) {
406 critter
.Flags
|=CF_IGNORECANSEE
;
408 //unsure, but could be similar to previous
409 if (inifile
->GetKeyAsBool(crittername
,"check_view_port", false)) {
410 critter
.Flags
|=CF_CHECKVIEWPORT
;
412 //unknown, this is used only in pst
413 if (inifile
->GetKeyAsBool(crittername
,"check_crowd", false)) {
414 critter
.Flags
|=CF_CHECKCROWD
;
416 //unknown, this is used only in pst
417 if (inifile
->GetKeyAsBool(crittername
,"find_safest_point", false)) {
418 critter
.Flags
|=CF_SAFESTPOINT
;
420 //disable spawn based on game difficulty
421 if (inifile
->GetKeyAsBool(crittername
,"area_diff_1", false)) {
422 critter
.Flags
|=CF_NO_DIFF_1
;
424 if (inifile
->GetKeyAsBool(crittername
,"area_diff_2", false)) {
425 critter
.Flags
|=CF_NO_DIFF_2
;
427 if (inifile
->GetKeyAsBool(crittername
,"area_diff_3", false)) {
428 critter
.Flags
|=CF_NO_DIFF_3
;
432 void IniSpawn::ReadSpawnEntry(DataFileMgr
*inifile
, const char *entryname
, SpawnEntry
&entry
)
436 entry
.interval
= (unsigned int) inifile
->GetKeyAsInt(entryname
,"interval",0);
437 //don't default to NULL here, some entries may be missing in original game
438 //an empty default string here will create an empty but consistent entry
439 s
= inifile
->GetKeyAsString(entryname
,"critters","");
440 int crittercount
= CountElements(s
,',');
441 entry
.crittercount
=crittercount
;
442 entry
.critters
=new CritterEntry
[crittercount
];
443 ieVariable
*critters
= new ieVariable
[crittercount
];
444 GetElements(s
, critters
, crittercount
);
445 while(crittercount
--) {
446 ReadCreature(inifile
, critters
[crittercount
], entry
.critters
[crittercount
]);
451 void IniSpawn::InitSpawn(const ieResRef DefaultArea
)
455 Holder
<DataFileMgr
> inifile
= GetIniFile(DefaultArea
);
457 strnuprcpy(NamelessSpawnArea
, DefaultArea
, 8);
461 s
= inifile
->GetKeyAsString("nameless","destare",DefaultArea
);
462 strnuprcpy(NamelessSpawnArea
, s
, 8);
463 s
= inifile
->GetKeyAsString("nameless","point","[0.0]");
465 if (sscanf(s
,"[%d.%d]", &x
, &y
)!=2) {
469 NamelessSpawnPoint
.x
=x
;
470 NamelessSpawnPoint
.y
=y
;
471 //35 - already standing
473 NamelessState
= inifile
->GetKeyAsInt("nameless","state",36);
475 namelessvarcount
= inifile
->GetKeysCount("namelessvar");
476 if (namelessvarcount
) {
477 NamelessVar
= new VariableSpec
[namelessvarcount
];
478 for (y
=0;y
<namelessvarcount
;y
++) {
479 const char* Key
= inifile
->GetKeyNameByIndex("namelessvar",y
);
480 strnlwrcpy(NamelessVar
[y
].Name
, Key
, 32);
481 NamelessVar
[y
].Value
= inifile
->GetKeyAsInt("namelessvar",Key
,0);
485 localscount
= inifile
->GetKeysCount("locals");
487 Locals
= new VariableSpec
[localscount
];
488 for (y
=0;y
<localscount
;y
++) {
489 const char* Key
= inifile
->GetKeyNameByIndex("locals",y
);
490 strnlwrcpy(Locals
[y
].Name
, Key
, 32);
491 Locals
[y
].Value
= inifile
->GetKeyAsInt("locals",Key
,0);
495 s
= inifile
->GetKeyAsString("spawn_main","enter",NULL
);
497 ReadSpawnEntry(inifile
.get(), s
, enterspawn
);
499 s
= inifile
->GetKeyAsString("spawn_main","events",NULL
);
501 eventcount
= CountElements(s
,',');
502 eventspawns
= new SpawnEntry
[eventcount
];
503 ieVariable
*events
= new ieVariable
[eventcount
];
504 GetElements(s
, events
, eventcount
);
507 ReadSpawnEntry(inifile
.get(), events
[ec
], eventspawns
[ec
]);
518 //respawn nameless after he bit the dust
519 void IniSpawn::RespawnNameless()
521 Game
*game
= core
->GetGame();
522 Actor
*nameless
= game
->GetPC(0, false);
524 if (NamelessSpawnPoint
.isnull()) {
525 core
->GetGame()->JoinParty(nameless
,JP_INITPOS
);
526 NamelessSpawnPoint
=nameless
->Pos
;
527 strnuprcpy(NamelessSpawnArea
, nameless
->Area
, 8);
530 nameless
->Resurrect();
532 if (NamelessState
==36) {
533 nameless
->SetStance(IE_ANI_PST_START
);
537 for (i
=0;i
<game
->GetPartySize(false);i
++) {
538 MoveBetweenAreasCore(game
->GetPC(i
, false),NamelessSpawnArea
,NamelessSpawnPoint
,-1, true);
541 //certain variables are set when nameless dies
542 for (i
=0;i
<namelessvarcount
;i
++) {
543 SetVariable(game
, NamelessVar
[i
].Name
,"GLOBAL", NamelessVar
[i
].Value
);
547 void IniSpawn::SpawnCreature(CritterEntry
&critter
)
549 if (!critter
.creaturecount
) {
553 ieDword specvar
= CheckVariable(map
, critter
.SpecVar
, critter
.SpecContext
);
555 if (critter
.SpecVar
[0]) {
556 if (critter
.SpecVarOperator
>=0) {
557 // dunno if this should be negated
558 if (!DiffCore(specvar
, critter
.SpecVarValue
, critter
.SpecVarOperator
) ) {
562 //ar0203 in PST seems to want the check this way.
563 //if other areas conflict and you want to use (!specvar),
564 //please research further
565 //researched further - ar0203 respawns only if specvar is 1
572 if (!(critter
.Flags
&CF_IGNORECANSEE
)) {
573 if (map
->IsVisible(critter
.SpawnPoint
, false) ) {
578 if (critter
.Flags
&CF_NO_DIFF_MASK
) {
582 core
->GetDictionary()->Lookup("Difficulty Level", difficulty
);
586 diff_bit
= CF_NO_DIFF_1
;
589 diff_bit
= CF_NO_DIFF_2
;
592 diff_bit
= CF_NO_DIFF_3
;
597 if (critter
.Flags
&diff_bit
) {
602 if (critter
.ScriptName
[0] && (critter
.Flags
&CF_CHECK_NAME
) ) {
603 //maybe this one needs to be using getobjectcount as well
604 //currently we cannot count objects with scriptname???
605 if (map
->GetActor( critter
.ScriptName
, 0 )) {
609 //Object *object = new Object();
611 //objectfields based on spec
612 object
.objectFields
[0]=critter
.Spec
[0];
613 object
.objectFields
[1]=critter
.Spec
[1];
614 object
.objectFields
[2]=critter
.Spec
[2];
615 object
.objectFields
[3]=critter
.Spec
[3];
616 object
.objectFields
[4]=critter
.Spec
[4];
617 object
.objectFields
[5]=critter
.Spec
[5];
618 object
.objectFields
[6]=critter
.Spec
[6];
619 object
.objectFields
[7]=critter
.Spec
[7];
620 object
.objectFields
[8]=critter
.Spec
[8];
621 int cnt
= GetObjectCount(map
, &object
);
622 if (cnt
>=critter
.TotalQuantity
) {
627 int x
= core
->Roll(1,critter
.creaturecount
,-1);
628 Actor
* cre
= gamedata
->GetCreature(critter
.CreFile
[x
]);
633 SetVariable(map
, critter
.SpecVar
, critter
.SpecContext
, specvar
+(ieDword
) critter
.SpecVarInc
);
636 if (critter
.SetSpec
[x
]) {
637 cre
->SetBase(StatValues
[x
], critter
.SetSpec
[x
]);
640 cre
->SetPosition( critter
.SpawnPoint
, 0, 0);//maybe critters could be repositioned
641 cre
->SetOrientation(critter
.Orientation
,false);
642 if (critter
.ScriptName
[0]) {
643 cre
->SetScriptName(critter
.ScriptName
);
645 if (critter
.OverrideScript
[0]) {
646 cre
->SetScript(critter
.OverrideScript
, SCR_OVERRIDE
);
648 if (critter
.ClassScript
[0]) {
649 cre
->SetScript(critter
.ClassScript
, SCR_CLASS
);
651 if (critter
.RaceScript
[0]) {
652 cre
->SetScript(critter
.RaceScript
, SCR_RACE
);
654 if (critter
.GeneralScript
[0]) {
655 cre
->SetScript(critter
.GeneralScript
, SCR_GENERAL
);
657 if (critter
.DefaultScript
[0]) {
658 cre
->SetScript(critter
.DefaultScript
, SCR_DEFAULT
);
660 if (critter
.AreaScript
[0]) {
661 cre
->SetScript(critter
.AreaScript
, SCR_AREA
);
663 if (critter
.SpecificScript
[0]) {
664 cre
->SetScript(critter
.SpecificScript
, SCR_SPECIFICS
);
666 if (critter
.Dialog
[0]) {
667 cre
->SetDialog(critter
.Dialog
);
671 void IniSpawn::SpawnGroup(SpawnEntry
&event
)
673 if (!event
.critters
) {
676 unsigned int interval
= event
.interval
;
678 if(core
->GetGame()->GameTime
/interval
<=last_spawndate
/interval
) {
682 last_spawndate
=core
->GetGame()->GameTime
;
684 for(int i
=0;i
<event
.crittercount
;i
++) {
685 CritterEntry
* critter
= event
.critters
+i
;
686 for(int j
=0;j
<critter
->SpawnCount
;j
++) {
687 SpawnCreature(*critter
);
692 //execute the initial spawn
693 void IniSpawn::InitialSpawn()
695 SpawnGroup(enterspawn
);
696 //these variables are set when entering first
697 for (int i
=0;i
<localscount
;i
++) {
698 SetVariable(map
, Locals
[i
].Name
,"LOCALS", Locals
[i
].Value
);
702 //checks if a respawn event occurred
703 void IniSpawn::CheckSpawn()
705 for(int i
=0;i
<eventcount
;i
++) {
706 SpawnGroup(eventspawns
[i
]);