papermario/src/23680.c
HailSanta 54e7bb973c
all sounds named (#1120)
* 128 left

* all sounds done

* most battle flags

* battle message states

* status bar and shop cleanup

---------

Co-authored-by: HailSanta <Hail2Santa@gmail.com>
2023-10-22 08:39:20 -04:00

918 lines
30 KiB
C

#include "common.h"
#include "npc.h"
#include "effects.h"
extern s32 gLastRenderTaskCount;
void spawn_drops(Enemy* enemy) {
PlayerData* playerData = &gPlayerData;
EncounterStatus* encounter = &gCurrentEncounter;
EnemyDropsFlat* drops = (EnemyDropsFlat*) enemy->drops; // TODO: unify EnemyDrops / EnemyDropsFlat
Npc* npc = get_npc_unsafe(enemy->npcID);
Camera* camera = &gCameras[gCurrentCameraID];
s32 pickupDelay;
s32 availableRenderTasks;
s32 availableShadows;
s32 itemToDrop;
f32 x, y, z;
f32 threshold;
f32 chance;
f32 attempts;
f32 fraction;
s32 maxCoinBonus;
s32 minCoinBonus;
s32 tempMax;
s32 spawnCounter;
s32 dropCount;
s32 totalWeight;
s32 angle;
s32 angleMult;
s32 i, j;
s32 flags;
availableShadows = 0;
for (i = 0; i < MAX_SHADOWS; i++) {
if (get_shadow_by_index(i) == NULL) {
availableShadows++;
}
}
spawnCounter = 0;
availableRenderTasks = 256 - 10 - gLastRenderTaskCount;
angle = clamp_angle(camera->curYaw + 90.0f);
x = npc->pos.x;
y = npc->pos.y + (npc->collisionHeight / 2);
z = npc->pos.z;
angleMult = 0;
pickupDelay = 0;
dropCount = drops->itemDropChance;
if (drops->itemDropChance > rand_int(100)) {
tempMax = 0;
for (i = 0; i < 8; i++) {
if (drops->itemDrops[3 * i] != 0) {
tempMax += drops->itemDrops[3 * i + 1];
} else {
break;
}
}
totalWeight = 0;
dropCount = rand_int(tempMax);
itemToDrop = ITEM_NONE;
for (i = 0; i < 8; i++) {
if (drops->itemDrops[3 * i] == 0) {
break;
}
totalWeight += drops->itemDrops[3 * i + 1];
if (drops->itemDrops[3 * i + 2] > 0) {
if (get_global_flag(EVT_INDEX_OF_GAME_FLAG(GF_Unused_NPC_6C) + drops->itemDrops[3 * i + 2])) {
continue;
}
}
if (totalWeight >= dropCount) {
itemToDrop = drops->itemDrops[3 * i];
do {} while (0); // TODO required to match
break;
}
}
// TODO this bullshit is required to match
flags = enemy->flags;
if (flags) {
flags = 0;
}
if (itemToDrop != ITEM_NONE) {
make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + angleMult * 360, 0);
spawnCounter++;
pickupDelay += 2;
angle += 30.0;
if (spawnCounter >= 12) {
angleMult++;
angle = angleMult * 8;
spawnCounter = 0;
}
if (drops->itemDrops[3 * i + 2] >= 0) {
set_global_flag(EVT_INDEX_OF_GAME_FLAG(GF_SpawnedItemDrop_00) + drops->itemDrops[3 * i + 2]);
}
}
}
if (encounter->dropWhackaBump) {
encounter->dropWhackaBump = FALSE;
make_item_entity(ITEM_WHACKAS_BUMP, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + angleMult * 360, 0);
spawnCounter++;
pickupDelay += 2;
angle += 30.0;
if (spawnCounter >= 12) {
angleMult++;
angle = angleMult * 8;
spawnCounter = 0;
}
}
dropCount = 0;
itemToDrop = ITEM_NONE;
fraction = playerData->curHP / (f32) playerData->curMaxHP;
for (i = 0; i < 8; i++) {
attempts = drops->heartDrops[4 * i];
threshold = drops->heartDrops[4 * i + 1];
attempts /= 32767.0f;
threshold /= 32767.0f;
if (fraction <= attempts && rand_int(100) <= threshold * 100.0f) {
attempts = drops->heartDrops[4 * i + 2];
chance = drops->heartDrops[4 * i + 3];
chance /= 32767.0f;
for (j = 0; j < attempts; j++) {
if (rand_int(100) <= chance * 100.0f) {
dropCount++;
}
}
break;
}
}
if (is_ability_active(ABILITY_HEART_FINDER)) {
dropCount += 1 + rand_int(2);
}
if (enemy->flags & ENEMY_FLAG_NO_DROPS) {
dropCount = 0;
}
if (dropCount != 0) {
itemToDrop = ITEM_HEART;
}
if (dropCount * 2 > availableRenderTasks) {
dropCount = availableRenderTasks / 2;
}
availableRenderTasks -= 2 * dropCount;
if (dropCount > availableShadows) {
dropCount = availableShadows;
}
availableShadows -= dropCount;
for (i = 0; i < dropCount; i++) {
make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + (angleMult * 360), 0);
spawnCounter++;
pickupDelay += 2;
angle += 30.0;
if (spawnCounter >= 12) {
spawnCounter = 0;
angleMult++;
angle = angleMult * 8;
}
}
dropCount = 0;
itemToDrop = ITEM_NONE;
fraction = playerData->curFP / (f32) playerData->curMaxFP;
for (i = 0; i < 8; i++) {
attempts = drops->flowerDrops[4 * i + 0];
threshold = drops->flowerDrops[4 * i + 1];
attempts /= 32767.0f;
threshold /= 32767.0f;
if (fraction <= attempts && rand_int(100) <= threshold * 100.0f) {
attempts = drops->flowerDrops[4 * i + 2];
chance = drops->flowerDrops[4 * i + 3];
chance /= 32767.0f;
for (j = 0; j < attempts; j++) {
if (rand_int(100) <= chance * 100.0f) {
dropCount++;
}
}
break;
}
}
if (is_ability_active(ABILITY_FLOWER_FINDER)) {
dropCount += 1 + rand_int(2);
}
if (enemy->flags & ENEMY_FLAG_NO_DROPS) {
dropCount = 0;
}
if (dropCount != 0) {
itemToDrop = ITEM_FLOWER_POINT;
}
if (dropCount * 2 > availableRenderTasks) {
dropCount = availableRenderTasks / 2;
}
availableRenderTasks -= 2 * dropCount;
if (dropCount > availableShadows) {
dropCount = availableShadows;
}
availableShadows -= dropCount;
for (i = 0; i < dropCount; i++) {
make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + (angleMult * 360), 0);
spawnCounter++;
pickupDelay += 2;
angle += 30.0;
if (spawnCounter >= 12) {
spawnCounter = 0;
angleMult++;
angle = angleMult * 8;
}
}
itemToDrop = ITEM_COIN;
do {} while (0);
minCoinBonus = drops->minCoinBonus;
tempMax = drops->maxCoinBonus;
if (drops->maxCoinBonus < drops->minCoinBonus) {
dropCount = minCoinBonus;
minCoinBonus = tempMax;
tempMax = dropCount;
}
if (minCoinBonus < 0) {
dropCount = rand_int(tempMax - minCoinBonus) + minCoinBonus;
} else {
dropCount = tempMax - minCoinBonus;
if (dropCount != 0) {
dropCount = rand_int(dropCount) + minCoinBonus;
} else {
dropCount = minCoinBonus;
}
}
if (dropCount < 0) {
dropCount = 0;
}
dropCount = dropCount + encounter->coinsEarned;
if (is_ability_active(ABILITY_PAY_OFF)) {
dropCount += encounter->damageTaken / 2;
encounter->damageTaken = 0;
}
if (encounter->hasMerleeCoinBonus) {
encounter->hasMerleeCoinBonus = FALSE;
dropCount *= 3;
}
if (is_ability_active(ABILITY_MONEY_MONEY)) {
dropCount *= 2;
}
if (dropCount > 20) {
dropCount = 20;
}
if (enemy->flags & ENEMY_FLAG_NO_DROPS) {
dropCount = 0;
}
if (dropCount * 2 > availableRenderTasks) {
dropCount = availableRenderTasks / 2;
}
availableRenderTasks -= 2 * dropCount;
if (dropCount > availableShadows) {
dropCount = availableShadows;
}
for (i = 0; i < dropCount; i++) {
make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + (angleMult * 360), 0);
spawnCounter++;
pickupDelay += 2;
angle = angle + 30.0;
if (spawnCounter >= 12) {
spawnCounter = 0;
angleMult++;
angle = angleMult * 8;
}
}
}
s32 get_coin_drop_amount(Enemy* enemy) {
EncounterStatus* currentEncounter = &gCurrentEncounter;
EnemyDrops* enemyDrops = enemy->drops;
s32 maxCoinBonus = enemyDrops->maxCoinBonus;
s32 amt = enemyDrops->minCoinBonus;
s32 minTemp = enemyDrops->minCoinBonus;
if (maxCoinBonus < amt) {
amt = enemyDrops->maxCoinBonus;
maxCoinBonus = enemyDrops->minCoinBonus;
}
minTemp = maxCoinBonus - amt;
if ((amt < 0) || (minTemp != 0)) {
amt = rand_int(minTemp) - -amt;
}
if (amt < 0) {
amt = 0;
}
if (is_ability_active(ABILITY_PAY_OFF)) {
amt += currentEncounter->damageTaken / 2;
}
if (currentEncounter->hasMerleeCoinBonus) {
amt *= 3;
}
if (is_ability_active(ABILITY_MONEY_MONEY)) {
amt *= 2;
}
amt += currentEncounter->coinsEarned;
if (enemy->flags & (ENEMY_FLAG_NO_DROPS | ENEMY_FLAG_40000)) {
amt = 0;
}
if (amt > 20) {
amt = 20;
}
return amt;
}
void func_80048E34(Enemy* enemy, s32 arg1, s32 arg2) {
Evt* newScript;
if (enemy->aiScript != NULL) {
kill_script_by_ID(enemy->aiScriptID);
enemy->aiScript = NULL;
}
if (enemy->unk_BC != NULL) {
kill_script_by_ID(enemy->unk_C0);
enemy->unk_BC = NULL;
}
if (enemy->aiBytecode != NULL) {
enemy->unk_C8 = arg2;
newScript = start_script(enemy->aiBytecode, EVT_PRIORITY_A, EVT_FLAG_RUN_IMMEDIATELY);
enemy->aiScript = newScript;
enemy->aiScriptID = newScript->id;
newScript->owner2.npcID = enemy->npcID;
newScript->owner1.enemy = enemy;
}
if (enemy->unk_B8 != NULL) {
enemy->unk_C4 = arg1;
newScript = start_script(enemy->unk_B8, EVT_PRIORITY_A, EVT_FLAG_RUN_IMMEDIATELY);
enemy->unk_BC = newScript;
enemy->unk_C0 = newScript->id;
newScript->owner2.npcID = enemy->npcID;
newScript->owner1.enemy = enemy;
}
}
s32 func_80048F0C(void) {
EncounterStatus* currentEncounter = &gCurrentEncounter;
s32 i;
s32 j;
for (i = 0; i < currentEncounter->numEncounters; i++) {
Encounter* encounter = currentEncounter->encounterList[i];
if (encounter != NULL) {
for (j = 0; j < encounter->count; j++) {
Enemy* enemy = encounter->enemy[j];
if (enemy != NULL && !(enemy->flags & ENEMY_FLAG_DISABLE_AI)) {
get_npc_unsafe(enemy->npcID);
}
}
}
}
return 0;
}
s32 is_point_within_region(s32 shape, f32 pointX, f32 pointY, f32 centerX, f32 centerY, f32 sizeX, f32 sizeZ) {
f32 dist1;
f32 dist2;
switch (shape) {
case 0:
dist1 = dist2D(pointX, pointY, centerX, centerY);
return (sizeX < dist1);
case 1:
dist1 = dist2D(pointX, 0, centerX, 0);
dist2 = dist2D(0, pointY, 0, centerY);
return ((sizeX < dist1) || (sizeZ < dist2));
default:
return FALSE;
}
}
s32 basic_ai_check_player_dist(EnemyDetectVolume* territory, Enemy* enemy, f32 radius, f32 fwdPosOffset, s8 useWorldYaw) {
Npc* npc = get_npc_unsafe(enemy->npcID);
PlayerStatus* playerStatus = &gPlayerStatus;
PartnerStatus* partnerStatus;
f32 x, y, z;
f32 dist;
s32 skipCheckForPlayer;
if (enemy->aiFlags & ENEMY_AI_FLAG_2) {
return FALSE;
}
partnerStatus = &gPartnerStatus;
if (partnerStatus->actingPartner == PARTNER_BOW && partnerStatus->partnerActionState
&& !(territory->detectFlags & AI_TERRITORY_IGNORE_HIDING)) {
return FALSE;
}
if (partnerStatus->actingPartner == PARTNER_SUSHIE && partnerStatus->partnerActionState
&& !(territory->detectFlags & AI_TERRITORY_IGNORE_HIDING)) {
return FALSE;
}
if (territory->skipPlayerDetectChance < 0) {
return FALSE;
}
if (territory->halfHeight <= fabsf(npc->pos.y - playerStatus->pos.y)
&& !(territory->detectFlags & AI_TERRITORY_IGNORE_ELEVATION)) {
return FALSE;
}
if (territory->sizeX | territory->sizeZ && is_point_within_region(territory->shape,
territory->pointX, territory->pointZ,
playerStatus->pos.x, playerStatus->pos.z,
territory->sizeX, territory->sizeZ)) {
return FALSE;
}
if ((playerStatus->actionState == ACTION_STATE_USE_SPINNING_FLOWER)) {
return FALSE;
}
// check for unbroken line of sight
if (enemy->aiDetectFlags & AI_DETECT_SIGHT) {
x = npc->pos.x;
y = npc->pos.y + npc->collisionHeight * 0.5;
z = npc->pos.z;
dist = dist2D(npc->pos.x, npc->pos.z, playerStatus->pos.x, playerStatus->pos.z);
if (npc_test_move_simple_with_slipping(COLLIDER_FLAG_IGNORE_PLAYER | COLLISION_IGNORE_ENTITIES,
&x, &y, &z,
dist, atan2(npc->pos.x, npc->pos.z, playerStatus->pos.x, playerStatus->pos.z),
0.1f, 0.1f)) {
return FALSE;
}
}
if (territory->skipPlayerDetectChance == 0) {
skipCheckForPlayer = 0;
} else {
skipCheckForPlayer = rand_int(territory->skipPlayerDetectChance + 1);
}
if (skipCheckForPlayer == 0) {
if (enemy->aiDetectFlags & AI_DETECT_SENSITIVE_MOTION) {
if (playerStatus->actionState == ACTION_STATE_WALK) {
radius *= 1.15;
} else if (playerStatus->actionState == ACTION_STATE_RUN) {
radius *= 1.3;
}
}
x = npc->pos.x;
z = npc->pos.z;
if (useWorldYaw & 0xFF) {
add_vec2D_polar(&x, &z, fwdPosOffset, npc->yaw);
} else {
add_vec2D_polar(&x, &z, fwdPosOffset, 270.0f - npc->renderYaw);
}
if (dist2D(x, z, playerStatus->pos.x, playerStatus->pos.z) <= radius) {
return TRUE;
}
}
return FALSE;
}
s32 ai_check_player_dist(Enemy* enemy, s32 chance, f32 radius, f32 moveSpeed) {
PlayerStatus* playerStatus = &gPlayerStatus;
Npc* npc = get_npc_unsafe(enemy->npcID);
f32 posX, posZ;
if (chance >= 0) {
s32 skipCheckForPlayer;
if (chance != 0) {
skipCheckForPlayer = rand_int(chance + 1);
} else {
skipCheckForPlayer = 0;
}
if (skipCheckForPlayer == 0) {
posX = npc->pos.x;
posZ = npc->pos.z;
add_vec2D_polar(&posX, &posZ, moveSpeed, 270.0f - npc->renderYaw);
if (dist2D(posX, posZ, playerStatus->pos.x, playerStatus->pos.z) <= radius) {
return TRUE;
}
}
}
return FALSE;
}
void ai_enemy_play_sound(Npc* npc, s32 soundID, s32 upperSoundFlags) {
Enemy* enemy = get_enemy(npc->npcID);
s32 soundFlags = (upperSoundFlags & SOUND_SPACE_PARAMS_MASK) | SOUND_SPACE_FULL;
if (upperSoundFlags & 1) {
soundFlags |= SOUND_PARAM_MUTE;
}
if (enemy->npcSettings->actionFlags & AI_ACTION_20) {
soundFlags |= SOUND_PARAM_CLIP_OFFSCREEN_ANY;
}
sfx_play_sound_at_position(soundID, soundFlags, npc->pos.x, npc->pos.y, npc->pos.z);
}
void ai_try_set_state(Evt* script, s32 state) {
Npc* npc = get_npc_unsafe(script->owner1.enemy->npcID);
npc->duration--;
if (npc->duration <= 0) {
script->AI_TEMP_STATE = state;
}
}
void basic_ai_wander_init(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
// chose a random direction and move time
npc->duration = (npcAISettings->moveTime / 2) + rand_int((npcAISettings->moveTime / 2) + 1);
npc->yaw = clamp_angle(npc->yaw + rand_int(60) - 30.0f);
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_WALK];
script->functionTemp[1] = 0;
if (enemy->territory->wander.moveSpeedOverride < 0) {
npc->moveSpeed = npcAISettings->moveSpeed;
} else {
npc->moveSpeed = enemy->territory->wander.moveSpeedOverride / 32767.0;
}
enemy->aiFlags &= ~ENEMY_AI_FLAG_40;
enemy->aiFlags &= ~ENEMY_AI_FLAG_20;
script->AI_TEMP_STATE = AI_STATE_WANDER;
}
void basic_ai_wander(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
s32 stillWithinTerritory = FALSE;
f32 x, y, z;
EffectInstance* sp34;
f32 yaw;
if (aiSettings->playerSearchInterval >= 0) {
if (script->functionTemp[1] <= 0) {
script->functionTemp[1] = aiSettings->playerSearchInterval;
if (basic_ai_check_player_dist(territory, enemy, aiSettings->alertRadius, aiSettings->alertOffsetDist, 0)) {
x = npc->pos.x;
y = npc->pos.y;
z = npc->pos.z;
yaw = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
if (!npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, aiSettings->chaseSpeed, yaw, npc->collisionHeight, npc->collisionDiameter)) {
npc->yaw = yaw;
ai_enemy_play_sound(npc, SOUND_AI_ALERT_A, SOUND_PARAM_MORE_QUIET);
fx_emote(EMOTE_EXCLAMATION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &sp34);
enemy->aiFlags &= ~ENEMY_AI_FLAG_40;
enemy->aiFlags &= ~ENEMY_AI_FLAG_20;
if (enemy->npcSettings->actionFlags & AI_ACTION_JUMP_WHEN_SEE_PLAYER) {
script->AI_TEMP_STATE = AI_STATE_ALERT_INIT;
} else {
script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
}
return;
}
}
}
script->functionTemp[1]--;
}
// check if the wander we've reached the boundary of the territory
if (is_point_within_region(enemy->territory->wander.wanderShape,
enemy->territory->wander.centerPos.x,
enemy->territory->wander.centerPos.z,
npc->pos.x,
npc->pos.z,
enemy->territory->wander.wanderSize.x,
enemy->territory->wander.wanderSize.z)
&& npc->moveSpeed < dist2D(enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z, npc->pos.x, npc->pos.z)) {
if (!(enemy->aiFlags & ENEMY_AI_FLAG_20)) {
enemy->aiFlags |= (ENEMY_AI_FLAG_20 | ENEMY_AI_FLAG_40);
}
if (enemy->aiFlags & ENEMY_AI_FLAG_40) {
npc->yaw = clamp_angle(atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z));
enemy->aiFlags &= ~ENEMY_AI_FLAG_40;
}
x = npc->pos.x;
y = npc->pos.y;
z = npc->pos.z;
if (npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, 2.0 * npc->moveSpeed, npc->yaw, npc->collisionHeight, npc->collisionDiameter)) {
yaw = clamp_angle(atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z));
enemy->aiFlags &= ~ENEMY_AI_FLAG_40;
ai_check_fwd_collisions(npc, 5.0f, &yaw, NULL, NULL, NULL);
npc->yaw = yaw;
}
stillWithinTerritory = TRUE;
} else if (enemy->aiFlags & ENEMY_AI_FLAG_20) {
enemy->aiFlags &= ~ENEMY_AI_FLAG_20;
enemy->aiFlags &= ~ENEMY_AI_FLAG_40;
}
// perform the motion
if (enemy->territory->wander.wanderSize.x | enemy->territory->wander.wanderSize.z | stillWithinTerritory) {
if (!npc->turnAroundYawAdjustment) {
npc_move_heading(npc, npc->moveSpeed, npc->yaw);
} else {
return;
}
}
// decide to loiter or continue wandering
if (aiSettings->moveTime > 0) {
npc->duration--;
if (npc->duration <= 0) {
script->AI_TEMP_STATE = AI_STATE_LOITER_INIT;
script->functionTemp[1] = rand_int(1000) % 3 + 2;
if (aiSettings->unk_AI_2C <= 0 || aiSettings->waitTime <= 0) {
script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
}
}
}
}
void basic_ai_loiter_init(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
npc->duration = (aiSettings->waitTime / 2) + rand_int((aiSettings->waitTime / 2) + 1);
npc->yaw = clamp_angle(npc->yaw + rand_int(180) - 90.0f);
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_IDLE];
script->AI_TEMP_STATE = AI_STATE_LOITER;
}
void basic_ai_loiter(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
f32 x, y, z;
f32 yaw;
EffectInstance* emoteTemp;
if (aiSettings->playerSearchInterval >= 0) {
if (basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 0)) {
x = npc->pos.x;
y = npc->pos.y;
z = npc->pos.z;
yaw = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
if (!npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, aiSettings->chaseSpeed, yaw, npc->collisionHeight, npc->collisionDiameter)) {
npc->yaw = yaw;
ai_enemy_play_sound(npc, SOUND_AI_ALERT_A, SOUND_PARAM_MORE_QUIET);
fx_emote(EMOTE_EXCLAMATION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &emoteTemp);
if (enemy->npcSettings->actionFlags & AI_ACTION_JUMP_WHEN_SEE_PLAYER) {
script->AI_TEMP_STATE = AI_STATE_ALERT_INIT;
} else {
script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
}
return;
}
}
}
// look around randomly
if (npc->turnAroundYawAdjustment == 0) {
npc->duration--;
if (npc->duration <= 0) {
script->functionTemp[1]--;
if (script->functionTemp[1]) {
if (!(enemy->npcSettings->actionFlags & AI_ACTION_LOOK_AROUND_DURING_LOITER)) {
npc->yaw = clamp_angle(npc->yaw + 180.0f);
}
npc->duration = (aiSettings->waitTime / 2) + rand_int(aiSettings->waitTime / 2 + 1);
return;
}
script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
}
}
}
void basic_ai_found_player_jump_init(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
ai_enemy_play_sound(npc, SOUND_AI_FOUND_PLAYER_JUMP, 0);
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_JUMP];
npc->jumpVel = 10.0f;
npc->jumpScale = 2.5f;
npc->moveToPos.y = npc->pos.y;
npc->flags |= NPC_FLAG_JUMPING;
script->AI_TEMP_STATE = AI_STATE_ALERT;
}
void basic_ai_found_player_jump(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
Npc* npc = get_npc_unsafe(script->owner1.enemy->npcID);
s32 done = FALSE;
if (npc->jumpVel <= 0.0) {
if (npc->pos.y <= npc->moveToPos.y) {
npc->pos.y = npc->moveToPos.y;
done = TRUE;
}
}
if (!done) {
npc->pos.y += npc->jumpVel;
npc->jumpVel -= npc->jumpScale;
} else {
npc->jumpVel = 0.0f;
npc->flags &= ~NPC_FLAG_JUMPING;
script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
}
}
void basic_ai_chase_init(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
s32 skipTurnAround = FALSE;
if ((gPlayerStatusPtr->actionState == ACTION_STATE_JUMP || gPlayerStatusPtr->actionState == ACTION_STATE_BOUNCE ||
gPlayerStatusPtr->actionState == ACTION_STATE_HOP || gPlayerStatusPtr->actionState == ACTION_STATE_FALLING) &&
(f64)dist2D(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z) < npc->collisionDiameter)
{
skipTurnAround = TRUE;
}
if (!skipTurnAround) {
f32 angle = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
f32 deltaAngleToPlayer = get_clamped_angle_diff(npc->yaw, angle);
if (npcAISettings->chaseTurnRate < fabsf(deltaAngleToPlayer)) {
angle = npc->yaw;
if (deltaAngleToPlayer < 0.0f) {
angle += -npcAISettings->chaseTurnRate;
} else {
angle += npcAISettings->chaseTurnRate;
}
}
npc->yaw = clamp_angle(angle);
npc->duration = (npcAISettings->chaseUpdateInterval / 2) + rand_int((npcAISettings->chaseUpdateInterval / 2) + 1);
} else {
npc->duration = 0;
}
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_CHASE];
npc->moveSpeed = npcAISettings->chaseSpeed;
script->AI_TEMP_STATE = AI_STATE_CHASE;
}
void basic_ai_chase(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
EffectInstance* sp28;
f32 x, y, z;
if (!basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 1)) {
fx_emote(EMOTE_QUESTION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &sp28);
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_IDLE];
npc->duration = 20;
script->AI_TEMP_STATE = AI_STATE_LOSE_PLAYER;
return;
}
if (enemy->npcSettings->actionFlags & AI_ACTION_04) {
if (dist2D(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z) > (npc->moveSpeed * 5.0)) {
x = npc->pos.x;
y = npc->pos.y;
z = npc->pos.z;
if (npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, 1.0f, npc->yaw, npc->collisionHeight, npc->collisionDiameter)) {
fx_emote(EMOTE_QUESTION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 0xC, &sp28);
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_IDLE];
npc->duration = 15;
script->AI_TEMP_STATE = AI_STATE_LOSE_PLAYER;
return;
}
}
}
spawn_surface_effects(npc, SURFACE_INTERACT_RUN);
npc_move_heading(npc, npc->moveSpeed, npc->yaw);
if (npc->moveSpeed > 8.0 && !(gGameStatusPtr->frameCounter % 5)) {
ai_enemy_play_sound(npc, SOUND_SMALL_NPC_STEP, 0);
}
if (npc->duration > 0) {
npc->duration--;
} else {
script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
}
}
void basic_ai_lose_player(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
npc->duration--;
if (npc->duration == 0) {
// turn to face home position
npc->yaw = clamp_angle(atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z));
script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
}
}
ApiStatus BasicAI_Main(Evt* script, s32 isInitialCall) {
Enemy* enemy = script->owner1.enemy;
Npc* npc = get_npc_unsafe(enemy->npcID);
Bytecode* args = script->ptrReadPos;
EnemyDetectVolume territory;
EnemyDetectVolume* pTerritory = &territory;
MobileAISettings* aiSettings = (MobileAISettings*) evt_get_variable(script, *args++);
territory.skipPlayerDetectChance = 0;
territory.shape = enemy->territory->wander.detectShape;
territory.pointX = enemy->territory->wander.detectPos.x;
territory.pointZ = enemy->territory->wander.detectPos.z;
territory.sizeX = enemy->territory->wander.detectSize.x;
territory.sizeZ = enemy->territory->wander.detectSize.z;
territory.halfHeight = 65.0f;
territory.detectFlags = 0;
if (isInitialCall || enemy->aiFlags & ENEMY_AI_FLAG_SUSPEND) {
script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
npc->duration = 0;
npc->curAnim = enemy->animList[ENEMY_ANIM_INDEX_IDLE];
npc->flags &= ~NPC_FLAG_JUMPING;
if (!enemy->territory->wander.isFlying) {
npc->flags |= NPC_FLAG_GRAVITY;
npc->flags &= ~NPC_FLAG_FLYING;
} else {
npc->flags &= ~NPC_FLAG_GRAVITY;
npc->flags |= NPC_FLAG_FLYING;
}
if (enemy->aiFlags & ENEMY_AI_FLAG_SUSPEND) {
script->AI_TEMP_STATE = AI_STATE_SUSPEND;
script->functionTemp[1] = AI_STATE_WANDER_INIT;
} else if (enemy->flags & ENEMY_FLAG_BEGIN_WITH_CHASING) {
script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
}
enemy->aiFlags &= ~ENEMY_AI_FLAG_SUSPEND;
enemy->flags &= ~ENEMY_FLAG_BEGIN_WITH_CHASING;
}
switch (script->AI_TEMP_STATE) {
case AI_STATE_WANDER_INIT:
basic_ai_wander_init(script, aiSettings, pTerritory);
case AI_STATE_WANDER:
basic_ai_wander(script, aiSettings, pTerritory);
break;
case AI_STATE_LOITER_INIT:
basic_ai_loiter_init(script, aiSettings, pTerritory);
case AI_STATE_LOITER:
basic_ai_loiter(script, aiSettings, pTerritory);
break;
case AI_STATE_ALERT_INIT:
basic_ai_found_player_jump_init(script, aiSettings, pTerritory);
case AI_STATE_ALERT:
basic_ai_found_player_jump(script, aiSettings, pTerritory);
break;
case AI_STATE_CHASE_INIT:
basic_ai_chase_init(script, aiSettings, pTerritory);
case AI_STATE_CHASE:
basic_ai_chase(script, aiSettings, pTerritory);
if (script->AI_TEMP_STATE != AI_STATE_LOSE_PLAYER) {
break;
}
case AI_STATE_LOSE_PLAYER:
basic_ai_lose_player(script, aiSettings, pTerritory);
break;
case AI_STATE_SUSPEND:
basic_ai_suspend(script);
break;
}
return ApiStatus_BLOCK;
}