Commit d16187dbe6e5f78d6c6501cad81d1c78495b4226

Authored by DarkGod
1 parent 313f908c

New chat format support, for a visual editor that will come later

... ... @@ -19,6 +19,7 @@
19 19
20 20 require "engine.class"
21 21 require "engine.dialogs.Chat"
  22 +require "Json2"
22 23 local slt2 = require "slt2"
23 24
24 25 --- Handle chats between the player and NPCs
... ... @@ -44,17 +45,22 @@ function _M:init(name, npc, player, data)
44 45 if not data.player then data.player = player end
45 46 if not data.npc then data.npc = npc end
46 47
47   - local f, err = loadfile(self:getChatFile(name))
48   - if not f and err then error(err) end
49   - local env = setmetatable({
50   - cur_chat = self,
51   - setDialogWidth = function(w) self.force_dialog_width = w end,
52   - newChat = function(c) self:addChat(c) end,
53   - setTextFont = function(font, size) self.dialog_text_font = {font, size} end,
54   - setAnswerFont = function(font, size) self.dialog_answer_font = {font, size} end,
55   - }, {__index=data})
56   - setfenv(f, env)
57   - self.default_id = f()
  48 + local filepath, is_chat_format = self:getChatFile(name)
  49 + if not is_chat_format then
  50 + local f, err = loadfile(filepath)
  51 + if not f and err then error(err) end
  52 + local env = setmetatable({
  53 + cur_chat = self,
  54 + setDialogWidth = function(w) self.force_dialog_width = w end,
  55 + newChat = function(c) self:addChat(c) end,
  56 + setTextFont = function(font, size) self.dialog_text_font = {font, size} end,
  57 + setAnswerFont = function(font, size) self.dialog_answer_font = {font, size} end,
  58 + }, {__index=data})
  59 + setfenv(f, env)
  60 + self.default_id = f()
  61 + else
  62 + self:loadChatFormat(filepath)
  63 + end
58 64
59 65 self:triggerHook{"Chat:load", data=data, env=env}
60 66 end
... ... @@ -65,11 +71,118 @@ end
65 71 function _M:getChatFile(file)
66 72 local _, _, addon, rfile = file:find("^([^+]+)%+(.+)$")
67 73 if addon and rfile then
  74 + if fs.exists("/data-"..addon.."/chats/"..rfile..".chat") then return "/data-"..addon.."/chats/"..rfile..".chat", true end
68 75 return "/data-"..addon.."/chats/"..rfile..".lua"
69 76 end
  77 + if fs.exists("/data/chats/"..file..".chat") then return "/data/chats/"..file..".chat", true end
70 78 return "/data/chats/"..file..".lua"
71 79 end
72 80
  81 +function _M:chatFormatActions(nodes, answer, node)
  82 + if not node then return end
  83 + local function getnext()
  84 + -- Find out if we have actions to take, conditions to apply and where to jump to
  85 + if table.sget(node, 'outputs', 'output_1', "connections", 1, "node") then
  86 + return nodes[table.sget(node, 'outputs', 'output_1', "connections", 1, "node")]
  87 + end
  88 + end
  89 +
  90 + local function add_action(action)
  91 + if answer.action then
  92 + local old = answer.action
  93 + answer.action = function(npc, player)
  94 + local r1 = old(npc, player)
  95 + local r2 = action(npc, player)
  96 + return r2 or r1
  97 + end
  98 + else
  99 + answer.action = action
  100 + end
  101 + end
  102 + local function add_cond(cond)
  103 + if answer.cond then
  104 + local old = answer.cond
  105 + answer.cond = function(npc, player)
  106 + return old(npc, player) and cond(npc, player)
  107 + end
  108 + else
  109 + answer.cond = cond
  110 + end
  111 + end
  112 +
  113 + if node.name == "chat" then
  114 + answer.jump = node.data.chatid
  115 + elseif node.name == "lua-code" then
  116 + local action, err = loadstring("return function(npc, player) "..node.data.code.." end")
  117 + if not action and err then error("[Chat] chatFormatActions ERROR: "..err) end
  118 + action = action()
  119 + add_action(action)
  120 + return self:chatFormatActions(nodes, answer, getnext())
  121 + elseif node.name == "lua-cond" then
  122 + if not node.data.code:find("return ") then node.data.code = "return "..node.data.code end
  123 + local cond, err = loadstring("return function(npc, player) "..node.data.code.." end")
  124 + if not cond and err then error("[Chat] chatFormatActions ERROR: "..err) end
  125 + cond = cond()
  126 + add_cond(cond)
  127 + return self:chatFormatActions(nodes, answer, getnext())
  128 + elseif node.name == "quest-set" then
  129 + local Quest = require "engine.Quest"
  130 + local sub = nil
  131 + if node.data.sub ~= "" then sub = node.data.sub end
  132 + add_action(function(npc, player) return player:setQuestStatus(node.data.quest, Quest[node.data.status], sub) end)
  133 + return self:chatFormatActions(nodes, answer, getnext())
  134 + elseif node.name == "quest-give" then
  135 + local Quest = require "engine.Quest"
  136 + add_action(function(npc, player) return player:grantQuest(node.data.quest) end)
  137 + return self:chatFormatActions(nodes, answer, getnext())
  138 + elseif node.name == "quest-cond" then
  139 + local Quest = require "engine.Quest"
  140 + local sub = nil
  141 + if node.data.sub ~= "" then sub = node.data.sub end
  142 + add_cond(function(npc, player) return player:isQuestStatus(node.data.quest, Quest[node.data.status], sub) end)
  143 + return self:chatFormatActions(nodes, answer, getnext())
  144 + end
  145 +end
  146 +
  147 +function _M:loadChatFormat(filepath)
  148 + local fdata = fs.readAll(filepath)
  149 + if not fdata then print("[Chat] loadChatFormat: error reading file") return end
  150 + local data = json.decode(fdata)
  151 +
  152 + -- Fix chatids to ensure uniqueness
  153 + local chatids = {}
  154 + for nodeid, node in pairs(data) do
  155 + if node.name == "chat" then
  156 + local chatid = node.data.chatid
  157 + while chatids[node.data.chatid] do node.data.chatid = chatid..rng.range(1, 99999) end
  158 + chatids[node.data.chatid] = node
  159 + end
  160 + end
  161 +
  162 + for nodeid, node in pairs(data) do
  163 + if node.name == "chat" then
  164 + local answers = {}
  165 + local i = 1
  166 + while node.data["answer"..i] do
  167 + answers[i] = {_t(node.data["answer"..i])}
  168 +
  169 + -- Find out if we have actions to take, conditions to apply and where to jump to
  170 + if table.sget(node, 'outputs', 'output_'..i, "connections", 1, "node") then
  171 + local tn = data[table.sget(node, 'outputs', 'output_'..i, "connections", 1, "node")]
  172 + self:chatFormatActions(data, answers[i], tn)
  173 + end
  174 +
  175 + i = i + 1
  176 + end
  177 + self:addChat{ id = node.data.chatid,
  178 + text = _t(node.data.chat),
  179 + answers = answers,
  180 + }
  181 + end
  182 + end
  183 + self.default_id = "welcome"
  184 +end
  185 +
73 186 --- Switch the NPC talking
74 187 -- @param[type=Actor] npc
75 188 -- @return NPC we switched from
... ...
... ... @@ -653,6 +653,18 @@ function table.get(table, ...)
653 653 end
654 654
655 655 --[=[
  656 + Strict verion of table.get, only returns if fully found
  657 +]=]
  658 +function table.sget(table, ...)
  659 + if type(table) ~= 'table' then return nil end
  660 + for _, key in ipairs({...}) do
  661 + if type(table) ~= 'table' then return nil end
  662 + table = table[key]
  663 + end
  664 + return table
  665 +end
  666 +
  667 +--[=[
656 668 Set the nested value in a table, creating empty tables as needed.
657 669 ]=]
658 670 function table.set(table, ...)
... ...
... ... @@ -2061,6 +2061,11 @@ function _M:setupCommands()
2061 2061 print("===============")
2062 2062 end end,
2063 2063 [{"_g","ctrl"}] = function() if config.settings.cheat then
  2064 + package.loaded["engine.Chat"] = nil
  2065 + local Chat = require "engine.Chat"
  2066 + local chat = Chat.new("tareyal+test", game.player, game.player)
  2067 + chat:invoke()
  2068 +do return end
2064 2069 game.player:takeHit(100, game.player)
2065 2070 game.player:useEnergy()
2066 2071 -- DamageType:get(DamageType.ACID).projector(game.player, game.player.x, game.player.y, DamageType.ACID, 100)
... ...