From e9d7dee11e2741100050fc806c89aeab896174a8 Mon Sep 17 00:00:00 2001 From: FaceDeer Date: Tue, 4 Feb 2020 01:59:17 -0700 Subject: [PATCH] initial commit --- init.lua | 487 ++++++++++++++++++++++++++-- locale/template.pot | 104 ++++++ locale/update.bat | 6 + mod.conf | 3 + textures/license.txt | 1 + textures/personal_log_open_book.png | Bin 0 -> 760 bytes 6 files changed, 570 insertions(+), 31 deletions(-) create mode 100644 locale/template.pot create mode 100644 locale/update.bat create mode 100644 mod.conf create mode 100644 textures/license.txt create mode 100644 textures/personal_log_open_book.png diff --git a/init.lua b/init.lua index ff1954e..1680355 100644 --- a/init.lua +++ b/init.lua @@ -1,58 +1,483 @@ -local init = os.clock() -minetest.log("action", "["..minetest.get_current_modname().."] loading...") +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +local ccompass_modpath = minetest.get_modpath("ccompass") +local default_modpath = minetest.get_modpath("default") +local modstore = minetest.get_mod_storage() -journal = { - modpath = minetest.get_modpath("journal") +local S = minetest.get_translator(modname) + +local categories = { + S("Location"), + S("Event"), + S("General"), } -dofile(journal.modpath.."/util.lua") -dofile(journal.modpath.."/players.lua") -dofile(journal.modpath.."/entries.lua") -dofile(journal.modpath.."/triggers.lua") -dofile(journal.modpath.."/form.lua") +local LOCATION_CATEGORY = 1 +local EVENT_CATEGORY = 2 +local GENERAL_CATEGORY = 3 + +-------------------------------------------------------- +-- Data store + +local function get_state(player_name) + local state = modstore:get(player_name .. "_state") + if state then + state = minetest.deserialize(state) + end + if not state then + state = {category=LOCATION_CATEGORY, entry_selected={0,0,0}, entry_counts={0,0,0}} + end + return state +end + +local function save_state(player_name, state) + modstore:set_string(player_name .. "_state", minetest.serialize(state)) +end + +local function save_entry(player_name, category_index, entry_index, entry_text, topic_text) + if topic_text then + modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_topic", + topic_text:gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("\n", " ")) + end + modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_content", + entry_text:gsub("\r\n", "\n"):gsub("\r", "\n")) +end + +local function swap(player_name, state, direction) + local category_index = state.category + local entry_index = state.entry_selected[category_index] + local next_index = entry_index + direction + if next_index < 1 or next_index > state.entry_counts[category_index] then + return + end + + local current_topic = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_topic") + local current_content = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_content") + local next_topic = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. next_index .. "_topic") + local next_content = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. next_index .. "_content") + + save_entry(player_name, category_index, entry_index, next_content, next_topic) + save_entry(player_name, category_index, next_index, current_content, current_topic) + state.entry_selected[category_index] = next_index + save_state(player_name, state) +end + +local function delete(player_name, state) + local category_index = state.category + local entry_count = state.entry_counts[category_index] + if entry_count == 0 then + return + end + local entry_index = state.entry_selected[category_index] + + for i = entry_index + 1, entry_count do + local topic = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_topic") + local content = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_content") + save_entry(player_name, category_index, i-1, content, topic) + end + + modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_count .. "_topic", "") + modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_count .. "_content", "") + entry_count = entry_count - 1 + state.entry_counts[category_index] = entry_count + if entry_index > entry_count then + state.entry_selected[category_index] = entry_count + end + save_state(player_name, state) +end + +---------------------------------------------------------------------------------------- +-- String functions + +local truncate_string = function(target, length) + if target:len() > length then + return target:sub(1,length-2).."..." + end + return target +end + +local first_line = function(target) + local first_return = target:find("\n") + if not first_return then + first_return = #target + else + first_return = first_return - 1 -- trim the hard return off + end + return target:sub(1, first_return) +end + +--------------------------------------------------------------- +-- Main formspec + +local function make_personal_log_formspec(player) + local player_name = player:get_player_name() + + local state = get_state(player_name) + local category_index = state.category + + local formspec = { + "formspec_version[2]" + .."size[10,10]" + .."dropdown[1.5,0.25;2,0.5;category_select;" + .. table.concat(categories, ",") .. ";"..category_index.."]" + .. "label[0.5,0.5;"..S("Category:").."]" + .. "label[4.5,0.5;"..S("Personal Log Entries").."]" + } + + local entries = {} + for i = 1, state.entry_counts[category_index] do + table.insert(entries, modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_content")) + end + local entry = "" + local entry_selected = state.entry_selected[category_index] + if entry_selected > 0 then + entry = entries[entry_selected] + end + + local topics = {} + for i = 1, state.entry_counts[category_index] do + table.insert(topics, modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_topic")) + end + local topic = "" + if entry_selected > 0 then + topic = topics[entry_selected] + end + + formspec[#formspec+1] = "tablecolumns[text;text]table[0.5,1.0;9,4.75;log_table;" + for i, entry in ipairs(entries) do + formspec[#formspec+1] = minetest.formspec_escape(truncate_string(topics[i], 30)) .. "," + formspec[#formspec+1] = minetest.formspec_escape(truncate_string(first_line(entry), 30)) + formspec[#formspec+1] = "," + end + formspec[#formspec] = ";"..entry_selected.."]" -- don't use +1, this overwrites the last "," + + if category_index == GENERAL_CATEGORY then + formspec[#formspec+1] = "textarea[0.5,6.0;9,0.5;topic_data;;" .. minetest.formspec_escape(topic) .. "]" + formspec[#formspec+1] = "textarea[0.5,6.5;9,1.75;entry_data;;".. minetest.formspec_escape(entry) .."]" + else + formspec[#formspec+1] = "textarea[0.5,6.0;9,2.25;entry_data;;".. minetest.formspec_escape(entry) .."]" + end + + formspec[#formspec+1] = "container[0.5,8.5]" + .."button[0,0;2,0.5;save;"..S("Save").."]" + .."button[2,0;2,0.5;create;"..S("New").."]" + .."button[4.5,0;2,0.5;move_up;"..S("Move Up").."]" + .."button[4.5,0.5;2,0.5;move_down;"..S("Move Down").."]" + .."button[7,0;2,0.5;delete;"..S("Delete") .."]" + + if default_modpath then + formspec[#formspec+1] = "button[0,0.75;1.25,0.5;copy_to;"..S("To Book").."]" + .."button[1.375,0.75;1.25,0.5;copy_from;"..S("From Book").."]" + end + + if ccompass_modpath and category_index == LOCATION_CATEGORY then + formspec[#formspec+1] = "button[2.75,0.75;1.25,0.5;set_ccompass;"..S("To Compass").."]" + end + + formspec[#formspec+1] = "container_end[]" + + return table.concat(formspec) +end + +--------------------------------------- +-- Reading and writing stuff to items + +-- Book parameters +local lpp = 14 +local max_text_size = 10000 +local max_title_size = 80 +local short_title_size = 35 +local function write_book(player_name) + local state = get_state(player_name) + local category = state.category + local entry_selected = state.entry_selected[category] + local content = modstore:get(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_content") or "" + local topic = modstore:get(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic") or "" + if state.category ~= 3 then + -- If it's a location or an event, add a little context to the title + topic = topic .. ": " .. first_line(content) + end + + local new_book = ItemStack("default:book_written") + local meta = new_book:get_meta() + + meta:set_string("owner", player_name) + meta:set_string("title", topic:sub(1, max_title_size)) + meta:set_string("description", S("\"@1\" by @2", truncate_string(topic, short_title_size), player_name)) + meta:set_string("text", content:sub(1, max_text_size)) + meta:set_int("page", 1) + meta:set_int("page_max", math.ceil((#content:gsub("[^\n]", "") + 1) / lpp)) + return new_book +end + +local function read_book(itemstack, player_name) + local meta = itemstack:get_meta() + local topic = meta:get_string("title") + local content = meta:get_string("text") + + local date_string = topic:match("^%d%d%d%d%-%d%d%-%d%d") + local pos_string = topic:match("^%(%-?[0-9]+,%-?[0-9]+,%-?[0-9]+%)") + + local category = GENERAL_CATEGORY + if date_string then + topic = date_string + category = EVENT_CATEGORY + elseif pos_string then + topic = pos_string + category = LOCATION_CATEGORY + end + + local state = get_state(player_name) + local entry_index = state.entry_counts[category] + 1 + state.entry_counts[category] = entry_index + save_entry(player_name, category, entry_index, content, topic) + save_state(player_name, state) +end + +local function set_ccompass(player_name) + local state = get_state(player_name) + local category = state.category + if category ~= LOCATION_CATEGORY then + return + end + local entry_selected = state.entry_selected[category] + local topic = modstore:get(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic") or "" + local pos = minetest.string_to_pos(topic) + if not pos then + return + end + + local content = modstore:get(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_content") or "" + content = truncate_string(first_line(content), max_title_size) + local new_ccompass = ItemStack("ccompass:0") + local param = { + target_pos_string = topic, + target_name = content, + playername = player_name + } + ccompass.set_target(new_ccompass, param) + return new_ccompass +end + +local ccompass_prefix = "ccompass:" +local ccompass_prefix_length = #ccompass_prefix +local detached_callbacks = { + allow_put = function(inv, listname, index, stack, player) + if listname == "write_book" then + if stack:get_name() == "default:book" then + return 1 + end + return 0 + elseif listname == "read_book" then + if stack:get_name() == "default:book_written" then + return 1 + end + return 0 + elseif listname == "set_ccompass" then + if stack:get_name():sub(1,ccompass_prefix_length) == ccompass_prefix then + return 1 + end + return 0 + end + end, + on_put = function(inv, listname, index, stack, player) + local player_name = player:get_player_name() + if listname == "write_book" then + inv:remove_item(listname, stack) + inv:add_item(listname, write_book(player_name)) + elseif listname == "read_book" then + read_book(stack, player_name) + elseif listname == "set_ccompass" then + local new_ccompass = set_ccompass(player_name) + if new_ccompass then + inv:remove_item(listname, stack) + inv:add_item(listname, new_ccompass) + end + end + end, +} + +local item_invs = {} +local function item_formspec(player_name, label, listname) + if not item_invs[player_name] then + local inv = minetest.create_detached_inventory("personal_log_"..player_name, detached_callbacks) + if default_modpath then + inv:set_size("write_book", 1) + inv:set_size("read_book", 1) + end + if ccompass_modpath then + inv:set_size("set_ccompass", 1) + end + item_invs[player_name] = true + end + + local formspec = "size[8,6]" + .. "label[1,0.25;" .. label .. "]" + .. "list[detached:personal_log_"..player_name..";"..listname..";3.5,0;1,1;]" + .. "list[current_player;main;0,1.5;8,4;]" + .. "listring[]" + .. "button[3.5,5.5;1,1;back;"..S("Back").."]" + + return formspec +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "personal_log:item" then + return + end + if fields.back then + minetest.show_formspec(player:get_player_name(),"personal_log:root", make_personal_log_formspec(player)) + end +end) + +------------------------------------------- + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "personal_log:root" then + return + end + local player_name = player:get_player_name() + local player_pos = player:get_pos() + local state = get_state(player_name) + local category = state.category + local entry_selected = state.entry_selected[category] + + if fields.log_table then + local table_event = minetest.explode_table_event(fields.log_table) + if table_event.type == "CHG" then + state.entry_selected[category] = table_event.row + save_state(player_name, state) + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + end + end + + if fields.save then + if category == GENERAL_CATEGORY then + save_entry(player_name, category, entry_selected, fields.entry_data, fields.topic_data) + else + save_entry(player_name, category, entry_selected, fields.entry_data) + end + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + end + + if fields.create then + local content = "" + local general_topic = "" + if entry_selected == 0 then + content = fields.entry_data + general_topic = fields.topic_data + end + + local entry_index = state.entry_counts[category] + 1 + state.entry_counts[category] = entry_index + state.entry_selected[category] = entry_index + if category == LOCATION_CATEGORY then + local pos = vector.round(player:get_pos()) + save_entry(player_name, category, entry_index, content, minetest.pos_to_string(pos)) + elseif category == EVENT_CATEGORY then + local current_date = os.date("%Y-%m-%d") + save_entry(player_name, category, entry_index, content, current_date) + else + save_entry(player_name, category, entry_index, content, general_topic) + end + save_state(player_name, state) + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + end + + if fields.move_up then + swap(player_name, state, -1) + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + end + if fields.move_down then + swap(player_name, state, 1) + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + end + if fields.delete then + delete(player_name, state) + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + end + + if fields.copy_to then + minetest.show_formspec(player_name, "personal_log:item", + item_formspec(player_name, S("Copy log to blank book:"), "write_book")) + return + end + if fields.copy_from then + minetest.show_formspec(player_name, "personal_log:item", + item_formspec(player_name, S("Copy log from written book:"), "read_book")) + return + end + if fields.set_ccompass then + minetest.show_formspec(player_name, "personal_log:item", + item_formspec(player_name, S("Set a compass to this location:"), "set_ccompass")) + return + end + + -- Do this one last, since it should always be true and we don't want to do it if we don't have to + if fields.category_select then + for i, category in ipairs(categories) do + if category == fields.category_select then + if state.category ~= i then + state.category = i + save_state(player_name, state) + minetest.show_formspec(player_name,"personal_log:root", make_personal_log_formspec(player)) + return + else + break + end + end + end + end +end) + + +------------------------------------------------------------------------------------------------------- + -- Unified Inventory -if minetest.get_modpath("unified_inventory") ~= nil then - unified_inventory.register_button("journal", { +if minetest.get_modpath("unified_inventory") then + unified_inventory.register_button("personal_log", { type = "image", - image = "default_book_written.png", - tooltip = "journal", + image = "personal_log_open_book.png", + tooltip = S("Your personal log for keeping track of what happens where"), action = function(player) local name = player:get_player_name() - minetest.show_formspec(name,"journal:journal_" .. name,journal.make_formspec(name)) + minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player)) end, }) end -- sfinv_buttons -if minetest.get_modpath("sfinv_buttons") ~= nil then - sfinv_buttons.register_button("journal", { - image = "default_book_written.png", - tooltip = "your personal journal keeping track of what happens", - title = "journal", +if minetest.get_modpath("sfinv_buttons") then + sfinv_buttons.register_button("personal_log", { + image = "personal_log_open_book.png", + tooltip = S("Your personal log for keeping track of what happens where"), + title = S("Log"), action = function(player) local name = player:get_player_name() - minetest.show_formspec(name,"journal:journal_" .. name,journal.make_formspec(name)) + minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player)) end, }) -elseif minetest.get_modpath("sfinv") ~= nil then - sfinv.register_page("journal:journal", { - title = "journal", +elseif minetest.get_modpath("sfinv") then + sfinv.register_page("personal_log:personal_log", { + title = S("Log"), get = function(_, player, context) local name = player:get_player_name() - minetest.show_formspec(name,"journal:journal_" .. name,journal.make_formspec(name)) - return sfinv.make_formspec(player, context, "button[2.5,3;3,1;open_journal;open journal]", false) + minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player)) + return sfinv.make_formspec(player, context, "button[2.5,3;3,1;open_personal_log;"..S("Open personal log").."]", false) end, on_player_receive_fields = function(_, player, _, fields) local name = player:get_player_name() - if fields.open_journal then - minetest.show_formspec(name,"journal:journal_" .. name,journal.make_formspec(name)) + if fields.open_personal_log then + minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player)) return true end end }) end - ---ready -local time_to_load= os.clock() - init -journal.log.action("loaded in %.4f s", time_to_load) diff --git a/locale/template.pot b/locale/template.pot new file mode 100644 index 0000000..6018b4c --- /dev/null +++ b/locale/template.pot @@ -0,0 +1,104 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-02-04 01:57-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: personal_log\init.lua:10 +msgid "Location" +msgstr "" + +#: personal_log\init.lua:11 +msgid "Event" +msgstr "" + +#: personal_log\init.lua:12 +msgid "General" +msgstr "" + +#: personal_log\init.lua:123 +msgid "Category:" +msgstr "" + +#: personal_log\init.lua:124 +msgid "Personal Log Entries" +msgstr "" + +#: personal_log\init.lua:162 +msgid "Save" +msgstr "" + +#: personal_log\init.lua:163 +msgid "New" +msgstr "" + +#: personal_log\init.lua:164 +msgid "Move Up" +msgstr "" + +#: personal_log\init.lua:165 +msgid "Move Down" +msgstr "" + +#: personal_log\init.lua:166 +msgid "Delete" +msgstr "" + +#: personal_log\init.lua:169 +msgid "To Book" +msgstr "" + +#: personal_log\init.lua:170 +msgid "From Book" +msgstr "" + +#: personal_log\init.lua:174 +msgid "To Compass" +msgstr "" + +#: personal_log\init.lua:206 +msgid "\"@1\" by @2" +msgstr "" + +#: personal_log\init.lua:319 +msgid "Back" +msgstr "" + +#: personal_log\init.lua:408 +msgid "Copy log to blank book:" +msgstr "" + +#: personal_log\init.lua:413 +msgid "Copy log from written book:" +msgstr "" + +#: personal_log\init.lua:418 +msgid "Set a compass to this location:" +msgstr "" + +#: personal_log\init.lua:448 +#: personal_log\init.lua:460 +msgid "Your personal log for keeping track of what happens where" +msgstr "" + +#: personal_log\init.lua:461 +#: personal_log\init.lua:469 +msgid "Log" +msgstr "" + +#: personal_log\init.lua:473 +msgid "Open personal log" +msgstr "" diff --git a/locale/update.bat b/locale/update.bat new file mode 100644 index 0000000..e87d44c --- /dev/null +++ b/locale/update.bat @@ -0,0 +1,6 @@ +@echo off +setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION +cd .. +set LIST= +for /r %%X in (*.lua) do set LIST=!LIST! %%X +..\intllib\tools\xgettext.bat %LIST% \ No newline at end of file diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..2208522 --- /dev/null +++ b/mod.conf @@ -0,0 +1,3 @@ +name = personal_log +description = A personal log where players can track events and places +optional_depends = unified_inventory, sfinv_buttons, sfinv, ccompass, default diff --git a/textures/license.txt b/textures/license.txt new file mode 100644 index 0000000..f5ea42e --- /dev/null +++ b/textures/license.txt @@ -0,0 +1 @@ +personal_log_open_book.png is a recoloured version of https://commons.wikimedia.org/wiki/File:Glossary.gif by Thao under the CC-BY-SA 3.0 license \ No newline at end of file diff --git a/textures/personal_log_open_book.png b/textures/personal_log_open_book.png new file mode 100644 index 0000000000000000000000000000000000000000..943aa7c6f1e63c77e674f086443a507b1c5468a7 GIT binary patch literal 760 zcmVp1PBrY#VOKp0000PbVXQnLvL+uWo~o;Lvm$dbY)~9 zcWHEJAV*0}P*;Ht7XSbPa!Eu%R5;7Ez&&f7WgLL<>%Pwu({|o*$Kr7hZJDpxI0Uu3#qzycRQ|s{0{xoL);zr zLZb~yD#pLWGNCBO8 zsuUo_Nh#Rg-60H$3WJ43CX+Eu>-1dMy0wjU#^mIf;c%I{OqiBTC8mfJ%rg(4J&%bZ zA{-nXAULFiC+E!S8n`q5`w-7Pno^jTpfWSsQb;&AcXmMy)t34G0+nTQI7Z^<-P>5# zGC65jUVacWqmi1bNI^t_%p;=_Vvckze;ocvL=PuODOek=&`EGS8Pf};lNym(D%GHq zLL}$Z>;^i0UQK2DZ z5~L{-$|qlcPBm`E93CCgNlOvKN~X~XjkF9;FAiRWmIl){Jf#QN0s6ztyH&@<& qBYyaPgH{_JKYx+e-~BZ4fA|NnL!V*Wqgnp|0000