mirror of
https://github.com/pmret/papermario.git
synced 2024-09-18 23:42:35 +02:00
54e7bb973c
* 128 left * all sounds done * most battle flags * battle message states * status bar and shop cleanup --------- Co-authored-by: HailSanta <Hail2Santa@gmail.com>
918 lines
30 KiB
C
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;
|
|
}
|