Log In  

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:

Harold's Bad Day by @biovoid

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)

Super Disc Box by @Farbs

P#106415 2022-02-06 08:55 ( Edited 2022-02-07 07:38)

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
P#106425 2022-02-06 10:54 ( Edited 2022-02-06 11:30)
1

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. :)

https://www.lexaloffle.com/bbs/?tid=46214

P#106476 2022-02-07 03:56 ( Edited 2022-03-07 21:03)

@dw817 wow, cool. Totally missed that.
Nothing new under the sun. :)

P#106478 2022-02-07 05:45

@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.

P#106489 2022-02-07 11:18

@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.

P#106509 2022-02-07 19:02

@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.

P#106510 2022-02-07 19:06

@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.

P#106517 2022-02-07 22:19

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2023-01-31 03:39:10 | 0.069s | Q:24