Paper Mario DX
Paper Mario (N64) modding
 
Loading...
Searching...
No Matches
23680.c
Go to the documentation of this file.
1#include "common.h"
2#include "vars_access.h"
3#include "npc.h"
4#include "effects.h"
5
6extern s32 gLastRenderTaskCount;
7
8void spawn_drops(Enemy* enemy) {
10 EnemyDrops* drops = enemy->drops;
11 Npc* npc = get_npc_unsafe(enemy->npcID);
13 s32 pickupDelay;
14 s32 availableRenderTasks;
15 s32 availableShadows;
16 s32 itemToDrop;
17 f32 x, y, z;
18 f32 threshold;
19 f32 chance;
20 f32 attempts;
21 f32 fraction;
22 s32 minCoinBonus;
23 s32 maxCoinBonus;
24 s32 spawnCounter;
25 s32 dropCount;
26 s32 totalWeight;
27 s32 curWeight;
28 s32 angle;
29 s32 angleMult;
30 s32 i, j;
31
32 availableShadows = 0;
33 for (i = 0; i < MAX_SHADOWS; i++) {
34 if (get_shadow_by_index(i) == NULL) {
35 availableShadows++;
36 }
37 }
38
39 spawnCounter = 0;
40 availableRenderTasks = 256 - 10 - gLastRenderTaskCount;
41 angle = clamp_angle(camera->curYaw + 90.0f);
42 x = npc->pos.x;
43 y = npc->pos.y + (npc->collisionHeight / 2);
44 z = npc->pos.z;
45
46 angleMult = 0;
47 pickupDelay = 0;
48
49 // try dropping items
50
51 dropCount = drops->itemDropChance;
52 if (drops->itemDropChance > rand_int(100)) {
53 totalWeight = 0;
54
55 for (i = 0; i < ARRAY_COUNT(drops->itemDrops); i++) {
56 if (drops->itemDrops[i].item != ITEM_NONE) {
57 totalWeight += drops->itemDrops[i].weight;
58 } else {
59 break;
60 }
61 }
62
63 curWeight = 0;
64 dropCount = rand_int(totalWeight);
65 itemToDrop = ITEM_NONE;
66
67 for (i = 0; i < ARRAY_COUNT(drops->itemDrops); i++) {
68 if (drops->itemDrops[i].item == ITEM_NONE) {
69 break;
70 }
71
72 curWeight += drops->itemDrops[i].weight;
73 if (drops->itemDrops[i].flagIdx > 0) {
75 continue;
76 }
77 }
78
79 if (curWeight >= dropCount) {
80 itemToDrop = drops->itemDrops[i].item;
81 break;
82 }
83 }
84
85 if (itemToDrop != ITEM_NONE) {
86 make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + angleMult * 360, 0);
87 spawnCounter++;
88 pickupDelay += 2;
89 angle += 30.0;
90 if (spawnCounter >= 12) {
91 angleMult++;
92 angle = angleMult * 8;
93 spawnCounter = 0;
94 }
95
96 if (drops->itemDrops[i].flagIdx >= 0) {
98 }
99 }
100 }
101
102 if (encounter->dropWhackaBump) {
103 encounter->dropWhackaBump = FALSE;
104 make_item_entity(ITEM_WHACKAS_BUMP, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + angleMult * 360, 0);
105 spawnCounter++;
106 pickupDelay += 2;
107 angle += 30.0;
108 if (spawnCounter >= 12) {
109 angleMult++;
110 angle = angleMult * 8;
111 spawnCounter = 0;
112 }
113 }
114
115 // determine number of hearts to drop
116
117 dropCount = 0;
118 itemToDrop = ITEM_NONE;
119 fraction = gPlayerData.curHP / (f32) gPlayerData.curMaxHP;
120
121 for (i = 0; i < ARRAY_COUNT(drops->heartDrops); i++) {
122 attempts = drops->heartDrops[i].cutoff;
123 threshold = drops->heartDrops[i].generalChance;
124 attempts /= 32767.0f;
125 threshold /= 32767.0f;
126
127 if (fraction <= attempts && rand_int(100) <= threshold * 100.0f) {
128 attempts = drops->heartDrops[i].attempts;
129 chance = drops->heartDrops[i].chancePerAttempt;
130 chance /= 32767.0f;
131 for (j = 0; j < attempts; j++) {
132 if (rand_int(100) <= chance * 100.0f) {
133 dropCount++;
134 }
135 }
136 break;
137 }
138 }
139
141 dropCount += 1 + rand_int(2);
142 }
143 if (enemy->flags & ENEMY_FLAG_NO_DROPS) {
144 dropCount = 0;
145 }
146
147 // spawn as many of the heart drops as possible
148
149 if (dropCount != 0) {
150 itemToDrop = ITEM_HEART;
151 }
152
153 if (dropCount * 2 > availableRenderTasks) {
154 dropCount = availableRenderTasks / 2;
155 }
156 availableRenderTasks -= 2 * dropCount;
157 if (dropCount > availableShadows) {
158 dropCount = availableShadows;
159 }
160
161 availableShadows -= dropCount;
162
163 for (i = 0; i < dropCount; i++) {
164 make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + (angleMult * 360), 0);
165 spawnCounter++;
166 pickupDelay += 2;
167 angle += 30.0;
168 if (spawnCounter >= 12) {
169 spawnCounter = 0;
170 angleMult++;
171 angle = angleMult * 8;
172 }
173 }
174
175 // determine number of flowers to drop
176
177 dropCount = 0;
178 itemToDrop = ITEM_NONE;
179
180 if (gPlayerData.curMaxFP > 0) {
181 fraction = gPlayerData.curFP / (f32) gPlayerData.curMaxFP;
182 } else {
183 fraction = 0.0;
184 }
185
186 for (i = 0; i < ARRAY_COUNT(drops->flowerDrops); i++) {
187 attempts = drops->flowerDrops[i].cutoff;
188 threshold = drops->flowerDrops[i].generalChance;
189 attempts /= 32767.0f;
190 threshold /= 32767.0f;
191
192 if (fraction <= attempts && rand_int(100) <= threshold * 100.0f) {
193 attempts = drops->flowerDrops[i].attempts;
194 chance = drops->flowerDrops[i].chancePerAttempt;
195 chance /= 32767.0f;
196 for (j = 0; j < attempts; j++) {
197 if (rand_int(100) <= chance * 100.0f) {
198 dropCount++;
199 }
200 }
201 break;
202 }
203 }
204
206 dropCount += 1 + rand_int(2);
207 }
208 if (enemy->flags & ENEMY_FLAG_NO_DROPS) {
209 dropCount = 0;
210 }
211
212 // spawn as many of the flower drops as possible
213
214 if (dropCount != 0) {
215 itemToDrop = ITEM_FLOWER_POINT;
216 }
217
218 if (dropCount * 2 > availableRenderTasks) {
219 dropCount = availableRenderTasks / 2;
220 }
221 availableRenderTasks -= 2 * dropCount;
222 if (dropCount > availableShadows) {
223 dropCount = availableShadows;
224 }
225
226 availableShadows -= dropCount;
227
228 for (i = 0; i < dropCount; i++) {
229 make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + (angleMult * 360), 0);
230 spawnCounter++;
231 pickupDelay += 2;
232 angle += 30.0;
233 if (spawnCounter >= 12) {
234 spawnCounter = 0;
235 angleMult++;
236 angle = angleMult * 8;
237 }
238 }
239
240 // determine number of coins to drop
241
242 itemToDrop = ITEM_COIN;
243
244 //TODO maybe use an ASSERT here and forgo the odd support for reversing min/max
245 if (drops->maxCoinBonus < drops->minCoinBonus) {
246 // swap if max < min
247 maxCoinBonus = drops->minCoinBonus;
248 minCoinBonus = drops->maxCoinBonus;
249 } else {
250 minCoinBonus = drops->minCoinBonus;
251 maxCoinBonus = drops->maxCoinBonus;
252 }
253
254 if (minCoinBonus < 0) {
255 dropCount = rand_int(maxCoinBonus - minCoinBonus) + minCoinBonus;
256 } else {
257 dropCount = maxCoinBonus - minCoinBonus;
258 if (dropCount != 0) {
259 dropCount = rand_int(dropCount) + minCoinBonus;
260 } else {
261 dropCount = minCoinBonus;
262 }
263 }
264
265 if (dropCount < 0) {
266 dropCount = 0;
267 }
268 dropCount = dropCount + encounter->coinsEarned;
269
271 dropCount += encounter->damageTaken / 2;
272 encounter->damageTaken = 0;
273 }
274 if (encounter->hasMerleeCoinBonus) {
275 encounter->hasMerleeCoinBonus = FALSE;
276 dropCount *= 3;
277 }
279 dropCount *= 2;
280 }
281 if (dropCount > 20) {
282 dropCount = 20;
283 }
284 if (enemy->flags & ENEMY_FLAG_NO_DROPS) {
285 dropCount = 0;
286 }
287 if (dropCount * 2 > availableRenderTasks) {
288 dropCount = availableRenderTasks / 2;
289 }
290
291 // spawn as many of the coin drops as possible
292
293 availableRenderTasks -= 2 * dropCount;
294
295 if (dropCount > availableShadows) {
296 dropCount = availableShadows;
297 }
298
299 for (i = 0; i < dropCount; i++) {
300 make_item_entity(itemToDrop, x, y, z, ITEM_SPAWN_MODE_BATTLE_REWARD, pickupDelay, angle + (angleMult * 360), 0);
301 spawnCounter++;
302 pickupDelay += 2;
303 angle = angle + 30.0;
304 if (spawnCounter >= 12) {
305 spawnCounter = 0;
306 angleMult++;
307 angle = angleMult * 8;
308 }
309 }
310}
311
313 EncounterStatus* currentEncounter = &gCurrentEncounter;
314 EnemyDrops* enemyDrops = enemy->drops;
315 s32 max = enemyDrops->maxCoinBonus;
316 s32 amt = enemyDrops->minCoinBonus;
317 s32 minTemp = enemyDrops->minCoinBonus;
318
319 if (max < amt) {
320 amt = enemyDrops->maxCoinBonus;
321 max = enemyDrops->minCoinBonus;
322 }
323
324 minTemp = max - amt;
325 if ((amt < 0) || (minTemp != 0)) {
326 amt = rand_int(minTemp) - -amt;
327 }
328
329 if (amt < 0) {
330 amt = 0;
331 }
332
334 amt += currentEncounter->damageTaken / 2;
335 }
336
337 if (currentEncounter->hasMerleeCoinBonus) {
338 amt *= 3;
339 }
340
342 amt *= 2;
343 }
344
345 amt += currentEncounter->coinsEarned;
346
348 amt = 0;
349 }
350
351 if (amt > 20) {
352 amt = 20;
353 }
354
355 return amt;
356}
357
358void func_80048E34(Enemy* enemy, s32 arg1, s32 arg2) {
359 Evt* newScript;
360
361 if (enemy->aiScript != NULL) {
363 enemy->aiScript = NULL;
364 }
365
366 if (enemy->unk_BC != NULL) {
368 enemy->unk_BC = NULL;
369 }
370
371 if (enemy->aiBytecode != NULL) {
372 enemy->unk_C8 = arg2;
374 enemy->aiScript = newScript;
375 enemy->aiScriptID = newScript->id;
376 newScript->owner2.npcID = enemy->npcID;
377 newScript->owner1.enemy = enemy;
378 }
379
380 if (enemy->unk_B8 != NULL) {
381 enemy->unk_C4 = arg1;
383 enemy->unk_BC = newScript;
384 enemy->unk_C0 = newScript->id;
385 newScript->owner2.npcID = enemy->npcID;
386 newScript->owner1.enemy = enemy;
387 }
388}
389
390s32 func_80048F0C(void) {
391 EncounterStatus* currentEncounter = &gCurrentEncounter;
392 s32 i;
393 s32 j;
394
395 for (i = 0; i < currentEncounter->numEncounters; i++) {
396 Encounter* encounter = currentEncounter->encounterList[i];
397
398 if (encounter != NULL) {
399 for (j = 0; j < encounter->count; j++) {
400 Enemy* enemy = encounter->enemy[j];
401
402 if (enemy != NULL && !(enemy->flags & ENEMY_FLAG_DISABLE_AI)) {
403 get_npc_unsafe(enemy->npcID);
404 }
405 }
406 }
407 }
408
409 return 0;
410}
411
412b32 is_point_outside_territory(s32 shape, f32 centerX, f32 centerZ, f32 pointX, f32 pointZ, f32 sizeX, f32 sizeZ) {
413 f32 dist1;
414 f32 dist2;
415
416 switch (shape) {
417 case SHAPE_CYLINDER:
418 dist1 = dist2D(centerX, centerZ, pointX, pointZ);
419 return (sizeX < dist1);
420 case SHAPE_RECT:
421 dist1 = dist2D(centerX, 0, pointX, 0);
422 dist2 = dist2D(0, centerZ, 0, pointZ);
423 return ((sizeX < dist1) || (sizeZ < dist2));
424 default:
425 return FALSE;
426 }
427}
428
429b32 basic_ai_check_player_dist(EnemyDetectVolume* territory, Enemy* enemy, f32 radius, f32 fwdPosOffset, b8 useWorldYaw) {
430 Npc* npc = get_npc_unsafe(enemy->npcID);
431 PlayerStatus* playerStatus = &gPlayerStatus;
432 PartnerStatus* partnerStatus;
433 f32 x, y, z;
434 f32 dist;
435 s32 skipCheckForPlayer;
436
437 if (enemy->aiFlags & AI_FLAG_CANT_DETECT_PLAYER) {
438 return FALSE;
439 }
440
441 partnerStatus = &gPartnerStatus;
442 if (partnerStatus->actingPartner == PARTNER_BOW && partnerStatus->partnerActionState
443 && !(territory->detectFlags & AI_TERRITORY_IGNORE_HIDING)) {
444 return FALSE;
445 }
446
447 if (partnerStatus->actingPartner == PARTNER_SUSHIE && partnerStatus->partnerActionState
448 && !(territory->detectFlags & AI_TERRITORY_IGNORE_HIDING)) {
449 return FALSE;
450 }
451
452 if (territory->skipPlayerDetectChance < 0) {
453 return FALSE;
454 }
455
456 if (territory->halfHeight <= fabsf(npc->pos.y - playerStatus->pos.y)
457 && !(territory->detectFlags & AI_TERRITORY_IGNORE_ELEVATION)) {
458 return FALSE;
459 }
460
461 if (territory->sizeX | territory->sizeZ && is_point_outside_territory(territory->shape,
462 territory->pointX, territory->pointZ,
463 playerStatus->pos.x, playerStatus->pos.z,
464 territory->sizeX, territory->sizeZ)) {
465 return FALSE;
466 }
467
468 if ((playerStatus->actionState == ACTION_STATE_USE_SPINNING_FLOWER)) {
469 return FALSE;
470 }
471
472 // check for unbroken line of sight
473 if (enemy->aiDetectFlags & AI_DETECT_SIGHT) {
474 x = npc->pos.x;
475 y = npc->pos.y + npc->collisionHeight * 0.5;
476 z = npc->pos.z;
477 dist = dist2D(npc->pos.x, npc->pos.z, playerStatus->pos.x, playerStatus->pos.z);
479 &x, &y, &z,
480 dist, atan2(npc->pos.x, npc->pos.z, playerStatus->pos.x, playerStatus->pos.z),
481 0.1f, 0.1f)) {
482 return FALSE;
483 }
484 }
485
486 if (territory->skipPlayerDetectChance == 0) {
487 skipCheckForPlayer = 0;
488 } else {
489 skipCheckForPlayer = rand_int(territory->skipPlayerDetectChance + 1);
490 }
491
492 if (skipCheckForPlayer == 0) {
494 if (playerStatus->actionState == ACTION_STATE_WALK) {
495 radius *= 1.15;
496 } else if (playerStatus->actionState == ACTION_STATE_RUN) {
497 radius *= 1.3;
498 }
499 }
500 x = npc->pos.x;
501 z = npc->pos.z;
502 if (useWorldYaw & 0xFF) {
503 add_vec2D_polar(&x, &z, fwdPosOffset, npc->yaw);
504 } else {
505 add_vec2D_polar(&x, &z, fwdPosOffset, 270.0f - npc->renderYaw);
506 }
507 if (dist2D(x, z, playerStatus->pos.x, playerStatus->pos.z) <= radius) {
508 return TRUE;
509 }
510 }
511
512 return FALSE;
513}
514
515s32 ai_check_player_dist(Enemy* enemy, s32 chance, f32 radius, f32 moveSpeed) {
516 PlayerStatus* playerStatus = &gPlayerStatus;
517 Npc* npc = get_npc_unsafe(enemy->npcID);
518 f32 posX, posZ;
519
520 if (chance >= 0) {
521 s32 skipCheckForPlayer;
522
523 if (chance != 0) {
524 skipCheckForPlayer = rand_int(chance + 1);
525 } else {
526 skipCheckForPlayer = 0;
527 }
528
529 if (skipCheckForPlayer == 0) {
530 posX = npc->pos.x;
531 posZ = npc->pos.z;
532 add_vec2D_polar(&posX, &posZ, moveSpeed, 270.0f - npc->renderYaw);
533
534 if (dist2D(posX, posZ, playerStatus->pos.x, playerStatus->pos.z) <= radius) {
535 return TRUE;
536 }
537 }
538 }
539
540 return FALSE;
541}
542
543void ai_enemy_play_sound(Npc* npc, s32 soundID, s32 upperSoundFlags) {
544 Enemy* enemy = get_enemy(npc->npcID);
545 s32 soundFlags = (upperSoundFlags & SOUND_SPACE_PARAMS_MASK) | SOUND_SPACE_FULL;
546
547 if (upperSoundFlags & 1) {
548 soundFlags |= SOUND_PARAM_MUTE;
549 }
550
551 if (enemy->npcSettings->actionFlags & AI_ACTION_20) {
552 soundFlags |= SOUND_PARAM_CLIP_OFFSCREEN_ANY;
553 }
554
555 sfx_play_sound_at_position(soundID, soundFlags, npc->pos.x, npc->pos.y, npc->pos.z);
556}
557
558void ai_try_set_state(Evt* script, s32 state) {
559 Npc* npc = get_npc_unsafe(script->owner1.enemy->npcID);
560
561 npc->duration--;
562 if (npc->duration <= 0) {
563 script->AI_TEMP_STATE = state;
564 }
565}
566
567void basic_ai_wander_init(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
568 Enemy* enemy = script->owner1.enemy;
569 Npc* npc = get_npc_unsafe(enemy->npcID);
570
571 // chose a random direction and move time
572 npc->duration = (npcAISettings->moveTime / 2) + rand_int((npcAISettings->moveTime / 2) + 1);
573 npc->yaw = clamp_angle(npc->yaw + rand_int(60) - 30.0f);
575 script->functionTemp[1] = 0;
576
577 if (enemy->territory->wander.moveSpeedOverride < 0) {
578 npc->moveSpeed = npcAISettings->moveSpeed;
579 } else {
580 npc->moveSpeed = enemy->territory->wander.moveSpeedOverride / 32767.0;
581 }
582
583 enemy->aiFlags &= ~AI_FLAG_NEEDS_HEADING;
584 enemy->aiFlags &= ~AI_FLAG_OUTSIDE_TERRITORY;
585 script->AI_TEMP_STATE = AI_STATE_WANDER;
586}
587
588void basic_ai_wander(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
589 Enemy* enemy = script->owner1.enemy;
590 Npc* npc = get_npc_unsafe(enemy->npcID);
591 s32 stillWithinTerritory = FALSE;
592 f32 x, y, z;
593 EffectInstance* sp34;
594 f32 yaw;
595
596 // search for the player
597 if (aiSettings->playerSearchInterval >= 0) {
598 if (script->functionTemp[1] <= 0) {
599 script->functionTemp[1] = aiSettings->playerSearchInterval;
600 if (basic_ai_check_player_dist(territory, enemy, aiSettings->alertRadius, aiSettings->alertOffsetDist, 0)) {
601 x = npc->pos.x;
602 y = npc->pos.y;
603 z = npc->pos.z;
604 yaw = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
605 if (!npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, aiSettings->chaseSpeed, yaw, npc->collisionHeight, npc->collisionDiameter)) {
606 npc->yaw = yaw;
608 fx_emote(EMOTE_EXCLAMATION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &sp34);
609 enemy->aiFlags &= ~AI_FLAG_NEEDS_HEADING;
610 enemy->aiFlags &= ~AI_FLAG_OUTSIDE_TERRITORY;
611
613 script->AI_TEMP_STATE = AI_STATE_ALERT_INIT;
614 } else {
615 script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
616 }
617 return;
618 }
619 }
620 }
621 script->functionTemp[1]--;
622 }
623
624 // check if we've wandered beyond the boundary of the territory
626 enemy->territory->wander.centerPos.x,
627 enemy->territory->wander.centerPos.z,
628 npc->pos.x,
629 npc->pos.z,
632 && npc->moveSpeed < dist2D(enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z, npc->pos.x, npc->pos.z)
633 ) {
634 if (!(enemy->aiFlags & AI_FLAG_OUTSIDE_TERRITORY)) {
636 }
637
638 if (enemy->aiFlags & AI_FLAG_NEEDS_HEADING) {
639 npc->yaw = clamp_angle(atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z));
640 enemy->aiFlags &= ~AI_FLAG_NEEDS_HEADING;
641 }
642
643 // if current heading is deflected by a wall, recalculate yaw to continue pursuing centerPos
644 x = npc->pos.x;
645 y = npc->pos.y;
646 z = npc->pos.z;
647 if (npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, 2.0 * npc->moveSpeed, npc->yaw, npc->collisionHeight, npc->collisionDiameter)) {
648 yaw = clamp_angle(atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z));
649 enemy->aiFlags &= ~AI_FLAG_NEEDS_HEADING;
650 ai_check_fwd_collisions(npc, 5.0f, &yaw, NULL, NULL, NULL);
651 npc->yaw = yaw;
652 }
653 stillWithinTerritory = TRUE;
654 } else if (enemy->aiFlags & AI_FLAG_OUTSIDE_TERRITORY) {
655 enemy->aiFlags &= ~AI_FLAG_OUTSIDE_TERRITORY;
656 enemy->aiFlags &= ~AI_FLAG_NEEDS_HEADING;
657 }
658
659 // perform the motion
660 if (enemy->territory->wander.wanderSize.x | enemy->territory->wander.wanderSize.z | stillWithinTerritory) {
661 if (!npc->turnAroundYawAdjustment) {
662 npc_move_heading(npc, npc->moveSpeed, npc->yaw);
663 } else {
664 return;
665 }
666 }
667
668 // decide to loiter or continue wandering
669 if (aiSettings->moveTime > 0) {
670 npc->duration--;
671 if (npc->duration <= 0) {
672 script->AI_TEMP_STATE = AI_STATE_LOITER_INIT;
673 script->functionTemp[1] = rand_int(1000) % 3 + 2;
674 if (aiSettings->unk_AI_2C <= 0 || aiSettings->waitTime <= 0) {
675 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
676 }
677 }
678 }
679}
680
681void basic_ai_loiter_init(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
682 Enemy* enemy = script->owner1.enemy;
683 Npc* npc = get_npc_unsafe(enemy->npcID);
684
685 npc->duration = (aiSettings->waitTime / 2) + rand_int((aiSettings->waitTime / 2) + 1);
686 npc->yaw = clamp_angle(npc->yaw + rand_int(180) - 90.0f);
688 script->AI_TEMP_STATE = AI_STATE_LOITER;
689}
690
691void basic_ai_loiter(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
692 Enemy* enemy = script->owner1.enemy;
693 Npc* npc = get_npc_unsafe(enemy->npcID);
694 f32 x, y, z;
695 f32 yaw;
696 EffectInstance* emoteTemp;
697
698 if (aiSettings->playerSearchInterval >= 0) {
699 if (basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 0)) {
700 x = npc->pos.x;
701 y = npc->pos.y;
702 z = npc->pos.z;
703 yaw = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
704 if (!npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, aiSettings->chaseSpeed, yaw, npc->collisionHeight, npc->collisionDiameter)) {
705 npc->yaw = yaw;
707 fx_emote(EMOTE_EXCLAMATION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &emoteTemp);
709 script->AI_TEMP_STATE = AI_STATE_ALERT_INIT;
710 } else {
711 script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
712 }
713 return;
714 }
715 }
716 }
717
718 // look around randomly
719 if (npc->turnAroundYawAdjustment == 0) {
720 npc->duration--;
721 if (npc->duration <= 0) {
722 script->functionTemp[1]--;
723 if (script->functionTemp[1]) {
725 npc->yaw = clamp_angle(npc->yaw + 180.0f);
726 }
727 npc->duration = (aiSettings->waitTime / 2) + rand_int(aiSettings->waitTime / 2 + 1);
728 return;
729 }
730 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
731 }
732 }
733}
734
736 Enemy* enemy = script->owner1.enemy;
737 Npc* npc = get_npc_unsafe(enemy->npcID);
738
741 npc->jumpVel = 10.0f;
742 npc->jumpScale = 2.5f;
743 npc->moveToPos.y = npc->pos.y;
744 npc->flags |= NPC_FLAG_JUMPING;
745 script->AI_TEMP_STATE = AI_STATE_ALERT;
746}
747
748void basic_ai_found_player_jump(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
749 Npc* npc = get_npc_unsafe(script->owner1.enemy->npcID);
750 s32 done = FALSE;
751
752 if (npc->jumpVel <= 0.0) {
753 if (npc->pos.y <= npc->moveToPos.y) {
754 npc->pos.y = npc->moveToPos.y;
755 done = TRUE;
756 }
757 }
758
759 if (!done) {
760 npc->pos.y += npc->jumpVel;
761 npc->jumpVel -= npc->jumpScale;
762 } else {
763 npc->jumpVel = 0.0f;
764 npc->flags &= ~NPC_FLAG_JUMPING;
765 script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
766 }
767}
768
769void basic_ai_chase_init(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
770 Enemy* enemy = script->owner1.enemy;
771 Npc* npc = get_npc_unsafe(enemy->npcID);
772 s32 skipTurnAround = FALSE;
773
777 {
778 skipTurnAround = TRUE;
779 }
780
781 if (!skipTurnAround) {
782 f32 angle = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
783 f32 deltaAngleToPlayer = get_clamped_angle_diff(npc->yaw, angle);
784
785 if (npcAISettings->chaseTurnRate < fabsf(deltaAngleToPlayer)) {
786 angle = npc->yaw;
787 if (deltaAngleToPlayer < 0.0f) {
788 angle += -npcAISettings->chaseTurnRate;
789 } else {
790 angle += npcAISettings->chaseTurnRate;
791 }
792 }
793 npc->yaw = clamp_angle(angle);
794 npc->duration = (npcAISettings->chaseUpdateInterval / 2) + rand_int((npcAISettings->chaseUpdateInterval / 2) + 1);
795 } else {
796 npc->duration = 0;
797 }
798
800 npc->moveSpeed = npcAISettings->chaseSpeed;
801 script->AI_TEMP_STATE = AI_STATE_CHASE;
802}
803
804void basic_ai_chase(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
805 Enemy* enemy = script->owner1.enemy;
806 Npc* npc = get_npc_unsafe(enemy->npcID);
807 EffectInstance* sp28;
808 f32 x, y, z;
809
810 if (!basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 1)) {
811 fx_emote(EMOTE_QUESTION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &sp28);
813 npc->duration = 20;
814 script->AI_TEMP_STATE = AI_STATE_LOSE_PLAYER;
815 return;
816 }
817
818 if (enemy->npcSettings->actionFlags & AI_ACTION_04) {
819 if (dist2D(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z) > (npc->moveSpeed * 5.0)) {
820 x = npc->pos.x;
821 y = npc->pos.y;
822 z = npc->pos.z;
823 if (npc_test_move_simple_with_slipping(npc->collisionChannel, &x, &y, &z, 1.0f, npc->yaw, npc->collisionHeight, npc->collisionDiameter)) {
824 fx_emote(EMOTE_QUESTION, npc, 0, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 0xC, &sp28);
826 npc->duration = 15;
827 script->AI_TEMP_STATE = AI_STATE_LOSE_PLAYER;
828 return;
829 }
830 }
831 }
832
834 npc_move_heading(npc, npc->moveSpeed, npc->yaw);
835
836 if (npc->moveSpeed > 8.0 && !(gGameStatusPtr->frameCounter % 5)) {
838 }
839
840 if (npc->duration > 0) {
841 npc->duration--;
842 } else {
843 script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
844 }
845}
846
847void basic_ai_lose_player(Evt* script, MobileAISettings* npcAISettings, EnemyDetectVolume* territory) {
848 Enemy* enemy = script->owner1.enemy;
849 Npc* npc = get_npc_unsafe(enemy->npcID);
850
851 npc->duration--;
852 if (npc->duration == 0) {
853 // turn to face home position
854 npc->yaw = clamp_angle(atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z));
855 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
856 }
857}
858
859API_CALLABLE(BasicAI_Main) {
860 Enemy* enemy = script->owner1.enemy;
861 Npc* npc = get_npc_unsafe(enemy->npcID);
862 Bytecode* args = script->ptrReadPos;
863 EnemyDetectVolume territory;
864 EnemyDetectVolume* pTerritory = &territory;
865 MobileAISettings* aiSettings = (MobileAISettings*) evt_get_variable(script, *args++);
866
867 territory.skipPlayerDetectChance = 0;
868 territory.shape = enemy->territory->wander.detectShape;
869 territory.pointX = enemy->territory->wander.detectPos.x;
870 territory.pointZ = enemy->territory->wander.detectPos.z;
871 territory.sizeX = enemy->territory->wander.detectSize.x;
872 territory.sizeZ = enemy->territory->wander.detectSize.z;
873 territory.halfHeight = 65.0f;
874 territory.detectFlags = 0;
875
876 if (isInitialCall || enemy->aiFlags & AI_FLAG_SUSPEND) {
877 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
878 npc->duration = 0;
879
881
882 npc->flags &= ~NPC_FLAG_JUMPING;
883 if (!enemy->territory->wander.isFlying) {
884 npc->flags |= NPC_FLAG_GRAVITY;
885 npc->flags &= ~NPC_FLAG_FLYING;
886 } else {
887 npc->flags &= ~NPC_FLAG_GRAVITY;
888 npc->flags |= NPC_FLAG_FLYING;
889 }
890
891 if (enemy->aiFlags & AI_FLAG_SUSPEND) {
892 script->AI_TEMP_STATE = AI_STATE_SUSPEND;
893 script->functionTemp[1] = AI_STATE_WANDER_INIT;
894 } else if (enemy->flags & ENEMY_FLAG_BEGIN_WITH_CHASING) {
895 script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
896 }
897
898 enemy->aiFlags &= ~AI_FLAG_SUSPEND;
899 enemy->flags &= ~ENEMY_FLAG_BEGIN_WITH_CHASING;
900 }
901
902 switch (script->AI_TEMP_STATE) {
904 basic_ai_wander_init(script, aiSettings, pTerritory);
905 case AI_STATE_WANDER:
906 basic_ai_wander(script, aiSettings, pTerritory);
907 break;
908
910 basic_ai_loiter_init(script, aiSettings, pTerritory);
911 case AI_STATE_LOITER:
912 basic_ai_loiter(script, aiSettings, pTerritory);
913 break;
914
916 basic_ai_found_player_jump_init(script, aiSettings, pTerritory);
917 case AI_STATE_ALERT:
918 basic_ai_found_player_jump(script, aiSettings, pTerritory);
919 break;
920
922 basic_ai_chase_init(script, aiSettings, pTerritory);
923 case AI_STATE_CHASE:
924 basic_ai_chase(script, aiSettings, pTerritory);
925 if (script->AI_TEMP_STATE != AI_STATE_LOSE_PLAYER) {
926 break;
927 }
928
930 basic_ai_lose_player(script, aiSettings, pTerritory);
931 break;
932 case AI_STATE_SUSPEND:
933 basic_ai_suspend(script);
934 break;
935 }
936 return ApiStatus_BLOCK;
937}
void ai_enemy_play_sound(Npc *npc, s32 soundID, s32 upperSoundFlags)
Definition 23680.c:543
b32 is_point_outside_territory(s32 shape, f32 centerX, f32 centerZ, f32 pointX, f32 pointZ, f32 sizeX, f32 sizeZ)
Definition 23680.c:412
void basic_ai_chase_init(Evt *script, MobileAISettings *npcAISettings, EnemyDetectVolume *territory)
Definition 23680.c:769
void basic_ai_lose_player(Evt *script, MobileAISettings *npcAISettings, EnemyDetectVolume *territory)
Definition 23680.c:847
void spawn_drops(Enemy *enemy)
Definition 23680.c:8
s32 get_coin_drop_amount(Enemy *enemy)
Definition 23680.c:312
b32 basic_ai_check_player_dist(EnemyDetectVolume *territory, Enemy *enemy, f32 radius, f32 fwdPosOffset, b8 useWorldYaw)
Definition 23680.c:429
void ai_try_set_state(Evt *script, s32 state)
Definition 23680.c:558
void basic_ai_wander(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
Definition 23680.c:588
s32 gLastRenderTaskCount
Definition model.c:636
void basic_ai_loiter(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
Definition 23680.c:691
void basic_ai_chase(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
Definition 23680.c:804
s32 func_80048F0C(void)
Definition 23680.c:390
void basic_ai_found_player_jump_init(Evt *script, MobileAISettings *npcAISettings, EnemyDetectVolume *territory)
Definition 23680.c:735
void basic_ai_wander_init(Evt *script, MobileAISettings *npcAISettings, EnemyDetectVolume *territory)
Definition 23680.c:567
void basic_ai_found_player_jump(Evt *script, MobileAISettings *npcAISettings, EnemyDetectVolume *territory)
Definition 23680.c:748
void func_80048E34(Enemy *enemy, s32 arg1, s32 arg2)
Definition 23680.c:358
void basic_ai_loiter_init(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
Definition 23680.c:681
s32 ai_check_player_dist(Enemy *enemy, s32 chance, f32 radius, f32 moveSpeed)
Definition 23680.c:515
s32 b32
union Evt::@8 owner1
Initially -1.
union Evt::@9 owner2
Initially -1.
s8 b8
#define sfx_play_sound_at_position
#define rand_int
#define clamp_angle
#define atan2
@ AI_DETECT_SIGHT
Definition enums.h:4621
@ AI_DETECT_SENSITIVE_MOTION
Definition enums.h:4622
@ EMOTE_EXCLAMATION
Definition enums.h:495
@ EMOTE_QUESTION
Definition enums.h:497
@ ENEMY_ANIM_INDEX_IDLE
Definition enums.h:3426
@ ENEMY_ANIM_INDEX_WALK
Definition enums.h:3427
@ ENEMY_ANIM_INDEX_JUMP
Definition enums.h:3430
@ ENEMY_ANIM_INDEX_CHASE
Definition enums.h:3429
@ ITEM_SPAWN_MODE_BATTLE_REWARD
Definition enums.h:2295
@ AI_FLAG_SUSPEND
Definition enums.h:4571
@ AI_FLAG_CANT_DETECT_PLAYER
Definition enums.h:4570
@ AI_FLAG_NEEDS_HEADING
Definition enums.h:4575
@ AI_FLAG_OUTSIDE_TERRITORY
Definition enums.h:4574
@ AI_STATE_WANDER
Definition enums.h:4582
@ AI_STATE_LOITER
Definition enums.h:4588
@ AI_STATE_SUSPEND
Definition enums.h:4598
@ AI_STATE_WANDER_INIT
Definition enums.h:4581
@ AI_STATE_LOITER_INIT
Definition enums.h:4587
@ AI_STATE_ALERT
Definition enums.h:4591
@ AI_STATE_CHASE_INIT
Definition enums.h:4592
@ AI_STATE_CHASE
Definition enums.h:4593
@ AI_STATE_LOSE_PLAYER
Definition enums.h:4594
@ AI_STATE_ALERT_INIT
Definition enums.h:4590
@ ENEMY_FLAG_NO_DROPS
Definition enums.h:4544
@ ENEMY_FLAG_DISABLE_AI
Definition enums.h:4526
@ ENEMY_FLAG_NO_DELAY_AFTER_FLEE
Definition enums.h:4539
@ ENEMY_FLAG_BEGIN_WITH_CHASING
Definition enums.h:4551
@ COLLIDER_FLAG_IGNORE_PLAYER
Definition enums.h:4696
@ COLLISION_IGNORE_ENTITIES
Definition enums.h:4698
@ ABILITY_FLOWER_FINDER
Definition enums.h:480
@ ABILITY_PAY_OFF
Definition enums.h:461
@ ABILITY_MONEY_MONEY
Definition enums.h:452
@ ABILITY_HEART_FINDER
Definition enums.h:479
@ SOUND_SMALL_NPC_STEP
Definition enums.h:1480
@ SOUND_AI_FOUND_PLAYER_JUMP
Definition enums.h:1289
@ SOUND_AI_ALERT_A
Definition enums.h:1078
@ AI_TERRITORY_IGNORE_HIDING
Definition enums.h:4627
@ AI_TERRITORY_IGNORE_ELEVATION
Definition enums.h:4628
@ PARTNER_BOW
Definition enums.h:2894
@ PARTNER_SUSHIE
Definition enums.h:2892
@ ACTION_STATE_JUMP
Definition enums.h:2430
@ ACTION_STATE_USE_SPINNING_FLOWER
Definition enums.h:2457
@ ACTION_STATE_FALLING
Definition enums.h:2435
@ ACTION_STATE_HOP
Released A before apex of jump.
Definition enums.h:2432
@ ACTION_STATE_WALK
Definition enums.h:2427
@ ACTION_STATE_BOUNCE
Used with Kooper.
Definition enums.h:2431
@ ACTION_STATE_RUN
Definition enums.h:2428
@ SURFACE_INTERACT_RUN
Definition enums.h:4685
@ AI_ACTION_20
Definition enums.h:4617
@ AI_ACTION_LOOK_AROUND_DURING_LOITER
Definition enums.h:4616
@ AI_ACTION_04
Definition enums.h:4614
@ AI_ACTION_JUMP_WHEN_SEE_PLAYER
Definition enums.h:4612
@ SOUND_SPACE_PARAMS_MASK
Definition enums.h:1740
@ SOUND_SPACE_FULL
Definition enums.h:1739
@ SOUND_PARAM_CLIP_OFFSCREEN_ANY
Definition enums.h:1742
@ SOUND_PARAM_MUTE
Definition enums.h:1741
@ SOUND_PARAM_MORE_QUIET
Definition enums.h:1746
@ NPC_FLAG_FLYING
Definition enums.h:3001
@ NPC_FLAG_JUMPING
Definition enums.h:3009
@ NPC_FLAG_GRAVITY
Definition enums.h:3007
@ EVT_PRIORITY_A
Definition evt.h:153
s32 Bytecode
Definition evt.h:7
#define ApiStatus_BLOCK
Definition evt.h:116
@ EVT_FLAG_RUN_IMMEDIATELY
don't wait for next update_scripts call
Definition evt.h:161
s32 evt_get_variable(Evt *script, Bytecode var)
Definition evt.c:1690
s32 is_ability_active(s32 arg0)
Definition inventory.c:1725
f32 fabsf(f32 f)
Shadow * get_shadow_by_index(s32 index)
Definition entity.c:534
f32 dist2D(f32 ax, f32 ay, f32 bx, f32 by)
Definition 43F0.c:670
s32 make_item_entity(s32 itemID, f32 x, f32 y, f32 z, s32 itemSpawnMode, s32 pickupDelay, s32 angle, s32 pickupVar)
b32 npc_test_move_simple_with_slipping(s32, f32 *, f32 *, f32 *, f32, f32, f32, f32)
Evt * start_script(EvtScript *source, s32 priority, s32 initialState)
void kill_script_by_ID(s32 id)
void add_vec2D_polar(f32 *x, f32 *y, f32 r, f32 theta)
Definition 43F0.c:685
s32 ai_check_fwd_collisions(Npc *npc, f32 arg1, f32 *arg2, f32 *arg3, f32 *arg4, f32 *arg5)
Definition 25AF0.c:22
f32 get_clamped_angle_diff(f32, f32)
Definition 43F0.c:606
void basic_ai_suspend(Evt *script)
Definition 25AF0.c:13
StatDrop heartDrops[8]
Definition npc.h:190
f32 chaseSpeed
Definition npc.h:99
s32 waitTime
Definition npc.h:95
enum TerritoryShape shape
Definition npc.h:201
s32 unk_C0
Definition npc.h:338
s16 coinsEarned
Definition npc.h:377
s32 chaseTurnRate
Definition npc.h:100
s32 chaseUpdateInterval
Definition npc.h:101
s32 flags
Definition npc.h:295
VecXZi detectSize
Definition npc.h:216
enum TerritoryShape detectShape
Definition npc.h:217
@ SHAPE_CYLINDER
Definition npc.h:197
@ SHAPE_RECT
Definition npc.h:197
s32 moveTime
Definition npc.h:94
s16 item
Definition npc.h:160
s16 npcID
Definition npc.h:300
EnemyDrops * drops
Definition npc.h:343
void npc_surface_spawn_fx(Npc *npc, SurfaceInteractMode mode)
Definition surfaces.c:394
Enemy * get_enemy(s32 npcID)
Looks for an enemy matching the specified npcID.
Definition npc.c:2540
StatDrop flowerDrops[8]
Definition npc.h:191
Encounter * encounterList[24]
Definition npc.h:392
s32 * animList
Definition npc.h:341
f32 moveSpeed
Definition npc.h:93
s16 flagIdx
Definition npc.h:162
u8 damageTaken
Definition npc.h:375
VecXZi wanderSize
Definition npc.h:212
s16 chancePerAttempt
% chance for a single heart/flower to be dropped from each attempt.
Definition npc.h:183
ItemDrop itemDrops[8]
Definition npc.h:189
b8 hasMerleeCoinBonus
Definition npc.h:374
s8 numEncounters
Definition npc.h:384
Enemy * enemy[16]
Definition npc.h:352
u8 itemDropChance
Definition npc.h:188
Npc * get_npc_unsafe(s32 npcID)
Definition npc.c:995
s32 unk_C4
Definition npc.h:339
s32 count
Definition npc.h:351
s16 actionFlags
Definition npc.h:156
s16 generalChance
% chance for any hearts/flowers to be dropped at all from this StatDrop.
Definition npc.h:181
s16 detectFlags
Definition npc.h:207
f32 alertOffsetDist
Definition npc.h:97
s16 weight
Definition npc.h:161
s32 aiScriptID
Definition npc.h:319
s16 attempts
Maximum number of hearts/flowers that can be dropped from this StatDrop.
Definition npc.h:182
s32 moveSpeedOverride
Definition npc.h:213
s32 playerSearchInterval
Definition npc.h:98
f32 chaseRadius
Definition npc.h:102
s16 maxCoinBonus
Definition npc.h:193
u8 aiDetectFlags
Definition npc.h:330
NpcSettings * npcSettings
Definition npc.h:304
EncounterStatus gCurrentEncounter
Definition encounter.c:176
f32 alertRadius
Definition npc.h:96
enum TerritoryShape wanderShape
Definition npc.h:214
s16 cutoff
% of max HP/FP. If current HP/FP > cutoff, no hearts/flowers can be dropped.
Definition npc.h:180
u32 aiFlags
Definition npc.h:332
s16 minCoinBonus
Definition npc.h:192
void npc_move_heading(Npc *npc, f32 speed, f32 yaw)
Definition npc.c:986
s32 unk_AI_2C
Definition npc.h:104
s8 dropWhackaBump
Definition npc.h:381
EvtScript * unk_B8
Definition npc.h:336
s32 unk_C8
Definition npc.h:340
s32 skipPlayerDetectChance
Definition npc.h:200
EvtScript * aiBytecode
Definition npc.h:307
struct Evt * unk_BC
Definition npc.h:337
EnemyTerritoryWander wander
Definition npc.h:232
struct Evt * aiScript
Definition npc.h:313
f32 chaseOffsetDist
Definition npc.h:103
EnemyTerritory * territory
Definition npc.h:342
Definition npc.h:294
@ GF_SpawnedItemDrop_00
comes from the third u16 in the selected item entry for enemy's drop table
#define ARRAY_COUNT(arr)
Definition macros.h:40
#define MAX_SHADOWS
Definition macros.h:93
#define EVT_INDEX_OF_GAME_FLAG(v)
Definition macros.h:142
s16 collisionDiameter
f32 jumpScale
f32 jumpVel
s32 flags
f32 renderYaw
AnimID curAnim
Vec3f moveToPos
s32 collisionChannel
s16 collisionHeight
s16 turnAroundYawAdjustment
Vec3f pos
f32 moveSpeed
s16 duration
PlayerStatus * gPlayerStatusPtr
PartnerStatus gPartnerStatus
Definition partners.c:42
GameStatus * gGameStatusPtr
Definition main_loop.c:32
Camera gCameras[4]
Definition cam_main.c:17
PlayerData gPlayerData
Definition 77480.c:40
PlayerStatus gPlayerStatus
Definition 77480.c:39
s32 gCurrentCameraID
Definition cam_math.c:4
s32 set_global_flag(s32 index)
Definition vars_access.c:65
s32 get_global_flag(s32 index)
Definition vars_access.c:89