sm64pc/enhancements/record_demo/record_demo.inc.c

164 lines
5.7 KiB
C

/*
* 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();
}