Name Typ Von
Little Achievements Definition Einzelkämpfer

Dieses Skript speichert Bedingungen in einer externen Datei und lädt sie wieder. So lässt sich ein einfaches lokales Achievement-System bauen, dessen Zustände unabhängig von einem bestimmten Spielstand erhalten bleiben.

Achievements gelten nur auf dem Gerät, auf dem sie erreicht wurden, und lassen sich nicht übertragen (zumindest habe ich versucht, Schummeln für normale Nutzer schwer genug zu machen). Veröffentlichst du dein Spiel auf einer Plattform wie Steam oder GOG, nutze besser deren ausgereifte Achievement-Systeme.


Anleitung

  1. Füge das Hauptskript im Skripteditor von Visionaire Studio hinzu und lege es als Definitionsskript fest.
  2. Lege im Editor Bedingungen für die Achievements mit dem Anfangswert false an (keine kombinierten Bedingungen). Ihre Namen müssen im ganzen Projekt eindeutig sein.
  3. Trage die Namen dieser Bedingungen in die Tabelle achvm_conditions des Skripts ein.
  4. Ändere den geheimen Schlüssel.
  5. Rufe "save_achievements()" auf, sobald sich im Spiel eine der Achievement-Bedingungen ändert.
  6. Rufe "load_achievements()" nach dem Laden eines Spielstands auf. Füge dazu auch das GameLoaded-Skript als Definitionsskript hinzu.
    Du musst dafür kein eigenes Definitionsskript anlegen, du kannst den Code auch ins Hauptskript schreiben. Vielleicht willst du den Event-Handler "engineEvent" aber auch für andere Funktionen nutzen, deshalb habe ich ihn getrennt gehalten. Hast du diesen Event-Handler in deinem Projekt schon, ergänze dort einfach die Funktion "load_achievements()".


Willst du die Achievements statt in einer eigenen Datei in der config.ini speichern, aktiviere den INI-Modus. Das Lesen und Schreiben der config.ini musst du dann allerdings mit einem eigenen Skript erledigen.

  1. Rufe "get_achievements()" in deinem config.ini-Skript auf und schreibe den zurückgegebenen String in die INI-Datei. Schreibe die config.ini jedes Mal neu, wenn sich eine Achievement-Bedingung ändert.
  2. Übergib den aus der config.ini gelesenen Achievement-String an die Funktion "set_achievements()". Lies die config.ini beim Spielstart und nach dem Laden eines Spielstands.


Hauptskript

--[[

Little Achievements
-------------------
Script for saving and loading condition states to/from an external file. It allows implementation of a basic local achievement system where condition states are available independent from a particular game session. Achievements are only valid on the device they were obtained and cannot be shared (at least I tried to make cheating difficult enough for the ordinary user). If you release your game on a proper game platform like Steam or GOG, you better make use of their advanced achievement systems. Enable the INI mode, if you want to save the achievements in the config.ini file instead of in a separate file. You would have to handle config.ini writing and reading through your own script though.

Author:       Einzelkämpfer
Version:      1.2
Date:         2023-05-21
Requirements: Visionaire Studio 5.2 or higher
License:      MIT License (details at the bottom of the script)

How to use (A: Regular mode / B: INI mode):
 1. Add your achievement conditions to the "achvm_conditions" table below and change the "secret_key".
A2. Call "save_achievements()" whenever you change one of the achievement conditions in your game.
A3. Call "load_achievements()" after loading a savegame (see the "GameLoaded" script).
B2. Call "get_achievements()" from your config.ini script and write the string you get to the .ini file.
    Rewrite the config.ini whenever you change one of the achievement conditions in your game.
B3. Pass the achievement string read from the config.ini to the "set_achievements()" function.
    Read the config.ini at game start and after loading a savegame.

ATTENTION: THIS VERSION OF THE SCRIPT HAS ONLY BEEN TESTED ON A SINGLE WINDOWS 10 MACHINE SO FAR, SO IT MAY WORK OR NOT WORK AS INTENDED ON OTHER OPERATING SYSTEMS.
]]



-------------------------------
-- DEFINE YOUR SETTINGS HERE --
-------------------------------

-- Add all your achievement conditions to this table. The conditions have to be set up in the editor with an
-- initial value of false (no combined conditions). Make sure their names are unique throughout the project.
local achvm_conditions = {
  "achievement_condition_1",
  "achievement_condition_2",
  "achievement_condition_3"
}

-- Enter a random string of any length
local secret_key = "gKEA82cajZS5Z8vd"

-- You may change the filename to store the achievements
local achvm_file = "achvm.dat"

-- If you would like to save the achievements in the config.ini instead of in a separate file,
-- set the INI mode to true. In that case you have to read and write the config.ini by yourself
-- and use the set_achievements() and get_achievements() functions in your config.ini script.
local ini_mode = false



--------------------------------------------------------
-- USUALLY NO NEED TO CHANGE ANYTHING BELOW THIS LINE --
--------------------------------------------------------

local achvm_hashed_conds = {}


-- Regular mode: Call this function after loading a game
function load_achievements()
  if not ini_mode then
    local read_file = io.open(localAppDir .. achvm_file, "r")
    local saved_achvm = {}
  
    if read_file then
      for line in read_file:lines() do
        line = string.gsub(line, '\r', '')
        saved_achvm[line] = line
      end

      read_file:close()
    
      for k,v in pairs(achvm_hashed_conds) do
        if(saved_achvm[k] ~= nil) then
          Conditions[v].Value = true
        else
          Conditions[v].Value = false
        end
      end
    end
  end
end


-- Regular mode: Call this function after you changed one of the achievement conditions
function save_achievements()
  if not ini_mode then
    local write_file = io.open(localAppDir .. achvm_file, "w")

    if write_file then
      for k,v in pairs(achvm_hashed_conds) do
        if(Conditions[v].Value) then
          write_file:write(k .. "\n")
        end
      end
    
      write_file:close()
    end
  end
end


-- INI mode: Pass the achievements string from the config.ini file to this function to set the achievement conditions accordingly
function set_achievements(ini_str)
  if ini_mode then
    ini_str = string.lower(ini_str)
  
    if string.sub(ini_str, 1, 15) == "achievements = " then
      local achvm_str = string.sub(ini_str, 16)
      local saved_achvm = {}
    
      if achvm_str ~= "" then
        for i in string.gmatch(achvm_str, "([^|]+)") do
          saved_achvm[i] = i
          print(i)
        end
      end
  
      for k,v in pairs(achvm_hashed_conds) do
        if(saved_achvm[k] ~= nil) then
          Conditions[v].Value = true
        else
          Conditions[v].Value = false
        end
      end
    end
  end
end


-- INI mode: Call this function to get the achievements string to save in the config.ini file
function get_achievements()
  if ini_mode then
    local get_achvm = {}
  
    for k,v in pairs(achvm_hashed_conds) do
      if(Conditions[v].Value) then
        table.insert(get_achvm, k)
      end
    end

    if get_achvm ~= nil then
      return "# Little achievements\nAchievements = " .. table.concat(get_achvm, "|")
    end
  
    return ""
  end
end


-- Try to create a unique hash to avoid cheating
function obfuscate_cond(cond)
  local system_info = system.systemInfo()
  local string = secret_key

  if system_info.cpuModel ~= nil then string = string .. system_info.cpuModel end
  if system_info.gpu ~= nil then string = string .. system_info.gpu end
  if system_info.os ~= nil then string = string .. system_info.os end
  if system_info.ram ~= nil then string = string .. system_info.ram end
  string = string .. localAppDir .. cond

  return sha1(string)
end


-- Initialize the achievements
function achievements_init()
  for k,v in pairs(achvm_conditions) do
    achvm_hashed_conds[ obfuscate_cond(v) ] = v
  end
end


achievements_init()

if not ini_mode then
  load_achievements()
end



--[[

MIT License

Copyright 2023 Einzelkämpfer

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

]]


GameLoaded-Skript

-- Gets executed right after loading a savegame
function onEngineEvent(event, path) 
  if event == "LoadingSavegameSuccess" then 
    load_achievements()
  end 
end 

registerEventHandler("engineEvent", "onEngineEvent")


Ressourcen

Name Beschreibung
Kleine Erfolge 1.2.zip Ein funktionierendes Beispiel für das Skript. Für die enthaltenen .ved-Dateien brauchst du Visionaire Studio 5.2+.