Alchemy
Loading...
Searching...
No Matches

player_control.c

Go to the documentation of this file.
1/// @file
2/// Player control: the configured keys and moving objects by the pad.
3/// Part of field.c, the field object, compiled only through it.
4
5#include "../field_internal.h"
6
7// What differs by edition. On the world map (mode 3), L and R raise their
8// fixed events while held in the Japanese release, and once for each press
9// from the English one on. The world map's probes for a step reach 8 pixels in
10// Japan and 7 from the English release on.
11#if TBS_PATCH_1
12#define FIELD_SHORTCUT_KEYS gKeysPressed
13#define WORLD_MAP_PROBE_RADIUS 0x70000
14#else
15#define FIELD_SHORTCUT_KEYS gKeysHeld
16#define WORLD_MAP_PROBE_RADIUS 0x80000
17#endif
18
19/// Stores value in a halfword of the event work.
20/// XXX: why the stores go through a helper rather than plain assignment;
21/// whether the assignment form changes Field_RequestShortcut's bytes would
22/// settle it.
23static __inline__ void Field_StoreHalfword(s16 *address, s32 value) {
24 *address = value;
25}
26
27/// Stores value as a word at the object's unk6c.
28UNUSED void ObjectDispatch_SetUpdateValue(void *rawObject, s32 value) {
29 struct ObjectRuntime *object = rawObject;
30
31 if (object != NULL)
32 *(s32 *)object->unk6c = value;
33}
34
35/// Handles a shortcut key's assignment: its top two bits choose what it
36/// requests (0 Psynergy, 1 an item) and the low 14 bits which one. With flag
37/// 0x107 set it raises event 250 instead, as the menu key does then; on the
38/// world map (mode 3) R raises event -888 (0xfc88), the world map picture,
39/// and L -889 (0xfc87), the visibility transition. Returns the request.
40///
41/// Called by Field_CheckConfiguredKeys. Sets fieldWork->command,
42/// fieldWork->item or fieldWork->event, which the scene loop (run_scene.c)
43/// acts on.
44u32 Field_RequestShortcut(u32 encodedAssignment) {
45 u32 assignmentKind = encodedAssignment >> 14;
46 u32 requestId = 0x3FFF & encodedAssignment;
47 struct FieldWork *fieldWork = (struct FieldWork *)gEventWork;
48
49 if (GameFlag_TestFar(0x107) != 0) {
50 Field_StoreHalfword(&fieldWork->event, 0xFA);
51 } else if (fieldWork->mode == 3) {
53 Field_StoreHalfword(&fieldWork->event, 0xFC88);
54 } else if (FIELD_SHORTCUT_KEYS & KEY_L) {
55 Field_StoreHalfword(&fieldWork->event, 0xFC87);
56 }
57 } else {
58 switch (assignmentKind) {
59 case 0:
60 Field_StoreHalfword(&fieldWork->command, requestId);
61 break;
62 case 1:
63 Field_StoreHalfword(&fieldWork->item, requestId);
64 break;
65 }
66 }
67
68 return requestId;
69}
70
71/// Checks the keys the player has configured: Select, A and Start set their
72/// flag in the event work and return 1; the two shortcut keys pass on their
73/// assignment (Field_RequestShortcut). Returns 0 when none is pressed. Only
74/// the first configured key pressed this frame counts, in that order.
75///
76/// Called each frame by the leader's control commands: SC_PLAYER, SC_WORLD
77/// and SC_KEY_COUNT. Sets fieldWork->menu, examine or start.
79 struct FieldWork *fieldWork = (struct FieldWork *)gEventWork;
80 s32 result = 0;
81
82 if (fieldWork == NULL)
83 return 0;
85 fieldWork->menu = 1;
86 result = 1;
87 } else if (gKeysPressed & gGameState.aKeys) {
88 fieldWork->examine = 1;
89 result = 1;
90 } else if (gKeysPressed & gGameState.startKeys) {
91 fieldWork->start = 1;
92 result = 1;
97 }
98 return result;
99}
100
101/// Returns 0 when two spheres overlap: at positions a and b (16.16 pixels)
102/// with radii radiusA and radiusB (pixels); -1 otherwise. Called by
103/// SC_PLAYER's push test, ScriptObject_CheckOverlap and
104/// ScriptObject_FindOverlappingEntry.
105s32 Runtime_CheckRadiusOverlap(s32 *firstPosition, s32 radiusA,
106 s32 *secondPosition, s32 radiusB) {
107 const struct FieldPosition *firstPoint =
108 (const struct FieldPosition *)firstPosition;
109 const struct FieldPosition *secondPoint =
110 (const struct FieldPosition *)secondPosition;
111 const s32 *firstCoordinate = &firstPoint->x;
112 const s32 *secondCoordinate = &secondPoint->x;
113 s32 dx = (*firstCoordinate++ - *secondCoordinate++) >> 16;
114 s32 dy = (*firstCoordinate++ - *secondCoordinate++) >> 16;
115 s32 dz = (*firstCoordinate - *secondCoordinate) >> 16;
116 s32 radius = radiusA + radiusB;
117
118 // dx and dz are already whole pixels (at most 32768), so these tests
119 // always pass and only the distance decides.
120 // XXX: likely meant as a quick reject of points over 64 pixels apart,
121 // because 0x400000 is 64 pixels in 16.16, the units before the shift.
122 if (!(dx > 0x400000) && !(dz > 0x400000) &&
123 dx * dx + dy * dy + dz * dz < radius * radius)
124 return 0;
125 return -1;
126}
127
128/// SC_PLAYER: moves the player on the field by the direction pad: walking or
129/// running, sliding around walls, pushing movable objects, turning, playing
130/// the matching animation and leaving footprints. Also checks the configured
131/// keys. Returns 1.
132///
133/// Run each frame by gObjectDispatch_PlayerScript, which the scene loop
134/// (run_scene.c) starts on the leader through
135/// ObjectDispatch_StartPlayerControl when it hands control back on the field.
136/// Sets the leader's speed, target, velocity, angle and action, moves the
137/// objects it pushes, counts gEventWork->blockedSteps and creates the
138/// footprint objects.
140 struct ObjectRuntime *object = (struct ObjectRuntime *)interpreter;
141
142 struct FieldPosition position;
143 struct FieldPosition testPosition;
144 struct FieldPosition contact;
145 s16 angleOffsets[22];
146 s32 collision;
147 s32 pushed;
148 s16 heading;
149 s32 angle;
150 s32 movementAnimation;
151 u16 direction;
152 s32 footprintAngle;
153 s32 angleIndex;
154 s32 headingCount;
155 struct ObjectRuntime *pushedObject;
156
157 collision = 0;
158 pushed = 0;
159 // Debug (flag 350): beep when fewer than 136 of the 512 resource blocks
160 // are free.
161 if (gDebugMode != 0 && GameFlag_TestFar(350) != 0) {
162 s32 freeBlocks;
163 s32 blockIndex;
164
165 freeBlocks = 0;
166 for (blockIndex = 0; blockIndex < 512; blockIndex++) {
167 if (gResourceBlockOwners[blockIndex] == 0xff)
168 freeBlocks++;
169 }
170 if (freeBlocks - 136 < 0)
172 }
173 /* FAKEMATCH(volatile): the volatile read puts gDebugMode's address in r5 here
174 * and at the test before it, as native; as gDebugMode != 0, 9 of 808
175 * instructions differ in tbs-en. */
176 // Debug: holding L spins through four empty loops (511, 96, 64 and 64
177 // passes), a short stall each frame.
178 if (*(volatile u8 *)&gDebugMode != 0) {
179 s32 delayKeyMask;
180
181 delayKeyMask = KEY_L;
182 if (gKeysHeld & delayKeyMask) {
183 s32 delayIterations;
184
185 delayIterations = delayKeyMask;
186 wait_a:
187 --delayIterations;
188 if (delayIterations != 0)
189 goto wait_a;
190 delayIterations = 95;
191 wait_b:
192 delayIterations--;
193 if (delayIterations >= 0)
194 goto wait_b;
195 delayIterations = 63;
196 wait_c:
197 delayIterations--;
198 if (delayIterations >= 0)
199 goto wait_c;
200 delayIterations = 63;
201 wait_d:
202 delayIterations--;
203 if (delayIterations >= 0)
204 goto wait_d;
205 }
206 }
207 // Walk at 1 pixel a frame, or run at 1.5 with the dash keys (animation 5
208 // instead of 2); flag 0x17f and B move at 4.
210 object->speedLimit = 0x18000;
211 object->acceleration = 0x4000;
212 movementAnimation = 5;
213 } else {
214 object->speedLimit = 0x10000;
215 object->acceleration = 0x4000;
216 movementAnimation = 2;
217 }
218 if (GameFlag_TestFar(0x17f) != 0 && (gKeysHeld & KEY_B)) {
219 object->speedLimit = 0x40000;
220 object->acceleration = 0x10000;
221 movementAnimation = 5;
222 }
223
224 // Step 8 pixels the way the direction pad points, when that step and the
225 // ones 22.5 and 45 degrees either side are clear (debug L walks through
226 // walls); otherwise try headings up to 67.5 degrees off. Bit 2 of
227 // collision means no direction is held, bit 0 that every heading is
228 // blocked.
230 if ((u16)angle == 0xffff) {
231 collision |= 4;
232 } else {
233 collision = 0;
234 position.x = object->x;
235 position.y = object->y;
236 position.z = object->z;
237 Vector_AddPolarOffset(0x80000, (u16)angle, &position);
238 if (gDebugMode != 0 && (gKeysHeld & KEY_L)) {
239 heading = angle;
240 } else {
241 if (Map_CheckObjectStep(object, &position) == 0) {
242 testPosition.x = object->x;
243 testPosition.y = object->y;
244 testPosition.z = object->z;
245 Vector_AddPolarOffset(0x80000, (u16)angle + 0x1000, &testPosition);
246 if (Map_CheckObjectStep(object, &testPosition) == 0) {
247 testPosition.x = object->x;
248 testPosition.y = object->y;
249 testPosition.z = object->z;
250 Vector_AddPolarOffset(0x80000, (u16)angle - 0x1000, &testPosition);
251 if (Map_CheckObjectStep(object, &testPosition) == 0) {
252 testPosition.x = object->x;
253 testPosition.y = object->y;
254 testPosition.z = object->z;
255 Vector_AddPolarOffset(0x80000, (u16)angle + 0x2000, &testPosition);
256 if (Map_CheckObjectStep(object, &testPosition) == 0) {
257 testPosition.x = object->x;
258 testPosition.y = object->y;
259 testPosition.z = object->z;
260 Vector_AddPolarOffset(0x80000, (u16)angle - 0x2000,
261 &testPosition);
262 if (Map_CheckObjectStep(object, &testPosition) == 0) {
263 heading = angle;
264 goto push_objects;
265 }
266 }
267 }
268 }
269 }
270 // The pad's way is blocked: try 22.5, 45 and 67.5 degrees off, nearest
271 // first, each needing the same three clear probes. The first that
272 // passes slides the player along the wall.
273 direction = angle;
274 headingCount = 0;
275 angleOffsets[headingCount++] = direction + 0x1000;
276 angleOffsets[headingCount++] = direction - 0x1000;
277 angleOffsets[headingCount++] = direction + 0x2000;
278 angleOffsets[headingCount++] = direction - 0x2000;
279 angleOffsets[headingCount++] = direction + 0x3000;
280 angleOffsets[headingCount++] = direction - 0x3000;
281 for (angleIndex = 0; angleIndex < headingCount; angleIndex++) {
282 heading = angleOffsets[angleIndex];
283 position.x = object->x;
284 position.y = object->y;
285 position.z = object->z;
286 Vector_AddPolarOffset(0x80000, (u16)heading, &position);
287 if (Map_CheckObjectStep(object, &position) != 0)
288 continue;
289 testPosition.x = object->x;
290 testPosition.y = object->y;
291 testPosition.z = object->z;
292 Vector_AddPolarOffset(0x80000, (u16)heading + 0x1000, &testPosition);
293 if (Map_CheckObjectStep(object, &testPosition) != 0)
294 continue;
295 testPosition.x = object->x;
296 testPosition.y = object->y;
297 testPosition.z = object->z;
298 Vector_AddPolarOffset(0x80000, (u16)heading - 0x1000, &testPosition);
299 if (Map_CheckObjectStep(object, &testPosition) != 0)
300 continue;
301 testPosition.x = object->x;
302 testPosition.y = object->y;
303 testPosition.z = object->z;
304 Vector_AddPolarOffset(0x80000, (u16)heading + 0x2000, &testPosition);
305 if (Map_CheckObjectStep(object, &testPosition) != 0)
306 continue;
307 testPosition.x = object->x;
308 testPosition.y = object->y;
309 testPosition.z = object->z;
310 Vector_AddPolarOffset(0x80000, (u16)heading - 0x2000, &testPosition);
311 if (Map_CheckObjectStep(object, &testPosition) == 0)
312 goto push_objects;
313 }
314 // Nothing passed: the step is the object's own position.
315 position.x = object->x;
316 position.y = object->y;
317 position.z = object->z;
318 collision |= 1;
319
320 push_objects:
321 // Push the movable objects (collisionFlags bit 1) just ahead 4 pixels
322 // further, when they have room; any other solid object there blocks the
323 // step (bit 1 of collision). Pushing slows the player down.
324 contact.x = object->x;
325 contact.y = object->y;
326 contact.z = object->z;
327 Vector_AddPolarOffset(0x40000, (u16)heading, &contact);
328 pushedObject = gObjectSlots;
329 for (angleIndex = 63; angleIndex >= 0; angleIndex -= 1, pushedObject++) {
330 s32 radius;
331
332 radius = object->radius - 2;
333 if (pushedObject->script == 0)
334 continue;
335 if ((pushedObject->collisionFlags & 1) == 0)
336 continue;
337 if (pushedObject == object)
338 continue;
339 if (Runtime_CheckRadiusOverlap(&pushedObject->x, radius, &contact.x,
340 pushedObject->radius - 2) < 0)
341 continue;
342 // A movable object that is not paused slides a quarter pixel away from
343 // the player, if no solid object is there and the ground 10 pixels on,
344 // straight and 22.5 degrees either side, is clear.
345 if ((pushedObject->collisionFlags & 2) != 0 &&
346 pushedObject->movementState == 0) {
347 direction =
348 ArcTan2(pushedObject->z - object->z, pushedObject->x - object->x);
349 testPosition.x = pushedObject->x;
350 testPosition.y = pushedObject->y;
351 testPosition.z = pushedObject->z;
352 heading = direction;
353 Vector_AddPolarOffset(0x4000, direction, &testPosition);
355 (struct ScriptObjectEntry *)pushedObject,
356 (s32 *)&testPosition)) {
357 testPosition.x = pushedObject->x;
358 testPosition.y = pushedObject->y;
359 testPosition.z = pushedObject->z;
360 Vector_AddPolarOffset(0xa0000, direction, &testPosition);
361 if (Map_CheckObjectStep(pushedObject, &testPosition) == 0) {
362 testPosition.x = pushedObject->x;
363 testPosition.y = pushedObject->y;
364 testPosition.z = pushedObject->z;
365 Vector_AddPolarOffset(0xa0000, direction + 0x1000, &testPosition);
366 // The same probe is tested twice.
367 if (Map_CheckObjectStep(pushedObject, &testPosition) == 0 &&
368 Map_CheckObjectStep(pushedObject, &testPosition) == 0) {
369 testPosition.x = pushedObject->x;
370 testPosition.y = pushedObject->y;
371 testPosition.z = pushedObject->z;
372 Vector_AddPolarOffset(0xa0000, direction - 0x1000,
373 &testPosition);
374 if (Map_CheckObjectStep(pushedObject, &testPosition) == 0) {
376 0x4000, direction,
377 (struct FieldPosition *)&pushedObject->x);
378 pushedObject->targetX = TARGET_UNSET;
379 pushedObject->targetY = TARGET_UNSET;
380 pushedObject->targetZ = TARGET_UNSET;
381 pushed |= 1;
382 continue;
383 }
384 }
385 }
386 }
387 }
388 collision |= 2;
389 }
390 // Pushing: a quarter pixel a frame.
391 if (collision == 0 && pushed != 0) {
392 object->speedLimit = 0x4000;
393 object->acceleration = 0x2000;
394 }
395 }
396 }
397 // Count the blocked steps, and pick the animation: push (8), bump (9, or
398 // 22 when the leader is down) or walk / run.
399 if (gEventWork != NULL) {
400 if (collision & 3)
402 else
404 }
405 if (pushed != 0) {
407 } else if (collision != 0) {
408 s32 action;
409
410 action = 9;
412 action = 22;
414 } else {
415 ObjectDispatch_SelectAnimation(object, movementAnimation);
416 }
417
418 // Blocked: stop and turn towards the pad, at most 22.5 degrees a frame.
419 // Otherwise move to the step, keeping the speed but along the heading.
420 // footprintSide = 2 marks a stop; the next footprint then takes animation 2.
421 if (collision != 0) {
422 object->targetX = TARGET_UNSET;
423 object->targetY = TARGET_UNSET;
424 object->targetZ = TARGET_UNSET;
425 object->velocityX = 0;
426 object->velocityZ = 0;
427 if (collision & 3) {
428 s32 difference;
429
430 difference = (u16)angle - object->angle;
431 difference = (s16)difference;
432 if (difference > 0x1000)
433 difference = 0x1000;
434 if (difference < -0x1000)
435 difference = -0x1000;
436 object->angle += difference;
437 }
438 object->action = 0;
439 object->footprintSide = 2;
440 } else {
441 s32 speed;
442
443 Object_SetMoveTarget(object, position.x, position.y, position.z);
444 speed = FixedSqrt(Iwram_MulQ16(object->velocityX, object->velocityX) +
445 Iwram_MulQ16(object->velocityZ, object->velocityZ));
446 object->velocityX = 0;
447 object->velocityZ = 0;
448 Vector_AddPolarOffset(speed, (u16)heading,
449 (struct FieldPosition *)&object->velocityX);
450 if (object->action != 0)
451 object->action--;
452 }
453
454 // Leave a footprint (object kind 25) every 18 walking or 12 running
455 // frames on maps that show them, alternating left and right; action
456 // counts down the frames to the next one.
457 footprintAngle = (u16)angle;
458 if (((struct MapScrollWork *)gMapWork[0])->footprints != 0 &&
459 object->action == 0 && collision == 0) {
460 struct ObjectRuntime *effect;
461
462 effect = FieldObject_Create(25, object->x, object->y, object->z);
463 if (effect != 0) {
464 struct AnimationObject *animation;
465
466 effect->terrainHeight = object->terrainHeight;
467 animation = effect->animation;
470 effect->priorityFlags = 2;
471 effect->flags = collision;
472 if (animation != 0) {
474 animation->flags = collision;
475 animation->rotation = footprintAngle + 0x4000;
476 animation->image.priority = 3;
477 }
478 if (object->footprintSide == 2) {
480 object->footprintSide = collision;
481 }
482 if (object->footprintSide != 0)
483 effect->angle = 0x8000;
484 if (movementAnimation == 5)
485 object->action = 12;
486 else
487 object->action = 18;
488 object->footprintSide ^= 1;
489 }
490 }
492 object->step++;
493 return 1;
494}
495
496/// SC_WORLD: moves the player on the world map by the direction pad, as
497/// SC_PLAYER does on the field, and turns the world map camera with the pad.
498/// Shallow water (terrain 6) leaves a ripple and terrain 9 hides the shadow.
499///
500/// Run each frame by gObjectDispatch_WorldMapScript, which the scene loop
501/// (run_scene.c) starts on the leader through
502/// ObjectDispatch_StartWorldMapControl on the world map. Sets the leader's
503/// speed, target, velocity, angle and action, turns the perspective work's
504/// yaw, counts gEventWork->blockedSteps and creates the ripple objects.
506 struct ObjectRuntime *object = (struct ObjectRuntime *)interpreter;
507
508 struct FieldPosition position;
509#if TBS_PATCH_1
510 struct FieldPosition testPosition;
511 s16 angleOffsets[28];
512#else
513 struct FieldPosition leftPosition;
514 struct FieldPosition rightPosition;
515 s16 angleOffsets[22];
516#endif
517 s32 collision;
518 s32 angle;
519 u16 direction;
520 s32 angleIndex;
521 s32 movementAnimation;
522 s32 count;
523 s32 difference;
524 s32 terrainCollision;
525 s32 speed;
526 s32 targetCameraYaw;
527 struct PerspectiveWork *perspective;
528 s32 yawStep;
529 struct AnimationObject *playerSprite;
530 struct ObjectRuntime *ripple;
531 struct AnimationObject *rippleSprite;
532
533 movementAnimation = 2;
534 collision = 0;
535#if !TBS_PATCH_1
536 count = 0;
537#endif
538 // Walk at half a pixel a frame, or 1 with the dash keys. L, as it
539 // repeats, raises the limit to 4 pixels; unlike the field's fast walk, no
540 // debug test or flag guards it.
542 object->speedLimit = 0x10000;
543 object->acceleration = 0x14000;
544 movementAnimation = 5;
545 } else {
546 object->speedLimit = 0x8000;
547 object->acceleration = 0x4000;
548 }
549 if ((gKeysRepeat & KEY_L) != 0)
550 object->speedLimit = 0x40000;
551
552 angle = gWorldMap_KeyHeadings[(gKeysHeld >> 4) & 15];
553 direction = angle;
554 if (direction == 0xffff) {
555 collision |= 4;
556 } else {
557 collision = 0;
558 position.x = object->x;
559 position.y = object->y;
560 position.z = object->z;
561 Vector_AddPolarOffset(WORLD_MAP_PROBE_RADIUS, direction, &position);
562 // Debug L walks anywhere. Otherwise the English release on tests the
563 // step and 22.5 and 45 degrees either side, then headings up to 90
564 // degrees off; the Japanese one tests the step and 30 degrees (0x1555)
565 // either side, then headings up to 67.5 degrees off.
566 if (gDebugMode == 0 || (gKeysHeld & KEY_L) == 0) {
567#if TBS_PATCH_1
568 if (CheckWorldMapWalkable((s32)object,
569 (struct WorldPosition *)&position) == 0) {
570 testPosition.x = object->x;
571 testPosition.y = object->y;
572 testPosition.z = object->z;
574 &testPosition);
575 if (CheckWorldMapWalkable((s32)object,
576 (struct WorldPosition *)&testPosition) == 0) {
577 testPosition.x = object->x;
578 testPosition.y = object->y;
579 testPosition.z = object->z;
581 &testPosition);
583 (s32)object, (struct WorldPosition *)&testPosition) == 0) {
584 testPosition.x = object->x;
585 testPosition.y = object->y;
586 testPosition.z = object->z;
588 &testPosition);
590 (s32)object, (struct WorldPosition *)&testPosition) == 0) {
591 testPosition.x = object->x;
592 testPosition.y = object->y;
593 testPosition.z = object->z;
595 &testPosition);
597 (s32)object, (struct WorldPosition *)&testPosition) == 0)
598 goto update_object;
599 }
600 }
601 }
602 }
603 count = 0;
604 angleOffsets[count++] = direction + 0x1000;
605 angleOffsets[count++] = direction - 0x1000;
606 angleOffsets[count++] = direction + 0x2000;
607 angleOffsets[count++] = direction - 0x2000;
608 angleOffsets[count++] = direction + 0x3000;
609 angleOffsets[count++] = direction - 0x3000;
610 angleOffsets[count++] = direction + 0x4000;
611 angleOffsets[count++] = direction - 0x4000;
612 for (angleIndex = 0; angleIndex < count; angleIndex++) {
613 direction = angleOffsets[angleIndex];
614 position.x = object->x;
615 position.y = object->y;
616 position.z = object->z;
617 Vector_AddPolarOffset(WORLD_MAP_PROBE_RADIUS, direction, &position);
618 if (CheckWorldMapWalkable((s32)object,
619 (struct WorldPosition *)&position) != 0)
620 continue;
621 testPosition.x = object->x;
622 testPosition.y = object->y;
623 testPosition.z = object->z;
625 &testPosition);
626 if (CheckWorldMapWalkable((s32)object,
627 (struct WorldPosition *)&testPosition) != 0)
628 continue;
629 testPosition.x = object->x;
630 testPosition.y = object->y;
631 testPosition.z = object->z;
633 &testPosition);
634 if (CheckWorldMapWalkable((s32)object,
635 (struct WorldPosition *)&testPosition) != 0)
636 continue;
637 testPosition.x = object->x;
638 testPosition.y = object->y;
639 testPosition.z = object->z;
641 &testPosition);
642 if (CheckWorldMapWalkable((s32)object,
643 (struct WorldPosition *)&testPosition) != 0)
644 continue;
645 testPosition.x = object->x;
646 testPosition.y = object->y;
647 testPosition.z = object->z;
649 &testPosition);
650 if (CheckWorldMapWalkable((s32)object,
651 (struct WorldPosition *)&testPosition) != 0)
652 continue;
653 angle = (s16)direction;
654 goto update_object;
655 }
656 collision |= 1;
657
658#else
659 leftPosition.x = object->x;
660 leftPosition.y = object->y;
661 leftPosition.z = object->z;
663 &leftPosition);
664 rightPosition.x = object->x;
665 rightPosition.y = object->y;
666 rightPosition.z = object->z;
668 &rightPosition);
669 if ((CheckWorldMapWalkable((s32)object,
670 (struct WorldPosition *)&position) |
672 (struct WorldPosition *)&leftPosition) |
674 (s32)object, (struct WorldPosition *)&rightPosition)) != 0) {
675 angleOffsets[count++] = direction + 0x1000;
676 angleOffsets[count++] = direction - 0x1000;
677 angleOffsets[count++] = direction + 0x2000;
678 angleOffsets[count++] = direction - 0x2000;
679 angleOffsets[count++] = direction + 0x3000;
680 angleOffsets[count++] = direction - 0x3000;
681 for (angleIndex = 0; angleIndex < count; angleIndex++) {
682 direction = angleOffsets[angleIndex];
683 position.x = object->x;
684 position.y = object->y;
685 position.z = object->z;
686 Vector_AddPolarOffset(WORLD_MAP_PROBE_RADIUS, direction, &position);
687 leftPosition.x = object->x;
688 leftPosition.y = object->y;
689 leftPosition.z = object->z;
691 &leftPosition);
692 rightPosition.x = object->x;
693 rightPosition.y = object->y;
694 rightPosition.z = object->z;
696 &rightPosition);
697 if ((CheckWorldMapWalkable((s32)object,
698 (struct WorldPosition *)&position) |
700 (struct WorldPosition *)&leftPosition) |
702 (s32)object, (struct WorldPosition *)&rightPosition)) != 0)
703 continue;
704 angle = (s16)direction;
705 goto update_object;
706 }
707 collision |= 1;
708 }
709#endif
710 }
711 }
712
713update_object:
714 if (gEventWork != NULL) {
715 if ((collision & 3) != 0)
717 else
719 }
720 if (collision != 0)
722 else
723 ObjectDispatch_SelectAnimation(object, movementAnimation);
724
725 if (collision != 0) {
726 object->targetX = TARGET_UNSET;
727 object->targetY = TARGET_UNSET;
728 object->targetZ = TARGET_UNSET;
729 object->velocityX = 0;
730 object->velocityZ = 0;
731 if ((collision & 3) != 0) {
732 direction = angle;
733 difference = (s16)(direction - object->angle);
734 if (difference > 0x1000)
735 difference = 0x1000;
736 if (difference < -0x1000)
737 difference = -0x1000;
738 object->angle += difference;
739 }
740 object->action = 0;
741 object->footprintSide = 2;
742 } else {
743 Object_SetMoveTarget(object, position.x, position.y, position.z);
744 speed = FixedSqrt(Iwram_MulQ16(object->velocityX, object->velocityX) +
745 Iwram_MulQ16(object->velocityZ, object->velocityZ));
746 object->velocityX = collision;
747 object->velocityZ = collision;
748 Vector_AddPolarOffset(speed, (u16)angle,
749 (struct FieldPosition *)&object->velocityX);
750 if (object->action != 0)
751 object->action--;
752 }
753
754 // Turn the camera towards the pad's yaw by an eighth of the difference a
755 // frame, at most 0x200 (about 2.8 degrees), finishing the turn once the
756 // step falls under 16.
757 perspective = gMapWork[0];
758 targetCameraYaw = gWorldMap_KeyYaws[(gKeysHeld >> 4) & 15];
759 yawStep = (s16)(targetCameraYaw - perspective->yaw) / 8;
760 if (yawStep > 0x200)
761 yawStep = 0x200;
762 if (yawStep < -0x200)
763 yawStep = -0x200;
764 if (yawStep > -16 && yawStep < 16)
765 yawStep = targetCameraYaw - perspective->yaw;
766 perspective->yaw += yawStep;
767
768 if (object->animationKind == 1) {
769 playerSprite = object->animation;
770 terrainCollision = GetWorldMapCollision((struct WorldPosition *)&object->x);
771 if (terrainCollision == 9) {
772 playerSprite->entries[1]->priority = 1;
773 playerSprite->flags = 0;
774 } else {
775 playerSprite->entries[1]->priority = 9;
776 playerSprite->flags = 1;
777 }
778 // In shallow water a ripple (object kind 24) follows every 10 frames of
779 // walking.
780 if (terrainCollision == 6 && object->action == 0 && collision == 0) {
781 ripple = FieldObject_Create(24, object->x, object->y, object->z);
782 if (ripple != 0) {
783 rippleSprite = ripple->animation;
786 ripple->flags = collision;
787 ripple->terrainId = 1;
788 if (rippleSprite != 0) {
789 AnimationObjects_SelectAnimation(rippleSprite, 1);
790 rippleSprite->flags = collision;
791 rippleSprite->image.objectMode = 1;
792 rippleSprite->image.priority = 2;
793 }
794 object->action = 10;
795 }
796 }
797 }
799 object->step++;
800 return 1;
801}
802
803/// SC_KEY_COUNT: checks the configured keys (Field_CheckConfiguredKeys).
804/// Returns 1. Run each frame by gObjectDispatch_KeyCheckScript, which the
805/// scene loop starts on the leader in movement mode 2, where the pad does
806/// not move it.
808 struct ObjectRuntime *object = (struct ObjectRuntime *)interpreter;
809
811 object->step++;
812 return 1;
813}
814
815/// SC_KEYS: moves an object 8 pixels at a time by the direction pad, with up
816/// and down changing its height (y) rather than its depth. Refuses steps
817/// into an occupied cell, across a change of collision code, or onto ground
818/// too high (up) or too low (down). Returns 1.
819///
820/// Run each frame by gObjectDispatch_KeysScript, which the scene loop starts
821/// on the leader in movement mode 1 (climbing). Sets the leader's speed,
822/// angle, target or velocity, and counts gEventWork->blockedSteps.
824 struct ObjectRuntime *object = (struct ObjectRuntime *)interpreter;
825
826 struct FieldPosition position;
827 /* FAKEMATCH(dead): an unused local for the 68 bytes of native frame no
828 * instruction touches, the size of Object_MoveOnWorldMap's probe locals;
829 * without it, 4 of 213 instructions differ in tbs-en. */
830 UNUSED u8 unusedProbeFrame[68];
831 struct MapCell *sourceCell;
832 struct MapCell *destinationCell;
833 u16 angle;
834 s32 animation;
835 s32 collisionFlags;
836 s32 deltaZ;
837 s32 terrainHeight;
838
839 object->speedLimit = 0x8000;
840 object->acceleration = 0x4000;
841 animation = 12;
842 angle = gWorldMap_KeyHeadings[(gKeysHeld >> 4) & 15];
843 if (angle == 0xffff) {
844 collisionFlags = 4;
845 } else {
846 // The animation by the pad's heading: 14 for headings below 0x1000, 15
847 // for 0x8000 to 0x8fff, 10 for the rest.
848 animation = 14;
849 if ((angle & 0xf000) != 0) {
850 animation = 15;
851 if ((angle & 0xf000) != 0x8000)
852 animation = 10;
853 }
854 collisionFlags = 0;
855 position.x = 0;
856 position.y = 0;
857 position.z = 0;
858 Vector_AddPolarOffset(0x80000, angle, &position);
859 // The step's depth becomes height: pad up climbs, pad down descends, and
860 // the object's angle becomes 0xc000 going up the map and 0x4000 going down.
861 position.x += object->x;
862 deltaZ = position.z;
863 if (deltaZ < 0)
864 object->angle = 0xc000;
865 if (deltaZ > 0)
866 object->angle = 0x4000;
867 position.y = object->y - position.z;
868 position.z = object->z;
869 // The cells (16 pixels, 0x100000) under the object and the step; a step
870 // into a cell with another collision code is refused.
871 sourceCell =
872 &((struct MapCell *)Ram_MapCellBuffer)[object->x / 0x100000 +
873 (position.z / 0x100000) * 128];
874 destinationCell =
875 &((struct MapCell *)Ram_MapCellBuffer)[position.x / 0x100000 +
876 (position.z / 0x100000) * 128];
877 if (CheckMapPositionCellOccupied((struct WorldPosition *)&position) != 0 ||
878 sourceCell->collisionCode != destinationCell->collisionCode) {
879 collisionFlags = 4;
880 animation = 12;
881 } else if (gKeysHeld & KEY_UP) {
882 // Climbing needs the ground a cell further up to stand at least 16
883 // pixels above the object; descending needs the ground at the step
884 // at least 8 pixels below it.
885 terrainHeight = Map_GetTerrainHeight(object->terrainId, position.x,
886 position.z - 0x100000);
887 if (terrainHeight - object->y < 0x100000)
888 goto refused;
889 } else if (gKeysHeld & KEY_DOWN) {
890 terrainHeight =
891 Map_GetTerrainHeight(object->terrainId, position.x, position.z);
892 if (terrainHeight - object->y > -0x80000) {
893 refused:
894 collisionFlags = 1;
895 animation = 12;
896 }
897 }
898 }
899 if (gEventWork != NULL) {
900 if (collisionFlags & 3)
902 else
904 }
905 ObjectDispatch_SelectAnimation(object, animation);
906 if (collisionFlags != 0) {
907 object->targetX = TARGET_UNSET;
908 object->targetY = TARGET_UNSET;
909 object->targetZ = TARGET_UNSET;
910 SET_VELOCITY(object, 0, 0, 0);
911 } else {
912 Object_SetMoveTarget(object, position.x, position.y, position.z);
913 }
914 object->step++;
915 return 1;
916}
917
918#undef FIELD_SHORTCUT_KEYS
919#undef WORLD_MAP_PROBE_RADIUS
s32 AnimationObjects_SelectAnimation(struct AnimationObject *sprite, s32 animation)
Starts animation flags & 0x7f on every layer of a sprite (bit 7 keeps each layer's place and timer),...
Definition animation.c:317
const s32 gWorldMap_KeyYaws[16]
The world map camera's turn rate for each state of the direction pad, read by Object_MoveOnWorldMap.
Definition facing.c:128
const s32 gWorldMap_FootprintActions[6]
Two scripts for the world map's footprint and dust effects: wait 120 or 30 frames,...
Definition facing.c:121
const u16 gWorldMap_KeyHeadings[16]
The heading for each state of the direction pad (bits 4-7 of the keys); 0xffff means no direction is ...
Definition facing.c:113
struct BattleUnit * UnitGetFar(u32 owner)
s32 GameFlag_TestFar(s32 flag)
void AudioCommand_PlayFar(s32 id)
struct ObjectRuntime * FieldObject_Create(s32 encodedSprite, s32 x, s32 y, s32 z)
Creates a field object at (x, y, z), in 16.16 pixels, or returns 0 when none is free.
Definition objects.c:245
void ObjectDispatch_Initialize(struct DispatchObject *object, const s32 *script)
Gives an object a script and starts it from the top.
Definition objects.c:323
void ObjectDispatch_SelectAnimation(void *rawObject, s32 value)
Switches all of an object's sprites to animation value.
Definition objects.c:365
What every part of the field object shares: its includes and the few names more than one part uses.
#define TARGET_UNSET
A movement target coordinate that is not set.
u8 gDebugMode
Debug builds: Start+Select pauses the frame loop.
struct GameState gGameState
void * gMapWork[]
@ KEY_UP
Definition io_reg.h:249
@ KEY_B
Definition io_reg.h:244
@ KEY_R
Definition io_reg.h:251
@ KEY_DOWN
Definition io_reg.h:250
@ KEY_L
Definition io_reg.h:252
#define Iwram_MulQ16(left, right)
Definition iwram_call.h:127
volatile u32 gKeysHeld
Keys down now.
volatile u32 gKeysPressed
Keys that went down this frame.
volatile u32 gKeysRepeat
Pressed keys, repeating while held: after 19 frames, then every 6.
#define SET_VELOCITY(p, vx, vy, vz)
Definition macros.h:18
void Vector_AddPolarOffset(s32 radius, s32 angle, void *position)
Moves the point position by radius in direction angle on the ground plane: x by the cosine,...
Definition math.c:23
s32 ArcTan2(s32 x, s32 y)
Returns the angle of the vector (x, y), in 1/65536 turns, measured from the y axis: 0 along +y,...
Definition math.c:36
s32 FixedSqrt(s32 value)
Square root of a 16.16 fixed-point value, in 16.16.
Definition math.c:149
void Object_SetMoveTarget(struct ObjectRuntime *object, s32 x, s32 y, s32 z)
Sends an object towards (x, y, z), in 16.16 pixels.
s32 Runtime_CheckRadiusOverlap(s32 *firstPosition, s32 radiusA, s32 *secondPosition, s32 radiusB)
Returns 0 when two spheres overlap: at positions a and b (16.16 pixels) with radii radiusA and radius...
s32 Object_MoveByKeys(struct ScriptInterpreter *interpreter)
SC_KEYS: moves an object 8 pixels at a time by the direction pad, with up and down changing its heigh...
s32 Field_CheckConfiguredKeysAndCount(struct ScriptInterpreter *interpreter)
SC_KEY_COUNT: checks the configured keys (Field_CheckConfiguredKeys).
#define FIELD_SHORTCUT_KEYS
s32 Object_MoveOnWorldMap(struct ScriptInterpreter *interpreter)
SC_WORLD: moves the player on the world map by the direction pad, as SC_PLAYER does on the field,...
s32 FieldObject_UpdatePlayerControl(struct ScriptInterpreter *interpreter)
SC_PLAYER: moves the player on the field by the direction pad: walking or running,...
#define WORLD_MAP_PROBE_RADIUS
UNUSED void ObjectDispatch_SetUpdateValue(void *rawObject, s32 value)
Stores value as a word at the object's unk6c.
static __inline__ void Field_StoreHalfword(s16 *address, s32 value)
Stores value in a halfword of the event work.
u32 Field_RequestShortcut(u32 encodedAssignment)
Handles a shortcut key's assignment: its top two bits choose what it requests (0 Psynergy,...
s32 Field_CheckConfiguredKeys(void)
Checks the keys the player has configured: Select, A and Start set their flag in the event work and r...
#define Ram_MapCellBuffer
Definition ram_buffer.h:10
s32 ScriptObject_CheckOverlap(struct ScriptObjectEntry *object, s32 *values)
Returns -1 when an object at position values would overlap a solid object (bit 0 of collisionFlags),...
#define SE_135
Definition songs.h:128
struct EventWork * gEventWork
#define NULL
Definition bilibin.h:6
u8 priority
drawing order 0 to 3 among the layers; above 3 not drawn
Definition animspr.h:46
An animated sprite: its image and shadow, scale, rotation, size and up to four animation layers.
Definition animspr.h:108
struct AnimationSpritePart image
Definition animspr.h:109
u16 rotation
1/65536 turns
Definition animspr.h:118
u8 flags
bits: 1 outlined in ObjectSystemWork's edge and fill colours | 0 shadow drawn (set when created)
Definition animspr.h:128
struct AnimationEntry * entries[4]
its layers; empty ones null
Definition animspr.h:130
u8 count
layers in use, up to the last one present
Definition animspr.h:129
u16 priority
2 when created
Definition animspr.h:100
s16 hp
current HP, 0 when downed
A field object as the object dispatcher's far calls take it.
s16 blockedSteps
frames the leader has pushed against something
Definition event_work.h:37
A position on the field.
s32 z
16.16 fixed-point pixels
Full event work allocated by Field_RunScene (heap slot 27).
s16 event
A raised event for Scene_RunRaisedEvent, or -1 when characters fell to poison or hazard damage,...
s16 command
a field Psynergy to use, as owner << 10 | Psynergy
s16 start
the Start button: open the pause menu
s16 menu
open the main menu
s16 mode
MODE_WORLD_MAP (3) on the world map.
s16 examine
the A button: examine what is ahead
s16 item
an item to use, packed as Field_UseItem reads it
u16 startKeys
KEY_START.
Definition game_state.h:77
u16 firstShortcut
The shortcuts' assignments.
Definition game_state.h:86
u16 selectKeys
the menu; KEY_SELECT
Definition game_state.h:76
u16 dashKeys
KEY_B.
Definition game_state.h:80
u16 secondShortcut
Definition game_state.h:87
u16 secondShortcutKeys
secondShortcut; KEY_R
Definition game_state.h:79
s32 selectedActor
the leader: its unit and its actor
Definition game_state.h:59
u16 aKeys
The buttons for each field command, as KEY_ masks; read with gKeysPressed except dashKeys,...
Definition game_state.h:74
u16 firstShortcutKeys
firstShortcut; KEY_L
Definition game_state.h:78
One cell of a map layer: its metatile and flags, its collision code and an attribute indexing the col...
Definition map.h:13
u8 collisionCode
The step code: 1 to 239 scene events, 240 and 241 encounter ground, 250 hazard, 252 to 254 for Field_...
Definition map.h:18
The field map's work: the followed position, screen shake, tile animations, blend script,...
Definition map_scroll.h:27
One of the 64 field objects in the object pool (heap slot 5): its script, position,...
u16 angle
1/65536 turns
s32 velocityZ
16.16 fixed-point pixels per frame
s32 terrainHeight
16.16 fixed-point pixels
s32 velocityX
16.16 fixed-point pixels per frame
u16 radius
collision radius in pixels
u8 movementState
nonzero skips its script and motion (paused)
s16 footprintSide
the side of the player's next footprint (2 after a stop)
s32 z
16.16 fixed-point pixels
u8 collisionFlags
FieldActor.collisionFlags: bit 0 solid, bit 1 the player pushes it, bits 2 to 4 for talk and Psynergy...
s32 * script
the object script it runs; null when the object is free
void * animation
its sprite, or its list of four, by animationKind
s32 x
16.16 fixed-point pixels
s32 y
16.16 fixed-point pixels
u8 animationKind
Low four bits: 0 no sprite, 1 one sprite, 2 a group of up to four.
s16 action
The parameter its action script reads (a follow distance or an angle), or the player's frames to its ...
u8 terrainId
the map layer its ground height is read from
u8 unk6c[0x04]
The update hook (FieldActor.update), which the unused ObjectDispatch_SetUpdateValue sets as a word.
The world map's perspective view: the followed position, screen shake, view position,...
Definition perspective.h:12
u16 yaw
1/65536 turns
Definition perspective.h:45
The prefix that command handlers read from a field object's 0x70-byte slot.
Definition script.h:16
A field object as the script commands' overlap checks see it.
A position on the world map, as its walkability check reads it.
Definition map.h:74
s32 x
16.16 fixed-point pixels
Definition map.h:75
s32 y
16.16 fixed-point pixels
Definition map.h:77
s32 Map_CheckObjectStep(struct ObjectRuntime *object, struct FieldPosition *position)
Returns whether an object can step to position: 2 when the cell there is a wall (collision code 0xff)...
Definition terrain.c:454
s32 CheckWorldMapWalkable(s32 unused, struct WorldPosition *position)
Returns 0 when the world map at position can be walked on (collision 5 to 12), else -1.
Definition terrain.c:586
s32 GetWorldMapCollision(struct WorldPosition *position)
Returns the world map's collision value (1-15) at position: from the upper collision layer where it h...
Definition terrain.c:535
s32 CheckMapPositionCellOccupied(struct WorldPosition *position)
Returns 0 when the cell under position (its x, and y less its height) in layer 2 is a wall (collision...
Definition terrain.c:506
s32 Map_GetTerrainHeight(s32 layer, s32 x, s32 y)
Returns the ground height of a layer at (x, y) in 16.16 pixels: the height function of the cell's ter...
Definition terrain.c:343
signed int s32
Definition types.h:14
unsigned int u32
Definition types.h:15
signed short s16
Definition types.h:12
unsigned short u16
Definition types.h:13
#define UNUSED
Definition types.h:22
unsigned char u8
Definition types.h:11
struct ScriptMotionObject * gObjectSlots
u8 gResourceBlockOwners[VRAM_BLOCK_COUNT]