1
2 #include "nx.h"
3 #include "endgame/island.h"
4 #include "endgame/credits.h"
5 #include "intro/intro.h"
6 #include "intro/title.h"
7 #include "pause/pause.h"
8 #include "pause/options.h"
9 #include "inventory.h"
10 #include "map_system.h"
11 #include "game.h"
12 #include "profile.h"
13 #include "game.fdh"
14
15 static struct TickFunctions
16 {
17 void (*OnTick)(void);
18 bool (*OnEnter)(int param);
19 void (*OnExit)(void);
20 }
21 tickfunctions[] =
22 {
23 NULL, NULL, NULL, // GM_NONE
24 game_tick_normal, NULL, NULL, // GM_NORMAL
25 inventory_tick, inventory_init, NULL, // GM_INVENTORY
26 ms_tick, ms_init, ms_close, // GM_MAP_SYSTEM
27 island_tick, island_init, NULL, // GM_ISLAND
28 credit_tick, credit_init, credit_close, // GM_CREDITS
29 intro_tick, intro_init, NULL, // GM_INTRO
30 title_tick, title_init, NULL, // GM_TITLE
31 pause_tick, pause_init, NULL, // GP_PAUSED
32 options_tick, options_init, options_close // GP_OPTIONS
33 //old_options_tick, old_options_init, old_options_close // GP_OPTIONS
34 };
35
36 Object *onscreen_objects[MAX_OBJECTS];
37 int nOnscreenObjects;
38
39 Game game;
40 TextBox textbox;
41 ObjProp objprop[OBJ_LAST];
42
43 // init Game object: only called once during startup
init()44 bool Game::init()
45 {
46 int i;
47
48 memset(&game, 0, sizeof(game));
49
50 // set default properties
51 memset(objprop, 0, sizeof(objprop));
52 for(i=0;i<OBJ_LAST;i++)
53 {
54 objprop[i].shaketime = 16;
55 objprop[i].sprite = SPR_NULL;
56 }
57
58 AssignSprites(); // auto-generated function to assign sprites to objects
59 AssignExtraSprites(); // assign rest of sprites (to be replaced at some point)
60
61 if (ai_init()) return 1; // setup function pointers to AI routines
62
63 if (initslopetable()) return 1;
64 if (initmapfirsttime()) return 1;
65
66 // create the player object--note that the player is NOT destroyed on map change
67 if (game.createplayer()) return 1;
68
69 return 0;
70 }
71
72
73 // reset things to prepare for entry to the next stage
initlevel()74 bool Game::initlevel()
75 {
76 Carets::DestroyAll(); // delete smoke clouds, ZZzz's etc...
77 ScreenEffects::Stop(); // prevents white flash after island scene when ballos defeated
78
79 game.frozen = false;
80 game.bossbar.object = NULL;
81 nOnscreenObjects = 0;
82
83 if (statusbar_init()) return 1; // reset his displayed health value
84 InitPlayer();
85 initmap();
86
87 game.stageboss.SetType(stages[game.curmap].bossNo);
88 game.stageboss.OnMapEntry();
89
90 map_scroll_jump(player->CenterX(), player->CenterY());
91
92 if (game.switchstage.eventonentry)
93 {
94 // this prevents a glitch otherwise caused by entry script to Last Cave.
95 // i.e. the script immediately <PRI's then fades in while the game is still
96 // frozen, thus the player code never has a chance to set the initial frame.
97 PHandleAttributes();
98 PSelectFrame();
99
100 NX_LOG("-- Starting on-entry script %d\n", game.switchstage.eventonentry);
101 StartScript(game.switchstage.eventonentry);
102 game.switchstage.eventonentry = 0;
103 }
104
105 return 0;
106 }
107
createplayer()108 bool Game::createplayer()
109 {
110 if (player)
111 {
112 NX_ERR("game.createplayer: player already exists!\n");
113 return 1;
114 }
115
116 player = (Player *)CreateObject(0, 0, OBJ_PLAYER);
117 PInitFirstTime();
118
119 return 0;
120 }
121
122
close(void)123 void Game::close(void)
124 {
125 // call any onexit/cleanup function for the current mode
126 setmode(GM_NONE);
127
128 Objects::DestroyAll(true); // destroy all objects and player
129 FloatText::DeleteAll();
130 }
131
132 /*
133 void c------------------------------() {}
134 */
135
setmode(int newmode,int param,bool force)136 bool Game::setmode(int newmode, int param, bool force)
137 {
138 if (newmode == 0)
139 newmode = GM_NORMAL;
140
141 if (game.mode == newmode && !force)
142 return 0;
143
144 NX_LOG("Setting tick function to type %d param %d\n", newmode, param);
145
146 if (tickfunctions[game.mode].OnExit)
147 tickfunctions[game.mode].OnExit();
148
149 game.mode = newmode;
150
151 if (tickfunctions[game.mode].OnEnter)
152 {
153 if (tickfunctions[game.mode].OnEnter(param))
154 {
155 NX_ERR("game.setmode: initilization failed for mode %d\n", newmode);
156 game.mode = GM_NONE;
157 return 1;
158 }
159 }
160
161 return 0;
162 }
163
pause(int pausemode,int param)164 bool Game::pause(int pausemode, int param)
165 {
166 if (game.paused == pausemode)
167 return 0;
168
169 NX_LOG("Setting pause: type %d param %d\n", pausemode, param);
170
171 if (tickfunctions[game.paused].OnExit)
172 tickfunctions[game.paused].OnExit();
173
174 game.paused = pausemode;
175
176 if (tickfunctions[game.paused].OnEnter)
177 {
178 if (tickfunctions[game.paused].OnEnter(param))
179 {
180 NX_ERR("game.pause: initilization failed for mode %d\n", pausemode);
181 game.paused = 0;
182 return 1;
183 }
184 }
185
186 if (!game.paused)
187 memset(inputs, 0, sizeof(inputs));
188
189 return 0;
190 }
191
tick(void)192 void Game::tick(void)
193 {
194 if (game.paused)
195 {
196 tickfunctions[game.paused].OnTick();
197 }
198 else
199 {
200 // run scripts
201 RunScripts();
202
203 // call the tick function for the current game mode
204 tickfunctions[game.mode].OnTick();
205 }
206 }
207
208
switchmap(int mapno,int scriptno,int px,int py)209 void Game::switchmap(int mapno, int scriptno, int px, int py)
210 {
211 game.switchstage.mapno = mapno;
212 game.switchstage.playerx = px;
213 game.switchstage.playery = py;
214 game.switchstage.eventonentry = scriptno;
215 }
216
217
reset()218 void Game::reset()
219 {
220 memset(inputs, 0, sizeof(inputs));
221 StopLoopSounds();
222 StopScripts();
223
224 game.pause(false);
225 game.setmode(GM_INTRO, 0, true);
226 }
227
228 /*
229 void c------------------------------() {}
230 */
231
232 // standard in-game tick (as opposed to title-screen, inventory etc)
game_tick_normal(void)233 void game_tick_normal(void)
234 {
235 Object *o;
236
237 player->riding = NULL;
238 player->bopped_object = NULL;
239 Objects::UpdateBlockStates();
240
241 if (!game.frozen)
242 {
243 // run AI for player and stageboss first
244 HandlePlayer();
245 game.stageboss.Run();
246
247 // now objects AI and move all objects to their new positions
248 Objects::RunAI();
249 Objects::PhysicsSim();
250
251 // run the "aftermove" AI routines
252 HandlePlayer_am();
253 game.stageboss.RunAftermove();
254
255 FOREACH_OBJECT(o)
256 {
257 if (!o->deleted)
258 o->OnAftermove();
259 }
260 }
261
262 // important to put this before and not after DrawScene(), or non-existant objects
263 // can wind up in the onscreen_objects[] array, and blow up the program on the next tick.
264 Objects::CullDeleted();
265
266 map_scroll_do();
267
268 DrawScene();
269 DrawStatusBar();
270 fade.Draw();
271
272 niku_run();
273 if (player->equipmask & EQUIP_NIKUMARU)
274 niku_draw(game.counter);
275
276 textbox.Draw();
277
278 ScreenEffects::Draw();
279 map_draw_map_name(); // stage name overlay as on entry
280 }
281
282
283 // shake screen.
quake(int quaketime,int snd)284 void quake(int quaketime, int snd)
285 {
286 if (game.quaketime < quaketime)
287 game.quaketime = quaketime;
288
289 if (snd)
290 sound((snd != -1) ? snd : SND_QUAKE);
291 }
292
293 // during Ballos fight, since there's already a perpetual quake,
294 // we need to be able to make an even BIGGER quake effect.
megaquake(int quaketime,int snd)295 void megaquake(int quaketime, int snd)
296 {
297 if (game.megaquaketime < quaketime)
298 {
299 game.megaquaketime = quaketime;
300 if (game.quaketime < game.megaquaketime)
301 game.quaketime = game.megaquaketime;
302 }
303
304 if (snd)
305 sound((snd != -1) ? snd : SND_QUAKE);
306 }
307
308
DrawScene(void)309 void DrawScene(void)
310 {
311 int scr_x, scr_y;
312
313 ClearScreen(BLACK);
314 // sporidically-used animated tile feature,
315 // e.g. water currents in Waterway
316 if (map.nmotiontiles)
317 AnimateMotionTiles();
318
319 // draw background map tiles
320 map_draw_backdrop();
321 map_draw(false);
322
323 // draw all objects following their z-order
324 nOnscreenObjects = 0;
325
326 for(Object *o = lowestobject;
327 o != NULL;
328 o = o->higher)
329 {
330 if (o == player) continue; // player drawn specially in DrawPlayer
331
332 // keep it's floattext linked with it's position
333 o->DamageText->UpdatePos(o);
334
335 // shake enemies that were just hit. when they stop shaking,
336 // start rising up how many damage they took.
337 if (o->shaketime)
338 {
339 o->display_xoff = (o->shaketime & 2) ? 1 : -1;
340 if (!--o->shaketime) o->display_xoff = 0;
341 }
342 else if (o->DamageWaiting > 0)
343 {
344 o->DamageText->AddQty(o->DamageWaiting);
345 o->DamageWaiting = 0;
346 }
347
348 // get object's onscreen position
349 scr_x = (o->x >> CSF) - (map.displayed_xscroll >> CSF);
350 scr_y = (o->y >> CSF) - (map.displayed_yscroll >> CSF);
351 scr_x -= sprites[o->sprite].frame[o->frame].dir[o->dir].drawpoint.x;
352 scr_y -= sprites[o->sprite].frame[o->frame].dir[o->dir].drawpoint.y;
353
354 // don't draw objects that are completely offscreen
355 // (+26 so floattext won't suddenly disappear on object near bottom of screen)
356 if (scr_x <= SCREEN_WIDTH && scr_y <= SCREEN_HEIGHT+26 && \
357 scr_x >= -sprites[o->sprite].w && scr_y >= -sprites[o->sprite].h)
358 {
359 if (nOnscreenObjects < MAX_OBJECTS-1)
360 {
361 onscreen_objects[nOnscreenObjects++] = o;
362 o->onscreen = true;
363 }
364 else
365 {
366 NX_ERR("%s:%d: Max Objects Overflow\n", __FILE__, __LINE__);
367 return;
368 }
369
370 if (!o->invisible && o->sprite != SPR_NULL)
371 {
372 scr_x += o->display_xoff;
373
374 if (o->clip_enable)
375 {
376 draw_sprite_clipped(scr_x, scr_y, o->sprite, o->frame, o->dir, o->clipx1, o->clipx2, o->clipy1, o->clipy2);
377 }
378 else
379 {
380 draw_sprite(scr_x, scr_y, o->sprite, o->frame, o->dir);
381 }
382 }
383 }
384 else
385 {
386 o->onscreen = false;
387 }
388 }
389
390 // draw the player
391 DrawPlayer();
392
393 // draw foreground map tiles
394 map_draw(TA_FOREGROUND);
395
396 // draw carets (always-on-top effects such as boomflash)
397 Carets::DrawAll();
398
399 // draw rising/falling water in maps like Almond
400 map_drawwaterlevel();
401
402 // draw all floattext (rising damage and XP amounts)
403 FloatText::DrawAll();
404
405 //if (game.debug.DrawBoundingBoxes) DrawBoundingBoxes();
406 //if (game.debug.debugmode) DrawAttrPoints();
407 }
408
409 /*
410 void c------------------------------() {}
411 */
412
game_load(int num)413 bool game_load(int num)
414 {
415 Profile p;
416
417 NX_LOG("game_load: loading savefile %d\n", num);
418
419 if (profile_load(GetProfileName(num), &p))
420 return 1;
421
422 return game_load(&p);
423 }
424
game_load(Profile * p)425 bool game_load(Profile *p)
426 {
427 int i;
428
429 player->hp = p->hp;
430 player->maxHealth = p->maxhp;
431
432 player->whimstar.nstars = p->num_whimstars;
433 player->equipmask = p->equipmask;
434
435 // load weapons
436 for(i=0;i<WPN_COUNT;i++)
437 {
438 player->weapons[i].hasWeapon = p->weapons[i].hasWeapon;
439 player->weapons[i].level = p->weapons[i].level;
440 player->weapons[i].xp = p->weapons[i].xp;
441 player->weapons[i].ammo = p->weapons[i].ammo;
442 player->weapons[i].maxammo = p->weapons[i].maxammo;
443 }
444
445 player->curWeapon = p->curWeapon;
446
447 // load inventory
448 memcpy(player->inventory, p->inventory, sizeof(player->inventory));
449 player->ninventory = p->ninventory;
450
451 // load flags
452 memcpy(game.flags, p->flags, sizeof(game.flags));
453
454 // load teleporter slots
455 textbox.StageSelect.ClearSlots();
456 for(i=0;i<p->num_teleslots;i++)
457 {
458 int slotno = p->teleslots[i].slotno;
459 int scriptno = p->teleslots[i].scriptno;
460
461 textbox.StageSelect.SetSlot(slotno, scriptno);
462 NX_LOG(" - Read Teleporter Slot %d: slotno=%d scriptno=%d\n", i, slotno, scriptno);
463 }
464
465 // have to load the stage last AFTER the flags are loaded because
466 // of the options to appear and disappear objects based on flags.
467 if (load_stage(p->stage)) return 1;
468 music(p->songno);
469
470 player->x = p->px;
471 player->y = p->py;
472 player->dir = p->pdir;
473 player->hide = false;
474 game.showmapnametime = 0;
475
476 return 0;
477 }
478
479
game_save(int num)480 bool game_save(int num)
481 {
482 Profile p;
483
484 NX_LOG("game_save: writing savefile %d\n", num);
485
486 if (game_save(&p))
487 return 1;
488
489 if (profile_save(GetProfileName(num), &p))
490 return 1;
491
492 return 0;
493 }
494
game_save(Profile * p)495 bool game_save(Profile *p)
496 {
497 int i;
498
499 memset(p, 0, sizeof(Profile));
500
501 p->stage = game.curmap;
502 p->songno = music_cursong();
503
504 p->px = player->x;
505 p->py = player->y;
506 p->pdir = player->dir;
507
508 p->hp = player->hp;
509 p->maxhp = player->maxHealth;
510
511 p->num_whimstars = player->whimstar.nstars;
512 p->equipmask = player->equipmask;
513
514 // save weapons
515 p->curWeapon = player->curWeapon;
516
517 for(i=0;i<WPN_COUNT;i++)
518 {
519 p->weapons[i].hasWeapon = player->weapons[i].hasWeapon;
520 p->weapons[i].level = player->weapons[i].level;
521 p->weapons[i].xp = player->weapons[i].xp;
522 p->weapons[i].ammo = player->weapons[i].ammo;
523 p->weapons[i].maxammo = player->weapons[i].maxammo;
524 }
525
526 // save inventory
527 p->ninventory = player->ninventory;
528 memcpy(p->inventory, player->inventory, sizeof(p->inventory));
529
530 // save flags
531 memcpy(p->flags, game.flags, sizeof(p->flags));
532
533 // save teleporter slots
534 for(i=0;i<NUM_TELEPORTER_SLOTS;i++)
535 {
536 int slotno, scriptno;
537 if (!textbox.StageSelect.GetSlotByIndex(i, &slotno, &scriptno))
538 {
539 p->teleslots[p->num_teleslots].slotno = slotno;
540 p->teleslots[p->num_teleslots].scriptno = scriptno;
541 p->num_teleslots++;
542 }
543 }
544
545 return 0;
546 }
547
548 /*
549 void c------------------------------() {}
550 */
551
552 // assign sprites for the objects that didn't get covered by the
553 // auto-generated spritesetup->cpp, and set some properties on the objects.
554 // This is mostly for objects where the sprite is not named the same as
555 // the object it is assigned to.
AssignExtraSprites(void)556 void AssignExtraSprites(void)
557 {
558 objprop[OBJ_PLAYER].sprite = SPR_MYCHAR;
559 objprop[OBJ_NPC_PLAYER].sprite = SPR_MYCHAR;
560 objprop[OBJ_PTELIN].sprite = SPR_MYCHAR;
561 objprop[OBJ_PTELOUT].sprite = SPR_MYCHAR;
562
563 objprop[OBJ_NULL].sprite = SPR_NULL;
564 objprop[OBJ_HVTRIGGER].sprite = SPR_NULL;
565 objprop[OBJ_BUBBLE_SPAWNER].sprite = SPR_NULL;
566 objprop[OBJ_DROPLET_SPAWNER].sprite = SPR_NULL;
567 objprop[OBJ_HEY_SPAWNER].sprite = SPR_NULL;
568 objprop[OBJ_WATERLEVEL].sprite = SPR_NULL;
569 objprop[OBJ_LAVA_DRIP_SPAWNER].sprite = SPR_NULL;
570 objprop[OBJ_RED_BAT_SPAWNER].sprite = SPR_NULL;
571 objprop[OBJ_SCROLL_CONTROLLER].sprite = SPR_NULL;
572 objprop[OBJ_DOCTOR_GHOST].sprite = SPR_NULL;
573 objprop[OBJ_FALLING_BLOCK].sprite = SPR_NULL; // set at runtime based on current map
574 objprop[OBJ_FALLING_BLOCK_SPAWNER].sprite = SPR_NULL;
575 objprop[OBJ_QUAKE].sprite = SPR_NULL;
576 objprop[OBJ_BUTE_SPAWNER].sprite = SPR_NULL;
577 objprop[OBJ_SMOKE_DROPPER].sprite = SPR_NULL;
578
579
580 objprop[OBJ_BUTE_ARROW].sprite = SPR_BUTE_ARROW_LEFT; // so spawn point is applied
581
582 objprop[OBJ_POLISHBABY].defaultnxflags |= NXFLAG_SLOW_WHEN_HURT;
583
584 objprop[OBJ_MIMIGAC1].sprite = SPR_MIMIGAC;
585 objprop[OBJ_MIMIGAC2].sprite = SPR_MIMIGAC;
586 objprop[OBJ_MIMIGAC_ENEMY].sprite = SPR_MIMIGAC;
587 objprop[OBJ_MIMIGAC_ENEMY].shaketime = 0;
588
589 objprop[OBJ_MISERY_FLOAT].sprite = SPR_MISERY;
590 objprop[OBJ_MISERY_FLOAT].damage = 1;
591 objprop[OBJ_MISERY_STAND].sprite = SPR_MISERY;
592
593 objprop[OBJ_PUPPY_WAG].sprite = SPR_PUPPY;
594 objprop[OBJ_PUPPY_BARK].sprite = SPR_PUPPY;
595 objprop[OBJ_PUPPY_CARRY].sprite = SPR_PUPPY;
596 objprop[OBJ_PUPPY_SLEEP].sprite = SPR_PUPPY_ASLEEP;
597 objprop[OBJ_PUPPY_RUN].sprite = SPR_PUPPY;
598 objprop[OBJ_PUPPY_ITEMS].sprite = SPR_PUPPY;
599
600 objprop[OBJ_BALROG_DROP_IN].sprite = SPR_BALROG;
601 objprop[OBJ_BALROG_BUST_IN].sprite = SPR_BALROG;
602
603 objprop[OBJ_CROWWITHSKULL].sprite = SPR_CROW;
604 objprop[OBJ_ARMADILLO].defaultnxflags |= (NXFLAG_FOLLOW_SLOPE | NXFLAG_SLOW_WHEN_HURT);
605 objprop[OBJ_SKULLHEAD_CARRIED].sprite = SPR_SKULLHEAD;
606
607 objprop[OBJ_TOROKO].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
608 objprop[OBJ_TOROKO_TELEPORT_IN].sprite = SPR_TOROKO;
609
610 objprop[OBJ_KING].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
611
612 objprop[OBJ_FAN_DROPLET].sprite = SPR_WATER_DROPLET;
613
614 objprop[OBJ_MGUN_TRAIL].defaultflags |= FLAG_IGNORE_SOLID;
615
616 objprop[OBJ_BLOCK_MOVEH].sprite = SPR_MOVING_BLOCK;
617 objprop[OBJ_BLOCK_MOVEV].sprite = SPR_MOVING_BLOCK;
618
619 objprop[OBJ_IRONH].shaketime = 8;
620
621 objprop[OBJ_OMEGA_BODY].shaketime = 0; // omega handles his own shaketime
622 objprop[OBJ_OMEGA_BODY].hurt_sound = SND_ENEMY_HURT_BIG;
623
624 objprop[OBJ_OMEGA_LEG].sprite = SPR_OMG_LEG_INAIR;
625 objprop[OBJ_OMEGA_STRUT].sprite = SPR_OMG_STRUT;
626
627 objprop[OBJ_OMEGA_SHOT].death_smoke_amt = 4;
628 objprop[OBJ_OMEGA_SHOT].death_sound = SND_EXPL_SMALL;
629 objprop[OBJ_OMEGA_SHOT].initial_hp = 1;
630 objprop[OBJ_OMEGA_SHOT].xponkill = 1;
631
632 objprop[OBJ_BAT_HANG].sprite = SPR_BAT;
633 objprop[OBJ_BAT_CIRCLE].sprite = SPR_BAT;
634
635 objprop[OBJ_FIREBALL1].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
636 objprop[OBJ_FIREBALL23].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
637
638 objprop[OBJ_CURLY_AI].sprite = SPR_CURLY;
639 objprop[OBJ_CURLY_AI].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
640
641 objprop[OBJ_CURLY].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
642
643 objprop[OBJ_MINICORE].hurt_sound = SND_ENEMY_HURT_COOL;
644 objprop[OBJ_CORE_CONTROLLER].hurt_sound = SND_CORE_HURT;
645
646 objprop[OBJ_CURLY_CARRIED].sprite = SPR_CURLY;
647
648 objprop[OBJ_BALROG_BOSS_RUNNING].sprite = SPR_BALROG;
649 objprop[OBJ_BALROG_BOSS_FLYING].sprite = SPR_BALROG;
650 objprop[OBJ_BALROG_BOSS_MISSILES].sprite = SPR_BALROG;
651
652 objprop[OBJ_XP].sprite = SPR_XP_SMALL;
653
654 objprop[OBJ_NPC_IGOR].sprite = SPR_IGOR;
655 objprop[OBJ_BOSS_IGOR].sprite = SPR_IGOR;
656 objprop[OBJ_BOSS_IGOR_DEFEATED].sprite = SPR_IGOR;
657 objprop[OBJ_IGOR_BALCONY].sprite = SPR_IGOR;
658
659 objprop[OBJ_X_TARGET].hurt_sound = SND_ENEMY_HURT_COOL;
660 objprop[OBJ_X_INTERNALS].shaketime = 9;
661 objprop[OBJ_X_MAINOBJECT].xponkill = 1;
662
663 objprop[OBJ_POOH_BLACK_BUBBLE].xponkill = 0;
664 objprop[OBJ_POOH_BLACK_DYING].sprite = SPR_POOH_BLACK;
665
666 objprop[OBJ_BOOSTER_FALLING].sprite = SPR_PROFESSOR_BOOSTER;
667
668 objprop[OBJ_MIMIGA_FARMER_STANDING].sprite = SPR_MIMIGA_FARMER;
669 objprop[OBJ_MIMIGA_FARMER_WALKING].sprite = SPR_MIMIGA_FARMER;
670 objprop[OBJ_DROLL_GUARD].sprite = SPR_DROLL;
671
672 objprop[OBJ_MA_PIGNON_CLONE].sprite = SPR_MA_PIGNON;
673
674 objprop[OBJ_DOCTOR_SHOT_TRAIL].sprite = SPR_DOCTOR_SHOT;
675
676 // they're still able to detect when they touch floor; etc,
677 // but we don't want say a falling one to get blocked by the ceiling.
678 objprop[OBJ_RED_ENERGY].defaultflags |= FLAG_IGNORE_SOLID;
679
680 objprop[OBJ_SUE_TELEPORT_IN].sprite = SPR_SUE;
681
682 objprop[OBJ_MISERY_BAT].sprite = SPR_ORANGE_BAT_FINAL;
683 objprop[OBJ_UD_MINICORE_IDLE].sprite = SPR_UD_MINICORE;
684
685 objprop[OBJ_WHIMSICAL_STAR].sprite = SPR_WHIMSICAL_STAR; // for bbox only, object is invisible
686 }
687
688