sm64pc/src/game/obj_behaviors.c

853 lines
26 KiB
C
Raw Normal View History

2019-08-25 04:46:40 +00:00
#include <ultra64.h>
#include "sm64.h"
#include "obj_behaviors.h"
#include "rendering_graph_node.h"
#include "memory.h"
#include "engine/behavior_script.h"
#include "engine/surface_collision.h"
#include "engine/math_util.h"
#include "object_helpers.h"
#include "behavior_data.h"
#include "mario.h"
2020-04-03 18:57:26 +00:00
#include "game_init.h"
2019-08-25 04:46:40 +00:00
#include "camera.h"
#include "mario_actions_cutscene.h"
#include "object_list_processor.h"
#include "save_file.h"
#include "area.h"
#include "mario_misc.h"
#include "level_update.h"
#include "audio/external.h"
#include "behavior_actions.h"
#include "spawn_object.h"
#include "spawn_sound.h"
#include "envfx_bubbles.h"
#include "ingame_menu.h"
#include "interaction.h"
2019-12-02 02:52:53 +00:00
#include "level_table.h"
#include "dialog_ids.h"
#include "course_table.h"
/**
* @file obj_behaviors.c
* This file contains a portion of the obj behaviors and many helper functions for those
* specific behaviors. Few functions besides the bhv_ functions are used elsewhere in the repo.
*/
2019-08-25 04:46:40 +00:00
#define o gCurrentObject
2019-12-02 02:52:53 +00:00
#define OBJ_COL_FLAG_GROUNDED (1 << 0)
#define OBJ_COL_FLAG_HIT_WALL (1 << 1)
2019-08-25 04:46:40 +00:00
#define OBJ_COL_FLAG_UNDERWATER (1 << 2)
2019-12-02 02:52:53 +00:00
#define OBJ_COL_FLAG_NO_Y_VEL (1 << 3)
#define OBJ_COL_FLAGS_LANDED (OBJ_COL_FLAG_GROUNDED | OBJ_COL_FLAG_NO_Y_VEL)
/**
* Current object floor as defined in object_step.
*/
static struct Surface *sObjFloor;
/**
* Set to false when an object close to the floor should not be oriented in reference
* to it. Happens with boulder, falling pillar, and the rolling snowman body.
*/
static s8 sOrientObjWithFloor = TRUE;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
/**
* Keeps track of Mario's previous non-zero room.
* Helps keep track of room when Mario is over an object.
*/
s16 sPrevCheckMarioRoom = 0;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
/**
* Tracks whether or not Yoshi has walked/jumped off the roof.
*/
s8 sYoshiDead = FALSE;
2019-08-25 04:46:40 +00:00
extern void *ccm_seg7_trajectory_snowman;
extern void *inside_castle_seg7_trajectory_mips;
2019-12-02 02:52:53 +00:00
/**
* Resets yoshi as spawned/despawned upon new file select.
* Possibly a function with stubbed code.
*/
void set_yoshi_as_not_dead(void) {
sYoshiDead = FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
2020-03-02 03:42:52 +00:00
* An unused geo function. Bears strong similarity to geo_bits_bowser_coloring, and relates something
2019-12-02 02:52:53 +00:00
* of the opacity of an object to something else. Perhaps like, giving a parent object the same
* opacity?
*/
Gfx UNUSED *geo_obj_transparency_something(s32 callContext, struct GraphNode *node, UNUSED Mat4 *mtx) {
Gfx *gfxHead;
Gfx *gfx;
struct Object *heldObject;
struct Object *obj;
UNUSED struct Object *unusedObject;
UNUSED s32 pad;
gfxHead = NULL;
if (callContext == GEO_CONTEXT_RENDER) {
heldObject = (struct Object *) gCurGraphNodeObject;
obj = (struct Object *) node;
unusedObject = (struct Object *) node;
2019-08-25 04:46:40 +00:00
2019-09-01 19:50:50 +00:00
if (gCurGraphNodeHeldObject != NULL) {
2019-12-02 02:52:53 +00:00
heldObject = gCurGraphNodeHeldObject->objNode;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
gfxHead = alloc_display_list(3 * sizeof(Gfx));
gfx = gfxHead;
obj->header.gfx.node.flags =
(obj->header.gfx.node.flags & 0xFF) | (GRAPH_NODE_TYPE_FUNCTIONAL | GRAPH_NODE_TYPE_400); // sets bits 8, 10 and zeros upper byte
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
gDPSetEnvColor(gfx++, 255, 255, 255, heldObject->oOpacity);
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
gSPEndDisplayList(gfx);
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return gfxHead;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* An absolute value function.
*/
2019-08-25 04:46:40 +00:00
f32 absf_2(f32 f) {
2019-09-01 19:50:50 +00:00
if (f < 0) {
2019-08-25 04:46:40 +00:00
f *= -1.0f;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
return f;
}
2019-12-02 02:52:53 +00:00
/**
* Turns an object away from floors/walls that it runs into.
*/
void turn_obj_away_from_surface(f32 velX, f32 velZ, f32 nX, UNUSED f32 nY, f32 nZ, f32 *objYawX,
2019-08-25 04:46:40 +00:00
f32 *objYawZ) {
2019-12-02 02:52:53 +00:00
*objYawX = (nZ * nZ - nX * nX) * velX / (nX * nX + nZ * nZ)
- 2 * velZ * (nX * nZ) / (nX * nX + nZ * nZ);
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
*objYawZ = (nX * nX - nZ * nZ) * velZ / (nX * nX + nZ * nZ)
- 2 * velX * (nX * nZ) / (nX * nX + nZ * nZ);
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Finds any wall collisions, applies them, and turns away from the surface.
*/
s32 obj_find_wall(f32 objNewX, f32 objY, f32 objNewZ, f32 objVelX, f32 objVelZ) {
2019-08-25 04:46:40 +00:00
struct WallCollisionData hitbox;
f32 wall_nX, wall_nY, wall_nZ, objVelXCopy, objVelZCopy, objYawX, objYawZ;
hitbox.x = objNewX;
hitbox.y = objY;
hitbox.z = objNewZ;
2020-02-03 05:51:26 +00:00
hitbox.offsetY = o->hitboxHeight / 2;
2019-08-25 04:46:40 +00:00
hitbox.radius = o->hitboxRadius;
if (find_wall_collisions(&hitbox) != 0) {
o->oPosX = hitbox.x;
o->oPosY = hitbox.y;
o->oPosZ = hitbox.z;
2019-12-02 02:52:53 +00:00
2019-08-25 04:46:40 +00:00
wall_nX = hitbox.walls[0]->normal.x;
wall_nY = hitbox.walls[0]->normal.y;
wall_nZ = hitbox.walls[0]->normal.z;
2019-12-02 02:52:53 +00:00
2019-08-25 04:46:40 +00:00
objVelXCopy = objVelX;
objVelZCopy = objVelZ;
2019-12-02 02:52:53 +00:00
// Turns away from the first wall only.
turn_obj_away_from_surface(objVelXCopy, objVelZCopy, wall_nX, wall_nY, wall_nZ, &objYawX, &objYawZ);
2019-08-25 04:46:40 +00:00
o->oMoveAngleYaw = atan2s(objYawZ, objYawX);
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return TRUE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Turns an object away from steep floors, similarly to walls.
*/
s32 turn_obj_away_from_steep_floor(struct Surface *objFloor, f32 floorY, f32 objVelX, f32 objVelZ) {
2019-08-25 04:46:40 +00:00
f32 floor_nX, floor_nY, floor_nZ, objVelXCopy, objVelZCopy, objYawX, objYawZ;
if (objFloor == NULL) {
2019-12-02 02:52:53 +00:00
//! (OOB Object Crash) TRUNC overflow exception after 36 minutes
2019-08-25 04:46:40 +00:00
o->oMoveAngleYaw += 32767.999200000002; /* ¯\_(ツ)_/¯ */
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
floor_nX = objFloor->normal.x;
floor_nY = objFloor->normal.y;
floor_nZ = objFloor->normal.z;
2019-12-02 02:52:53 +00:00
// If the floor is steep and we are below it (i.e. walking into it), turn away from the floor.
2019-08-25 04:46:40 +00:00
if (floor_nY < 0.5 && floorY > o->oPosY) {
objVelXCopy = objVelX;
objVelZCopy = objVelZ;
2019-12-02 02:52:53 +00:00
turn_obj_away_from_surface(objVelXCopy, objVelZCopy, floor_nX, floor_nY, floor_nZ, &objYawX,
2019-08-25 04:46:40 +00:00
&objYawZ);
o->oMoveAngleYaw = atan2s(objYawZ, objYawX);
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return TRUE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Orients an object with the given normals, typically the surface under the object.
*/
void obj_orient_graph(struct Object *obj, f32 normalX, f32 normalY, f32 normalZ) {
Vec3f objVisualPosition, surfaceNormals;
2019-08-25 04:46:40 +00:00
2019-10-05 19:08:05 +00:00
Mat4 *throwMatrix;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
// Passes on orienting certain objects that shouldn't be oriented, like boulders.
if (sOrientObjWithFloor == FALSE) {
2019-08-25 04:46:40 +00:00
return;
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
// Passes on orienting billboard objects, i.e. coins, trees, etc.
if ((obj->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) != 0) {
return;
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
2019-10-05 19:08:05 +00:00
throwMatrix = alloc_display_list(sizeof(*throwMatrix));
2019-12-02 02:52:53 +00:00
// If out of memory, fail to try orienting the object.
2019-09-01 19:50:50 +00:00
if (throwMatrix == NULL) {
2019-08-25 04:46:40 +00:00
return;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
objVisualPosition[0] = obj->oPosX;
objVisualPosition[1] = obj->oPosY + obj->oGraphYOffset;
objVisualPosition[2] = obj->oPosZ;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
surfaceNormals[0] = normalX;
surfaceNormals[1] = normalY;
surfaceNormals[2] = normalZ;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
mtxf_align_terrain_normal(*throwMatrix, surfaceNormals, objVisualPosition, obj->oFaceAngleYaw);
2019-08-25 04:46:40 +00:00
obj->header.gfx.throwMatrix = (void *) throwMatrix;
}
2019-12-02 02:52:53 +00:00
/**
* Determines an object's forward speed multiplier.
*/
void calc_obj_friction(f32 *objFriction, f32 floor_nY) {
2019-09-01 19:50:50 +00:00
if (floor_nY < 0.2 && o->oFriction < 0.9999) {
2019-08-25 04:46:40 +00:00
*objFriction = 0;
2019-09-01 19:50:50 +00:00
} else {
2019-08-25 04:46:40 +00:00
*objFriction = o->oFriction;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Updates an objects speed for gravity and updates Y position.
*/
void calc_new_obj_vel_and_pos_y(struct Surface *objFloor, f32 objFloorY, f32 objVelX, f32 objVelZ) {
2019-08-25 04:46:40 +00:00
f32 floor_nX = objFloor->normal.x;
f32 floor_nY = objFloor->normal.y;
f32 floor_nZ = objFloor->normal.z;
f32 objFriction;
2019-12-02 02:52:53 +00:00
// Caps vertical speed with a "terminal velocity".
2019-08-25 04:46:40 +00:00
o->oVelY -= o->oGravity;
2019-09-01 19:50:50 +00:00
if (o->oVelY > 75.0) {
2019-08-25 04:46:40 +00:00
o->oVelY = 75.0;
2019-09-01 19:50:50 +00:00
}
if (o->oVelY < -75.0) {
2019-08-25 04:46:40 +00:00
o->oVelY = -75.0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
o->oPosY += o->oVelY;
2019-12-02 02:52:53 +00:00
//Snap the object up to the floor.
2019-08-25 04:46:40 +00:00
if (o->oPosY < objFloorY) {
o->oPosY = objFloorY;
2019-12-02 02:52:53 +00:00
// Bounces an object if the ground is hit fast enough.
2019-09-01 19:50:50 +00:00
if (o->oVelY < -17.5) {
2020-02-03 05:51:26 +00:00
o->oVelY = -(o->oVelY / 2);
2019-09-01 19:50:50 +00:00
} else {
2019-08-25 04:46:40 +00:00
o->oVelY = 0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
//! (Obj Position Crash) If you got an object with height past 2^31, the game would crash.
2019-08-25 04:46:40 +00:00
if ((s32) o->oPosY >= (s32) objFloorY && (s32) o->oPosY < (s32) objFloorY + 37) {
2019-12-02 02:52:53 +00:00
obj_orient_graph(o, floor_nX, floor_nY, floor_nZ);
// Adds horizontal component of gravity for horizontal speed.
2019-08-25 04:46:40 +00:00
objVelX += floor_nX * (floor_nX * floor_nX + floor_nZ * floor_nZ)
/ (floor_nX * floor_nX + floor_nY * floor_nY + floor_nZ * floor_nZ) * o->oGravity
2019-09-01 19:50:50 +00:00
* 2;
2019-08-25 04:46:40 +00:00
objVelZ += floor_nZ * (floor_nX * floor_nX + floor_nZ * floor_nZ)
/ (floor_nX * floor_nX + floor_nY * floor_nY + floor_nZ * floor_nZ) * o->oGravity
2019-09-01 19:50:50 +00:00
* 2;
2019-12-02 02:52:53 +00:00
2019-09-01 19:50:50 +00:00
if (objVelX < 0.000001 && objVelX > -0.000001) {
2019-08-25 04:46:40 +00:00
objVelX = 0;
2019-09-01 19:50:50 +00:00
}
if (objVelZ < 0.000001 && objVelZ > -0.000001) {
2019-08-25 04:46:40 +00:00
objVelZ = 0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-09-01 19:50:50 +00:00
if (objVelX != 0 || objVelZ != 0) {
2019-08-25 04:46:40 +00:00
o->oMoveAngleYaw = atan2s(objVelZ, objVelX);
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
calc_obj_friction(&objFriction, floor_nY);
2019-08-25 04:46:40 +00:00
o->oForwardVel = sqrtf(objVelX * objVelX + objVelZ * objVelZ) * objFriction;
}
}
2019-12-02 02:52:53 +00:00
void calc_new_obj_vel_and_pos_y_underwater(struct Surface *objFloor, f32 floorY, f32 objVelX, f32 objVelZ,
2019-08-25 04:46:40 +00:00
f32 waterY) {
f32 floor_nX = objFloor->normal.x;
f32 floor_nY = objFloor->normal.y;
f32 floor_nZ = objFloor->normal.z;
f32 netYAccel = (1.0f - o->oBuoyancy) * (-1.0f * o->oGravity);
o->oVelY -= netYAccel;
2019-12-02 02:52:53 +00:00
// Caps vertical speed with a "terminal velocity".
2019-09-01 19:50:50 +00:00
if (o->oVelY > 75.0) {
2019-08-25 04:46:40 +00:00
o->oVelY = 75.0;
2019-09-01 19:50:50 +00:00
}
if (o->oVelY < -75.0) {
2019-08-25 04:46:40 +00:00
o->oVelY = -75.0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
o->oPosY += o->oVelY;
2019-12-02 02:52:53 +00:00
//Snap the object up to the floor.
2019-08-25 04:46:40 +00:00
if (o->oPosY < floorY) {
o->oPosY = floorY;
2019-12-02 02:52:53 +00:00
// Bounces an object if the ground is hit fast enough.
2019-09-01 19:50:50 +00:00
if (o->oVelY < -17.5) {
o->oVelY = -(o->oVelY / 2);
} else {
2019-08-25 04:46:40 +00:00
o->oVelY = 0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
// If moving fast near the surface of the water, flip vertical speed? To emulate skipping?
if (o->oForwardVel > 12.5 && (waterY + 30.0f) > o->oPosY && (waterY - 30.0f) < o->oPosY) {
2019-08-25 04:46:40 +00:00
o->oVelY = -o->oVelY;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
if ((s32) o->oPosY >= (s32) floorY && (s32) o->oPosY < (s32) floorY + 37) {
2019-12-02 02:52:53 +00:00
obj_orient_graph(o, floor_nX, floor_nY, floor_nZ);
// Adds horizontal component of gravity for horizontal speed.
2019-08-25 04:46:40 +00:00
objVelX += floor_nX * (floor_nX * floor_nX + floor_nZ * floor_nZ)
2019-09-01 19:50:50 +00:00
/ (floor_nX * floor_nX + floor_nY * floor_nY + floor_nZ * floor_nZ) * netYAccel * 2;
2019-08-25 04:46:40 +00:00
objVelZ += floor_nZ * (floor_nX * floor_nX + floor_nZ * floor_nZ)
2019-09-01 19:50:50 +00:00
/ (floor_nX * floor_nX + floor_nY * floor_nY + floor_nZ * floor_nZ) * netYAccel * 2;
2019-08-25 04:46:40 +00:00
}
2019-09-01 19:50:50 +00:00
if (objVelX < 0.000001 && objVelX > -0.000001) {
2019-08-25 04:46:40 +00:00
objVelX = 0;
2019-09-01 19:50:50 +00:00
}
if (objVelZ < 0.000001 && objVelZ > -0.000001) {
2019-08-25 04:46:40 +00:00
objVelZ = 0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-09-01 19:50:50 +00:00
if (o->oVelY < 0.000001 && o->oVelY > -0.000001) {
2019-08-25 04:46:40 +00:00
o->oVelY = 0;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-09-01 19:50:50 +00:00
if (objVelX != 0 || objVelZ != 0) {
2019-08-25 04:46:40 +00:00
o->oMoveAngleYaw = atan2s(objVelZ, objVelX);
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
// Decreases both vertical velocity and forward velocity. Likely so that skips above
// don't loop infinitely.
2019-08-25 04:46:40 +00:00
o->oForwardVel = sqrtf(objVelX * objVelX + objVelZ * objVelZ) * 0.8;
o->oVelY *= 0.8;
}
2019-12-02 02:52:53 +00:00
/**
* Updates an objects position from oForwardVel and oMoveAngleYaw.
*/
void obj_update_pos_vel_xz(void) {
2019-08-25 04:46:40 +00:00
f32 xVel = o->oForwardVel * sins(o->oMoveAngleYaw);
f32 zVel = o->oForwardVel * coss(o->oMoveAngleYaw);
o->oPosX += xVel;
o->oPosZ += zVel;
}
2019-12-02 02:52:53 +00:00
/**
* Generates splashes if at surface of water, entering water, or bubbles
* if underwater.
*/
void obj_splash(s32 waterY, s32 objY) {
2019-08-25 04:46:40 +00:00
u32 globalTimer = gGlobalTimer;
2019-12-02 02:52:53 +00:00
// Spawns waves if near surface of water and plays a noise if entering.
2019-08-25 04:46:40 +00:00
if ((f32)(waterY + 30) > o->oPosY && o->oPosY > (f32)(waterY - 30)) {
2020-03-02 03:42:52 +00:00
spawn_object(o, MODEL_IDLE_WATER_WAVE, bhvObjectWaterWave);
2019-12-02 02:52:53 +00:00
2019-09-01 19:50:50 +00:00
if (o->oVelY < -20.0f) {
2020-03-02 03:42:52 +00:00
cur_obj_play_sound_2(SOUND_OBJ_DIVING_INTO_WATER);
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
// Spawns bubbles if underwater.
2019-09-01 19:50:50 +00:00
if ((objY + 50) < waterY && (globalTimer & 0x1F) == 0) {
2019-12-02 02:52:53 +00:00
spawn_object(o, MODEL_WHITE_PARTICLE_SMALL, bhvObjectBubble);
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Generic object move function. Handles walls, water, floors, and gravity.
* Returns flags for certain interactions.
*/
2020-02-03 05:51:26 +00:00
s16 object_step(void) {
2019-08-25 04:46:40 +00:00
f32 objX = o->oPosX;
f32 objY = o->oPosY;
f32 objZ = o->oPosZ;
2019-12-02 02:52:53 +00:00
2019-08-25 04:46:40 +00:00
f32 floorY;
f32 waterY = -10000.0;
2019-12-02 02:52:53 +00:00
2019-08-25 04:46:40 +00:00
f32 objVelX = o->oForwardVel * sins(o->oMoveAngleYaw);
f32 objVelZ = o->oForwardVel * coss(o->oMoveAngleYaw);
2019-12-02 02:52:53 +00:00
2019-08-25 04:46:40 +00:00
s16 collisionFlags = 0;
2019-12-02 02:52:53 +00:00
// Find any wall collisions, receive the push, and set the flag.
if (obj_find_wall(objX + objVelX, objY, objZ + objVelZ, objVelX, objVelZ) == 0) {
2019-08-25 04:46:40 +00:00
collisionFlags += OBJ_COL_FLAG_HIT_WALL;
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
floorY = find_floor(objX + objVelX, objY, objZ + objVelZ, &sObjFloor);
if (turn_obj_away_from_steep_floor(sObjFloor, floorY, objVelX, objVelZ) == 1) {
2019-08-25 04:46:40 +00:00
waterY = find_water_level(objX + objVelX, objZ + objVelZ);
if (waterY > objY) {
2019-12-02 02:52:53 +00:00
calc_new_obj_vel_and_pos_y_underwater(sObjFloor, floorY, objVelX, objVelZ, waterY);
2019-08-25 04:46:40 +00:00
collisionFlags += OBJ_COL_FLAG_UNDERWATER;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
calc_new_obj_vel_and_pos_y(sObjFloor, floorY, objVelX, objVelZ);
2019-09-01 19:50:50 +00:00
}
} else {
2019-12-02 02:52:53 +00:00
// Treat any awkward floors similar to a wall.
2019-08-25 04:46:40 +00:00
collisionFlags +=
2019-12-02 02:52:53 +00:00
((collisionFlags & OBJ_COL_FLAG_HIT_WALL) ^ OBJ_COL_FLAG_HIT_WALL);
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
obj_update_pos_vel_xz();
2019-09-01 19:50:50 +00:00
if ((s32) o->oPosY == (s32) floorY) {
2019-08-25 04:46:40 +00:00
collisionFlags += OBJ_COL_FLAG_GROUNDED;
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
2019-09-01 19:50:50 +00:00
if ((s32) o->oVelY == 0) {
2019-08-25 04:46:40 +00:00
collisionFlags += OBJ_COL_FLAG_NO_Y_VEL;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
// Generate a splash if in water.
obj_splash((s32) waterY, (s32) o->oPosY);
2019-08-25 04:46:40 +00:00
return collisionFlags;
}
2019-12-02 02:52:53 +00:00
/**
* Takes an object step but does not orient with the object's floor.
* Used for boulders, falling pillars, and the rolling snowman body.
*/
2020-02-03 05:51:26 +00:00
s16 object_step_without_floor_orient(void) {
2019-08-25 04:46:40 +00:00
s16 collisionFlags = 0;
2019-12-02 02:52:53 +00:00
sOrientObjWithFloor = FALSE;
collisionFlags = object_step();
sOrientObjWithFloor = TRUE;
2019-08-25 04:46:40 +00:00
return collisionFlags;
}
/**
2019-12-02 02:52:53 +00:00
* Uses an object's forward velocity and yaw to move its X, Y, and Z positions.
* This does accept an object as an argument, though it is always called with `o`.
* If it wasn't called with `o`, it would modify `o`'s X and Z velocities based on
* `obj`'s forward velocity and yaw instead of `o`'s, and wouldn't update `o`'s
* position.
*/
2019-08-25 04:46:40 +00:00
void obj_move_xyz_using_fvel_and_yaw(struct Object *obj) {
o->oVelX = obj->oForwardVel * sins(obj->oMoveAngleYaw);
o->oVelZ = obj->oForwardVel * coss(obj->oMoveAngleYaw);
obj->oPosX += o->oVelX;
obj->oPosY += obj->oVelY;
obj->oPosZ += o->oVelZ;
}
2019-12-02 02:52:53 +00:00
/**
* Checks if a point is within distance from Mario's graphical position. Test is exclusive.
*/
2019-08-25 04:46:40 +00:00
s32 is_point_within_radius_of_mario(f32 x, f32 y, f32 z, s32 dist) {
f32 mGfxX = gMarioObject->header.gfx.pos[0];
f32 mGfxY = gMarioObject->header.gfx.pos[1];
f32 mGfxZ = gMarioObject->header.gfx.pos[2];
if ((x - mGfxX) * (x - mGfxX) + (y - mGfxY) * (y - mGfxY) + (z - mGfxZ) * (z - mGfxZ)
2019-09-01 19:50:50 +00:00
< (f32)(dist * dist)) {
2019-12-02 02:52:53 +00:00
return TRUE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Checks whether a point is within distance of a given point. Test is exclusive.
*/
s32 is_point_close_to_object(struct Object *obj, f32 x, f32 y, f32 z, s32 dist) {
2019-08-25 04:46:40 +00:00
f32 objX = obj->oPosX;
f32 objY = obj->oPosY;
f32 objZ = obj->oPosZ;
if ((x - objX) * (x - objX) + (y - objY) * (y - objY) + (z - objZ) * (z - objZ)
2019-09-01 19:50:50 +00:00
< (f32)(dist * dist)) {
2019-12-02 02:52:53 +00:00
return TRUE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Sets an object as visible if within a certain distance of Mario's graphical position.
*/
void set_object_visibility(struct Object *obj, s32 dist) {
2019-08-25 04:46:40 +00:00
f32 objX = obj->oPosX;
f32 objY = obj->oPosY;
f32 objZ = obj->oPosZ;
2019-12-02 02:52:53 +00:00
if (is_point_within_radius_of_mario(objX, objY, objZ, dist) == TRUE) {
obj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
obj->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Turns an object towards home if Mario is not near to it.
*/
s32 obj_return_home_if_safe(struct Object *obj, f32 homeX, f32 y, f32 homeZ, s32 dist) {
2019-08-25 04:46:40 +00:00
f32 homeDistX = homeX - obj->oPosX;
f32 homeDistZ = homeZ - obj->oPosZ;
2019-12-02 02:52:53 +00:00
s16 angleTowardsHome = atan2s(homeDistZ, homeDistX);
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
if (is_point_within_radius_of_mario(homeX, y, homeZ, dist) == TRUE) {
return TRUE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
obj->oMoveAngleYaw = approach_s16_symmetric(obj->oMoveAngleYaw, angleTowardsHome, 320);
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Randomly displaces an objects home if RNG says to, and turns the object towards its home.
*/
void obj_return_and_displace_home(struct Object *obj, f32 homeX, UNUSED f32 homeY, f32 homeZ, s32 baseDisp) {
2019-08-25 04:46:40 +00:00
s16 angleToNewHome;
f32 homeDistX, homeDistZ;
2020-04-03 18:57:26 +00:00
if ((s32)(random_float() * 50.0f) == 0) {
obj->oHomeX = (f32)(baseDisp * 2) * random_float() - (f32) baseDisp + homeX;
obj->oHomeZ = (f32)(baseDisp * 2) * random_float() - (f32) baseDisp + homeZ;
2019-08-25 04:46:40 +00:00
}
homeDistX = obj->oHomeX - obj->oPosX;
homeDistZ = obj->oHomeZ - obj->oPosZ;
angleToNewHome = atan2s(homeDistZ, homeDistX);
obj->oMoveAngleYaw = approach_s16_symmetric(obj->oMoveAngleYaw, angleToNewHome, 320);
}
2019-12-02 02:52:53 +00:00
/**
* A series of checks using sin and cos to see if a given angle is facing in the same direction
* of a given angle, within a certain range.
*/
s32 obj_check_if_facing_toward_angle(u32 base, u32 goal, s16 range) {
s16 dAngle = (u16) goal - (u16) base;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
if (((f32) sins(-range) < (f32) sins(dAngle)) && ((f32) sins(dAngle) < (f32) sins(range))
&& (coss(dAngle) > 0)) {
return TRUE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Finds any wall collisions and returns what the displacement vector would be.
*/
s32 obj_find_wall_displacement(Vec3f dist, f32 x, f32 y, f32 z, f32 radius) {
2019-08-25 04:46:40 +00:00
struct WallCollisionData hitbox;
UNUSED u8 filler[0x20];
hitbox.x = x;
hitbox.y = y;
hitbox.z = z;
hitbox.offsetY = 10.0f;
2019-12-02 02:52:53 +00:00
hitbox.radius = radius;
2019-08-25 04:46:40 +00:00
if (find_wall_collisions(&hitbox) != 0) {
dist[0] = hitbox.x - x;
dist[1] = hitbox.y - y;
dist[2] = hitbox.z - z;
2019-12-02 02:52:53 +00:00
return TRUE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
return FALSE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Spawns a number of coins at the location of an object
* with a random forward velocity, y velocity, and direction.
*/
void obj_spawn_yellow_coins(struct Object *obj, s8 nCoins) {
2019-08-25 04:46:40 +00:00
struct Object *coin;
s8 count;
2019-09-01 19:50:50 +00:00
for (count = 0; count < nCoins; count++) {
2019-08-25 04:46:40 +00:00
coin = spawn_object(obj, MODEL_YELLOW_COIN, bhvMovingYellowCoin);
2020-04-03 18:57:26 +00:00
coin->oForwardVel = random_float() * 20;
coin->oVelY = random_float() * 40 + 20;
coin->oMoveAngleYaw = random_u16();
2019-08-25 04:46:40 +00:00
}
}
2019-12-02 02:52:53 +00:00
/**
* Controls whether certain objects should flicker/when to despawn.
*/
s32 obj_flicker_and_disappear(struct Object *obj, s16 lifeSpan) {
if (obj->oTimer < lifeSpan) {
return FALSE;
2019-09-01 19:50:50 +00:00
}
2019-12-02 02:52:53 +00:00
if (obj->oTimer < lifeSpan + 40) {
2019-09-01 19:50:50 +00:00
if (obj->oTimer % 2 != 0) {
2019-12-02 02:52:53 +00:00
obj->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
obj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
} else {
obj->activeFlags = 0;
2019-12-02 02:52:53 +00:00
return TRUE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Checks if a given room is Mario's current room, even if on an object.
*/
s8 current_mario_room_check(s16 room) {
s16 result;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
// Since object surfaces have room 0, this tests if the surface is an
// object first and uses the last room if so.
2019-08-25 04:46:40 +00:00
if (gMarioCurrentRoom == 0) {
2019-12-02 02:52:53 +00:00
if (room == sPrevCheckMarioRoom) {
return TRUE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
return FALSE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
} else {
2019-12-02 02:52:53 +00:00
if (room == gMarioCurrentRoom) {
result = TRUE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
result = FALSE;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
sPrevCheckMarioRoom = gMarioCurrentRoom;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return result;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Triggers dialog when Mario is facing an object and controls it while in the dialog.
*/
s16 trigger_obj_dialog_when_facing(s32 *inDialog, s16 dialogID, f32 dist, s32 actionArg) {
s16 dialogueResponse;
if ((is_point_within_radius_of_mario(o->oPosX, o->oPosY, o->oPosZ, (s32) dist) == 1
&& obj_check_if_facing_toward_angle(o->oFaceAngleYaw, gMarioObject->header.gfx.angle[1] + 0x8000, 0x1000) == 1
&& obj_check_if_facing_toward_angle(o->oMoveAngleYaw, o->oAngleToMario, 0x1000) == 1)
|| (*inDialog == 1)) {
*inDialog = 1;
if (set_mario_npc_dialog(actionArg) == 2) { //If Mario is speaking.
2020-01-03 15:38:57 +00:00
dialogueResponse = cutscene_object_with_dialog(CUTSCENE_DIALOG, o, dialogID);
2019-12-02 02:52:53 +00:00
if (dialogueResponse != 0) {
2019-08-25 04:46:40 +00:00
set_mario_npc_dialog(0);
2019-12-02 02:52:53 +00:00
*inDialog = 0;
return dialogueResponse;
2019-08-25 04:46:40 +00:00
}
return 0;
}
}
return 0;
}
2019-12-02 02:52:53 +00:00
/**
*Checks if a floor is one that should cause an object to "die".
*/
void obj_check_floor_death(s16 collisionFlags, struct Surface *floor) {
2019-09-01 19:50:50 +00:00
if (floor == NULL) {
2019-08-25 04:46:40 +00:00
return;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
if ((collisionFlags & OBJ_COL_FLAG_GROUNDED) == 1)
2019-08-25 04:46:40 +00:00
{
switch (floor->type) {
case SURFACE_BURNING:
o->oAction = OBJ_ACT_LAVA_DEATH;
break;
2019-12-02 02:52:53 +00:00
//! @BUG Doesn't check for the vertical wind death floor.
2019-08-25 04:46:40 +00:00
case SURFACE_DEATH_PLANE:
o->oAction = OBJ_ACT_DEATH_PLANE_DEATH;
break;
default:
break;
}
}
}
2019-12-02 02:52:53 +00:00
/**
* Controls an object dying in lava by creating smoke, sinking the object, playing
* audio, and eventually despawning it. Returns TRUE when the obj is dead.
*/
s32 obj_lava_death(void) {
2019-08-25 04:46:40 +00:00
struct Object *deathSmoke;
if (o->oTimer >= 31) {
o->activeFlags = 0;
2019-12-02 02:52:53 +00:00
return TRUE;
2019-09-01 19:50:50 +00:00
} else {
2019-12-02 02:52:53 +00:00
// Sinking effect
2019-08-25 04:46:40 +00:00
o->oPosY -= 10.0f;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
if ((o->oTimer % 8) == 0) {
2020-03-02 03:42:52 +00:00
cur_obj_play_sound_2(SOUND_OBJ_BULLY_EXPLODE_2);
2019-08-25 04:46:40 +00:00
deathSmoke = spawn_object(o, MODEL_SMOKE, bhvBobombBullyDeathSmoke);
2020-04-03 18:57:26 +00:00
deathSmoke->oPosX += random_float() * 20.0f;
deathSmoke->oPosY += random_float() * 20.0f;
deathSmoke->oPosZ += random_float() * 20.0f;
deathSmoke->oForwardVel = random_float() * 10.0f;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
return FALSE;
2019-08-25 04:46:40 +00:00
}
2019-12-02 02:52:53 +00:00
/**
* Spawns an orange number object relatively, such as those that count up for secrets.
*/
void spawn_orange_number(s8 behParam, s16 relX, s16 relY, s16 relZ) {
2019-08-25 04:46:40 +00:00
struct Object *orangeNumber;
2019-12-02 02:52:53 +00:00
if (behParam >= 10) {
2019-08-25 04:46:40 +00:00
return;
2019-09-01 19:50:50 +00:00
}
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
orangeNumber = spawn_object_relative(behParam, relX, relY, relZ, o, MODEL_NUMBER, bhvOrangeNumber);
2019-08-25 04:46:40 +00:00
orangeNumber->oPosY += 25.0f;
}
2019-12-02 02:52:53 +00:00
/**
* Unused variables for debug_sequence_tracker.
*/
s8 sDebugSequenceTracker = 0;
s8 sDebugTimer = 0;
2019-08-25 04:46:40 +00:00
2019-12-02 02:52:53 +00:00
/**
* Unused presumably debug function that tracks for a sequence of inputs,
* perhaps for the Konami code sequence of inputs.
*/
s32 UNUSED debug_sequence_tracker(s16 debugInputSequence[]) {
// If end of sequence reached, return true.
if (debugInputSequence[sDebugSequenceTracker] == 0) {
sDebugSequenceTracker = 0;
return TRUE;
}
// If the third controller button pressed is next in sequence, reset timer and progress to next value.
if ((debugInputSequence[sDebugSequenceTracker] & gPlayer3Controller->buttonPressed) != 0) {
sDebugSequenceTracker++;
sDebugTimer = 0;
// If wrong input or timer reaches 10, reset sequence progress.
} else if (sDebugTimer == 10 || gPlayer3Controller->buttonPressed != 0) {
sDebugSequenceTracker = 0;
sDebugTimer = 0;
return FALSE;
}
sDebugTimer++;
return FALSE;
2019-08-25 04:46:40 +00:00
}
#include "behaviors/moving_coin.inc.c"
#include "behaviors/seaweed.inc.c"
#include "behaviors/bobomb.inc.c"
#include "behaviors/cannon_door.inc.c"
#include "behaviors/whirlpool.inc.c"
#include "behaviors/amp.inc.c"
#include "behaviors/butterfly.inc.c"
#include "behaviors/hoot.inc.c"
#include "behaviors/beta_holdable_object.inc.c"
#include "behaviors/bubble.inc.c"
#include "behaviors/water_wave.inc.c"
#include "behaviors/explosion.inc.c"
#include "behaviors/corkbox.inc.c"
#include "behaviors/bully.inc.c"
#include "behaviors/water_ring.inc.c"
#include "behaviors/bowser_bomb.inc.c"
#include "behaviors/celebration_star.inc.c"
#include "behaviors/drawbridge.inc.c"
#include "behaviors/bomp.inc.c"
#include "behaviors/sliding_platform.inc.c"
#include "behaviors/moneybag.inc.c"
#include "behaviors/bowling_ball.inc.c"
#include "behaviors/cruiser.inc.c"
#include "behaviors/spindel.inc.c"
#include "behaviors/pyramid_wall.inc.c"
#include "behaviors/pyramid_elevator.inc.c"
#include "behaviors/pyramid_top.inc.c"
2019-09-01 19:50:50 +00:00
#include "behaviors/sound_waterfall.inc.c"
#include "behaviors/sound_volcano.inc.c"
#include "behaviors/castle_flag.inc.c"
#include "behaviors/sound_birds.inc.c"
#include "behaviors/sound_ambient.inc.c"
#include "behaviors/sound_sand.inc.c"
#include "behaviors/castle_cannon_grate.inc.c"
2019-08-25 04:46:40 +00:00
#include "behaviors/snowman.inc.c"
#include "behaviors/boulder.inc.c"
#include "behaviors/cap.inc.c"
#include "behaviors/spawn_star.inc.c"
#include "behaviors/red_coin.inc.c"
#include "behaviors/hidden_star.inc.c"
#include "behaviors/rolling_log.inc.c"
#include "behaviors/mushroom_1up.inc.c"
#include "behaviors/controllable_platform.inc.c"
#include "behaviors/breakable_box_small.inc.c"
#include "behaviors/snow_mound.inc.c"
#include "behaviors/floating_platform.inc.c"
#include "behaviors/arrow_lift.inc.c"
#include "behaviors/orange_number.inc.c"
#include "behaviors/manta_ray.inc.c"
#include "behaviors/falling_pillar.inc.c"
#include "behaviors/floating_box.inc.c"
#include "behaviors/decorative_pendulum.inc.c"
#include "behaviors/treasure_chest.inc.c"
#include "behaviors/mips.inc.c"
#include "behaviors/yoshi.inc.c"