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)
32 #include "Interface.h"
35 static const int StatValues
[9]={
36 IE_EA
, IE_FACTION
, IE_TEAM
, IE_GENERAL
, IE_RACE
, IE_CLASS
, IE_SPECIFIC
,
37 IE_SEX
, IE_ALIGNMENT
};
39 IniSpawn::IniSpawn(Map
*owner
)
42 NamelessSpawnArea
[0] = 0;
60 Holder
<DataFileMgr
> GetIniFile(const ieResRef DefaultArea
)
62 //the lack of spawn ini files is not a serious problem, happens all the time
63 if (!gamedata
->Exists( DefaultArea
, IE_INI_CLASS_ID
)) {
67 DataStream
* inifile
= gamedata
->GetResource( DefaultArea
, IE_INI_CLASS_ID
);
71 if (!core
->IsAvailable( IE_INI_CLASS_ID
)) {
72 printStatus( "ERROR", LIGHT_RED
);
73 printMessage( "IniSpawn","No INI Importer Available.\n",LIGHT_RED
);
77 PluginHolder
<DataFileMgr
> ini(IE_INI_CLASS_ID
);
78 ini
->Open(inifile
, true ); //autofree
82 /*** initializations ***/
84 inline int CountElements(const char *s
, char separator
)
88 if (*s
==separator
) ret
++;
94 inline void GetElements(const char *s
, ieResRef
*storage
, int count
)
97 ieResRef
*field
= storage
+count
;
98 strnuprcpy(*field
, s
, sizeof(ieResRef
)-1);
99 for(size_t i
=0;i
<sizeof(ieResRef
) && (*field
)[i
];i
++) {
100 if ((*field
)[i
]==',') {
106 while(*s
&& *s
!=',') s
++;
108 if (*s
==' ') s
++; //this is because there is one single screwed up entry in ar1100.ini
112 inline void GetElements(const char *s
, ieVariable
*storage
, int count
)
115 ieVariable
*field
= storage
+count
;
116 strnuprcpy(*field
, s
, sizeof(ieVariable
)-1);
117 for(size_t i
=0;i
<sizeof(ieVariable
) && (*field
)[i
];i
++) {
118 if ((*field
)[i
]==',') {
123 while(*s
&& *s
!=',') s
++;
128 // possible values implemented in DiffMode, but not needed here
129 // BINARY_LESS_OR_EQUALS 6 //(left has only bits in right)
130 // BINARY_MORE_OR_EQUALS 7 //(left has equal or more bits than right)
131 // BINARY_INTERSECT 8 //(left and right has at least one common bit)
132 // BINARY_NOT_INTERSECT 9 //(no common bits)
133 // BINARY_MORE 10 //left has more bits than right
134 // BINARY_LESS 11 //left has less bits than right
136 int IniSpawn::GetDiffMode(const char *keyword
)
138 if (!keyword
) return NO_OPERATION
; //-1
139 if (keyword
[0]==0) return NO_OPERATION
; //-1
140 if (!stricmp(keyword
,"less_or_equal_to") ) return LESS_OR_EQUALS
; //0 (gemrb ext)
141 if (!stricmp(keyword
,"equal_to") ) return EQUALS
; // 1
142 if (!stricmp(keyword
,"less_than") ) return LESS_THAN
; // 2
143 if (!stricmp(keyword
,"greater_than") ) return GREATER_THAN
; //3
144 if (!stricmp(keyword
,"greater_or_equal_to") ) return GREATER_THAN
; //4 (gemrb ext)
145 if (!stricmp(keyword
,"not_equal_to") ) return NOT_EQUALS
; //5
149 //unimplemented tags:
151 // good_mod, law_mod, lady_mod, murder_mod
156 // check_by_view_port
159 // hold_selected_point_key
160 // inc_spawn_point_index
167 void IniSpawn::ReadCreature(DataFileMgr
*inifile
, const char *crittername
, CritterEntry
&critter
)
172 memset(&critter
,0,sizeof(critter
));
174 //all specvars are using global, but sometimes it is explicitly given
175 s
= inifile
->GetKeyAsString(crittername
,"spec_var",NULL
);
177 if ((strlen(s
)>9) && s
[6]==':' && s
[7]==':') {
178 strnuprcpy(critter
.SpecContext
, s
, 6);
179 strnlwrcpy(critter
.SpecVar
, s
+8, 32);
181 strnuprcpy(critter
.SpecContext
, "GLOBAL", 6);
182 strnlwrcpy(critter
.SpecVar
, s
, 32);
186 //add this to specvar at each spawn
187 ps
= inifile
->GetKeyAsInt(crittername
,"spec_var_inc", 0);
188 critter
.SpecVarInc
=ps
;
190 //use this value with spec_var_operation to determine spawn
191 ps
= inifile
->GetKeyAsInt(crittername
,"spec_var_value",0);
192 critter
.SpecVarValue
=ps
;
193 //this operation uses DiffCore
194 s
= inifile
->GetKeyAsString(crittername
,"spec_var_operation","");
195 critter
.SpecVarOperator
=GetDiffMode(s
);
196 //the amount of critters to spawn
197 critter
.TotalQuantity
= inifile
->GetKeyAsInt(crittername
,"spec_qty",1);
198 critter
.SpawnCount
= inifile
->GetKeyAsInt(crittername
,"create_qty",critter
.TotalQuantity
);
200 //the creature resource(s)
201 s
= inifile
->GetKeyAsString(crittername
,"cre_file",NULL
);
203 critter
.creaturecount
= CountElements(s
,',');
204 critter
.CreFile
=new ieResRef
[critter
.creaturecount
];
205 GetElements(s
, critter
.CreFile
, critter
.creaturecount
);
207 printMessage( "IniSpawn"," ", LIGHT_RED
);
208 printf("Invalid spawn entry: %s\n", crittername
);
211 s
= inifile
->GetKeyAsString(crittername
,"point_select",NULL
);
219 s
= inifile
->GetKeyAsString(crittername
,"spawn_point",NULL
);
221 //expect more than one spawnpoint
223 //select one of the spawnpoints randomly
224 int count
= core
->Roll(1,CountElements(s
,']'),-1);
225 //go to the selected spawnpoint
230 //parse the selected spawnpoint
232 if (sscanf(s
,"[%d.%d:%d]", &x
, &y
, &o
)==3) {
233 critter
.SpawnPoint
.x
=(short) x
;
234 critter
.SpawnPoint
.y
=(short) y
;
235 critter
.Orientation
=o
;
237 if (sscanf(s
,"[%d.%d]", &x
, &y
)==2) {
238 critter
.SpawnPoint
.x
=(short) x
;
239 critter
.SpawnPoint
.y
=(short) y
;
240 critter
.Orientation
=core
->Roll(1,16,-1);
245 //store or retrieve spawn point
246 s
= inifile
->GetKeyAsString(crittername
,"spawn_point_global", NULL
);
250 critter
.SpawnPoint
.fromDword(CheckVariable(map
, s
+8,s
));
253 //see save_selected_point
254 //SetVariable(map, s+8, s, critter.SpawnPoint.asDword());
259 //take facing from variable
260 s
= inifile
->GetKeyAsString(crittername
,"spawn_facing_global", NULL
);
264 critter
.Orientation
=(int) CheckVariable(map
, s
+8,s
);
267 //see save_selected_point
268 //SetVariable(map, s+8, s, (ieDword) critter.Orientation);
273 s
= inifile
->GetKeyAsString(crittername
,"save_selected_point",NULL
);
275 if ((strlen(s
)>9) && s
[6]==':' && s
[7]==':') {
276 SetVariable(map
, s
+8, s
, critter
.SpawnPoint
.asDword());
278 SetVariable(map
, s
, "GLOBAL", critter
.SpawnPoint
.asDword());
281 s
= inifile
->GetKeyAsString(crittername
,"save_selected_facing",NULL
);
283 if ((strlen(s
)>9) && s
[6]==':' && s
[7]==':') {
284 SetVariable(map
, s
+8, s
, (ieDword
) critter
.Orientation
);
286 SetVariable(map
, s
, "GLOBAL", (ieDword
) critter
.Orientation
);
290 //sometimes only the orientation is given, the point is stored in a variable
291 ps
= inifile
->GetKeyAsInt(crittername
,"facing",-1);
292 if (ps
!=-1) critter
.Orientation
= ps
;
293 ps
= inifile
->GetKeyAsInt(crittername
, "ai_ea",-1);
294 if (ps
!=-1) critter
.SetSpec
[AI_EA
] = (ieByte
) ps
;
295 ps
= inifile
->GetKeyAsInt(crittername
, "ai_team",-1);
296 if (ps
!=-1) critter
.SetSpec
[AI_TEAM
] = (ieByte
) ps
;
297 ps
= inifile
->GetKeyAsInt(crittername
, "ai_general",-1);
298 if (ps
!=-1) critter
.SetSpec
[AI_GENERAL
] = (ieByte
) ps
;
299 ps
= inifile
->GetKeyAsInt(crittername
, "ai_race",-1);
300 if (ps
!=-1) critter
.SetSpec
[AI_RACE
] = (ieByte
) ps
;
301 ps
= inifile
->GetKeyAsInt(crittername
, "ai_class",-1);
302 if (ps
!=-1) critter
.SetSpec
[AI_CLASS
] = (ieByte
) ps
;
303 ps
= inifile
->GetKeyAsInt(crittername
, "ai_specifics",-1);
304 if (ps
!=-1) critter
.SetSpec
[AI_SPECIFICS
] = (ieByte
) ps
;
305 ps
= inifile
->GetKeyAsInt(crittername
, "ai_gender",-1);
306 if (ps
!=-1) critter
.SetSpec
[AI_GENDER
] = (ieByte
) ps
;
307 ps
= inifile
->GetKeyAsInt(crittername
, "ai_alignment",-1);
308 if (ps
!=-1) critter
.SetSpec
[AI_ALIGNMENT
] = (ieByte
) ps
;
310 s
= inifile
->GetKeyAsString(crittername
,"spec",NULL
);
314 ps
= sscanf(s
,"[%d.%d.%d.%d.%d.%d.%d.%d.%d]", x
, x
+1, x
+2, x
+3, x
+4, x
+5,
317 strnuprcpy(critter
.ScriptName
, s
, 32);
318 critter
.Flags
|=CF_CHECK_NAME
;
319 memset(critter
.Spec
,-1,sizeof(critter
.Spec
));
322 critter
.Spec
[ps
]=(ieByte
) x
[ps
];
327 s
= inifile
->GetKeyAsString(crittername
,"script_name",NULL
);
329 strnuprcpy(critter
.ScriptName
, s
, 32);
332 //iwd2 script names (override remains the same)
334 s
= inifile
->GetKeyAsString(crittername
,"script_special_1",NULL
);
336 strnuprcpy(critter
.AreaScript
,s
, 8);
339 s
= inifile
->GetKeyAsString(crittername
,"script_special_2",NULL
);
341 strnuprcpy(critter
.ClassScript
,s
, 8);
343 //special 3 == general
344 s
= inifile
->GetKeyAsString(crittername
,"script_special_3",NULL
);
346 strnuprcpy(critter
.GeneralScript
,s
, 8);
349 s
= inifile
->GetKeyAsString(crittername
,"script_team",NULL
);
351 strnuprcpy(critter
.SpecificScript
,s
, 8);
355 s
= inifile
->GetKeyAsString(crittername
,"script_combat",NULL
);
357 strnuprcpy(critter
.RaceScript
,s
, 8);
359 //movement == default
360 s
= inifile
->GetKeyAsString(crittername
,"script_movement",NULL
);
362 strnuprcpy(critter
.DefaultScript
,s
, 8);
366 s
= inifile
->GetKeyAsString(crittername
,"script_override",NULL
);
368 strnuprcpy(critter
.OverrideScript
,s
, 8);
370 s
= inifile
->GetKeyAsString(crittername
,"script_class",NULL
);
372 strnuprcpy(critter
.ClassScript
,s
, 8);
374 s
= inifile
->GetKeyAsString(crittername
,"script_race",NULL
);
376 strnuprcpy(critter
.RaceScript
,s
, 8);
378 s
= inifile
->GetKeyAsString(crittername
,"script_general",NULL
);
380 strnuprcpy(critter
.GeneralScript
,s
, 8);
382 s
= inifile
->GetKeyAsString(crittername
,"script_default",NULL
);
384 strnuprcpy(critter
.DefaultScript
,s
, 8);
386 s
= inifile
->GetKeyAsString(crittername
,"script_area",NULL
);
388 strnuprcpy(critter
.AreaScript
,s
, 8);
390 s
= inifile
->GetKeyAsString(crittername
,"script_specifics",NULL
);
392 strnuprcpy(critter
.SpecificScript
,s
, 8);
394 s
= inifile
->GetKeyAsString(crittername
,"dialog",NULL
);
396 strnuprcpy(critter
.Dialog
,s
, 8);
400 if (inifile
->GetKeyAsBool(crittername
,"death_scriptname",false)) {
401 critter
.Flags
|=CF_DEATHVAR
;
403 //don't spawn when spawnpoint is visible
404 if (inifile
->GetKeyAsBool(crittername
,"ignore_can_see",false)) {
405 critter
.Flags
|=CF_IGNORECANSEE
;
407 //unsure, but could be similar to previous
408 if (inifile
->GetKeyAsBool(crittername
,"check_view_port", false)) {
409 critter
.Flags
|=CF_CHECKVIEWPORT
;
411 //unknown, this is used only in pst
412 if (inifile
->GetKeyAsBool(crittername
,"check_crowd", false)) {
413 critter
.Flags
|=CF_CHECKCROWD
;
415 //unknown, this is used only in pst
416 if (inifile
->GetKeyAsBool(crittername
,"find_safest_point", false)) {
417 critter
.Flags
|=CF_SAFESTPOINT
;
419 //disable spawn based on game difficulty
420 if (inifile
->GetKeyAsBool(crittername
,"area_diff_1", false)) {
421 critter
.Flags
|=CF_NO_DIFF_1
;
423 if (inifile
->GetKeyAsBool(crittername
,"area_diff_2", false)) {
424 critter
.Flags
|=CF_NO_DIFF_2
;
426 if (inifile
->GetKeyAsBool(crittername
,"area_diff_3", false)) {
427 critter
.Flags
|=CF_NO_DIFF_3
;
431 void IniSpawn::ReadSpawnEntry(DataFileMgr
*inifile
, const char *entryname
, SpawnEntry
&entry
)
435 entry
.interval
= (unsigned int) inifile
->GetKeyAsInt(entryname
,"interval",0);
436 //don't default to NULL here, some entries may be missing in original game
437 //an empty default string here will create an empty but consistent entry
438 s
= inifile
->GetKeyAsString(entryname
,"critters","");
439 int crittercount
= CountElements(s
,',');
440 entry
.crittercount
=crittercount
;
441 entry
.critters
=new CritterEntry
[crittercount
];
442 ieVariable
*critters
= new ieVariable
[crittercount
];
443 GetElements(s
, critters
, crittercount
);
444 while(crittercount
--) {
445 ReadCreature(inifile
, critters
[crittercount
], entry
.critters
[crittercount
]);
450 void IniSpawn::InitSpawn(const ieResRef DefaultArea
)
454 Holder
<DataFileMgr
> inifile
= GetIniFile(DefaultArea
);
456 strnuprcpy(NamelessSpawnArea
, DefaultArea
, 8);
460 s
= inifile
->GetKeyAsString("nameless","destare",DefaultArea
);
461 strnuprcpy(NamelessSpawnArea
, s
, 8);
462 s
= inifile
->GetKeyAsString("nameless","point","[0.0]");
464 if (sscanf(s
,"[%d.%d]", &x
, &y
)!=2) {
468 NamelessSpawnPoint
.x
=x
;
469 NamelessSpawnPoint
.y
=y
;
470 //35 - already standing
472 NamelessState
= inifile
->GetKeyAsInt("nameless","state",36);
474 namelessvarcount
= inifile
->GetKeysCount("namelessvar");
475 if (namelessvarcount
) {
476 NamelessVar
= new VariableSpec
[namelessvarcount
];
477 for (y
=0;y
<namelessvarcount
;y
++) {
478 const char* Key
= inifile
->GetKeyNameByIndex("namelessvar",y
);
479 strnlwrcpy(NamelessVar
[y
].Name
, Key
, 32);
480 NamelessVar
[y
].Value
= inifile
->GetKeyAsInt("namelessvar",Key
,0);
484 localscount
= inifile
->GetKeysCount("locals");
486 Locals
= new VariableSpec
[localscount
];
487 for (y
=0;y
<localscount
;y
++) {
488 const char* Key
= inifile
->GetKeyNameByIndex("locals",y
);
489 strnlwrcpy(Locals
[y
].Name
, Key
, 32);
490 Locals
[y
].Value
= inifile
->GetKeyAsInt("locals",Key
,0);
494 s
= inifile
->GetKeyAsString("spawn_main","enter",NULL
);
496 ReadSpawnEntry(inifile
.get(), s
, enterspawn
);
498 s
= inifile
->GetKeyAsString("spawn_main","events",NULL
);
500 eventcount
= CountElements(s
,',');
501 eventspawns
= new SpawnEntry
[eventcount
];
502 ieVariable
*events
= new ieVariable
[eventcount
];
503 GetElements(s
, events
, eventcount
);
506 ReadSpawnEntry(inifile
.get(), events
[ec
], eventspawns
[ec
]);
517 //respawn nameless after he bit the dust
518 void IniSpawn::RespawnNameless()
520 Game
*game
= core
->GetGame();
521 Actor
*nameless
= game
->GetPC(0, false);
523 if (NamelessSpawnPoint
.isnull()) {
524 core
->GetGame()->JoinParty(nameless
,JP_INITPOS
);
525 NamelessSpawnPoint
=nameless
->Pos
;
526 strnuprcpy(NamelessSpawnArea
, nameless
->Area
, 8);
529 nameless
->Resurrect();
531 if (NamelessState
==36) {
532 nameless
->SetStance(IE_ANI_PST_START
);
536 for (i
=0;i
<game
->GetPartySize(false);i
++) {
537 MoveBetweenAreasCore(game
->GetPC(i
, false),NamelessSpawnArea
,NamelessSpawnPoint
,-1, true);
540 //certain variables are set when nameless dies
541 for (i
=0;i
<namelessvarcount
;i
++) {
542 SetVariable(game
, NamelessVar
[i
].Name
,"GLOBAL", NamelessVar
[i
].Value
);
546 void IniSpawn::SpawnCreature(CritterEntry
&critter
)
548 if (!critter
.creaturecount
) {
552 ieDword specvar
= CheckVariable(map
, critter
.SpecVar
, critter
.SpecContext
);
554 if (critter
.SpecVar
[0]) {
555 if (critter
.SpecVarOperator
>=0) {
556 // dunno if this should be negated
557 if (!DiffCore(specvar
, critter
.SpecVarValue
, critter
.SpecVarOperator
) ) {
561 //ar0203 in PST seems to want the check this way.
562 //if other areas conflict and you want to use (!specvar),
563 //please research further
564 //researched further - ar0203 respawns only if specvar is 1
571 if (!(critter
.Flags
&CF_IGNORECANSEE
)) {
572 if (map
->IsVisible(critter
.SpawnPoint
, false) ) {
577 if (critter
.Flags
&CF_NO_DIFF_MASK
) {
581 core
->GetDictionary()->Lookup("Difficulty Level", difficulty
);
585 diff_bit
= CF_NO_DIFF_1
;
588 diff_bit
= CF_NO_DIFF_2
;
591 diff_bit
= CF_NO_DIFF_3
;
596 if (critter
.Flags
&diff_bit
) {
601 if (critter
.ScriptName
[0] && (critter
.Flags
&CF_CHECK_NAME
) ) {
602 //maybe this one needs to be using getobjectcount as well
603 //currently we cannot count objects with scriptname???
604 if (map
->GetActor( critter
.ScriptName
, 0 )) {
608 //Object *object = new Object();
610 //objectfields based on spec
611 object
.objectFields
[0]=critter
.Spec
[0];
612 object
.objectFields
[1]=critter
.Spec
[1];
613 object
.objectFields
[2]=critter
.Spec
[2];
614 object
.objectFields
[3]=critter
.Spec
[3];
615 object
.objectFields
[4]=critter
.Spec
[4];
616 object
.objectFields
[5]=critter
.Spec
[5];
617 object
.objectFields
[6]=critter
.Spec
[6];
618 object
.objectFields
[7]=critter
.Spec
[7];
619 object
.objectFields
[8]=critter
.Spec
[8];
620 int cnt
= GetObjectCount(map
, &object
);
621 if (cnt
>=critter
.TotalQuantity
) {
626 int x
= core
->Roll(1,critter
.creaturecount
,-1);
627 Actor
* cre
= gamedata
->GetCreature(critter
.CreFile
[x
]);
632 SetVariable(map
, critter
.SpecVar
, critter
.SpecContext
, specvar
+(ieDword
) critter
.SpecVarInc
);
635 if (critter
.SetSpec
[x
]) {
636 cre
->SetBase(StatValues
[x
], critter
.SetSpec
[x
]);
639 cre
->SetPosition( critter
.SpawnPoint
, 0, 0);//maybe critters could be repositioned
640 cre
->SetOrientation(critter
.Orientation
,false);
641 if (critter
.ScriptName
[0]) {
642 cre
->SetScriptName(critter
.ScriptName
);
644 if (critter
.OverrideScript
[0]) {
645 cre
->SetScript(critter
.OverrideScript
, SCR_OVERRIDE
);
647 if (critter
.ClassScript
[0]) {
648 cre
->SetScript(critter
.ClassScript
, SCR_CLASS
);
650 if (critter
.RaceScript
[0]) {
651 cre
->SetScript(critter
.RaceScript
, SCR_RACE
);
653 if (critter
.GeneralScript
[0]) {
654 cre
->SetScript(critter
.GeneralScript
, SCR_GENERAL
);
656 if (critter
.DefaultScript
[0]) {
657 cre
->SetScript(critter
.DefaultScript
, SCR_DEFAULT
);
659 if (critter
.AreaScript
[0]) {
660 cre
->SetScript(critter
.AreaScript
, SCR_AREA
);
662 if (critter
.SpecificScript
[0]) {
663 cre
->SetScript(critter
.SpecificScript
, SCR_SPECIFICS
);
665 if (critter
.Dialog
[0]) {
666 cre
->SetDialog(critter
.Dialog
);
670 void IniSpawn::SpawnGroup(SpawnEntry
&event
)
672 if (!event
.critters
) {
675 unsigned int interval
= event
.interval
;
677 if(core
->GetGame()->GameTime
/interval
<=last_spawndate
/interval
) {
681 last_spawndate
=core
->GetGame()->GameTime
;
683 for(int i
=0;i
<event
.crittercount
;i
++) {
684 CritterEntry
* critter
= event
.critters
+i
;
685 for(int j
=0;j
<critter
->SpawnCount
;j
++) {
686 SpawnCreature(*critter
);
691 //execute the initial spawn
692 void IniSpawn::InitialSpawn()
694 SpawnGroup(enterspawn
);
695 //these variables are set when entering first
696 for (int i
=0;i
<localscount
;i
++) {
697 SetVariable(map
, Locals
[i
].Name
,"LOCALS", Locals
[i
].Value
);
701 //checks if a respawn event occurred
702 void IniSpawn::CheckSpawn()
704 for(int i
=0;i
<eventcount
;i
++) {
705 SpawnGroup(eventspawns
[i
]);