diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f86266d1..8dbfb0d12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,9 @@ set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases set(DEVELOPMENT_BUILD FALSE) +set(ENABLE_UPDATE_CHECKER (NOT ${DEVELOPMENT_BUILD}) CACHE BOOL + "Whether to enable update checks by default") + set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA}) diff --git a/README.md b/README.md index f3ffa6c57..f7c1490eb 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ General options and their default values: ENABLE_SYSTEM_JSONCPP=OFF - Use JsonCPP from system OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory) + ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default USE_GPROF=FALSE - Enable profiling using GProf VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> MultiCraft 0.4.9-foobar) ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt) diff --git a/build/android/build.gradle b/build/android/build.gradle index 8cfb99704..f47ebaad9 100644 --- a/build/android/build.gradle +++ b/build/android/build.gradle @@ -5,6 +5,7 @@ project.ext.set("versionMinor", 0) // Version Minor project.ext.set("versionPatch", 1) // Version Patch project.ext.set("versionExtra", "") // Version Extra project.ext.set("versionCode", 100) // Android Version Code +project.ext.set("developmentBuild", 0) // Whether it is a development build, or a release // NOTE: +2 after each release! // +1 for ARM and +1 for ARM64 APK's, because // each APK must have a larger `versionCode` than the previous diff --git a/build/android/native/build.gradle b/build/android/native/build.gradle index a5346be99..aeb503f5e 100644 --- a/build/android/native/build.gradle +++ b/build/android/native/build.gradle @@ -15,7 +15,8 @@ android { "versionMajor=${versionMajor}", "versionMinor=${versionMinor}", "versionPatch=${versionPatch}", - "versionExtra=${versionExtra}" + "versionExtra=${versionExtra}", + "developmentBuild=${developmentBuild}" } } } diff --git a/build/android/native/jni/Android.mk b/build/android/native/jni/Android.mk index b16d1a16e..76a112b23 100644 --- a/build/android/native/jni/Android.mk +++ b/build/android/native/jni/Android.mk @@ -87,6 +87,7 @@ LOCAL_CFLAGS += \ -DVERSION_MINOR=${versionMinor} \ -DVERSION_PATCH=${versionPatch} \ -DVERSION_EXTRA=${versionExtra} \ + -DDEVELOPMENT_BUILD=${developmentBuild} \ $(GPROF_DEF) ifdef NDEBUG diff --git a/build/macOS/MultiCraft/MultiCraft.xcodeproj/project.pbxproj b/build/macOS/MultiCraft/MultiCraft.xcodeproj/project.pbxproj index e69156db0..42b5ab5ff 100644 --- a/build/macOS/MultiCraft/MultiCraft.xcodeproj/project.pbxproj +++ b/build/macOS/MultiCraft/MultiCraft.xcodeproj/project.pbxproj @@ -2224,10 +2224,12 @@ "USE_LEVELDB=1", "USE_LUAJIT=1", "USE_SYSTEM_GMP=0", + "ENABLE_UPDATE_CHECKER=1", "VERSION_MAJOR=2", "VERSION_MINOR=0", "VERSION_PATCH=1", "VERSION_EXTRA=\"\"", + "DEVELOPMENT_BUILD=1", ); HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -2286,10 +2288,12 @@ "USE_LEVELDB=1", "USE_LUAJIT=1", "USE_SYSTEM_GMP=0", + "ENABLE_UPDATE_CHECKER=1", "VERSION_MAJOR=2", "VERSION_MINOR=0", "VERSION_PATCH=1", "VERSION_EXTRA=\"\"", + "DEVELOPMENT_BUILD=0", ); HEADER_SEARCH_PATHS = ( "$(inherited)", diff --git a/builtin/mainmenu/dlg_version_info.lua b/builtin/mainmenu/dlg_version_info.lua new file mode 100644 index 000000000..de653e5dd --- /dev/null +++ b/builtin/mainmenu/dlg_version_info.lua @@ -0,0 +1,198 @@ +--[[ +Minetest +Copyright (C) 2018-2020 SmallJoker, 2022 rubenwardy +Copyright (C) 2022 MultiCraft Development Team + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +]] + +if not core.get_http_api then + function check_new_version() + end + return +end + +local esc = core.formspec_escape +local defaulttexturedir = esc(defaulttexturedir) + +local LANG = core.settings:get("language") +if not (LANG and (LANG ~= "")) then LANG = os.getenv("LANG") end +if not (LANG and (LANG ~= "")) then LANG = "en" end + +local function version_info_formspec(data) + local changes = data.changes + + -- Hack to work around https://github.com/minetest/minetest/issues/11727 + if changes:sub(2, 2) == " " then + local idx = changes:find("\n", 3, true) or #changes + 1 + changes = "\194\160" .. changes:sub(1, idx - 1) .. "\194\160" .. + changes:sub(idx) + end + + changes = changes:gsub("\\n", "\n") + + return ([[ + style_type[image_button;content_offset=0] + image[4.9,0;2.5,2.5;%s] + image_button[1,2;10,0.8;%s;;%s;false;false] + hypertext[1.3,2.6;10,2;;
%s
] + style[version_check_remind;bgcolor=yellow] + button[2,4.5;4,0.8;version_check_remind;%s] + style[version_check_visit;bgcolor=green] + button[6,4.5;4,0.8;version_check_visit;%s] + ]]):format( + defaulttexturedir .. "logo.png", + defaulttexturedir .. "blank.png", + esc(data.title), + esc(changes), + fgettext("Cancel"), + fgettext("Update") + ) -- "Remind me later", "Update now" +end + +local function version_info_buttonhandler(this, fields) + if fields.version_check_remind then + -- Erase last known, user will be reminded again at next check + core.settings:remove("update_last_known") + this:delete() + return true + end + -- if fields.version_check_never then + -- core.settings:set("update_last_checked", "disabled") + -- this:delete() + -- return true + -- end + if fields.version_check_visit then + if type(this.data.url) == "string" then + core.open_url(this.data.url) + end + -- this:delete() + -- return true + end + + return false +end + +local function create_version_info_dlg(title, changes, url) + assert(type(title) == "string") + assert(type(changes) == "string") + assert(type(url) == "string") + + local retval = dialog_create("version_info", + version_info_formspec, + version_info_buttonhandler, + nil, true) + + retval.data.title = title + retval.data.changes = changes + retval.data.url = url + + return retval +end + +local function get_version_code(version_string) + -- Format: Major.Minor.Patch + -- Convert to MMMNNNPPP + local ver_major, ver_minor, ver_patch = version_string:match("^(%d+).(%d+).(%d+)") + + if not ver_patch then + core.log("error", "Failed to parse version numbers (invalid tag format?)") + return + end + + return (ver_major * 1000 + ver_minor) * 1000 + ver_patch +end + +local function on_version_info_received(update_info) + local maintab = ui.find_by_name("maintab") + if maintab.hidden then + -- Another dialog is open, abort. + return + end + + local known_update = tonumber(core.settings:get("update_last_known")) or 0 + + -- Parse the latest version number + local new_number = get_version_code(update_info.latest_version or "") + if not new_number then + core.log("error", "dlg_version_info.lua: Failed to read version number") + return + end + + -- Compare with the current number + local client_version = core.get_version() + local cur_number = get_version_code(client_version.string) + if not cur_number or new_number <= known_update or new_number < cur_number then + return + end + + -- Also consider updating from 1.2.3-dev to 1.2.3 + if new_number == cur_number and not client_version.is_dev then + return + end + + -- core.settings:set("update_last_known", tostring(new_number)) + + -- Show version info dialog (once) + maintab:hide() + + local url = update_info.url or "https://multicraft.world/downloads" + if not url:find("://", 1, true) then + url = "https://" .. url + end + + local version_info_dlg = create_version_info_dlg(update_info.title or "", update_info.changes or "", url) + version_info_dlg:set_parent(maintab) + version_info_dlg:show() + + ui.update() +end + +function check_new_version() + local url = core.settings:get("update_information_url") + if core.settings:get("update_last_checked") == "disabled" or + url == "" then + -- Never show any updates + return + end + + local time_now = os.time() +-- local time_checked = tonumber(core.settings:get("update_last_checked")) or 0 +-- if time_now - time_checked < 2 * 24 * 3600 then +-- -- Check interval of 2 entire days +-- return +-- end + + core.settings:set("update_last_checked", tostring(time_now)) + + url = ("%s?lang=%s&platform=%s"):format(url, LANG, PLATFORM) + + core.handle_async(function(params) + local http = core.get_http_api() + return http.fetch_sync(params) + end, { url = url }, function(result) + local json = result.succeeded and core.parse_json(result.data) + if type(json) ~= "table" then + core.log("error", "Failed to read JSON output from " .. url .. + ", status code = " .. result.code) + return + elseif not json.latest_version then + -- No platform-specific update information, don't display anything + return + end + + on_version_info_received(json) + end) +end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index fea2b506e..887eec30c 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -51,6 +51,8 @@ if not mobile then dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") end +dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua") + local tabs = {} if not mobile then @@ -148,8 +150,9 @@ function menudata.init_tabs() end ui.set_default("maintab") - tv_main:show() + check_new_version() + tv_main:show() ui.update() end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 28d55e039..89727a39d 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -998,6 +998,19 @@ client_mapblock_limit (Mapblock limit) int 7500 # Whether to show the client debug info (has the same effect as hitting F5). show_debug (Show debug info) bool false +# URL to JSON file which provides information about the newest MultiCraft release +update_information_url (Update information URL) string https://updates.multicraft.world/app.json + +# Unix timestamp (integer) of when the client last checked for an update +# Set this value to "all" to never check for updates. +update_last_checked (Last update check) string + +# Version number which was last seen during an update check. +# +# Representation: MMMIIIPPP, where M=Major, I=Minor, P=Patch +# Ex: 5.5.0 is 005005000 +update_last_known (Last known version update) int 0 + [Server / Singleplayer] # Name of the server, to be displayed when players join and in the serverlist. diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 148891a5f..575deee15 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4558,6 +4558,7 @@ Utilities * `string`: Simple version, eg, "1.2.3-dev" * `hash`: Full git version (only set if available), eg, "1.2.3-dev-01234567-dirty". + * `is_dev`: Boolean value indicating whether it's a development build Use this for informational purposes only. The information in the returned table does not represent the capabilities of the engine, nor is it reliable or verifiable. Compatible forks will have a different name and diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 778602018..e58c90711 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -15,6 +15,8 @@ #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" #define ICON_DIR "@ICONDIR@" #cmakedefine01 RUN_IN_PLACE +#cmakedefine01 DEVELOPMENT_BUILD +#cmakedefine01 ENABLE_UPDATE_CHECKER #cmakedefine01 USE_GETTEXT #cmakedefine01 USE_CURL #cmakedefine01 USE_SOUND diff --git a/src/config.h b/src/config.h index ef3aaeb6b..0bd299a1a 100644 --- a/src/config.h +++ b/src/config.h @@ -15,6 +15,7 @@ #define PROJECT_NAME "multicraft" #define PROJECT_NAME_C "MultiCraft" #define STATIC_SHAREDIR "" + #define ENABLE_UPDATE_CHECKER 1 #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) #ifdef NDEBUG #define BUILD_TYPE "Release" diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6b810b4c3..668030896 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -372,6 +372,12 @@ void set_default_settings() settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default"); #endif + settings->setDefault("update_information_url", "https://updates.multicraft.world/app.json"); + #if ENABLE_UPDATE_CHECKER + settings->setDefault("update_last_checked", ""); + #else + settings->setDefault("update_last_checked", "disabled"); + #endif // Server settings->setDefault("compat_player_model", "character.b3d,3d_armor_character.b3d,skinsdb_3d_armor_character_5.b3d"); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 962d91028..4a2eaed80 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -454,6 +454,8 @@ int ModApiUtil::l_get_version(lua_State *L) lua_setfield(L, table, "hash"); } + lua_pushboolean(L, DEVELOPMENT_BUILD); + lua_setfield(L, table, "is_dev"); return 1; } diff --git a/util/bump_version.sh b/util/bump_version.sh index 4b12935bd..eea9cb95d 100755 --- a/util/bump_version.sh +++ b/util/bump_version.sh @@ -26,6 +26,7 @@ perform_release() { sed -i -re "s/^set\(DEVELOPMENT_BUILD TRUE\)$/set(DEVELOPMENT_BUILD FALSE)/" CMakeLists.txt sed -i 's/project.ext.set("versionExtra", "-dev")/project.ext.set("versionExtra", "")/' build/android/build.gradle + sed -i 's/project.ext.set("developmentBuild", 1)/project.ext.set("developmentBuild", 0)/' android/build.gradle sed -i -re "s/\"versionCode\", [0-9]+/\"versionCode\", $NEW_ANDROID_VERSION_CODE/" build/android/build.gradle sed -i '/\