1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2024-11-14 21:40:03 +00:00

Target System Docs: Actor Searching [3/?] (#2120)

* document target actor searching

* missed something

* review

* mzx review
This commit is contained in:
fig02 2024-09-03 00:02:24 -04:00 committed by GitHub
parent 2b25c31588
commit 6f396e7cc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 120 additions and 68 deletions

View file

@ -431,7 +431,7 @@ Actor* Actor_SpawnAsChild(ActorContext* actorCtx, Actor* parent, PlayState* play
void Actor_SpawnTransitionActors(PlayState* play, ActorContext* actorCtx);
Actor* Actor_SpawnEntry(ActorContext* actorCtx, ActorEntry* actorEntry, PlayState* play);
Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play);
Actor* func_80032AF0(PlayState* play, ActorContext* actorCtx, Actor** actorPtr, Player* player);
Actor* Target_FindTargetableActor(PlayState* play, ActorContext* actorCtx, Actor** targetableActorP, Player* player);
Actor* Actor_Find(ActorContext* actorCtx, s32 actorId, s32 actorCategory);
void Enemy_StartFinishingBlow(PlayState* play, Actor* actor);
void BodyBreak_Alloc(BodyBreak* bodyBreak, s32 count, PlayState* play);

View file

@ -460,7 +460,7 @@ void func_8002C7BC(TargetContext* targetCtx, Player* player, Actor* actorArg, Pl
(player->controlStickDirections[player->controlStickDataIndex] == PLAYER_STICK_DIR_BACKWARD)) {
targetCtx->arrowHoverActor = NULL;
} else {
func_80032AF0(play, &play->actorCtx, &unkActor, player);
Target_FindTargetableActor(play, &play->actorCtx, &unkActor, player);
targetCtx->arrowHoverActor = unkActor;
}
@ -1542,25 +1542,36 @@ PosRot Actor_GetWorldPosShapeRot(Actor* actor) {
return worldPosRot;
}
f32 func_8002EFC0(Actor* actor, Player* player, s16 arg2) {
s16 yawTemp = (s16)(actor->yawTowardsPlayer - 0x8000) - arg2;
/**
* Returns the squared xyz distance from the actor to Player.
* This distance will be weighted if Player is already targeting another actor.
*/
f32 Target_WeightedDistToPlayerSq(Actor* actor, Player* player, s16 playerShapeYaw) {
s16 yawTemp = (s16)(actor->yawTowardsPlayer - 0x8000) - playerShapeYaw;
s16 yawTempAbs = ABS(yawTemp);
if (player->unk_664 != NULL) {
if ((yawTempAbs > 0x4000) || (actor->flags & ACTOR_FLAG_27)) {
return MAXFLOAT;
} else {
f32 ret =
f32 adjDistSq;
// The distance returned is scaled down as the player faces more toward the actor.
// At 90 degrees, 100% of the original distance will be returned.
// This scales down linearly to 60% when facing 0 degrees away.
adjDistSq =
actor->xyzDistToPlayerSq - actor->xyzDistToPlayerSq * 0.8f * ((0x4000 - yawTempAbs) * (1.0f / 0x8000));
return ret;
return adjDistSq;
}
}
// An actor will not be considered targetable if Player is facing more than ~60 degrees away
if (yawTempAbs > 0x2AAA) {
return MAXFLOAT;
}
// Unweighted distSq
return actor->xyzDistToPlayerSq;
}
@ -1572,14 +1583,17 @@ typedef struct TargetRangeParams {
#define TARGET_RANGE(range, leash) \
{ SQ(range), (f32)range / leash }
TargetRangeParams D_80115FF8[] = {
TargetRangeParams sTargetRanges[] = {
TARGET_RANGE(70, 140), TARGET_RANGE(170, 255), TARGET_RANGE(280, 5600), TARGET_RANGE(350, 525),
TARGET_RANGE(700, 1050), TARGET_RANGE(1000, 1500), TARGET_RANGE(100, 105.36842), TARGET_RANGE(140, 163.33333),
TARGET_RANGE(240, 576), TARGET_RANGE(280, 280000),
};
u32 func_8002F090(Actor* actor, f32 arg1) {
return arg1 < D_80115FF8[actor->targetMode].rangeSq;
/**
* Checks if an actor at distance `distSq` is inside the range specified by its targetMode
*/
u32 Target_ActorIsInRange(Actor* actor, f32 distSq) {
return distSq < sTargetRanges[actor->targetMode].rangeSq;
}
s32 func_8002F0C8(Actor* actor, Player* player, s32 flag) {
@ -1598,7 +1612,7 @@ s32 func_8002F0C8(Actor* actor, Player* player, s32 flag) {
dist = actor->xyzDistToPlayerSq;
}
return !func_8002F090(actor, D_80115FF8[actor->targetMode].leashScale * dist);
return !Target_ActorIsInRange(actor, sTargetRanges[actor->targetMode].leashScale * dist);
}
return false;
@ -3102,58 +3116,87 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) {
return newHead;
}
int func_80032880(PlayState* play, Actor* actor) {
s16 sp1E;
s16 sp1C;
/**
* Checks that an actor is on-screen enough to be considered targetable.
*
* Note that the screen bounds checks are larger than the actual screen region
* to give room for error.
*/
int Target_InTargetableScreenRegion(PlayState* play, Actor* actor) {
s16 x;
s16 y;
Actor_GetScreenPos(play, actor, &sp1E, &sp1C);
Actor_GetScreenPos(play, actor, &x, &y);
return (sp1E > -20) && (sp1E < 340) && (sp1C > -160) && (sp1C < 400);
#define X_LEEWAY 20
#define Y_LEEWAY 160
return (x > 0 - X_LEEWAY) && (x < SCREEN_WIDTH + X_LEEWAY) && (y > 0 - Y_LEEWAY) && (y < SCREEN_HEIGHT + Y_LEEWAY);
}
Actor* D_8015BBE8;
Actor* D_8015BBEC;
f32 D_8015BBF0;
f32 sbgmEnemyDistSq;
s32 D_8015BBF8;
s16 D_8015BBFC;
Actor* sNearestTargetableActor;
Actor* sPrioritizedTargetableActor;
f32 sNearestTargetableActorDistSq;
f32 sBgmEnemyDistSq;
s32 sHighestTargetablePriority;
s16 sTargetPlayerRotY;
void func_800328D4(PlayState* play, ActorContext* actorCtx, Player* player, u32 actorCategory) {
f32 var;
/**
* Search for targetable actors within the specified category.
*
* For an actor to be considered targetable it needs to:
* - Have a non-NULL update function (still active)
* - Not be player (this is technically a redundant check because the PLAYER category is never searched)
* - Be targetable (specified by ACTOR_FLAG_0)
* - Not be the already targeted actor
* - Be the closest targetable actor found so far
* - Be within range, specified by targetMode
* - Be roughly on-screen
* - Not be blocked by a surface
*
* If an actor has a priority value set and the value is the lowest found so far, it will be set as the prioritized
* targetable actor. Otherwise, it is set as the nearest targetable actor.
*
* This function is expected to be called with almost every actor category in each cycle. On a new cycle its global
* variables must be reset by the caller, otherwise the information of the previous cycle will be retained.
*/
void Target_FindTargetableActorInCategory(PlayState* play, ActorContext* actorCtx, Player* player, u32 actorCategory) {
f32 distSq;
Actor* actor;
Actor* sp84;
CollisionPoly* sp80;
s32 sp7C;
Vec3f sp70;
Actor* unk_664;
CollisionPoly* poly;
s32 bgId;
Vec3f lineTestResultPos;
actor = actorCtx->actorLists[actorCategory].head;
sp84 = player->unk_664;
unk_664 = player->unk_664;
while (actor != NULL) {
if ((actor->update != NULL) && ((Player*)actor != player) && CHECK_FLAG_ALL(actor->flags, ACTOR_FLAG_0)) {
// This block below is for determining the closest actor to player in determining the volume
// used while playing enemy background music
// Enemy background music actor is updated here, despite not being too related to the Target system
if ((actorCategory == ACTORCAT_ENEMY) && CHECK_FLAG_ALL(actor->flags, ACTOR_FLAG_0 | ACTOR_FLAG_2) &&
(actor->xyzDistToPlayerSq < SQ(500.0f)) && (actor->xyzDistToPlayerSq < sbgmEnemyDistSq)) {
(actor->xyzDistToPlayerSq < SQ(500.0f)) && (actor->xyzDistToPlayerSq < sBgmEnemyDistSq)) {
actorCtx->targetCtx.bgmEnemy = actor;
sbgmEnemyDistSq = actor->xyzDistToPlayerSq;
sBgmEnemyDistSq = actor->xyzDistToPlayerSq;
}
if (actor != sp84) {
var = func_8002EFC0(actor, player, D_8015BBFC);
if ((var < D_8015BBF0) && func_8002F090(actor, var) && func_80032880(play, actor) &&
(!BgCheck_CameraLineTest1(&play->colCtx, &player->actor.focus.pos, &actor->focus.pos, &sp70, &sp80,
1, 1, 1, 1, &sp7C) ||
SurfaceType_IsIgnoredByProjectiles(&play->colCtx, sp80, sp7C))) {
if (actor != unk_664) {
distSq = Target_WeightedDistToPlayerSq(actor, player, sTargetPlayerRotY);
if ((distSq < sNearestTargetableActorDistSq) && Target_ActorIsInRange(actor, distSq) &&
Target_InTargetableScreenRegion(play, actor) &&
(!BgCheck_CameraLineTest1(&play->colCtx, &player->actor.focus.pos, &actor->focus.pos,
&lineTestResultPos, &poly, true, true, true, true, &bgId) ||
SurfaceType_IsIgnoredByProjectiles(&play->colCtx, poly, bgId))) {
if (actor->targetPriority != 0) {
if (actor->targetPriority < D_8015BBF8) {
D_8015BBEC = actor;
D_8015BBF8 = actor->targetPriority;
// Lower values are considered higher priority
if (actor->targetPriority < sHighestTargetablePriority) {
sPrioritizedTargetableActor = actor;
sHighestTargetablePriority = actor->targetPriority;
}
} else {
D_8015BBE8 = actor;
D_8015BBF0 = var;
sNearestTargetableActor = actor;
sNearestTargetableActorDistSq = distSq;
}
}
}
@ -3163,45 +3206,54 @@ void func_800328D4(PlayState* play, ActorContext* actorCtx, Player* player, u32
}
}
u8 D_801160A0[] = {
u8 sTargetableCategorySearchOrder[] = {
ACTORCAT_BOSS, ACTORCAT_ENEMY, ACTORCAT_BG, ACTORCAT_EXPLOSIVE, ACTORCAT_NPC, ACTORCAT_ITEMACTION,
ACTORCAT_CHEST, ACTORCAT_SWITCH, ACTORCAT_PROP, ACTORCAT_MISC, ACTORCAT_DOOR, ACTORCAT_SWITCH,
};
Actor* func_80032AF0(PlayState* play, ActorContext* actorCtx, Actor** actorPtr, Player* player) {
/**
* Search for the nearest targetable actor by iterating through most actor categories.
* See `Target_FindTargetableActorInCategory` for more details on search criteria.
*
* The actor found is stored in the `targetableActorP` parameter, which is also returned.
* It may be NULL if no actor that fulfills the criteria is found.
*/
Actor* Target_FindTargetableActor(PlayState* play, ActorContext* actorCtx, Actor** targetableActorP, Player* player) {
s32 i;
u8* entry;
u8* category;
D_8015BBE8 = D_8015BBEC = NULL;
D_8015BBF0 = sbgmEnemyDistSq = MAXFLOAT;
D_8015BBF8 = 0x7FFFFFFF;
sNearestTargetableActor = sPrioritizedTargetableActor = NULL;
sNearestTargetableActorDistSq = sBgmEnemyDistSq = MAXFLOAT;
sHighestTargetablePriority = INT32_MAX;
if (!Player_InCsMode(play)) {
entry = &D_801160A0[0];
category = &sTargetableCategorySearchOrder[0];
actorCtx->targetCtx.bgmEnemy = NULL;
D_8015BBFC = player->actor.shape.rot.y;
sTargetPlayerRotY = player->actor.shape.rot.y;
// Search the first 3 actor categories first for a targetable actor
// These are Boss, Enemy, and Bg, in order.
for (i = 0; i < 3; i++) {
func_800328D4(play, actorCtx, player, *entry);
entry++;
Target_FindTargetableActorInCategory(play, actorCtx, player, *category);
category++;
}
if (D_8015BBE8 == NULL) {
for (; i < ARRAY_COUNT(D_801160A0); i++) {
func_800328D4(play, actorCtx, player, *entry);
entry++;
// If no actor in the above categories was found, then try searching in the remaining categories
if (sNearestTargetableActor == NULL) {
for (; i < ARRAY_COUNT(sTargetableCategorySearchOrder); i++) {
Target_FindTargetableActorInCategory(play, actorCtx, player, *category);
category++;
}
}
}
if (D_8015BBE8 == NULL) {
*actorPtr = D_8015BBEC;
if (sNearestTargetableActor == NULL) {
*targetableActorP = sPrioritizedTargetableActor;
} else {
*actorPtr = D_8015BBE8;
*targetableActorP = sNearestTargetableActor;
}
return *actorPtr;
return *targetableActorP;
}
/**

View file

@ -408,8 +408,8 @@ func_8002ED80 = 0x80022B94; // type:func
Actor_GetFocus = 0x80022CB0; // type:func
Actor_GetWorld = 0x80022CE4; // type:func
Actor_GetWorldPosShapeRot = 0x80022D18; // type:func
func_8002EFC0 = 0x80022D94; // type:func
func_8002F090 = 0x80022E64; // type:func
Target_WeightedDistToPlayerSq = 0x80022D94; // type:func
Target_ActorIsInRange = 0x80022E64; // type:func
func_8002F0C8 = 0x80022EA0; // type:func
Actor_TalkOfferAccepted = 0x80022F70; // type:func
Actor_OfferTalkExchange = 0x80022FA0; // type:func
@ -469,9 +469,9 @@ Actor_SpawnAsChild = 0x80025A30; // type:func
Actor_SpawnTransitionActors = 0x80025AC8; // type:func
Actor_SpawnEntry = 0x80025C04; // type:func
Actor_Delete = 0x80025C88; // type:func
func_80032880 = 0x80025D8C; // type:func
func_800328D4 = 0x80025DE0; // type:func
func_80032AF0 = 0x80025FFC; // type:func
Target_InTargetableScreenRegion = 0x80025D8C; // type:func
Target_FindTargetableActorInCategory = 0x80025DE0; // type:func
Target_FindTargetableActor = 0x80025FFC; // type:func
Actor_Find = 0x8002614C; // type:func
Enemy_StartFinishingBlow = 0x8002618C; // type:func
FaceChange_UpdateBlinking = 0x800261C4; // type:func