Come-back-later mechanics in Tumbleweed Ridge

Since this question was asked again recently in discord I thought I’d outline the approach used in Tumbleweed Ridge (TWR) for its come-back-later mechanics, in case it’s of help to other people.

The essence of Tumbleweed Ridge’s mechanic is to make the player feel like the world exists whether they’re playing it or not, even if when you come back to it you’re on a completely new server with different players. This is easier to do than it sounds if you know the state the player was in when they left and know how much time has elapsed since then, so you can “fast forward” time, performing any actions just the same as if if the player had stayed all that time.

Tumbleweed Ridge is built on top of the “Adventure” Starter Game. This contains things like a shop, an inventory and most importantly has a persistent save system on the User template, whereby things like the inventory can persist between sessions.

The autoSaveScript on the User template is responsible for this behaviour by sending events called onLoad (once in OnUserLogin), onSave (every few minutes and again in OnUserLogout) and onUnload (after the final save in OnUserLogout). The inventorySaveScript, in the Adventure Starter Game, uses the first two of these (onLoad and onSave) to read the inventory from save data and write it out again.

For Tumbleweed Ridge we used a new script the farmSaveScript which plugged in to all three of these events to run the following logic:

onLoad - During onLoad the user loads its farm data, finds a farm to use (trying the last one that the player saved on first, and if not selecting any of the other free farms), then calls a special function called Grow() on the farm itself, this calculates the difference between the current “real world” time - using GetUTCTime() - and the time of the last save, and updates each of the planted crops by this “time delta”. The same Grow function is called in a schedule every few seconds so the growing of crops also happens when you’re in the game, this time just called with the amount of time that the schedule waits between calls.

onSave - This writes out the state of the farm and also, importantly, writes out the UTC time that the save happened at, so that the onLoad call can work out the time delta.

onUnload - This burns down the farm (not literally) by removing all the crops and marking it as unused ready for a new user.

UTC time is a really useful feature for creating these sorts of mechanics. For example we also used it to control the “in demand” shop items and assign them a higher sell price. This is done every few minutes on a schedule by taking the real world time (remember GetUTCTime actually returns the number of seconds elapsed since the 1st January 1970) and dividing it by the amount of seconds in a day. This gives a unique number for “today”. We then seed the random number generator to that number (using math.randomseed) and then using math.random to select from the crops. Seeded randomness is really important in all sorts of procedural generation because you can perform the same sequence of calls to math.random whenever you want and on whatever server or client you want, and as long as you use the same seed value you will get the same random sequence back.

Feel free to ask any questions about this or related subjects here and I’ll do my best to help.

3 Likes

During onLoad the user loads its farm data, finds a farm to use (trying the last one that the player saved on first, and if not selecting any of the other free farms)

Could this be made into a tutorial on how this was created? Or maybe a package? Im trying to create something similar but cant get my head round the way each user can have its own unique terrain.

1 Like

Its not quite separate enough for a package but happy to talk more about that aspect. The basics of developing it were that there’s a “farm” template which was developed first, responding to clicks to plant crops, etc. Then I added a “farm manager” which stores an array of farms and tracks which ones are currently being used by a User and which ones are free. So when a user arrives (in the onLoad bit), it requests a farm from the farm manager, the farm manager returns it one of the free ones and the user then takes ownership of the farm, telling it what state to load in to. The final bit of the puzzle was making it so that a farm only responds to clicks from its “owner” User rather than from anyone.

This is roughly the steps I’d do to create something similar. The basic thing to get right is the link between the user script (in my case farmUserScript) which deals with the saving and loading and the actual terrain (farmScript in my case) which deals with what that save data actually means.

I’ve just re read the whole thing and that makes a bit more sense lol. so all the info for each user is stored in an array