1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10 #include "fireball/fireballs.h"
11 #include "asteroid/asteroid.h"
12 #include "cmdline/cmdline.h"
13 #include "gamesnd/gamesnd.h"
14 #include "graphics/tmapper.h"
15 #include "localization/localize.h"
16 #include "model/model.h"
17 #include "nebula/neb.h"
18 #include "object/object.h"
19 #include "options/Option.h"
20 #include "parse/parselo.h"
21 #include "render/3d.h"
22 #include "render/batching.h"
23 #include "ship/ship.h"
24 #include "tracing/Monitor.h"
25
26 #include <cstdlib>
27
28
29 int Knossos_warp_ani_used;
30
31 #define WARPHOLE_GROW_TIME (2.35f) // time for warphole to reach max size (also time to shrink to nothing once it begins to shrink)
32
33 #define MAX_WARP_LOD 0
34
35 constexpr int INTITIAL_FIREBALL_CONTAINTER_SIZE = 256;
36
37 SCP_vector<fireball> Fireballs;
38 SCP_vector<int> Unused_fireball_indices;
39
40 fireball_info Fireball_info[MAX_FIREBALL_TYPES];
41
42 int fireball_used[MAX_FIREBALL_TYPES];
43
44 int Num_fireball_types = 0;
45
46 bool fireballs_inited = false;
47 bool fireballs_parsed = false;
48
49 bool Fireball_use_3d_warp = false;
50
51 static auto WarpOption = options::OptionBuilder<bool>("Graphics.3dWarp", "3D Warp", "Use a 3D model for warp effects")
52 .category("Graphics")
53 .default_val(true)
54 .level(options::ExpertLevel::Advanced)
55 .bind_to(&Fireball_use_3d_warp)
56 .importance(65)
57 .finish();
58
59 /**
60 * Play warp in sound for warp effect
61 */
fireball_play_warphole_open_sound(int ship_class,fireball * fb)62 void fireball_play_warphole_open_sound(int ship_class, fireball *fb)
63 {
64 gamesnd_id sound_index;
65 float range_multiplier = 1.0f;
66 object *fireball_objp;
67
68 Assert((fb != NULL) && (fb->objnum >= 0));
69 if((fb == NULL) || (fb->objnum < 0)){
70 return;
71 }
72 fireball_objp = &Objects[fb->objnum];
73
74 sound_index = gamesnd_id(GameSounds::WARP_IN);
75
76 if(fb->warp_open_sound_index.isValid()) {
77 sound_index = fb->warp_open_sound_index;
78 } else if ((ship_class >= 0) && (ship_class < ship_info_size())) {
79 if ( Ship_info[ship_class].is_huge_ship() ) {
80 sound_index = gamesnd_id(GameSounds::CAPITAL_WARP_IN);
81 fb->flags |= FBF_WARP_CAPITAL_SIZE;
82 } else if ( Ship_info[ship_class].is_big_ship() ) {
83 range_multiplier = 6.0f;
84 fb->flags |= FBF_WARP_CRUISER_SIZE;
85 }
86 }
87
88 snd_play_3d(gamesnd_get_game_sound(sound_index), &fireball_objp->pos, &Eye_position, fireball_objp->radius, NULL, 0, 1.0f, SND_PRIORITY_DOUBLE_INSTANCE, NULL, range_multiplier); // play warp sound effect
89 }
90
91 /**
92 * Play warp out sound for warp effect
93 */
fireball_play_warphole_close_sound(fireball * fb)94 void fireball_play_warphole_close_sound(fireball *fb)
95 {
96 gamesnd_id sound_index;
97
98 object *fireball_objp;
99
100 fireball_objp = &Objects[fb->objnum];
101
102 sound_index = gamesnd_id(GameSounds::WARP_OUT);
103
104 if ( fb->warp_close_sound_index.isValid() ) {
105 sound_index = fb->warp_close_sound_index;
106 } else if ( fb->flags & FBF_WARP_CAPITAL_SIZE ) {
107 sound_index = gamesnd_id(GameSounds::CAPITAL_WARP_OUT);
108 } else {
109 return;
110 }
111
112 snd_play_3d(gamesnd_get_game_sound(sound_index), &fireball_objp->pos, &Eye_position, fireball_objp->radius); // play warp sound effect
113 }
114
fireball_generate_unique_id(char * unique_id,int buffer_len,int fireball_index)115 static void fireball_generate_unique_id(char *unique_id, int buffer_len, int fireball_index)
116 {
117 Assert((fireball_index >= 0) && (fireball_index < MAX_FIREBALL_TYPES));
118
119 switch (fireball_index)
120 {
121 // use sensible names for the fireball.tbl default entries
122 case FIREBALL_EXPLOSION_MEDIUM:
123 strncpy(unique_id, "Medium Explosion", buffer_len);
124 break;
125
126 case FIREBALL_WARP:
127 strncpy(unique_id, "Warp Effect", buffer_len);
128 break;
129
130 case FIREBALL_KNOSSOS:
131 strncpy(unique_id, "Knossos Effect", buffer_len);
132 break;
133
134 case FIREBALL_ASTEROID:
135 strncpy(unique_id, "Asteroid Explosion", buffer_len);
136 break;
137
138 case FIREBALL_EXPLOSION_LARGE1:
139 strncpy(unique_id, "Large Explosion 1", buffer_len);
140 break;
141
142 case FIREBALL_EXPLOSION_LARGE2:
143 strncpy(unique_id, "Large Explosion 2", buffer_len);
144 break;
145
146 // base the id on the index
147 default:
148 snprintf(unique_id, buffer_len, "Custom Fireball %d", fireball_index - NUM_DEFAULT_FIREBALLS + 1);
149 break;
150 }
151
152 // null-terminate
153 unique_id[buffer_len - 1] = '\0';
154 }
155
156 /**
157 * Set default colors for each explosion type (original values from object.cpp)
158 */
fireball_set_default_color(int idx)159 static void fireball_set_default_color(int idx)
160 {
161 Assert((idx >= 0) && (idx < MAX_FIREBALL_TYPES));
162
163 switch (idx)
164 {
165 case FIREBALL_EXPLOSION_LARGE1:
166 case FIREBALL_EXPLOSION_LARGE2:
167 case FIREBALL_EXPLOSION_MEDIUM:
168 case FIREBALL_ASTEROID:
169 Fireball_info[idx].exp_color[0] = 1.0f;
170 Fireball_info[idx].exp_color[1] = 0.5f;
171 Fireball_info[idx].exp_color[2] = 0.125f;
172 break;
173
174 case FIREBALL_WARP:
175 Fireball_info[idx].exp_color[0] = 0.75f;
176 Fireball_info[idx].exp_color[1] = 0.75f;
177 Fireball_info[idx].exp_color[2] = 1.0f;
178 break;
179
180
181 case FIREBALL_KNOSSOS:
182 Fireball_info[idx].exp_color[0] = 0.75f;
183 Fireball_info[idx].exp_color[1] = 1.0f;
184 Fireball_info[idx].exp_color[2] = 0.75f;
185 break;
186
187 default:
188 Fireball_info[idx].exp_color[0] = 1.0f;
189 Fireball_info[idx].exp_color[1] = 1.0f;
190 Fireball_info[idx].exp_color[2] = 1.0f;
191 break;
192 }
193 }
194
fireball_set_default_warp_attributes(int idx)195 static void fireball_set_default_warp_attributes(int idx)
196 {
197 Assert((idx >= 0) && (idx < MAX_FIREBALL_TYPES));
198
199 switch (idx)
200 {
201 case FIREBALL_WARP:
202 case FIREBALL_KNOSSOS:
203 strcpy_s(Fireball_info[idx].warp_glow, "warpglow01");
204 strcpy_s(Fireball_info[idx].warp_ball, "warpball01");
205 strcpy_s(Fireball_info[idx].warp_model, "warp.pof");
206 break;
207 }
208 }
209
fireball_info_clear(fireball_info * fb)210 void fireball_info_clear(fireball_info *fb)
211 {
212 Assert(fb != nullptr);
213 memset(fb, 0, sizeof(fireball_info));
214
215 for (int i = 0; i < MAX_FIREBALL_LOD; ++i)
216 fb->lod[i].bitmap_id = -1;
217
218 fb->warp_glow_bitmap = -1;
219 fb->warp_ball_bitmap = -1;
220 fb->warp_model_id = -1;
221 }
222
fireball_info_lookup(const char * unique_id)223 int fireball_info_lookup(const char *unique_id)
224 {
225 for (int i = 0; i < Num_fireball_types; ++i)
226 if (!stricmp(Fireball_info[i].unique_id, unique_id))
227 return i;
228
229 return -1;
230 }
231
232 /**
233 * Parse fireball tbl
234 */
parse_fireball_tbl(const char * table_filename)235 static void parse_fireball_tbl(const char *table_filename)
236 {
237 try
238 {
239 read_file_text(table_filename, CF_TYPE_TABLES);
240 reset_parse();
241
242 required_string("#Start");
243
244 while (required_string_one_of(3, "#End", "$Name:", "$Unique ID:"))
245 {
246 fireball_info *fi;
247 int existing_idx = -1;
248 char unique_id[NAME_LENGTH];
249 char fireball_filename[MAX_FILENAME_LEN];
250
251 // unique ID, because indexes are unpredictable
252 memset(unique_id, 0, NAME_LENGTH);
253 if (optional_string("$Unique ID:"))
254 stuff_string(unique_id, F_NAME, NAME_LENGTH);
255
256 // base filename
257 required_string("$Name:");
258 stuff_string(fireball_filename, F_NAME, MAX_FILENAME_LEN);
259
260 // find out if we are overriding a previous entry;
261 // per precedent, these strings should only be in TBMs
262 // UNLIKE precedent, we can now add fireballs in modular tables, not just replace them
263 if (Parsing_modular_table)
264 {
265 if (optional_string("+Explosion_Medium"))
266 existing_idx = FIREBALL_EXPLOSION_MEDIUM;
267 else if (optional_string("+Warp_Effect"))
268 existing_idx = FIREBALL_WARP;
269 else if (optional_string("+Knossos_Effect"))
270 existing_idx = FIREBALL_KNOSSOS;
271 else if (optional_string("+Asteroid"))
272 existing_idx = FIREBALL_ASTEROID;
273 else if (optional_string("+Explosion_Large1"))
274 existing_idx = FIREBALL_EXPLOSION_LARGE1;
275 else if (optional_string("+Explosion_Large2"))
276 existing_idx = FIREBALL_EXPLOSION_LARGE2;
277 else if (optional_string("+Custom_Fireball"))
278 stuff_int(&existing_idx);
279
280 // we can ALSO override a previous entry by specifying a previously used unique ID
281 // either way will work, but if both are specified, unique ID takes precedence
282 if (strlen(unique_id) > 0)
283 {
284 int temp_idx = fireball_info_lookup(unique_id);
285 if (temp_idx >= 0)
286 existing_idx = temp_idx;
287 }
288 }
289
290 bool first_time;
291 // now select our entry accordingly...
292 // are we using a previous entry?
293 if (existing_idx >= 0)
294 {
295 fi = &Fireball_info[existing_idx];
296 first_time = false;
297 }
298 // we are creating a new entry, so set some defaults
299 else
300 {
301 // make sure we don't exceed the max
302 if (Num_fireball_types >= MAX_FIREBALL_TYPES)
303 {
304 error_display(0, "Too many fireball entries! Max is %d", MAX_FIREBALL_TYPES);
305 return;
306 }
307
308 fi = &Fireball_info[Num_fireball_types];
309 fireball_info_clear(fi);
310
311 // If the table didn't specify a unique ID, generate one. This will be assigned a few lines later.
312 if (strlen(unique_id) == 0)
313 fireball_generate_unique_id(unique_id, NAME_LENGTH, Num_fireball_types);
314
315 // Set remaining fireball defaults
316 fireball_set_default_color(Num_fireball_types);
317 fireball_set_default_warp_attributes(Num_fireball_types);
318
319 Num_fireball_types++;
320 first_time = true;
321 }
322
323 // copy over what we already parsed
324 if (strlen(unique_id) > 0)
325 strcpy_s(fi->unique_id, unique_id);
326 strcpy_s(fi->lod[0].filename, fireball_filename);
327
328
329 // Do we have a LOD num?
330 if (optional_string("$LOD:"))
331 {
332 stuff_int(&fi->lod_count);
333
334 if (fi->lod_count > MAX_FIREBALL_LOD)
335 fi->lod_count = MAX_FIREBALL_LOD;
336 } else if (first_time) {
337 //assume a LOD of at least 1
338 fi->lod_count = 1;
339 }
340
341 // check for particular lighting color
342 if (optional_string("$Light color:"))
343 {
344 int r, g, b;
345
346 stuff_int(&r);
347 stuff_int(&g);
348 stuff_int(&b);
349
350 CLAMP(r, 0, 255);
351 CLAMP(g, 0, 255);
352 CLAMP(b, 0, 255);
353
354 fi->exp_color[0] = (r / 255.0f);
355 fi->exp_color[1] = (g / 255.0f);
356 fi->exp_color[2] = (b / 255.0f);
357 }
358
359 // check for custom warp glow
360 if (optional_string("$Warp glow:"))
361 stuff_string(fi->warp_glow, F_NAME, NAME_LENGTH);
362
363 // check for custom warp ball
364 if (optional_string("$Warp ball:"))
365 stuff_string(fi->warp_ball, F_NAME, NAME_LENGTH);
366
367 // check for custom warp model
368 if (optional_string("$Warp model:"))
369 stuff_string(fi->warp_model, F_NAME, NAME_LENGTH);
370 }
371
372 required_string("#End");
373 }
374 catch (const parse::ParseException& e)
375 {
376 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", table_filename, e.what()));
377 return;
378 }
379 }
380
fireball_parse_tbl()381 void fireball_parse_tbl()
382 {
383 if (fireballs_parsed)
384 return;
385
386 // every newly parsed fireball_info will get cleared before being added
387 // must do this outside of parse_fireball_tbl because it's called twice
388 Num_fireball_types = 0;
389
390 parse_fireball_tbl("fireball.tbl");
391
392 // look for any modular tables
393 parse_modular_table(NOX("*-fbl.tbm"), parse_fireball_tbl);
394
395 // fill in extra LOD filenames
396 for (auto &fi: Fireball_info)
397 {
398 if (fi.lod_count > 1)
399 {
400 auto lod0 = fi.lod[0].filename;
401
402 for (int j = 1; j < fi.lod_count; ++j)
403 sprintf(fi.lod[j].filename, "%s_%d", lod0, j);
404 }
405 }
406
407 fireballs_parsed = true;
408 }
409
fireball_load_data()410 void fireball_load_data()
411 {
412 int i, idx;
413 fireball_info *fd;
414
415 for ( i = 0; i < Num_fireball_types; i++ ) {
416 fd = &Fireball_info[i];
417
418 for(idx=0; idx<fd->lod_count; idx++){
419 // we won't use a warp effect lod greater than MAX_WARP_LOD so don't load it either
420 if ( (i == FIREBALL_WARP) && (idx > MAX_WARP_LOD) )
421 continue;
422
423 fd->lod[idx].bitmap_id = bm_load_animation( fd->lod[idx].filename, &fd->lod[idx].num_frames, &fd->lod[idx].fps, nullptr, nullptr, true );
424 if ( fd->lod[idx].bitmap_id < 0 ) {
425 Error(LOCATION, "Could not load %s anim file\n", fd->lod[idx].filename);
426 }
427 }
428
429 if (strlen(fd->warp_glow) > 0) {
430 mprintf(("Loading warp glow '%s'\n", fd->warp_glow));
431 fd->warp_glow_bitmap = bm_load(fd->warp_glow);
432 } else {
433 fd->warp_glow_bitmap = -1;
434 }
435
436 if (strlen(fd->warp_ball) > 0) {
437 mprintf(("Loading warp ball '%s'\n", fd->warp_ball));
438 fd->warp_ball_bitmap = bm_load(fd->warp_ball);
439 } else {
440 fd->warp_ball_bitmap = -1;
441 }
442 }
443 }
444
445 // This will get called at the start of each level.
fireball_init()446 void fireball_init()
447 {
448 if ( !fireballs_inited ) {
449 // Do all the processing that happens only once
450 fireball_parse_tbl();
451 fireball_load_data();
452
453 fireballs_inited = true;
454 }
455
456 // Reset everything between levels
457 Fireballs.clear();
458 Fireballs.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
459 Unused_fireball_indices.clear();
460 Unused_fireball_indices.reserve(INTITIAL_FIREBALL_CONTAINTER_SIZE);
461
462 // Goober5000 - reset Knossos warp flag
463 Knossos_warp_ani_used = 0;
464 }
465
MONITOR(NumFireballsRend)466 MONITOR( NumFireballsRend )
467
468 /**
469 * Delete a fireball.
470 * Called by object_delete() code... do not call directly.
471 */
472 void fireball_delete( object * obj )
473 {
474 int num;
475 fireball *fb;
476
477 num = obj->instance;
478 // Make sure the new system works fine.
479 Assert(obj->instance > -1);
480 Assert(static_cast<int>(Fireballs.size()) > obj->instance);
481 fb = &Fireballs[num];
482
483 Assert( fb->objnum == OBJ_INDEX(obj));
484
485 Fireballs[num].objnum = -1;
486 Unused_fireball_indices.push_back(num);
487 }
488
489 /**
490 * Delete all active fireballs, by calling obj_delete directly.
491 */
fireball_delete_all()492 void fireball_delete_all()
493 {
494 for (auto& current_fireball : Fireballs) {
495 if ( current_fireball.objnum != -1 ) {
496 obj_delete(current_fireball.objnum);
497 }
498 }
499 }
500
fireball_set_framenum(int num)501 void fireball_set_framenum(int num)
502 {
503 int framenum;
504 fireball *fb;
505 fireball_info *fd;
506 fireball_lod *fl;
507
508 Assert(static_cast<int>(Fireballs.size()) > num);
509
510 fb = &Fireballs[num];
511 fd = &Fireball_info[Fireballs[num].fireball_info_index];
512
513 // valid lod?
514 fl = NULL;
515 if((fb->lod >= 0) && (fb->lod < fd->lod_count)){
516 fl = &Fireball_info[Fireballs[num].fireball_info_index].lod[fb->lod];
517 }
518 if(fl == NULL){
519 // argh
520 return;
521 }
522
523 if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT ) {
524 framenum = bm_get_anim_frame(fl->bitmap_id, fb->time_elapsed, 0.0f, true);
525
526 if ( fb->orient ) {
527 // warp out effect plays backwards
528 framenum = fl->num_frames-framenum-1;
529 fb->current_bitmap = fl->bitmap_id + framenum;
530 } else {
531 fb->current_bitmap = fl->bitmap_id + framenum;
532 }
533 } else {
534 // ignore setting of OF_SHOULD_BE_DEAD, see fireball_process_post
535 framenum = bm_get_anim_frame(fl->bitmap_id, fb->time_elapsed, fb->total_time);
536 fb->current_bitmap = fl->bitmap_id + framenum;
537 }
538 }
539
fireball_is_perishable(object * obj)540 int fireball_is_perishable(object * obj)
541 {
542 // return 1;
543 int num, objnum;
544 fireball *fb;
545
546 num = obj->instance;
547 objnum = OBJ_INDEX(obj);
548 // Make sure the new system works fine.
549 Assert(obj->instance > -1);
550 Assert((int)Fireballs.size() > obj->instance);
551 Assert( Fireballs[num].objnum == objnum );
552
553 fb = &Fireballs[num];
554
555 if ( fb->fireball_render_type == FIREBALL_MEDIUM_EXPLOSION )
556 return 1;
557
558 if ( !(fb->fireball_render_type == FIREBALL_WARP_EFFECT) ) {
559 if ( !(obj->flags[Object::Object_Flags::Was_rendered])) {
560 return 1;
561 }
562 }
563
564 return 0;
565 }
566
fireball_is_warp(object * obj)567 int fireball_is_warp(object * obj)
568 {
569 int num, objnum;
570 fireball *fb;
571
572 num = obj->instance;
573 objnum = OBJ_INDEX(obj);
574 // Make sure the new system works fine.
575 Assert(obj->instance > -1);
576 Assert(static_cast<int>(Fireballs.size()) > obj->instance);
577 Assert( Fireballs[num].objnum == objnum );
578
579 fb = &Fireballs[num];
580
581 if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT)
582 return 1;
583
584 return 0;
585 }
586
587 // maybe play sound effect for warp hole closing
fireball_maybe_play_warp_close_sound(fireball * fb)588 void fireball_maybe_play_warp_close_sound(fireball *fb)
589 {
590 float life_left;
591
592 // If not a warphole fireball, do a quick out
593 if ( !(fb->fireball_render_type == FIREBALL_WARP_EFFECT)) {
594 return;
595 }
596
597 // If the warhole close sound has been played, don't play it again!
598 if ( fb->flags & FBF_WARP_CLOSE_SOUND_PLAYED ) {
599 return;
600 }
601
602 life_left = fb->total_time - fb->time_elapsed;
603
604 if ( life_left < fb->warp_close_duration ) {
605 fireball_play_warphole_close_sound(fb);
606 fb->flags |= FBF_WARP_CLOSE_SOUND_PLAYED;
607 }
608 }
609
MONITOR(NumFireballs)610 MONITOR( NumFireballs )
611
612 void fireball_process_post(object * obj, float frame_time)
613 {
614 int num, objnum;
615 fireball *fb;
616
617 MONITOR_INC( NumFireballs, 1 );
618
619 num = obj->instance;
620 objnum = OBJ_INDEX(obj);
621 // Make sure the new system works fine.
622 Assert(obj->instance > -1);
623 Assert(static_cast<int>(Fireballs.size()) > obj->instance);
624 Assert( Fireballs[num].objnum == objnum );
625
626 fb = &Fireballs[num];
627
628 fb->time_elapsed += frame_time;
629 if ( fb->time_elapsed > fb->total_time ) {
630 obj->flags.set(Object::Object_Flags::Should_be_dead);
631 }
632
633 fireball_maybe_play_warp_close_sound(fb);
634
635 fireball_set_framenum(num);
636 }
637
638 /**
639 * Returns life left of a fireball in seconds
640 */
fireball_lifeleft(object * obj)641 float fireball_lifeleft( object *obj )
642 {
643 int num, objnum;
644 fireball *fb;
645
646 num = obj->instance;
647 objnum = OBJ_INDEX(obj);
648 // Make sure the new system works fine.
649 Assert(obj->instance > -1);
650 Assert(static_cast<int>(Fireballs.size()) > obj->instance);
651
652 Assert( Fireballs[num].objnum == objnum );
653
654 fb = &Fireballs[num];
655
656 return fb->total_time - fb->time_elapsed;
657 }
658
659 /**
660 * Returns life left of a fireball in percent
661 */
fireball_lifeleft_percent(object * obj)662 float fireball_lifeleft_percent( object *obj )
663 {
664 int num, objnum;
665 fireball *fb;
666
667 // Make sure the new system works fine.
668 Assert(obj->instance > -1);
669 Assert(static_cast<int>(Fireballs.size()) > obj->instance);
670
671 num = obj->instance;
672 objnum = OBJ_INDEX(obj);
673 Assert( Fireballs[num].objnum == objnum );
674
675 fb = &Fireballs[num];
676
677 float p = (fb->total_time - fb->time_elapsed) / fb->total_time;
678 if (p < 0)p=0.0f;
679 return p;
680 }
681
682 /**
683 * Determine LOD to use
684 */
fireball_get_lod(vec3d * pos,fireball_info * fd,float size)685 int fireball_get_lod(vec3d *pos, fireball_info *fd, float size)
686 {
687 vertex v;
688 int x, y, w, h, bm_size;
689 int must_stop = 0;
690 int ret_lod = 1;
691 int behind = 0;
692
693 // bogus
694 if(fd == NULL){
695 return 1;
696 }
697
698 // start the frame
699 extern int G3_count;
700
701 if(!G3_count){
702 g3_start_frame(1);
703 must_stop = 1;
704 }
705 g3_set_view_matrix(&Eye_position, &Eye_matrix, Eye_fov);
706
707 // get extents of the rotated bitmap
708 g3_rotate_vertex(&v, pos);
709
710 // if vertex is behind, find size if in front, then drop down 1 LOD
711 if (v.codes & CC_BEHIND) {
712 float dist = vm_vec_dist_quick(&Eye_position, pos);
713 vec3d temp;
714
715 behind = 1;
716 vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
717 g3_rotate_vertex(&v, &temp);
718
719 // if still behind, bail and go with default
720 if (v.codes & CC_BEHIND) {
721 behind = 0;
722 }
723 }
724
725 if(!g3_get_bitmap_dims(fd->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
726 if (Detail.hardware_textures == 4) {
727 // straight LOD
728 if(w <= bm_size/8){
729 ret_lod = 3;
730 } else if(w <= bm_size/2){
731 ret_lod = 2;
732 } else if(w <= (1.56*bm_size)){
733 ret_lod = 1;
734 } else {
735 ret_lod = 0;
736 }
737 } else {
738 // less aggressive LOD for lower detail settings
739 if(w <= bm_size/8){
740 ret_lod = 3;
741 } else if(w <= bm_size/3){
742 ret_lod = 2;
743 } else if(w <= (1.2*bm_size)){
744 ret_lod = 1;
745 } else {
746 ret_lod = 0;
747 }
748 }
749 }
750
751 // if it's behind, bump up LOD by 1
752 if (behind) {
753 ret_lod++;
754 }
755
756 // end the frame
757 if(must_stop){
758 g3_end_frame();
759 }
760
761 // return the best lod
762 return MIN(ret_lod, fd->lod_count - 1);
763 }
764
765 /**
766 * Create a fireball, return object index.
767 */
fireball_create(vec3d * pos,int fireball_type,int render_type,int parent_obj,float size,bool reverse,vec3d * velocity,float warp_lifetime,int ship_class,matrix * orient_override,int low_res,int extra_flags,gamesnd_id warp_open_sound,gamesnd_id warp_close_sound,float warp_open_duration,float warp_close_duration)768 int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_obj, float size, bool reverse, vec3d *velocity, float warp_lifetime, int ship_class, matrix *orient_override, int low_res, int extra_flags, gamesnd_id warp_open_sound, gamesnd_id warp_close_sound, float warp_open_duration, float warp_close_duration)
769 {
770 int n, objnum, fb_lod;
771 object *obj;
772 fireball_info *fd;
773 fireball_lod *fl;
774 Assert( fireball_type > -1 );
775 Assert( fireball_type < Num_fireball_types );
776
777 fd = &Fireball_info[fireball_type];
778
779 // check to make sure this fireball type exists
780 if (!fd->lod_count)
781 return -1;
782
783 if ( !(Game_detail_flags & DETAIL_FLAG_FIREBALLS) ) {
784 if ( !((fireball_type == FIREBALL_WARP) || (fireball_type == FIREBALL_KNOSSOS)) ) {
785 return -1;
786 }
787 }
788
789 if (Num_objects >= MAX_OBJECTS) {
790 return -1;
791 }
792
793
794 if (!Unused_fireball_indices.empty()) {
795 n = Unused_fireball_indices.back();
796 Unused_fireball_indices.pop_back();
797 }
798 else {
799 n = static_cast<int>(Fireballs.size());
800 Fireballs.emplace_back();
801 }
802
803 fireball* new_fireball = &Fireballs[n];
804
805 // get an lod to use
806 fb_lod = fireball_get_lod(pos, fd, size);
807
808 // change lod if low res is desired
809 if (low_res) {
810 fb_lod++;
811 fb_lod = MIN(fb_lod, fd->lod_count - 1);
812 }
813
814 // if this is a warpout fireball, never go higher than LOD 1
815 if(fireball_type == FIREBALL_WARP){
816 fb_lod = MAX_WARP_LOD;
817 }
818 fl = &fd->lod[fb_lod];
819
820 new_fireball->lod = (char)fb_lod;
821
822 new_fireball->flags = extra_flags;
823 new_fireball->warp_open_sound_index = warp_open_sound;
824 new_fireball->warp_close_sound_index = warp_close_sound;
825 new_fireball->warp_open_duration = (warp_open_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_open_duration;
826 new_fireball->warp_close_duration = (warp_close_duration < 0.0f) ? WARPHOLE_GROW_TIME : warp_close_duration;
827
828 matrix orient;
829 if(orient_override != NULL){
830 orient = *orient_override;
831 } else {
832 if ( parent_obj < 0 ) {
833 orient = vmd_identity_matrix;
834 } else {
835 orient = Objects[parent_obj].orient;
836 }
837 }
838
839 flagset<Object::Object_Flags> default_flags;
840 default_flags.set(Object::Object_Flags::Renders);
841 objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, default_flags, false);
842
843 obj = &Objects[objnum];
844
845 new_fireball->fireball_info_index = fireball_type;
846 new_fireball->fireball_render_type = render_type;
847 new_fireball->time_elapsed = 0.0f;
848 new_fireball->objnum = objnum;
849 new_fireball->current_bitmap = -1;
850
851 switch( new_fireball->fireball_render_type ) {
852
853 case FIREBALL_MEDIUM_EXPLOSION:
854 new_fireball->orient = Random::next() & 7; // 0 - 7
855 break;
856
857 case FIREBALL_LARGE_EXPLOSION:
858 new_fireball->orient = Random::next(360); // 0 - 359
859 break;
860
861 case FIREBALL_WARP_EFFECT:
862 // Play sound effect for warp hole opening up
863 fireball_play_warphole_open_sound(ship_class, new_fireball);
864
865 // warp in type
866 if (reverse) {
867 new_fireball->orient = 1;
868 // if warp out, then reverse the orientation
869 vm_vec_scale( &obj->orient.vec.fvec, -1.0f ); // Reverse the forward vector
870 vm_vec_scale( &obj->orient.vec.rvec, -1.0f ); // Reverse the right vector
871 } else {
872 new_fireball->orient = 0;
873 }
874 break;
875
876 default:
877 UNREACHABLE("Bad type set in fireball_create");
878 break;
879 }
880
881 if ( new_fireball->fireball_render_type == FIREBALL_WARP_EFFECT ) {
882 Assert( warp_lifetime >= 4.0f ); // Warp lifetime must be at least 4 seconds!
883 if ( warp_lifetime < 4.0f )
884 warp_lifetime = 4.0f;
885 new_fireball->total_time = warp_lifetime; // in seconds
886 } else {
887 new_fireball->total_time = i2fl(fl->num_frames) / fl->fps; // in seconds
888 }
889
890 fireball_set_framenum(n);
891
892 if ( velocity ) {
893 // Make the explosion move at a constant velocity.
894 obj->flags.set(Object::Object_Flags::Physics);
895 obj->phys_info.mass = 1.0f;
896 obj->phys_info.side_slip_time_const = 0.0f;
897 obj->phys_info.rotdamp = 0.0f;
898 obj->phys_info.vel = *velocity;
899 obj->phys_info.max_vel = *velocity;
900 obj->phys_info.desired_vel = *velocity;
901 obj->phys_info.speed = vm_vec_mag(velocity);
902 vm_vec_zero(&obj->phys_info.max_rotvel);
903 }
904
905 return objnum;
906 }
907
908 /**
909 * Called at game shutdown to clean up the fireball system
910 */
fireball_close()911 void fireball_close()
912 {
913 if ( !fireballs_inited )
914 return;
915
916 fireball_delete_all();
917 }
918
fireballs_page_in()919 void fireballs_page_in()
920 {
921 int i, idx;
922 fireball_info *fd;
923
924 for ( i = 0; i < Num_fireball_types; i++ ) {
925 fd = &Fireball_info[i];
926
927 if((i < NUM_DEFAULT_FIREBALLS) || fireball_used[i]) {
928 // if this is a Knossos ani, only load if Knossos_warp_ani_used is true
929 if ( (i == FIREBALL_KNOSSOS) && !Knossos_warp_ani_used)
930 continue;
931
932 for(idx=0; idx<fd->lod_count; idx++) {
933 // we won't use a warp effect lod greater than MAX_WARP_LOD so don't load it either
934 if ( (i == FIREBALL_WARP) && (idx > MAX_WARP_LOD) )
935 continue;
936
937 bm_page_in_texture( fd->lod[idx].bitmap_id, fd->lod[idx].num_frames );
938 }
939 }
940
941 // page in glow and ball bitmaps, if we have any
942 bm_page_in_texture(fd->warp_glow_bitmap);
943 bm_page_in_texture(fd->warp_ball_bitmap);
944
945 // load the warp model, if we have one
946 if (strlen(fd->warp_model) > 0 && cf_exists_full(fd->warp_model, CF_TYPE_MODELS)) {
947 mprintf(("Loading warp model '%s'\n", fd->warp_model));
948 fd->warp_model_id = model_load(fd->warp_model, 0, nullptr, 0);
949 } else {
950 fd->warp_model_id = -1;
951 }
952 }
953 }
954
fireball_get_color(int idx,float * red,float * green,float * blue)955 void fireball_get_color(int idx, float *red, float *green, float *blue)
956 {
957 Assert( red && blue && green );
958
959 if ( (idx < 0) || (idx >= Num_fireball_types) ) {
960 Int3();
961
962 *red = 1.0f;
963 *green = 1.0f;
964 *blue = 1.0f;
965
966 return;
967 }
968
969 fireball_info *fbi = &Fireball_info[idx];
970
971 *red = fbi->exp_color[0];
972 *green = fbi->exp_color[1];
973 *blue = fbi->exp_color[2];
974 }
975
fireball_ship_explosion_type(ship_info * sip)976 int fireball_ship_explosion_type(ship_info *sip)
977 {
978 Assert( sip != NULL );
979
980 int index = -1;
981 int ship_fireballs = (int)sip->explosion_bitmap_anims.size();
982 int objecttype_fireballs = -1;
983
984 if (sip->class_type >= 0) {
985 objecttype_fireballs = (int)Ship_types[sip->class_type].explosion_bitmap_anims.size();
986 }
987
988 if(ship_fireballs > 0){
989 index = sip->explosion_bitmap_anims[Random::next(ship_fireballs)];
990 } else if(objecttype_fireballs > 0){
991 index = Ship_types[sip->class_type].explosion_bitmap_anims[Random::next(objecttype_fireballs)];
992 }
993
994 return index;
995 }
996
fireball_asteroid_explosion_type(asteroid_info * aip)997 int fireball_asteroid_explosion_type(asteroid_info *aip)
998 {
999 Assert( aip != NULL );
1000
1001 if (aip->explosion_bitmap_anims.empty())
1002 return -1;
1003
1004 int index = -1;
1005 int roid_fireballs = (int)aip->explosion_bitmap_anims.size();
1006
1007 if (roid_fireballs > 0) {
1008 index = aip->explosion_bitmap_anims[Random::next(roid_fireballs)];
1009 }
1010
1011 return index;
1012 }
1013
fireball_wormhole_intensity(fireball * fb)1014 float fireball_wormhole_intensity(fireball *fb)
1015 {
1016 float t = fb->time_elapsed;
1017 float rad;
1018
1019 if ( t < fb->warp_open_duration ) {
1020 rad = (float)pow(t / fb->warp_open_duration, 0.4f);
1021 } else if ( t < fb->total_time - fb->warp_close_duration ) {
1022 rad = 1.0f;
1023 } else {
1024 rad = (float)pow((fb->total_time - t) / fb->warp_close_duration, 0.4f);
1025 }
1026 return rad;
1027 }
1028
1029 extern void warpin_queue_render(model_draw_list *scene, object *obj, matrix *orient, vec3d *pos, int texture_bitmap_num, float radius, float life_percent, float max_radius, bool warp_3d, int warp_glow_bitmap, int warp_ball_bitmap, int warp_model_id);
1030
fireball_render(object * obj,model_draw_list * scene)1031 void fireball_render(object* obj, model_draw_list *scene)
1032 {
1033 int num;
1034 vertex p;
1035 fireball *fb;
1036
1037 MONITOR_INC( NumFireballsRend, 1 );
1038
1039 // Make sure the new system works fine.
1040 Assert(obj->instance > -1);
1041 Assert(static_cast<int>(Fireballs.size()) > obj->instance);
1042
1043 num = obj->instance;
1044 fb = &Fireballs[num];
1045
1046 if ( fb->current_bitmap < 0 )
1047 return;
1048
1049 float alpha = 1.0f;
1050
1051 if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_fireballs)
1052 alpha *= neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_FIREBALL(obj->radius));
1053
1054 g3_transfer_vertex(&p, &obj->pos);
1055
1056 switch ( fb->fireball_render_type ) {
1057
1058 case FIREBALL_MEDIUM_EXPLOSION: {
1059 batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius, alpha);
1060 }
1061 break;
1062
1063 case FIREBALL_LARGE_EXPLOSION: {
1064 // Make the big explosions rotate with the viewer.
1065 batching_add_volume_bitmap_rotated(fb->current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius, alpha);
1066 }
1067 break;
1068
1069 case FIREBALL_WARP_EFFECT: {
1070 float percent_life = fb->time_elapsed / fb->total_time;
1071 float rad = obj->radius * fireball_wormhole_intensity(fb);
1072
1073 fireball_info *fi = &Fireball_info[fb->fireball_info_index];
1074 warpin_queue_render(scene, obj, &obj->orient, &obj->pos, fb->current_bitmap, rad, percent_life, obj->radius, (fb->flags & FBF_WARP_3D) != 0, fi->warp_glow_bitmap, fi->warp_ball_bitmap, fi->warp_model_id);
1075 }
1076 break;
1077
1078
1079 default:
1080 Int3();
1081 }
1082 }
1083
1084 // Because fireballs are only added and removed in two places, and Unused_fireball_indices is always updated in those places to contain unused indices,
1085 // this very simple code will give you the correct count of currently existing fireballs in use.
fireball_get_count()1086 int fireball_get_count()
1087 {
1088 int count = static_cast<int>(Fireballs.size()) - static_cast<int>(Unused_fireball_indices.size());
1089
1090 Assert (count >= 0);
1091
1092 return count;
1093 }
1094