-
Notifications
You must be signed in to change notification settings - Fork 0
/
solids.cp
1855 lines (1700 loc) · 77.9 KB
/
solids.cp
1
/****************************************************************************************//* SOLIDS.CP *//****************************************************************************************//* (c) 1995 by Magnet Interactive Studios, inc. All rights reserved. *//****************************************************************************************//* Revision History: *//* 7/6/94 File first created. By Andrew Looney. *//* v1.0 8/4/94 First polished version. By Andrew Looney. *//* 8/8/94 Started making changes for next version. *//* v2.0 8/24/94 Seven level version burned onto CD-ROM. By Andrew Looney. *//* v2.1 8/29/94 Comments brought back up to date. By Andrew Looney. *//* v2.2 9/7/94 Sixteen level version. By Andrew Looney. *//* v2.3 9/23/94 Thirty level version burned onto CD-ROM. By Andrew Looney. *//* v2.4 10/19/94 Fifty level version. By Andrew Looney. *//* v2.5 10/31/94 Sixty level version burned onto CD-ROM. By Andrew Looney. *//* v3.0 10/31/94 Began switching over to real artwork. By Andrew Looney. *//* v3.1 11/11/94 COMDEX version. By Andrew Looney. *//* v3.2 12/2/94 Seventy-five level version. By Andrew Looney. *//* v3.3 12/14/94 Ack! Zombies! By Andrew Looney. *//* v3.4 12/23/94 Orange Meanies! By Andrew Looney. *//* v3.5 1/3/95 100 Levels! By Andrew Looney. *//* v3.6 1/5/95 101 Levels. By Andrew Looney. *//* v3.7 1/16/95 New, improved Brainy Seeker logic! By Andrew Looney. *//* v3.8 1/27/95 114 level version burned onto CD-ROM. By Andrew Looney. *//* v3.9 2/3/95 New CD-ROM burned for shipment to 3DO. By Andrew Looney. *//* v4.0 2/9/95 New CD-ROM featuring the big level grid. By Andrew Looney. *//* v4.1 2/22/95 New CD-ROM with rough draft of full interface. By Andrew Looney. *//* v4.2 3/17/95 The St. Patrick's Day Version. By Andrew Looney. *//* v4.3 3/20/95 The last version before the movies get added. By Andrew Looney. *//* v4.4 3/21/95 First version with movies integrated. By Andrew Looney. *//* v4.5 3/22/95 Second version with movies integrated. By Andrew Looney. *//* v4.6 3/27/95 Third version with movies integrated. By Andrew Looney. *//* v5.0 3/28/95 Newest version sent to QA. By Andrew Looney. *//* v5.1 3/28/95 Now, with Easter Eggs! By Andrew Looney. *//* v5.2 3/29/95 Could this be the final version? By Andrew Looney. *//* v5.3 3/30/95 OK, now maybe THIS is the final version! By Andrew Looney. *//* v5.4 4/3/95 Made a couple more minor changes. By Andrew Looney. *//****************************************************************************************//***************************** WHAT THIS SOFTWARE DOES ********************************** The solids class is designed to provide an easy system for managing a large number of solid objects (within the context of objects sitting on and moving around within a landscape as depicted by the landscape class). In particular, this includes: 1) sorting the objects so that they are drawn to the screen in the correct order to create the sense that objects visually obstruct other objects that they are directly in front of 2) comprehensive collision detection To understand the functions within this class, you must first understand the various data structures employed by this class. The first of these is the inanimates lookup table. Given that this class is meant tobe used in conjunction with the landscape class, we also make the assumption that the region will be populated with a collection of objects that each sit in the middle of a tile. Given this, then, we create a lookup table similar to the one in the landscape class, which specifies what type of object occupies each tile. (A value of NOTHING is acceptable.) Different populations for different levels can then easily be created justby loading up a different lookup table at the start of a round. Once the lookup table is established, we can then use the table to create all of the objects in the landscape. This is done by creating a table of CCBs and then using the table to determine the appearance of each piece in the table. Once we have a collection of object images, we can then use them to create our sorted database of solid objects. This list is the heart of this software package. The solids list is a doubly-linked list, sorted by location on the screen, with each node containing an identifier that denotes the object type, a pointer to the solid's artwork, and collision detection parameters specific to the object. Once created, this list can easily be sorted through when wishing to check for collisions, and when moving all of theobjects at once, and when drawing the objects to the screen. When one object moves relative to the others, it can easily be pulled from the list and reinserted at the correct location. And when an object is destroyed, it is simply removed from the list altogether. Another data structure of note is the trash can. For efficiency's sake, it isdesirable to have external functions keep track of pointers to specific objects in the solids list themselves, since they can then reference a specific object on the list without forcing the functions in this class to traverse the list in order to locate the object they wish to deal with. But because of this, software elements beyond the scopeof this class will have their fingers pointing at objects in this list which we cannot know about. This in turn means that we have to be careful about deleting objects on this list. We can't just throw an object away without giving the external users a chance to realize the object has been destroyed. Therefore, whenever an object is about to be destroyed, it is transferred to another linked list called the trash can. The object's artwork pointer is cleared, to indicate to anyone accessing that object that it is no longer valid. After the application software feels anyone using the solids list has had enough time to realize that an object has been transferred to the trash can, it can then call a routine to empty the trash, which will destroy an object for good by deallocating its dynamic memory. Finally, there's the anisolid structure. This is used to keep track of and deal withanimated solids. For each animated solid in the universe, there will be an entry on thesolids list describing the solid itself, and an entry on the anisolids list which keepstrack of the animation information and has a pointer to the object's entry in the solidslist. This setup enables us to find and deal with animated solids in a fast and efficientway, and without having to traverse the entire solids list just to deal with a smallnumber of animated solids. In addition to these structures, we also use a number of local, private variables. Wekeep a set of master artwork element pointers, which will either point to the master copyof a piece of artwork or be set to NULL. Rather than waste memory storing art we don'tcurrently need, we load only those art pieces we need for the given level. The value ofthe pointer tells us if the artwork element is already in memory or if it needs to beloaded. A final note is a symbol defined in the application's header file called FUDGE_FACTOR.The application that this class was created for features objects that have long shadowsstretching out from them on one side. Because these shadows are insubstantial, they sometimes look odd when they fall across other objects. So, because of this, the sortingroutine uses a fudge factor to bring an object to the front if it's directly in the path of the shadow cast by a given object (i.e. to the right and slightly behind the shadow casting object). Here's how you go about adding a new type of object: 1.) Create a new symbol for the art element (for example, RED_PYRAMID) in the big art elements list, and adjust the value of TOTAL_ART_ELEMENTS (in ICEBREAKER.H). 2.) Create a new symbol that defines the name of the art file(s) that are used for display of the object, (for example, RED_PIECE_FILE and RED_DEATH_ANIM) along with variables (for example, red_piece_cel and red_death) that will serve as the master artwork element pointers for the object. (in LANDSCAPE.H). 3.) Add initialization of the variables to BootSolids (in this file). 4.) Add a case to the switch statement in LoadArtwork to handle the new object. 5.) Add an if statement to ShutdownUnusedArtwork to handle the new object. 6.) Add an if statement to ShutdownForExit to handle the new object. 7.) Add a case to the switch statement in ChangeArt to handle the object. 8.) If the object is a BOULDER or other non-uniform object, add the custom collision detection numbers for the object to InitializeSolids. 9.) If the object is an animated object, add a call to CreateAnimatedSolid for the object in AddToList. 10.) If the object can be destroyed by contact with the dudemeyer or is fatal to the dudemeyer, add handling for this in the function Obstructed (in DUDEMEYER.CP). 11.) If the object can be destroyed by the shots the dudemeyer fires, add handling for this to the function DetectHit (in WEAPON.CP). 12.) Add a case for the new object to the switch statement in the function ArtworkMissing (in ICEBREAKER.CP). 13.) Add a case for the new object's ascii code in the parsing of level parameters in the fucntion LoadLevel (in LEVELS.CP). 14.) If the object is a BOULDER, add an exemption for the object to the calculation of the number of pyramids in the function LoadLevel (in LEVELS.CP).*****************************************************************************************//***** regular includes *****/#include "graphics.h"#include "stdio.h"#include "stdlib.h"#include "mem.h"#include "types.h"#include "hardware.h"#include "event.h"#include "strings.h"#include "access.h"#include "UMemory.h"#include "Form3DO.h"#include "Init3DO.h"#include "Parse3DO.h"#include "Utils3DO.h"#include "audio.h"#include "music.h"/***** magnet includes *****/#include "icebreaker.h"#include "animation.h"#include "solids.h"#include "seeker.h"#include "landscape.h"/***** special c++ include (this must be last) *****/#include "CPlusSwiHack.h"/***** global variables *****/extern ScreenContext g_screen;extern int32 g_total_pyramids;extern bool g_art_usage[TOTAL_ART_ELEMENTS];extern int32 level_lookup_table [ROWS_IN_LANDSCAPE] [COLUMNS_IN_LANDSCAPE];extern Item timer_request_item;extern IOInfo timer_request_info;extern struct timeval current_time;extern seeker enemies;extern landscape pavement;/******************************** solids::BootSolids *********************************** This function simply sets all of the artwork element pointer values to NULL. This isimportant because artwork is only loaded if the pointer is set to NULL, and on bootup wecan't really be certain that these pointers will be pre-initialized to the value NULL.*****************************************************************************************/void solids::BootSolids(void){ red_piece_cel = (CCB *) NULL; blue_piece_cel = (CCB *) NULL; green_piece_cel = (CCB *) NULL; purple_piece_cel = (CCB *) NULL; boulder0_cel = (CCB *) NULL; boulder1_cel = (CCB *) NULL; boulder2_cel = (CCB *) NULL; boulder3_cel = (CCB *) NULL; boulder4_cel = (CCB *) NULL; boulder5_cel = (CCB *) NULL; boulder6_cel = (CCB *) NULL; boulder7_cel = (CCB *) NULL; boulder8_cel = (CCB *) NULL; boulder9_cel = (CCB *) NULL; boulderA_cel = (CCB *) NULL; boulderD_cel = (CCB *) NULL; blue_death.anim_pointer = (ANIM *) NULL; red_death.anim_pointer = (ANIM *) NULL; purple_death.anim_pointer = (ANIM *) NULL; green_death.anim_pointer = (ANIM *) NULL; concrete_death.anim_pointer = (ANIM *) NULL; concrete_puff[0].anim_pointer = (ANIM *) NULL; concrete_puff[1].anim_pointer = (ANIM *) NULL; concrete_puff[2].anim_pointer = (ANIM *) NULL; rainbow_piece_anim.anim_pointer = (ANIM *) NULL; concrete_piece_anim.anim_pointer = (ANIM *) NULL;}/****************************** solids::LoadArtwork *********************************** This function is called whenever the software determines that it needs a given artworkelement but that this element has not been loaded into memory (i.e. the pointer to thatelement is set to NULL.) This function takes as input the symbol which uniquely identifiesthis piece of artwork, and loads it into memory. If the file could not be found, thisfunction calls the routine ArtworkMissing, which will identify the missing art and abortthe program. (It is assumed that such problems will occur only during testing.) Note that in some cases, a call to load a given piece of art may result in loadingseveral pieces of art. For example, if we are called upon to load the red pyramid art,we automatically also load the animation for the destruction of the red pyramid, sincewe know it will also be needed.*****************************************************************************************/void solids::LoadArtwork (int32 element_to_load){ switch(element_to_load) { case RED_PYRAMID: red_piece_cel = LoadCel(RED_PIECE_FILE, MEMTYPE_CEL); if (red_piece_cel == NULL) ArtworkMissing(RED_PYRAMID); if (!(red_death.LoadArtwork (RED_DEATH_ANIM))) ArtworkMissing(RED_PYRAMID); break; case BLUE_PYRAMID: blue_piece_cel = LoadCel(BLUE_PIECE_FILE, MEMTYPE_CEL); if (blue_piece_cel == NULL) ArtworkMissing(BLUE_PYRAMID); if (!(blue_death.LoadArtwork (BLUE_DEATH_ANIM))) ArtworkMissing(BLUE_PYRAMID); break; case GREEN_PYRAMID: green_piece_cel = LoadCel(GREEN_PIECE_FILE, MEMTYPE_CEL); if (green_piece_cel == NULL) ArtworkMissing(GREEN_PYRAMID); if (!(green_death.LoadArtwork (GREEN_DEATH_ANIM))) ArtworkMissing(GREEN_PYRAMID); break; case RAINBOW_PYRAMID: if (!(rainbow_piece_anim.LoadArtwork(RAINBOW_ANIM))) ArtworkMissing(RAINBOW_PYRAMID); break; case PURPLE_PYRAMID: purple_piece_cel = LoadCel(PURPLE_PIECE_FILE, MEMTYPE_CEL); if (purple_piece_cel == NULL) ArtworkMissing(PURPLE_PYRAMID); if (!(purple_death.LoadArtwork (PURPLE_DEATH_ANIM))) ArtworkMissing(PURPLE_PYRAMID); break; case BOULDER0: boulder0_cel = LoadCel(BOULDER0_FILE, MEMTYPE_CEL); if (boulder0_cel == NULL) ArtworkMissing(BOULDER0); break; case BOULDER1: boulder1_cel = LoadCel(BOULDER1_FILE, MEMTYPE_CEL); if (boulder1_cel == NULL) ArtworkMissing(BOULDER1); break; case BOULDER2: boulder2_cel = LoadCel(BOULDER2_FILE, MEMTYPE_CEL); if (boulder2_cel == NULL) ArtworkMissing(BOULDER2); break; case BOULDER3: boulder3_cel = LoadCel(BOULDER3_FILE, MEMTYPE_CEL); if (boulder3_cel == NULL) ArtworkMissing(BOULDER3); break; case BOULDER4: boulder4_cel = LoadCel(BOULDER4_FILE, MEMTYPE_CEL); if (boulder4_cel == NULL) ArtworkMissing(BOULDER4); break; case BOULDER5: boulder5_cel = LoadCel(BOULDER5_FILE, MEMTYPE_CEL); if (boulder5_cel == NULL) ArtworkMissing(BOULDER5); break; case BOULDER6: boulder6_cel = LoadCel(BOULDER6_FILE, MEMTYPE_CEL); if (boulder6_cel == NULL) ArtworkMissing(BOULDER6); break; case BOULDER7: boulder7_cel = LoadCel(BOULDER7_FILE, MEMTYPE_CEL); if (boulder7_cel == NULL) ArtworkMissing(BOULDER7); break; case BOULDER8: boulder8_cel = LoadCel(BOULDER8_FILE, MEMTYPE_CEL); if (boulder8_cel == NULL) ArtworkMissing(BOULDER8); break; case BOULDER9: boulder9_cel = LoadCel(BOULDER9_FILE, MEMTYPE_CEL); if (boulder9_cel == NULL) ArtworkMissing(BOULDER9); break; case BOULDERA: boulderA_cel = LoadCel(BOULDERA_FILE, MEMTYPE_CEL); if (boulderA_cel == NULL) ArtworkMissing(BOULDERA); break; case BOULDERD: boulderD_cel = LoadCel(BOULDERD_FILE, MEMTYPE_CEL); if (boulderD_cel == NULL) ArtworkMissing(BOULDERD); break; case CONCRETE_PYRAMID: if (!(concrete_piece_anim.LoadArtwork(CONCRETE_ANIM))) ArtworkMissing(CONCRETE_PYRAMID); if (!(concrete_death.LoadArtwork (CONCRETE_DEATH_ANIM))) ArtworkMissing(CONCRETE_PYRAMID); if (!(concrete_puff[0].LoadArtwork (CONCRETE_PUFF_1))) ArtworkMissing(CONCRETE_PYRAMID); if (!(concrete_puff[1].LoadArtwork (CONCRETE_PUFF_2))) ArtworkMissing(CONCRETE_PYRAMID); if (!(concrete_puff[2].LoadArtwork (CONCRETE_PUFF_3))) ArtworkMissing(CONCRETE_PYRAMID); break; }}/********************* solids::PlaceSolidsInStartingPositions ************************* This function is based on the assumption that users of this class will be interested in a universe of solitary solid objects each resting upon a single tile (as implemented by the landscape class). Given this, we use the same method of positioning objects onthe screen that the landscape class uses to lay out the tiles. This works fine sincewe are using the same size cel for our solid objects that we use for tiles (although this does usually leaves a lot of empty space around the object).*****************************************************************************************/void solids::PlaceSolidsInStartingPositions (void){ int32 i,j; if (red_piece_cel == NULL) LoadArtwork(RED_PYRAMID); if (blue_piece_cel == NULL) LoadArtwork(BLUE_PYRAMID); if (green_piece_cel == NULL) LoadArtwork(GREEN_PYRAMID); /* create CCBs for all static solids */ for (i = 0; i < ROWS_IN_LANDSCAPE; i++) for (j = 0; j < COLUMNS_IN_LANDSCAPE; j++) { inanimates_cel_database[i][j] = new(CCB); memcpy(inanimates_cel_database[i][j],green_piece_cel,sizeof(CCB)); } /* position the first ccb, from which all others derive */ inanimates_cel_database[0][0]->ccb_XPos = INITIAL_UPPER_LEFT_X; inanimates_cel_database[0][0]->ccb_YPos = INITIAL_UPPER_LEFT_Y; ChangeArt(inanimates_cel_database[0][0],level_lookup_table[0][0]); /* position the first ccb in each row */ for (i = 1; i < ROWS_IN_LANDSCAPE; i++) { inanimates_cel_database[i][0]->ccb_XPos = inanimates_cel_database[0][0]->ccb_XPos; inanimates_cel_database[i][0]->ccb_YPos = inanimates_cel_database[i-1][0]->ccb_YPos + (TILE_HEIGHT << 16); ChangeArt(inanimates_cel_database[i][0],level_lookup_table[i][0]); } /* Now position the other elements in each row */ for (i = 0; i < ROWS_IN_LANDSCAPE; i++) { for (j = 1; j < COLUMNS_IN_LANDSCAPE; j++) { inanimates_cel_database[i][j]->ccb_XPos = inanimates_cel_database[i][j-1]->ccb_XPos + (HALF_TILE_WIDTH << 16); if (j % 2) inanimates_cel_database[i][j]->ccb_YPos = inanimates_cel_database[i][j-1]->ccb_YPos - (HALF_TILE_HEIGHT << 16); else inanimates_cel_database[i][j]->ccb_YPos = inanimates_cel_database[i][j-1]->ccb_YPos + (HALF_TILE_HEIGHT << 16); ChangeArt(inanimates_cel_database[i][j],level_lookup_table[i][j]); } }}/**************************** solids::InitializeSolids ******************************** This function performs all of the steps needed to fully initialize the universe ofsolid objects and to set things up so that objects can easily be added to and removedfrom the universe. It is meant to be called prior to each new round that uses a set ofsolid objects.*****************************************************************************************/void solids::InitializeSolids (void) { int32 i,j; CCB *temp; solids_list = (solid_object *) NULL; trash_can = (solid_object *) NULL; anisolid_list = (anisolid *) NULL; ShutdownUnusedArtwork(); PlaceSolidsInStartingPositions(); for (i = 0; i < ROWS_IN_LANDSCAPE; i++) { for (j = 0; j < COLUMNS_IN_LANDSCAPE; j++) { if (level_lookup_table[i][j] != NOTHING) { switch (level_lookup_table[i][j]) { case BOULDER0: AddToList(inanimates_cel_database[i][j], BOULDER0, 27, 4); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 15, 11); break; case BOULDER1: AddToList(inanimates_cel_database[i][j], BOULDER1, 26, 3); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 10, 11); break; case BOULDER2: AddToList(inanimates_cel_database[i][j], BOULDER2, 27, 4); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 10, 11); break; case BOULDER3: AddToList(inanimates_cel_database[i][j], BOULDER3, 24, 3); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 10, 10); break; case BOULDER4: AddToList(inanimates_cel_database[i][j], BOULDER4, 23, 2); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 10, 10); break; case BOULDER5: AddToList(inanimates_cel_database[i][j], BOULDER5, 27, 4); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 15, 11); break; case BOULDER6: AddToList(inanimates_cel_database[i][j], BOULDER6, 27, 4); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 13, 11); break; case BOULDER7: AddToList(inanimates_cel_database[i][j], BOULDER7, 27, 4); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 10, 11); break; case BOULDER8: AddToList(inanimates_cel_database[i][j], BOULDER8, 24, 2); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 9, 10); break; case BOULDER9: AddToList(inanimates_cel_database[i][j], BOULDER9, 26, 3); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 8, 10); break; case BOULDERA: AddToList(inanimates_cel_database[i][j], BOULDERA, 27, 3); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 9, 12); break; case BOULDERD: AddToList(inanimates_cel_database[i][j], BOULDERD, 27, 4); temp = new(CCB); memcpy(temp,inanimates_cel_database[i][j],sizeof(CCB)); AddToList(temp, PHANTOM_BOULDER, 13, 10); break; default: AddToList(inanimates_cel_database[i][j], level_lookup_table[i][j], PYRAMID_COL_DETECT_X, PYRAMID_COL_DETECT_Y); break; } } } }}/******************************** solids::AddToList *********************************** As the name suggests, this function adds a new solid to the solid objects list. Doing this requires dynamically allocating memory to store the list entry. If the object is an animated solid, this function also makes the appropriate calls needed to add the objectto the animated solids list as well as the regular solids list.*****************************************************************************************/void solids::AddToList (CCB *target, int32 object_type, int32 cd_x, int32 cd_y){ register solid_object *new_solid; new_solid = new(solid_object); new_solid->cel = target; new_solid->object_type = object_type; new_solid->col_detect_x = cd_x; new_solid->col_detect_y = cd_y; new_solid->next = (solid_object *) NULL; new_solid->previous = (solid_object *) NULL; if (object_type == RAINBOW_PYRAMID) { if (rainbow_piece_anim.anim_pointer == NULL) LoadArtwork(RAINBOW_PYRAMID); CreateAnimatedSolid (&rainbow_piece_anim,STANDARD_FRAME_RATE >> (RandomNumber(0,0)), new_solid, TRUE); if (purple_piece_cel == NULL) LoadArtwork(PURPLE_PYRAMID); } if (object_type == CONCRETE_PYRAMID) { if (concrete_piece_anim.anim_pointer == NULL) LoadArtwork(CONCRETE_PYRAMID); CreateAnimatedSolid (&concrete_piece_anim, STANDARD_FRAME_RATE, new_solid, FALSE); } if (solids_list == (solid_object *) NULL) solids_list = new_solid; else InsertAtProperPointInList(new_solid);}/********************** solids::InsertAtProperPointInList ***************************** This function takes as input a pointer to a solid object record, then it figures out where in the solids list it belongs and inserts it at that point. The objects are sorted such that the first one in the list is the object farthest up and to the left on the field of solid objects. It is important to understand that all objects in the linked list must be drawn in a cel of the same size, with center of the base of the object centered within the cel.*****************************************************************************************/void solids::InsertAtProperPointInList (solid_object *new_solid){ register solid_object *traversal_ptr; traversal_ptr = solids_list; /* first, find out if this object ought to be first on the list: */ if ( (((ABS(new_solid->cel->ccb_YPos - traversal_ptr->cel->ccb_YPos)) <= FUDGE_FACTOR) && (new_solid->cel->ccb_XPos < traversal_ptr->cel->ccb_XPos)) || (new_solid->cel->ccb_YPos + FUDGE_FACTOR < traversal_ptr->cel->ccb_YPos) ) { new_solid->next = solids_list; new_solid->previous = (solid_object *) NULL; solids_list->previous = new_solid; solids_list = new_solid; return; } /* now traverse the list and insert the object in front of the first object that */ /* we encounter with x and y coordinates lower and further right on the screen */ /* than our own: */ while (traversal_ptr->next != (solid_object *) NULL) { if ( (((ABS(new_solid->cel->ccb_YPos-traversal_ptr->cel->ccb_YPos)) <= FUDGE_FACTOR) && (new_solid->cel->ccb_XPos < traversal_ptr->cel->ccb_XPos)) || (new_solid->cel->ccb_YPos + FUDGE_FACTOR < traversal_ptr->cel->ccb_YPos) ) { new_solid->next = traversal_ptr; new_solid->previous = traversal_ptr->previous; traversal_ptr->previous = new_solid; new_solid->previous->next = new_solid; return; } traversal_ptr = traversal_ptr->next; } /* if we still haven't returned, then we've reached the end of the list. The */ /* traversal_ptr is pointing to the last element in the list, but the comparison of */ /* this element to the new object has not yet been made. The question we now have to */ /* answer is, does the new object get placed before or after the final object? */ if ( (((ABS(new_solid->cel->ccb_YPos - traversal_ptr->cel->ccb_YPos)) <= FUDGE_FACTOR) && (new_solid->cel->ccb_XPos < traversal_ptr->cel->ccb_XPos)) || (new_solid->cel->ccb_YPos + FUDGE_FACTOR < traversal_ptr->cel->ccb_YPos) ) { new_solid->next = traversal_ptr; new_solid->previous = traversal_ptr->previous; traversal_ptr->previous = new_solid; new_solid->previous->next = new_solid; } else { /* add new object at end of list */ new_solid->previous = traversal_ptr; new_solid->next = (solid_object *) NULL; traversal_ptr->next = new_solid; } /* Note: Yes, there's some redundancy here... but the other choice is to keep going */ /* in the while loop until traversal_ptr == NULL, after which we would know that the */ /* new object gets placed at the end of the list, but in that case, traversal_ptr */ /* would have been set to NULL and we'd have to retraverse the entire list just to */ /* get that pointer value back. Another alternative would be to keep a second */ /* pointer going, lagging one node behind traversal_ptr, but that wastes a lot of */ /* processing time in all of the cases except for when we get near to the end of */ /* the list. So, this seemed like the best approach. */}/************************* solids::TamperWithSorting ********************************** In some cases, the way we sort objects in InsertAtProperPointInList may not quite besatisfactory. For such cases, this function was created. If two objects are not in the desired order on the list, this function will place them in the desired positionsrelative to each other.*****************************************************************************************/void solids::TamperWithSorting (solid_object *first_solid, solid_object *second_solid, bool second_first){ if (second_first) { RemoveFromList(second_solid); if (solids_list == first_solid) { solids_list = second_solid; second_solid->next = first_solid; second_solid->previous = (solid_object *) NULL; first_solid->previous = second_solid; } else { second_solid->previous = first_solid->previous; first_solid->previous = second_solid; second_solid->previous->next = second_solid; second_solid->next = first_solid; } } else { RemoveFromList(second_solid); if (first_solid->next != (solid_object *) NULL) first_solid->next->previous = second_solid; second_solid->next = first_solid->next; second_solid->previous = first_solid; first_solid->next = second_solid; }}/***************************** solids::RemoveFromList ********************************* This function removes an object from a doubly linked list of solid objects andcorrects the pointers around it to cope with the object's removal. It does not, however, deallocate the memory used by the object, nor change the object in any way.*****************************************************************************************/void solids::RemoveFromList (solid_object *target_solid){ /* if object is at head of list, adjust master list pointer */ if (target_solid == solids_list) { solids_list = target_solid->next; /* then adjust new first element, unless element being remove was last one */ if (solids_list != (solid_object *) NULL) solids_list->previous = (solid_object *) NULL; } /* now handle removing any elements other than first one */ else { target_solid->previous->next = target_solid->next; /* correct next element's previous pointer, unless there is no next element */ if (target_solid->next != (solid_object *) NULL) target_solid->next->previous = target_solid->previous; }}/************************** solids::FindPointerIntoList ******************************* This function searches through a list of solid objects until it finds an object witha cel pointer that matches the one passed in. The main purpose of this function is to allow external users to learn where their object of interest is on the solids list sothat they can refer to their own object directly, rather than allowing them to be lazyand force us to traverse the solids list more often than we need to.*****************************************************************************************/solid_object* solids::FindPointerIntoList (CCB *target){ register solid_object *traversal_ptr; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { if (traversal_ptr->cel == target) return(traversal_ptr); traversal_ptr = traversal_ptr->next; } printf("MAJOR BUMMER: target object not found by solids::FindPointerIntoList().\n"); return(traversal_ptr);}/*************************** solids::EliminateObject ********************************** There are two versions of the EliminateObject function. The first is meant for lazyusers who wish to destroy a solid object but know it only by its picture; the second is for users who are able to provide a pointer to the actual object structure that they wish to destroy. This is the first of these functions. It first calls FindPointerIntoList to get the pointer, and then it simply calls the other version of EliminateObject.*****************************************************************************************/void solids::EliminateObject (CCB *cel_to_eliminate){ solid_object *object_to_eliminate; object_to_eliminate = FindPointerIntoList(cel_to_eliminate); EliminateObject(object_to_eliminate);}/*************************** solids::EliminateObject ********************************** There are two versions of the EliminateObject function. The first is meant for lazyusers who wish to destroy a solid object but know it only by its picture; the second is for users who are able to provide a pointer to the actual object structure that they wish to destroy. This is the second of these functions. It pulls the object out of the solids list, and then alters it to indicate that the object is being destroyed, and finally places the object in the trash can.*****************************************************************************************/void solids::EliminateObject (solid_object *object_to_eliminate){ /* set the cel pointer of this object to NULL, to alert anyone who might be holding */ /* a pointer to this object to the fact that it's been destroyed, then add it */ /* to the list of objects to be thrown away: */ object_to_eliminate->cel = (CCB *) NULL; RemoveFromList(object_to_eliminate); object_to_eliminate->next = trash_can; trash_can = object_to_eliminate;}/***************************** solids::EmptyTrash ************************************* This function is the final stage in the destruction of a solid object. Assuming theobject was first transferred onto the trash can list, and then allowed to stay therelong enough for all routines that use objects externally to have executed at least once,and thereby realize the object was being destroyed, this function can then be called to remove all items on the trash can list and deallocate their memory.*****************************************************************************************/void solids::EmptyTrash (void){ solid_object *traversal_ptr,*vanguard_pointer; traversal_ptr = trash_can; while (traversal_ptr != (solid_object *) NULL) { vanguard_pointer = traversal_ptr->next; if ((traversal_ptr->object_type == PHANTOM_BOULDER)) delete(traversal_ptr->cel); delete(traversal_ptr); traversal_ptr = vanguard_pointer; } trash_can = (solid_object *) NULL;}/**************************** solids::DisplaySolids *********************************** This function traverses the entire solids list and draws everything thereon into the screen buffer indicated by the external global variable g_screen. Well, not quite everything. To save time, anything that isn't visible isn't drawn.*****************************************************************************************/void solids::DisplaySolids (void){ register solid_object *traversal_ptr; CCB *draw_me; register CCB *draw_me_next; MaintainAnimatedObjects(); draw_me = (CCB *) NULL; draw_me_next = draw_me; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { traversal_ptr->cel->ccb_NextPtr = (CCB *) NULL; traversal_ptr->cel->ccb_Flags &= ~CCB_LAST; if ((traversal_ptr->cel->ccb_XPos + (traversal_ptr->cel->ccb_Width << 16) >= 0) && (traversal_ptr->cel->ccb_XPos <= (SCREEN_WIDTH << 16)) && (traversal_ptr->cel->ccb_YPos + (traversal_ptr->cel->ccb_Height << 16) >= 0) && (traversal_ptr->cel->ccb_YPos <= (SCREEN_HEIGHT << 16)) && (traversal_ptr->object_type != PHANTOM_BOULDER)) { if (draw_me == (CCB *) NULL) { draw_me = traversal_ptr->cel; draw_me_next = draw_me; } else { draw_me_next->ccb_NextPtr = traversal_ptr->cel; draw_me_next = traversal_ptr->cel; } } traversal_ptr = traversal_ptr->next; } /* draw_me and draw_me next will be NULL if no objects are visible at all on the */ /* screen, which can happen if you destroy all pyramids on screen and fall into a */ /* pit before any new seekers reappear on screen. For this rather rare case, we need */ /* to make sure that draw_me isn't NULL. */ if (draw_me == NULL) return; draw_me_next->ccb_Flags |= CCB_LAST; DrawScreenCels(g_screen.sc_Screens[g_screen.sc_curScreen],draw_me);}/*************************** solids::FixPositionOnList ******************************** As with EliminateObject, there are two versions of this function, one for those who know the object in question only by its cel pointer, and one for those who know theobject pointer. This is the first version, and it just gets the pointer it needs andthen calls the other version.*****************************************************************************************/void solids::FixPositionOnList (CCB *target_cel){ solid_object *target; target = FindPointerIntoList(target_cel); FixPositionOnList(target);}/*************************** solids::FixPositionOnList ******************************** As with EliminateObject, there are two versions of this function, one for those who know the object in question only by its cel pointer, and one for those who know theobject pointer. This is the second version. This function is used to make sure that an object which has moved relative to other objects is still in the right spot on the linked list. If it has moved enough to be outof place on the list, then this function deletes it from the list and reinserts it where it belongs.*****************************************************************************************/void solids::FixPositionOnList (solid_object *target){ /* CASE 1: Object is at start of list */ if (target == solids_list) { if ( (((ABS(target->cel->ccb_YPos - target->next->cel->ccb_YPos)) <= FUDGE_FACTOR) && (target->cel->ccb_XPos < target->next->cel->ccb_XPos)) || (target->cel->ccb_YPos + FUDGE_FACTOR < target->next->cel->ccb_YPos) ) return; RemoveFromList(target); InsertAtProperPointInList(target); return; } /* CASE 2: Object is at end of list */ if (target->next == (solid_object *) NULL) { if ( (((ABS(target->previous->cel->ccb_YPos-target->cel->ccb_YPos)) <= FUDGE_FACTOR) && (target->previous->cel->ccb_XPos < target->cel->ccb_XPos)) || (target->previous->cel->ccb_YPos + FUDGE_FACTOR < target->cel->ccb_YPos) ) return; RemoveFromList(target); InsertAtProperPointInList(target); return; } /* CASE 3: Object is in the middle of the list */ if ( ( (((ABS(target->cel->ccb_YPos - target->next->cel->ccb_YPos)) <= FUDGE_FACTOR) && (target->cel->ccb_XPos < target->next->cel->ccb_XPos)) || (target->cel->ccb_YPos + FUDGE_FACTOR < target->next->cel->ccb_YPos) ) && ( (((ABS(target->previous->cel->ccb_YPos - target->cel->ccb_YPos)) <= FUDGE_FACTOR) && (target->previous->cel->ccb_XPos < target->cel->ccb_XPos)) || (target->previous->cel->ccb_YPos + FUDGE_FACTOR < target->cel->ccb_YPos) ) ) return; RemoveFromList(target); InsertAtProperPointInList(target);}/**************************** solids::DetectCollision ********************************* This function is a comprehensive collision detection function. It takes as input an object to be checked, along with collision detection parameters for that object, and returns either NULL, in the event that no collisions were found, or a pointer to the closest solid object that it has collided with. Note that the target object need not beon the solids list. This function has been tailored somewhat to meet the specific needs of Icebreaker. If the object in question collides with an object of a specific type, namely DEATH_SCENE,it will report this only if that was the only object that was collided with. If a more substantial object was run into, then that object will be reported instead of the DEATH_SCENE object.*****************************************************************************************/solid_object* solids::DetectCollision (CCB *target, int32 target_cd_x,int32 target_cd_y, bool exclude_dudemeyer){ register solid_object *traversal_ptr; register int32 object_center_x,object_center_y; register int32 target_center_x,target_center_y; solid_object *collision_list[MAX_DETECTABLE_COLLISIONS]; int32 collisions; int32 x_diff[2],y_diff[2]; target_center_x = FIND_CENTER_X (target); target_center_y = FIND_CENTER_Y (target); traversal_ptr = solids_list; collisions = 0; while (traversal_ptr != (solid_object *) NULL) { if (traversal_ptr->cel != target) { object_center_x = FIND_CENTER_X (traversal_ptr->cel); object_center_y = FIND_CENTER_Y (traversal_ptr->cel); if ( ((ABS(object_center_x - target_center_x)) <= traversal_ptr->col_detect_x + target_cd_x) && ((ABS(object_center_y - target_center_y)) <= traversal_ptr->col_detect_y + target_cd_y) ) { collisions++; if (collisions > MAX_DETECTABLE_COLLISIONS) { printf("Warning: trying to exceed MAX_DETECTABLE_COLLISIONS.\n"); break; } /* We only register this object as a collision in the following cases: */ /* a) it's not a death scene */ /* b) the only collision we find is a death scene */ /* c) it's the dudemeyer and the exclusion flag is not set */ if (traversal_ptr->object_type == DEATH_SCENE) { /* thus, if this is a death scene, we save it if we've got nothing */ /* else of note, otherwise we forget we ever saw it. */ if (collisions == 1) collision_list[collisions - 1] = traversal_ptr; else collisions--; } else { /* and if this isn't a death scene, but we previously had saved a */ /* death scene, we now remove the death scene from the list and put */ /* this down instead. */ if ((collisions == 2) && (collision_list[0]->object_type==DEATH_SCENE)) collisions = 1; collision_list[collisions - 1] = traversal_ptr; } if ((traversal_ptr->object_type == DUDEMEYER) && (exclude_dudemeyer)) collisions--; } } traversal_ptr = traversal_ptr->next; } if (collisions == 0) return((solid_object *) NULL); /* if more than one object was collided with, then the one to be reported is the */ /* one closest to the target object. */ traversal_ptr = collision_list[0]; for (int32 i = 1; i < collisions; i++) { x_diff[0] = (ABS(FIND_CENTER_X(traversal_ptr->cel) - target_center_x)); y_diff[0] = (ABS(FIND_CENTER_Y(traversal_ptr->cel) - target_center_y)); x_diff[1] = (ABS(FIND_CENTER_X(collision_list[i]->cel) - target_center_x)); y_diff[1] = (ABS(FIND_CENTER_Y(collision_list[i]->cel) - target_center_y)); if ((x_diff[0] > x_diff[1]) || (y_diff[0] > y_diff[1])) traversal_ptr = collision_list[i]; } return(traversal_ptr);}/**************************** solids::DetectCollision ********************************* This is an alternate version of the DetectCollision function which has been optimizedfor use when checking to see if an object that IS stored on the solids list has collidedwith another object on the solids list. It only checks as much of the solids list asneeded to determine if any collisions occurred.*****************************************************************************************/solid_object* solids::DetectCollision (solid_object *target, bool ignore_rocks){ register solid_object *traversal_ptr; register int32 object_center_x,object_center_y; register int32 target_center_x,target_center_y; solid_object *collision_list[MAX_DETECTABLE_COLLISIONS]; int32 collisions; int32 x_diff[2],y_diff[2]; target_center_x = FIND_CENTER_X (target->cel); target_center_y = FIND_CENTER_Y (target->cel); collisions = 0; /* we start by checking against all objects upstream of the target object, stopping */ /* when we get to an object that is out of range along both axes. */ traversal_ptr = target->previous; while (traversal_ptr != (solid_object *) NULL) { object_center_x = FIND_CENTER_X (traversal_ptr->cel); object_center_y = FIND_CENTER_Y (traversal_ptr->cel); /* once we find an object that is up and to the left of us and farther away */ /* than the maximum size an object can be, then we stop looking further. */ if ((traversal_ptr->cel->ccb_XPos + (TILE_WIDTH << 16) < target->cel->ccb_XPos) && (traversal_ptr->cel->ccb_YPos + (TILE_HEIGHT << 16) < target->cel->ccb_YPos)) break; if ( ((ABS(object_center_x - target_center_x)) <= traversal_ptr->col_detect_x + target->col_detect_x) && ((ABS(object_center_y - target_center_y)) <= traversal_ptr->col_detect_y + target->col_detect_y) ) { collisions++; if (collisions > MAX_DETECTABLE_COLLISIONS) { printf("Warning: trying to exceed MAX_DETECTABLE_COLLISIONS (upstream).\n"); collisions = MAX_DETECTABLE_COLLISIONS; break; } /* We only register this object as a collision in the following cases: */ /* a) it's not a death scene */ /* b) the only collision we find is a death scene */ /* c) it's a rock and ignore_rocks is not set */ if ((ignore_rocks) && (traversal_ptr->object_type >= FIRST_BOULDER) && (traversal_ptr->object_type <= LAST_BOULDER)) collisions--; else { if (traversal_ptr->object_type == DEATH_SCENE) { /* thus, if this is a death scene, we save it if we've got nothing */ /* else of note, otherwise we forget we ever saw it. */ if (collisions == 1) collision_list[collisions - 1] = traversal_ptr; else collisions--; } else { /* and if this isn't a death scene, but we previously had saved a */ /* death scene, we now remove the death scene from the list and put */ /* this down instead. */ if ((collisions == 2) && (collision_list[0]->object_type==DEATH_SCENE)) collisions = 1; collision_list[collisions - 1] = traversal_ptr; } } } traversal_ptr = traversal_ptr->previous; } /* now we check all of the downstream objects that are within range on either axis. */ traversal_ptr = target->next; while (traversal_ptr != (solid_object *) NULL) { object_center_x = FIND_CENTER_X (traversal_ptr->cel); object_center_y = FIND_CENTER_Y (traversal_ptr->cel); /* once we find an object that is down and to the right of us and farther away */ /* than the maximum size an object can be, then we stop looking further. */ if ((traversal_ptr->cel->ccb_XPos > target->cel->ccb_XPos + (TILE_WIDTH << 16)) && (traversal_ptr->cel->ccb_YPos > target->cel->ccb_YPos + (TILE_HEIGHT << 16))) break; if ( ((ABS(object_center_x - target_center_x)) <= traversal_ptr->col_detect_x + target->col_detect_x) && ((ABS(object_center_y - target_center_y)) <= traversal_ptr->col_detect_y + target->col_detect_y) ) { collisions++; if (collisions > MAX_DETECTABLE_COLLISIONS) { printf("Warning: trying to exceed MAX_DETECTABLE_COLLISIONS (downstream).\n"); collisions = MAX_DETECTABLE_COLLISIONS; break; } /* We only register this object as a collision in the following cases: */ /* a) it's not a death scene */ /* b) the only collision we find is a death scene */ /* c) it's a rock and ignore_rocks is not set */ if ((ignore_rocks) && (traversal_ptr->object_type >= FIRST_BOULDER) && (traversal_ptr->object_type <= LAST_BOULDER)) collisions--; else { if (traversal_ptr->object_type == DEATH_SCENE) { /* thus, if this is a death scene, we save it if we've got nothing */ /* else of note, otherwise we forget we ever saw it. */ if (collisions == 1) collision_list[collisions - 1] = traversal_ptr; else collisions--; } else { /* and if this isn't a death scene, but we previously had saved a */ /* death scene, we now remove the death scene from the list and put */ /* this down instead. */ if ((collisions == 2) && (collision_list[0]->object_type==DEATH_SCENE)) collisions = 1; collision_list[collisions - 1] = traversal_ptr; } } } traversal_ptr = traversal_ptr->next; } if (collisions == 0) return((solid_object *) NULL); /* if more than one object was collided with, then the one to be reported is the */ /* one closest to the target object. */ traversal_ptr = collision_list[0]; for (int32 i = 1; i < collisions; i++) { x_diff[0] = (ABS(FIND_CENTER_X(traversal_ptr->cel) - target_center_x)); y_diff[0] = (ABS(FIND_CENTER_Y(traversal_ptr->cel) - target_center_y)); x_diff[1] = (ABS(FIND_CENTER_X(collision_list[i]->cel) - target_center_x)); y_diff[1] = (ABS(FIND_CENTER_Y(collision_list[i]->cel) - target_center_y)); if ((x_diff[0] > x_diff[1]) || (y_diff[0] > y_diff[1])) traversal_ptr = collision_list[i]; } return(traversal_ptr);}/************************** solids::CompareForCollisions ****************************** This function takes as input pointers to a pair of solid objects, and returns TRUE if they have collided. If either object no longer exists, or if there is no collision, then FALSE is returned. This function is meant as a time saver for cases in which one object expects to be collide repeatedly with another specific object. In these cases, there isno need to traverse the entire solids list looking for collisions where you know one is likely to occur with one particular object.*****************************************************************************************/bool solids::CompareForCollisions(solid_object *thing1, solid_object *thing2){ if ((thing1 == (solid_object *) NULL) || (thing2 == (solid_object *) NULL)) return(FALSE); if ((thing1->cel == (CCB *) NULL) || (thing2->cel == (CCB *) NULL)) return(FALSE); return ( ((ABS((FIND_CENTER_X (thing1->cel)) - (FIND_CENTER_X (thing2->cel)))) <= thing1->col_detect_x + thing2->col_detect_x) && ((ABS((FIND_CENTER_Y (thing1->cel)) - (FIND_CENTER_Y (thing2->cel)))) <= thing1->col_detect_y + thing2->col_detect_y) );}/***************************** solids::MoveWorld ************************************** This function shifts all of the objects on the solids list a given distance in either or both directions as specified by the values of x_change and y_change. These parameterscan have either positive or negative values depending on which direction you wish to move the objects.*****************************************************************************************/void solids::MoveWorld (int32 x_change, int32 y_change){ register solid_object *traversal_ptr; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { traversal_ptr->cel->ccb_XPos += x_change; traversal_ptr->cel->ccb_YPos += y_change; traversal_ptr = traversal_ptr->next; }}/******************************* solids::ChangeArt ************************************ Changing the artwork a CCB considers its own is very easy: you just change the values of two pointers within its structure so that they reference a difference CCB's art information. However, which CCB should we go to in order to get our new art pointers?This routine decides that, too.*****************************************************************************************/void solids::ChangeArt(CCB *target_cel, int32 solid_id){ CCB *source_cel; int32 x,y; source_cel = green_piece_cel; switch (solid_id) { case BLUE_PYRAMID: if (blue_piece_cel == NULL) LoadArtwork(BLUE_PYRAMID); source_cel = blue_piece_cel; break; case RED_PYRAMID: if (red_piece_cel == NULL) LoadArtwork(RED_PYRAMID); source_cel = red_piece_cel; break; case GREEN_PYRAMID: if (green_piece_cel == NULL) LoadArtwork(GREEN_PYRAMID); source_cel = green_piece_cel; break; case PURPLE_PYRAMID: if (purple_piece_cel == NULL) LoadArtwork(PURPLE_PYRAMID); source_cel = purple_piece_cel; break; case BOULDER0: if (boulder0_cel == NULL) LoadArtwork(BOULDER0); source_cel = boulder0_cel; break; case BOULDER1: if (boulder1_cel == NULL) LoadArtwork(BOULDER1); source_cel = boulder1_cel; break; case BOULDER2: if (boulder2_cel == NULL) LoadArtwork(BOULDER2); source_cel = boulder2_cel; break; case BOULDER3: if (boulder3_cel == NULL) LoadArtwork(BOULDER3); source_cel = boulder3_cel; break; case BOULDER4: if (boulder4_cel == NULL) LoadArtwork(BOULDER4); source_cel = boulder4_cel; break; case BOULDER5: if (boulder5_cel == NULL) LoadArtwork(BOULDER5); source_cel = boulder5_cel; break; case BOULDER6: if (boulder6_cel == NULL) LoadArtwork(BOULDER6); source_cel = boulder6_cel; break; case BOULDER7: if (boulder7_cel == NULL) LoadArtwork(BOULDER7); source_cel = boulder7_cel; break; case BOULDER8: if (boulder8_cel == NULL) LoadArtwork(BOULDER8); source_cel = boulder8_cel; break; case BOULDER9: if (boulder9_cel == NULL) LoadArtwork(BOULDER9); source_cel = boulder9_cel; break; case BOULDERA: if (boulderA_cel == NULL) LoadArtwork(BOULDERA); source_cel = boulderA_cel; break; case BOULDERD: if (boulderD_cel == NULL) LoadArtwork(BOULDERD); source_cel = boulderD_cel; break; case YELLOW_PYRAMID: source_cel = enemies.yellow_piece_cel; break; case LTBLUE_PYRAMID: source_cel = enemies.ltblue_piece_cel; break; case PINK_PYRAMID: source_cel = enemies.pink_piece_cel; break; case LIME_PYRAMID: source_cel = enemies.lime_piece_cel; break; case LURKER_PYRAMID: source_cel = enemies.lurker_piece_cel; break; } if (source_cel == NULL) { printf("Whoa! solids::ChangeArt got a null pointer! Object type: %ld!\n",solid_id); exit(0); } x = target_cel->ccb_XPos; y = target_cel->ccb_YPos; memcpy(target_cel,source_cel,sizeof(CCB)); target_cel->ccb_XPos = x; target_cel->ccb_YPos = y;}/*************************** solids::TransformSolid *********************************** This function is pretty specific to Icebreaker. It randomly selects an object fromthe inanimates database and then, if the object still exists, it alters it in some way.Once this function has succeeded in altering an object somewhere on the list, it waitsa little while before changing something else.*****************************************************************************************/bool solids::TransformSolid(void){ register solid_object *traversal_ptr; static struct timeval old_time; int32 solid_to_transform; int32 index_into_solids_list; /* if less than a second has elapsed since the last time we did one, do nothing. */ DoIO(timer_request_item,&timer_request_info); if (!( ((old_time.tv_sec + PYRAMID_POP_DELAY <= current_time.tv_sec) && (old_time.tv_usec <= current_time.tv_usec)) || (old_time.tv_sec + PYRAMID_POP_DELAY + 1 <= current_time.tv_sec))) return(FALSE); /* Now, pick a number between 1 and the total supply of static pyramids. Then work */ /* through the list, counting only static pyramids, until we get to the Xth one. */ solid_to_transform = RandomNumber(1,196); if (solid_to_transform > g_total_pyramids) return(FALSE); index_into_solids_list = 1; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { if ((traversal_ptr->object_type >= FIRST_STATIC_PYRAMID) && (traversal_ptr->object_type <= LAST_STATIC_PYRAMID)) { if (index_into_solids_list == solid_to_transform) break; index_into_solids_list++; } traversal_ptr = traversal_ptr->next; } /* If the traversal pointer is NULL at this point, then it means we got to the end */ /* of the list without finding the Xth object, which in turn means that no more */ /* static pyramids remain or that our calcuations were incorrect. */ if (traversal_ptr == (solid_object *) NULL) return(FALSE); if (traversal_ptr->object_type == RED_PYRAMID) { traversal_ptr->object_type = BLUE_PYRAMID; ChangeArt(traversal_ptr->cel, BLUE_PYRAMID); old_time.tv_sec = current_time.tv_sec; old_time.tv_usec = current_time.tv_usec; return(TRUE); } else if (traversal_ptr->object_type == GREEN_PYRAMID) { traversal_ptr->object_type = RED_PYRAMID; ChangeArt(traversal_ptr->cel, RED_PYRAMID); old_time.tv_sec = current_time.tv_sec; old_time.tv_usec = current_time.tv_usec; return(TRUE); } else if (traversal_ptr->object_type == BLUE_PYRAMID) { traversal_ptr->object_type = GREEN_PYRAMID; ChangeArt(traversal_ptr->cel, GREEN_PYRAMID); old_time.tv_sec = current_time.tv_sec; old_time.tv_usec = current_time.tv_usec; return(TRUE); } /* if we get here, then the Xth object wasn't one of the 3 that we can transform. */ return(FALSE);}/***************************** solids::DumpList *************************************** This function is used solely for testing and debug purposes. It dumps out a list ofsolids (it can be either the solids list or the trash can).*****************************************************************************************/void solids::DumpList(solid_object *list_to_be_dumped){ solid_object *traversal_ptr; traversal_ptr = list_to_be_dumped; while (traversal_ptr != (solid_object *) NULL) { switch(traversal_ptr->object_type) { case YELLOW_PYRAMID: printf("yellow "); break; case LTBLUE_PYRAMID: printf("ltblue "); break; case PINK_PYRAMID: printf("pink "); break; case LIME_PYRAMID: printf("lime "); break; case YELLOW_SEEKER: printf("YELLOW\n"); break; case LTBLUE_SEEKER: printf("LTBLUE\n"); break; case PINK_SEEKER: printf("PINK\n"); break; case LIME_SEEKER: printf("LIME\n"); break; case DORMANT_CHAMELEON: printf("DCHAM\n"); break; case WAKING_CHAMELEON: printf("WCHAM\n"); break; case ACTIVE_CHAMELEON: printf("ACHAM\n"); break; case ZOMBIE: printf("ZOMBIE\n"); break; case LURKER: printf("LURKER\n"); break; case MEANY: printf("MEANY\n"); break; case NASTY: printf("NASTY\n"); break; case GRUMPY: printf("GRUMPY\n"); break; case PHANTOM_BOULDER: printf("~ "); break; case BOULDER0: printf("0 "); break; case BOULDER1: printf("1 "); break; case BOULDER2: printf("2 "); break; case BOULDER3: printf("3 "); break; case BOULDER4: printf("4 "); break; case BOULDER5: printf("5 "); break; case BOULDER6: printf("6 "); break; case BOULDER7: printf("7 "); break; case BOULDER8: printf("8 "); break; case BOULDER9: printf("9 "); break; case BOULDERA: printf("A "); break; case BOULDERD: printf("D "); break; case DUDEMEYER: printf("DM\n"); break; case NOTHING: printf("-\n"); break; case BLUE_PYRAMID: printf("B "); break; case RED_PYRAMID: printf("R "); break; case GREEN_PYRAMID: printf("G "); break; case RAINBOW_PYRAMID: printf("T "); break; case CONCRETE_PYRAMID: printf("C "); break; case PURPLE_PYRAMID: printf("P "); break; case BIRTH_SCENE: printf("BIRTH\n"); break; case DEATH_SCENE: printf("DEATH\n"); break; case SPLIT_SCENE: printf("SPLIT\n"); break; default: printf("???????\n"); break; } traversal_ptr = traversal_ptr->next; } printf("\n"); printf("==========================================================================\n");}/************************** solids::CreateAnimatedSolid ******************************** Since animated solids require constant maintenance in order to keep up their ever changing appearance, we maintain a list of the animated solids in the population so as to avoid wasting time searching through the entire list of solid objects just to find out which ones have animations. This function adds a new object to the list of animated solids and initializes the solid by linking the data structure to the master copy of the solid's animation, as provided in the parameters list.*****************************************************************************************/void solids::CreateAnimatedSolid (anim_source *original, int32 frame_rate, solid_object *solids_entry, bool endless_value){ int32 i,j; anisolid *new_anisolid; new_anisolid = new(anisolid); new_anisolid->solid_anim.InitializeAnim (original, frame_rate); new_anisolid->endless = endless_value; new_anisolid->solids_entry = solids_entry; new_anisolid->solid_anim.Restart(); new_anisolid->next = anisolid_list; anisolid_list = new_anisolid; if (solids_entry->object_type == RAINBOW_PYRAMID) { j = RandomNumber(1,18); for (i = 0; i <= j; i++) new_anisolid->solid_anim.AdvanceFrame(); } if (solids_entry->object_type == CONCRETE_PYRAMID) new_anisolid->solid_anim.AdvanceFrame(); i = solids_entry->cel->ccb_XPos; j = solids_entry->cel->ccb_YPos; memcpy(solids_entry->cel,new_anisolid->solid_anim.current_frame_ccb,sizeof(CCB)); solids_entry->cel->ccb_XPos = i; solids_entry->cel->ccb_YPos = j;}/************************ solids::EliminateAnimatedObject ****************************** There are two versions of the EliminateAnimatedObject function. One deletes an animated solid from the animated solids list, based on a pointer into that list, and the other deletes the solid based only on a pointer to the solid object itself. The second function simply finds the animated solid's entry on the anisolids list and passes it along to the first function. This is the first of these functions.*****************************************************************************************/void solids::EliminateAnimatedObject(anisolid *target){ anisolid *traversal_ptr; /* if object is at head of list, adjust master list pointer */ if (target == anisolid_list) anisolid_list = target->next; else { /* look through the list until we find the target, then cut it out of the list */ traversal_ptr = anisolid_list; while (traversal_ptr->next != target) traversal_ptr = traversal_ptr->next; traversal_ptr->next = traversal_ptr->next->next; } /* now free up the memory used by the record */ target->solid_anim.ShutdownForRestart(); delete(target);}/************************ solids::EliminateAnimatedObject ****************************** There are two versions of the EliminateAnimatedObject function. One deletes an animated solid from the animated solids list, based on a pointer into that list, and the other deletes the solid based only on a pointer to the solid object itself. The second function simply finds the animated solid's entry on the anisolids list and passes it along to the first function. This is the second of these functions.*****************************************************************************************/void solids::EliminateAnimatedObject(solid_object *target){ EliminateAnimatedObject(LocateAnimatedObject(target));}/************************* solids::LocateAnimatedObject ******************************** For those cases when someone needs to have a pointer to an animated object's animation,this function looks through the list until it finds it.*****************************************************************************************/anisolid* solids::LocateAnimatedObject(solid_object *target){ anisolid *traversal_ptr; traversal_ptr = anisolid_list; while (traversal_ptr != NULL) { if (traversal_ptr->solids_entry == target) return(traversal_ptr); traversal_ptr = traversal_ptr->next; } printf("Zorak! What have you done with that animated Object????\n"); return((anisolid *) NULL);}/************************ solids::MaintainAnimatedObjects ****************************** This function is called just prior to displaying the set of solid objects, and it takes care of advancing the frame on all animated solids in the population. In addition, if any of the animations are one-timers, such as transformation sequences that play once and end with the solid taking on a new appearance, then this function handles the cleanup needed when an animation of this type reaches the end. *****************************************************************************************/void solids::MaintainAnimatedObjects(void){ anisolid *traversal_ptr,*destroy_me; traversal_ptr = anisolid_list; while (traversal_ptr != (anisolid *) NULL) { if (traversal_ptr->solids_entry->cel == (CCB *) NULL) { destroy_me = traversal_ptr; traversal_ptr = traversal_ptr->next; EliminateAnimatedObject(destroy_me); } else { if ((traversal_ptr->endless) || (!(traversal_ptr->solid_anim.AnimComplete()))) { /* if the scene goes on forever or it isn't over: advance frame and move on */ if (traversal_ptr->solids_entry->object_type != CONCRETE_PYRAMID) traversal_ptr->solid_anim.AdvanceFrame(); traversal_ptr->solids_entry->cel->ccb_SourcePtr = traversal_ptr->solid_anim.current_frame_ccb->ccb_SourcePtr; traversal_ptr->solids_entry->cel->ccb_PLUTPtr = traversal_ptr->solid_anim.current_frame_ccb->ccb_PLUTPtr; traversal_ptr = traversal_ptr->next; } else { /* if the scene is done and it's a one-timer: get rid of it */ destroy_me = traversal_ptr; traversal_ptr = traversal_ptr->next; EliminateAnimatedObject(destroy_me); } } }}/**************************** solids::ConcealChameleons ******************************** This function is used to randomly scatter Chameleon seekers in amongst the supply ofnormal green pyramids on a level. It is used only during intialization.*****************************************************************************************/void solids::ConcealChameleons (int32 how_many){ solid_object *traversal_ptr; int32 green_supply; green_supply = 0; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { if (traversal_ptr->object_type == GREEN_PYRAMID) green_supply++; traversal_ptr = traversal_ptr->next; } if (how_many > green_supply) { printf("Hey! What are you trying to pull?!?! That's too many chameleons!\n"); how_many = green_supply / 2; } while (how_many) { if (traversal_ptr == (solid_object *) NULL) traversal_ptr = solids_list; if ((traversal_ptr->object_type == GREEN_PYRAMID) && (!(enemies.CheckChameleonProximity(traversal_ptr->cel)))) { if (RandomNumber(1,100) == 1) { enemies.CreateSeeker(DORMANT_CHAMELEON, traversal_ptr->cel->ccb_XPos, traversal_ptr->cel->ccb_YPos, NO_DIRECTION); EliminateObject(traversal_ptr); traversal_ptr = solids_list; how_many--; green_supply--; } } traversal_ptr = traversal_ptr->next; }}/************************* solids::ResolveRainbowPyramid ******************************* This function randomly determines what type of piece a Rainbow pyramid really is. Thetypes of pieces it can legitimately become is determined by the g_art_usage table. If agiven type of seeker is not in use on the level, then that type of seeker will be invalidfor a Rainbow piece option.*****************************************************************************************/void solids::ResolveRainbowPyramid (solid_object *target){ int16 seekers_available,decision; seekers_available = 0; if (enemies.Loaded(LIME_SEEKER)) seekers_available++; if (enemies.Loaded(YELLOW_SEEKER)) seekers_available++; if (enemies.Loaded(LTBLUE_SEEKER)) seekers_available++; if (enemies.Loaded(PINK_SEEKER)) seekers_available++; if (enemies.Loaded(LURKER)) seekers_available++; if (seekers_available > 2) decision = (int16) RandomNumber(0,3 + seekers_available); else decision = (int16) RandomNumber(0,5); switch(decision) { case 0: target->object_type = BLUE_PYRAMID; return; case 1: target->object_type = RED_PYRAMID; return; case 2: target->object_type = GREEN_PYRAMID; return; case 3: target->object_type = PURPLE_PYRAMID; return; } if (seekers_available == 0) { target->object_type = RED_PYRAMID; return; } while (target->object_type == RAINBOW_PYRAMID) { target->object_type = RandomNumber(YELLOW_PYRAMID,LIME_PYRAMID); if (((target->object_type == LIME_PYRAMID) && (!(enemies.Loaded(LIME_SEEKER)))) || ((target->object_type == YELLOW_PYRAMID) && (!(enemies.Loaded(YELLOW_SEEKER)))) || ((target->object_type == LTBLUE_PYRAMID) && (!(enemies.Loaded(LTBLUE_SEEKER)))) || ((target->object_type == PINK_PYRAMID) && (!(enemies.Loaded(PINK_SEEKER)))) || ((target->object_type == LURKER_PYRAMID) && (!(enemies.Loaded(LURKER))))) target->object_type = RAINBOW_PYRAMID; }}/*********************** solids::RegisterRocksWithLandscape **************************** This function is used only during initialization. It traverses the entire solids listand tells a function within the landscape class about each rock it encounters. This isdone for the benefit of seekers who wish an easy list of rocks which they will regard ashazards for the purpose of efficient avoidance.*****************************************************************************************/void solids::RegisterRocksWithLandscape (void){ register solid_object *traversal_ptr; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { if ((traversal_ptr->object_type >= FIRST_BOULDER) && (traversal_ptr->object_type <= LAST_BOULDER)) pavement.RegisterRockyTile(traversal_ptr->cel); traversal_ptr = traversal_ptr->next; }}/************************* solids::CheckForPotentialPits ****************************** This function is used only during initialization. It traverses the entire solids listand makes sure that for all tiles that have purple or rainbow pieces on them, the correctpit transformation anim is loaded into memory.*****************************************************************************************/void solids::CheckForPotentialPits (void){ register solid_object *traversal_ptr; traversal_ptr = solids_list; while (traversal_ptr != (solid_object *) NULL) { if ((traversal_ptr->object_type == PURPLE_PYRAMID) || (traversal_ptr->object_type == RAINBOW_PYRAMID)) pavement.LoadPitFormation(traversal_ptr->cel); traversal_ptr = traversal_ptr->next; }}/************************* solids::ShutdownForRestart ********************************* When a game ends, the player will either wish to quit or play again. In either case,this function should be called to surrender memory that was dynamically allocated duringthe game. Needless to say, it is particularly important that this function be calledwhenever a game is to be replayed; otherwise the memory that was allocated will be losttrack of and wasted.*****************************************************************************************/void solids::ShutdownForRestart(){ anisolid *anisolid_traversal, *anisolid_vanguard; int32 i,j; /****** throw away all solids ******/ EmptyTrash(); trash_can = solids_list; EmptyTrash(); /****** deallocate all memory used by solids CCBs ******/ for (i = 0; i < ROWS_IN_LANDSCAPE; i++) for (j = 0; j < COLUMNS_IN_LANDSCAPE; j++) delete(inanimates_cel_database[i][j]); /***** recover all memory used by animated solids *****/ anisolid_traversal = anisolid_list; while (anisolid_traversal != (anisolid *) NULL) { anisolid_vanguard = anisolid_traversal->next; anisolid_traversal->solid_anim.ShutdownForRestart(); delete(anisolid_traversal); anisolid_traversal = anisolid_vanguard; }}/************************** solids::ShutdownForExit *********************************** This function deallocates memory that was reserved for storage of artwork elementsthat are used repeatedly over a sequence of many games. This function should be calledonly when the program is about to be shutdown for good.*****************************************************************************************/void solids::ShutdownForExit(){ if (red_piece_cel != NULL) UnloadCel(red_piece_cel); if (blue_piece_cel != NULL) UnloadCel(blue_piece_cel); if (green_piece_cel != NULL) UnloadCel(green_piece_cel); if (purple_piece_cel != NULL) UnloadCel(purple_piece_cel); if (boulder0_cel != NULL) UnloadCel(boulder0_cel); if (boulder1_cel != NULL) UnloadCel(boulder1_cel); if (boulder2_cel != NULL) UnloadCel(boulder2_cel); if (boulder3_cel != NULL) UnloadCel(boulder3_cel); if (boulder4_cel != NULL) UnloadCel(boulder4_cel); if (boulder5_cel != NULL) UnloadCel(boulder5_cel); if (boulder6_cel != NULL) UnloadCel(boulder6_cel); if (boulder7_cel != NULL) UnloadCel(boulder7_cel); if (boulder8_cel != NULL) UnloadCel(boulder8_cel); if (boulder9_cel != NULL) UnloadCel(boulder9_cel); if (boulderA_cel != NULL) UnloadCel(boulderA_cel); if (boulderD_cel != NULL) UnloadCel(boulderD_cel); rainbow_piece_anim.ShutdownForExit(); concrete_piece_anim.ShutdownForExit(); blue_death.ShutdownForExit(); red_death.ShutdownForExit(); purple_death.ShutdownForExit(); green_death.ShutdownForExit(); concrete_death.ShutdownForExit(); concrete_puff[0].ShutdownForExit(); concrete_puff[1].ShutdownForExit(); concrete_puff[2].ShutdownForExit();}/************************* landscape::ShutdownUnusedArtwork **************************** This function examines all of the art elements used by this class and gets rid of anythat are not currently needed, thus freeing up the memory this art was consuming for moreworthwhile causes. The way in which we know whether or not a piece of art is needed isby examining the values in the global art usage table (g_art_usage). This table is filledout by the function LoadLevel as the level parameters are parsed. Anything that isn'tset to TRUE in this table will not be needed during this level and can therefore safelybe discarded.*****************************************************************************************/void solids::ShutdownUnusedArtwork(void){ if ((red_piece_cel != NULL) && (g_art_usage[RED_PYRAMID] == FALSE)) { UnloadCel(red_piece_cel); red_piece_cel = (CCB *) NULL; red_death.ShutdownForExit(); } if ((blue_piece_cel != NULL) && (g_art_usage[BLUE_PYRAMID] == FALSE)) { UnloadCel(blue_piece_cel); blue_piece_cel = (CCB *) NULL; blue_death.ShutdownForExit(); } if ((green_piece_cel != NULL) && (g_art_usage[GREEN_PYRAMID] == FALSE)) { UnloadCel(green_piece_cel); green_piece_cel = (CCB *) NULL; green_death.ShutdownForExit(); } if ((purple_piece_cel != NULL) && (g_art_usage[PURPLE_PYRAMID] == FALSE)) { UnloadCel(purple_piece_cel); purple_piece_cel = (CCB *) NULL; purple_death.ShutdownForExit(); } if ((boulder0_cel != NULL) && (g_art_usage[BOULDER0] == FALSE)) { UnloadCel(boulder0_cel); boulder0_cel = (CCB *) NULL; } if ((boulder1_cel != NULL) && (g_art_usage[BOULDER1] == FALSE)) { UnloadCel(boulder1_cel); boulder1_cel = (CCB *) NULL; } if ((boulder2_cel != NULL) && (g_art_usage[BOULDER2] == FALSE)) { UnloadCel(boulder2_cel); boulder2_cel = (CCB *) NULL; } if ((boulder3_cel != NULL) && (g_art_usage[BOULDER3] == FALSE)) { UnloadCel(boulder3_cel); boulder3_cel = (CCB *) NULL; } if ((boulder4_cel != NULL) && (g_art_usage[BOULDER4] == FALSE)) { UnloadCel(boulder4_cel); boulder4_cel = (CCB *) NULL; } if ((boulder5_cel != NULL) && (g_art_usage[BOULDER5] == FALSE)) { UnloadCel(boulder5_cel); boulder5_cel = (CCB *) NULL; } if ((boulder6_cel != NULL) && (g_art_usage[BOULDER6] == FALSE)) { UnloadCel(boulder6_cel); boulder6_cel = (CCB *) NULL; } if ((boulder7_cel != NULL) && (g_art_usage[BOULDER7] == FALSE)) { UnloadCel(boulder7_cel); boulder7_cel = (CCB *) NULL; } if ((boulder8_cel != NULL) && (g_art_usage[BOULDER8] == FALSE)) { UnloadCel(boulder8_cel); boulder8_cel = (CCB *) NULL; } if ((boulder9_cel != NULL) && (g_art_usage[BOULDER9] == FALSE)) { UnloadCel(boulder9_cel); boulder9_cel = (CCB *) NULL; } if ((boulderA_cel != NULL) && (g_art_usage[BOULDERA] == FALSE)) { UnloadCel(boulderA_cel); boulderA_cel = (CCB *) NULL; } if ((boulderD_cel != NULL) && (g_art_usage[BOULDERD] == FALSE)) { UnloadCel(boulderD_cel); boulderD_cel = (CCB *) NULL; } if (g_art_usage[RAINBOW_PYRAMID] == FALSE) { rainbow_piece_anim.ShutdownForExit(); } if (g_art_usage[CONCRETE_PYRAMID]==FALSE) { concrete_piece_anim.ShutdownForExit(); concrete_death.ShutdownForExit(); concrete_puff[0].ShutdownForExit(); concrete_puff[1].ShutdownForExit(); concrete_puff[2].ShutdownForExit(); } ScavengeMem();}/**************************************** EOF *******************************************/