mirror of https://github.com/ctnlaring/sm64
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
5.8 KiB
163 lines
5.8 KiB
/*
|
|
* This enhancement allows you to record gameplay demos for the mario head screen.
|
|
*
|
|
* Note:
|
|
* This enhancement does require the lastest versions of PJ64 from the nightly builds,
|
|
* because it uses the javascript API to automatically dump the demo files from RAM
|
|
* once the demo is completed. See RecordDemo.js for more info
|
|
*
|
|
* SETUP:
|
|
*
|
|
* First add the following above the 'thread5_game_loop' function in src/game/game.c
|
|
#include "../enhancements/record_demo/record_demo.inc.c"
|
|
*
|
|
* Then, inside thread5_game_loop(), add the recordingDemo function call RIGHT AFTER
|
|
* read_controller_inputs like so:
|
|
func_802494A8();
|
|
func_80247FAC();
|
|
read_controller_inputs();
|
|
recordingDemo();
|
|
addr = level_script_execute(addr);
|
|
display_and_vsync();
|
|
*/
|
|
|
|
#include "../src/game/mario.h"
|
|
|
|
#define DEMOREC_STATUS_NOT_RECORDING 0
|
|
#define DEMOREC_STATUS_PREPARING 1
|
|
#define DEMOREC_STATUS_RECORDING 2
|
|
#define DEMOREC_STATUS_STOPPING 3
|
|
#define DEMOREC_STATUS_DONE 4
|
|
|
|
#define DEMOREC_PRINT_X 10
|
|
#define DEMOREC_PRINT_Y 10
|
|
|
|
#define DEMOREC_DONE_DELAY 60 // Show "DONE" message for 2 seconds.
|
|
|
|
#define DEMOREC_MAX_INPUTS 1025 // Max number of recorded inputs.
|
|
|
|
/*
|
|
DO NOT REMOVE, MODIFY, OR MAKE A COPY OF THIS EXACT STRING!
|
|
This is here so that the js dump script can find the control variables easily.
|
|
*/
|
|
char gDemoRecTag[] = "DEMORECVARS";
|
|
|
|
// Control variables. It is easier if they are each 4 byte aligned, which is why they are u32.
|
|
u32 gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING;
|
|
u32 gDoneDelay = 0;
|
|
u32 gNumOfRecordedInputs = 0;
|
|
struct DemoInput gRecordedInputs[DEMOREC_MAX_INPUTS];
|
|
struct DemoInput* gRecordedInputsPtr = (struct DemoInput*)gRecordedInputs;
|
|
struct DemoInput gRecordedDemoInputCopy;
|
|
|
|
void record_new_demo_input(void) {
|
|
if(gRecordedDemoInput.timer == 1 && gRecordedDemoInputCopy.timer > 0) {
|
|
gRecordedInputs[gNumOfRecordedInputs].timer = gRecordedDemoInputCopy.timer;
|
|
gRecordedInputs[gNumOfRecordedInputs + 1].timer = 0;
|
|
gRecordedInputs[gNumOfRecordedInputs].rawStickX = gRecordedDemoInputCopy.rawStickX;
|
|
gRecordedInputs[gNumOfRecordedInputs + 1].rawStickX = gRecordedDemoInputCopy.rawStickX;
|
|
gRecordedInputs[gNumOfRecordedInputs].rawStickY = gRecordedDemoInputCopy.rawStickY;
|
|
gRecordedInputs[gNumOfRecordedInputs + 1].rawStickY = gRecordedDemoInputCopy.rawStickY;
|
|
gRecordedInputs[gNumOfRecordedInputs].button = gRecordedDemoInputCopy.button;
|
|
gRecordedInputs[gNumOfRecordedInputs + 1].button = gRecordedDemoInputCopy.button;
|
|
gNumOfRecordedInputs++;
|
|
}
|
|
}
|
|
|
|
// Self explanitory
|
|
void copy_gRecordedDemoInput(void) {
|
|
gRecordedDemoInputCopy.timer = gRecordedDemoInput.timer;
|
|
gRecordedDemoInputCopy.rawStickX = gRecordedDemoInput.rawStickX;
|
|
gRecordedDemoInputCopy.rawStickY = gRecordedDemoInput.rawStickY;
|
|
gRecordedDemoInputCopy.button = gRecordedDemoInput.button;
|
|
}
|
|
|
|
// Runs when the demo is recording.
|
|
void recording(void) {
|
|
|
|
// Force-stop when someone makes too many inputs.
|
|
if(gNumOfRecordedInputs + 1 > DEMOREC_MAX_INPUTS) {
|
|
gRecordingStatus = DEMOREC_STATUS_STOPPING;
|
|
return;
|
|
}
|
|
|
|
copy_gRecordedDemoInput();
|
|
record_demo(); // Defined in game.c
|
|
record_new_demo_input();
|
|
}
|
|
|
|
// Makes sure the last demo input is zeroed out, to make it look more clean.
|
|
void record_cleanup(void) {
|
|
gRecordedInputs[gNumOfRecordedInputs].timer = 0;
|
|
gRecordedInputs[gNumOfRecordedInputs].rawStickX = 0;
|
|
gRecordedInputs[gNumOfRecordedInputs].rawStickY = 0;
|
|
gRecordedInputs[gNumOfRecordedInputs].button = 0;
|
|
|
|
// Make sure the done delay is reset before moving to DONE status.
|
|
gDoneDelay = 0;
|
|
}
|
|
|
|
void record_run(void) {
|
|
switch(gRecordingStatus) {
|
|
case DEMOREC_STATUS_NOT_RECORDING:
|
|
break;
|
|
case DEMOREC_STATUS_PREPARING:
|
|
if(gMarioObject != NULL && gCurrLevelNum >= 5) { // If the game is in an active level
|
|
gRecordingStatus = DEMOREC_STATUS_RECORDING;
|
|
|
|
// A bit of a hack, but it works.
|
|
gNumOfRecordedInputs = 1;
|
|
gRecordedInputs[0].timer = gCurrLevelNum;
|
|
gRecordedInputs[0].rawStickX = 0;
|
|
gRecordedInputs[0].rawStickY = 0;
|
|
gRecordedInputs[0].button = 0;
|
|
}
|
|
break;
|
|
case DEMOREC_STATUS_RECORDING:
|
|
recording();
|
|
break;
|
|
case DEMOREC_STATUS_DONE:
|
|
if(gDoneDelay > DEMOREC_DONE_DELAY)
|
|
gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING;
|
|
else
|
|
gDoneDelay++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Prints the status on the bottom-left side of the screen in colorful text.
|
|
void print_status(void) {
|
|
switch(gRecordingStatus) {
|
|
case DEMOREC_STATUS_PREPARING:
|
|
print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "READY");
|
|
break;
|
|
case DEMOREC_STATUS_RECORDING:
|
|
print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "REC");
|
|
break;
|
|
case DEMOREC_STATUS_STOPPING:
|
|
print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "WAIT");
|
|
break;
|
|
case DEMOREC_STATUS_DONE:
|
|
print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "DONE");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Main function that should be called from thread5_game_loop()
|
|
void recordingDemo(void) {
|
|
// Mario needs to enter directly into a level and not from a warp,
|
|
// so the debug level select is used for that.
|
|
gDebugLevelSelect = TRUE;
|
|
|
|
if(gPlayer1Controller->buttonPressed & L_TRIG) {
|
|
if(gRecordingStatus == DEMOREC_STATUS_NOT_RECORDING) {
|
|
gRecordingStatus = DEMOREC_STATUS_PREPARING;
|
|
} else if (gRecordingStatus == DEMOREC_STATUS_RECORDING) {
|
|
gRecordingStatus = DEMOREC_STATUS_STOPPING;
|
|
record_cleanup();
|
|
}
|
|
}
|
|
|
|
record_run();
|
|
print_status();
|
|
}
|