Hi. I created simple save/load system for my project and since there currently aren't any other save/load systems in GameGuru, I'll share it here in case somebody needs one.
Name: Save and Load game script
Developer: Jussi Palomäki a.k.a. Moshroom. (Parts of the script are based on the default scripts of GameGuru.)
License: Edit freely, use in any project (non-commercial or commercial) with GameGuru. Name me as script creator if you find this useful.
The script creates simple save prompt that let's the player save his/her progress to up to 10 slots. The moshroom_savepoint script creates a .dat file that stores player health, player lives and Counter table with 100 variables ("Counters"). These can be loaded with moshroom_loadpoint script.
Counters are used to save entity data. There are currently three scripts that are compatible with Counters: moshroom_ai_keymaster, moshroom_remotedoor and moshroom_health. Keymaster is a character that opens the remote door when player first talks to him/her. Health is based on default health script and the only difference between this and the original is that this adds the Counter table.
The best way to use my script is to create one large map with save- and loadpoints at the center and lots of unlockable features that open new sections of the map.
Setting up the save and load points
1) Create an entity with Static Mode boolean set to NO and ai system (script) set to moshroom_loadpoint.lua.
2) Repeat step 1 with moshroom_savepoint.lua. This script introduces the Counter table so you probably should set the Always active boolean to YES if you have large map (at least I think... not 100 % sure about this).
Setting the save/load system compatible entities
3) Create an entity with Static Mode boolean set to NO and ai system (script) set to moshroom_ai_keymaster, moshroom_remotedoor or moshroom_health
4) Set the name of your keymaster AND remotedoor entities to a number between 1 and 100 (one number per one pair). This links them together and to Counter table, which allows the save/load system to store whether player has talked to keymaster to trigger unlocking of the door
5) Set the name of your health item to a number between 1 and 100 (different than any keymaster/remotedoor pair). This links it to Counter table which allows the save/load system to store whether the item has been picked up or not.
6) Make sure there is only one keymaster/remotedoor pair or health item per each Counter (in other words, make sure that every keymaster/remotedoor pair and health item has different numbers as a name).
There are some limitations that are due to lack of certain LUA command in the current GameGuru version. Please read carefully:
- data can be only saved from entities that use the Counter table
- player position can't be loaded
- weapon data can't be saved nor loaded
- everything happens in single map
A workaround is probably possible for at least the last three of these limitations, but it requires game-specific manual editing of the script. Unless new LUA commands are added to GameGuru, these will probably never work out-of-box. It's left to be seen whether new LUA commands will be available before the official save/load system (which is currenlty at top at the feature vote).
Download:
http://www.univelka.net/games/moshroom_saveload.zip (inclues all 5 lua:s mentioned here and very simple example map)
Videos:
https://www.youtube.com/watch?v=3dZ5FEKMLw0 (savepoint, loadpoint, ai_keymaster and remotedoor)
https://www.youtube.com/watch?v=jgWWIvc7Vm4 (health)
Code:
moshroom_savepoint.lua (version 2.0, 11.11.2015)
-- LUA Script - precede every function and global member with lowercase name of script
-- Moshroom's save game script, vesrion 2.0
-- This script creates classical style save prompt that let's the player save
-- his/her process to up to 10 slots.
-- The data that gets saved includes player health and lives, position and
-- direction, Counter tables which are used to save data from compatible
-- entities and RPG Module variables and tables.
-- Currently the player position and direction can not be loaded because the
-- neccessary LUA commands don't exist in current version of GameGuru.
-- Version 2.0
-- File structure was updated with all the RPG Module parts now fully functional
-- Define flags to communicate with other RPG scripts
FLAG_SAVELOAD = 1
-- Define the constants
SAVE_DISTANCE = 100
NUM_INV = 14
NUM_COUNTERS = 999
-- Define tables for inventory
InvItem = {}
InvWeapon = {}
InvArmor = {}
InvAccessory = {}
-- Define tables to store entity data
Counter = {}
DCounter = {}
HP = {}
EntityX = {}
EntityY = {}
EntityZ = {}
-- Define savePhase as table to allow multiple save points
savePhase = {}
-- init when level first runs
function moshroom_savepoint_init(e)
-- Init the clock (Parts of RPG Menu - ignore if there is no menu on the map)
TimeSecond = 0
TimeMinute = 0
TimeHour = 0
-- Init the options (Parts of RPG Menu - ignore if there is no menu on the map)
CursorPosition = 1
TextSize = 1
FrontPageText = 1
MenuActive = 0
ShopActive = 0
-- Init the RPG stats
g_RPG_Level = 1
g_RPG_EXP = 0
g_RPG_SpellPower = 100
g_RPG_Vitality = 100
g_RPG_Agility = 100
g_RPG_Sight = 100
g_RPG_State = 1
g_RPG_Gold = 0
-- Init the equipment
g_Eq_Weapon = 0
g_Eq_Armor = 0
g_Eq_Accessory = 0
-- Init the save/load system
activeSlot = 0
savePhase[e] = 0
end
function moshroom_savepoint_main(e)
PlayerDist = GetPlayerDistance(e)
-- Check if player is near enough to interact
if PlayerDist < SAVE_DISTANCE and MenuActive == 0 and ShopActive == 0 then
-- Phase 0 = Activate save mode
if savePhase[e] == 0 then
Prompt("Press E to save.")
-- If E is pressed, activate first phase
if g_KeyPressE == 1 then
savePhase[e] = 1
end
end
-- Phase 1 = Choose slot
if savePhase[e] == 1 then
-- Choose saving slot
Prompt("Choose slot by pressing 0 - 9.")
if g_InKey == "1" then
activeSlot = 1
savePhase[e] = 2
elseif g_InKey == "2" then
activeSlot = 2
savePhase[e] = 2
elseif g_InKey == "3" then
activeSlot = 3
savePhase[e] = 2
elseif g_InKey == "4" then
activeSlot = 4
savePhase[e] = 2
elseif g_InKey == "5" then
activeSlot = 5
savePhase[e] = 2
elseif g_InKey == "6" then
activeSlot = 6
savePhase[e] = 2
elseif g_InKey == "7" then
activeSlot = 7
savePhase[e] = 2
elseif g_InKey == "8" then
activeSlot = 8
savePhase[e] = 2
elseif g_InKey == "9" then
activeSlot = 9
savePhase[e] = 2
elseif g_InKey == "0" then
activeSlot = 0
savePhase[e] = 2
end
end
-- Phase 2 = Open file and write
if savePhase[e] == 2 then
-- Create local variable file and link it to slotX.dat
local file = io.open("slot" .. activeSlot .. ".dat", "w")
-- Write player data and custom variables to file
file:write(
TimeHour .. "\n" ..
TimeMinute .. "\n" ..
TimeSecond .. "\n" ..
g_RPG_Gold .. "\n" ..
g_RPG_Level .. "\n" ..
g_RPG_EXP .. "\n" ..
g_RPG_SpellPower .. "\n" ..
g_RPG_Vitality .. "\n" ..
g_RPG_Agility .. "\n" ..
g_RPG_Sight .. "\n" ..
g_RPG_State .. "\n" ..
g_PlayerHealth .. "\n" ..
g_PlayerLives .. "\n" ..
g_PlayerPosX .. "\n" ..
g_PlayerPosY .. "\n" ..
g_PlayerPosZ .. "\n" ..
g_PlayerAngX .. "\n" ..
g_PlayerAngY .. "\n" ..
g_PlayerAngZ .. "\n" ..
g_Eq_Weapon .. "\n" ..
g_Eq_Armor .. "\n" ..
g_Eq_Accessory .. "\n" ..
TextSize .. "\n" ..
CursorPosition .. "\n" ..
FrontPageText .. "\n")
-- Write the inventory tables to file
for i = 1,NUM_INV do
if InvItem[i] ~= nil then
file:write(InvItem[i] .. "\n")
else
file:write("0\n")
end
end
for i = 1,NUM_INV do
if InvWeapon[i] ~= nil then
file:write(InvWeapon[i] .. "\n")
else
file:write("0\n")
end
end
for i = 1,NUM_INV do
if InvArmor[i] ~= nil then
file:write(InvArmor[i] .. "\n")
else
file:write("0\n")
end
end
for i = 1,NUM_INV do
if InvAccessory[i] ~= nil then
file:write(InvAccessory[i] .. "\n")
else
file:write("0\n")
end
end
-- Write the Counter table to file
for i = 1,NUM_COUNTERS do
if Counter[i] ~= nil then
file:write(Counter[i] .. "\n")
else
file:write("0\n")
end
end
-- Write the DCounter table to file
for i = 1,NUM_COUNTERS do
if DCounter[i] ~= nil then
file:write(DCounter[i] .. "\n")
else
file:write("0\n")
end
end
-- Write the HP table to file
for i = 1,NUM_COUNTERS do
if HP[i] ~= nil then
file:write(HP[i] .. "\n")
else
file:write("0\n")
end
end
-- Write the entity X coordinates to file
for i = 1,NUM_COUNTERS do
if g_Entity[i] ~= nil then
file:write(g_Entity[i]['x'] .. "\n")
else
file:write("0\n")
end
end
-- Write the entity Y coordinates to file
for i = 1,NUM_COUNTERS do
if g_Entity[i] ~= nil then
file:write(g_Entity[i]['y'] .. "\n")
else
file:write("0\n")
end
end
-- Write the entity Z coordinates to file
for i = 1,NUM_COUNTERS do
if g_Entity[i] ~= nil then
file:write(g_Entity[i]['z'] .. "\n")
else
file:write("0\n")
end
end
-- Save file to disk
file:close()
-- Prompt when ready
Prompt("Game saved successfully to slot " .. activeSlot .. ".")
end
-- If player is too far away, abort save mode
else
savePhase[e] = 0
end
end
moshroom_loadpoint.lua (version 2.0, 11.11.2015)
-- LUA Script - precede every function and global member with lowercase name of script
-- Moshroom's load game script, version 2.0
-- This script loads game data saved with Moshroom's save game script. Please see
-- m_savepoint.lua for more details.
-- Currently the player position and direction can not be loaded because the
-- neccessary LUA commands don't exist in current version of GameGuru.
-- Define flags to communicate with other RPG scripts
FLAG_SAVELOAD = 1
-- Define the constants
LOAD_DISTANCE = 100
NUM_INV = 14
NUM_COUNTERS = 999
-- Define tables for inventory
InvItem = {}
InvWeapon = {}
InvArmor = {}
InvAccessory = {}
-- Define tables to store entity data
Counter = {}
DCounter = {}
HP = {}
EntityX = {}
EntityY = {}
EntityZ = {}
-- Define loadPhase as table to allow multiple load points
loadPhase = {}
-- Init when level first runs
function moshroom_loadpoint_init(e)
activeSlot = 0
loadPhase[e] = 0
MenuActive = 0
ShopActive = 0
end
function moshroom_loadpoint_main(e)
PlayerDist = GetPlayerDistance(e)
-- Check if player is near enough to interact
if PlayerDist < LOAD_DISTANCE and MenuActive == 0 and ShopActive == 0 then
-- Phase 0 = Activate load mode
if loadPhase[e] == 0 then
Prompt("Press E to load.")
-- If E is pressed, activate first phase
if g_KeyPressE == 1 then
loadPhase[e] = 1
end
end
-- Phase 1 = Choose slot
if loadPhase[e] == 1 then
-- Choose loading slot
Prompt("Choose slot by pressing 0 - 9.")
if g_InKey == "1" then
activeSlot = 1
loadPhase[e] = 2
elseif g_InKey == "2" then
activeSlot = 2
loadPhase[e] = 2
elseif g_InKey == "3" then
activeSlot = 3
loadPhase[e] = 2
elseif g_InKey == "4" then
activeSlot = 4
loadPhase[e] = 2
elseif g_InKey == "5" then
activeSlot = 5
loadPhase[e] = 2
elseif g_InKey == "6" then
activeSlot = 6
loadPhase[e] = 2
elseif g_InKey == "7" then
activeSlot = 7
loadPhase[e] = 2
elseif g_InKey == "8" then
activeSlot = 8
loadPhase[e] = 2
elseif g_InKey == "9" then
activeSlot = 9
loadPhase[e] = 2
elseif g_InKey == "0" then
activeSlot = 0
loadPhase[e] = 2
end
end
-- Phase 2 = Open file and read
if loadPhase[e] == 2 then
-- Create local variable file and link it to slotX.dat
local file = io.open("slot" .. activeSlot .. ".dat", "r")
-- If slotx.dat doesn't exist, return nil
if file == nil then
Prompt("No saved game in slot " .. activeSlot .. ".")
-- If slotx.dat does exist, load previous data from file
else
-- Load and set the clock
TimeHour = file:read("*n", "*l")
TimeMinute = file:read("*n", "*l")
TimeSecond = file:read("*n", "*l")
-- Load and set the RPG variables
g_RPG_Gold = file:read("*n", "*l")
g_RPG_Level = file:read("*n", "*l")
g_RPG_EXP = file:read("*n", "*l")
g_RPG_SpellPower = file:read("*n", "*l")
g_RPG_Vitality = file:read("*n", "*l")
g_RPG_Agility = file:read("*n", "*l")
g_RPG_Sight = file:read("*n", "*l")
g_RPG_State = file:read("*n", "*l")
-- Load player health and lives
local fHealth = file:read("*n", "*l")
local fLives = file:read("*n", "*l")
-- Load player position and angle (these can't be used in current GameGuru version beause the neccessary LUA commands don't exist)
-- Just read the data and move on
local fPosX = file:read("*n", "*l")
local fPosY = file:read("*n", "*l")
local fPosZ = file:read("*n", "*l")
local fAngX = file:read("*n", "*l")
local fAngY = file:read("*n", "*l")
local fAngZ = file:read("*n", "*l")
-- Load and set player equipment
g_Eq_Weapon = file:read("*n", "*l")
g_Eq_Armor = file:read("*n", "*l")
g_Eq_Accessory = file:read("*n", "*l")
-- Load and set the RPG menu options
TextSize = file:read("*n", "*l")
CursorPosition = file:read("*n", "*l")
FrontPageText = file:read("*n", "*l")
-- Load and set the inventory
for i = 1,NUM_INV do
InvItem[i] = file:read("*n", "*l")
end
for i = 1,NUM_INV do
InvWeapon[i] = file:read("*n", "*l")
end
for i = 1,NUM_INV do
InvArmor[i] = file:read("*n", "*l")
end
for i = 1,NUM_INV do
InvAccessory[i] = file:read("*n", "*l")
end
-- Load and set counters
for i = 1,NUM_COUNTERS do
Counter[i] = file:read("*n", "*l")
end
for i = 1,NUM_COUNTERS do
DCounter[i] = file:read("*n", "*l")
end
for i = 1,NUM_COUNTERS do
HP[i] = file:read("*n", "*l")
end
for i = 1,NUM_COUNTERS do
EntityX[i] = file:read("*n", "*l")
end
for i = 1,NUM_COUNTERS do
EntityY[i] = file:read("*n", "*l")
end
for i = 1,NUM_COUNTERS do
EntityZ[i] = file:read("*n", "*l")
end
-- Set GameGuru specific player data : Basic stats
SetPlayerHealth(fHealth)
SetPlayerLives(e, fLives)
-- Set GameGuru specific palyer data : RPG Module
SetPlayerPower(e, g_RPG_SpellPower)
-- Reset Inventory to prevent crash
for i = 1,NUM_INV do
if InvItem[i] == 0 then
InvItem[i] = nil
end
end
-- For some reason this causes a weird glitch, so I'll leave it out for now
-- Might have something to do with g_Entity[i]['y'] not updating...
-- Set entity positions
-- for i = 1,NUM_COUNTERS do
-- if EntityX[i] ~= 0 then
-- ResetPosition(i,EntityX[i],EntityY[i],EntityZ[i])
-- end
-- end
-- When ready, prompt
Prompt("Game loaded succesfully from slot " .. activeSlot .. ".")
end
end
-- If player is too far away, abort load mode
else
loadPhase[e] = 0
end
end
moshroom_keymaster.lua
-- LUA Script - precede every function and global member with lowercase name of script
-- The keymaster character unlocks moshroom_remotedoor. The meeting with the
-- keymaster is stored to Counter[EID], the value which is saved to a file when
-- Moshroom's save script is used and if the player loads a saved game, the door
-- stays unlocked.
-- When the player first talks to this character the Counter[EID] is defined.
-- When the conversation is over Counter[EID] gets value 1, which makes
-- the moshroom_remotedoor passable.
-- Define table EID to store ID:s of different keymaster entities
EID = {}
-- Define tables storyLine and textTime to control the speech of different keymaster entities
storyLine = {}
textTime = {}
-- Init when level first runs
function moshroom_ai_keymaster_init_name(e, name)
EID[e] = name
EID[e] = tonumber(EID[e])
storyLine.e = 0
textTime.e = 0
end
function moshroom_ai_keymaster_main(e)
PlayerDist = GetPlayerDistance(e)
-- Check if player is near enough to interact
if PlayerDist < 75 then
-- If E is pressed, initiate or move forward with the conversation
if g_KeyPressE == 1 and GetTimer(e)>textTime[e] then
textTime[e] = GetTimer(e) + 1000
storyLine[e] = storyLine[e] + 1
end
-- Variable storyLine controls the phase of the conversation
-- storyLine 0 = Ask player to interact
if storyLine[e] == 0 then
Prompt("Press E to talk.")
end
-- storyLine 1 = The character talks his first line
if storyLine[e] == 1 then
RotateToPlayer(e)
-- Use Counter[EID] to check whether player has already talked to this character
-- If (and only if) Counter[EID] returns nil, initiate it
if Counter[EID[e]] == nil then
Counter[EID[e]] = 0
end
-- If Counter[EID] equals 0, prompt the first line
if Counter[EID[e]] == 0 then
Prompt("Hello. I'm the keymaster.")
end
-- If Counter[EID] equals 1, prompt different line
if Counter[EID[e]] == 1 then
Prompt("We'll meet again.")
end
end
-- storyLine 2 = The keymaster opens the locked door
if storyLine[e] == 2 then
RotateToPlayer(e)
Prompt("I've opened the locked door for you.")
-- Change Counter[EID] to 1 -> Open the door
Counter[EID[e]] = 1
end
-- If player moves away, end the conversation
else
storyLine[e] = 0
textTime[e] = 0
end
end
moshroom_remotedoor.lua
-- LUA Script - precede every function and global member with lowercase name of script + '_main'
-- Door Prompts 'Closed' can be opened with entity collected specified by 'USE KEY'
-- This door is operated by the keymaster character through global variable
-- Counter[EID] the value which is saved to a file when Moshroom's save script is
-- used and if the player loads a saved game, the door stays unlocked.
-- Based on deafult door.lua script.
-- state to ensure user must release E key before can open/close again
door_pressed = 0
-- Define table EID to store ID:s of different remotedoor entities
EID = {}
function moshroom_remotedoor_init_name(e, name)
-- Read the name of the entity and store it to the table EID
EID[e] = name
EID[e] = tonumber(EID[e])
end
function moshroom_remotedoor_main(e)
PlayerDist = GetPlayerDistance(e)
if PlayerDist < 75 and g_PlayerHealth > 0 then
GetEntityPlayerVisibility(e)
if 1 then
-- If the door is locked
if g_Entity[e]['activated'] == 0 then
-- If and only if Counter[EID] is nil, define it now
if Counter[EID[e]] == nil then
Counter[EID[e]] = 0
end
-- If player has not met keymaster
if Counter[EID[e]] == 0 then
Prompt("The door is locked. Ask keymaster to unlock it.")
end
-- If player has met keymaster
if Counter[EID[e]] == 1 then
SetActivated(e,1)
end
else
-- Check whether player has loaded saved game after opening the door
-- If the door hasn't been unlocked in the loaded game, keep it locked
if Counter[EID[e]] == 0 then
SetActivated(e,0)
end
-- If the door is not locked
if g_Entity[e]['activated'] == 1 then
-- Door is unlocked and closed
if g_Entity[e]['plrvisible'] == 1 then
Prompt("Press E to open door")
if g_KeyPressE == 1 and g_Entity[e]['animating'] == 0 and door_pressed == 0 then
SetAnimation(0)
PlayAnimation(e)
g_Entity[e]['animating'] = 1
SetActivated(e,2)
ActivateIfUsed(e)
PlaySound(e,0)
StartTimer(e)
door_pressed = 1
end
end
else
if g_Entity[e]['activated'] == 2 then
-- Door collision off after 1 second
if GetTimer(e)>1000 then
CollisionOff(e)
end
-- Door is open
if g_Entity[e]['plrvisible'] == 1 then
if g_KeyPressE == 1 and g_Entity[e]['animating'] == 0 and door_pressed == 0 then
SetAnimation(1)
PlayAnimation(e)
g_Entity[e]['animating'] = 1
SetActivated(e,1)
PlaySound(e,1)
CollisionOn(e)
door_pressed = 1
end
end
end
end
end
end
end
-- If player walks away, close the door
if PlayerDist > 200 and g_Entity[e]['activated'] == 2 then
SetAnimation(1)
PlayAnimation(e)
SetActivated(e,1)
CollisionOn(e)
door_pressed = 1
end
if g_KeyPressE == 0 then
door_pressed = 0
end
--PromptLocal ( e, "activated=" .. g_Entity[e]['activated'] .. "PlayerDist=" .. PlayerDist .. " plrvisible=" .. g_Entity[e]['plrvisible'] .. " haskey=" .. g_Entity[e]['haskey'] )
end
moshroom_health.lua (version 2.0, saves "Healing Potion" to inventory, if inventory is available, 12.11.2015)
-- LUA Script - precede every function and global member with lowercase name of script + '_main'
-- Player Collects Health
-- Define table for inventory
InvItem = {}
DCounter = {}
function moshroom_health_init(e)
end
function moshroom_health_main(e)
-- Main processing
PlayerDist = GetPlayerDistance(e)
-- If (and only if) Counter[e] returns nil, initiate it
if DCounter[e] == nil then
DCounter[e] = 0
end
if PlayerDist < 80 and g_PlayerHealth > 0 and DCounter[e] == 0 then
PlaySound(e,0)
if FLAG_MENU == nil then
-- If there is no menu, use the item right away
PromptDuration("Collected health",3000)
AddPlayerHealth(e)
else
-- If there is menu, save the item to the inventory as "Healing Potion"
PromptDuration("Picked up " .. Item[1] .. "!",3000)
if InvItem[1] == nil then
InvItem[1] = 1
else
InvItem[1] = InvItem[1] + 1
end
end
ActivateIfUsed(e)
DCounter[e] = 1
end
if DCounter[e] == 1 then
Hide(e)
end
if DCounter[e] == 0 then
Show(e)
end
end
moshroom_magic.lua (version 2.0, saves "Magic Source" to inventory, if inventory is available, 12.11.2015)
-- LUA Script - precede every function and global member with lowercase name of script + '_main'
-- Define flags to communicate with other RPG scripts
FLAG_MAGIC = 1
-- Define table for inventory
InvItem = {}
DCounter = {}
function moshroom_magic_init(e)
g_RPG_SpellPower = 100
end
function moshroom_magic_main(e)
-- Main processing
PlayerDist = GetPlayerDistance(e)
-- If (and only if) Counter[e] returns nil, initiate it
if DCounter[e] == nil then
DCounter[e] = 0
end
if PlayerDist < 80 and g_PlayerHealth > 0 and DCounter[e] == 0 then
PlaySound(e,0)
if FLAG_MENU == nil then
-- If there is no menu, use the item right away
PromptDuration("Collected magic",3000)
g_RPG_SpellPower = g_RPG_SpellPower + g_Entity[e]['health']
SetPlayerPower(e,g_RPG_SpellPower)
else
-- If there is menu, save the item to the inventory as "Power Source"
PromptDuration("Picked up " .. Item[5] .. !",3000)
if InvItem[5] == nil then
InvItem[5] = 1
else
InvItem[5] = InvItem[5] + 1
end
end
ActivateIfUsed(e)
DCounter[e] = 1
end
if DCounter[e] == 1 then
Hide(e)
end
if DCounter[e] == 0 then
Show(e)
end
end