Paper Mario DX
Paper Mario (N64) modding
 
Loading...
Searching...
No Matches
SentinelAI.inc.c
Go to the documentation of this file.
1#include "common.h"
2#include "npc.h"
3#include "effects.h"
4#include "sprite.h"
5
6// required include args
7#ifndef AI_SENTINEL_FIRST_NPC
8#error AI_SENTINEL_FIRST_NPC must be defined for SentinelAI.inc.c
9#define AI_SENTINEL_FIRST_NPC 0
10#endif
11#ifndef AI_SENTINEL_LAST_NPC
12#error AI_SENTINEL_LAST_NPC must be defined for SentinelAI.inc.c
13#define AI_SENTINEL_LAST_NPC 0
14#endif
15
16// prerequisites
18
36
37#define SENTINEL_AI_FLAG_CHASING 0x100 // use to ensure only one sentinel can 'attack' at a time
38#define SENTINEL_AI_FLAG_PLAYING_SOUND 0x1000 // use to force looping sound to stop
39
40#define SENTINEL_AI_DESCEND_RATE 1.8
41
42void N(SentinelAI_ChaseInit)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
43 Enemy* enemy = script->owner1.enemy;
44 Npc* npc = get_npc_unsafe(enemy->npcID);
45 f32 deltaAngle;
46 f32 angle;
47
48 npc->duration--;
49 if (npc->duration <= 0) {
50 npc->flags &= ~NPC_FLAG_FLIP_INSTANTLY;
51 npc->duration = aiSettings->chaseUpdateInterval / 2 + rand_int(aiSettings->chaseUpdateInterval / 2 + 1);
53 npc->moveSpeed = aiSettings->chaseSpeed;
54 angle = atan2(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x, gPlayerStatusPtr->pos.z);
55 deltaAngle = get_clamped_angle_diff(npc->yaw, angle);
56 if (aiSettings->chaseTurnRate < fabsf(deltaAngle)) {
57 angle = npc->yaw;
58 if (deltaAngle < 0.0f) {
59 angle += -aiSettings->chaseTurnRate;
60 } else {
61 angle += aiSettings->chaseTurnRate;
62 }
63 }
64 npc->yaw = clamp_angle(angle);
65 script->AI_TEMP_STATE = AI_STATE_SENTINEL_CHASE;
66 }
67}
68
69void N(SentinelAI_Chase)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
70 Enemy* enemy = script->owner1.enemy;
71 Npc* npc = get_npc_unsafe(enemy->npcID);
72
73 if (basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 1)) {
74 npc_move_heading(npc, npc->moveSpeed, npc->yaw);
75 if (dist2D(npc->pos.x, npc->pos.z, gPlayerStatusPtr->pos.x,
76 gPlayerStatusPtr->pos.z) <= (npc->moveSpeed * 2.5)) {
77 npc->duration = 0;
78 script->AI_TEMP_STATE = AI_STATE_SENTINEL_DESCEND_INIT;
79 } else {
80 npc->duration--;
81 if (npc->duration <= 0) {
83 script->AI_TEMP_STATE = AI_STATE_SENTINEL_CHASE_INIT;
84 }
85 }
86 } else {
87 script->AI_TEMP_STATE = AI_STATE_SENTINEL_LOSE_PLAYER_INIT;
88 }
89}
90
91void N(SentinelAI_DescendInit)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
92 Enemy* enemy = script->owner1.enemy;
93 Npc* npc = get_npc_unsafe(enemy->npcID);
94 s32 i;
95
97 if (i != npc->npcID && (get_enemy(i)->varTable[0] & SENTINEL_AI_FLAG_CHASING)) {
98 return;
99 }
100 }
101
102 enemy->varTable[0] |= SENTINEL_AI_FLAG_CHASING;
103 npc->pos.x = gPlayerStatusPtr->pos.x;
104 npc->pos.z = gPlayerStatusPtr->pos.z;
105 if (!(enemy->varTable[0] & SENTINEL_AI_FLAG_PLAYING_SOUND)) {
106 enemy->varTable[0] |= SENTINEL_AI_FLAG_PLAYING_SOUND;
107 }
109 npc->duration = 0;
110 script->AI_TEMP_STATE = AI_STATE_SENTINEL_DESCEND;
111}
112
113void N(SentinelAI_Descend)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
114 Enemy* enemy = script->owner1.enemy;
115 Npc* npc = get_npc_unsafe(enemy->npcID);
116 f32 posX, posY, posZ, hitDepth;
117 s32 color;
118
121 if (!basic_ai_check_player_dist(territory, enemy, aiSettings->chaseRadius, aiSettings->chaseOffsetDist, 1)) {
122 enemy->varTable[0] &= ~SENTINEL_AI_FLAG_CHASING;
123 npc->rot.y = 0.0f;
124 npc->flags &= ~NPC_FLAG_FLIP_INSTANTLY;
125 script->AI_TEMP_STATE = AI_STATE_SENTINEL_LOSE_PLAYER_INIT;
126 } else {
127 npc->pos.x = gPlayerStatusPtr->pos.x;
128 npc->pos.z = gPlayerStatusPtr->pos.z + 2.0f;
129 npc->rot.y += 25.0f;
130 if (npc->rot.y > 360.0) {
131 npc->rot.y -= 360.0;
132 }
133 color = 255.0f - (cosine((s32)npc->rot.y % 180) * 56.0f);
134 set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_SET_COLOR, color, color, color, 255, 0);
135
136 posX = gPlayerStatusPtr->pos.x;
137 posY = gPlayerStatusPtr->pos.y;
138 posZ = gPlayerStatusPtr->pos.z;
139 hitDepth = 1000.0f;
140 npc_raycast_down_sides(npc->collisionChannel, &posX, &posY, &posZ, &hitDepth);
141 if (fabsf(npc->pos.y - posY) > 24.0) {
143 } else {
144 npc->rot.y = 0.0f;
145 npc->flags &= ~NPC_FLAG_FLIP_INSTANTLY;
149 npc->duration = 0;
150 script->AI_TEMP_STATE = AI_STATE_SENTINEL_GRAB_PLAYER;
151 } else {
152 script->AI_TEMP_STATE = AI_STATE_SENTINEL_LOSE_PLAYER_INIT;
153 }
154 }
155 }
156}
157
158void N(SentinelAI_LosePlayerInit)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
159 Enemy* enemy = script->owner1.enemy;
160 Npc* npc = get_npc_unsafe(enemy->npcID);
161
162 enemy->varTable[0] &= ~SENTINEL_AI_FLAG_CHASING;
163 set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_CLEAR, 0, 0, 0, 0, 0);
164 if (enemy->varTable[0] & SENTINEL_AI_FLAG_PLAYING_SOUND) {
166 enemy->varTable[0] &= ~SENTINEL_AI_FLAG_PLAYING_SOUND;
167 }
169 npc->duration = 20;
170 script->AI_TEMP_STATE = AI_STATE_SENTINEL_LOSE_PLAYER;
171}
172
173void N(SentinelAI_LosePlayer)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
174 Enemy* enemy = script->owner1.enemy;
175 Npc* npc = get_npc_unsafe(enemy->npcID);
176 f32 posX, posY, posZ, posW;
177 f32 idleHeight = (f32)enemy->varTable[3] / 100.0;
178 EffectInstance* emoteTemp;
179
180 npc->pos.y += 2.5;
181 posX = npc->pos.x;
182 posY = npc->pos.y;
183 posZ = npc->pos.z;
184 posW = 1000.0f;
185 npc_raycast_down_sides(npc->collisionChannel, &posX, &posY, &posZ, &posW);
186 if (!(npc->pos.y < posY + idleHeight)) {
187 npc->yaw = atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z);
188 npc->pos.y = posY + idleHeight;
189 fx_emote(EMOTE_QUESTION, npc, 0.0f, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 10, &emoteTemp);
190 npc->duration = 10;
191 script->AI_TEMP_STATE = AI_STATE_SENTINEL_POST_LOSE_PLAYER;
192 }
193}
194
195void N(SentinelAI_PostLosePlayer)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
196 Enemy* enemy = script->owner1.enemy;
197 Npc* npc = get_npc_unsafe(enemy->npcID);
198
199 npc->duration--;
200 if (npc->duration <= 0) {
201 script->AI_TEMP_STATE = AI_STATE_SENTINEL_RETURN_HOME_INIT;
202 }
203}
204
205void N(SentinelAI_GrabPlayer)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
206 Enemy* enemy = script->owner1.enemy;
207 Npc* npc = get_npc_unsafe(enemy->npcID);
208
209 npc->duration++;
210 if (npc->duration >= 3) {
212 npc->duration = 0;
213 script->AI_TEMP_STATE = AI_STATE_SENTINEL_CAUGHT_PLAYER;
214 } else {
217 script->AI_TEMP_STATE = AI_STATE_SENTINEL_LOSE_PLAYER_INIT;
218 }
219 }
220}
221
222void N(SentinelAI_ReturnHomeInit)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
223 Enemy* enemy = script->owner1.enemy;
224 Npc* npc = get_npc_unsafe(enemy->npcID);
225
226 enemy->varTable[0] &= ~SENTINEL_AI_FLAG_CHASING;
227 npc->flags &= ~NPC_FLAG_FLIP_INSTANTLY;
228 npc->moveSpeed = 2.0 * aiSettings->moveSpeed;
229 enemy->varTable[2] = 0;
230 enemy->varTable[4] = npc->pos.y * 100.0;
231 script->functionTemp[1] = 30;
232}
233
234void N(SentinelAI_ReturnHome)(Evt* script, MobileAISettings* aiSettings, EnemyDetectVolume* territory) {
235 Enemy* enemy = script->owner1.enemy;
236 Npc* npc = get_npc_unsafe(enemy->npcID);
237 f32 posX = npc->pos.x;
238 f32 posY = npc->pos.y;
239 f32 posZ = npc->pos.z;
240 f32 hitDepth = 1000.0f;
241 f32 idleHeight = (f32)enemy->varTable[3] / 100.0;
242 f32 temp_f24 = idleHeight + (f32)((f32)enemy->varTable[7] / 100.0);
243 f32 undulateAmplitude = (f32)enemy->varTable[1] / 100.0;
244 f32 undulateAmount = sin_deg(enemy->varTable[2]);
245 EffectInstance* emoteTemp;
246
247 if (npc_raycast_down_sides(npc->collisionChannel, &posX, &posY, &posZ, &hitDepth)) {
248 npc->pos.y = posY + idleHeight + (undulateAmount * undulateAmplitude);
249 } else {
250 npc->pos.y = temp_f24 + (undulateAmount * undulateAmplitude);
251 }
252
253 enemy->varTable[2] = clamp_angle(enemy->varTable[2] + 12);
254 if (script->functionTemp[1] <= 0) {
255 script->functionTemp[1] = aiSettings->playerSearchInterval;
256 if (basic_ai_check_player_dist(territory, enemy, aiSettings->alertRadius * 0.5, aiSettings->alertOffsetDist * 0.5, 0)) {
257 fx_emote(EMOTE_EXCLAMATION, npc, 0.0f, npc->collisionHeight, 1.0f, 2.0f, -20.0f, 12, &emoteTemp);
259 npc->moveToPos.y = npc->pos.y;
260 script->AI_TEMP_STATE = AI_STATE_SENTINEL_CHASE_INIT;
261 return;
262 }
263 }
264
265 script->functionTemp[1]--;
266 if (npc->turnAroundYawAdjustment == 0) {
267 npc->yaw = atan2(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z);
268 npc_move_heading(npc, npc->moveSpeed, npc->yaw);
269 hitDepth = dist2D(npc->pos.x, npc->pos.z, enemy->territory->wander.centerPos.x, enemy->territory->wander.centerPos.z);
270 if (hitDepth <= (2.0f * npc->moveSpeed)) {
271 script->functionTemp[1] = (rand_int(1000) % 3) + 2;
272 script->AI_TEMP_STATE = AI_STATE_SENTINEL_LOITER_INIT;
273 }
274 }
275}
276
277API_CALLABLE(N(SentinelAI_Main)) {
278 Enemy* enemy = script->owner1.enemy;
279 Npc* npc = get_npc_unsafe(enemy->npcID);
280 Bytecode* args = script->ptrReadPos;
281 EnemyDetectVolume territory;
282 EnemyDetectVolume* territoryPtr = &territory;
283 MobileAISettings* aiSettings = (MobileAISettings*) evt_get_variable(script, *args);
284
285 territory.skipPlayerDetectChance = 0;
286 territory.shape = enemy->territory->wander.detectShape;
287 territory.pointX = enemy->territory->wander.detectPos.x;
288 territory.pointZ = enemy->territory->wander.detectPos.z;
289 territory.sizeX = enemy->territory->wander.detectSize.x;
290 territory.sizeZ = enemy->territory->wander.detectSize.z;
291 territory.halfHeight = 125.0f;
292 territory.detectFlags = 0;
293
294 if (isInitialCall) {
295 script->AI_TEMP_STATE = AI_STATE_SENTINEL_WANDER_INIT;
296 N(FlyingAI_Init)(npc, enemy, script, aiSettings);
297 }
298
299 switch (script->AI_TEMP_STATE) {
301 N(FlyingAI_WanderInit)(script, aiSettings, territoryPtr);
302 set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_CLEAR, 0, 0, 0, 0, 0);
303 // fallthrough
305 N(FlyingAI_Wander)(script, aiSettings, territoryPtr);
306 if (script->AI_TEMP_STATE == AI_STATE_SENTINEL_CHASE_INIT) {
307 npc->duration = 6;
308 }
309 break;
311 N(FlyingAI_LoiterInit)(script, aiSettings, territoryPtr);
312 // fallthrough
314 N(FlyingAI_Loiter)(script, aiSettings, territoryPtr);
315 if (script->AI_TEMP_STATE == AI_STATE_SENTINEL_CHASE_INIT) {
316 npc->duration = 6;
317 }
318 break;
320 N(SentinelAI_ChaseInit)(script, aiSettings, territoryPtr);
321 if (script->AI_TEMP_STATE != AI_STATE_SENTINEL_CHASE) {
322 break;
323 }
324 // fallthrough
326 N(SentinelAI_Chase)(script, aiSettings, territoryPtr);
327 break;
329 N(SentinelAI_DescendInit)(script, aiSettings, territoryPtr);
330 if (script->AI_TEMP_STATE != AI_STATE_SENTINEL_DESCEND) {
331 break;
332 }
333 // fallthrough
335 N(SentinelAI_Descend)(script, aiSettings, territoryPtr);
336 break;
338 N(SentinelAI_LosePlayerInit)(script, aiSettings, territoryPtr);
339 // fallthrough
341 N(SentinelAI_LosePlayer)(script, aiSettings, territoryPtr);
342 break;
344 N(SentinelAI_PostLosePlayer)(script, aiSettings, territoryPtr);
345 break;
347 N(SentinelAI_GrabPlayer)(script, aiSettings, territoryPtr);
348 break;
350 N(SentinelAI_ReturnHomeInit)(script, aiSettings, territoryPtr);
351 // fallthrough
353 N(SentinelAI_ReturnHome)(script, aiSettings, territoryPtr);
354 break;
355 }
356
357 if (script->AI_TEMP_STATE == AI_STATE_SENTINEL_CAUGHT_PLAYER) {
358 return ApiStatus_DONE2; // when player is caught, relinquish control to the AI evt script
359 } else {
360 return ApiStatus_BLOCK;
361 }
362}
void N FlyingAI_LoiterInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N FlyingAI_Loiter(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N FlyingAI_Wander(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 SentinelAI_GrabPlayer(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N SentinelAI_Descend(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N SentinelAI_PostLosePlayer(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
#define SENTINEL_AI_FLAG_CHASING
#define SENTINEL_AI_DESCEND_RATE
#define AI_SENTINEL_FIRST_NPC
#define SENTINEL_AI_FLAG_PLAYING_SOUND
void N SentinelAI_ReturnHome(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N SentinelAI_LosePlayerInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N SentinelAI_ChaseInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N SentinelAI_Chase(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
void N SentinelAI_LosePlayer(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
#define AI_SENTINEL_LAST_NPC
void N SentinelAI_ReturnHomeInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
AiStateSentinel
@ AI_STATE_SENTINEL_LOITER
@ AI_STATE_SENTINEL_DESCEND
@ AI_STATE_SENTINEL_LOSE_PLAYER_INIT
@ AI_STATE_SENTINEL_CHASE_INIT
@ AI_STATE_SENTINEL_CHASE
@ AI_STATE_SENTINEL_WANDER
@ AI_STATE_SENTINEL_GRAB_PLAYER
@ AI_STATE_SENTINEL_LOITER_INIT
@ AI_STATE_SENTINEL_DESCEND_INIT
@ AI_STATE_SENTINEL_POST_LOSE_PLAYER
@ AI_STATE_SENTINEL_RETURN_HOME_INIT
@ AI_STATE_SENTINEL_RETURN_HOME
@ AI_STATE_SENTINEL_LOSE_PLAYER
@ AI_STATE_SENTINEL_CAUGHT_PLAYER
@ AI_STATE_SENTINEL_WANDER_INIT
void N SentinelAI_DescendInit(Evt *script, MobileAISettings *aiSettings, EnemyDetectVolume *territory)
#define sfx_play_sound_at_position
#define npc_raycast_down_sides
#define sin_deg
#define rand_int
#define clamp_angle
#define atan2
@ IMGFX_SET_COLOR
Definition enums.h:5123
@ IMGFX_CLEAR
Definition enums.h:5117
@ EMOTE_EXCLAMATION
Definition enums.h:495
@ EMOTE_QUESTION
Definition enums.h:497
@ ENEMY_ANIM_INDEX_MELEE_PRE
Definition enums.h:3434
@ ENEMY_ANIM_INDEX_MELEE_HIT
Definition enums.h:3435
@ SOUND_LOOP_SENTINEL_ALARM
Definition enums.h:1598
@ SOUND_AI_ALERT_A
Definition enums.h:1078
@ PARTNER_BOW
Definition enums.h:2894
@ SOUND_SPACE_FULL
Definition enums.h:1739
@ SOUND_PARAM_MORE_QUIET
Definition enums.h:1746
@ NPC_FLAG_FLIP_INSTANTLY
Definition enums.h:3019
#define ApiStatus_DONE2
Definition evt.h:118
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 fabsf(f32 f)
void partner_disable_input(void)
Definition partners.c:2489
void ai_enemy_play_sound(Npc *npc, s32 arg1, s32 arg2)
Definition 23680.c:543
s32 disable_player_input(void)
Definition 77480.c:990
f32 cosine(s16 arg0)
Definition 43F0.c:354
s32 enable_player_input(void)
Definition 77480.c:998
f32 dist2D(f32 ax, f32 ay, f32 bx, f32 by)
Definition 43F0.c:670
f32 get_clamped_angle_diff(f32, f32)
Definition 43F0.c:606
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
s32 * animList
Definition npc.h:341
Npc * get_npc_unsafe(s32 npcID)
Definition npc.c:995
s16 detectFlags
Definition npc.h:207
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
void partner_enable_input(void)
Definition partners.c:2480
void sfx_stop_sound(s32 soundID)
Definition sfx.c:507
s32 sfx_adjust_env_sound_pos(s32 soundID, s32 sourceFlags, f32 x, f32 y, f32 z)
Definition sfx.c:431
void set_npc_imgfx_all(s32 spriteIdx, ImgFXType imgfxType, s32 imgfxArg1, s32 imgfxArg2, s32 imgfxArg3, s32 imgfxArg4, s32 imgfxArg5)
Definition sprite.c:1254
s32 flags
s32 spriteInstanceID
AnimID curAnim
Vec3f moveToPos
s32 collisionChannel
s16 collisionHeight
s16 turnAroundYawAdjustment
Vec3f rot
Vec3f pos
f32 moveSpeed
s16 duration
PlayerStatus * gPlayerStatusPtr
PartnerStatus gPartnerStatus
Definition partners.c:42