Paper Mario DX
Paper Mario (N64) modding
 
Loading...
Searching...
No Matches
LakituAI.inc.c
Go to the documentation of this file.
1#include "common.h"
2#include "npc.h"
3#include "sprite/npc/Lakitu.h"
4
5// required include args
6#ifndef AI_LAKITU_FIRST_SPINY_ID
7#error AI_LAKITU_FIRST_SPINY_ID must be defined for LakituAI
8#define AI_LAKITU_FIRST_SPINY_ID 0
9#endif
10#ifndef AI_LAKITU_LAST_SPINY_ID
11#error AI_LAKITU_LAST_SPINY_ID must be defined for LakituAI
12#define AI_LAKITU_LAST_SPINY_ID 0
13#endif
14
15// prerequisites
17
19 s32 npcID;
20
21 for (npcID = AI_LAKITU_FIRST_SPINY_ID; npcID <= AI_LAKITU_LAST_SPINY_ID; npcID++) {
22 if (get_enemy(npcID)->varTable[10] == 0) {
23 return npcID;
24 }
25 }
26
27 return -1;
28}
29
30void N(LakituAI_Wander)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
31 Enemy* enemy = script->owner1.enemy;
32 Npc* npc = get_npc_unsafe(enemy->npcID);
33 f32 x, y, z, w;
34 EffectInstance* emoteTemp;
35 f32 temp_f20;
36 f32 temp_f22;
37 f32 temp_f24;
38 f32 var1 = enemy->varTable[4];
39 f32 var2 = enemy->varTable[3];
40 f32 var3;
41
42 temp_f22 = var1 / 100.0;
43 temp_f24 = var2 / 100.0;
44 if (npc->duration > 0) {
45 npc->duration--;
46 }
47 var3 = enemy->varTable[1];
48 temp_f20 = var3 / 100.0;
49
50 x = npc->pos.x;
51 y = npc->pos.y;
52 z = npc->pos.z;
53 w = 1000.0f;
54
55 npc_raycast_down_sides(npc->collisionChannel, &x, &y, &z, &w);
56 npc->pos.y = y + temp_f24 + (sin_deg(enemy->varTable[2]) * temp_f20);
57 enemy->varTable[2] = clamp_angle(enemy->varTable[2] + 12);
58
59 if (aiSettings->playerSearchInterval >= 0) {
60 if (script->functionTemp[1] <= 0) {
61 script->functionTemp[1] = aiSettings->playerSearchInterval;
62 if (basic_ai_check_player_dist(territory, enemy, aiSettings->alertRadius, aiSettings->alertOffsetDist, 0) != 0) {
63 fx_emote(EMOTE_EXCLAMATION, npc, 0.0f, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &emoteTemp);
65 x = npc->pos.x;
66 y = npc->pos.y;
67 z = npc->pos.z;
68 w = 1000.0f;
69 npc_raycast_down_sides(npc->collisionChannel, &x, &y, &z, &w);
70 npc->moveToPos.y = y + temp_f24;
71 script->functionTemp[0] = 12;
72 return;
73 }
74 }
75 script->functionTemp[1]--;
76 }
77
79 enemy->territory->wander.centerPos.z, npc->pos.x, npc->pos.z, enemy->territory->wander.wanderSize.x,
80 enemy->territory->wander.wanderSize.z) != 0) {
81 npc->yaw = atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z);
82 }
83
84 if (npc->turnAroundYawAdjustment == 0) {
85 if (npc->duration > 0) {
86 npc_move_heading(npc, npc->moveSpeed, npc->yaw);
87 if (enemy->varTable[0] & 1) {
88 x = npc->pos.x;
89 y = temp_f22;
90 z = npc->pos.z;
91 w = 1000.0f;
92 npc_raycast_down_sides(npc->collisionChannel, &x, &y, &z, &w);
93 y += temp_f24;
94 w = y - temp_f22;
95 if (w > 2.0) {
96 temp_f22 += 2.0;
97 } else if (w < -2.0) {
98 temp_f22 -= 2.0;
99 } else {
100 temp_f22 = y;
101 }
102 enemy->varTable[4] = (temp_f22 * 100.0);
103 }
104 if (npc->duration > 0) {
105 return;
106 }
107 }
108 script->functionTemp[0] = 2;
109 script->functionTemp[1] = (rand_int(1000) % 3) + 2;
110 if ((aiSettings->unk_AI_2C <= 0) || (aiSettings->moveTime <= 0) || (script->functionTemp[1] == 0)) {
111 script->functionTemp[0] = 0;
112 }
113 }
114}
115
116void N(LakituAI_Loiter)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
117 Enemy* enemy = script->owner1.enemy;
118 Npc* npc = get_npc_unsafe(enemy->npcID);
119 f32 posX, posY, posZ, hitDepth;
120 EffectInstance* emoteTemp;
121 f32 var1 = enemy->varTable[3];
122 f32 var2;
123 f32 temp_f20;
124 f32 temp_f22;
125
126 temp_f22 = var1 / 100.0;
127 if (npc->duration > 0) {
128 npc->duration--;
129 }
130 var2 = enemy->varTable[1];
131 temp_f20 = var2 / 100.0;
132
133 posX = npc->pos.x;
134 posY = npc->pos.y;
135 posZ = npc->pos.z;
136 hitDepth = 1000.0f;
137
138 npc_raycast_down_sides(npc->collisionChannel, &posX, &posY, &posZ, &hitDepth);
139 npc->pos.y = posY + temp_f22 + (sin_deg(enemy->varTable[2]) * temp_f20);
140 enemy->varTable[2] = clamp_angle(enemy->varTable[2] + 12);
141 if (basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 1) != 0) {
142 fx_emote(EMOTE_EXCLAMATION, npc, 0.0f, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 15, &emoteTemp);
144 script->AI_TEMP_STATE = AI_STATE_CHASE_INIT;
145 return;
146 }
147
148 if ((npc->turnAroundYawAdjustment == 0) && (npc->duration <= 0)) {
149 script->functionTemp[1]--;
150 if (script->functionTemp[1] > 0) {
151 npc->yaw = clamp_angle(npc->yaw + 180.0f);
152 npc->duration = (rand_int(1000) % 11) + 5;
153 return;
154 }
155 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
156 }
157}
158
159API_CALLABLE(N(LakituAI_Main)) {
160 Enemy* enemy = script->owner1.enemy;
161 Bytecode* args = script->ptrReadPos;
162 Npc* npc = get_npc_unsafe(enemy->npcID);
163 EnemyDetectVolume territory;
164 EnemyDetectVolume* territoryPtr = &territory;
165 MobileAISettings* aiSettings = (MobileAISettings*)evt_get_variable(script, *args);
166 Enemy* spinyEnemy;
167
168 territory.skipPlayerDetectChance = 0;
169 territory.shape = enemy->territory->wander.detectShape;
170 territory.pointX = enemy->territory->wander.detectPos.x;
171 territory.pointZ = enemy->territory->wander.detectPos.z;
172 territory.sizeX = enemy->territory->wander.detectSize.x;
173 territory.sizeZ = enemy->territory->wander.detectSize.z;
174 territory.halfHeight = 120.0f;
175 territory.detectFlags = 0;
176
177 if (isInitialCall) {
178 N(FlyingAI_Init)(npc, enemy, script, aiSettings);
179 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
180 }
181 npc->verticalRenderOffset = -3;
182
183 if (enemy->aiFlags & AI_FLAG_SUSPEND) {
184 if (enemy->aiSuspendTime != 0) {
185 return ApiStatus_BLOCK;
186 }
187 enemy->aiFlags &= ~AI_FLAG_SUSPEND;
188 }
189
190 switch (script->AI_TEMP_STATE) {
192 N(FlyingAI_WanderInit)(script, aiSettings, territoryPtr);
193 // fallthrough
194 case AI_STATE_WANDER:
195 N(LakituAI_Wander)(script, aiSettings, territoryPtr);
196 break;
198 N(FlyingAI_LoiterInit)(script, aiSettings, territoryPtr);
199 // fallthrough
200 case AI_STATE_LOITER:
201 N(LakituAI_Loiter)(script, aiSettings, territoryPtr);
202 break;
203 }
204
205 if (script->AI_TEMP_STATE == AI_STATE_CHASE_INIT) {
206 npc->yaw = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
207 enemy->varTable[4] = N(LakituAI_GetAvailableSpiny)();
208 if (enemy->varTable[4] >= 0) {
209 spinyEnemy = get_enemy(enemy->varTable[4]);
210 spinyEnemy->varTable[10] = 1;
211 spinyEnemy->varTable[11] = enemy->npcID;
212 npc->duration = 15;
213 npc->curAnim = ANIM_Lakitu_Anim14;
214 script->AI_TEMP_STATE = 30;
215 }
216 }
217
218 switch (script->AI_TEMP_STATE) {
220 N(FlyingAI_ChaseInit)(script, aiSettings, territoryPtr);
221 // fallthrough
222 case AI_STATE_CHASE:
223 N(FlyingAI_Chase)(script, aiSettings, territoryPtr);
224 break;
226 N(FlyingAI_LosePlayer)(script, aiSettings, territoryPtr);
227 break;
228 }
229
230 switch (script->AI_TEMP_STATE) {
232 npc->duration--;
233 if (npc->duration > 0) {
234 break;
235 }
236 spinyEnemy = get_enemy(enemy->varTable[4]);
237 spinyEnemy->varTable[10] = 2;
238 npc->duration = 5;
239 script->AI_TEMP_STATE = AI_STATE_PROJECTILE_HITBOX_31;
240 // fallthrough
242 npc->duration--;
243 if (npc->duration > 0) {
244 break;
245 }
246 npc->curAnim = ANIM_Lakitu_Anim15;
247 spinyEnemy = get_enemy(enemy->varTable[4]);
248 spinyEnemy->varTable[10] = 3;
249 npc->duration = 10;
250 script->AI_TEMP_STATE = AI_STATE_PROJECTILE_HITBOX_32;
251 // fallthrough
253 npc->duration--;
254 if (npc->duration > 0) {
255 break;
256 }
257 npc->duration = 3;
258 script->AI_TEMP_STATE = AI_STATE_PROJECTILE_HITBOX_33;
259 // fallthrough
261 npc->duration--;
262 if (npc->duration <= 0) {
263 script->AI_TEMP_STATE = AI_STATE_WANDER_INIT;
264 }
265 break;
266 }
267
268 if (script->AI_TEMP_STATE == AI_STATE_PROJECTILE_HITBOX_30 || script->AI_TEMP_STATE == AI_STATE_PROJECTILE_HITBOX_31) {
269 f32 playerDist;
270 f32 lerpDist;
271
272 npc->yaw = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
273 playerDist = dist2D(gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z, npc->pos.x, npc->pos.z);
274 if (!is_point_outside_territory(territoryPtr->shape,
275 territoryPtr->pointX, territoryPtr->pointZ,
276 npc->pos.x, npc->pos.z,
277 territoryPtr->sizeX, territoryPtr->sizeZ)) {
278 if ((playerDist > 30.0) && (npc->turnAroundYawAdjustment == 0)) {
279 lerpDist = playerDist - 20.0;
280 if (lerpDist < 0.0) {
281 lerpDist = 0.0f;
282 }
283 if (lerpDist > 200.0) {
284 lerpDist = 200.0f;
285 }
286 npc->moveSpeed = update_lerp(EASING_LINEAR, aiSettings->moveSpeed, aiSettings->chaseSpeed, lerpDist, 200);
287 npc_move_heading(npc, npc->moveSpeed, npc->yaw);
288 }
289 }
290 }
291
292 return ApiStatus_BLOCK;
293}
294
void N FlyingAI_ChaseInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N FlyingAI_LoiterInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N FlyingAI_Init(Npc *npc, Enemy *enemy, Evt *script, MobileAISettings *aiSettings)
void N FlyingAI_WanderInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N FlyingAI_Chase(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N FlyingAI_LosePlayer(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
#define AI_LAKITU_LAST_SPINY_ID
void N LakituAI_Loiter(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
#define AI_LAKITU_FIRST_SPINY_ID
Definition LakituAI.inc.c:8
void N LakituAI_Wander(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
s32 N LakituAI_GetAvailableSpiny(void)
#define npc_raycast_down_sides
#define sin_deg
#define rand_int
#define clamp_angle
#define atan2
@ EMOTE_EXCLAMATION
Definition enums.h:495
@ AI_FLAG_SUSPEND
Definition enums.h:4571
@ AI_STATE_PROJECTILE_HITBOX_30
Definition enums.h:4605
@ AI_STATE_WANDER
Definition enums.h:4582
@ AI_STATE_LOITER
Definition enums.h:4588
@ AI_STATE_WANDER_INIT
Definition enums.h:4581
@ AI_STATE_LOITER_INIT
Definition enums.h:4587
@ AI_STATE_CHASE_INIT
Definition enums.h:4592
@ AI_STATE_CHASE
Definition enums.h:4593
@ AI_STATE_PROJECTILE_HITBOX_32
Definition enums.h:4607
@ AI_STATE_PROJECTILE_HITBOX_31
Definition enums.h:4606
@ AI_STATE_PROJECTILE_HITBOX_33
Definition enums.h:4608
@ AI_STATE_LOSE_PLAYER
Definition enums.h:4594
@ EASING_LINEAR
Definition enums.h:510
@ SOUND_AI_ALERT_A
Definition enums.h:1078
@ SOUND_PARAM_MORE_QUIET
Definition enums.h:1746
s32 Bytecode
Definition evt.h:7
#define ApiStatus_BLOCK
Definition evt.h:116
s32 evt_get_variable(Evt *script, Bytecode var)
Definition evt.c:1690
f32 update_lerp(s32 easing, f32 start, f32 end, s32 elapsed, s32 duration)
Definition 43F0.c:735
b32 is_point_outside_territory(s32 shape, f32 centerX, f32 centerZ, f32 pointX, f32 pointZ, f32 sizeX, f32 sizeZ)
Definition 23680.c:412
void ai_enemy_play_sound(Npc *npc, s32 arg1, s32 arg2)
Definition 23680.c:543
f32 dist2D(f32 ax, f32 ay, f32 bx, f32 by)
Definition 43F0.c:670
f32 chaseSpeed
Definition npc.h:99
enum TerritoryShape shape
Definition npc.h:201
VecXZi detectSize
Definition npc.h:216
enum TerritoryShape detectShape
Definition npc.h:217
s32 basic_ai_check_player_dist(EnemyDetectVolume *arg0, Enemy *arg1, f32 arg2, f32 arg3, b8 arg4)
Definition 23680.c:429
s16 npcID
Definition npc.h:300
Enemy * get_enemy(s32 npcID)
Looks for an enemy matching the specified npcID.
Definition npc.c:2540
f32 moveSpeed
Definition npc.h:93
VecXZi wanderSize
Definition npc.h:212
s8 aiSuspendTime
Definition npc.h:333
Npc * get_npc_unsafe(s32 npcID)
Definition npc.c:995
s16 detectFlags
Definition npc.h:207
enum TerritoryShape wanderShape
Definition npc.h:214
u32 aiFlags
Definition npc.h:332
void npc_move_heading(Npc *npc, f32 speed, f32 yaw)
Definition npc.c:986
s32 skipPlayerDetectChance
Definition npc.h:200
EnemyTerritoryWander wander
Definition npc.h:232
EnemyTerritory * territory
Definition npc.h:342
Definition npc.h:294
AnimID curAnim
Vec3f moveToPos
s32 collisionChannel
s16 collisionHeight
s16 turnAroundYawAdjustment
s8 verticalRenderOffset
Vec3f pos
f32 moveSpeed
s16 duration
PlayerStatus * gPlayerStatusPtr