diff options
Diffstat (limited to 'ch_data')
-rw-r--r-- | ch_data/api.md | 109 | ||||
-rw-r--r-- | ch_data/init.lua | 764 | ||||
-rw-r--r-- | ch_data/license.txt | 15 | ||||
-rw-r--r-- | ch_data/mod.conf | 3 |
4 files changed, 891 insertions, 0 deletions
diff --git a/ch_data/api.md b/ch_data/api.md new file mode 100644 index 0000000..05ca9cc --- /dev/null +++ b/ch_data/api.md @@ -0,0 +1,109 @@ +# Veřejné funkce + +## Data + + ch_data.online_charinfo[player_name] + +Tabulka neperzistentních dat pro danou postavu, která je ve hře. Musí obsahovat minimálně následující položky: + +* formspec_version : int : nejvyšší verze formspec podporovaná klientem; 0, pokud údaj není k dispozici +* join_timestamp : int : časová známka vytvoření online_charinfo (vstupu postavy do hry), z get_us_time() +* lang_code : string : jazykový kód (obvykle "cs") +* news_role : string : co udělat po připojení (významy jednotlivých řetězců se mění) +* player_name : string : přihlašovací jméno + + ch_data.offline_charinfo[player_name] + +Tabulka perzistentních dat pro danou postavu. Vytváří se pro každou známou postavu. Musí obsahovat minimálně +položky z ch_data.initial_offline_charinfo. + + ch_data.initial_offline_charinfo + +Tabulka klíč/hodnota. Klíče specifikují klíče vyžadované v záznamech offline_charinfo. Hodnoty specifikují +výchozí hodnoty těchto klíčů, které se buď doplní při načtení, pokud nejsou k dispozici, nebo se doplní +při vytvoření nového záznamu offline_charinfo. Položky v této tabulce mohou být přepisovány z jiných +módů, ale tyto změny se uplatní teprve při vytváření nových záznamů offline_charinfo. + +## Funkce + + ch_data.get_flag(charinfo : table, flag_name : string, default_result : any) -> string or any + +Z daného charinfo (offline_charinfo) přečte znak odpovídající požadovanému příznaku. Není-li příznak +dostupný, vrací default_result, popř. " ". + + ch_data.get_flags(charinfo : table) -> table + +Z daného charinfo (offline_charinfo) přečte všechny příznaky a vrátí je ve formě tabulky. +Vhodné pro dumpování. + + ch_data.set_flag(charinfo, flag_name, value) -> bool + +V daném charinfo (offline_charinfo) nastaví hodnotu požadovaného příznaku. Pokud příznak neexistuje, vrátí false. + + ch_data.get_joining_online_charinfo(player) -> table + +Volá se z callbacků on_joinplayer; vrátí ch_data.online_charinfo[player_name]. Pokud není, inicializuje jej. +Může být použita víckrát po sobě. + + ch_data.get_leaving_online_charinfo(player) -> table + +Volá se z callbacků on_leaveplayer; vrátí ch_data.online_charinfo[player_name] a vyřadí ho z tabulky. +Pokud už bylo vyřazeno, vrátí poslední vyřazenou tabulku. To umožňuje pracovat s online daty postavy +po celou dobu jejího odpojování. + + ch_data.delete_offline_charinfo(player_name) -> bool, string + +Pokud postava není ve hře, smaže její ch_data.offline_charinfo[] se vším všudy a vrátí true. +Pokud postava je ve hře, nebo její offline_charinfo neexistuje, vrátí false a chybovou zprávu. + + ch_data.get_offline_charinfo(player_name) -> table + +Vrátí ch_data.offline_charinfo[player_name]. Pokud neexistuje, skončí s chybou. + + ch_data.get_or_add_offline_charinfo(player_name) -> table + +Vrátí ch_data.offline_charinfo[player_name]. Pokud neexistuje, inicializuje nové. + + ch_data.save_offline_charinfo(player_name) -> bool + +Uloží ch_data.offline_charinfo[player_name]. Vrátí false, pokud dané offline_charinfo neexistuje nebo je jméno postavy nepřijatelné. + + ch_data.clear_help(player) + +Pro danou postavu smaže ch_data.online_charinfo[player_name].navody (perzistentně). + + ch_data.should_show_help(player : PlayerRef, online_charinfo : table, item_name : string) -> table or nil + +Otestuje, zda podle online_charinfo má dané postavě být zobrazený v četu návod k položce daného názvu. +Pokud ano, nastaví příznak, aby se to znovu již nestalo, a vrátí definici daného předmětu, +z níž lze z položek description a _ch_help sestavit text k zobrazení. Jinak vrátí nil. + + ch_data.nastavit_shybani(player_name : string, shybat : bool) -> bool + +Pro danou postavu perzistentně nastaví shýbání. (Volá ch_data.save_offline_charinfo().) + +# Příkazy v četu + +## /delete_offline_charinfo + +Syntaxe: + +``/delete_offline_charinfo Jmeno_postavy`` + +Odstraní údaje o postavě uložené v systému ch_data. Postava nesmí být ve hře. Vyžaduje právo `server`. + +## /návodyznovu + +Syntaxe: + +``/návodyznovu`` + +Smaže údaje o tom, ke kterým předmětům již byly postavě zobrazeny nápovědy, takže budou znovu zobrazovány nápovědy ke všem předmětům. + +## /shýbat + +Syntaxe: + +``/shýbat <ano|ne>`` + +Trvale vypne či zapne shýbání postavy při držení Shiftu. diff --git a/ch_data/init.lua b/ch_data/init.lua new file mode 100644 index 0000000..6373461 --- /dev/null +++ b/ch_data/init.lua @@ -0,0 +1,764 @@ +ch_base.open_mod(core.get_current_modname()) + +local worldpath = core.get_worldpath() +local datapath = worldpath.."/ch_playerdata" +local playerlist_path = worldpath.."/ch_data_players" +local storage = core.get_mod_storage() +local old_online_charinfo = {} -- uchovává online_charinfo[] po odpojení postavy +local players_list, players_set = {}, {} -- seznam/množina všech známých hráčských postav (pro offline_charinfo) +local lc_to_player_name = {} -- pro jména existujících postav lowercase => loginname +local current_format_version = 2 + +ch_data = { + online_charinfo = {}, + offline_charinfo = {}, + supported_lang_codes = {cs = true, sk = true}, + -- Tato funkce může být přepsána. Rozhoduje, zda zadané jméno postavy je přijatelné. + is_acceptable_name = function(player_name) return true end, + initial_offline_charinfo = { + -- int (> 0) -- pro [ch_core/ap], udává aktuální úroveň postavy + ap_level = 1, -- musí být > 0 + -- int (> 0) -- pro [ch_core/ap], udává verzi systému AP (pro upgrade) + ap_version = 1, + -- int (>= 0) -- pro [ch_core/ap], udává celkový počet bodů aktivity postavy + ap_xp = 0, + -- int {0, 1} -- 0 = nic, 1 = předměty házet do koše + discard_drops = 0, + -- string -- pro [ch_core/teleport], pozice uložená příkazem /domů + domov = "", + -- int (>= 0) -- pro [ch_core/chat] udává aktuální doslech + doslech = 50, + -- int {0, 1} -- pro [ch_core] 0 = normální velikost, 1 = rozšířený inventář + extended_inventory = 0, + -- string -- pole příznaků + flags = "", + -- string YYYY-MM-DD nebo "" -- datum, kdy byla hráči/ce naposledy vypsána oznámení po přihlášení do hry (YYYY-MM-DD) + last_ann_shown_date = "1970-01-01", + -- int (>= 0) -- v sekundách od 1. 1. 2000 UTC; 0 značí neplatnou hodnotu + last_login = 0, + -- int {0, 1} -- 0 = shýbat se při stisku Shift; 1 = neshýbat se + neshybat = 0, + -- int {0, 1} -- 0 = krásná obloha ano, 1 ne + no_ch_sky = 0, + -- float -- v sekundách + past_ap_playtime = 0.0, + -- float -- v sekundách + past_playtime = 0.0, + -- string -- výčet dodatečných práv naplánovaných pro registraci postavy + pending_registration_privs = "", + -- string -- typ naplánované registrace postavy + pending_registration_type = "", + -- int -- pro [ch_bank] + rezim_plateb = 0, + -- int {0, 1} -- 0 => zobrazit, 1 => skrýt + skryt_body = 0, + -- int {0, 1} -- 0 => zobrazovat (výchozí), 1 => skrýt + skryt_hlad = 0, + -- int {0, 1} -- 0 => zobrazovat (výchozí), 1 => skrýt + skryt_zbyv = 0, + -- string -- pro [ch_core/teleport], pozice uložená příkazem /stavím + stavba = "", + -- string -- nastavení filtru událostí + ui_event_filter = "", + -- int (>= 1) -- číslo verze uložených dat (umožňuje upgrade) + version = current_format_version, + -- int {1, 2, 3} -- volba cíle pro /začátek: 1 => Začátek, 2 => Masarykovo náměstí, 3 => Hlavní nádraží + zacatek_kam = 1, + }, + initial_offline_playerinfo = { + -- int -- výše uloženého trestu (může být záporná) + trest = 0, + } +} + +-- POZNÁMKA: protože příznaky z následujícího pole se mapují na znaky v řetězci 'flags', nesmí se mazat ani zakomentovat! +-- Je však možno je přejmenovat při zachování pozice. +local flag_ids = { + "discard_drops", + "extended_inventory", + "neshybat", + "no_ch_sky", + "skryt_body", + "skryt_hlad", + "skryt_zbyv", +} +local flag_name_to_id = {} + +for i, flag in ipairs(flag_ids) do + local old_id = flag_name_to_id[flag] + if old_id ~= nil then + error("Flag '"..flag.."' has multiple IDs: "..old_id..", "..i.."!") + end + flag_name_to_id[flag] = i +end +ch_data.initial_offline_charinfo.flags = string.rep(" ", #flag_ids) + +local function add_player(player_name, new_offline_charinfo, player_info) + if players_set[player_name] then + return false + end + assert(new_offline_charinfo) + local lcase = string.lower(player_name) + -- add player to players_list (persistently): + table.insert(players_list, player_name) + core.safe_file_write(playerlist_path, assert(core.serialize(players_list))) + -- add player to players_set and lc_to_player_name: + players_set[player_name] = true + lc_to_player_name[lcase] = player_name + -- add new offline_charinfo: + ch_data.offline_charinfo[player_name] = new_offline_charinfo + -- add new offline_charinfo[].player: + new_offline_charinfo.player = player_info or table.copy(ch_data.initial_offline_playerinfo) + if new_offline_charinfo.player.name == nil then + new_offline_charinfo.player.name = player_name + end + return true +end + +local function delete_player(player_name) + -- check if the player exists: + if not players_set[player_name] then + return false + end + -- detach offline_playerinfo: + local offline_player_info = assert(ch_data.offline_charinfo[player_name].player) + local aliases = {} + for alias, offline_charinfo in pairs(ch_data.offline_charinfo) do + if alias ~= player_name and offline_charinfo.player.name == player_name then + offline_player_info = offline_charinfo.player + table.insert(aliases, alias) + end + end + if #aliases > 0 then + offline_player_info.name = aliases[1] + core.log("debug", "delete_player(): dotplayer of "..table.concat(aliases, ",").." corrected to "..player_info.name) + for _, alias in ipairs(aliases) do + ch_core.save_offline_playerinfo(alias) + end + end + -- remove player from players_list: + local lcase = string.lower(player_name) + for i, pname in ipairs(players_list) do + if pname == player_name then + table.remove(players_list, i) + break + end + end + -- save the player_list: + core.safe_file_write(playerlist_path, assert(core.serialize(players_list))) + -- remove player from players_set: + players_set[player_name] = nil + -- remove player from lc_to_player_name: + lc_to_player_name[lcase] = nil + -- remove player from offline_charinfo: + ch_data.offline_charinfo[player_name] = nil + return true +end + +function ch_data.get_flag(charinfo, flag_name, default_result) + local id = flag_name_to_id[flag_name] + if id ~= nil then + local result = (charinfo.flags or ""):sub(id, id) + if result ~= "" then + return result + end + end + return default_result or " " +end + +function ch_data.get_flags(charinfo) + local flags = charinfo.flags or "" + local result = {} + for id, name in ipairs(flag_ids) do + local value = flags:sub(id, id) + if value == "" then + value = " " + end + result[name] = value + end + return result +end + +function ch_data.set_flag(charinfo, flag_name, value) + local id = flag_name_to_id[flag_name] + if id == nil then + return false + end + value = (tostring(value).." "):sub(1,1) + local flags = charinfo.flags or "" + if flags:len() < id then + flags = flags..string.rep(" ", id - flags:len() - 1)..value + else + flags = flags:sub(1, id - 1)..value..flags:sub(id + 1, -1) + end + charinfo.flags = flags + return true +end + +function ch_data.correct_player_name_casing(name) + return lc_to_player_name[string.lower(name)] +end + +function ch_data.get_joining_online_charinfo(player) + assert(core.is_player(player)) + local player_name = player:get_player_name() + local result = ch_data.online_charinfo[player_name] + if result ~= nil then + return result + end + local player_info = core.get_player_information(player_name) + local now = core.get_us_time() + result = { + areas = {{ + id = 0, + name = "Český hvozd", + type = 1, + }}, + -- časová známka vytvoření online_charinfo (vstupu postavy do hry) + join_timestamp = now, + -- jazykový kód (obvykle "cs") + lang_code = player_info.lang_code or "", + -- úroveň osvětlení postavy + light_level = 0, + -- časová známka pro úroveň osvětlení postavy + light_level_timestamp = now, + -- verze protokolu + protocol_version = player_info.protocol_version or 0, + -- tabulka již zobrazených nápověd (bude deserializována níže) + navody = {}, + -- co udělat těsně po připojení + news_role = "new_player", + -- přihlašovací jméno + player_name = player_name, + } + + -- news_role: + --[[ + 5.5.x => formspec_version = 5, protocol_version = 40 + 5.6.x => formspec_version = 6, protocol_version = 41 + 5.7.x => formspec_version = 6, protocol_version = 42 + 5.8.0 => formspec_version = 7, protocol_version = 43 + 5.9.0 => formspec_version = ?, protocol_version = ? + 5.10.0 => formspec_version = 8, protocol_version = 46 + ]] + if result.protocol_version < 42 then + result.news_role = "disconnect" + elseif not ch_data.supported_lang_codes[result.lang_code] and not core.check_player_privs(player, "server") then + result.news_role = "invalid_locale" + elseif core.check_player_privs(player, "ch_registered_player") then + result.news_role = "player" + elseif not ch_data.is_acceptable_name(player_name) then + result.news_role = "invalid_name" + else + result.news_role = "new_player" + end + + ch_data.online_charinfo[player_name] = result + local prev_online_charinfo = old_online_charinfo[player_name] + if prev_online_charinfo ~= nil then + old_online_charinfo[player_name] = nil + if prev_online_charinfo.leave_timestamp ~= nil then + result.prev_leave_timestamp = prev_online_charinfo.leave_timestamp + end + end + core.log("action", "JOIN PLAYER(" .. player_name ..") at "..now.." with lang_code \""..result.lang_code.. + "\", formspec_version = "..tostring(player_info.formspec_version)..", protocol_version = ".. + result.protocol_version..", news_role = "..result.news_role..", ip_address = "..tostring(player_info.address)) + + -- deserializovat návody: + local meta = player:get_meta() + local s = meta:get_string("navody") + if s and s ~= "" then + result.navody = core.deserialize(s, true) or result.navody + end + + if result.news_role ~= "invalid_name" then + ch_data.get_or_add_offline_charinfo(player_name) + end + --[[ + TODO: + if core.is_creative_enabled(player_name) then + result.is_creative = true + if ch_core.set_immortal then + ch_core.set_immortal(player, true) + end + end + else + core.log("error", "Player object not available for "..player_name.." in get_joining_online_charinfo()!") + end + ]] + return result +end + +function ch_data.get_leaving_online_charinfo(player) + assert(core.is_player(player)) + local player_name = player:get_player_name() + local result = ch_data.online_charinfo[player_name] + if result ~= nil then + result.leave_timestamp = core.get_us_time() + old_online_charinfo[player_name] = result + ch_data.online_charinfo[player_name] = nil + return result + else + return old_online_charinfo[player_name] + end +end + +function ch_data.delete_offline_charinfo(player_name) + if ch_data.online_charinfo[player_name] ~= nil then + return false, "Postava je ve hře!" + end + if not delete_player(player_name) then + return false, "Mazání selhalo." + end + local success, errmsg = os.remove(worldpath.."/ch_playerdata/"..player_name) + if success then + return true, "Úspěšně smazáno." + else + return false, "Mazání souboru selhalo: "..(errmsg or "nil") + end +end + +function ch_data.get_offline_charinfo(player_name) + local result = ch_data.offline_charinfo[player_name] + if result == nil then + error("Offline charinfo not found for player '"..player_name.."'!") + end + return result +end + +function ch_data.get_or_add_offline_charinfo(player_name) + local result = ch_data.offline_charinfo[player_name] + if result == nil then + add_player(player_name, table.copy(ch_data.initial_offline_charinfo)) + result = assert(ch_data.offline_charinfo[player_name]) + core.log("action", "[ch_data] Offline charinfo initialized for "..player_name) + ch_data.save_offline_charinfo(player_name) + end + return result +end + +local debug_flag = false + +function ch_data.save_offline_charinfo(player_name, include_playerinfo) + if players_set[player_name] == nil then + return false + end + local data = ch_data.offline_charinfo[player_name] + if data == nil then + return false + end + core.safe_file_write(datapath.."/"..player_name, assert(core.serialize(data))) + if include_playerinfo and data.player.name ~= player_name then + local dotplayer_name = data.player.name + assert(ch_data.offline_charinfo[dotplayer_name]) + assert(ch_data.offline_charinfo[dotplayer_name].player.name == dotplayer_name) + return ch_data.save_offline_charinfo(dotplayer_name) + end + return true +end + +function ch_data.save_offline_playerinfo(player_name) + if players_set[player_name] == nil then + return false + end + local data = ch_data.offline_charinfo[player_name] + if data == nil then + return false + end + local dotplayer_name = assert(data.player.name) + assert(ch_data.offline_charinfo[dotplayer_name]) + assert(ch_data.offline_charinfo[dotplayer_name].player.name == dotplayer_name) + return ch_data.save_offline_charinfo(dotplayer_name) +end + +local function on_joinplayer(player, last_login) + ch_data.get_joining_online_charinfo(player) +end + +local function on_leaveplayer(player) + ch_data.get_leaving_online_charinfo(player) +end + +core.register_on_joinplayer(on_joinplayer) +core.register_on_leaveplayer(on_leaveplayer) + +local function upgrade_offline_charinfo(player_name, data) + local old_version = data.version + if data.version <= 1 then + data.player.trest = data.trest or 0 + data.trest = nil + end + data.version = current_format_version + core.log("info", "Offline_charinfo["..player_name.."] upgraded from version "..old_version.." to the current version "..data.version..".") + return true +end + +-- Load and initialize: + +core.mkdir(datapath) +local function initialize() + local f = io.open(playerlist_path) + if f then + local text = f:read("*a") + f:close() + if text ~= nil and text ~= "" then + local new_players = core.deserialize(text, true) + if type(new_players) == "table" then + core.log("action", "[ch_data] "..#new_players.." known players.") + players_list = new_players + players_set = {} + for _, player_name in ipairs(players_list) do + players_set[player_name] = true + end + end + end + end + for _, player_name in ipairs(players_list) do + f = io.open(datapath.."/"..player_name) + if f then + local text = f:read("*a") + f:close() + if text ~= nil and text ~= "" then + local data = core.deserialize(text, true) + if type(data) == "table" then + ch_data.offline_charinfo[player_name] = data + lc_to_player_name[string.lower(player_name)] = player_name + end + end + end + if ch_data.offline_charinfo[player_name] == nil then + core.log("error", "[ch_data] deserialization of offline_charinfo["..player_name.."] failed!") + end + end + for player_name, poc in pairs(ch_data.offline_charinfo) do + -- vivify/upgrade/correct offline_charinfo: + for key, value in pairs(ch_data.initial_offline_charinfo) do + if poc[key] == nil then + poc[key] = value + core.log("warning", "Missing offline_charinfo key "..player_name.."/"..key.." vivified.") + end + end + -- correct invalid past_playtime: + if poc.past_playtime < 0 then + core.log("warning", "Invalid past_playtime for "..player_name.." ("..poc.past_playtime..") corrected to zero!") + poc.past_playtime = 0 -- correction of invalid data + end + -- vivify .player table (including .player.name) + if poc.player == nil or poc.player.name == nil then + poc.player = {name = player_name} + end + end + -- link .player tables according to .player.name: + for player_name, poc in pairs(ch_data.offline_charinfo) do + local dotplayer_name = assert(poc.player.name) + if dotplayer_name ~= player_name then + if players_set[dotplayer_name] and ch_data.offline_charinfo[dotplayer_name] then + poc.player = assert(ch_data.offline_charinfo[dotplayer_name].player) + else + error("offline_charinfo["..player_name.."] looks for player data of '"..dotplayer_name.."', but it doesn't exist!") + end + end + end + -- upgrade old data: + for player_name, poc in pairs(ch_data.offline_charinfo) do + if poc.version < current_format_version then + upgrade_offline_charinfo(player_name, poc) + end + end +end + +initialize() +initialize = nil + +-- Obsluhy událostí: +-- ====================================================================================================================== +local function on_joinplayer(player, last_login) + local player_name = player:get_player_name() + local online_charinfo = ch_data.get_joining_online_charinfo(player) + local offline_charinfo = ch_data.get_offline_charinfo(player_name) + online_charinfo.doslech = offline_charinfo.doslech + + if offline_charinfo ~= nil then + offline_charinfo.last_login = os.time() - 946684800 + ch_data.save_offline_charinfo(player_name) + -- lc_to_player_name[string.lower(player_name)] = player_name + end + + return true +end + +local function save_playtime(online_charinfo, offline_charinfo) + if offline_charinfo == nil then + return 0, 0, 0 + end + local now = core.get_us_time() + local past_playtime = offline_charinfo.past_playtime or 0 + local current_playtime = math.max(0, 1.0e-6 * (now - online_charinfo.join_timestamp)) + local total_playtime = past_playtime + current_playtime + + offline_charinfo.past_playtime = total_playtime + ch_data.save_offline_charinfo(online_charinfo.player_name) + online_charinfo.join_timestamp = nil + return past_playtime, current_playtime, total_playtime +end + +local function on_leaveplayer(player, timedout) + local player_name = player:get_player_name() + local online_info = ch_data.get_leaving_online_charinfo(player) + + if online_info.join_timestamp then + local past_playtime, current_playtime, total_playtime = save_playtime(online_info, ch_data.offline_charinfo[player_name]) + print("PLAYER(" .. player_name .."): played seconds: " .. current_playtime .. " / " .. total_playtime) + end +end + +local function on_shutdown() + for player_name, online_info in pairs(table.copy(ch_data.online_charinfo)) do + if online_info.join_timestamp then + local past_playtime, current_playtime, total_playtime + past_playtime, current_playtime, total_playtime = save_playtime(online_info, ch_data.offline_charinfo[player_name]) + print("PLAYER(" .. player_name .."): played seconds: " .. current_playtime .. " / " .. total_playtime) + end + end +end + +local function on_placenode(pos, newnode, placer, oldnode, itemstack, pointed_thing) + if core.is_player(placer) then + local player_name = placer:get_player_name() + local online_charinfo = ch_data.online_charinfo[player_name] + if online_charinfo ~= nil then + online_charinfo.last_placenode_ustime = core.get_us_time() + end + end +end + +local function on_dignode(pos, oldnode, digger) + if core.is_player(digger) then + local player_name = digger:get_player_name() + local online_charinfo = ch_data.online_charinfo[player_name] + if online_charinfo ~= nil then + online_charinfo.last_dignode_ustime = core.get_us_time() + end + end +end + +function ch_data.clear_help(player) + local player_name = player:get_player_name() + local online_charinfo = ch_data.online_charinfo[player_name] + if online_charinfo then + online_charinfo.navody = {} + player:get_meta():set_string("navody", core.serialize(online_charinfo.navody)) + return true + else + return false + end +end + +--[[ + Otestuje, zda podle online_charinfo má dané postavě být zobrazený + v četu návod k položce daného názvu. Pokud ano, nastaví příznak, aby se + to znovu již nestalo, a vrátí definici daného předmětu, + z níž lze z položek description a _ch_help sestavit text k zobrazení. +]] +function ch_data.should_show_help(player, online_charinfo, item_name) + local def = core.registered_items[item_name] + if def and def._ch_help then + if def._ch_help_group then + item_name = def._ch_help_group + end + local navody = online_charinfo.navody + if not navody then + navody = {[item_name] = 1} + online_charinfo.navody = navody + player:get_meta():set_string("navody", core.serialize(navody)) + return def.description ~= nil and def + end + if not navody[item_name] then + navody[item_name] = 1 + player:get_meta():set_string("navody", core.serialize(navody)) + return def.description ~= nil and def + end + end + return nil +end + +function ch_data.nastavit_shybani(player_name, shybat) + local offline_charinfo = ch_data.offline_charinfo[player_name] + if offline_charinfo == nil then + core.log("error", "ch_data.nastavit_shybani(): Expected offline charinfo for player "..player_name.." not found!") + return false + end + local new_state + if shybat then + new_state = 0 + else + new_state = 1 + end + offline_charinfo.neshybat = new_state + ch_data.save_offline_charinfo(player_name) + return true +end + +core.register_on_joinplayer(on_joinplayer) +core.register_on_leaveplayer(on_leaveplayer) +core.register_on_shutdown(on_shutdown) +core.register_on_dignode(on_dignode) +core.register_on_placenode(on_placenode) + +local def = { + description = "Smaže údaje o tom, ke kterým předmětům již byly postavě zobrazeny nápovědy, takže budou znovu zobrazovány nápovědy ke všem předmětům.", + func = function(player_name, param) + if ch_data.clear_help(core.get_player_by_name(player_name)) then + return true, "Údaje smazány." + else + core.log("error", "/návodyznovu: vnitřní chyba serveru ("..player_name..")!") + return false, "Vnitřní chyba serveru" + end + end, +} +core.register_chatcommand("návodyznovu", def) +core.register_chatcommand("navodyznovu", def) + +def = { + description = "Odstraní údaje o postavě uložené v systému ch_data. Postava nesmí být ve hře.", + privs = {server = true}, + func = function(player_name, param) + local offline_charinfo = ch_data.offline_charinfo[param] + if not offline_charinfo then + return false, "Data o "..param.." nenalezena!" + end + if ch_data.delete_offline_charinfo(param) then + return true, "Data o "..param.." smazána." + else + return false, "Při odstraňování nastala chyba." + end + end, +} + +core.register_chatcommand("delete_offline_charinfo", def) + +def = { + description = "Trvale vypne či zapne shýbání postavy při držení Shiftu.", + params = "<ano|ne>", + func = function(player_name, param) + if param ~= "ano" and param ~= "ne" then + return false, "Chybná syntaxe." + end + if ch_data.nastavit_shybani(player_name, param == "ano") then + core.chat_send_player(player_name, "*** Shýbání postavy "..(param == "ne" and "vypnuto" or "zapnuto")..".") + return true + else + core.log("error", "/shybat: Expected offline charinfo for player "..player_name.." not found!") + return false, "Vnitřní chyba serveru: Data postavy nenalezena." + end + end, +} + +core.register_chatcommand("shýbat", def) +core.register_chatcommand("shybat", def) + +local function merge_playerinfos(player_name_a, player_name_b) + if player_name_a == player_name_b then + return false, "'"..player_name_a.."' a '"..player_name_b.."' reprezentují tutéž postavu!" + end + local oci_a = ch_data.offline_charinfo[player_name_a] + local oci_b = ch_data.offline_charinfo[player_name_b] + if oci_a == nil then + return false, "Postava '"..player_name_a.."' neexistuje!" + end + if oci_b == nil then + return false, "Postava '"..player_name_b.."' neexistuje!" + end + local dotplayer_a = assert(oci_a.player.name) + local dotplayer_b = assert(oci_b.player.name) + if dotplayer_a == dotplayer_b then + return false, "Postavy '"..player_name_a.."' a '"..player_name_b.."' již patří stejné/mu hráči/ce '"..dotplayer_a.."'." + end + local aliases = {dotplayer_b} + for alias, offline_charinfo in pairs(ch_data.offline_charinfo) do + if alias ~= dotplayer_b and offline_charinfo.player.name == dotplayer_b then + table.insert(aliases, alias) + end + end + ch_data.save_offline_charinfo(dotplayer_b) + for _, alias in ipairs(aliases) do + ch_data.offline_charinfo[alias].player = oci_a.player + ch_data.save_offline_charinfo(alias) + core.log("action", "[MERGE] Player info of .player "..dotplayer_a.." assigned to player "..alias..".") + end + return true, "Postavy "..table.concat(aliases, ",").." přiřazeny hráči/ce "..dotplayer_a.."." +end + +local function set_main_player(player_name) + local oci_a = ch_data.offline_charinfo[player_name] + if oci_a == nil then + return false, "Postava '"..player_name.."' neexistuje!" + end + local old_main = assert(oci_a.player.name) + if old_main == player_name then + return false, "Postava '"..player_name.."' již je hlavní." + end + local aliases = {} + for alias, offline_charinfo in pairs(ch_data.offline_charinfo) do + if alias ~= old_main and offline_charinfo.player.name == old_main then + table.insert(aliases, alias) + end + end + oci_a.player.name = player_name + ch_data.save_offline_charinfo(old_main) + for _, alias in ipairs(aliases) do + ch_data.save_offline_charinfo(alias) + end + return true, "Postava "..player_name.." nastavena jako hlavní (původní hlavní postava: "..old_main..")." +end + +function ch_data.get_player_characters(player_name) + local offline_charinfo = ch_data.offline_charinfo[player_name] + if offline_charinfo == nil then + return nil + end + local main_name = offline_charinfo.player.name + local result = {} + for name, oci in pairs(ch_data.offline_charinfo) do + if oci.player.name == main_name then + table.insert(result, name) + end + end + if #result > 1 then + table.sort(result, function(a, b) return (a == main_name and b ~= main_name) or a < b end) -- TODO: better sorting + end + return result, main_name +end + +def = { + description = "Sloučí hráčská data odpovídající postavě B s hráčskými daty odpovídajícími postavě A", + params = "<Jmeno_postavy_hlavni_A> <Jmeno_postavy_vedlejsi_B>", + privs = {server = true}, + func = function(admin_name, param) + local a, b = string.match(param, "^([^ ]+) +([^ ]+)$") + if a == nil or b == nil then + return false, "Chybné zadání!" + end + local result, message = merge_playerinfos(a, b) + return result, message + end, +} + +core.register_chatcommand("připojit_postavu", def) +core.register_chatcommand("pripojit_postavu", def) + +def = { + description = "Změní hlavní postavu hráče/ky", + params = "<Nova_hlavni_postava>", + privs = {server = true}, + func = function(admin_name, param) + local result, message = set_main_player(param) + return result, message + end, +} + +core.register_chatcommand("hlavní_postava", def) +core.register_chatcommand("hlavni_postava", def) + +ch_base.close_mod(core.get_current_modname()) diff --git a/ch_data/license.txt b/ch_data/license.txt new file mode 100644 index 0000000..6295ca7 --- /dev/null +++ b/ch_data/license.txt @@ -0,0 +1,15 @@ +License of source code +---------------------- + +MIT License + +Copyright (c) 2025 Singularis <singularis@volny.cz> + +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. + +Licenses of media (models, sounds, textures) +------------------------------------ diff --git a/ch_data/mod.conf b/ch_data/mod.conf new file mode 100644 index 0000000..a9f8eb8 --- /dev/null +++ b/ch_data/mod.conf @@ -0,0 +1,3 @@ +name = ch_data +description = Player data API from Český hvozd +depends = ch_base |