record_demo.patch (6804B)
1 diff --git a/src/game/game_init.c b/src/game/game_init.c 2 index b961ca52..adfde049 100644 3 --- a/src/game/game_init.c 4 +++ b/src/game/game_init.c 5 @@ -11,6 +11,7 @@ 6 #include "game_init.h" 7 #include "main.h" 8 #include "memory.h" 9 +#include "object_list_processor.h" 10 #include "profiler.h" 11 #include "save_file.h" 12 #include "seq_ids.h" 13 @@ -386,6 +387,45 @@ void display_and_vsync(void) { 14 // Controls 15 // ---------------------------------------------------------------------------------------------------- 16 17 +/* 18 + * This enhancement allows you to record gameplay demos for the mario head screen. 19 + * 20 + * Note: 21 + * This enhancement does require the lastest versions of PJ64 from the nightly builds, 22 + * because it uses the javascript API to automatically dump the demo files from RAM 23 + * once the demo is completed. See enhancements/RecordDemo.js for more info 24 + * 25 +*/ 26 + 27 +#include "mario.h" 28 + 29 +#define DEMOREC_STATUS_NOT_RECORDING 0 30 +#define DEMOREC_STATUS_PREPARING 1 31 +#define DEMOREC_STATUS_RECORDING 2 32 +#define DEMOREC_STATUS_STOPPING 3 33 +#define DEMOREC_STATUS_DONE 4 34 + 35 +#define DEMOREC_PRINT_X 10 36 +#define DEMOREC_PRINT_Y 10 37 + 38 +#define DEMOREC_DONE_DELAY 60 // Show "DONE" message for 2 seconds. 39 + 40 +#define DEMOREC_MAX_INPUTS 1025 // Max number of recorded inputs. 41 + 42 +/* 43 + DO NOT REMOVE, MODIFY, OR MAKE A COPY OF THIS EXACT STRING! 44 + This is here so that the js dump script can find the control variables easily. 45 +*/ 46 +char gDemoRecTag[] = "DEMORECVARS"; 47 + 48 +// Control variables. It is easier if they are each 4 byte aligned, which is why they are u32. 49 +u32 gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING; 50 +u32 gDoneDelay = 0; 51 +u32 gNumOfRecordedInputs = 0; 52 +struct DemoInput gRecordedInputs[DEMOREC_MAX_INPUTS]; 53 +struct DemoInput* gRecordedInputsPtr = (struct DemoInput*)gRecordedInputs; 54 +struct DemoInput gRecordedDemoInputCopy; 55 + 56 /** 57 * This function records distinct inputs over a 255-frame interval to RAM locations and was likely 58 * used to record the demo sequences seen in the final game. This function is unused. 59 @@ -420,6 +460,118 @@ UNUSED static void record_demo(void) { 60 gRecordedDemoInput.timer++; 61 } 62 63 +void record_new_demo_input(void) { 64 + if (gRecordedDemoInput.timer == 1 && gRecordedDemoInputCopy.timer > 0) { 65 + gRecordedInputs[gNumOfRecordedInputs].timer = gRecordedDemoInputCopy.timer; 66 + gRecordedInputs[gNumOfRecordedInputs + 1].timer = 0; 67 + gRecordedInputs[gNumOfRecordedInputs].rawStickX = gRecordedDemoInputCopy.rawStickX; 68 + gRecordedInputs[gNumOfRecordedInputs + 1].rawStickX = gRecordedDemoInputCopy.rawStickX; 69 + gRecordedInputs[gNumOfRecordedInputs].rawStickY = gRecordedDemoInputCopy.rawStickY; 70 + gRecordedInputs[gNumOfRecordedInputs + 1].rawStickY = gRecordedDemoInputCopy.rawStickY; 71 + gRecordedInputs[gNumOfRecordedInputs].buttonMask = gRecordedDemoInputCopy.buttonMask; 72 + gRecordedInputs[gNumOfRecordedInputs + 1].buttonMask = gRecordedDemoInputCopy.buttonMask; 73 + gNumOfRecordedInputs++; 74 + } 75 +} 76 + 77 +// Self explanitory 78 +void copy_gRecordedDemoInput(void) { 79 + gRecordedDemoInputCopy.timer = gRecordedDemoInput.timer; 80 + gRecordedDemoInputCopy.rawStickX = gRecordedDemoInput.rawStickX; 81 + gRecordedDemoInputCopy.rawStickY = gRecordedDemoInput.rawStickY; 82 + gRecordedDemoInputCopy.buttonMask = gRecordedDemoInput.buttonMask; 83 +} 84 + 85 +// Runs when the demo is recording. 86 +void recording(void) { 87 + 88 + // Force-stop when someone makes too many inputs. 89 + if (gNumOfRecordedInputs + 1 > DEMOREC_MAX_INPUTS) { 90 + gRecordingStatus = DEMOREC_STATUS_STOPPING; 91 + return; 92 + } 93 + 94 + copy_gRecordedDemoInput(); 95 + record_demo(); 96 + record_new_demo_input(); 97 +} 98 + 99 +// Makes sure the last demo input is zeroed out, to make it look more clean. 100 +void record_cleanup(void) { 101 + gRecordedInputs[gNumOfRecordedInputs].timer = 0; 102 + gRecordedInputs[gNumOfRecordedInputs].rawStickX = 0; 103 + gRecordedInputs[gNumOfRecordedInputs].rawStickY = 0; 104 + gRecordedInputs[gNumOfRecordedInputs].buttonMask = 0; 105 + 106 + // Make sure the done delay is reset before moving to DONE status. 107 + gDoneDelay = 0; 108 +} 109 + 110 +void record_run(void) { 111 + switch(gRecordingStatus) { 112 + case DEMOREC_STATUS_NOT_RECORDING: 113 + break; 114 + case DEMOREC_STATUS_PREPARING: 115 + if (gMarioObject != NULL && gCurrLevelNum != LEVEL_NONE) { // If the game is in an active level 116 + gRecordingStatus = DEMOREC_STATUS_RECORDING; 117 + 118 + // First 4 values in demo inputs are used to define level ID 119 + gNumOfRecordedInputs = 1; 120 + gRecordedInputs[0].timer = gCurrLevelNum; 121 + gRecordedInputs[0].rawStickX = 0; 122 + gRecordedInputs[0].rawStickY = 0; 123 + gRecordedInputs[0].buttonMask = 0; 124 + } 125 + break; 126 + case DEMOREC_STATUS_RECORDING: 127 + recording(); 128 + break; 129 + case DEMOREC_STATUS_DONE: 130 + if (gDoneDelay > DEMOREC_DONE_DELAY) 131 + gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING; 132 + else 133 + gDoneDelay++; 134 + break; 135 + } 136 +} 137 + 138 +// Prints the status on the bottom-left side of the screen in colorful text. 139 +void print_status(void) { 140 + switch(gRecordingStatus) { 141 + case DEMOREC_STATUS_PREPARING: 142 + print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "READY"); 143 + break; 144 + case DEMOREC_STATUS_RECORDING: 145 + print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "REC"); 146 + break; 147 + case DEMOREC_STATUS_STOPPING: 148 + print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "WAIT"); 149 + break; 150 + case DEMOREC_STATUS_DONE: 151 + print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "DONE"); 152 + break; 153 + } 154 +} 155 + 156 +// Main function that should be called from thread5_game_loop() 157 +void recordingDemo(void) { 158 + // Mario needs to enter directly into a level and not from a warp, 159 + // so the debug level select is used for that. 160 + gDebugLevelSelect = TRUE; 161 + 162 + if (gPlayer1Controller->buttonPressed & L_TRIG) { 163 + if (gRecordingStatus == DEMOREC_STATUS_NOT_RECORDING) { 164 + gRecordingStatus = DEMOREC_STATUS_PREPARING; 165 + } else if (gRecordingStatus == DEMOREC_STATUS_RECORDING) { 166 + gRecordingStatus = DEMOREC_STATUS_STOPPING; 167 + record_cleanup(); 168 + } 169 + } 170 + 171 + record_run(); 172 + print_status(); 173 +} 174 + 175 /** 176 * Take the updated controller struct and calculate the new x, y, and distance floats. 177 */ 178 @@ -684,6 +836,7 @@ void thread5_game_loop(UNUSED void *arg) { 179 audio_game_loop_tick(); 180 select_gfx_pool(); 181 read_controller_inputs(); 182 + recordingDemo(); 183 addr = level_script_execute(addr); 184 185 display_and_vsync();