1 //----------------------------------------------------------------------------
2 //  EDGE Data Definition File Code (Attack Types)
3 //----------------------------------------------------------------------------
4 //
5 //  Copyright (c) 1999-2008  The EDGE Team.
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; either version 2
10 //  of the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 //
17 //----------------------------------------------------------------------------
18 //
19 // Attacks Setup and Parser Code
20 //
21 // 1998/10/29 -KM- Finalisation of sound code.  SmartProjectile.
22 //
23 
24 #include "local.h"
25 
26 #undef  DF
27 #define DF  DDF_FIELD
28 
29 atkdef_container_c atkdefs;
30 
31 extern mobjtype_c * dynamic_mobj;
32 
33 // these logically belongs with buffer_atk:
34 static float a_damage_range;
35 static float a_damage_multi;
36 
37 static void DDF_AtkGetType(const char *info, void *storage);
38 static void DDF_AtkGetSpecial(const char *info, void *storage);
39 static void DDF_AtkGetLabel(const char *info, void *storage);
40 
41 #undef  DDF_CMD_BASE
42 #define DDF_CMD_BASE  dummy_damage
43 damage_c dummy_damage;
44 
45 const commandlist_t damage_commands[] =
46 {
47 	DF("VAL",   nominal,    DDF_MainGetFloat),
48 	DF("MAX",   linear_max, DDF_MainGetFloat),
49 	DF("ERROR", error,      DDF_MainGetFloat),
50 	DF("DELAY", delay,      DDF_MainGetTime),
51 
52 	DF("OBITUARY",    obituary, DDF_MainGetString),
53 	DF("PAIN_STATE",  pain,     DDF_AtkGetLabel),
54 	DF("DEATH_STATE", death,    DDF_AtkGetLabel),
55 	DF("OVERKILL_STATE", overkill, DDF_AtkGetLabel),
56 
57 	DDF_CMD_END
58 };
59 
60 // -KM- 1998/09/27 Major changes to sound handling
61 // -KM- 1998/11/25 Accuracy + Translucency are now fraction.  Added a spare attack for BFG.
62 // -KM- 1999/01/29 Merged in thing commands, so there is one list of
63 //  thing commands for all types of things (scenery, items, creatures + projectiles)
64 
65 static atkdef_c *dynamic_atk;
66 
67 #undef  DDF_CMD_BASE
68 #define DDF_CMD_BASE  dummy_atk
69 static atkdef_c dummy_atk;
70 
71 static const commandlist_t attack_commands[] =
72 {
73 	// sub-commands
74 	DDF_SUB_LIST("DAMAGE", damage, damage_commands),
75 
76 	DF("ATTACKTYPE", attackstyle, DDF_AtkGetType),
77 	DF("ATTACK_SPECIAL", flags, DDF_AtkGetSpecial),
78 	DF("ACCURACY_SLOPE", accuracy_slope, DDF_MainGetSlope),
79 	DF("ACCURACY_ANGLE", accuracy_angle, DDF_MainGetAngle),
80 	DF("ATTACK_HEIGHT", height, DDF_MainGetFloat),
81 	DF("SHOTCOUNT", count, DDF_MainGetNumeric),
82 	DF("X_OFFSET", xoffset, DDF_MainGetFloat),
83 	DF("Y_OFFSET", yoffset, DDF_MainGetFloat),
84 	DF("ANGLE_OFFSET", angle_offset, DDF_MainGetAngle),
85 	DF("SLOPE_OFFSET", slope_offset, DDF_MainGetSlope),
86 	DF("ATTACKRANGE", range, DDF_MainGetFloat),
87 	DF("TOO_CLOSE_RANGE", tooclose, DDF_MainGetNumeric),
88 	DF("BERSERK_MULTIPLY", berserk_mul, DDF_MainGetFloat),
89 	DF("NO_TRACE_CHANCE", notracechance, DDF_MainGetPercent),
90 	DF("KEEP_FIRING_CHANCE", keepfirechance, DDF_MainGetPercent),
91 	DF("TRACE_ANGLE", trace_angle, DDF_MainGetAngle),
92 	DF("ASSAULT_SPEED", assault_speed, DDF_MainGetFloat),
93 	DF("ATTEMPT_SOUND", initsound, DDF_MainLookupSound),
94 	DF("ENGAGED_SOUND", sound, DDF_MainLookupSound),
95 	DF("SPAWNED_OBJECT", spawnedobj_ref, DDF_MainGetString),
96 	DF("SPAWN_OBJECT_STATE", objinitstate_ref, DDF_MainGetString),
97 	DF("SPAWN_LIMIT", spawn_limit, DDF_MainGetNumeric),
98 	DF("PUFF", puff_ref, DDF_MainGetString),
99 	DF("ATTACK_CLASS", attack_class, DDF_MainGetBitSet),
100 
101 	// -AJA- backward compatibility cruft...
102 	DF("DAMAGE", damage.nominal, DDF_MainGetFloat),
103 
104 	DDF_CMD_END
105 };
106 
107 
CreateAtkMobj(const char * atk_name)108 static mobjtype_c * CreateAtkMobj(const char *atk_name)
109 {
110 	mobjtype_c *mobj = new mobjtype_c();
111 
112 	// determine a name
113 	char mobj_name[256];
114 
115 	snprintf(mobj_name, sizeof(mobj_name)-2, "atk:%s", atk_name);
116 	mobj_name[255] = 0;
117 
118 	mobj->name = mobj_name;  // copies it
119 	mobj->number = ATTACK__MOBJ;
120 
121 	return mobj;
122 }
123 
124 
125 //
126 //  DDF PARSE ROUTINES
127 //
128 
AttackStartEntry(const char * name,bool extend)129 static void AttackStartEntry(const char *name, bool extend)
130 {
131 	if (!name || !name[0])
132 	{
133 		DDF_WarnError("New attack entry is missing a name!");
134 		name = "ATTACK_WITH_NO_NAME";
135 	}
136 
137 	a_damage_range = -1;
138 	a_damage_multi = -1;
139 
140 	// mobj counterpart will be created only if needed
141 	dynamic_mobj = NULL;
142 
143 	dynamic_atk = atkdefs.Lookup(name);
144 
145 	if (extend)
146 	{
147 		if (! dynamic_atk)
148 			DDF_Error("Unknown attack to extend: %s\n", name);
149 
150 		// Intentional Const Override
151 		dynamic_mobj = (mobjtype_c *)dynamic_atk->atk_mobj;
152 
153 		if (dynamic_mobj)
154 			DDF_StateBeginRange(dynamic_mobj->state_grp);
155 
156 		return;
157 	}
158 
159 	// replaces an existing entry?
160 	if (dynamic_atk)
161 	{
162 		dynamic_atk->Default();
163 		return;
164 	}
165 
166 	// not found, create a new one
167 	dynamic_atk = new atkdef_c;
168 	dynamic_atk->name = name;
169 
170 	atkdefs.Insert(dynamic_atk);
171 }
172 
173 
AttackDoTemplate(const char * contents)174 static void AttackDoTemplate(const char *contents)
175 {
176 	atkdef_c *other = atkdefs.Lookup(contents);
177 
178 	if (!other || other == dynamic_atk)
179 		DDF_Error("Unknown attack template: '%s'\n", contents);
180 
181 	dynamic_atk->CopyDetail(*other);
182 	dynamic_atk->atk_mobj = NULL;
183 
184 	dynamic_mobj = NULL;
185 
186 	if (other->atk_mobj)
187 	{
188 		dynamic_mobj = CreateAtkMobj(dynamic_atk->name.c_str());
189 
190 		dynamic_mobj->CopyDetail(*(mobjtype_c*)other->atk_mobj);
191 
192 		dynamic_atk->atk_mobj = dynamic_mobj;
193 
194 		DDF_StateBeginRange(dynamic_mobj->state_grp);
195 	}
196 }
197 
198 
AttackParseField(const char * field,const char * contents,int index,bool is_last)199 static void AttackParseField(const char *field, const char *contents,
200 							 int index, bool is_last)
201 {
202 #if (DEBUG_DDF)
203 	I_Debugf("ATTACK_PARSE: %s = %s;\n", field, contents);
204 #endif
205 
206 	if (DDF_CompareName(field, "TEMPLATE") == 0)
207 	{
208 		AttackDoTemplate(contents);
209 		return;
210 	}
211 
212 	// backward compatibility...
213 	if (DDF_CompareName(field, "DAMAGE_RANGE") == 0)
214 	{
215 		a_damage_range = atof(contents);
216 		return;
217 	}
218 	else if (DDF_CompareName(field, "DAMAGE_MULTI") == 0)
219 	{
220 		a_damage_multi = atof(contents);
221 		return;
222 	}
223 
224 	// first, check attack commands
225 	if (DDF_MainParseField(attack_commands, field, contents, (byte *)dynamic_atk))
226 		return;
227 
228 	// we need to create an MOBJ for this attack
229 	if (! dynamic_mobj)
230 	{
231 		dynamic_mobj = CreateAtkMobj(dynamic_atk->name.c_str());
232 
233 		dynamic_atk->atk_mobj = dynamic_mobj;
234 
235 		DDF_StateBeginRange(dynamic_mobj->state_grp);
236 	}
237 
238 	ThingParseField(field, contents, index, is_last);
239 }
240 
241 
AttackFinishEntry(void)242 static void AttackFinishEntry(void)
243 {
244 	// handle attacks that have mobjs
245 	if (dynamic_mobj)
246 	{
247 		DDF_StateFinishRange(dynamic_mobj->state_grp);
248 
249 		// check MOBJ stuff
250 
251 		if (dynamic_mobj->explode_damage.nominal < 0)
252 		{
253 			DDF_WarnError("Bad EXPLODE_DAMAGE.VAL value %f in DDF.\n",
254 				dynamic_mobj->explode_damage.nominal);
255 		}
256 
257 		if (dynamic_mobj->explode_radius < 0)
258 		{
259 			DDF_WarnError("Bad EXPLODE_RADIUS value %f in DDF.\n",
260 				dynamic_mobj->explode_radius);
261 		}
262 
263 		if (dynamic_mobj->model_skin < 0 || dynamic_mobj->model_skin > 9)
264 			DDF_Error("Bad MODEL_SKIN value %d in DDF (must be 0-9).\n",
265 				dynamic_mobj->model_skin);
266 
267 		if (dynamic_mobj->dlight[0].radius > 512)
268 			DDF_Warning("DLIGHT RADIUS value %1.1f too large (over 512).\n",
269 				dynamic_mobj->dlight[0].radius);
270 	}
271 
272 	// check DAMAGE stuff
273 	if (dynamic_atk->damage.nominal < 0)
274 	{
275 		DDF_WarnError("Bad DAMAGE.VAL value %f in DDF.\n", dynamic_atk->damage.nominal);
276 	}
277 
278 	// compute an attack class, if none specified
279 	if (dynamic_atk->attack_class == BITSET_EMPTY)
280 	{
281 		dynamic_atk->attack_class = dynamic_mobj ? BITSET_MAKE('M') :
282 			(dynamic_atk->attackstyle == ATK_CLOSECOMBAT ||
283 			 dynamic_atk->attackstyle == ATK_SKULLFLY) ?
284 			BITSET_MAKE('C') : BITSET_MAKE('B');
285 	}
286 
287 	// -AJA- 2001/01/27: Backwards compatibility
288 	if (a_damage_range > 0)
289 	{
290 		dynamic_atk->damage.nominal = a_damage_range;
291 
292 		if (a_damage_multi > 0)
293 			dynamic_atk->damage.linear_max = a_damage_range * a_damage_multi;
294 	}
295 
296 	// -AJA- 2005/08/06: Berserk backwards compatibility
297 	if (DDF_CompareName(dynamic_atk->name.c_str(), "PLAYER_PUNCH") == 0 &&
298 		dynamic_atk->berserk_mul == 1.0f)
299 	{
300 		dynamic_atk->berserk_mul = 10.0f;
301 	}
302 
303 	// TODO: check more stuff...
304 }
305 
306 
AttackClearAll(void)307 static void AttackClearAll(void)
308 {
309 	I_Warning("Ignoring #CLEARALL in attacks.ddf\n");
310 }
311 
312 
DDF_ReadAtks(void * data,int size)313 bool DDF_ReadAtks(void *data, int size)
314 {
315 	readinfo_t attacks;
316 
317 	attacks.memfile = (char*)data;
318 	attacks.memsize = size;
319 	attacks.tag = "ATTACKS";
320 	attacks.entries_per_dot = 2;
321 
322 	if (attacks.memfile)
323 	{
324 		attacks.message = NULL;
325 		attacks.filename = NULL;
326 		attacks.lumpname = "DDFATK";
327 	}
328 	else
329 	{
330 		attacks.message = "DDF_InitAttacks";
331 		attacks.filename = "attacks.ddf";
332 		attacks.lumpname = NULL;
333 	}
334 
335 	attacks.start_entry  = AttackStartEntry;
336 	attacks.parse_field  = AttackParseField;
337 	attacks.finish_entry = AttackFinishEntry;
338 	attacks.clear_all    = AttackClearAll;
339 
340 	return DDF_MainReadFile(&attacks);
341 }
342 
DDF_AttackInit(void)343 void DDF_AttackInit(void)
344 {
345 	atkdefs.Clear();
346 }
347 
DDF_AttackCleanUp(void)348 void DDF_AttackCleanUp(void)
349 {
350 	epi::array_iterator_c it;
351 	atkdef_c *a;
352 
353 	for (it = atkdefs.GetIterator(0); it.IsValid(); it++)
354 	{
355 		a = ITERATOR_TO_TYPE(it, atkdef_c*);
356 
357 		cur_ddf_entryname = epi::STR_Format("[%s]  (attacks.ddf)", a->name.c_str());
358 
359 		// lookup thing references
360 
361 		a->puff = a->puff_ref.empty() ?
362 				NULL : mobjtypes.Lookup(a->puff_ref);
363 
364 		a->spawnedobj = a->spawnedobj_ref.empty() ?
365 						NULL : mobjtypes.Lookup(a->spawnedobj_ref);
366 
367 		if (a->spawnedobj)
368 		{
369 			if (a->objinitstate_ref.empty())
370 				a->objinitstate = a->spawnedobj->spawn_state;
371 			else
372 				a->objinitstate = DDF_MainLookupDirector(a->spawnedobj, a->objinitstate_ref);
373 		}
374 
375 		cur_ddf_entryname.clear();
376 	}
377 
378 	atkdefs.Trim();
379 }
380 
381 
382 static const specflags_t attack_specials[] =
383 {
384     {"SMOKING_TRACER", AF_TraceSmoke, 0},
385     {"KILL_FAILED_SPAWN", AF_KillFailedSpawn, 0},
386     {"REMOVE_FAILED_SPAWN", AF_KillFailedSpawn, 1},
387     {"PRESTEP_SPAWN", AF_PrestepSpawn, 0},
388     {"SPAWN_TELEFRAGS", AF_SpawnTelefrags, 0},
389     {"NEED_SIGHT", AF_NeedSight, 0},
390     {"FACE_TARGET", AF_FaceTarget, 0},
391 
392     {"FORCE_AIM", AF_ForceAim, 0},
393     {"ANGLED_SPAWN", AF_AngledSpawn, 0},
394     {"PLAYER_ATTACK", AF_Player, 0},
395     {"TRIGGER_LINES", AF_NoTriggerLines, 1},
396     {"SILENT_TO_MONSTERS", AF_SilentToMon, 0},
397     {"TARGET", AF_NoTarget, 1},
398     {"VAMPIRE", AF_Vampire, 0},
399 
400     // -AJA- backwards compatibility cruft...
401     {"NOAMMO", AF_None, 0},
402 
403     {NULL, AF_None, 0}
404 };
405 
DDF_AtkGetSpecial(const char * info,void * storage)406 static void DDF_AtkGetSpecial(const char *info, void *storage)
407 {
408 	attackflags_e *var = (attackflags_e *)storage;
409 
410 	int flag_value;
411 
412 	switch (DDF_MainCheckSpecialFlag(info, attack_specials, &flag_value, true, false))
413 	{
414 		case CHKF_Positive:
415 			*var = (attackflags_e)(*var | flag_value);
416 			break;
417 
418 		case CHKF_Negative:
419 			*var = (attackflags_e)(*var & ~flag_value);
420 			break;
421 
422 		case CHKF_User:
423 		case CHKF_Unknown:
424 			DDF_WarnError("DDF_AtkGetSpecials: Unknown Attack Special: %s\n", info);
425 			break;
426 	}
427 }
428 
429 // -KM- 1998/11/25 Added new attack type for BFG: Spray
430 static const char *attack_class[NUMATKCLASS] =
431 {
432     "NONE",
433     "PROJECTILE",
434     "SPAWNER",
435     "TRIPLE_SPAWNER",
436     "FIXED_SPREADER",
437     "RANDOM_SPREADER",
438     "SHOT",
439     "TRACKER",
440     "CLOSECOMBAT",
441     "SHOOTTOSPOT",
442     "SKULLFLY",
443     "SMARTPROJECTILE",
444     "SPRAY"
445 };
446 
DDF_AtkGetType(const char * info,void * storage)447 static void DDF_AtkGetType(const char *info, void *storage)
448 {
449 	attackstyle_e *var = (attackstyle_e *)storage;
450 
451 	int i;
452 
453 	for (i=0; i < NUMATKCLASS; i++)
454 		if (DDF_CompareName(info, attack_class[i]) == 0)
455 			break;
456 
457 	if (i >= NUMATKCLASS)
458 	{
459 		DDF_WarnError("DDF_AtkGetType: No such attack type '%s'\n", info);
460 		*var = ATK_SHOT;
461 		return;
462 	}
463 
464   	*var = (attackstyle_e) i;
465 }
466 
467 //
468 // DDF_AtkGetLabel
469 //
DDF_AtkGetLabel(const char * info,void * storage)470 static void DDF_AtkGetLabel(const char *info, void *storage)
471 {
472 	label_offset_c *lab = (label_offset_c *)storage;
473 
474 	// check for `:' in string
475 	const char *div = strchr(info, ':');
476 
477 	int i = div ? (div - info) : (int)strlen(info);
478 
479 	if (i <= 0)
480 		DDF_Error("Bad State `%s'.\n", info);
481 
482 	lab->label.Set(info, i);
483 	lab->offset = div ? MAX(0, atoi(div+1) - 1) : 0;
484 }
485 
486 // Attack definition class
487 
488 //
489 // atkdef_c Constructor
490 //
atkdef_c()491 atkdef_c::atkdef_c() : name()
492 {
493 	Default();
494 }
495 
496 //
497 // atkdef_c Destructor
498 //
~atkdef_c()499 atkdef_c::~atkdef_c()
500 {
501 }
502 
503 //
504 // atkdef_c::CopyDetail()
505 //
CopyDetail(atkdef_c & src)506 void atkdef_c::CopyDetail(atkdef_c &src)
507 {
508 	attackstyle = src.attackstyle;
509 	flags = src.flags;
510 	initsound = src.initsound;
511 	sound = src.sound;
512 	accuracy_slope = src.accuracy_slope;
513 	accuracy_angle = src.accuracy_angle;
514 	xoffset = src.xoffset;
515 	yoffset = src.yoffset;
516 	angle_offset = src.angle_offset;
517 	slope_offset = src.slope_offset;
518 	trace_angle  = src.trace_angle;
519 	assault_speed = src.assault_speed;
520 	height = src.height;
521 	range = src.range;
522 	count = src.count;
523 	tooclose = src.tooclose;
524 	berserk_mul = src.berserk_mul;
525 
526 	damage = src.damage;
527 
528 	attack_class = src.attack_class;
529 	objinitstate = src.objinitstate;
530 	objinitstate_ref = src.objinitstate_ref;
531 	notracechance = src.notracechance;
532 	keepfirechance = src.keepfirechance;
533 	atk_mobj = src.atk_mobj;
534 	spawnedobj = src.spawnedobj;
535 	spawnedobj_ref = src.spawnedobj_ref;
536 	spawn_limit = src.spawn_limit;
537 	puff = src.puff;
538 	puff_ref = src.puff_ref;
539 }
540 
541 //
542 // atkdef_c::Default()
543 //
Default()544 void atkdef_c::Default()
545 {
546 	attackstyle = ATK_NONE;
547 	flags = AF_None;
548 	initsound = NULL;
549 	sound = NULL;
550 	accuracy_slope = 0;
551 	accuracy_angle = 0;
552 	xoffset = 0;
553 	yoffset = 0;
554 	angle_offset = 0;
555 	slope_offset = 0;
556 	trace_angle = (ANG270 / 16);
557 	assault_speed = 0;
558 	height = 0;
559 	range = 0;
560 	count = 0;
561 	tooclose = 0;
562 	berserk_mul = 1.0f;
563 
564 	damage.Default(damage_c::DEFAULT_Attack);
565 
566 	attack_class = BITSET_EMPTY;
567 	objinitstate = 0;
568 	objinitstate_ref.clear();
569 	notracechance = PERCENT_MAKE(0);
570 	keepfirechance = PERCENT_MAKE(0);
571 	atk_mobj = NULL;
572 	spawnedobj = NULL;
573 	spawnedobj_ref.clear();
574 	spawn_limit = 0;  // unlimited
575 	puff = NULL;
576 	puff_ref.clear();
577 }
578 
579 
580 // --> atkdef_container_c class
581 
582 //
583 // atkdef_container_c::atkdef_container_c()
584 //
atkdef_container_c()585 atkdef_container_c::atkdef_container_c() : epi::array_c(sizeof(atkdef_c*))
586 {
587 }
588 
589 //
590 // ~atkdef_container_c::atkdef_container_c()
591 //
~atkdef_container_c()592 atkdef_container_c::~atkdef_container_c()
593 {
594 	Clear();					// <-- Destroy self before exiting
595 }
596 
597 //
598 // atkdef_container_c::CleanupObject()
599 //
CleanupObject(void * obj)600 void atkdef_container_c::CleanupObject(void *obj)
601 {
602 	atkdef_c *a = *(atkdef_c**)obj;
603 
604 	if (a)
605 		delete a;
606 
607 	return;
608 }
609 
610 //
611 // atkdef_c* atkdef_container_c::Lookup()
612 //
613 // Looks an atkdef by name, returns a fatal error if it does not exist.
614 //
Lookup(const char * refname)615 atkdef_c* atkdef_container_c::Lookup(const char *refname)
616 {
617 	epi::array_iterator_c it;
618 	atkdef_c *a;
619 
620 	if (!refname || !refname[0])
621 		return NULL;
622 
623 	for (it = GetIterator(0); it.IsValid(); it++)
624 	{
625 		a = ITERATOR_TO_TYPE(it, atkdef_c*);
626 		if (DDF_CompareName(a->name.c_str(), refname) == 0)
627 			return a;
628 	}
629 
630 	return NULL;
631 }
632 
633 //--- editor settings ---
634 // vi:ts=4:sw=4:noexpandtab
635