Add an instant replay to your game!
(Shown here: OMEGA ZONE by @kometbomb)
New from the mind that brought you Instant 3D! and still hasn't apologized, Instant Replays!
Paste this snippet into the end of your game and be amazed at your new replay capabilities!!! Or it could fail completely! That's the mystery of it!!!
do local prev={ _update=_update, _update60=_update60, _draw=_draw, btn=btn, btnp=btnp } local bstate,pstate,addr,isplay={},{},0x8000,false local function updatebtns() for i=0,5 do pstate[i]=bstate[i] end if isplay then local mask=peek(addr) addr+=1 if(mask==0xff)run() for i=0,5 do bstate[i]=mask&(1<<i)~=0 end else local mask=0 for i=0,5 do bstate[i]=prev.btn(i) if(bstate[i]) mask|=1<<i end if addr<0x8000+0x42ff then poke(addr,mask) addr+=1 end end end local function doreplay() poke(addr,0xff) memcpy(0,0x8000,0x4300) cstore(0,0,0x4300,"mot_replay.p8") dset(63,1) run() end cartdata("replay") cartdata=function() end isplay=dget(63)==1 if not isplay then local seed=rnd(0xffff.ffff) poke4(addr,seed)addr+=4 srand(seed) menuitem(5,"replay",doreplay) else reload(0,0,0x4300,"mot_replay.p8") memcpy(0x8000,0,0x4300) reload(0,0,0x4300) local seed=peek4(addr)addr+=4 srand(seed) menuitem(5,"end replay",function()dset(63,0)run()end) end if _update then _update=function() updatebtns() prev._update() end end if _update60 then _update60=function() updatebtns() prev._update60() end end btn=function(i) return bstate[i] end btnp=function(i) return bstate[i] and not pstate[i] end _draw=function() prev._draw() camera() clip() if isplay then print("replay",103,121,8) else print(((addr-0x8000)/0x4300*100\1).."%",115,121,8) end end end |
Okay, I've exceeded by exclamation point quota.
This is a little experiment in capturing the player's input and replaying it exactly as before in order to make the game play out exactly the same way - assuming everything goes to plan.
To use, paste the snippet into the end of you cart, after the existing code. Then play the game for a minute and select "Replay" from the pause menu. The replay will continue looping until you select "End replay" from the pause menu.
Note - There are a bunch of requirements/caveats for this to actually work:
- Your game must be controlled by the player 1 gamepad only. Player 2 controls, mouse, keyboard etc is not captured.
- Pico-8 must be able to read/write to an external cart (named "mot_replay.p8"). I don't think this will work in binary/web exports.
- Your game must not use:
- Pico-8 memory above 0x8000
- Menu item slot 5
- Cart data slot 63
- The snippet will hijack your cart data and use cartdata("replay") instead
- Replays cut off after 10 minutes or so due to limited storage space.
And there's probably other ways it can fail that I haven't thought of :-). Try it and see..
A few games it seems to work with:
Phoenix by @pahammond
(Had to remove the cartdata() call from the snippet)
Ninja Cat by @cubee
(Had to remove the cartdata() call from the snippet)
Added run-length-encoding for longer replays.
do local prev={ _update=_update, _update60=_update60, _draw=_draw, btn=btn, btnp=btnp } local bstate,pstate,addr,isplay,mask,pmask,runlen={},{},0x8000,false,0,0,0 local function flushrunlen() if runlen>0 then poke(addr,runlen|0x80)addr+=1 runlen=0 end end local function updatebtns() for i=0,5 do pstate[i]=bstate[i] end if isplay then if runlen==0 then local v=peek(addr) addr+=1 if(v==0xff)run() if v&0x80~=0 then runlen=(v&0x7f)-1 else mask=v end else runlen-=1 end for i=0,5 do bstate[i]=mask&(1<<i)~=0 end else mask=0 for i=0,5 do bstate[i]=prev.btn(i) if(bstate[i]) mask|=1<<i end if addr<0x8000+0x42fe then if pmask~=mask then flushrunlen() poke(addr,mask) addr+=1 pmask=mask else runlen+=1 if(runlen>=0x7e)flushrunlen() end end end end local function doreplay() flushrunlen() poke(addr,0xff) memcpy(0,0x8000,0x4300) cstore(0,0,0x4300,"mot_replay.p8") dset(63,1) run() end local function setmenuitem() if isplay then menuitem(5,"end replay",function()dset(63,0)run()end) else menuitem(5,"replay",doreplay) end end cartdata("replay") --comment this line out if it errors cartdata=function()end isplay=dget(63)==1 if not isplay then local seed=rnd(0xffff.ffff) poke4(addr,seed)addr+=4 srand(seed) else reload(0,0,0x4300,"mot_replay.p8") memcpy(0x8000,0,0x4300) reload(0,0,0x4300) local seed=peek4(addr)addr+=4 srand(seed) end if _update then _update=function() updatebtns() prev._update() setmenuitem() end end if _update60 then _update60=function() updatebtns() prev._update60() setmenuitem() end end btn=function(i) return bstate[i] end btnp=function(i) return bstate[i] and not pstate[i] end _draw=function() prev._draw() camera() clip() if isplay then print("replay",103,121,8) else print(((addr-0x8000)/0x4300*100\1).."%",115,121,8) end end end |
Yep, I did something like this recently, @Mot. With the exception, mine does not fail, the keystrokes are compressed so you could easily record a full hour of playback, and is only 436-characters with about 45-char each for both recording and playback of keystrokes. :)
@dw817 wow, cool. Totally missed that.
Nothing new under the sun. :)
@Mot Nice, it works very well with Harold, thanks for the mention. BTW I ran your Instant 3D! routine over another game I've been working on. Worked fairly decently straight out of the box:
Love these copy & paste add-ons.
Also, well done @dw817 for getting there first. I'll have a play with your solution tomorrow.
@biovoid looks awesome.
I've made a version of the 3D snippet with pset and circfill support that you might want to try (the one in the bottom post of the thread). Should hopefully fix the flamethrower turret effect.
@Mot, hopefully not off topic, could you please add LINE() in addition to your 3D command selections. Making them FLAT, part of the 3d terrain. And making LINE2() to make them stand up like sprites.
Also make every single command, SPR(), SSPR(), MAP(), RECT(), RECTFILL(), CIRC(), CIRCFILL() OVAL(), OVALFILL(), etc. Make them all have the option of being flat against the 3D floor or standing up, the programmer can choose in their code.
@dw817, could probably add line, rect and oval/ovalfill.
Making flat versions would require quite a bit of code though - it's at 1k tokens already.
I have been thinking about writing a library of general purpose 3D routines at some stage though. No "Instant 3D" gimmick, just dedicated 3D functions you can call.
[Please log in to post a comment]