1 /*
2 ** info.cpp
3 ** Keeps track of available actors and their states
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 ** This is completely different from Doom's info.c.
34 **
35 */
36 
37 
38 #include "info.h"
39 #include "m_fixed.h"
40 #include "c_dispatch.h"
41 #include "d_net.h"
42 #include "v_text.h"
43 
44 #include "gi.h"
45 
46 #include "actor.h"
47 #include "r_state.h"
48 #include "i_system.h"
49 #include "p_local.h"
50 #include "templates.h"
51 #include "cmdlib.h"
52 #include "g_level.h"
53 
54 extern void LoadActors ();
55 extern void InitBotStuff();
56 extern void ClearStrifeTypes();
57 
58 FRandom FState::pr_statetics("StateTics");
59 
60 //==========================================================================
61 //
62 //
63 //==========================================================================
64 
GetSpriteIndex(const char * spritename,bool add)65 int GetSpriteIndex(const char * spritename, bool add)
66 {
67 	static char lastsprite[5];
68 	static int lastindex;
69 
70 	// Make sure that the string is upper case and 4 characters long
71 	char upper[5]={0,0,0,0,0};
72 	for (int i = 0; spritename[i] != 0 && i < 4; i++)
73 	{
74 		upper[i] = toupper (spritename[i]);
75 	}
76 
77 	// cache the name so if the next one is the same the function doesn't have to perform a search.
78 	if (!strcmp(upper, lastsprite))
79 	{
80 		return lastindex;
81 	}
82 	strcpy(lastsprite, upper);
83 
84 	for (unsigned i = 0; i < sprites.Size (); ++i)
85 	{
86 		if (strcmp (sprites[i].name, upper) == 0)
87 		{
88 			return (lastindex = (int)i);
89 		}
90 	}
91 	if (!add)
92 	{
93 		return (lastindex = -1);
94 	}
95 	spritedef_t temp;
96 	strcpy (temp.name, upper);
97 	temp.numframes = 0;
98 	temp.spriteframes = 0;
99 	return (lastindex = (int)sprites.Push (temp));
100 }
101 
102 
103 //==========================================================================
104 //
105 //
106 //==========================================================================
107 
StaticInit()108 void FActorInfo::StaticInit ()
109 {
110 	sprites.Clear();
111 	if (sprites.Size() == 0)
112 	{
113 		spritedef_t temp;
114 
115 		// Sprite 0 is always TNT1
116 		memcpy (temp.name, "TNT1", 5);
117 		temp.numframes = 0;
118 		temp.spriteframes = 0;
119 		sprites.Push (temp);
120 
121 		// Sprite 1 is always ----
122 		memcpy (temp.name, "----", 5);
123 		sprites.Push (temp);
124 
125 		// Sprite 2 is always ####
126 		memcpy (temp.name, "####", 5);
127 		sprites.Push (temp);
128 	}
129 
130 	Printf ("LoadActors: Load actor definitions.\n");
131 	ClearStrifeTypes();
132 	LoadActors ();
133 	InitBotStuff();
134 }
135 
136 //==========================================================================
137 //
138 // Called after Dehacked patches are applied
139 //
140 //==========================================================================
141 
StaticSetActorNums()142 void FActorInfo::StaticSetActorNums ()
143 {
144 	for (unsigned int i = 0; i < PClass::m_RuntimeActors.Size(); ++i)
145 	{
146 		PClass::m_RuntimeActors[i]->ActorInfo->RegisterIDs ();
147 	}
148 }
149 
150 //==========================================================================
151 //
152 //
153 //==========================================================================
154 
RegisterIDs()155 void FActorInfo::RegisterIDs ()
156 {
157 	const PClass *cls = PClass::FindClass(Class->TypeName);
158 
159 	// Conversation IDs have never been filtered by game so we cannot start doing that.
160 	if (ConversationID > 0)
161 	{
162 		StrifeTypes[ConversationID] = cls;
163 		if (cls != Class)
164 		{
165 			Printf(TEXTCOLOR_RED"Conversation ID %d refers to hidden class type '%s'\n", SpawnID, cls->TypeName.GetChars());
166 		}
167 	}
168 	if (GameFilter == GAME_Any || (GameFilter & gameinfo.gametype))
169 	{
170 		if (SpawnID > 0)
171 		{
172 			SpawnableThings[SpawnID] = cls;
173 			if (cls != Class)
174 			{
175 				Printf(TEXTCOLOR_RED"Spawn ID %d refers to hidden class type '%s'\n", SpawnID, cls->TypeName.GetChars());
176 			}
177 		}
178 		if (DoomEdNum != -1)
179 		{
180 			FDoomEdEntry *oldent = DoomEdMap.CheckKey(DoomEdNum);
181 			if (oldent != NULL && oldent->Special == -2)
182 			{
183 				Printf(TEXTCOLOR_RED"Editor number %d defined twice for classes '%s' and '%s'\n", DoomEdNum, cls->TypeName.GetChars(), oldent->Type->TypeName.GetChars());
184 			}
185 			FDoomEdEntry ent;
186 			memset(&ent, 0, sizeof(ent));
187 			ent.Type = cls;
188 			ent.Special = -2;	// use -2 instead of -1 so that we can recognize DECORATE defined entries and print a warning message if duplicates occur.
189 			DoomEdMap.Insert(DoomEdNum, ent);
190 			if (cls != Class)
191 			{
192 				Printf(TEXTCOLOR_RED"Editor number %d refers to hidden class type '%s'\n", DoomEdNum, cls->TypeName.GetChars());
193 			}
194 		}
195 	}
196 }
197 
198 //==========================================================================
199 //
200 //
201 //==========================================================================
202 
GetReplacement(bool lookskill)203 FActorInfo *FActorInfo::GetReplacement (bool lookskill)
204 {
205 	FName skillrepname;
206 
207 	if (lookskill && AllSkills.Size() > (unsigned)gameskill)
208 	{
209 		skillrepname = AllSkills[gameskill].GetReplacement(this->Class->TypeName);
210 		if (skillrepname != NAME_None && PClass::FindClass(skillrepname) == NULL)
211 		{
212 			Printf("Warning: incorrect actor name in definition of skill %s: \n"
213 				   "class %s is replaced by non-existent class %s\n"
214 				   "Skill replacement will be ignored for this actor.\n",
215 				   AllSkills[gameskill].Name.GetChars(),
216 				   this->Class->TypeName.GetChars(), skillrepname.GetChars());
217 			AllSkills[gameskill].SetReplacement(this->Class->TypeName, NAME_None);
218 			AllSkills[gameskill].SetReplacedBy(skillrepname, NAME_None);
219 			lookskill = false; skillrepname = NAME_None;
220 		}
221 	}
222 	if (Replacement == NULL && (!lookskill || skillrepname == NAME_None))
223 	{
224 		return this;
225 	}
226 	// The Replacement field is temporarily NULLed to prevent
227 	// potential infinite recursion.
228 	FActorInfo *savedrep = Replacement;
229 	Replacement = NULL;
230 	FActorInfo *rep = savedrep;
231 	// Handle skill-based replacement here. It has precedence on DECORATE replacement
232 	// in that the skill replacement is applied first, followed by DECORATE replacement
233 	// on the actor indicated by the skill replacement.
234 	if (lookskill && (skillrepname != NAME_None))
235 	{
236 		rep = PClass::FindClass(skillrepname)->ActorInfo;
237 	}
238 	// Now handle DECORATE replacement chain
239 	// Skill replacements are not recursive, contrarily to DECORATE replacements
240 	rep = rep->GetReplacement(false);
241 	// Reset the temporarily NULLed field
242 	Replacement = savedrep;
243 	return rep;
244 }
245 
246 //==========================================================================
247 //
248 //
249 //==========================================================================
250 
GetReplacee(bool lookskill)251 FActorInfo *FActorInfo::GetReplacee (bool lookskill)
252 {
253 	FName skillrepname;
254 
255 	if (lookskill && AllSkills.Size() > (unsigned)gameskill)
256 	{
257 		skillrepname = AllSkills[gameskill].GetReplacedBy(this->Class->TypeName);
258 		if (skillrepname != NAME_None && PClass::FindClass(skillrepname) == NULL)
259 		{
260 			Printf("Warning: incorrect actor name in definition of skill %s: \n"
261 				   "non-existent class %s is replaced by class %s\n"
262 				   "Skill replacement will be ignored for this actor.\n",
263 				   AllSkills[gameskill].Name.GetChars(),
264 				   skillrepname.GetChars(), this->Class->TypeName.GetChars());
265 			AllSkills[gameskill].SetReplacedBy(this->Class->TypeName, NAME_None);
266 			AllSkills[gameskill].SetReplacement(skillrepname, NAME_None);
267 			lookskill = false;
268 		}
269 	}
270 	if (Replacee == NULL && (!lookskill || skillrepname == NAME_None))
271 	{
272 		return this;
273 	}
274 	// The Replacee field is temporarily NULLed to prevent
275 	// potential infinite recursion.
276 	FActorInfo *savedrep = Replacee;
277 	Replacee = NULL;
278 	FActorInfo *rep = savedrep;
279 	if (lookskill && (skillrepname != NAME_None) && (PClass::FindClass(skillrepname) != NULL))
280 	{
281 		rep = PClass::FindClass(skillrepname)->ActorInfo;
282 	}
283 	rep = rep->GetReplacee (false);	Replacee = savedrep;
284 	return rep;
285 }
286 
287 //==========================================================================
288 //
289 //
290 //==========================================================================
291 
SetDamageFactor(FName type,fixed_t factor)292 void FActorInfo::SetDamageFactor(FName type, fixed_t factor)
293 {
294 	if (DamageFactors == NULL)
295 	{
296 		DamageFactors = new DmgFactors;
297 	}
298 	DamageFactors->Insert(type, factor);
299 }
300 
301 //==========================================================================
302 //
303 //
304 //==========================================================================
305 
SetPainChance(FName type,int chance)306 void FActorInfo::SetPainChance(FName type, int chance)
307 {
308 	if (chance >= 0)
309 	{
310 		if (PainChances == NULL) PainChances=new PainChanceList;
311 		PainChances->Insert(type, MIN(chance, 256));
312 	}
313 	else
314 	{
315 		if (PainChances != NULL)
316 			PainChances->Remove(type);
317 	}
318 }
319 
320 //==========================================================================
321 //
322 //
323 //==========================================================================
324 
SetPainFlash(FName type,PalEntry color)325 void FActorInfo::SetPainFlash(FName type, PalEntry color)
326 {
327 	if (PainFlashes == NULL)
328 		PainFlashes = new PainFlashList;
329 
330 	PainFlashes->Insert(type, color);
331 }
332 
333 //==========================================================================
334 //
335 //
336 //==========================================================================
337 
GetPainFlash(FName type,PalEntry * color) const338 bool FActorInfo::GetPainFlash(FName type, PalEntry *color) const
339 {
340 	const FActorInfo *info = this;
341 
342 	while (info != NULL)
343 	{
344 		if (info->PainFlashes != NULL)
345 		{
346 			PalEntry *flash = info->PainFlashes->CheckKey(type);
347 			if (flash != NULL)
348 			{
349 				*color = *flash;
350 				return true;
351 			}
352 		}
353 		// Try parent class
354 		info = info->Class->ParentClass->ActorInfo;
355 	}
356 	return false;
357 }
358 
359 //==========================================================================
360 //
361 //
362 //==========================================================================
363 
SetColorSet(int index,const FPlayerColorSet * set)364 void FActorInfo::SetColorSet(int index, const FPlayerColorSet *set)
365 {
366 	if (set != NULL)
367 	{
368 		if (ColorSets == NULL) ColorSets = new FPlayerColorSetMap;
369 		ColorSets->Insert(index, *set);
370 	}
371 	else
372 	{
373 		if (ColorSets != NULL)
374 			ColorSets->Remove(index);
375 	}
376 }
377 
378 //==========================================================================
379 //
380 // DmgFactors :: CheckFactor
381 //
382 // Checks for the existance of a certain damage type. If that type does not
383 // exist, the damage factor for type 'None' will be returned, if present.
384 //
385 //==========================================================================
386 
CheckFactor(FName type)387 fixed_t *DmgFactors::CheckFactor(FName type)
388 {
389 	fixed_t *pdf = CheckKey(type);
390 	if (pdf == NULL && type != NAME_None)
391 	{
392 		pdf = CheckKey(NAME_None);
393 	}
394 	return pdf;
395 }
396 
SummonActor(int command,int command2,FCommandLine argv)397 static void SummonActor (int command, int command2, FCommandLine argv)
398 {
399 	if (CheckCheatmode ())
400 		return;
401 
402 	if (argv.argc() > 1)
403 	{
404 		const PClass *type = PClass::FindClass (argv[1]);
405 		if (type == NULL)
406 		{
407 			Printf ("Unknown class '%s'\n", argv[1]);
408 			return;
409 		}
410 		Net_WriteByte (argv.argc() > 2 ? command2 : command);
411 		Net_WriteString (type->TypeName.GetChars());
412 
413 		if (argv.argc () > 2) {
414 			Net_WriteWord (atoi (argv[2])); // angle
415 			if (argv.argc () > 3) Net_WriteWord (atoi (argv[3])); // TID
416 			else Net_WriteWord (0);
417 			if (argv.argc () > 4) Net_WriteByte (atoi (argv[4])); // special
418 			else Net_WriteByte (0);
419 			for(int i = 5; i < 10; i++) { // args[5]
420 				if(i < argv.argc()) Net_WriteLong (atoi (argv[i]));
421 				else Net_WriteLong (0);
422 			}
423 		}
424 	}
425 }
426 
CCMD(summon)427 CCMD (summon)
428 {
429 	SummonActor (DEM_SUMMON, DEM_SUMMON2, argv);
430 }
431 
CCMD(summonfriend)432 CCMD (summonfriend)
433 {
434 	SummonActor (DEM_SUMMONFRIEND, DEM_SUMMONFRIEND2, argv);
435 }
436 
CCMD(summonmbf)437 CCMD (summonmbf)
438 {
439 	SummonActor (DEM_SUMMONMBF, DEM_SUMMONFRIEND2, argv);
440 }
441 
CCMD(summonfoe)442 CCMD (summonfoe)
443 {
444 	SummonActor (DEM_SUMMONFOE, DEM_SUMMONFOE2, argv);
445 }
446 
447 
448 // Damage type defaults / global settings
449 
450 TMap<FName, DamageTypeDefinition> GlobalDamageDefinitions;
451 
Apply(FName type)452 void DamageTypeDefinition::Apply(FName type)
453 {
454 	GlobalDamageDefinitions[type] = *this;
455 }
456 
Get(FName type)457 DamageTypeDefinition *DamageTypeDefinition::Get(FName type)
458 {
459 	return GlobalDamageDefinitions.CheckKey(type);
460 }
461 
IgnoreArmor(FName type)462 bool DamageTypeDefinition::IgnoreArmor(FName type)
463 {
464 	DamageTypeDefinition *dtd = Get(type);
465 	if (dtd) return dtd->NoArmor;
466 	return false;
467 }
468 
469 //==========================================================================
470 //
471 // DamageTypeDefinition :: ApplyMobjDamageFactor
472 //
473 // Calculates mobj damage based on original damage, defined damage factors
474 // and damage type.
475 //
476 // If the specific damage type is not defined, the damage factor for
477 // type 'None' will be used (with 1.0 as a default value).
478 //
479 // Globally declared damage types may override or multiply the damage
480 // factor when 'None' is used as a fallback in this function.
481 //
482 //==========================================================================
483 
ApplyMobjDamageFactor(int damage,FName type,DmgFactors const * const factors)484 int DamageTypeDefinition::ApplyMobjDamageFactor(int damage, FName type, DmgFactors const * const factors)
485 {
486 	if (factors)
487 	{
488 		// If the actor has named damage factors, look for a specific factor
489 		fixed_t const *pdf = factors->CheckKey(type);
490 		if (pdf) return FixedMul(damage, *pdf); // type specific damage type
491 
492 		// If this was nonspecific damage, don't fall back to nonspecific search
493 		if (type == NAME_None) return damage;
494 	}
495 
496 	// If this was nonspecific damage, don't fall back to nonspecific search
497 	else if (type == NAME_None)
498 	{
499 		return damage;
500 	}
501 	else
502 	{
503 		// Normal is unsupplied / 1.0, so there's no difference between modifying and overriding
504 		DamageTypeDefinition *dtd = Get(type);
505 		return dtd ? FixedMul(damage, dtd->DefaultFactor) : damage;
506 	}
507 
508 	{
509 		fixed_t const *pdf  = factors->CheckKey(NAME_None);
510 		DamageTypeDefinition *dtd = Get(type);
511 		// Here we are looking for modifications to untyped damage
512 		// If the calling actor defines untyped damage factor, that is contained in "pdf".
513 		if (pdf) // normal damage available
514 		{
515 			if (dtd)
516 			{
517 				if (dtd->ReplaceFactor) return FixedMul(damage, dtd->DefaultFactor); // use default instead of untyped factor
518 				return FixedMul(damage, FixedMul(*pdf, dtd->DefaultFactor)); // use default as modification of untyped factor
519 			}
520 			return FixedMul(damage, *pdf); // there was no default, so actor default is used
521 		}
522 		else if (dtd)
523 		{
524 			return FixedMul(damage, dtd->DefaultFactor); // implicit untyped factor 1.0 does not need to be applied/replaced explicitly
525 		}
526 	}
527 	return damage;
528 }
529