Log In  

I'm in the middle of making a game (top-down adventure game) and I want to add checkpoints, so when you die your progress isn't lost. Previously, if you died I just reloaded the cart and it set you back to the beginning. I figured out how to use reload() to reset the map data, and I also keep track of the player's spawnpoint x and y position, and set them back there when resetting to a checkpoint. However, I do use mset() to change the map at times, and reload() resets all that progress. Which is sometimes ideal, because I want to undo things the player did after they saved at the checkpoint, but I want to keep the progress from before the checkpoint. Is there a way I can store the map data when the player reaches a checkpoint, and then load that data when they die? I know cstore() exists but it confuses me and I'm not even sure it's the correct thing to use in this situation.

P#99526 2021-11-03 00:08

You have 2 MB to work with. A table or string is fine, as long as you can find a way of storing the data that you find easy enough to do. I think there's several tutorials of different ways that have different levels of compression, in case you need it.

That said, for your specific case it depends on how much specific work you feel like doing. The quickest way with the least code would be to store the whole map into a table of tables to form a 2d grid, just using mget() and mset():

function save_map_state()
  t = {}
  for y=1,64 do  -- or whatever height you're using
    t[y] = {}
    for x=1,128 do -- or whatever width you're using
      t[y][x] = mget(x, y)
    end
  end
  return t
end

function load_map_state(t)
  for y=1,64 do
    for x=1,128 do
      mset(x,y,t[y][x])
    end
  end
end

However, that's among the least memory efficient ways to do it. An alternative, if you don't mind going back through and adding code where ever progress is set, is to just store how the map has changed. For example, if a tile with the index 12 at x,y position (4, 30) was changed to 16, this could work:

function store_change(t, x, y, pt, nt)
  add(t, {x,y,pt,nt})
end
function apply_changes(t)
  --note: this assumes each tile is only changed once
  for _,v in pairs(t) do
    mset(t[1],t[2],t[4])
  end
end
function revert_changes(t)
  for _,v in pairs(t) do
    mset(t[1],t[2],t[3])
  end
end
curr_progress = {}
store_changes(curr_progress,4,30,12,16)

That would allow you to re-apply the map changes when needed as well as reset the map without reloading if needed.

Regarding cstore(), that's really more for developing complex cartridges from what I can tell. If you don't mind requiring the game to played locally (as in, not on this website or through splore's access of this website), you can store saved games in separate cartridge files. That might be overkill, though, if your game isn't extremely long and complicated.

If you do want to allow saved games, you could use cartdata(), dget() and dset(), instead. That only allows 256 bytes (or 64 numbers if not doing anything fancy) to be saved though, so you'd need to be efficient. They're used like this:

-- reserve a spot in the player's save location by name
cartdata("unique_name_no_one_else_thought_of")
-- load data
spot0number = dget(0)
-- game happens
-- save data
dset(0, number_for_spot_0)

If using that, you'd probably be best off handling the setting of map changes manually, based on the values in the save data. That can be down with just a whole bunch of if/then statements. However, if you only need map changes, a table could be used as well:

cartdata("unique_name_no_one_else_thought_of")
map_changes = {
-- some example data using string indices this time to be easier to read
  {x=2, y=5, tile=12},
  {x=5, y=30, tile=16},
}
for i=1,#map_changes do
  if (dget(i) > 0) then
    mset(map_changes[i].x, map_changes[i].y, map_changes[i].tile)
  end
end

I hope at least one of those fits the needs of your game.

P#99542 2021-11-03 09:11

I feel that the three different techniques explained here may be confusing to the asker.
The right one to use depends on how exactly checkpoints should work:

  • Checkpoints only during one playthrough, but always restart from scratch when running the game: use the first technique, meaning change map with mset when collecting things, use a table to keep inventory, use a table and some code to register when a checkpoint is reached, respawn there on death without reloading the map.

  • Checkpoints are saved if you quit and run the game: use cartdata/dset/dget to save current position and inventory. items will respawn when you run, given that the mset changes are not persisted.

  • Can only play the game once: use cstore to permanently remove items from the map.
P#99557 2021-11-03 17:12

CSTORE is not that complex to use, @ooooggll and @kimiyoribaka. I've used it a few times now. The first was to prove it could be done saving 4096-bytes of data.

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

The latest was 8192 bytes in a paint program. Now I'm determined to go to write code that can load and save 65536 bytes of data, my next project.

P#99562 2021-11-03 18:33

does cstore work with the html-versions (like here in the forum) of pico8? Can be a big disadvantage.

For non-resistent savestate (lost on power down):

  • The "copy to lua-table methode", see post above
  • Copy in Map to other memory-location
    look here: https://pico-8.fandom.com/wiki/Memory
    It depends a little bit how big the map is. When you use the "shared" part for Sprites, it should fit the the Userdata-Memory 0x4300-0x5dff, use memcpy to transfer the data.
    When you use the shared for map-data, you can activate the high-memory (0x8000-0xffff) with "poke(0x5f36,16)" and use this part. Note: This is "undocumented" at the moment, but it is planed, that this high-memory will always useable.

for resistent game-Saves.
As mention above use the cartdata()-function to activate the permantent storage. You can then store datas with dset() and read it with dget(). But you have only 64 32bit-values. Alternativ you can store the datas in 0x5e00-0x5eff direct with poke/peek - so you can store 256 Bytes (values from 0-255).
The Problem: how to save the complete map there? Answer: You can't. But you can do other things. For Example Checkpoints. Often old games allows save only on certain positions. This way you can control what you want to save. You need a good gamedesign. For example a "final puzzle", where you get a key and can open a door. After this door you can set the Savepoint. When the player has reached the Savepoint, he must solved the puzzle. So you know what he must changed to reached this point. So when you load you can simple change the map. Or more simple, prevent had the player can go back - then you don't must change the map at all.
You must think about, how to save things in only 256 Bytes. There is no perfect solution for this problem.

A different method would be to use cstore/reload - there is a "cart-filename-parameter" - so you can store in a other "cart" - up to 32 carts are possible. Disadvantege would be, that you can't use html-play.

P#99565 2021-11-03 19:03
1

Hi, @GPI.

CSTORE works with ONLINE webpages including Lexaloffle, export to HTML, export to itch.io, compile to EXE for offline, the Pico-8 system itself, SPLORE, and P8, an APK for cellphones and androids that runs Pico-8 programs offline via their .PNG.

You can see why I have a vested interest in it.

P#99567 2021-11-03 19:06 ( Edited 2021-11-03 19:09)

cstore() on different cart too?

because cstore on the same cart would erase the original data - so you can't play it twice....

P#99568 2021-11-03 19:09

CSTORE only overwrites your actual cart if you don't give it a name. CSTORE does allow you to save to a unique filename, @GPI. It's up to you to not get your filenames mixed up. Mind you, you can share names and I believe you can share one data file between two programs. Let me see if this is the case ...

Hmm ... Yes it does !

Interesting. So yes, two programs can share the same data file if you know the name of it. Which of course could be saved from the first program, and loaded by the next. Two separate programs in all.

I tested this by shutting down the entire Pico-8 system and bringing it back up. It's verified. Two or more programs can read the same data file. That's better than CARTDATA().

For verification of this you can try out my silly paint program which does actually load and save every single 128x128 pixel on the screen, 8192-bytes, and does not in any way affect the sprites, mapper, sounds, or music.

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

P#99569 2021-11-03 19:10 ( Edited 2021-11-03 19:42)

Thanks @kimiyoribaka, I think I'm gonna use the first option since it makes the most sense to me lol.

P#99578 2021-11-03 21:25

@kimiyoribaka

It's a bit late to reply to this now, but I just found a small bug with your code:

function save_map_state()
  t = {}
  for y=1,64 do  -- or whatever height you're using
    t[y] = {}
    for x=1,128 do -- or whatever width you're using
      t[y][x] = mget(x, y)
    end
  end
  return t
end

function load_map_state(t)
  for y=1,64 do
    for x=1,128 do
      mset(x,y,t[y][x])
    end
  end
end

The problem is that the map goes from tile (0, 0) to tile (127, 63) instead of from (1, 1) to (128, 64). I've been trying a lot of things in my game to try to figure out what was happening and I finally figured it out. It was pretty easy to fix luckily enough.

P#108158 2022-03-06 22:14

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-28 10:39:28 | 0.055s | Q:23