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