/* * File: z_sub_s.c * Description: Various miscellaneous helpers */ #include "global.h" #include "overlays/actors/ovl_En_Door/z_en_door.h" s16 sPathDayFlags[] = { 0x40, 0x20, 0x10, 8, 4, 2, 1, 0 }; #include "code/sub_s/sub_s.c" Vec3f gOneVec3f = { 1.0f, 1.0f, 1.0f }; s32 D_801C5DBC[] = { 0, 1 }; // Unused /** * Finds the first EnDoor instance with unk_1A4 == 5 and the specified switchFlag. */ EnDoor* SubS_FindDoor(PlayState* play, s32 switchFlag) { Actor* actor = NULL; EnDoor* door; while (true) { actor = SubS_FindActor(play, actor, ACTORCAT_DOOR, ACTOR_EN_DOOR); door = (EnDoor*)actor; if (actor == NULL) { break; } if ((door->unk_1A4 == 5) && (door->switchFlag == (u8)switchFlag)) { break; } if (actor->next == NULL) { door = NULL; break; } actor = actor->next; } return door; } Gfx* SubS_DrawTransformFlexLimb(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, TransformLimbDraw transformLimbDraw, Actor* actor, Mtx** mtx, Gfx* gfx) { StandardLimb* limb; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; newDList = limbDList = limb->dList; if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &newDList, &pos, &rot, actor, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); Matrix_Push(); transformLimbDraw(play, limbIndex, actor, &gfx); if (newDList != NULL) { Matrix_ToMtx(*mtx); gSPMatrix(gfx++, *mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(gfx++, newDList); (*mtx)++; } else if (limbDList != NULL) { Matrix_ToMtx(*mtx); (*mtx)++; } Matrix_Pop(); } if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &limbDList, &rot, actor, &gfx); } if (limb->child != LIMB_DONE) { gfx = SubS_DrawTransformFlexLimb(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, transformLimbDraw, actor, mtx, gfx); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { gfx = SubS_DrawTransformFlexLimb(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, transformLimbDraw, actor, mtx, gfx); } return gfx; } /** * Draw all limbs of type `StandardLimb` in a given flexible skeleton * Limbs in a flexible skeleton have meshes that can stretch to line up with other limbs. * An array of matrices is dynamically allocated so each limb can access any transform to ensure its meshes line up. * * Also makes use of a `TransformLimbDraw`, which transforms limbs based on world coordinates, as opposed to local limb * coordinates. * Note that the `TransformLimbDraw` does not have a NULL check, so must be provided even if empty. */ Gfx* SubS_DrawTransformFlex(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, TransformLimbDraw transformLimbDraw, Actor* actor, Gfx* gfx) { StandardLimb* rootLimb; s32 pad; Gfx* newDlist; Gfx* limbDList; Vec3f pos; Vec3s rot; Mtx* mtx = GRAPH_ALLOC(play->state.gfxCtx, ALIGN16(dListCount * sizeof(Mtx))); if (skeleton == NULL) { return NULL; } gSPSegment(gfx++, 0x0D, mtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable->x; pos.y = jointTable->y; pos.z = jointTable->z; rot = jointTable[1]; newDlist = rootLimb->dList; limbDList = rootLimb->dList; if (overrideLimbDraw == NULL || !overrideLimbDraw(play, 1, &newDlist, &pos, &rot, actor, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); Matrix_Push(); transformLimbDraw(play, 1, actor, &gfx); if (newDlist != NULL) { Matrix_ToMtx(mtx); gSPMatrix(gfx++, mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(gfx++, newDlist); mtx++; } else if (limbDList != NULL) { Matrix_ToMtx(mtx); mtx++; } Matrix_Pop(); } if (postLimbDraw != NULL) { postLimbDraw(play, 1, &limbDList, &rot, actor, &gfx); } if (rootLimb->child != LIMB_DONE) { gfx = SubS_DrawTransformFlexLimb(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, transformLimbDraw, actor, &mtx, gfx); } Matrix_Pop(); return gfx; } s32 SubS_InCsMode(PlayState* play) { s32 inCsMode = false; if (Play_InCsMode(play)) { inCsMode = true; } return inCsMode; } /** * Computes a limb's position and rotation for use in TransformLimbDraws * * @param[in] newRotZ value to override newRot's Z value if override is true * @param[in] newRotY value to override newRot's Y value if override is true * @param[out] pos limb's computed position * @param[out] rot limb's computed rotation * @param[in] stepRot boolean, step towards newRot instead of setting directly * @param[in] overrideRot boolean, override newRot with the specified input. * * @note if overrideRot is true, the rotation will automatically step instead of setting directly */ s32 SubS_UpdateLimb(s16 newRotZ, s16 newRotY, Vec3f* pos, Vec3s* rot, s32 stepRot, s32 overrideRot) { Vec3f newPos; Vec3f zeroVec = gZeroVec3f; Vec3s newRot; MtxF curState; Matrix_MultVec3f(&zeroVec, &newPos); Matrix_Get(&curState); Matrix_MtxFToYXZRot(&curState, &newRot, MTXMODE_NEW); *pos = newPos; if (!stepRot && !overrideRot) { rot->x = newRot.x; rot->y = newRot.y; rot->z = newRot.z; return true; } if (overrideRot) { newRot.z = newRotZ; newRot.y = newRotY; } Math_SmoothStepToS(&rot->x, newRot.x, 3, 0x2AA8, 0xB6); Math_SmoothStepToS(&rot->y, newRot.y, 3, 0x2AA8, 0xB6); Math_SmoothStepToS(&rot->z, newRot.z, 3, 0x2AA8, 0xB6); return true; } void SubS_UpdateFlags(u16* flags, u16 setBits, u16 unsetBits) { *flags = (*flags & ~unsetBits) | setBits; } /** * Fills the knot array to be used with time paths * * The default knot array just pads with `order` duplicate knots of the first knot at the front and of the last knot * at the end. * * @param[out] knots an array of values that are used to compute the progress and the individual weights * @param[in] order the order of the interpolation i.e. the number of points in the interpolation * @param[in] numPoints the number of points to fill, generally the path count + order * * @note Same note as SubS_TimePathing_Update() */ void SubS_TimePathing_FillKnots(f32 knots[], s32 order, s32 numPoints) { s32 i; f32 val = 0.0f; for (i = 0; i < numPoints; i++) { if ((i >= order) && (i < (numPoints - order + 1))) { val += 1.0f; } knots[i] = val; } } typedef enum { /* 0 */ SUBS_TIME_PATHING_PROGRESS_STATUS_ERROR, /* 1 */ SUBS_TIME_PATHING_PROGRESS_STATUS_STILL_ON_PATH, /* 2 */ SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END } SUBS_TIME_PATHING_PROGRESS_STATUS; /** * Computes the progress to be used with time paths * * @param[out] progress the progress along the path, used to compute the weights * @param[in] elapsedTime how much time has passed * @param[in] waypointTime how much time per each waypoint * @param[in] totalTime how much time the path should take to travel * @param[in] pathCount the path count * @param[in] order the order of the interpolation i.e. the number of points in the interpolation * @param[in] knots see SubS_TimePathing_FillKnots() * * @return see SUBS_TIME_PATHING_PROGRESS_STATUS */ s32 SubS_TimePathing_ComputeProgress(f32* progress, s32 elapsedTime, s32 waypointTime, s32 totalTime, s32 pathCount, s32 order, f32 knots[]) { s32 i; s32 j; s32 k; f32 waypointTimeInv; // The fraction of a waypoint a single unit of time contains *progress = 0.0f; if ((waypointTime <= 0) || (elapsedTime < 0)) { return SUBS_TIME_PATHING_PROGRESS_STATUS_ERROR; } // When using the knots from SubS_TimePathing_FillKnots() these nested loops seem to simplify to // *progress = (f32)elapsedTime / (f32)waypointTime; waypointTimeInv = 1.0f / waypointTime; k = 0; for (i = order - 1; i < pathCount; i++) { for (j = 0; j < waypointTime; j++) { if (k == elapsedTime) { break; } *progress += (knots[i + 1] - knots[i]) * waypointTimeInv; k++; } } return (elapsedTime == totalTime) ? SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END : SUBS_TIME_PATHING_PROGRESS_STATUS_STILL_ON_PATH; } /** * Computes the interpolation weights to be used with time paths * * Seems to use some kind of B-Spline interpolation algorithm * * @param[in] order the order of the interpolation i.e. the number of points in the interpolation, max is 10 * @param[in] progress see SubS_TimePathing_ComputeProgress() * @param[in] waypoint the current waypoint * @param[in] knots see SubS_TimePathing_FillKnots() * @param[out] weights how much to weight each point considered */ void SubS_TimePathing_ComputeWeights(s32 order, f32 progress, s32 waypoint, f32 knots[], f32 weights[]) { f32 weightsTemp[10][11]; s32 i; s32 j; s32 k; for (i = 0; i < order; i++) { for (j = 0; j < order + 1; j++) { weightsTemp[i][j] = 0.0f; } } weightsTemp[0][order - 1] = 1.0f; for (i = 1; i < order; i++) { for (j = waypoint - i, k = (order - 1) - i; j <= waypoint; j++, k++) { if (knots[j + i] != knots[j]) { weightsTemp[i][k] = ((progress - knots[j]) / (knots[j + i] - knots[j])) * weightsTemp[i - 1][k]; } else { weightsTemp[i][k] = 0.0f; } if (knots[j + i + 1] != knots[j + 1]) { weightsTemp[i][k] += ((knots[j + i + 1] - progress) / (knots[j + i + 1] - knots[j + 1])) * weightsTemp[i - 1][k + 1]; } } } for (j = 0; j < order; j++) { weights[j] = weightsTemp[order - 1][j]; } } /** * Computes the X and Z component of the position to move to in time based paths * * @param[out] x computed x position * @param[out] z computed z position * @param[in] progress see SubS_TimePathing_ComputeProgress() * @param[in] order the order of the interpolation i.e. the number of points in the interpolation, max is 10 * @param[in] waypoint the current waypoint * @param[in] points the path's points * @param[in] knots see SubS_TimePathing_FillKnots() */ void SubS_TimePathing_ComputeTargetPosXZ(f32* x, f32* z, f32 progress, s32 order, s32 waypoint, Vec3s points[], f32 knots[]) { f32 xPos; f32 zPos; f32 weights[11]; f32 weightedX; f32 weightedZ; f32 weightedTotal; s32 i; SubS_TimePathing_ComputeWeights(order, progress, waypoint, knots, weights); weightedTotal = 0.0f; weightedZ = 0.0f; weightedX = 0.0f; for (i = 0; i < order; i++) { xPos = points[waypoint - order + i + 1].x; zPos = points[waypoint - order + i + 1].z; weightedX += weights[i] * xPos; weightedZ += weights[i] * zPos; weightedTotal += weights[i]; } *x = weightedX / weightedTotal; *z = weightedZ / weightedTotal; } /** * Updates a time based path that an actor follows by: * - Computing the X and Z components of the next point to move to * - Updating the waypoint * - Updating the time * * @param[in] path * @param[out] progress see SubS_TimePathing_ComputeProgress() * @param[in,out] elapsedTime how much time has passed * @param[in] waypointTime how much time per each waypoint * @param[in] totalTime how much time the path should take to travel * @param[in,out] waypoint the current waypoint, this and the previous two points will be used to compute the targetPos * @param[in] knots see SubS_TimePathing_FillKnots() * @param[out] targetPos the computed position to move to * @param[in] timeSpeed how fast time moves * * @return s32 returns true when the end has been reached. * * @note This system/function makes a couple of assumptions about the order used: * 1. the order is assumed to be 3, see SUBS_TIME_PATHING_ORDER * 2. even if SUBS_TIME_PATHING_ORDER is updated, the order can only be a max of 10 */ s32 SubS_TimePathing_Update(Path* path, f32* progress, s32* elapsedTime, s32 waypointTime, s32 totalTime, s32* waypoint, f32 knots[], Vec3f* targetPos, s32 timeSpeed) { Vec3s* points = Lib_SegmentedToVirtual(path->points); s32 state; f32 endX; f32 endZ; s32 reachedEnd = false; if (*waypoint >= path->count) { state = SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END; } else { state = SubS_TimePathing_ComputeProgress(progress, *elapsedTime, waypointTime, totalTime, path->count, SUBS_TIME_PATHING_ORDER, knots); } switch (state) { case SUBS_TIME_PATHING_PROGRESS_STATUS_STILL_ON_PATH: reachedEnd = false; SubS_TimePathing_ComputeTargetPosXZ(&targetPos->x, &targetPos->z, *progress, SUBS_TIME_PATHING_ORDER, *waypoint, points, knots); break; case SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END: endX = points[path->count - 1].x; endZ = points[path->count - 1].z; targetPos->x = endX * 1; targetPos->z = endZ * 1; reachedEnd = true; break; } *elapsedTime += timeSpeed; if (*elapsedTime >= totalTime) { *elapsedTime = totalTime; } else if (*elapsedTime < 0) { *elapsedTime = 0; } *waypoint = (*elapsedTime / waypointTime) + (SUBS_TIME_PATHING_ORDER - 1); return reachedEnd; } /** * Computes the initial Y component of a time based path * * @param[in] play * @param[in] path * @param[in] waypoint the current waypoint, this and the previous two points will be used to compute the target pos * @param[out] targetPos the computed position to move to, only the Y component has meaning * * @note Same note as SubS_TimePathing_Update() */ void SubS_TimePathing_ComputeInitialY(PlayState* play, Path* path, s32 waypoint, Vec3f* targetPos) { Vec3s* points = Lib_SegmentedToVirtual(path->points); Vec3f posA; Vec3f posB; Vec3f posResult; s32 i = waypoint - (SUBS_TIME_PATHING_ORDER - 1); s16 max; s16 min; s32 isSetup; CollisionPoly* outPoly = NULL; s32 bgId = 0; max = 0; min = 0; isSetup = false; for (; i <= waypoint; i++) { if (isSetup) { if (max < points[i].y) { max = points[i].y; } if (points[i].y < min) { min = points[i].y; } } else { max = min = points[i].y; } isSetup = true; } max += 30; min -= 30; posA = *targetPos; posB = *targetPos; posA.y = max; posB.y = min; if (BgCheck_EntityLineTest1(&play->colCtx, &posA, &posB, &posResult, &outPoly, true, true, true, true, &bgId)) { targetPos->y = posResult.y; } } Path* SubS_GetAdditionalPath(PlayState* play, u8 pathIndex, s32 limit) { Path* path; s32 i = 0; do { path = &play->setupPathList[pathIndex]; if (i >= limit) { break; } pathIndex = path->unk1; i++; } while (pathIndex != 0xFF); return path; } /** * Finds the nearest actor instance of a specified Id and category to an actor. */ Actor* SubS_FindNearestActor(Actor* actor, PlayState* play, u8 actorCategory, s16 actorId) { Actor* actorIter = NULL; Actor* actorTmp; f32 dist; Actor* closestActor = NULL; f32 minDist = 99999.0f; s32 isSetup = false; do { actorIter = SubS_FindActor(play, actorIter, actorCategory, actorId); actorTmp = actorIter; if (actorTmp == NULL) { break; } actorIter = actorTmp; if (actorIter != actor) { dist = Actor_DistanceBetweenActors(actor, actorIter); if (!isSetup || dist < minDist) { closestActor = actorIter; minDist = dist; isSetup = true; } } actorIter = actorIter->next; } while (actorIter != NULL); return closestActor; } s32 SubS_ChangeAnimationByInfoS(SkelAnime* skelAnime, AnimationInfoS* animations, s32 index) { s32 endFrame; s32 startFrame; animations += index; endFrame = animations->frameCount; if (animations->frameCount < 0) { endFrame = Animation_GetLastFrame(&animations->animation->common); } startFrame = animations->startFrame; if (startFrame >= endFrame || startFrame < 0) { return false; } if (animations->playSpeed < 0.0f) { SWAP(s32, endFrame, startFrame); } Animation_Change(skelAnime, animations->animation, animations->playSpeed, startFrame, endFrame, animations->mode, animations->morphFrames); return true; } s32 SubS_HasReachedPoint(Actor* actor, Path* path, s32 pointIndex) { Vec3s* points = Lib_SegmentedToVirtual(path->points); s32 count = path->count; s32 index = pointIndex; s32 reached = false; f32 diffX; f32 diffZ; f32 px; f32 pz; f32 d; Vec3f point; Math_Vec3s_ToVec3f(&point, &points[index]); if (index == 0) { diffX = points[1].x - points[0].x; diffZ = points[1].z - points[0].z; } else if (index == count - 1) { diffX = points[count - 1].x - points[count - 2].x; diffZ = points[count - 1].z - points[count - 2].z; } else { diffX = points[index + 1].x - points[index - 1].x; diffZ = points[index + 1].z - points[index - 1].z; } func_8017B7F8(&point, RADF_TO_BINANG(func_80086B30(diffX, diffZ)), &px, &pz, &d); if (((px * actor->world.pos.x) + (pz * actor->world.pos.z) + d) > 0.0f) { reached = true; } return reached; } Path* SubS_GetDayDependentPath(PlayState* play, u8 pathIndex, u8 max, s32* startPointIndex) { Path* path = NULL; s32 found = false; s32 time = (((s16)TIME_TO_MINUTES_F(gSaveContext.save.time) % 60) + ((s16)TIME_TO_MINUTES_F(gSaveContext.save.time) / 60) * 60) / 30; s32 day = CURRENT_DAY; if (pathIndex == max) { return NULL; } while (pathIndex != 0xFF) { path = &play->setupPathList[pathIndex]; if (sPathDayFlags[day] & path->unk2) { found = true; break; } pathIndex = path->unk1; } if (found == true) { *startPointIndex = time; *startPointIndex = CLAMP(*startPointIndex, 0, path->count - 1); } else { *startPointIndex = 0; } return path; } /** * Computes the point to move toward using a weight based algorithm that considers 4 points along the path * * @param path * @param waypoint the current waypoint, this and the previous three points will be used to compute the point * @param point the point computed * @param progress the main weight value used to compute the weights for the points considered * @param direction the direciton along the path to move, 1 for forwards, anything else for backwards * * @note only computes X and Z components of the point */ s32 SubS_WeightPathing_ComputePoint(Path* path, s32 waypoint, Vec3f* point, f32 progress, s32 direction) { s32 i; f32 weight0; f32 weight1; f32 weight2; f32 weight3; s32 lastPoint; s32 secondLastPoint; s32 secondPoint; s32 firstPoint; f32 xPoints[4]; f32 zPoints[4]; f32 oneMinusProgress; f32 squared; f32 cubed; Vec3s* points; s32 count = path->count; s32 pointIndex; s32 tmp; if (path == NULL) { return false; } if (direction == 1) { if (waypoint <= 2) { pointIndex = 2; } else { pointIndex = (waypoint == 3) ? 3 : waypoint; } for (i = 0; i < 4; i++, pointIndex--) { if (pointIndex <= 0) { pointIndex = 0; } points = Lib_SegmentedToVirtual(path->points); points = &points[pointIndex]; xPoints[i] = points->x; zPoints[i] = points->z; } lastPoint = count - 1; secondLastPoint = count - 2; secondPoint = 3; firstPoint = 2; } else { if (waypoint >= count - 3) { pointIndex = count - 3; } else { tmp = waypoint + 4; pointIndex = (count == tmp) ? count - 4 : waypoint; } for (i = 0; i < 4; i++, pointIndex++) { if (pointIndex >= path->count) { pointIndex = path->count - 1; } points = Lib_SegmentedToVirtual(path->points); points = &points[pointIndex]; xPoints[i] = points->x; zPoints[i] = points->z; } lastPoint = 0; secondLastPoint = 1; secondPoint = count - 4; firstPoint = count - 3; } if (waypoint == lastPoint) { oneMinusProgress = 1.0f - progress; squared = progress * progress; cubed = progress * squared; weight0 = oneMinusProgress * oneMinusProgress * oneMinusProgress; weight1 = (1.75f * cubed) - (4.5f * squared) + (3.0f * progress); weight2 = ((-11.0f / 12.0f) * cubed) + (1.5f * squared); weight3 = (1.0f / 6.0f) * cubed; } else if (waypoint == secondLastPoint) { oneMinusProgress = 1.0f - progress; squared = progress * progress; cubed = progress * squared; weight0 = oneMinusProgress * oneMinusProgress * oneMinusProgress * ((void)0, 0.25f); //! FAKE: weight1 = ((7.0f / 12.0f) * cubed) - (1.25f * squared) + (0.25f * progress) + (7.0f / 12.0f); weight2 = (-0.5f * cubed) + (0.5f * squared) + (progress * 0.5f) + (1.0f / 6.0f); weight3 = cubed * (1.0f / 6.0f); } else if (waypoint == secondPoint) { oneMinusProgress = 1.0f - progress; squared = oneMinusProgress * oneMinusProgress; cubed = oneMinusProgress * squared; weight0 = (1.0f / 6.0f) * cubed; weight1 = (-0.5f * cubed) + (0.5f * squared) + (0.5f * oneMinusProgress) + (1.0f / 6.0f); weight2 = ((7.0f / 12.0f) * cubed) - (1.25f * squared) + (0.25f * oneMinusProgress) + (7.0f / 12.0f); weight3 = progress * progress * progress * 0.25f; } else if (((direction == 1) && (firstPoint >= waypoint)) || ((direction != 1) && (waypoint >= firstPoint))) { oneMinusProgress = 1.0f - progress; squared = oneMinusProgress * oneMinusProgress; cubed = oneMinusProgress * squared; weight0 = (1.0f / 6.0f) * cubed; weight1 = ((-11.0f / 12.0f) * cubed) + (1.5f * squared); weight2 = (1.75f * cubed) - (4.5f * squared) + (3.0f * oneMinusProgress); weight3 = progress * progress * progress; } else { oneMinusProgress = 1.0f - progress; squared = progress * progress; cubed = squared * progress; weight0 = oneMinusProgress * oneMinusProgress; weight0 = oneMinusProgress * weight0 / 6.0f; weight1 = (cubed * 0.5f) - squared + (2.0f / 3.0f); weight2 = (cubed / -2.0f) + (squared * 0.5f) + (progress * 0.5f) + (1.0f / 6.0f); weight3 = cubed / 6.0f; } point->x = (weight0 * xPoints[0]) + (weight1 * xPoints[1]) + (weight2 * xPoints[2]) + (weight3 * xPoints[3]); point->z = (weight0 * zPoints[0]) + (weight1 * zPoints[1]) + (weight2 * zPoints[2]) + (weight3 * zPoints[3]); return true; } // WeightPathing System is completely unused /** * Moves an actor based on a weight based algorithm that takes into account 4 points along the path * * @param actor * @param path * @param waypoint the current waypoint, this and the previous three points will be used to move forward * @param progress the progress towards a given waypoint, used to compute the weights * @param direction the direction along the path to move, 1 for forwards, anything else for backwards * @param returnStart boolean, true if the actor should wrap back to start when reaching the end * * @return s32 true if actor reached the end of the path in this iteration, false otherwise */ s32 SubS_WeightPathing_Move(Actor* actor, Path* path, s32* waypoint, f32* progress, s32 direction, s32 returnStart) { Vec3f worldPos = actor->world.pos; Vec3f velocity = actor->velocity; Vec3f point; f32 dist; if (((direction != 1) && (*waypoint >= (path->count - 2))) || ((direction == 1) && (*waypoint < 2))) { return false; } while (true) { if (!SubS_WeightPathing_ComputePoint(path, *waypoint, &point, *progress, direction) || ((s32)(actor->speedXZ * 10000.0f) == 0)) { return false; } dist = Math_Vec3f_DistXZ(&actor->world.pos, &point); actor->world.rot.y = Math_Vec3f_Yaw(&actor->world.pos, &point); Actor_MoveWithGravity(actor); if (Math_Vec3f_DistXZ(&actor->world.pos, &point) < dist) { break; } *progress += 0.1f; if (*progress >= 1.1f) { if (direction != 1) { (*waypoint)++; if (*waypoint >= (path->count - 2)) { if (returnStart) { *waypoint = 0; } else { return true; } } } else { (*waypoint)--; if (*waypoint < 2) { if (returnStart) { *waypoint = path->count - 2; } else { return true; } } } *progress = 0.0f; } actor->world.pos = worldPos; actor->velocity = velocity; } return false; } s32 SubS_CopyPointFromPathCheckBounds(Path* path, s32 pointIndex, Vec3f* dst) { Vec3s* point; if ((path == NULL) || (pointIndex >= path->count) || (pointIndex < 0)) { return false; } point = Lib_SegmentedToVirtual(path->points); point = &point[pointIndex]; dst->x = point->x; dst->y = point->y; dst->z = point->z; return true; } //! TODO: Needs docs with func_800B8500 s32 func_8013C964(Actor* actor, PlayState* play, f32 xzRange, f32 yRange, s32 itemId, s32 type) { s32 ret = false; s16 x; s16 y; f32 xzDistToPlayerTemp; Actor_GetScreenPos(play, actor, &x, &y); switch (type) { case 1: yRange = fabsf(actor->playerHeightRel) + 1.0f; xzRange = actor->xzDistToPlayer + 1.0f; ret = Actor_PickUp(actor, play, itemId, xzRange, yRange); break; case 2: if ((fabsf(actor->playerHeightRel) <= yRange) && (actor->xzDistToPlayer <= xzRange)) { ret = func_800B8500(actor, play, xzRange, yRange, itemId); } break; case 3: //! @bug: Both x and y conditionals are always true, || should be an && if (((x >= 0) || (x < SCREEN_WIDTH)) && ((y >= 0) || (y < SCREEN_HEIGHT))) { ret = func_800B8500(actor, play, xzRange, yRange, itemId); } break; case 4: yRange = fabsf(actor->playerHeightRel) + 1.0f; xzRange = actor->xzDistToPlayer + 1.0f; xzDistToPlayerTemp = actor->xzDistToPlayer; actor->xzDistToPlayer = 0.0f; actor->flags |= 0x10000; ret = func_800B8500(actor, play, xzRange, yRange, itemId); actor->xzDistToPlayer = xzDistToPlayerTemp; break; case 5: //! @bug: Both x and y conditionals are always true, || should be an && if (((x >= 0) || (x < SCREEN_WIDTH)) && ((y >= 0) || (y < SCREEN_HEIGHT)) && (fabsf(actor->playerHeightRel) <= yRange) && (actor->xzDistToPlayer <= xzRange) && actor->isTargeted) { actor->flags |= 0x10000; ret = func_800B8500(actor, play, xzRange, yRange, itemId); } break; case 6: //! @bug: Both x and y conditionals are always true, || should be an && if (((x >= 0) || (x < SCREEN_WIDTH)) && ((y >= 0) || (y < SCREEN_HEIGHT)) && (fabsf(actor->playerHeightRel) <= yRange) && (actor->xzDistToPlayer <= xzRange)) { actor->flags |= 0x10000; ret = func_800B8500(actor, play, xzRange, yRange, itemId); } break; } return ret; } const u8 sShadowMaps[4][12][12] = { { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, }, }; void SubS_FillShadowTex(s32 startCol, s32 startRow, u8* tex, s32 size) { s32 i; s32 j; s32 start; for (i = 0; i < 12; i++) { start = ((startRow + i) * 64) + startCol - 390; for (j = 0; j < 12; j++) { if (sShadowMaps[size][i][j] != 0) { if ((start + j >= 0) && (start + j < SUBS_SHADOW_TEX_SIZE)) { tex[start + j] = 255; } } } } } void SubS_GenShadowTex(Vec3f bodyPartsPos[], Vec3f* worldPos, u8* tex, f32 tween, u8 bodyPartsNum, u8 sizes[], s8 parentBodyParts[]) { Vec3f pos; Vec3f startVec; s32 i; s32 parentBodyPart; Vec3f* bodyPartPos; s32 startCol; s32 startRow; for (i = 0; i < bodyPartsNum; i++) { if (parentBodyParts[i] >= 0) { parentBodyPart = parentBodyParts[i]; bodyPartPos = &bodyPartsPos[i]; pos.x = (bodyPartsPos[parentBodyPart].x - bodyPartPos->x) * tween + (bodyPartPos->x - worldPos->x); pos.y = (bodyPartsPos[parentBodyPart].y - bodyPartPos->y) * tween + (bodyPartPos->y - worldPos->y); pos.z = (bodyPartsPos[parentBodyPart].z - bodyPartPos->z) * tween + (bodyPartPos->z - worldPos->z); } else { bodyPartPos = &bodyPartsPos[i]; pos.x = bodyPartPos->x - worldPos->x; pos.y = bodyPartPos->y - worldPos->y; pos.z = bodyPartPos->z - worldPos->z; } Matrix_MultVec3f(&pos, &startVec); startCol = 64.0f + startVec.x; startRow = 64.0f - startVec.z; SubS_FillShadowTex(startCol >> 1, startRow >> 1, tex, sizes[i]); } } void SubS_DrawShadowTex(Actor* actor, GameState* gameState, u8* tex) { s32 pad; GraphicsContext* gfxCtx = gameState->gfxCtx; OPEN_DISPS(gfxCtx); func_8012C28C(gfxCtx); gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0, 100); gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 0); Matrix_Translate(actor->world.pos.x, 0.0f, actor->world.pos.z, MTXMODE_NEW); Matrix_Scale(0.6f, 1.0f, 0.6f, MTXMODE_APPLY); gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_OPA_DISP++, gShadowMaterialDL); gDPLoadTextureBlock(POLY_OPA_DISP++, tex, G_IM_FMT_I, G_IM_SIZ_8b, SUBS_SHADOW_TEX_WIDTH, SUBS_SHADOW_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, 6, 6, G_TX_NOLOD, G_TX_NOLOD); gSPDisplayList(POLY_OPA_DISP++, gShadowModelDL); CLOSE_DISPS(gfxCtx); } /** * Computes the rotation based on the options and target rotation value * * @param[in,out] rot the computed rotation * @param[in] rotMax the max rotation in binary angles * @param[in] target the target rotation value * @param[in] slowness how slow to rotate, the larger the number the slower the rotation, cannot be 0 * @param[in] stepMin the minimun step in degrees * @param[in] stepMax the maximum step in degrees */ s16 SubS_ComputeTrackPointRot(s16* rot, s16 rotMax, s16 target, f32 slowness, f32 stepMin, f32 stepMax) { s16 prevRot = *rot; f32 step; f32 prevRotStep; step = (f32)(target - *rot) * (360.0f / (f32)0x10000); step *= gFramerateDivisorHalf; prevRotStep = step; if (step >= 0.0f) { step /= slowness; step = CLAMP(step, stepMin, stepMax); *rot += (s16)((step * (f32)0x10000) / 360.0f); if (prevRotStep < stepMin) { *rot = target; } if (rotMax != 0) { *rot = CLAMP(*rot, -rotMax, rotMax); } } else { step = (step / slowness) * -1.0f; step = CLAMP(step, stepMin, stepMax); *rot -= (s16)((step * (f32)0x10000) / 360.0f); if (-stepMin < prevRotStep) { *rot = target; } if (rotMax != 0) { *rot = CLAMP(*rot, -rotMax, rotMax); } } return prevRot - *rot; } /** * Computes the necessary HeadRot and TorsoRot to smoothly turn an actors's head and torso to a point * * @param[in] target the point to turn to * @param[in] focusPos the actor's focus postion * @param[in] shapeRot the actor's shape rotation * @param[in,out] trackTarget the intermediate target step that headRot and torsoRot step towards * @param[in,out] headRot the computed head rotation * @param[in,out] torsoRot the computed torso rotation * @param[in] options various options to adjust how the actor turns, see SubS_ComputeTrackPointRot() */ s32 SubS_TrackPoint(Vec3f* target, Vec3f* focusPos, Vec3s* shapeRot, Vec3s* trackTarget, Vec3s* headRot, Vec3s* torsoRot, TrackOptionsSet* options) { s16 pitch; s16 yaw; s16 pad; s16 targetY; f32 diffX = target->x - focusPos->x; s16 targetX; f32 diffZ = target->z - focusPos->z; yaw = Math_FAtan2F(diffZ, diffX); pitch = Math_FAtan2F(sqrtf(SQ(diffX) + SQ(diffZ)), target->y - focusPos->y); Math_SmoothStepToS(&trackTarget->x, pitch, 4, 0x2710, 0); Math_SmoothStepToS(&trackTarget->y, yaw, 4, 0x2710, 0); targetX = SubS_ComputeTrackPointRot(&headRot->x, options->headRotX.rotMax, trackTarget->x, options->headRotX.slowness, options->headRotX.rotStepMin, options->headRotX.rotStepMax); //! @bug: torsoRotX uses headRotX slowness SubS_ComputeTrackPointRot(&torsoRot->x, options->torsoRotX.rotMax, targetX, options->headRotX.slowness, options->torsoRotX.rotStepMin, options->torsoRotX.rotStepMax); targetY = trackTarget->y - shapeRot->y; SubS_ComputeTrackPointRot(&headRot->y, options->headRotY.rotMax, targetY - torsoRot->y, options->headRotY.slowness, options->headRotY.rotStepMin, options->headRotY.rotStepMax); SubS_ComputeTrackPointRot(&torsoRot->y, options->torsoRotY.rotMax, targetY - headRot->y, options->torsoRotY.slowness, options->torsoRotY.rotStepMin, options->torsoRotY.rotStepMax); return true; } s32 SubS_AngleDiffLessEqual(s16 angleA, s16 threshold, s16 angleB) { return (ABS_ALT(BINANG_SUB(angleB, angleA)) <= threshold) ? true : false; } Path* SubS_GetPathByIndex(PlayState* play, s16 pathIndex, s16 max) { return (pathIndex != max) ? &play->setupPathList[pathIndex] : NULL; } s32 SubS_CopyPointFromPath(Path* path, s32 pointIndex, Vec3f* dst) { Vec3s* point; if (path == NULL) { return false; } point = Lib_SegmentedToVirtual(path->points); point = &point[pointIndex]; dst->x = point->x; dst->y = point->y; dst->z = point->z; return true; } s16 SubS_GetDistSqAndOrientPoints(Vec3f* vecA, Vec3f* vecB, f32* distSq) { f32 diffX = vecA->x - vecB->x; f32 diffZ = vecA->z - vecB->z; *distSq = SQ(diffX) + SQ(diffZ); return Math_Atan2S(diffX, diffZ); } /** * Returns true when the actor has reached the inputed point */ s32 SubS_MoveActorToPoint(Actor* actor, Vec3f* point, s16 rotStep) { Vec3f offsetBefore; Vec3f offsetAfter; f32 distSqBefore; f32 distSqAfter; Actor_OffsetOfPointInActorCoords(actor, &offsetBefore, point); Math_SmoothStepToS(&actor->world.rot.y, SubS_GetDistSqAndOrientPoints(point, &actor->world.pos, &distSqBefore), 4, rotStep, 1); actor->shape.rot.y = actor->world.rot.y; Actor_MoveWithGravity(actor); Actor_OffsetOfPointInActorCoords(actor, &offsetAfter, point); SubS_GetDistSqAndOrientPoints(point, &actor->world.pos, &distSqAfter); return ((offsetBefore.z > 0.0f) && (offsetAfter.z <= 0.0f)) ? true : false; } s16 SubS_GetDistSqAndOrientPath(Path* path, s32 pointIndex, Vec3f* pos, f32* distSq) { Vec3s* point; f32 diffX = 0.0f; f32 diffZ = 0.0f; if (path != NULL) { point = Lib_SegmentedToVirtual(path->points); point = &point[pointIndex]; diffX = point->x - pos->x; diffZ = point->z - pos->z; } *distSq = SQ(diffX) + SQ(diffZ); return Math_Atan2S(diffX, diffZ); } s8 SubS_IsObjectLoaded(s8 index, PlayState* play) { return !Object_IsLoaded(&play->objectCtx, index) ? false : true; } s8 SubS_GetObjectIndex(s16 id, PlayState* play) { return Object_GetIndex(&play->objectCtx, id); } /** * Finds the first actor instance of a specified Id and category. */ Actor* SubS_FindActor(PlayState* play, Actor* actorListStart, u8 actorCategory, s16 actorId) { Actor* actor = actorListStart; if (actor == NULL) { actor = play->actorCtx.actorLists[actorCategory].first; } while (actor != NULL && actorId != actor->id) { actor = actor->next; } return actor; } s32 SubS_FillLimbRotTables(PlayState* play, s16* limbRotTableY, s16* limbRotTableZ, s32 numLimbs) { s32 i; u32 frames = play->gameplayFrames; for (i = 0; i < numLimbs; i++) { limbRotTableY[i] = (i * 50 + 0x814) * frames; limbRotTableZ[i] = (i * 50 + 0x940) * frames; } return true; } s32 SubS_IsFloorAbove(PlayState* play, Vec3f* pos, f32 distAbove) { CollisionPoly* outPoly; Vec3f posA; Vec3f posB; Vec3f posResult; s32 bgId; posA = posB = *pos; posB.y += distAbove; return BgCheck_EntityLineTest1(&play->colCtx, &posA, &posB, &posResult, &outPoly, false, true, false, true, &bgId); } s32 SubS_CopyPointFromPathList(Path* paths, s32 pathIndex, s32 pointIndex, Vec3f* dst) { Path* path = &paths[pathIndex]; Vec3s* point = &((Vec3s*)Lib_SegmentedToVirtual(path->points))[pointIndex]; dst->x = point->x; dst->y = point->y; dst->z = point->z; return false; } u8 SubS_GetPathCountFromPathList(Path* paths, s32 pathIndex) { Path* path = &paths[pathIndex]; return path->count; } void SubS_ActorPathing_Init(PlayState* play, Vec3f* worldPos, Actor* actor, ActorPathing* actorPath, Path* paths, s32 pathIndex, s32 begPointIndex, s32 endPointIndex, s32 curPointIndex, u8 flags) { Path* path; actorPath->setupPathList = play->setupPathList; actorPath->pathIndex = pathIndex; path = &paths[pathIndex]; actorPath->points = Lib_SegmentedToVirtual(path->points); actorPath->count = path->count; actorPath->begPointIndex = begPointIndex; if (endPointIndex == 0) { actorPath->endPointIndex = actorPath->count - 1; } else if (endPointIndex > 0) { actorPath->endPointIndex = endPointIndex; } else { //! @bug: endPointIndex is negative, subtraction causes result to be past the end actorPath->endPointIndex = (actorPath->count - endPointIndex) - 1; } actorPath->curPointIndex = curPointIndex; actorPath->curPoint.x = actorPath->points[0].x; actorPath->curPoint.y = actorPath->points[0].y; actorPath->curPoint.z = actorPath->points[0].z; Math_Vec3f_Copy(&actorPath->prevPoint, &actorPath->curPoint); actorPath->worldPos = worldPos; actorPath->actor = actor; actorPath->flags = flags; actorPath->prevFlags = flags; } s32 SubS_ActorPathing_Update(PlayState* play, ActorPathing* actorPath, ActorPathingComputeFunc computePointInfoFunc, ActorPathingUpdateFunc updateActorInfoFunc, ActorPathingUpdateFunc moveFunc, ActorPathingUpdateFunc setNextPointFunc) { s32 shouldSetNextPoint; s32 reupdate; actorPath->computePointInfoFunc = computePointInfoFunc; actorPath->updateActorInfoFunc = updateActorInfoFunc; actorPath->moveFunc = moveFunc; actorPath->setNextPointFunc = setNextPointFunc; actorPath->flags &= ~ACTOR_PATHING_REACHED_TEMPORARY; reupdate = false; if (actorPath->flags & ACTOR_PATHING_MOVE_BACKWARDS) { if (!(actorPath->prevFlags & ACTOR_PATHING_MOVE_BACKWARDS)) { actorPath->curPointIndex--; } } else if (actorPath->prevFlags & ACTOR_PATHING_MOVE_BACKWARDS) { actorPath->curPointIndex++; } do { shouldSetNextPoint = false; if (actorPath->computePointInfoFunc != NULL) { actorPath->computePointInfoFunc(play, actorPath); } if (actorPath->updateActorInfoFunc != NULL) { shouldSetNextPoint = actorPath->updateActorInfoFunc(play, actorPath); } if (shouldSetNextPoint) { if (actorPath->setNextPointFunc != NULL) { reupdate = actorPath->setNextPointFunc(play, actorPath); } } else if (actorPath->moveFunc != NULL) { reupdate = actorPath->moveFunc(play, actorPath); } } while (reupdate); actorPath->prevFlags = actorPath->flags; return false; } void SubS_ActorPathing_ComputePointInfo(PlayState* play, ActorPathing* actorPath) { Vec3f diff; actorPath->curPoint.x = actorPath->points[actorPath->curPointIndex].x + actorPath->pointOffset.x; actorPath->curPoint.y = actorPath->points[actorPath->curPointIndex].y + actorPath->pointOffset.y; actorPath->curPoint.z = actorPath->points[actorPath->curPointIndex].z + actorPath->pointOffset.z; diff.x = actorPath->curPoint.x - actorPath->worldPos->x; diff.y = actorPath->curPoint.y - actorPath->worldPos->y; diff.z = actorPath->curPoint.z - actorPath->worldPos->z; actorPath->distSqToCurPointXZ = Math3D_XZLengthSquared(diff.x, diff.z); actorPath->distSqToCurPoint = Math3D_LengthSquared(&diff); actorPath->rotToCurPoint.y = Math_FAtan2F(diff.z, diff.x); actorPath->rotToCurPoint.x = Math_FAtan2F(sqrtf(actorPath->distSqToCurPointXZ), -diff.y); actorPath->rotToCurPoint.z = 0; } s32 SubS_ActorPathing_MoveWithGravity(PlayState* play, ActorPathing* actorPath) { Actor_MoveWithGravity(actorPath->actor); return false; } s32 SubS_ActorPathing_MoveWithoutGravityReverse(PlayState* play, ActorPathing* actorPath) { Actor_MoveWithoutGravityReverse(actorPath->actor); return false; } s32 SubS_ActorPathing_SetNextPoint(PlayState* play, ActorPathing* actorPath) { s32 reupdate = true; Math_Vec3f_Copy(&actorPath->prevPoint, &actorPath->curPoint); if (!(actorPath->flags & ACTOR_PATHING_MOVE_BACKWARDS)) { if (actorPath->curPointIndex >= actorPath->endPointIndex) { if (actorPath->flags & ACTOR_PATHING_RETURN_TO_START) { actorPath->curPointIndex = actorPath->begPointIndex; } else if (actorPath->flags & ACTOR_PATHING_SWITCH_DIRECTION) { actorPath->flags |= ACTOR_PATHING_MOVE_BACKWARDS; } else { reupdate = false; } actorPath->flags |= ACTOR_PATHING_REACHED_END; } else { actorPath->curPointIndex++; } actorPath->flags |= ACTOR_PATHING_REACHED_POINT; } else { if (actorPath->begPointIndex >= actorPath->curPointIndex) { if (actorPath->flags & ACTOR_PATHING_RETURN_TO_START) { actorPath->curPointIndex = actorPath->endPointIndex; } else if (actorPath->flags & ACTOR_PATHING_SWITCH_DIRECTION) { actorPath->flags &= ~ACTOR_PATHING_MOVE_BACKWARDS; } else { reupdate = false; } actorPath->flags |= ACTOR_PATHING_REACHED_END; } else { actorPath->curPointIndex--; } } actorPath->flags |= ACTOR_PATHING_REACHED_POINT; return reupdate; } void SubS_ChangeAnimationBySpeedInfo(SkelAnime* skelAnime, AnimationSpeedInfo* animations, s32 nextIndex, s32* curIndex) { AnimationSpeedInfo* animation = &animations[nextIndex]; f32 startFrame = skelAnime->curFrame; f32 endFrame; f32 morphFrames; if ((*curIndex < 0) || (nextIndex == *curIndex)) { morphFrames = 0.0f; if (*curIndex < 0) { startFrame = 0.0f; } } else { morphFrames = animation->morphFrames; if (nextIndex != *curIndex) { startFrame = 0.0f; } } if (animation->playSpeed >= 0.0f) { endFrame = Animation_GetLastFrame(&animation->animation->common); } else { startFrame = Animation_GetLastFrame(&animation->animation->common); endFrame = 0.0f; } Animation_Change(skelAnime, animation->animation, animation->playSpeed, startFrame, endFrame, animation->mode, morphFrames); *curIndex = nextIndex; } s32 SubS_StartActorCutscene(Actor* actor, s16 nextCutscene, s16 curCutscene, s32 type) { s32 isStarted = false; if ((curCutscene != -1) && (ActorCutscene_GetCurrentIndex() == curCutscene)) { ActorCutscene_Stop(curCutscene); ActorCutscene_SetIntentToPlay(nextCutscene); } else if (ActorCutscene_GetCanPlayNext(nextCutscene)) { switch (type) { case SUBS_CUTSCENE_SET_UNK_LINK_FIELDS: ActorCutscene_StartAndSetUnkLinkFields(nextCutscene, actor); break; case SUBS_CUTSCENE_NORMAL: ActorCutscene_Start(nextCutscene, actor); break; case SUBS_CUTSCENE_SET_FLAG: ActorCutscene_StartAndSetFlag(nextCutscene, actor); break; } isStarted = true; } else { ActorCutscene_SetIntentToPlay(nextCutscene); } return isStarted; } s32 SubS_FillCutscenesList(Actor* actor, s16 cutscenes[], s16 numCutscenes) { s16 cs; s32 i; for (i = 0; i < numCutscenes; i++) { cutscenes[i] = -1; } cs = actor->cutscene; i = 0; while (cs != -1) { // Note: Infinite loop if numCutscenes is less than possible additional cutscenes if (i < numCutscenes) { cutscenes[i] = cs; cs = ActorCutscene_GetAdditionalCutscene(cs); i++; } } return i; } /** * Computes a plane based on a point on the plane, a unit vector and two angles * * @param[in] point a point on the plane * @param[in] unitVec the unit vector rotated that becomes the plane's normal * @param[in] rot the angles to rotate with, uses just the x and y components * @param[out] plane the computed plane * * @note the unit input vector is expected to already be normalized (only uses are with the z unit vector) */ void SubS_ConstructPlane(Vec3f* point, Vec3f* unitVec, Vec3s* rot, Plane* plane) { f32 sin; f32 cos; f32 temp; f32 unitVecZ; f32 normY; f32 unitVecYX; sin = Math_SinS(-rot->x); cos = Math_CosS(-rot->x); unitVecZ = unitVec->z; unitVecYX = unitVec->y; // Apply a rotation by -x about the X axis temp = (unitVecZ * cos) - (unitVecYX * sin); normY = (unitVecZ * sin) + (unitVecYX * cos); sin = Math_SinS(rot->y); cos = Math_CosS(rot->y); unitVecYX = unitVec->x; plane->normal.y = normY; // Apply a rotation by y about the Y axis plane->normal.z = (temp * cos) - (unitVecYX * sin); plane->normal.x = (temp * sin) + (unitVecYX * cos); plane->originDist = -((point->x * plane->normal.x) + (plane->normal.y * point->y) + (plane->normal.z * point->z)); } s32 SubS_LineSegVsPlane(Vec3f* point, Vec3s* rot, Vec3f* unitVec, Vec3f* linePointA, Vec3f* linePointB, Vec3f* intersect) { s32 lineSegVsPlane; Plane plane; SubS_ConstructPlane(point, unitVec, rot, &plane); lineSegVsPlane = Math3D_LineSegVsPlane(plane.normal.x, plane.normal.y, plane.normal.z, plane.originDist, linePointA, linePointB, intersect, false); return lineSegVsPlane ? true : false; } /** * Finds the first actor instance of a specified Id and category verified with a custom callback. * The callback should return `true` when the actor is succesfully verified. */ Actor* SubS_FindActorCustom(PlayState* play, Actor* actor, Actor* actorListStart, u8 actorCategory, s16 actorId, void* verifyData, VerifyActor verifyActor) { Actor* actorIter = actorListStart; if (actorListStart == NULL) { actorIter = play->actorCtx.actorLists[actorCategory].first; } while (actorIter != NULL && (actorId != actorIter->id || (actorId == actorIter->id && (verifyActor == NULL || (verifyActor != NULL && !verifyActor(play, actor, actorIter, verifyData)))))) { actorIter = actorIter->next; } return actorIter; } //! TODO: Needs docs with func_800B8500 s32 func_8013E748(Actor* actor, PlayState* play, f32 xzRange, f32 yRange, s32 exchangeItemId, void* data, func_8013E748_VerifyFunc verifyFunc) { s32 ret = false; if ((verifyFunc == NULL) || ((verifyFunc != NULL) && verifyFunc(play, actor, data))) { ret = func_800B8500(actor, play, xzRange, yRange, exchangeItemId); } return ret; } s32 SubS_ActorAndPlayerFaceEachOther(PlayState* play, Actor* actor, void* data) { Player* player = GET_PLAYER(play); Vec3s* yawTols = (Vec3s*)data; s16 playerYaw = ABS(BINANG_SUB(Actor_YawBetweenActors(&player->actor, actor), player->actor.shape.rot.y)); s16 actorYaw = ABS(BINANG_SUB(actor->yawTowardsPlayer, actor->shape.rot.y)); s32 areFacing = false; s32 actorYawTol = ABS(yawTols->y); s32 playerYawTol; if (actorYaw < (s16)actorYawTol) { playerYawTol = ABS(yawTols->x); if (playerYaw < (s16)playerYawTol) { areFacing = true; } } return areFacing; } //! TODO: Needs docs with func_800B8500 s32 func_8013E8F8(Actor* actor, PlayState* play, f32 xzRange, f32 yRange, s32 exhangeItemId, s16 playerYawTol, s16 actorYawTol) { Vec3s yawTols; yawTols.x = playerYawTol; yawTols.y = actorYawTol; return func_8013E748(actor, play, xzRange, yRange, exhangeItemId, &yawTols, SubS_ActorAndPlayerFaceEachOther); } /** * Computes the necessary HeadRot and TorsoRot steps to be added to the normal rotation to smoothly turn an actors's * head and torso to a point * * @param[in] worldPos the actor's world position * @param[in] focusPos the actor's focus position * @param[in] shapeYRot the actor's shape's Y rotation * @param[in] yawTarget the target point to determine desired yaw * @param[in] pitchTarget the target point to determine desired pitch * @param[in,out] headZRotStep the computed actors' head's Z rotation step * @param[in,out] headXRotStep the computed actors' head's X rotation step * @param[in,out] torsoZRotStep the computed actors' torso's Z rotation step * @param[in,out] torsoXRotStep the computed actors' torso's X rotation step * @param[in] headZRotStepMax the max head's Z rotation step * @param[in] headXRotStepMax the max head's X rotation step * @param[in] torsoZRotStepMax the max torso's Z rotation step * @param[in] torsoXRotStepMax the max torso's X rotation step */ s32 SubS_TrackPointStep(Vec3f* worldPos, Vec3f* focusPos, s16 shapeYRot, Vec3f* yawTarget, Vec3f* pitchTarget, s16* headZRotStep, s16* headXRotStep, s16* torsoZRotStep, s16* torsoXRotStep, u16 headZRotStepMax, u16 headXRotStepMax, u16 torsoZRotStepMax, u16 torsoXRotStepMax) { s16 yaw = Math_Vec3f_Yaw(worldPos, yawTarget) - shapeYRot; s16 pad; s16 pad2; s16 pitch = Math_Vec3f_Pitch(focusPos, pitchTarget); if (BINANG_ADD(headXRotStepMax, torsoXRotStepMax) >= (s16)ABS(yaw)) { Math_ApproachS(headXRotStep, yaw - *torsoXRotStep, 4, 0x2AA8); *headXRotStep = CLAMP(*headXRotStep, -headXRotStepMax, headXRotStepMax); Math_ApproachS(torsoXRotStep, yaw - *headXRotStep, 4, 0x2AA8); *torsoXRotStep = CLAMP(*torsoXRotStep, -torsoXRotStepMax, torsoXRotStepMax); } else { Math_ApproachS(headXRotStep, 0, 4, 0x2AA8); Math_ApproachS(torsoXRotStep, 0, 4, 0x2AA8); } if (BINANG_ADD(headZRotStepMax, torsoZRotStepMax) >= (s16)ABS(pitch)) { Math_ApproachS(headZRotStep, pitch - *torsoZRotStep, 4, 0x2AA8); *headZRotStep = CLAMP(*headZRotStep, -headZRotStepMax, headZRotStepMax); Math_ApproachS(torsoZRotStep, pitch - *headZRotStep, 4, 0x2AA8); *torsoZRotStep = CLAMP(*torsoZRotStep, -torsoZRotStepMax, torsoZRotStepMax); } else { Math_ApproachS(headZRotStep, 0, 4, 0x2AA8); Math_ApproachS(torsoZRotStep, 0, 4, 0x2AA8); } return true; }