Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Crimson Wizard

#1281
Perhaps try using "late_repeatedly_execute_always" function instead.
https://adventuregamestudio.github.io/ags-manual/RepExec.html
#1282
Quote from: Snarky on Mon 03/06/2024 20:47:23Alternatively, users can write a pathfinding algorithm for polygon-based regions, and use the new WalkPath() method to apply it. This would enable e.g. overlapping walkable areas.

Oh, you meant, write a pathfinder in script and and pass array of points to a character.
Yes, that might work...
I did not add WalkPath to the actual code yet, it's in plans, but I'll amend this PR soon.
#1283
Quote from: Snarky on Mon 03/06/2024 18:57:54
Quote from: Crimson Wizard on Mon 03/06/2024 15:42:15We need a pathfinder that works with a list of geometric shapes instead.

Do we, though? Why?


How do you implement vector-based walkable areas without that?
#1284
Quote from: Snarky on Mon 03/06/2024 15:37:26Though by exposing the pathfinding API as you've just been working on, @Crimson Wizard, it becomes possible to implement vector-based walkable areas (and editing) within AGS.

But how? This is simply an "route search" interface in script, but it has exact same pathfinding implementation underneath, that is - based on painted masks.

We need a pathfinder that works with a list of geometric shapes instead.

Maybe an existing JPS implementation can be reworked into one, if it's possible to convert a geometric map into the same navigation grid. But I am not sure, personally, I've only ever had an experience with node-based pathfinders which worked with pre-built node maps. I would have to research this topic before I could have any educated opinion on this.
#1285
Quote from: Walmaker on Mon 03/06/2024 14:14:27I like the walkboxes better because with the four corners of each box being connected, it gives the engine a good idea of how a walkable area should be like at an angle, making the character's movement much smoother (even if it's movement is linked with the animation). Whereas the draw-in walkable areas in are too pixely to understand what part of the WA is a straight line or what part is at an angle, making the movement of the characters clunky when their movement is linked with their animation.

Do you mean that making diagonal areas is complicated, because character may walk in "ladder" steps instead of one straight diagonal line? I was not at first sure what kind of "boxes" are you referring to, and I never used scumm engine.


EDIT:
Whatever decision is made in regards to this, I must be honest, I doubt that such feature will be in AGS in the nearby future. We have very little people working on the program at the moment, and a list of priority tasks;  while implementing new pathfinding mode will require to make changes in number of parts in the editor and engine. Unless somebody volunteers to put a good time and effort into this, this is going to remain in "plans" for a while.
Meaning, if you are making your game in AGS right now, then you should not rely on this.
#1286
Could you post some videos for comparison, or maybe some documentation about how it worked in LA games, to let us know clearly which effect you like to achieve?

If by "jagged movement" you mean that the characters are making sharp turns, then I suppose that is not because walkable areas are painted masks, but because of how pathfinder works: it creates straight lines between points, instead of curved paths, for example. For this reason I doubt that having vector based areas alone will improve walking in any way. It would require changing pathfinder instead.

Indeed calculating curved paths may be easier with vector-based areas, but then our current pathfinder generates a navigation grid from the painted mask, which brings things somewhat closer to vector-based, internally.

Not that there's anything wrong with having vector-based areas in AGS, but these are two separate problems, in my opinion.

#1287
Editor Development / Pathfinding API in script
Sun 02/06/2024 17:55:53
I had a sudden moment of enthusiasm today, so wrote a draft for the basic Pathfinding API...
(To be fair, this is not a sudden idea, we discussed this on github a while ago.)

PR: https://github.com/adventuregamestudio/ags/pull/2441
Download for a test (based on AGS 4): https://cirrus-ci.com/task/5429664145997824
Last Updated: 14th June

The Pathfinding api lets user to use AGS internal pathfinder in script. This allows to calculate paths on the current room's walkable areas, but also lets to create a pathfinder object with a custom mask (from a sprite).

The new API is following:

Code: ags
builtin managed struct Pathfinder {
  /// Tries to find a move path from source to destination, returns an array of navigation points.
  Point*[] FindPath(int srcx, int srcy, int dstx, int dsty);
  /// Traces a straight path from source to destination, returns the furthermost point reached.
  Point* Trace(int srcx, int srcy, int dstx, int dsty);
};

builtin managed struct MaskPathfinder extends Pathfinder {
  /// Creates a new MaskPathfinder, initialized with the given 8-bit mask sprite
  static MaskPathfinder* Create(int mask_sprite); 
  /// Assigns a new mask to this MaskPathfinder
  void SetMask(int mask_sprite);
};

Pathfinder class is a parent class that provides generic methods, unrelated to how the pathfinding works. User cannot create objects of this class directly.
MaskPathfinder is a child class that implements a pathfinder based on 8-bit masks.

**IMPORTANT**: MaskPathfinder is currently disabled in script, because AGS does not let create or import 8-bit sprites in hi-color games. There's a separate task planned to resolve this problem. After that is done, MaskPathfinder may be re-enabled.

And this to the Room struct:

Code: ags
struct Room {
  <...>
  /// Gets this Room's Pathfinder object that lets find route around walkable areas.
  readonly Pathfinder *PathFinder;
}

Note that the Room returns a pointer of parent type Pathfinder, but it creates MaskPathfinder internally. This is done to restrict user from changing the room pathfinder's mask, because this pathfinder object is meant to represent actual room's walkable areas.

Then new variants of Walk and Move that accept array of Points:
Code: ags
/// Moves the character along the path, ignoring walkable areas, without playing his walking animation.
function Character.MovePath(Point*[], BlockingStyle=eNoBlock);
/// Moves the character along the path, ignoring walkable areas, automatically playing his walking animation.
function Character.WalkPath(Point*[], BlockingStyle=eNoBlock);
/// Returns the moving path of this character, or null if it's not moving
Point*[] Character.GetPath();
/// Moves the object along the path, ignoring walkable areas.
function Object.MovePath(Point*[], int speed, BlockingStyle=eNoBlock);
/// Returns the moving path of this object, or null if it's not moving
Point*[] Object.GetPath();

In these MovePath and WalkPath functions it is possible to pass a result of Pathfinder.FindPath, but also possible to just make your own path the way you please. GetPath() functions return the copies of the current path. If moving order changes, the previously returned array becomes obsolete (and not automatically updated, of course).

The working example script:
Spoiler

WARNING: player Character must be set Solid = false for this script to work! otherwise paths won't be found, because they start inside the Character's blocking area.

Code: ags
DynamicSprite *pathspr;
DynamicSprite *mask_copy;
Overlay *pathover;

function on_mouse_click(MouseButton button)
{
	if (pathover == null)
	{
		pathspr = DynamicSprite.Create(Room.Width, Room.Height);
		pathover = Overlay.CreateRoomGraphical(0, 0, pathspr.Graphic);
	}
	
	if (mask_copy == null)
	{
		mask_copy = DynamicSprite.Create(Room.Width, Room.Height);
	}
	
	DrawingSurface *mask_copy_ds = mask_copy.GetDrawingSurface();
	DrawingSurface *mask = WalkableArea.GetDrawingSurface();
	mask_copy_ds.Clear(30);
	mask_copy_ds.DrawSurface(mask);
	mask.Release();
	mask_copy_ds.Release();
  
	Point *goal = Screen.ScreenToRoomPoint(mouse.x, mouse.y);
	Pathfinder *finder = Room.PathFinder;
	Point *path[] = finder.FindPath(player.x, player.y, goal.x, goal.y);
	Point *trace = finder.Trace(player.x, player.y, goal.x, goal.y);
	
	DrawingSurface *ds = pathspr.GetDrawingSurface();
	ds.Clear(COLOR_TRANSPARENT);
	ds.DrawImage(0, 0, mask_copy.Graphic, 50);
	
	if (path != null)
	{
		for (int i = 0; i < path.Length; i++)
		{
			if (i > 0)
			{
				ds.DrawingColor = 1;
				ds.DrawLine(path[i - 1].x, path[i - 1].y, path[i].x, path[i].y);
			}
			ds.DrawingColor = 4;
			ds.DrawRectangle(path[i].x - 1, path[i].y - 1, path[i].x + 1, path[i].y + 1);
		}
	}
	
	if (trace != null)
	{
		ds.DrawingColor = 12;
		ds.DrawLine(player.x, player.y, trace.x, trace.y);
	}
	
	ds.Release();
}
[close]

Illustration of running above script:
(clicking around calculates a walk path from character to a certain point, and draws that path on screen)



Possibly we'd also need extra property or argument for the Pathfinder returned by the Room, which tells to ignore solid objects and characters, because right now they may unexpectedly "get in a way" (both literally and figuratively).
**UPDATE:** this problem is left for the future. At the moment the workaround is to manually toggle Solid property of wanted objects or characters before calling FindPath.
#1288
Quote from: Jordanowen42 on Sun 02/06/2024 00:07:44Thank you- sorry for the silly problem.

I did not mean that it is your fault that this is a silly problem, I meant that the fact that template does not have a proper save/load access is silly, and this should have been addressed a long time ago.

Quote from: Jordanowen42 on Sun 02/06/2024 00:12:05Another question- how do I implement these things without screwing up my game?

Ah, you have an existing game, sorry I misunderstood, I thought you restart making your game in a BASS template.

Hmm... first of all, Save and Load dialogs already exist in old BASS template, you may find them in game GUIs, and you may find the related script in GlobalScript. The problem is that they do not have a on-screen button to call them, and may only be called with a hotkeys: F5 and F7 respectively.

So, you have to specify what exactly do you want to change or add.

For the reference, my related changes to BASS were this:
1. Added options menu from Sierra template (the one with save/load/about/quit and audio sliders).
2. On the inventory bar I replaced up/down scroll arrows with left/right arrows and replaced a "quit" button with "menu" button, as seen here:
https://i.imgur.com/FWa57WD.png
3. Added script for these menus.

You do not have to do all of these things in your game of course, and you don't have to do it exactly the way I did. If you want only ways to call Save/Load dialogs with on screen button, you need to decide how to arrange these. This is more a design question rather than a scripting question. The very simplest change could be to squeeze Save and Load buttons on Inventory bar, but that would make inventory itself too short. (That's the reason why I added menu button instead of them there, since menu have both save and load)
#1289
Quote from: Snarky on Sat 01/06/2024 07:16:50In terms of how this would actually work, Crimson Wizard, are you thinking that players would configure it in winsetup.exe (or by editing the cfg file), and it would adjust the default color and volume values set in the editor?

Because as we know, AGS games don't come with any default in-game configuration UI.

Also, this would only work if the game developer never uses the script API to overrule the starting values set in the editor, because then they become hardcoded. My intuition is that most games do set these values in script at some point, and so this solution would have quite limited effectiveness, but I might be wrong about that.


The way I look at this, there are two different scenarios:
1. When the accessibility is implemented by the author of the game.
2. When the accessibility is not implemented by the author of the game.

For the first situation, the engine should provide at least a minimal necessary API for making this possible; and then the rest is doable with the help of script modules, and adjusted on per game basis.

For the second situation, the engine should provide a way to override certain game behavior, such as text looks, or audio controls. How this is configured is a problem, but at the very least there is config file.

I imagine that, for the fine tuning, the configuration would include a substitution table: font overrides, color overrides etc. Then, whenever engine draws a text, it would take the current game option for a font, or text color (whether it is coming from the global options or "drawtext" function arguments), and use that as a key in this table, where the overriding font or color is a return value.
This might also be not a precise color, but a rule, that sets minimal color levels, such as brightness, saturation etc.

Similar approach may be used for other things.

I would definitely throw in an ability to configure skipping options externally (text skipping, video skipping, cutscene skipping etc), because some games may simply not implement these or implement inconveniently.

EDIT:
The latest version of the engine supports several "overrides" in config (not exposed to winsetup), suggested to be used in case of emergency. For example, there's a way to set save/restore hotkeys that bring up default builtin save/restore dialogs. I introduced this after couple of games were released with the broken save/load functionality. Visually this looks out of place of course, but at least lets to save your progress in such game.
#1290
Hello.

This is a silly problem, and recently I updated both BASS and Verb Coin templates to have these.
You may find updated templates here:
https://github.com/adventuregamestudio/ags-templates/
Or their source code here:
https://github.com/adventuregamestudio/ags-template-source/

Given how often this problem comes up, I might probably include these new versions into the next upcoming 3.6.1 patch.
#1291
Strictly from the technical point of view (not considering potential design or configuration issues),

Quote from: gendiskan on Thu 30/05/2024 22:31:22* Adjustable text size, color, and opacity, including fonts for players with dyslexia.
* High contrast filters to highlight characters and interactive elements.
* Filters for major types of color blindness: Protanopia, Deuteranopia, and Tritanopia.

* Sound adjustments (mono or stereo) and volume settings for effects, music, and voices, if any.

These should be relatively straightforward to do.
Speaking of fonts, in AGS currently fonts may be replaced even by placing another font file into the game directory. That is how additional translations are done (including "unofficial" translations).
But anything that changes text size may conflict with the game elements, and I don't know if that's even possible to resolve without participation from the game developer.

Quote from: gendiskan on Thu 30/05/2024 22:31:22* Analog control, allowing players to navigate menus and interactive elements with keys or buttons.

I think this is theoretically possible, but will require implementing whole new control system within the engine from scratch, possibly with external input configuration, because keys or control buttons may be also used for certain purposes within the game (script).


Quote from: gendiskan on Thu 30/05/2024 22:31:22* Screen reader compatibility to verbalize menus, texts, and subtitles.

I have a guess that this is easier to do using an external tool, which works over the game window with final game image.
In AGS this may be complicated to do, because it does not have distinct "text element", and texts are baked onto other graphical elements. Some game objects have "text" property, but not all, so they won't be able to tell what is drawn on them. And there's also a "raw drawing" feature that lets paint text on sprites.


Quote from: gendiskan on Thu 30/05/2024 22:31:22* Compatibility with keyboard, gamepad, and Xbox Adaptive Controller to connect specific controllers for all types of motor impairments.

I suppose this is a task for the backend library we're using (which is currently SDL2).


PS.
As an additional note, I would also explore if above tasks may be accomplished by a engine plugin.
Plugins are convenient in the sense that one may experiment with them without adding maintenance burden to the core engine.
But, on the other hand, this may require expanding plugin API in the engine too.
#1292
I recall there's a way to make this automatic. If you assign a magic button text "(INVNS)", then it should display active inventory item on its own.

https://adventuregamestudio.github.io/ags-manual/Button.html#buttontext
#1293
I meant manually setting x and y position.

If it has to be a blocking move, then this may be done in a loop. If non-blocking, then in repeatedly_execute function (or repeatedly_execute_always, if necessary).

But I don't know your use case. Above solution implies you are in full control of movement path.

If, on another hand, you need to use AGS pathfinding (object moving along walkable areas), then the situation becomes more complicated, because AGS does not expose pathfinding results to script. In such case the only workaround that I know is a "ghost" object/character: order an invisible object to move, and then move your actual object relatively to the "ghost", with custom adjustments. This is in very simple terms, but depending on a practical case this may require remembering the "ghost"'s steps, for example, or more.
#1294
No, I'm afraid that for the most of these things (acceleration, moving not in straight line) you'll have to script your own movement.
I may also recommend checking out Tween module which adds interpolation functions for many game properties, and may have something suitable (but it depends on a use case):
https://www.adventuregamestudio.co.uk/forums/modules-plugins-tools/tween-2-3-0-with-ags-3-6-0-support!/

In regards to have a move speeds less than 1 pixel per game update, that is possible by passing negative speed value, where -N would mean 1 pixel per N updates.

#1295
Quote from: speksteen on Wed 29/05/2024 02:34:12Moho is vector based but it can export raster based gifs. Any idea on the maximum number of sprites per view? I tried looking it up in the manual but couldn't find it.

There's no hard limit for the number of frames per view or loop.

Having many frames may make loading an animation slower at runtime, especially if it uses high-res graphics. But idk if that's going to be a problem in your case, only tests may show.
#1296
I looked "Moho" up, and it appears to be a vector animation tool. Indeed, AGS is strictly a raster animations engine, so complex vector animations might not be directly suitable.
#1297
Do you mean that you convert gif to pngs prior to import?

I recall that AGS can import gifs alright, it splits them into separate frames though, because that's how its was historically made to work: each animation frame is a separate sprite. But it does so automatically without any additional action required from your side.

For convenience you may also create sprite folder per each animation, and import gifs there.

Views also allow to create animation loops from a sprite folder, there's a respective command in a context menu.
#1298
There are always multiple ways to code same thing, fast ways and slow ways.

Sometimes it's possible to tell whether what you do is not optimal by looking at the code, but it's not always possible to say if it's going to be slow or not in practice.

If you like to get an idea about your code, then fill your arrays with large amounts of generated dummy data (code that in script), and run performance tests.
I mean, do something like this on startup:
Code: ags
int NUM_ITEMS_TO_FILL = 1000;
for (int i = 0; i < NUM_ITEMS_TO_FILL; i++)
{
    Item[i].name="blablablabla"; // or generate a random name, there are also ways to do that   
    Item[i].sprite = Random(MAX_SPRITES);
    Item[i].data = ...
}




Speaking on the particular code that you posted above, I think you may speed this up greatly if you do not iterate your arrays at all, but instead add a reference to your array's element to InventoryItem. I mean, have an array index connected to each InventoryItem.
This may be done either using Custom Properties, or a Dictionary.
https://adventuregamestudio.github.io/ags-manual/CustomProperties.html
https://adventuregamestudio.github.io/ags-manual/Dictionary.html

Then you can do this, for example:
Code: ags
Inventoryitem*selected=inventoryitem.GetatscreenXY(mouse.x,mouse.y);
int my_index = selected.GetProperty("MyIndex");
item[my_index]....

If you  don't like to setup all of these indexes in the editor, you may as well generate them in script, on startup. That would take some time, but will happen only once, so not a big problem.
Code: ags
for (int i = 0; i < Game.InventoryItemCount; i++)
{
    InventoryItem *invitem = inventory[i];
    for (int myitem = 0; myitem < itemlimit; myitem++)
    {
        if (invitem.name==item[myitem].name){
            invitem.SetProperty("MyIndex", myitem);
            break;
        }
    }
}


Overall, when you need to have 2 objects connected, don't do that with iterating arrays, instead create "links" for a faster access.
When possible use integer indexes instead of strings, because comparing strings is slower.
If you really need to use strings, check out "Dictionary", which is made for a faster searches by a string key.

PS. I also recommend renaming this topic, because right now its title does not exactly reflect the question.

PPS. Ah, I should also note that AGS has a limit of 300 inventory items, for some reason. If you think that your game will need more of these, then you would need to think of another solution (reusing InventoryItems for multiple purposes, for example, or else).
#1299
One problem in AGS is that objects interact with regions only by a single point, their "origin", which is left-bottom corner for Room Objects and middle-bottom point for Characters. Thus an object is considered to be "on region" only if this point is on region.
This works more or less for characters (since their origin is at their horizontal center", but not much for objects.

But then, there's also no default way to apply a tint only to a part of the sprite; the tint and lighting levels are applied always to the whole sprite.

The solution in your case, possibly, could be to not use a region tint, but instead create a translucent greenish object where you want this effect be, and make sure its Baseline is set so the cars are behind.
Translucency is achieved by setting Transparency property.
https://adventuregamestudio.github.io/ags-manual/Object.html#objecttransparency
#1300
I'd like to revisit a problem of loading older saves into an updated AGS game.

For the reference, the old thread about my previous working prototype:
https://www.adventuregamestudio.co.uk/forums/engine-development/load-older-game-saves-prototype-solution/
And the more recent conversation on the issue:
https://www.adventuregamestudio.co.uk/forums/engine-development/save-system-overhaul/

The problem with saves: in case someone does not know or have forgotten

AGS "saves" are in fact "savestates", not exactly equivalent, but close to a plain game memory dump.
When the savestate is restored, engine tries to fill the read data into the game objects. Dynamically created objects don't have any issues, but objects created at design-time (we may call these "static" objects) must match in numbers and order. If number of order of these objects is different, restoration of this save will fail (if only order is different, then engine may proceed, but result will be erroneous). This refers to script data as well (global script variables).
The potential save breaking reasons and existing workarounds are explained in full detail in this article in the manual:
https://adventuregamestudio.github.io/ags-manual/GameSavesCompatibility.html

When you look at this problem, at the first glance the solution could be to use unique object identifiers (like "script names") to match read data with existing objects. But this is complicated by the following major reasons:
1. Not all "objects" have a script name or any kind of unique id.
2. Not all "objects" have their names written into a compiled game, some exist only at design time and during compilation (best example: names of script variables).
3. Strange as it is, but in many places script names are not obligatory in AGS, and may be missing.
(Also, even when present, they are not written into a save game, although that's easier to fix on its own.)

There are other major issues with the existing save system, such as the fact that it stores literally everything, even something that you won't normally expect to be saved and restored in a game. This fact makes me believe that in the long run it may not be worth to "fix" existing save system, but rather worth developing a different one, based on a new principles.

Unfortunately, I do not feel like having an energy and enthusiasm for such task today. So I leave that for other times or other developers.

But at the same time I would like to try to provide a minimal possible solution that would let users patch their published games without fear of breaking older saves, at least for the time being.
There are still workarounds for that (mentioned in the page I linked above), but these still require predicting this issue, and making preparations.

The requirements for this "minimal solution", in my opinion, are:

1. User should be able to apply this solution when upgrading an older game, published before this solution was implemented.
2. This solution should be safe to add to existing game, and removed from game, that is - toggled on and off, without affecting the game itself.
3. User should at least be able to add more items of each type, while letting older saves still load. I think it's okay to not support item removal, as unnecessary items may be detached from the use (hidden etc).
4. With this solution, user must have an opportunity to script reaction to a "old" save, and tell whether to load it or not; and be able to update the game state after load is completed as necessary.
5. If an older save is loaded and has less items of certain type in it, the remaining items must reset to their initial state, as they are at design time. If requirement 4 is implemented, then user will be able to adjust these items after load, as necessary.
6. For the requirement 4, there has to be a guaranteed way to deduce which "game version" the save was written by. It's likely that script variables cannot be used here, as the preliminary save check has to be done prior to loading any modifiable game data such as script variables. Also because of requirements 1. and 2.



There are two things that I've been thinking about adding to AGS for this purpose. These may be reviewed, implemented and used in parallel or individually (separate from each other).

1. Minimal way of telling which parts of game data to write into save or load from a save.

In the past I noticed that there are things that likely should not be a part of a game's save, but are netherless written into a save state. This may cause problems when patching a game, but also may unnecessarily increase save's size.

One good example are GUI. In most games GUI are used just as menus, so something that lies "outside" of the game's progress state.
Classic problem here, an author wants to add a new translation, and translations are selected in-game by pressing a button. New translation means new button, but that new button will break the old saves. That is stupid, especially if we note that GUI don't have to be in saves.

Views - rarely have a meaning to be saved, but since they are saved "just in case" (because some of the frame properties may be modified at runtime), this makes them contributing the older save breakage.

Another example is dynamically created assets (dynamic sprites). These take much memory, but may be recreated knowing game situation from script variables right after a save is restored, for example. So there may be a reason to not put them into saves.

My thought is to have a simple way to tell the engine which types of data to put into a save and load from existing save. Thankfully, our current save format is divided into "components", which makes it technically easier to accomplish. What is needed here is to figure out a script API. Either this should be a new function that sets "save configuration" for the further use, or an optional argument to existing SaveGameSlot / RestoreGameSlot functions. Such argument may be either a set of bit flags (numeric constants combined in one integer value), or a formatted string.

Such option might solve this problem for many of the use cases already.



2. Allow to read saves with less contents than the current game

This is the original solution that I tried to implement a number years ago. It might go roughly like this:

1. While the engine reads the save, and finds out that save has less saved objects (or less script data, etc) than the current game, it runs a script callback. In this script callback user's script must be used to pass back the resolution whether to keep loading or cancel with error.
If save has more data than the game, or if there's no callback found, or if callback returned negative answer, then the restoration is cancelled.
This way the presence of a callback in script serves a safety check, that the user expected their games to load older saves with less data.

2. If restoration is allowed, engine restarts the game, loading its initial set of data, and then restores this save. This is done so to ensure that all data which is not present in the game is reset to initial state, for safety.

There are few nuances to think through here. One is a timing of executing that script callback. It may be done immediately as a problem is detected, but if done after partially restoring a save, the whole game may be in an unstable state. So running script could not be reliable. Another option is to only find if such callback is present in the script, restore the save, but run callback after the save got restored. Since it's still technically possible to read it. This way it may be possible to also check contents of a script data in that callback.

Another nuance is the game versioning. If you read the old thread you may notice that there I implemented a whole new struct containing numbers of various game objects for the user to check. Today I doubt this is a good idea, as it may be not very convenient or reliable, so I'd rather go with a more explicit "game version".
I don't know if we should rely on user having a variable that tells this game's version. We might also add a game property for this, and write it both into a game data and saves.

SMF spam blocked by CleanTalk