local ACTOR = require("pasta.actor")
local SCENE = require("pasta.scene")
local GLOBAL = require("pasta.global")
local STORE = require("pasta.store")
local log = require "@pasta_log"
local function group_by_actor(tokens)
if not tokens or #tokens == 0 then
return {}
end
local result = {}
local current_actor_token = nil local current_actor = nil
for _, token in ipairs(tokens) do
local t = token.type
if t == "spot" or t == "clear_spot" then
table.insert(result, token)
elseif t == "talk" or t == "sakura_script" then
local talk_actor = token.actor
if current_actor_token == nil or talk_actor ~= current_actor then
current_actor_token = {
type = "actor",
actor = talk_actor,
tokens = {}
}
table.insert(result, current_actor_token)
current_actor = talk_actor
end
table.insert(current_actor_token.tokens, token)
elseif t == "raw_script" then
if current_actor_token then
table.insert(current_actor_token.tokens, token)
else
table.insert(result, token)
end
else
if current_actor_token then
table.insert(current_actor_token.tokens, token)
end
end
end
return result
end
local function merge_consecutive_talks(grouped)
local result = {}
for _, token in ipairs(grouped) do
if token.type == "actor" then
local merged_tokens = {}
local pending_talk = nil
for _, inner in ipairs(token.tokens) do
if inner.type == "talk" then
if pending_talk then
pending_talk.text = pending_talk.text .. inner.text
else
pending_talk = {
type = "talk",
actor = inner.actor,
text = inner.text
}
end
else
if pending_talk then
table.insert(merged_tokens, pending_talk)
pending_talk = nil
end
table.insert(merged_tokens, inner)
end
end
if pending_talk then
table.insert(merged_tokens, pending_talk)
end
table.insert(result, {
type = "actor",
actor = token.actor,
tokens = merged_tokens
})
else
table.insert(result, token)
end
end
return result
end
local ACT = {}
local ACT_IMPL = {}
function ACT_IMPL.__index(self, key)
local method = ACT_IMPL[key]
if method then return method end
local actor = self.actors[key]
if actor then
return ACTOR.create_proxy(actor, self)
end
return nil
end
function ACT.new(actors)
local obj = {
actors = actors or {},
save = require("pasta.save"),
app_ctx = STORE.app_ctx,
var = {},
token = {},
current_scene = nil,
}
return setmetatable(obj, ACT_IMPL)
end
function ACT_IMPL.init_scene(self, scene)
if scene.__global_name__ then
STORE.last_global_scene = scene.__global_name__
end
self.current_scene = scene
return self.save, self.var
end
function ACT_IMPL.talk(self, actor, text)
table.insert(self.token, { type = "talk", actor = actor, text = text })
return self
end
function ACT_IMPL.sakura_script(self, actor, text)
table.insert(self.token, { type = "sakura_script", actor = actor, text = text })
return self
end
function ACT_IMPL.raw_script(self, text)
table.insert(self.token, { type = "raw_script", text = text })
return self
end
function ACT_IMPL.surface(self, id)
table.insert(self.token, { type = "surface", id = id })
return self
end
function ACT_IMPL.wait(self, ms)
ms = math.max(0, math.floor(ms or 0))
table.insert(self.token, { type = "wait", ms = ms })
return self
end
function ACT_IMPL.newline(self, n)
table.insert(self.token, { type = "newline", n = n or 1 })
return self
end
function ACT_IMPL.clear(self)
table.insert(self.token, { type = "clear" })
return self
end
function ACT_IMPL.choice(self, target, display)
table.insert(self.token, { type = "choice", target = target, display = display or target })
return self
end
function ACT_IMPL.choice_timeout(self, seconds)
table.insert(self.token, { type = "choice_timeout", seconds = seconds })
return self
end
local function search_dictionary(SEARCH, mode, key, scene_name)
if mode == "word" then
local result = SEARCH:search_word(key, scene_name)
if result ~= nil then return result end
else
local result = SCENE.search(key, scene_name)
if result then return result.func end
end
return nil
end
function ACT_IMPL.find_act_handler(self, mode, key)
local ok, SEARCH = pcall(require, "@pasta_search")
if not ok then SEARCH = nil end
if self.current_scene and self.current_scene[key] ~= nil then
return self.current_scene[key]
end
local scene_name = self.current_scene and self.current_scene.__global_name__
if SEARCH and scene_name then
local result = search_dictionary(SEARCH, mode, key, scene_name)
if result ~= nil then return result end
end
local method = self[key]
if type(method) == "function" then
return method
end
if GLOBAL[key] ~= nil then
return GLOBAL[key]
end
if SEARCH then
local result = search_dictionary(SEARCH, mode, key, nil)
if result ~= nil then return result end
end
return nil
end
function ACT_IMPL.find_handler(self, mode, key)
return self:find_act_handler(mode, key)
end
function ACT_IMPL.word(self, name)
if not name or name == "" then
return nil
end
local handler = self:find_handler("word", name)
if handler == nil then
log.warn(string.format("act:word - handler not found: key='%s', mode='word', via=act",
tostring(name)))
return nil
end
if type(handler) == "function" then
return handler(self)
end
return tostring(handler)
end
function ACT_IMPL.expr_fn(self, key, ...)
local handler = self:find_handler("expr", key)
if type(handler) == "function" then
return handler(self, ...)
end
log.warn(string.format("act:expr_fn - handler not found: key='%s', mode='expr', via=act",
tostring(key)))
return nil
end
function ACT_IMPL.build(self)
local tokens = self.token
self.token = {}
if #tokens == 0 then
return nil
end
local grouped = group_by_actor(tokens)
local merged = merge_consecutive_talks(grouped)
return merged
end
function ACT_IMPL.yield(self)
local result = self:build()
coroutine.yield(result)
return self
end
function ACT_IMPL.find_scene(self, key, global_scene_name, attrs)
return self:find_handler("scene", key)
end
function ACT_IMPL.call(self, global_scene_name, key, attrs, ...)
if key == nil then
log.warn("act:call - nil key (undefined variable?), skipping scene search")
return nil
end
local handler = self:find_handler("scene", key)
if type(handler) == "function" then
return handler(self, ...)
end
log.warn(string.format("act:call - handler not found: key='%s', mode='scene', via=act",
tostring(key)))
return nil
end
function ACT_IMPL.set_spot(self, name, number)
local actor = self.actors[name]
if actor then
table.insert(self.token, { type = "spot", actor = actor, spot = number })
end
end
function ACT_IMPL.clear_spot(self)
table.insert(self.token, { type = "clear_spot" })
end
ACT.IMPL = ACT_IMPL
return ACT