Encourage level restarts on very_old_saves (#2518)

* Encourage level restarts on very_old_saves

Changes this UIInformation to a UIConfirmDIalog for old saves.

* Add overhauled save version logs

* Add Dutch, Czech, French, Spanish translation

---

Co-authored-by: jansakos
Co-authored-by: Alberth <alberth289346@gmail.com>
Co-authored-by: Antoine Lemaire <2025537+AntoineLemaire@users.noreply.github.com>
Co-authored-by: Stephen E. Baker <baker.stephen.e@gmail.com>
This commit is contained in:
lewri
2025-06-22 01:09:02 +01:00
committed by GitHub
parent 07468879e0
commit 6f073f6205
8 changed files with 166 additions and 79 deletions

View File

@@ -96,7 +96,7 @@ function App:init()
print("")
print("---------------------------------------------------------------")
print("")
print("Welcome to CorsixTH " .. self:getVersion() .. "!")
print("Welcome to CorsixTH " .. self:getReleaseString() .. "!")
print("")
print("---------------------------------------------------------------")
print("")
@@ -1680,60 +1680,136 @@ function App:loadLuaFolder(dir, no_results, append_to)
end
end
--! Returns the version number (name) of the local copy of the game based on
--! which save game version it is. This was added after the Beta 8
--! release, which is why the checks prior to that version aren't made.
--!param version An optional value if you want to find what game version
-- a specific savegame version is from.
function App:getVersion(version)
local ver = version or self.savegame_version
--[[
CorsixTH versioning follows a convention similar to semantic versioning but it
does not accurately follow its standard. As we are currently <1.0.0 the major
integer is not at play. Instead:
Minor - denotes an new milestone of the program that often encompassses multiple
patches that could break savegames without in-situ afterLoads
Revision - denotes a revised hotfix release of the program that patches a major
bug before the next milestone. The bug is then patched for the next milestone.
Moving forward, revisions should have a savegame_version bump of 1, as 10
savegame versions are reserved each release for patching.
All beta versions must have a savegame increment each time
Each patch note must begin with a '-'
--]]
local release_table = {
-- Format: major, minor, revision, patch (string), savegame_version
{major = 0, minor = 0, revision = 8, patch = "", version = 0}, -- Beta 8 or below
{major = 0, minor = 1, revision = 0, patch = "", version = 51},
{major = 0, minor = 10, revision = 0, patch = "", version = 53},
{major = 0, minor = 11, revision = 0, patch = "", version = 54},
{major = 0, minor = 20, revision = 0, patch = "", version = 66},
{major = 0, minor = 21, revision = 0, patch = "", version = 72},
{major = 0, minor = 30, revision = 0, patch = "", version = 78},
{major = 0, minor = 40, revision = 0, patch = "", version = 91},
{major = 0, minor = 50, revision = 0, patch = "", version = 105},
{major = 0, minor = 60, revision = 0, patch = "", version = 111},
{major = 0, minor = 61, revision = 0, patch = "", version = 122},
{major = 0, minor = 62, revision = 0, patch = "", version = 127},
{major = 0, minor = 63, revision = 0, patch = "", version = 134},
{major = 0, minor = 64, revision = 0, patch = "", version = 138},
{major = 0, minor = 65, revision = 0, patch = "", version = 156},
-- There was also 0.65.1, not differentiated by version number
{major = 0, minor = 66, revision = 0, patch = "", version = 170},
{major = 0, minor = 67, revision = 0, patch = "", version = 180},
{major = 0, minor = 68, revision = 0, patch = "", version = 194},
{major = 0, minor = 69, revision = 0, patch = "-beta-1", version = 216}
}
-- Versioning format is major.minor.revision (required) Patch (optional)
-- Old versions (<= 0.67) retain existing format
-- All patch versions should be retained in this table (due to be replaced, see PR2518)
if ver > 216 then
return "Trunk"
elseif ver > 194 then
return "v0.69.0-beta1"
elseif ver > 180 then
return "v0.68.0"
elseif ver > 170 then
return "v0.67"
elseif ver > 156 then
return "v0.66"
elseif ver > 138 then
return "v0.65"
elseif ver > 134 then
return "v0.64"
elseif ver > 127 then
return "v0.63"
elseif ver > 122 then
return "v0.62"
elseif ver > 111 then
return "v0.61"
elseif ver > 105 then
return "v0.60"
elseif ver > 91 then
return "0.50"
elseif ver > 78 then
return "0.40"
elseif ver > 72 then
return "0.30"
elseif ver > 66 then
return "0.21"
elseif ver > 54 then
return "0.20"
elseif ver > 53 then
return "0.11"
elseif ver > 51 then
return "0.10"
elseif ver > 45 then
return "0.01"
else
return "Beta 8 or earlier"
--! Retrieve the current savegame version as defined in the application.
function App:getCurrentVersion()
return self.savegame_version
end
--! Requests data regarding a given savegame version
--!param savegame_version (number) What to lookup, uses application version if blank
--!return the matching release table entry
-- If no releases match it returns the base release with the savegame version
-- inserted
function App:getReleaseData(savegame_version)
savegame_version = savegame_version or self:getCurrentVersion()
local release_data
for i = #release_table, 1, -1 do
local release = release_table[i]
if release.version == savegame_version then
release_data = release
break
elseif (release.version - savegame_version) < 0 then
-- we're not on a release version
local develop = shallow_clone(release) -- prevent recursion
develop.version = savegame_version
develop.patch = release.patch .. "-dev" .. savegame_version
release_data = develop
break
end
end
return release_data
end
--! Provides the release (or development) string for a given savegame version
--!param savegame_version (number) The version to look up
--!return A string in the format "v<major>.<minor>.<revision>[patch]"
--! For development builds, the savegame version is appended as a patch e.g. 'dev213'
function App:getReleaseString(savegame_version)
local release = self:getReleaseData(savegame_version)
local release_string = "v" .. release.major .. "." .. release.minor .. "." ..
release.revision
release_string = release_string .. release.patch
return release_string
end
--! Reports a difference between two versions based on requested methodology.
--!param version_a (number or table) The first (usually newer) version to test
--!param version_b (number or table) The second (usually older) version to test
--!param method (string) What method to compare by
--- method(release) reports the difference between the matching releases in steps,
--- revisions are not counted.
--- For development builds it will use the base release it started from when using
--- the release method
--- method(version) reports the difference between two savegame versions
--!return The step difference between release a and release b for release method
--- or The raw savegame version difference for version method
function App:compareVersions(version_a, version_b, method)
assert(type(version_a) == "table" or type(version_a) == "number",
"version_a requires savegame version or an entry from the version table to compare")
assert(type(version_b) == "table" or type(version_b) == "number",
"version_b requires savegame version or an entry from the version table to compare")
assert(method == "release" or method == "version",
"Not using a valid compare method")
if method == "release" then
local function countBackward(version_to_check)
local step = 0
for i = #release_table, 1, -1 do
local release = release_table[i]
if release.version == version_to_check then
break
elseif (release.version - version_to_check) < 0 then
-- we're not on a release version
if step == 0 then step = 1 break end -- working from current development
break
end
if release.revision == 0 and release.patch == "" then
step = step - 1
end
end
return step
end
if type(version_a) == "number" then version_a = self:getReleaseData(version_a) end
if type(version_b) == "number" then version_b = self:getReleaseData(version_b) end
return countBackward(version_a.version) - countBackward(version_b.version)
end
if method == "version" then
local a = version_a.version or version_a
local b = version_b.version or version_b
return a - b
end
end
function App:save(filename)
return SaveGameFile(filename)
end
@@ -1821,9 +1897,10 @@ function App:_checkOrFind(test_file, campaign_dir)
end
--! Restarts the current level (offers confirmation window first)
function App:restart()
--!param message (string) Optional message to the player
function App:restart(message)
assert(self.map, "Trying to restart while no map is loaded.")
self.ui:addWindow(UIConfirmDialog(self.ui, false, _S.confirmation.restart_level,
self.ui:addWindow(UIConfirmDialog(self.ui, true, message or _S.confirmation.restart_level,
--[[persistable:app_confirm_restart]] function()
self:worldExited()
local campaign_info = self.world.campaign_info
@@ -1882,9 +1959,9 @@ function App:afterLoad()
local first = self.world.original_savegame_version
-- Generate the human-readable version number (old [loaded save], new [program], first [original])
local first_version = first .. " (" .. self:getVersion(first) .. ")"
local old_version = old .. " (" .. self:getVersion(old) .. ")"
local new_version = new .. " (" .. self:getVersion() .. ")"
local first_version = first .. " (" .. self:getReleaseString(first) .. ")"
local old_version = old .. " (" .. self:getReleaseString(old) .. ")"
local new_version = new .. " (" .. self:getReleaseString(new) .. ")"
if new == old then
local msg_same = "Savegame version is %s, originally it was %s."
@@ -1900,7 +1977,7 @@ function App:afterLoad()
self.world:gameLog(msg_newer:format(old_version, new_version))
self.ui:addWindow(UIInformation(self.ui, { _S.warnings.newersave }))
end
self.world.release_version = self:getVersion()
self.world.release_version = self:getReleaseString(new)
self.world.savegame_version = new
if old < 87 then
@@ -1944,11 +2021,11 @@ function App:checkForUpdates()
-- Default language to use for the changelog if no localised version is available
local default_language = "en"
local current_version = self:getVersion()
local current_version = self:getReleaseString()
-- Only check for updates against released versions
if current_version == "Trunk" then
print("Will not check for updates since this is the Trunk version.")
if string.find(current_version, "dev") then
print("Will not check for updates since this is a development version.")
return
end
@@ -2057,7 +2134,7 @@ function App:gamelogHeader()
table.concat(comp_details, ", "), self.video:getRendererDetails())
local running = string.format("%s run with api version: %s, game version: %s, savegame version: %s\n",
compile_opts.jit or _VERSION, tostring(corsixth.require("api_version")),
self:getVersion(), tostring(SAVEGAME_VERSION))
self:getReleaseString(), tostring(SAVEGAME_VERSION))
return (compiled .. running)
end

View File

@@ -68,7 +68,8 @@ function UIMainMenu:UIMainMenu(ui)
-- Work out the menu's height, giving extra space for the version information
local line_height = 15
local top_padding = 20
local num_lines = computeSize({TheApp:isUpdateCheckDisabledByConfig(), TheApp.config.debug, TheApp:getVersion()}) -- see UIMainMenu:draw for how these are used
local release_string = TheApp:getReleaseString()
local num_lines = computeSize({TheApp:isUpdateCheckDisabledByConfig(), TheApp.config.debug, release_string}) -- see UIMainMenu:draw for how these are used
local bottom_text_height = (line_height * num_lines)
self.height = top_padding + ((menu_item_height + 10) * #menu_items) + bottom_text_height
@@ -81,7 +82,7 @@ function UIMainMenu:UIMainMenu(ui)
-- The main menu also shows the version number of the player's copy of the game.
self.label_font = TheApp.gfx:loadFontAndSpriteTable("QData", "Font01V", nil, nil, 0, 0, label_ttf_col)
self.version_number = TheApp:getVersion()
self.release_string = release_string
-- individual buttons
self.default_button_sound = "selectx.wav"
@@ -126,7 +127,7 @@ function UIMainMenu:draw(canvas, x, y)
self.label_font:draw(canvas, _S.main_menu.savegame_version .. TheApp.savegame_version, x + 5, ly, 190, 0, "right")
ly = ly - 15
end
self.label_font:draw(canvas, _S.main_menu.version .. self.version_number, x + 5, ly, 190, 0, "right")
self.label_font:draw(canvas, _S.main_menu.version .. self.release_string, x + 5, ly, 190, 0, "right")
end
function UIMainMenu:onTick()

View File

@@ -303,6 +303,8 @@ confirmation = {
return_to_blueprint = "Jste si jisti, že se chcete vrátit do režimu Návrhu?",
maximum_screen_size = "Velikost obrazovky, kterou jste zadali, je vyšší než 3000 x 2000. Na tato rozlišení lze přejít za předpokladu, že máte hardware schopný tuto hru udržet v hratelné rychlosti. Opravdu chcete pokračovat?",
remove_destroyed_room = "Opravdu chcete odebrat místnost? Bude to stát $%d.",
very_old_save = "Od doby, kdy jste začali s touto hrou, jsme již mnohokrát program aktualizovali. Chcete teď restartovat úroveň, pro jistotu, že vše správně funguje?//" ..
"Vaše uložené rozehrané hry nebudou smazány, dokud je sami nepřepíšete novým uložením.",
}
adviser = {
goals = {

View File

@@ -2582,6 +2582,7 @@ confirmation = { --spaces on the end make the text fit properly in text windows
maximum_screen_size = "De resolutie die je hebt ingevoerd is groter dan 3000x2000. Een hoge resolutie is mogelijk, mits je hardware goed genoeg is om een speelbare framerate te behalen. Weet je zeker dat je wilt doorgaan?",
replace_machine_extra_info = "De nieuwe machine zal %d vermogen hebben (momenteel %d)",
remove_destroyed_room = "Wil je de kamer voor $%d verwijderen?",
very_old_save = "Er zijn veel updates geweest sinds je met dit level bent begonnen. Om zeker te zijn dat alles werkt zoals het hoort, zou je nu opnieuw willen beginnen dit level?//Je opgeslagen spel blijft beschikbaar tenzij je het overschrijft.",
}
menu_display = {
mcga_lo_res = " LAGE RES ",

View File

@@ -799,13 +799,14 @@ confirmation = {
replace_machine_extra_info = "The new machine will have %d strength (currently %d).",
restart_mapeditor = "Are you sure you want to restart the map editor?",
quit_mapeditor = "Are you sure you want to quit the map editor?",
very_old_save = "There have been a lot of updates to the game since you started. To be sure that all features work as intended would you like to restart this level now?//" ..
"Your old save won't be deleted unless you overwrite it.",
}
information = {
custom_game = "Welcome to CorsixTH. Have fun with this custom map!",
no_custom_game_in_demo = "Sorry, but in the demo version you can't play any custom maps.",
cannot_restart = "Unfortunately this custom game was saved before the restart feature was implemented.",
very_old_save = "There have been a lot of updates to the game since you started this level. To be sure that all features work as intended please consider restarting it.",
level_lost = {
"Bummer! You failed the level. Better luck next time!",
"The reason you lost:",
@@ -1039,6 +1040,7 @@ tooltip.status = {
options_window.change_resolution = "Change resolution"
tooltip.options_window.change_resolution = "Change the window resolution to the dimensions entered on the left"
information.very_old_save = "There have been a lot of updates to the game since you started this level. To be sure that all features work as intended please consider restarting it."
cheats_window.cheats = {
toggle_infected = show_infected,

View File

@@ -1046,7 +1046,9 @@ confirmation = {
abort_edit_room = "Vous êtes actuellement en train de construire ou d'éditer une pièce. Si tous les objets requis sont placés, elle sera validée, mais sinon elle sera détruite. Continuer ?",
maximum_screen_size = "La taille de l'écran que vous avez entrée est supérieure à 3000 x 2000. Des plus hautes résolutions sont possibles, mais il faudra un meilleur matériel afin de maintenir un taux de trame jouable. Êtes-vous sûr de vouloir continuer?",
remove_destroyed_room = "Souhaitez-vous supprimer la salle pour %d $ ?",
replace_machine_extra_info = "La nouvelle machine aura une puissance de %d (actuellement %d)."
replace_machine_extra_info = "La nouvelle machine aura une puissance de %d (actuellement %d).",
very_old_save = "Il y a eu de nombreuses mises à jour du jeu depuis que vous avez commencé. Pour être sûr que toutes les fonctionnalités fonctionnent comme prévu, voudriez-vous recommencer ce niveau maintenant ?//"..
"Votre ancienne sauvegarde ne sera pas supprimée à moins que vous ne l'écrasiez."
}
-- Information dialog

View File

@@ -1212,6 +1212,8 @@ confirmation = {
music_warning = "Nota: Necesitas el archivo smpeg.dll o el equivalente para tu sistema operativo, de lo contrario no tendrás música en el juego. ¿Quieres continuar?",
remove_destroyed_room = "¿Te gustaría eliminar la habitación por $%d?",
replace_machine_extra_info = "La nueva máquina tendrá una resistencia de %d (actualmente es de %d).",
very_old_save = "Ha habido muchas actualizaciones en el juego desde que comenzaste. Para asegurarte de que todas las características funcionen como se espera, ¿te gustaría reiniciar este nivel ahora?//" ..
"Tu archivo de guardado anterior no se eliminará a menos que lo sobrescribas."
}
information = {

View File

@@ -90,7 +90,7 @@ function World:World(app, free_build_mode)
self.floating_dollars = {}
self.game_log = {} -- saves list of useful debugging information
self.savegame_version = app.savegame_version -- Savegame version number
self.release_version = app:getVersion(self.savegame_version) -- Savegame release version (e.g. 0.60), or Trunk
self.release_version = app:getReleaseString() -- Savegame release version (e.g. 0.60), or Trunk
-- Also preserve this throughout future updates.
self.original_savegame_version = app.savegame_version
@@ -2175,16 +2175,6 @@ end
--!param old (integer) The old version of the save game.
--!param new (integer) The current version of the save game format.
function World:afterLoad(old, new)
if not self.original_savegame_version then
self.original_savegame_version = old
end
-- If the original save game version is considerably lower than the current, warn the player.
-- For 2024 release, bump cutoff from 20 to 25 pending new methods in PR2518
if new - 25 > self.original_savegame_version then
self.ui:addWindow(UIInformation(self.ui, {_S.information.very_old_save}))
end
self:setUI(self.ui)
-- insert global compatibility code here
@@ -2571,9 +2561,19 @@ function World:afterLoad(old, new)
self:previousSpeed()
self.earthquake:afterLoad(old, new)
-- Savegame version housekeeping
if not self.original_savegame_version then
self.original_savegame_version = old
end
-- If the original save game version is considerably lower than the current, ask
-- the player if they want to restart the level.
if TheApp:compareVersions(new, old, "release") > 2 then
TheApp:restart(_S.confirmation.very_old_save)
end
self.savegame_version = new
self.release_version = TheApp:getVersion(new)
self.system_pause = false -- Reset flag on load
self.release_version = TheApp:getReleaseString(new)
self:setSystemPause(false) -- Reset flag on load
end
function World:playLoadedEntitySounds()