Persistence
Everything you store in script fields (_rewardGiven, _gangMembers) vanishes into thin air when you close GTA V or press the reload key. If you want a gang reputation score, bought property, or custom vehicle garage to survive between play sessions, you must write that state to disk.
The great news: there is no GTA native for saving data. You use standard, battle-tested C# and .NET: System.IO and System.Text.Json.
The data contract: Keep it pure
Define a dedicated class or struct to represent your saved state:
public sealed class PlayerProfile
{
public const int CurrentVersion = 1;
public int Version { get; set; } = CurrentVersion;
public int Reputation { get; set; }
public int MissionsCompleted { get; set; }
}
Notice the Version property. When you update your mod next month and add five new fields, checking Version == CurrentVersion tells you whether the file on disk is compatible or needs migration.
Writing JSON to disk
Saving is a two-step process: serialize the object to a JSON string, then write it to a file inside your mod's folder:
using System.IO;
using System.Text.Json;
private const string SavePath = "scripts/my_mod/profile.json";
private static void SaveProfile(PlayerProfile profile)
{
try
{
string directory = Path.GetDirectoryName(SavePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string json = JsonSerializer.Serialize(profile, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SavePath, json);
}
catch (Exception ex)
{
// Never crash the game on save failure: log and continue
File.AppendAllText("scripts/my_mod/error.log", $"Save failed: {ex.Message}\n");
}
}
Reading and surviving corrupt data
Reading is where bugs happen: the user edits the JSON by hand and breaks a comma, an antivirus locks the file, or the file does not exist yet because it is the player's first time playing.
Always wrap file deserialization in defensive error handling:
private static PlayerProfile LoadProfile()
{
try
{
// Case 1: First launch -> return default profile cleanly
if (!File.Exists(SavePath))
{
return new PlayerProfile();
}
string json = File.ReadAllText(SavePath);
PlayerProfile loaded = JsonSerializer.Deserialize<PlayerProfile>(json);
// Case 2: Incompatible version -> discard or migrate
if (loaded == null || loaded.Version != PlayerProfile.CurrentVersion)
{
return new PlayerProfile();
}
return loaded;
}
catch (Exception ex)
{
// Case 3: Corrupted file -> fallback to default without crashing
File.AppendAllText("scripts/my_mod/error.log", $"Corrupted save recovered: {ex.Message}\n");
return new PlayerProfile();
}
}
Notice that LoadProfile never throws an exception out into the game loop. If the file is missing, corrupted, or unreadable, it returns a clean default profile, logs the incident, and lets the player keep playing.
In Project 08, you build a complete persistent faction reputation system.