diff options
Diffstat (limited to 'src/libjin-lua/scripts/ai/state_machine.lua')
-rw-r--r-- | src/libjin-lua/scripts/ai/state_machine.lua | 182 |
1 files changed, 180 insertions, 2 deletions
diff --git a/src/libjin-lua/scripts/ai/state_machine.lua b/src/libjin-lua/scripts/ai/state_machine.lua index b4ec768..296296f 100644 --- a/src/libjin-lua/scripts/ai/state_machine.lua +++ b/src/libjin-lua/scripts/ai/state_machine.lua @@ -1,7 +1,185 @@ jin.ai = jin.ai or {} -local statemachine = {} +--[[ -jin.ai.newStateMachine = function() +https://github.com/kyleconroy/lua-state-machine +Copyright (c) 2012 Kyle Conroy + +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. + +]] +local machine = {} +machine.__index = machine + +local NONE = "none" +local ASYNC = "async" + +local function call_handler(handler, params) + if handler then + return handler(unpack(params)) + end +end + +local function create_transition(name) + local can, to, from, params + + local function transition(self, ...) + if self.asyncState == NONE then + can, to = self:can(name) + from = self.current + params = { self, name, from, to, ...} + + if not can then return false end + self.currentTransitioningEvent = name + + local beforeReturn = call_handler(self["onbefore" .. name], params) + local leaveReturn = call_handler(self["onleave" .. from], params) + + if beforeReturn == false or leaveReturn == false then + return false + end + + self.asyncState = name .. "WaitingOnLeave" + + if leaveReturn ~= ASYNC then + transition(self, ...) + end + + return true + elseif self.asyncState == name .. "WaitingOnLeave" then + self.current = to + + local enterReturn = call_handler(self["onenter" .. to] or self["on" .. to], params) + + self.asyncState = name .. "WaitingOnEnter" + + if enterReturn ~= ASYNC then + transition(self, ...) + end + + return true + elseif self.asyncState == name .. "WaitingOnEnter" then + call_handler(self["onafter" .. name] or self["on" .. name], params) + call_handler(self["onstatechange"], params) + self.asyncState = NONE + self.currentTransitioningEvent = nil + return true + else + if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then + self.asyncState = NONE + transition(self, ...) + return true + end + end + + self.currentTransitioningEvent = nil + return false + end + + return transition +end + +local function add_to_map(map, event) + if type(event.from) == 'string' then + map[event.from] = event.to + else + for _, from in ipairs(event.from) do + map[from] = event.to + end + end end + +function machine.create(options) + assert(options.events) + + local fsm = {} + setmetatable(fsm, machine) + + fsm.options = options + fsm.current = options.initial or 'none' + fsm.asyncState = NONE + fsm.events = {} + + for _, event in ipairs(options.events or {}) do + local name = event.name + fsm[name] = fsm[name] or create_transition(name) + fsm.events[name] = fsm.events[name] or { map = {} } + add_to_map(fsm.events[name].map, event) + end + + for name, callback in pairs(options.callbacks or {}) do + fsm[name] = callback + end + + return fsm +end + +function machine:is(state) + return self.current == state +end + +function machine:can(e) + local event = self.events[e] + local to = event and event.map[self.current] or event.map['*'] + return to ~= nil, to +end + +function machine:cannot(e) + return not self:can(e) +end + +function machine:todot(filename) + local dotfile = io.open(filename,'w') + dotfile:write('digraph {\n') + local transition = function(event,from,to) + dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) + end + for _, event in pairs(self.options.events) do + if type(event.from) == 'table' then + for _, from in ipairs(event.from) do + transition(event.name,from,event.to) + end + else + transition(event.name,event.from,event.to) + end + end + dotfile:write('}\n') + dotfile:close() +end + +function machine:transition(event) + if self.currentTransitioningEvent == event then + return self[self.currentTransitioningEvent](self) + end +end + +function machine:cancelTransition(event) + if self.currentTransitioningEvent == event then + self.asyncState = NONE + self.currentTransitioningEvent = nil + end +end + +machine.NONE = NONE +machine.ASYNC = ASYNC + +-- Import to Jin. + +jin.ai.newStateMachine = machine.create |