2019-08-25 06:46:40 +02:00
|
|
|
#include <ultra64.h>
|
|
|
|
|
|
|
|
#include "sm64.h"
|
|
|
|
#include "engine/math_util.h"
|
|
|
|
#include "area.h"
|
|
|
|
#include "engine/geo_layout.h"
|
|
|
|
#include "engine/graph_node.h"
|
|
|
|
#include "object_helpers.h"
|
|
|
|
#include "engine/behavior_script.h"
|
|
|
|
#include "engine/surface_collision.h"
|
|
|
|
#include "audio/external.h"
|
|
|
|
#include "level_update.h"
|
|
|
|
#include "spawn_object.h"
|
|
|
|
#include "object_list_processor.h"
|
2019-12-02 03:52:53 +01:00
|
|
|
#include "level_table.h"
|
2019-08-25 06:46:40 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An unused linked list struct that seems to have been replaced by ObjectNode.
|
|
|
|
*/
|
|
|
|
struct LinkedList {
|
|
|
|
struct LinkedList *next;
|
|
|
|
struct LinkedList *prev;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the doubly linked usedList. Singly link each item in the pool into
|
|
|
|
* a list, and return this list in pFreeList.
|
|
|
|
* Appears to have been replaced by init_free_object_list.
|
|
|
|
*/
|
2020-02-03 06:51:26 +01:00
|
|
|
void unused_init_free_list(struct LinkedList *usedList, struct LinkedList **pFreeList,
|
2019-08-25 06:46:40 +02:00
|
|
|
struct LinkedList *pool, s32 itemSize, s32 poolLength) {
|
|
|
|
s32 i;
|
|
|
|
struct LinkedList *node = pool;
|
|
|
|
|
|
|
|
usedList->next = usedList;
|
|
|
|
usedList->prev = usedList;
|
|
|
|
|
|
|
|
*pFreeList = pool;
|
|
|
|
|
|
|
|
for (i = 0; i < poolLength - 1; i++) {
|
|
|
|
// Add next node to free list
|
|
|
|
node = (struct LinkedList *) ((u8 *) node + itemSize);
|
|
|
|
pool->next = node;
|
|
|
|
pool = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
// End the list
|
|
|
|
pool->next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt to allocate a node from freeList (singly linked) and append it
|
|
|
|
* to the end of destList (doubly linked). Return the object, or NULL if
|
|
|
|
* freeList is empty.
|
|
|
|
* Appears to have been replaced by try_allocate_object.
|
|
|
|
*/
|
2020-02-03 06:51:26 +01:00
|
|
|
struct LinkedList *unused_try_allocate(struct LinkedList *destList,
|
2019-08-25 06:46:40 +02:00
|
|
|
struct LinkedList *freeList) {
|
|
|
|
struct LinkedList *node = freeList->next;
|
|
|
|
|
|
|
|
if (node != NULL) {
|
|
|
|
// Remove from free list
|
|
|
|
freeList->next = node->next;
|
|
|
|
|
|
|
|
// Insert at the end of destination list
|
|
|
|
node->prev = destList->prev;
|
|
|
|
node->next = destList;
|
|
|
|
destList->prev->next = node;
|
|
|
|
destList->prev = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt to allocate an object from freeList (singly linked) and append it
|
|
|
|
* to the end of destList (doubly linked). Return the object, or NULL if
|
|
|
|
* freeList is empty.
|
|
|
|
*/
|
2020-02-03 06:51:26 +01:00
|
|
|
struct Object *try_allocate_object(struct ObjectNode *destList, struct ObjectNode *freeList) {
|
2019-08-25 06:46:40 +02:00
|
|
|
struct ObjectNode *nextObj;
|
|
|
|
|
|
|
|
if ((nextObj = freeList->next) != NULL) {
|
|
|
|
// Remove from free list
|
|
|
|
freeList->next = nextObj->next;
|
|
|
|
|
|
|
|
// Insert at end of destination list
|
|
|
|
nextObj->prev = destList->prev;
|
|
|
|
nextObj->next = destList;
|
|
|
|
destList->prev->next = nextObj;
|
|
|
|
destList->prev = nextObj;
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
geo_remove_child(&nextObj->gfx.node);
|
|
|
|
geo_add_child(&gObjParentGraphNode, &nextObj->gfx.node);
|
|
|
|
|
|
|
|
return (struct Object *) nextObj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the node from the doubly linked list it's in, and place it in the
|
|
|
|
* singly linked freeList.
|
|
|
|
* This function seems to have been replaced by deallocate_object.
|
|
|
|
*/
|
2020-02-03 06:51:26 +01:00
|
|
|
void unused_deallocate(struct LinkedList *freeList, struct LinkedList *node) {
|
2019-08-25 06:46:40 +02:00
|
|
|
// Remove from doubly linked list
|
|
|
|
node->next->prev = node->prev;
|
|
|
|
node->prev->next = node->next;
|
|
|
|
|
|
|
|
// Insert at beginning of singly linked list
|
|
|
|
node->next = freeList->next;
|
|
|
|
freeList->next = node;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Remove the given object from the object list that it's currently in, and
|
|
|
|
* insert it at the beginning of the free list (singly linked).
|
|
|
|
*/
|
|
|
|
static void deallocate_object(struct ObjectNode *freeList, struct ObjectNode *obj) {
|
|
|
|
// Remove from object list
|
|
|
|
obj->next->prev = obj->prev;
|
|
|
|
obj->prev->next = obj->next;
|
|
|
|
|
|
|
|
// Insert at beginning of free list
|
|
|
|
obj->next = freeList->next;
|
|
|
|
freeList->next = obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add every object in the pool to the free object list.
|
|
|
|
*/
|
|
|
|
void init_free_object_list(void) {
|
|
|
|
s32 i;
|
|
|
|
s32 poolLength = OBJECT_POOL_CAPACITY;
|
|
|
|
|
|
|
|
// Add the first object in the pool to the free list
|
|
|
|
struct Object *obj = &gObjectPool[0];
|
|
|
|
gFreeObjectList.next = (struct ObjectNode *) obj;
|
|
|
|
|
|
|
|
// Link each object in the pool to the following object
|
|
|
|
for (i = 0; i < poolLength - 1; i++) {
|
|
|
|
obj->header.next = &(obj + 1)->header;
|
|
|
|
obj++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// End the list
|
|
|
|
obj->header.next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear each object list, without adding the objects back to the free list.
|
|
|
|
*/
|
|
|
|
void clear_object_lists(struct ObjectNode *objLists) {
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_OBJ_LISTS; i++) {
|
|
|
|
objLists[i].next = &objLists[i];
|
|
|
|
objLists[i].prev = &objLists[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-05-17 10:49:34 +02:00
|
|
|
* Delete the leaf graph nodes under obj and obj's siblings.
|
2019-08-25 06:46:40 +02:00
|
|
|
*/
|
|
|
|
static void unused_delete_leaf_nodes(struct Object *obj) {
|
|
|
|
struct Object *children;
|
|
|
|
struct Object *sibling;
|
|
|
|
struct Object *obj0 = obj;
|
|
|
|
|
|
|
|
if ((children = (struct Object *) obj->header.gfx.node.children) != NULL) {
|
|
|
|
unused_delete_leaf_nodes(children);
|
|
|
|
} else {
|
|
|
|
// No children
|
|
|
|
mark_obj_for_deletion(obj);
|
|
|
|
}
|
|
|
|
|
2020-05-17 10:49:34 +02:00
|
|
|
while ((sibling = (struct Object *) obj->header.gfx.node.next) != obj0) {
|
2019-08-25 06:46:40 +02:00
|
|
|
unused_delete_leaf_nodes(sibling);
|
|
|
|
obj = (struct Object *) sibling->header.gfx.node.next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Free the given object.
|
|
|
|
*/
|
|
|
|
void unload_object(struct Object *obj) {
|
|
|
|
obj->activeFlags = ACTIVE_FLAGS_DEACTIVATED;
|
|
|
|
obj->prevObj = NULL;
|
|
|
|
|
|
|
|
obj->header.gfx.throwMatrix = NULL;
|
|
|
|
func_803206F8(obj->header.gfx.cameraToObject);
|
|
|
|
geo_remove_child(&obj->header.gfx.node);
|
|
|
|
geo_add_child(&gObjParentGraphNode, &obj->header.gfx.node);
|
|
|
|
|
|
|
|
obj->header.gfx.node.flags &= ~GRAPH_RENDER_BILLBOARD;
|
2020-05-17 08:34:05 +02:00
|
|
|
obj->header.gfx.node.flags &= ~GRAPH_RENDER_CYLBOARD;
|
2019-08-25 06:46:40 +02:00
|
|
|
obj->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
|
|
|
|
|
|
|
|
deallocate_object(&gFreeObjectList, &obj->header);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt to allocate a new object slot into the given object list, freeing
|
|
|
|
* an unimportant object if necessary. If this is not possible, hang using an
|
|
|
|
* infinite loop.
|
|
|
|
*/
|
2020-02-03 06:51:26 +01:00
|
|
|
struct Object *allocate_object(struct ObjectNode *objList) {
|
2019-08-25 06:46:40 +02:00
|
|
|
s32 i;
|
|
|
|
struct Object *obj = try_allocate_object(objList, &gFreeObjectList);
|
|
|
|
|
|
|
|
// The object list is full if the newly created pointer is NULL.
|
|
|
|
// If this happens, we first attempt to unload unimportant objects
|
|
|
|
// in order to finish allocating the object.
|
|
|
|
if (obj == NULL) {
|
|
|
|
// Look for an unimportant object to kick out.
|
|
|
|
struct Object *unimportantObj = find_unimportant_object();
|
|
|
|
|
|
|
|
// If no unimportant object exists, then the object pool is exhausted.
|
|
|
|
if (unimportantObj == NULL) {
|
|
|
|
// We've met with a terrible fate.
|
|
|
|
while (TRUE) {
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If an unimportant object does exist, unload it and take its slot.
|
|
|
|
unload_object(unimportantObj);
|
|
|
|
obj = try_allocate_object(objList, &gFreeObjectList);
|
|
|
|
if (gCurrentObject == obj) {
|
|
|
|
//! Uh oh, the unimportant object was in the middle of
|
|
|
|
// updating! This could cause some interesting logic errors,
|
|
|
|
// but I don't know of any unimportant objects that spawn
|
|
|
|
// other objects.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize object fields
|
|
|
|
|
|
|
|
obj->activeFlags = ACTIVE_FLAG_ACTIVE | ACTIVE_FLAG_UNK8;
|
|
|
|
obj->parentObj = obj;
|
|
|
|
obj->prevObj = NULL;
|
|
|
|
obj->collidedObjInteractTypes = 0;
|
|
|
|
obj->numCollidedObjs = 0;
|
|
|
|
|
2019-11-03 20:36:27 +01:00
|
|
|
#if IS_64_BIT
|
2020-02-03 06:51:26 +01:00
|
|
|
for (i = 0; i < 0x50; i++) {
|
|
|
|
obj->rawData.asS32[i] = 0;
|
2019-11-03 20:36:27 +01:00
|
|
|
obj->ptrData.asVoidPtr[i] = NULL;
|
2019-08-25 06:46:40 +02:00
|
|
|
}
|
2020-02-03 06:51:26 +01:00
|
|
|
#else
|
|
|
|
// -O2 needs everything until = on the same line
|
|
|
|
for (i = 0; i < 0x50; i++) obj->rawData.asS32[i] = 0;
|
|
|
|
#endif
|
2019-08-25 06:46:40 +02:00
|
|
|
|
2019-10-05 21:08:05 +02:00
|
|
|
obj->unused1 = 0;
|
2020-04-03 20:57:26 +02:00
|
|
|
obj->bhvStackIndex = 0;
|
|
|
|
obj->bhvDelayTimer = 0;
|
2019-08-25 06:46:40 +02:00
|
|
|
|
|
|
|
obj->hitboxRadius = 50.0f;
|
|
|
|
obj->hitboxHeight = 100.0f;
|
|
|
|
obj->hurtboxRadius = 0.0f;
|
|
|
|
obj->hurtboxHeight = 0.0f;
|
|
|
|
obj->hitboxDownOffset = 0.0f;
|
2019-10-05 21:08:05 +02:00
|
|
|
obj->unused2 = 0;
|
2019-08-25 06:46:40 +02:00
|
|
|
|
|
|
|
obj->platform = NULL;
|
|
|
|
obj->collisionData = NULL;
|
|
|
|
obj->oIntangibleTimer = -1;
|
|
|
|
obj->oDamageOrCoinValue = 0;
|
|
|
|
obj->oHealth = 2048;
|
|
|
|
|
|
|
|
obj->oCollisionDistance = 1000.0f;
|
|
|
|
if (gCurrLevelNum == LEVEL_TTC) {
|
|
|
|
obj->oDrawingDistance = 2000.0f;
|
|
|
|
} else {
|
|
|
|
obj->oDrawingDistance = 4000.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtxf_identity(obj->transform);
|
|
|
|
|
|
|
|
obj->respawnInfoType = RESPAWN_INFO_TYPE_NULL;
|
|
|
|
obj->respawnInfo = NULL;
|
|
|
|
|
|
|
|
obj->oDistanceToMario = 19000.0f;
|
|
|
|
obj->oRoom = -1;
|
|
|
|
|
|
|
|
obj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
|
|
|
|
obj->header.gfx.pos[0] = -10000.0f;
|
|
|
|
obj->header.gfx.pos[1] = -10000.0f;
|
|
|
|
obj->header.gfx.pos[2] = -10000.0f;
|
|
|
|
obj->header.gfx.throwMatrix = NULL;
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the object is close to being on the floor, move it to be exactly on the
|
|
|
|
* floor.
|
|
|
|
*/
|
|
|
|
static void snap_object_to_floor(struct Object *obj) {
|
|
|
|
struct Surface *surface;
|
|
|
|
|
|
|
|
obj->oFloorHeight = find_floor(obj->oPosX, obj->oPosY, obj->oPosZ, &surface);
|
|
|
|
|
|
|
|
if (obj->oFloorHeight + 2.0f > obj->oPosY && obj->oPosY > obj->oFloorHeight - 10.0f) {
|
|
|
|
obj->oPosY = obj->oFloorHeight;
|
|
|
|
obj->oMoveFlags |= OBJ_MOVE_ON_GROUND;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Spawn an object at the origin with the behavior script at virtual address
|
2020-04-03 20:57:26 +02:00
|
|
|
* bhvScript.
|
2019-08-25 06:46:40 +02:00
|
|
|
*/
|
2020-04-03 20:57:26 +02:00
|
|
|
struct Object *create_object(const BehaviorScript *bhvScript) {
|
2019-08-25 06:46:40 +02:00
|
|
|
s32 objListIndex;
|
|
|
|
struct Object *obj;
|
|
|
|
struct ObjectNode *objList;
|
2020-04-03 20:57:26 +02:00
|
|
|
const BehaviorScript *behavior = bhvScript;
|
2019-08-25 06:46:40 +02:00
|
|
|
|
|
|
|
// If the first behavior script command is "begin <object list>", then
|
|
|
|
// extract the object list from it
|
2020-04-03 20:57:26 +02:00
|
|
|
if ((bhvScript[0] >> 24) == 0) {
|
|
|
|
objListIndex = (bhvScript[0] >> 16) & 0xFFFF;
|
2019-08-25 06:46:40 +02:00
|
|
|
} else {
|
|
|
|
objListIndex = OBJ_LIST_DEFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
objList = &gObjectLists[objListIndex];
|
|
|
|
obj = allocate_object(objList);
|
|
|
|
|
2020-04-03 20:57:26 +02:00
|
|
|
obj->curBhvCommand = bhvScript;
|
2019-08-25 06:46:40 +02:00
|
|
|
obj->behavior = behavior;
|
|
|
|
|
|
|
|
if (objListIndex == OBJ_LIST_UNIMPORTANT) {
|
|
|
|
obj->activeFlags |= ACTIVE_FLAG_UNIMPORTANT;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! They intended to snap certain objects to the floor when they spawn.
|
|
|
|
// However, at this point the object's position is the origin. So this will
|
|
|
|
// place the object at the floor beneath the origin. Typically this
|
|
|
|
// doesn't matter since the caller of this function sets oPosX/Y/Z
|
|
|
|
// themselves.
|
|
|
|
switch (objListIndex) {
|
|
|
|
case OBJ_LIST_GENACTOR:
|
|
|
|
case OBJ_LIST_PUSHABLE:
|
|
|
|
case OBJ_LIST_POLELIKE:
|
|
|
|
snap_object_to_floor(obj);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark an object to be unloaded at the end of the frame.
|
|
|
|
*/
|
|
|
|
void mark_obj_for_deletion(struct Object *obj) {
|
2020-03-02 04:42:52 +01:00
|
|
|
//! Same issue as obj_mark_for_deletion
|
2019-08-25 06:46:40 +02:00
|
|
|
obj->activeFlags = ACTIVE_FLAGS_DEACTIVATED;
|
|
|
|
}
|