mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2025-12-17 21:42:42 +00:00
Coop now maintains a counter that increments at the start of each game tick, And another counter that increments at the start of each frame render. This is to be able to identify specific frames regardless of mod load, hook, and execution order. --------- Co-authored-by: MysterD <myster@d>
630 lines
24 KiB
C
630 lines
24 KiB
C
#include <ultra64.h>
|
|
|
|
#include "sm64.h"
|
|
#include "gfx_dimensions.h"
|
|
#include "audio/external.h"
|
|
#include "buffers/buffers.h"
|
|
#include "gfx_dimensions.h"
|
|
#include "buffers/gfx_output_buffer.h"
|
|
#include "buffers/framebuffers.h"
|
|
#include "buffers/zbuffer.h"
|
|
#include "engine/level_script.h"
|
|
#include "game_init.h"
|
|
#include "main.h"
|
|
#include "memory.h"
|
|
#include "profiler.h"
|
|
#include "save_file.h"
|
|
#include "seq_ids.h"
|
|
#include "sound_init.h"
|
|
#include "print.h"
|
|
#include "segment2.h"
|
|
#include "segment_symbols.h"
|
|
#include "rng_position.h"
|
|
#include "pc/djui/djui.h"
|
|
#include "pc/djui/djui_panel_pause.h"
|
|
#include "rumble_init.h"
|
|
#include <prevent_bss_reordering.h>
|
|
#include "bettercamera.h"
|
|
#include "hud.h"
|
|
#include "pc/controller/controller_mouse.h"
|
|
|
|
// FIXME: I'm not sure all of these variables belong in this file, but I don't
|
|
// know of a good way to split them
|
|
|
|
struct Controller gControllers[MAX_PLAYERS] = { 0 };
|
|
struct SPTask *gGfxSPTask = NULL;
|
|
Gfx *gDisplayListHead = NULL;
|
|
u8 *gGfxPoolEnd = NULL;
|
|
struct GfxPool *gGfxPool = NULL;
|
|
OSContStatus gControllerStatuses[4] = { 0 };
|
|
OSContPad gControllerPads[4] = { 0 };
|
|
u8 gControllerBits = 0;
|
|
s8 gEepromProbe = 0;
|
|
OSMesgQueue gGameVblankQueue = { 0 };
|
|
OSMesgQueue D_80339CB8 = { 0 };
|
|
OSMesg D_80339CD0 = NULL;
|
|
OSMesg D_80339CD4 = NULL;
|
|
struct VblankHandler gGameVblankHandler = { 0 };
|
|
uintptr_t gPhysicalFrameBuffers[3] = { 0 };
|
|
uintptr_t gPhysicalZBuffer = 0;
|
|
void *gDemoTargetAnim = NULL;
|
|
struct MarioAnimation D_80339D10[MAX_PLAYERS] = { 0 };
|
|
struct MarioAnimation gDemo = { 0 };
|
|
|
|
u32 gGlobalTimer = 0;
|
|
|
|
static u16 sCurrFBNum = 0;
|
|
u16 frameBufferIndex = 0;
|
|
void (*gGoddardVblankCallback)(void) = NULL;
|
|
struct Controller *gPlayer1Controller = &gControllers[0];
|
|
struct Controller *gPlayer2Controller = &gControllers[1];
|
|
// probably debug only, see note below
|
|
struct Controller *gPlayer3Controller = &gControllers[2];
|
|
struct DemoInput *gCurrDemoInput = NULL; // demo input sequence
|
|
u16 gDemoInputListID = 0;
|
|
struct DemoInput gRecordedDemoInput = { 0 }; // possibly removed in EU. TODO: Check
|
|
u64 gGameTickCounter = 0;
|
|
|
|
/**
|
|
* Initializes the Reality Display Processor (RDP).
|
|
* This function initializes settings such as texture filtering mode,
|
|
* scissoring, and render mode (although keep in mind that this render
|
|
* mode is not used in-game, where it is set in render_graph_node.c).
|
|
*/
|
|
void my_rdp_init(void) {
|
|
gDPPipeSync(gDisplayListHead++);
|
|
gDPPipelineMode(gDisplayListHead++, G_PM_1PRIMITIVE);
|
|
|
|
gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE);
|
|
|
|
gDPSetTextureLOD(gDisplayListHead++, G_TL_TILE);
|
|
gDPSetTextureLUT(gDisplayListHead++, G_TT_NONE);
|
|
gDPSetTextureDetail(gDisplayListHead++, G_TD_CLAMP);
|
|
gDPSetTexturePersp(gDisplayListHead++, G_TP_PERSP);
|
|
gDPSetTextureFilter(gDisplayListHead++, G_TF_BILERP);
|
|
gDPSetTextureConvert(gDisplayListHead++, G_TC_FILT);
|
|
|
|
gDPSetCombineKey(gDisplayListHead++, G_CK_NONE);
|
|
gDPSetAlphaCompare(gDisplayListHead++, G_AC_NONE);
|
|
gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
|
gDPSetColorDither(gDisplayListHead++, G_CD_MAGICSQ);
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_FILL);
|
|
|
|
#ifdef VERSION_SH
|
|
gDPSetAlphaDither(gDisplayListHead++, G_AD_PATTERN);
|
|
#endif
|
|
gDPPipeSync(gDisplayListHead++);
|
|
}
|
|
|
|
/**
|
|
* Initializes the RSP's built-in geometry and lighting engines.
|
|
* Most of these (with the notable exception of gSPNumLights), are
|
|
* almost immediately overwritten.
|
|
*/
|
|
void my_rsp_init(void) {
|
|
gSPClearGeometryMode(gDisplayListHead++, G_SHADE | G_SHADING_SMOOTH | G_CULL_BOTH | G_FOG
|
|
| G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD);
|
|
|
|
gSPSetGeometryMode(gDisplayListHead++, G_SHADE | G_SHADING_SMOOTH | G_CULL_BACK | G_LIGHTING);
|
|
|
|
gSPNumLights(gDisplayListHead++, NUMLIGHTS_1);
|
|
gSPTexture(gDisplayListHead++, 0, 0, 0, G_TX_RENDERTILE, G_OFF);
|
|
|
|
// @bug Nintendo did not explicitly define the clipping ratio.
|
|
// For Fast3DEX2, this causes the dreaded warped vertices issue
|
|
// unless the clipping ratio is changed back to the intended value,
|
|
// as Fast3DEX2 uses a different initial value than Fast3D(EX).
|
|
#ifdef F3DEX_GBI_2
|
|
gSPClipRatio(gDisplayListHead++, FRUSTRATIO_1);
|
|
#endif
|
|
}
|
|
|
|
/** Clear the Z buffer. */
|
|
void clear_z_buffer(void) {
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetDepthSource(gDisplayListHead++, G_ZS_PIXEL);
|
|
gDPSetDepthImage(gDisplayListHead++, gPhysicalZBuffer);
|
|
|
|
gDPSetColorImage(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WIDTH, gPhysicalZBuffer);
|
|
gDPSetFillColor(gDisplayListHead++,
|
|
GPACK_ZDZ(G_MAXFBZ, 0) << 16 | GPACK_ZDZ(G_MAXFBZ, 0));
|
|
|
|
gDPFillRectangle(gDisplayListHead++, 0, BORDER_HEIGHT, SCREEN_WIDTH - 1,
|
|
SCREEN_HEIGHT - 1 - BORDER_HEIGHT);
|
|
}
|
|
|
|
/** Sets up the final framebuffer image. */
|
|
void display_frame_buffer(void) {
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_1CYCLE);
|
|
gDPSetColorImage(gDisplayListHead++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WIDTH,
|
|
gPhysicalFrameBuffers[frameBufferIndex]);
|
|
gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH,
|
|
SCREEN_HEIGHT - BORDER_HEIGHT);
|
|
}
|
|
|
|
/** Clears the framebuffer, allowing it to be overwritten. */
|
|
void clear_frame_buffer(s32 color) {
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_FILL);
|
|
|
|
gDPSetFillColor(gDisplayListHead++, color);
|
|
gDPFillRectangle(gDisplayListHead++,
|
|
GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(0), BORDER_HEIGHT,
|
|
GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(0) - 1, SCREEN_HEIGHT - BORDER_HEIGHT - 1);
|
|
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_1CYCLE);
|
|
}
|
|
|
|
/** Clears and initializes the viewport. */
|
|
void clear_viewport(Vp *viewport, s32 color) {
|
|
s16 vpUlx = (viewport->vp.vtrans[0] - viewport->vp.vscale[0]) / 4 + 1;
|
|
s16 vpUly = (viewport->vp.vtrans[1] - viewport->vp.vscale[1]) / 4 + 1;
|
|
s16 vpLrx = (viewport->vp.vtrans[0] + viewport->vp.vscale[0]) / 4 - 2;
|
|
s16 vpLry = (viewport->vp.vtrans[1] + viewport->vp.vscale[1]) / 4 - 2;
|
|
|
|
vpUlx = GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(vpUlx);
|
|
vpLrx = GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(SCREEN_WIDTH - vpLrx);
|
|
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_FILL);
|
|
|
|
gDPSetFillColor(gDisplayListHead++, color);
|
|
gDPFillRectangle(gDisplayListHead++, vpUlx, vpUly, vpLrx, vpLry);
|
|
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_1CYCLE);
|
|
}
|
|
|
|
/** Draws the horizontal screen borders */
|
|
void draw_screen_borders(void) {
|
|
gDPPipeSync(gDisplayListHead++);
|
|
|
|
gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
|
|
gDPSetCycleType(gDisplayListHead++, G_CYC_FILL);
|
|
|
|
gDPSetFillColor(gDisplayListHead++, GPACK_RGBA5551(0, 0, 0, 0) << 16 | GPACK_RGBA5551(0, 0, 0, 0));
|
|
|
|
#if BORDER_HEIGHT != 0
|
|
gDPFillRectangle(gDisplayListHead++, GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(0), 0,
|
|
GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(0) - 1, BORDER_HEIGHT - 1);
|
|
gDPFillRectangle(gDisplayListHead++,
|
|
GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(0), SCREEN_HEIGHT - BORDER_HEIGHT,
|
|
GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(0) - 1, SCREEN_HEIGHT - 1);
|
|
#endif
|
|
}
|
|
|
|
void make_viewport_clip_rect(Vp *viewport) {
|
|
s16 vpUlx = (viewport->vp.vtrans[0] - viewport->vp.vscale[0]) / 4 + 1;
|
|
s16 vpPly = (viewport->vp.vtrans[1] - viewport->vp.vscale[1]) / 4 + 1;
|
|
s16 vpLrx = (viewport->vp.vtrans[0] + viewport->vp.vscale[0]) / 4 - 1;
|
|
s16 vpLry = (viewport->vp.vtrans[1] + viewport->vp.vscale[1]) / 4 - 1;
|
|
|
|
gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, vpUlx, vpPly, vpLrx, vpLry);
|
|
}
|
|
|
|
/**
|
|
* Loads the F3D microcodes.
|
|
* Refer to this function if you would like to load
|
|
* other microcodes (i.e. S2DEX).
|
|
*/
|
|
void create_task_structure(void) {
|
|
s32 entries = gDisplayListHead - gGfxPool->buffer;
|
|
|
|
gGfxSPTask->msgqueue = &D_80339CB8;
|
|
gGfxSPTask->msg = (OSMesg) 2;
|
|
gGfxSPTask->task.t.type = M_GFXTASK;
|
|
|
|
gGfxSPTask->task.t.ucode_size = SP_UCODE_SIZE; // (this size is ignored)
|
|
gGfxSPTask->task.t.ucode_data_size = SP_UCODE_DATA_SIZE;
|
|
gGfxSPTask->task.t.dram_stack = (u64 *) gGfxSPTaskStack;
|
|
gGfxSPTask->task.t.dram_stack_size = SP_DRAM_STACK_SIZE8;
|
|
gGfxSPTask->task.t.output_buff = gGfxSPTaskOutputBuffer;
|
|
gGfxSPTask->task.t.output_buff_size =
|
|
(u64 *)((u8 *) gGfxSPTaskOutputBuffer + sizeof(gGfxSPTaskOutputBuffer));
|
|
gGfxSPTask->task.t.data_ptr = (u64 *) &gGfxPool->buffer;
|
|
gGfxSPTask->task.t.data_size = entries * sizeof(Gfx);
|
|
gGfxSPTask->task.t.yield_data_ptr = (u64 *) gGfxSPTaskYieldBuffer;
|
|
gGfxSPTask->task.t.yield_data_size = OS_YIELD_DATA_SIZE;
|
|
}
|
|
|
|
/** Starts rendering the scene. */
|
|
void init_render_image(void) {
|
|
my_rdp_init();
|
|
my_rsp_init();
|
|
clear_z_buffer();
|
|
display_frame_buffer();
|
|
}
|
|
|
|
/** Ends the master display list. */
|
|
void end_master_display_list(void) {
|
|
draw_screen_borders();
|
|
if (gShowProfiler) {
|
|
draw_profiler();
|
|
}
|
|
|
|
extern void djui_render(void);
|
|
djui_render();
|
|
|
|
gDPFullSync(gDisplayListHead++);
|
|
gSPEndDisplayList(gDisplayListHead++);
|
|
|
|
create_task_structure();
|
|
}
|
|
|
|
//void draw_reset_bars(void) { // TARGET_64 only
|
|
// Stubbed. Only N64 target uses this
|
|
// }
|
|
|
|
void rendering_init(void) {
|
|
gGfxPool = &gGfxPools[0];
|
|
set_segment_base_addr(1, gGfxPool->buffer);
|
|
gGfxSPTask = &gGfxPool->spTask;
|
|
gDisplayListHead = gGfxPool->buffer;
|
|
gGfxPoolEnd = (u8 *) (gGfxPool->buffer + GFX_POOL_SIZE);
|
|
alloc_display_list_reset();
|
|
init_render_image();
|
|
clear_frame_buffer(0);
|
|
end_master_display_list();
|
|
send_display_list(&gGfxPool->spTask);
|
|
|
|
frameBufferIndex++;
|
|
gGlobalTimer++;
|
|
}
|
|
|
|
void config_gfx_pool(void) {
|
|
gGfxPool = &gGfxPools[gGlobalTimer % GFX_NUM_POOLS];
|
|
set_segment_base_addr(1, gGfxPool->buffer);
|
|
gGfxSPTask = &gGfxPool->spTask;
|
|
gDisplayListHead = gGfxPool->buffer;
|
|
gGfxPoolEnd = (u8 *) (gGfxPool->buffer + GFX_POOL_SIZE);
|
|
alloc_display_list_reset();
|
|
}
|
|
|
|
/** Handles vsync. */
|
|
void display_and_vsync(void) {
|
|
profiler_log_thread5_time(BEFORE_DISPLAY_LISTS);
|
|
osRecvMesg(&D_80339CB8, &D_80339BEC, OS_MESG_BLOCK);
|
|
if (gGoddardVblankCallback != NULL) {
|
|
gGoddardVblankCallback();
|
|
gGoddardVblankCallback = NULL;
|
|
}
|
|
|
|
// we only produce interpolated frames now
|
|
//send_display_list(&gGfxPool->spTask);
|
|
|
|
profiler_log_thread5_time(AFTER_DISPLAY_LISTS);
|
|
osRecvMesg(&gGameVblankQueue, &D_80339BEC, OS_MESG_BLOCK);
|
|
osViSwapBuffer((void *) PHYSICAL_TO_VIRTUAL(gPhysicalFrameBuffers[sCurrFBNum]));
|
|
profiler_log_thread5_time(THREAD5_END);
|
|
osRecvMesg(&gGameVblankQueue, &D_80339BEC, OS_MESG_BLOCK);
|
|
if (++sCurrFBNum == 3) {
|
|
sCurrFBNum = 0;
|
|
}
|
|
if (++frameBufferIndex == 3) {
|
|
frameBufferIndex = 0;
|
|
}
|
|
gGlobalTimer++;
|
|
}
|
|
|
|
// this function records distinct inputs over a 255-frame interval to RAM locations and was likely
|
|
// used to record the demo sequences seen in the final game. This function is unused.
|
|
static void record_demo(void) {
|
|
// record the player's button mask and current rawStickX and rawStickY.
|
|
u8 buttonMask =
|
|
((gPlayer1Controller->buttonDown & (A_BUTTON | B_BUTTON | Z_TRIG | START_BUTTON)) >> 8)
|
|
| (gPlayer1Controller->buttonDown & (U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS));
|
|
s8 rawStickX = gPlayer1Controller->rawStickX;
|
|
s8 rawStickY = gPlayer1Controller->rawStickY;
|
|
|
|
// if the stick is in deadzone, set its value to 0 to
|
|
// nullify the effects. We do not record deadzone inputs.
|
|
if (rawStickX > -8 && rawStickX < 8) {
|
|
rawStickX = 0;
|
|
}
|
|
|
|
if (rawStickY > -8 && rawStickY < 8) {
|
|
rawStickY = 0;
|
|
}
|
|
|
|
// record the distinct input and timer so long as they
|
|
// are unique. If the timer hits 0xFF, reset the timer
|
|
// for the next demo input.
|
|
if (gRecordedDemoInput.timer == 0xFF || buttonMask != gRecordedDemoInput.buttonMask
|
|
|| rawStickX != gRecordedDemoInput.rawStickX || rawStickY != gRecordedDemoInput.rawStickY) {
|
|
gRecordedDemoInput.timer = 0;
|
|
gRecordedDemoInput.buttonMask = buttonMask;
|
|
gRecordedDemoInput.rawStickX = rawStickX;
|
|
gRecordedDemoInput.rawStickY = rawStickY;
|
|
}
|
|
gRecordedDemoInput.timer++;
|
|
}
|
|
|
|
// take the updated controller struct and calculate
|
|
// the new x, y, and distance floats.
|
|
void adjust_analog_stick(struct Controller *controller) {
|
|
// Reset the controller's x and y floats.
|
|
controller->stickX = 0;
|
|
controller->stickY = 0;
|
|
|
|
// Modulate the rawStickX and rawStickY to be the new f32 values by adding/subtracting 6.
|
|
if (controller->rawStickX <= -8) {
|
|
controller->stickX = controller->rawStickX + 6;
|
|
}
|
|
|
|
if (controller->rawStickX >= 8) {
|
|
controller->stickX = controller->rawStickX - 6;
|
|
}
|
|
|
|
if (controller->rawStickY <= -8) {
|
|
controller->stickY = controller->rawStickY + 6;
|
|
}
|
|
|
|
if (controller->rawStickY >= 8) {
|
|
controller->stickY = controller->rawStickY - 6;
|
|
}
|
|
|
|
// Calculate f32 magnitude from the center by vector length.
|
|
controller->stickMag =
|
|
sqrtf(controller->stickX * controller->stickX + controller->stickY * controller->stickY);
|
|
|
|
// Magnitude cannot exceed 64.0f: if it does, modify the values appropriately to
|
|
// flatten the values down to the allowed maximum value.
|
|
if (controller->stickMag > 64) {
|
|
controller->stickX *= 64 / controller->stickMag;
|
|
controller->stickY *= 64 / controller->stickMag;
|
|
controller->stickMag = 64;
|
|
}
|
|
|
|
/*extern bool gDebugToggle;
|
|
if (gDebugToggle) {
|
|
controller->stickX = 64;
|
|
controller->stickY = 0;
|
|
controller->stickMag = 64;
|
|
}*/
|
|
|
|
}
|
|
|
|
// if a demo sequence exists, this will run the demo
|
|
// input list until it is complete. called every frame.
|
|
void run_demo_inputs(void) {
|
|
/*
|
|
Check if a demo inputs list
|
|
exists and if so, run the
|
|
active demo input list.
|
|
*/
|
|
if (gCurrDemoInput != NULL) {
|
|
/*
|
|
clear player 2's inputs if they exist. Player 2's controller
|
|
cannot be used to influence a demo. At some point, Nintendo
|
|
may have planned for there to be a demo where 2 players moved
|
|
around instead of just one, so clearing player 2's influence from
|
|
the demo had to have been necessary to perform this. Co-op mode, perhaps?
|
|
*/
|
|
if (gControllers[1].controllerData != NULL) {
|
|
gControllers[1].controllerData->stick_x = 0;
|
|
gControllers[1].controllerData->stick_y = 0;
|
|
gControllers[1].controllerData->button = 0;
|
|
}
|
|
|
|
// the timer variable being 0 at the current input means the demo is over.
|
|
// set the button to the END_DEMO mask to end the demo.
|
|
if (gCurrDemoInput->timer == 0) {
|
|
gControllers[0].controllerData->stick_x = 0;
|
|
gControllers[0].controllerData->stick_y = 0;
|
|
gControllers[0].controllerData->button = END_DEMO;
|
|
} else {
|
|
// backup the start button if it is pressed, since we don't want the
|
|
// demo input to override the mask where start may have been pressed.
|
|
u16 startPushed = gControllers[0].controllerData->button & START_BUTTON;
|
|
|
|
// perform the demo inputs by assigning the current button mask and the stick inputs.
|
|
gControllers[0].controllerData->stick_x = gCurrDemoInput->rawStickX;
|
|
gControllers[0].controllerData->stick_y = gCurrDemoInput->rawStickY;
|
|
|
|
/*
|
|
to assign the demo input, the button information is stored in
|
|
an 8-bit mask rather than a 16-bit mask. this is because only
|
|
A, B, Z, Start, and the C-Buttons are used in a demo, as bits
|
|
in that order. In order to assign the mask, we need to take the
|
|
upper 4 bits (A, B, Z, and Start) and shift then left by 8 to
|
|
match the correct input mask. We then add this to the masked
|
|
lower 4 bits to get the correct button mask.
|
|
*/
|
|
gControllers[0].controllerData->button =
|
|
((gCurrDemoInput->buttonMask & 0xF0) << 8) + ((gCurrDemoInput->buttonMask & 0xF));
|
|
|
|
// if start was pushed, put it into the demo sequence being input to
|
|
// end the demo.
|
|
gControllers[0].controllerData->button |= startPushed;
|
|
|
|
// run the current demo input's timer down. if it hits 0, advance the
|
|
// demo input list.
|
|
if (--gCurrDemoInput->timer == 0) {
|
|
gCurrDemoInput++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update the controller struct with available inputs if present.
|
|
void read_controller_inputs(void) {
|
|
// If any controllers are plugged in, update the
|
|
// controller information.
|
|
if (gControllerBits) {
|
|
osRecvMesg(&gSIEventMesgQueue, &D_80339BEC, OS_MESG_BLOCK);
|
|
osContGetReadData(gInteractableOverridePad ? &gInteractablePad : &gControllerPads[0]);
|
|
}
|
|
run_demo_inputs();
|
|
|
|
for (s32 i = 0; i < 1; i++) {
|
|
struct Controller *controller = &gControllers[i];
|
|
|
|
// if we're receiving inputs, update the controller struct
|
|
// with the new button info.
|
|
if (controller->controllerData != NULL) {
|
|
controller->rawStickX = controller->controllerData->stick_x;
|
|
controller->rawStickY = controller->controllerData->stick_y;
|
|
controller->extStickX = controller->controllerData->ext_stick_x;
|
|
controller->extStickY = controller->controllerData->ext_stick_y;
|
|
controller->buttonPressed = (~controller->buttonDown & controller->controllerData->button);
|
|
controller->buttonReleased = (~controller->controllerData->button & controller->buttonDown);
|
|
// 0.5x A presses are a good meme
|
|
controller->buttonDown = controller->controllerData->button;
|
|
adjust_analog_stick(controller);
|
|
} else if (i != 0) {
|
|
// otherwise, if the controllerData is NULL, 0 out all of the inputs.
|
|
controller->rawStickX = 0;
|
|
controller->rawStickY = 0;
|
|
controller->extStickX = 0;
|
|
controller->extStickY = 0;
|
|
controller->buttonPressed = 0;
|
|
controller->buttonReleased = 0;
|
|
controller->buttonDown = 0;
|
|
controller->stickX = 0;
|
|
controller->stickY = 0;
|
|
controller->stickMag = 0;
|
|
}
|
|
|
|
}
|
|
|
|
// For some reason, player 1's inputs are copied to player 3's port. This
|
|
// potentially may have been a way the developers "recorded" the inputs
|
|
// for demos, despite record_demo existing.
|
|
/*gPlayer3Controller->rawStickX = gPlayer1Controller->rawStickX;
|
|
gPlayer3Controller->rawStickY = gPlayer1Controller->rawStickY;
|
|
gPlayer3Controller->stickX = gPlayer1Controller->stickX;
|
|
gPlayer3Controller->stickY = gPlayer1Controller->stickY;
|
|
gPlayer3Controller->stickMag = gPlayer1Controller->stickMag;
|
|
gPlayer3Controller->buttonPressed = gPlayer1Controller->buttonPressed;
|
|
gPlayer3Controller->buttonReleased = gPlayer1Controller->buttonReleased;
|
|
gPlayer3Controller->buttonDown = gPlayer1Controller->buttonDown;*/
|
|
|
|
// Mouse Input
|
|
u32 prev_mouse_window_buttons = mouse_window_buttons;
|
|
controller_mouse_read_window();
|
|
mouse_window_buttons_pressed = ~prev_mouse_window_buttons & mouse_window_buttons;
|
|
mouse_window_buttons_released = ~mouse_window_buttons & prev_mouse_window_buttons;
|
|
|
|
if (gGlobalTimer > mouse_scroll_timestamp) {
|
|
mouse_scroll_x = 0;
|
|
mouse_scroll_y = 0;
|
|
}
|
|
}
|
|
|
|
// initialize the controller structs to point at the OSCont information.
|
|
void init_controllers(void) {
|
|
s16 port, cont;
|
|
|
|
// set controller 1 to point to the set of status/pads for input 1 and
|
|
// init the controllers.
|
|
gControllers[0].statusData = &gControllerStatuses[0];
|
|
gControllers[0].controllerData = &gControllerPads[0];
|
|
osContInit(&gSIEventMesgQueue, &gControllerBits, &gControllerStatuses[0]);
|
|
|
|
// strangely enough, the EEPROM probe for save data is done in this function.
|
|
// save pak detection?
|
|
gEepromProbe = osEepromProbe(&gSIEventMesgQueue);
|
|
|
|
// loop over the 4 ports and link the controller structs to the appropriate
|
|
// status and pad. Interestingly, although there are pointers to 3 controllers,
|
|
// only 2 are connected here. The third seems to have been reserved for debug
|
|
// purposes and was never connected in the retail ROM, thus gPlayer3Controller
|
|
// cannot be used, despite being referenced in various code.
|
|
for (cont = 0, port = 0; port < 4 && cont < 2; port++) {
|
|
// is controller plugged in?
|
|
if (gControllerBits & (1 << port)) {
|
|
// the game allows you to have just 1 controller plugged
|
|
// into any port in order to play the game. this was probably
|
|
// so if any of the ports didn't work, you can have controllers
|
|
// plugged into any of them and it will work.
|
|
gControllers[cont].port = port;
|
|
gControllers[cont].statusData = &gControllerStatuses[port];
|
|
gControllers[cont++].controllerData = &gControllerPads[port];
|
|
}
|
|
}
|
|
|
|
// load bettercam settings from the config file
|
|
newcam_init_settings();
|
|
}
|
|
|
|
void setup_game_memory(void) {
|
|
set_segment_base_addr(0, (void *) 0x80000000);
|
|
osCreateMesgQueue(&D_80339CB8, &D_80339CD4, 1);
|
|
osCreateMesgQueue(&gGameVblankQueue, &D_80339CD0, 1);
|
|
gPhysicalZBuffer = VIRTUAL_TO_PHYSICAL(gZBuffer);
|
|
gPhysicalFrameBuffers[0] = VIRTUAL_TO_PHYSICAL(gFrameBuffer0);
|
|
gPhysicalFrameBuffers[1] = VIRTUAL_TO_PHYSICAL(gFrameBuffer1);
|
|
gPhysicalFrameBuffers[2] = VIRTUAL_TO_PHYSICAL(gFrameBuffer2);
|
|
gDemoTargetAnim = calloc(1, 2048);
|
|
set_segment_base_addr(24, (void *) gDemoTargetAnim);
|
|
alloc_anim_dma_table(&gDemo, gDemoInputs, gDemoTargetAnim);
|
|
load_segment(0x10, _entrySegmentRomStart, _entrySegmentRomEnd, MEMORY_POOL_LEFT);
|
|
load_segment_decompress(2, _segment2_mio0SegmentRomStart, _segment2_mio0SegmentRomEnd);
|
|
}
|
|
|
|
|
|
static struct LevelCommand *levelCommandAddr;
|
|
|
|
// main game loop thread. runs forever as long as the game
|
|
// continues.
|
|
void thread5_game_loop(UNUSED void *arg) {
|
|
|
|
setup_game_memory();
|
|
init_rumble_pak_scheduler_queue();
|
|
init_controllers();
|
|
create_thread_6();
|
|
save_file_load_all(FALSE);
|
|
|
|
set_vblank_handler(2, &gGameVblankHandler, &gGameVblankQueue, (OSMesg) 1);
|
|
|
|
// point levelCommandAddr to the entry point into the level script data.
|
|
levelCommandAddr = segmented_to_virtual(level_script_entry);
|
|
|
|
play_music(SEQ_PLAYER_SFX, SEQUENCE_ARGS(0, SEQ_SOUND_PLAYER), 0);
|
|
set_sound_mode(save_file_get_sound_mode());
|
|
|
|
thread6_rumble_loop(NULL);
|
|
|
|
gGlobalTimer++;
|
|
}
|
|
|
|
void game_loop_one_iteration(void) {
|
|
profiler_log_thread5_time(THREAD5_START);
|
|
|
|
gGameTickCounter++;
|
|
|
|
// if any controllers are plugged in, start read the data for when
|
|
// read_controller_inputs is called later.
|
|
if (gControllerBits) {
|
|
//block_until_rumble_pak_free();
|
|
osContStartReadData(&gSIEventMesgQueue);
|
|
}
|
|
|
|
audio_game_loop_tick();
|
|
config_gfx_pool();
|
|
read_controller_inputs();
|
|
levelCommandAddr = level_script_execute(levelCommandAddr);
|
|
display_and_vsync();
|
|
|
|
// when debug info is enabled, print the "BUF %d" information.
|
|
if (gShowDebugText) {
|
|
// subtract the end of the gfx pool with the display list to obtain the
|
|
// amount of free space remaining.
|
|
print_text_fmt_int(180, 20, "BUF %d", gGfxPoolEnd - (u8 *) gDisplayListHead);
|
|
}
|
|
|
|
// custom coop hooks
|
|
rng_position_update();
|
|
}
|