mirror of
https://github.com/SAT-R/sa2.git
synced 2026-10-28 09:43:38 +00:00
336 lines
17 KiB
C
336 lines
17 KiB
C
#ifndef GUARD_SAKIT_GLOBALS_H
|
|
#define GUARD_SAKIT_GLOBALS_H
|
|
|
|
#include "multi_sio.h"
|
|
#include "core.h"
|
|
|
|
#define GAME_MODE_SINGLE_PLAYER 0
|
|
#define GAME_MODE_TIME_ATTACK 1
|
|
|
|
#define GAME_MODE_MULTI_PLAYER 3
|
|
#if (GAME == GAME_SA1)
|
|
#define GAME_MODE_RACE 2
|
|
#define GAME_MODE_CHAO_HUNT 4
|
|
#define GAME_MODE_TEAM_PLAY 5
|
|
#define GAME_MODE_MULTI_PLAYER_COLLECT_RINGS 6
|
|
#elif (GAME == GAME_SA2)
|
|
#define GAME_MODE_BOSS_TIME_ATTACK 2
|
|
#define GAME_MODE_TEAM_PLAY 4
|
|
#define GAME_MODE_MULTI_PLAYER_COLLECT_RINGS 5
|
|
#endif
|
|
|
|
#if (GAME == GAME_SA1)
|
|
// In SA1 this check also assumed multiplayer was team play
|
|
#define IS_TEAM_PLAY ((gGameMode == GAME_MODE_MULTI_PLAYER) || (gGameMode == GAME_MODE_TEAM_PLAY))
|
|
#elif (GAME == GAME_SA2)
|
|
// In SA2 this only checks that we are in the team play game mode
|
|
#define IS_TEAM_PLAY ((gGameMode == GAME_MODE_TEAM_PLAY))
|
|
#endif
|
|
|
|
#if (GAME == GAME_SA1)
|
|
#define IS_SINGLE_PLAYER ((gGameMode == GAME_MODE_SINGLE_PLAYER) || (gGameMode == GAME_MODE_TIME_ATTACK))
|
|
#elif (GAME == GAME_SA2)
|
|
#define IS_SINGLE_PLAYER \
|
|
((gGameMode == GAME_MODE_SINGLE_PLAYER) || (gGameMode == GAME_MODE_TIME_ATTACK) || (gGameMode == GAME_MODE_BOSS_TIME_ATTACK))
|
|
#endif
|
|
|
|
#if (GAME == GAME_SA1)
|
|
#define GAME_MODE_IS_TIME_ATTACK (gGameMode == GAME_MODE_TIME_ATTACK)
|
|
#elif (GAME == GAME_SA2)
|
|
#define GAME_MODE_IS_TIME_ATTACK (gGameMode == GAME_MODE_TIME_ATTACK || gGameMode == GAME_MODE_BOSS_TIME_ATTACK)
|
|
#endif
|
|
|
|
#if (GAME == GAME_SA1)
|
|
#define GAME_MODE_REQUIRES_ITEM_RNG \
|
|
((gGameMode != GAME_MODE_SINGLE_PLAYER) && (gGameMode != GAME_MODE_TIME_ATTACK) && (gGameMode != GAME_MODE_RACE) \
|
|
&& (gGameMode != GAME_MODE_MULTI_PLAYER))
|
|
#elif (GAME == GAME_SA2)
|
|
#define GAME_MODE_REQUIRES_ITEM_RNG (gGameMode == GAME_MODE_MULTI_PLAYER_COLLECT_RINGS)
|
|
#endif
|
|
|
|
#define IS_MULTI_PLAYER (!(IS_SINGLE_PLAYER))
|
|
|
|
#define STAGE_FLAG__CLEAR 0x0000
|
|
#define STAGE_FLAG__ACT_START 0x0001 // Turns timer off, likely other effects?
|
|
#define STAGE_FLAG__2 0x0002
|
|
#define STAGE_FLAG__TIMER_REVERSED 0x0004 // Timer gets lower on every tick, and player loses life on 0:00:00.
|
|
#define STAGE_FLAG__10 0x0010
|
|
#define STAGE_FLAG__DISABLE_PAUSE_MENU 0x0020
|
|
#define STAGE_FLAG__DEMO_RUNNING 0x0040 // TODO: Check accuracy of name
|
|
#define STAGE_FLAG__GRAVITY_INVERTED 0x0080
|
|
#define STAGE_FLAG__100 0x0100 // Set during stage's "loading screen"
|
|
#define STAGE_FLAG__TURN_OFF_TIMER 0x0200
|
|
#define STAGE_FLAG__TURN_OFF_HUD 0x0400
|
|
|
|
#define DIFFICULTY_NORMAL 0
|
|
#define DIFFICULTY_EASY 1
|
|
|
|
#define MAX_PLAYER_NAME_LENGTH 6
|
|
|
|
#define GRAVITY_IS_INVERTED (gStageFlags & STAGE_FLAG__GRAVITY_INVERTED)
|
|
|
|
typedef struct {
|
|
u8 unk0;
|
|
u8 unk1;
|
|
u8 unk2;
|
|
u8 unk3;
|
|
u8 unk4;
|
|
u8 unk5;
|
|
u16 fadeoutSpeed;
|
|
} MusicManagerState; /* size: 8 */
|
|
|
|
typedef struct {
|
|
s32 squarePlayerDistance;
|
|
u16 angle;
|
|
} HomingTarget;
|
|
|
|
// Common RoomEvent variables
|
|
#define ROOMEVENT_BASE u8 type
|
|
|
|
// RoomEvent opaque struct
|
|
// See 'multiplayer_event_mgr.h' for more information on this
|
|
typedef struct {
|
|
ROOMEVENT_BASE;
|
|
|
|
u8 opaque[7];
|
|
} RoomEvent; /* 0x8 */
|
|
|
|
#define CHEESE_DISTANCE_MAX 200
|
|
typedef struct {
|
|
/* 0x00 */ s32 squarePlayerDistance;
|
|
/* 0x04 */ Task *task;
|
|
} CheeseTarget; /* size: unknown (but >= 0x8?) */
|
|
|
|
// Incomplete
|
|
extern u8 gDemoPlayCounter;
|
|
|
|
#if (GAME == GAME_SA1)
|
|
extern u16 ALIGNED(4) gSpecialStageReturnX;
|
|
#endif
|
|
|
|
extern u8 gGameMode;
|
|
|
|
extern s8 gCurrentLevel;
|
|
extern s8 gSelectedCharacter;
|
|
#if (GAME == GAME_SA1)
|
|
// NOTE: Treat gTailsEnabled as bool8, it just does not match unsigned
|
|
extern s8 gTailsEnabled;
|
|
extern s8 gNumSingleplayerCharacters;
|
|
#define NUM_SINGLEPLAYER_CHARS_MAX 2
|
|
#elif (GAME == GAME_SA2)
|
|
extern u8 gNumSingleplayerCharacters;
|
|
#endif
|
|
|
|
extern u8 gMultiplayerLanguage;
|
|
extern s8 gMultiplayerCurrentLevel;
|
|
|
|
// Sometimes loaded as s16, but as u16 most of the time.
|
|
// If you encounter it being loaded as s16, please cast it.
|
|
extern u16 gRingCount;
|
|
|
|
extern MusicManagerState gMusicManagerState;
|
|
|
|
// Time that is being used to score the player (or in Time Attack)
|
|
extern u32 gCourseTime;
|
|
|
|
// Collected Special Rings in current stage?
|
|
extern u8 gSpecialRingCount;
|
|
|
|
// TODO: Types not checked yet!
|
|
extern s32 gWorldSpeedY;
|
|
#if (GAME == GAME_SA2)
|
|
extern s32 gWorldSpeedX;
|
|
#endif
|
|
|
|
extern u16 gBossCameraClampYLower;
|
|
extern u16 gBossCameraClampYUpper;
|
|
extern u8 gRandomItemBox;
|
|
#if (GAME == GAME_SA1)
|
|
extern u16 gSpecialStageCollectedRings;
|
|
extern u16 gUnknown_0300507C;
|
|
#endif
|
|
extern u8 gSpikesUnknownTimer;
|
|
|
|
extern s8 SA2_LABEL(gUnknown_0300543C);
|
|
extern Task *gEntitiesManagerTask;
|
|
|
|
extern u8 gDestroySpotlights;
|
|
|
|
extern u8 gRoomEventQueueSendPos;
|
|
|
|
// "Extra State" (see above #defines for states)
|
|
// TODO: Find better name. Put somewhere else?
|
|
extern u16 gStageFlags;
|
|
extern u16 gPrevStageFlags;
|
|
|
|
extern u8 gDifficultyLevel;
|
|
|
|
extern s8 gTrappedAnimalVariant;
|
|
|
|
extern u8 gBossIndex; // TODO: Rename to gLastCheckpointIndex? (SA1 AND SA2!)
|
|
|
|
// Incremented by 1 every frame if the game is not paused.
|
|
// Starts before the stage-timer that is used for scores does.
|
|
extern u32 gStageTime;
|
|
extern u32 gMPStageStartFrameCount;
|
|
|
|
extern u32 gCheckpointTime; // Checkpoint timer?
|
|
|
|
extern u8 gRoomEventQueueWritePos;
|
|
|
|
extern u8 gBossRingsRespawnCount;
|
|
extern bool8 gBossRingsShallRespawn;
|
|
extern bool8 gBoostEffectTasksCreated;
|
|
|
|
extern Task *gMultiplayerPlayerTasks[4];
|
|
extern s8 gMultiplayerCharacters[4];
|
|
extern s8 gMultiplayerRanks[4];
|
|
extern u8 gOamMatrixIndex;
|
|
|
|
extern u8 gMultiplayerMissingHeartbeats[4];
|
|
extern u8 gActiveCollectRingEffectCount;
|
|
|
|
extern u8 gMultiplayerUnlockedCharacters;
|
|
|
|
#if (GAME == GAME_SA1)
|
|
extern u16 gSpecialStageReturnY;
|
|
#elif (GAME == GAME_SA2)
|
|
extern u8 gMultiplayerUnlockedLevels;
|
|
#endif
|
|
|
|
extern u32 gMultiplayerIds[MULTI_SIO_PLAYERS_MAX];
|
|
extern u16 gMultiplayerNames[MULTI_SIO_PLAYERS_MAX][MAX_PLAYER_NAME_LENGTH];
|
|
|
|
extern u32 gMultiplayerPseudoRandom;
|
|
|
|
extern s32 gLevelScore;
|
|
extern u8 gNumLives;
|
|
extern bool8 gFinalBossActive;
|
|
|
|
extern HomingTarget gHomingTarget;
|
|
|
|
extern u8 gMultiplayerConnections;
|
|
|
|
// Only set after the player passed it, used to determine extra score
|
|
extern s32 gStageGoalX;
|
|
|
|
extern u8 gMPRingCollectWins[4];
|
|
extern u8 gMultiplayerCharRings[MULTI_SIO_PLAYERS_MAX];
|
|
|
|
extern RoomEvent gRoomEventQueue[16];
|
|
|
|
extern CheeseTarget gCheeseTarget;
|
|
|
|
extern Task *gChaoTasks[3];
|
|
|
|
#define MPC_FLAG_SHIFT_REGISTERED 0
|
|
#define MPC_FLAG_SHIFT_B_TEAM 4
|
|
|
|
#define MPC_FLAG_SET(pid, flag) (gMultiplayerConnections |= ((1 << MPC_FLAG_SHIFT_##flag) << (pid)))
|
|
#define MPC_FLAG_CLEAR(pid, flag) (gMultiplayerConnections &= ~((1 << MPC_FLAG_SHIFT_##flag) << (pid)))
|
|
|
|
#define MPC_FLAG_GET(pid, flag) (gMultiplayerConnections & ((1 << MPC_FLAG_SHIFT_##flag) << (pid)))
|
|
#define MPC_FLAG_CHECK(pid, flag) ((gMultiplayerConnections & ((1 << MPC_FLAG_SHIFT_##flag) << (pid))) >> ((pid) + MPC_FLAG_SHIFT_##flag))
|
|
|
|
#define CONNECTION_REGISTERED(pid) MPC_FLAG_GET(pid, REGISTERED)
|
|
#define IS_SAME_TEAM(pid1, pid2) (MPC_FLAG_CHECK(pid1, B_TEAM) == MPC_FLAG_CHECK(pid2, B_TEAM))
|
|
|
|
#define EXTRACT_REGISTERED_CONNECTIONS(statusFlags) ((statusFlags & MULTI_SIO_ALL_CONNECTED) >> 8)
|
|
|
|
// I wonder if this was stubbed and you had to provide your own implementation
|
|
extern void LinkCommunicationError(void);
|
|
|
|
#define LINK_HEARTBEAT() \
|
|
({ \
|
|
if (IS_MULTI_PLAYER) { \
|
|
u32 i; \
|
|
for (i = 0; i < MULTI_SIO_PLAYERS_MAX && CONNECTION_REGISTERED(i); i++) { \
|
|
if (!(gMultiSioStatusFlags & MULTI_SIO_RECV_ID(i))) { \
|
|
if (gMultiplayerMissingHeartbeats[i]++ > 180) { \
|
|
TasksDestroyAll(); \
|
|
PAUSE_BACKGROUNDS_QUEUE(); \
|
|
gBgSpritesCount = 0; \
|
|
gVramGraphicsCopyCursor = gVramGraphicsCopyQueueIndex; \
|
|
LinkCommunicationError(); \
|
|
return; \
|
|
} \
|
|
} else { \
|
|
gMultiplayerMissingHeartbeats[i] = 0; \
|
|
} \
|
|
} \
|
|
} \
|
|
})
|
|
|
|
#if (GAME == GAME_SA1) && !defined(BUG_FIX)
|
|
// NOTE: gNumLives is u8, so without the bounds-check,
|
|
// which SA1 did not have, 255 would overflow to 0.
|
|
// During regular gameplay you would not get that many 1UPs, though.
|
|
#define LIVES_BOUND_CHECK_A(lives) (lives)
|
|
#define LIVES_BOUND_CHECK_B(lives) (lives)
|
|
#define NEW_LIVE_COUNT(_type, _varname, _inc) (gNumLives += 1)
|
|
|
|
#define BOUND_CHK_VARNAME gNumLives
|
|
#else
|
|
#define LIVES_BOUND_CHECK_A(lives) \
|
|
({ \
|
|
if ((lives) > 255) \
|
|
(lives) = 255; \
|
|
gNumLives = (lives); \
|
|
})
|
|
|
|
#define LIVES_BOUND_CHECK_B(lives) \
|
|
({ \
|
|
if (lives > 255) { \
|
|
gNumLives = 255; \
|
|
} else { \
|
|
gNumLives = lives; \
|
|
} \
|
|
})
|
|
|
|
#define NEW_LIVE_COUNT(_type, _varname, _inc) \
|
|
_type _varname = divResA - divResB; \
|
|
_varname += _inc;
|
|
|
|
#define BOUND_CHK_VARNAME lives
|
|
#endif
|
|
|
|
#define INCREMENT_SCORE_BASE(_inc, restartMusic, BoundCheck) \
|
|
{ \
|
|
s32 divResA, divResB; \
|
|
s32 oldScore = gLevelScore; \
|
|
gLevelScore += _inc; \
|
|
\
|
|
divResA = Div(gLevelScore, 50000); \
|
|
divResB = Div(oldScore, 50000); \
|
|
\
|
|
if ((divResA != divResB) && (gGameMode == GAME_MODE_SINGLE_PLAYER)) { \
|
|
NEW_LIVE_COUNT(u16, BOUND_CHK_VARNAME, gNumLives); \
|
|
BoundCheck(BOUND_CHK_VARNAME); \
|
|
\
|
|
if (restartMusic) \
|
|
gMusicManagerState.unk3 = 0x10 | 0x0; \
|
|
} \
|
|
}
|
|
|
|
#define INCREMENT_SCORE_A(_inc) INCREMENT_SCORE_BASE(_inc, FALSE, LIVES_BOUND_CHECK_A)
|
|
#define INCREMENT_SCORE(_inc) INCREMENT_SCORE_BASE(_inc, TRUE, LIVES_BOUND_CHECK_A)
|
|
#define INCREMENT_SCORE_B(_inc) INCREMENT_SCORE_BASE(_inc, TRUE, LIVES_BOUND_CHECK_B)
|
|
|
|
#define INCREMENT_RINGS(_inc) \
|
|
{ \
|
|
s32 prevLives, newLives; \
|
|
u16 oldRings = gRingCount; \
|
|
gRingCount = oldRings + (_inc); \
|
|
if (!(IS_EXTRA_STAGE(gCurrentLevel))) { \
|
|
newLives = Div(gRingCount, 100); \
|
|
prevLives = Div(oldRings, 100); \
|
|
if ((newLives != prevLives) && (gGameMode == GAME_MODE_SINGLE_PLAYER)) { \
|
|
u16 lives = gNumLives + 1; \
|
|
gNumLives = LIVES_BOUND_CHECK_A(lives); \
|
|
gMusicManagerState.unk3 = 0x10 | 0x0; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#endif // GUARD_SAKIT_GLOBALS_H
|