From 0c30055c527c921df5286a573da1855a821f2c96 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Tue, 19 Mar 2013 19:11:26 -0700 Subject: [PATCH 1/2] Add gamesaves to the application --- src/app.lua | 2 +- src/hawk/application.lua | 17 ++--- src/hawk/gamesave.lua | 33 +++++++++ src/hawk/middleclass.lua | 147 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 src/hawk/gamesave.lua create mode 100644 src/hawk/middleclass.lua diff --git a/src/app.lua b/src/app.lua index d7beb3cf3..8d5bfd27a 100644 --- a/src/app.lua +++ b/src/app.lua @@ -1,2 +1,2 @@ local app = require 'hawk/application' -return app.new('config.json') +return app('config.json') diff --git a/src/hawk/application.lua b/src/hawk/application.lua index dce90ec38..7f32f53eb 100644 --- a/src/hawk/application.lua +++ b/src/hawk/application.lua @@ -1,18 +1,15 @@ local json = require 'hawk/json' +local middle = require 'hawk/middleclass' +local gamesave = require 'hawk/gamesave' -local application = {} -application.__index = application - -function application.new(configurationPath) - local app = {} - setmetatable(app, application) +local Application = middle.class('Application') +function Application:initialize(configurationPath) assert(love.filesystem.exists(configurationPath), "Can't read app configuration") local contents, _ = love.filesystem.read(configurationPath) - app.config = json.decode(contents) - - return app + self.config = json.decode(contents) + self.gamesaves = gamesave(3) end -return application +return Application diff --git a/src/hawk/gamesave.lua b/src/hawk/gamesave.lua new file mode 100644 index 000000000..07056ff69 --- /dev/null +++ b/src/hawk/gamesave.lua @@ -0,0 +1,33 @@ +local middle = require 'hawk/middleclass' +local store = require 'hawk/store' + +local SCHEMA = 1 +local Gamesave = middle.class('Gamesave') + +function Gamesave:initialize(slots) + self.slots = { + store.load('gamesaves-alpha-' .. SCHEMA), + store.load('gamesaves-beta-' .. SCHEMA), + store.load('gamesaves-gamma-' .. SCHEMA), + } + self.active = 1 +end + +function Gamesave:all() + return self.slots +end + +function Gamesave:active() + return self.slots[self.active] +end + +function Gamesave:activate(slot) + self.active = slot + return true +end + +function Gamesave:save() + return self.slots[self.active]:flush() +end + +return Gamesave diff --git a/src/hawk/middleclass.lua b/src/hawk/middleclass.lua new file mode 100644 index 000000000..9052a60fb --- /dev/null +++ b/src/hawk/middleclass.lua @@ -0,0 +1,147 @@ +-- middleclass.lua - v2.0 (2011-09) +-- Copyright (c) 2011 Enrique GarcĂ­a Cota +-- 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. +-- Based on YaciCode, from Julien Patte and LuaObject, from Sebastien Rocca-Serra + +local _classes = setmetatable({}, {__mode = "k"}) + +local function _setClassDictionariesMetatables(klass) + local dict = klass.__instanceDict + dict.__index = dict + + local super = klass.super + if super then + local superStatic = super.static + setmetatable(dict, super.__instanceDict) + setmetatable(klass.static, { __index = function(_,k) return dict[k] or superStatic[k] end }) + else + setmetatable(klass.static, { __index = function(_,k) return dict[k] end }) + end +end + +local function _setClassMetatable(klass) + setmetatable(klass, { + __tostring = function() return "class " .. klass.name end, + __index = klass.static, + __newindex = klass.__instanceDict, + __call = function(self, ...) return self:new(...) end + }) +end + +local function _createClass(name, super) + local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} } + klass.subclasses = setmetatable({}, {__mode = "k"}) + + _setClassDictionariesMetatables(klass) + _setClassMetatable(klass) + _classes[klass] = true + + return klass +end + +local function _createLookupMetamethod(klass, name) + return function(...) + local method = klass.super[name] + assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" ) + return method(...) + end +end + +local function _setClassMetamethods(klass) + for _,m in ipairs(klass.__metamethods) do + klass[m]= _createLookupMetamethod(klass, m) + end +end + +local function _setDefaultInitializeMethod(klass, super) + klass.initialize = function(instance, ...) + return super.initialize(instance, ...) + end +end + +local function _includeMixin(klass, mixin) + assert(type(mixin)=='table', "mixin must be a table") + for name,method in pairs(mixin) do + if name ~= "included" and name ~= "static" then klass[name] = method end + end + if mixin.static then + for name,method in pairs(mixin.static) do + klass.static[name] = method + end + end + if type(mixin.included)=="function" then mixin:included(klass) end + klass.__mixins[mixin] = true +end + +local Object = _createClass("Object", nil) + +Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt', + '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' } + +function Object.static:allocate() + assert(_classes[self], "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") + return setmetatable({ class = self }, self.__instanceDict) +end + +function Object.static:new(...) + local instance = self:allocate() + instance:initialize(...) + return instance +end + +function Object.static:subclass(name) + assert(_classes[self], "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") + assert(type(name) == "string", "You must provide a name(string) for your class") + + local subclass = _createClass(name, self) + _setClassMetamethods(subclass) + _setDefaultInitializeMethod(subclass, self) + self.subclasses[subclass] = true + self:subclassed(subclass) + + return subclass +end + +function Object.static:subclassed(other) end + +function Object.static:include( ... ) + assert(_classes[self], "Make sure you that you are using 'Class:include' instead of 'Class.include'") + for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end + return self +end + +function Object:initialize() end + +function Object:__tostring() return "instance of " .. tostring(self.class) end + +local function class(name, super, ...) + super = super or Object + return super:subclass(name, ...) +end + +local function instanceOf(aClass, obj) + if not _classes[aClass] or type(obj) ~= 'table' or not _classes[obj.class] then return false end + if obj.class == aClass then return true end + return subclassOf(aClass, obj.class) +end + +local function subclassOf(other, aClass) + if not _classes[aClass] or not _classes[other] or aClass.super == nil then return false end + return aClass.super == other or subclassOf(other, aClass.super) +end + +local function includes(mixin, aClass) + if not _classes[aClass] then return false end + if aClass.__mixins[mixin] then return true end + return includes(mixin, aClass.super) +end + +return { + ["class"] = class, + ["subclassOf"] = subclassOf, + ["instanceOf"] = instanceOf, + ["includes"] = includes, +} + From 615715c8ea16e7389edb8d83022073fa289cdbdd Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Tue, 19 Mar 2013 19:31:28 -0700 Subject: [PATCH 2/2] Add gamesaves to scene triggers Also refactor modules as classes, which removes annoying boiler plate --- src/controls.lua | 2 +- src/hawk/gamesave.lua | 14 +++++++------- src/hawk/store.lua | 38 ++++++++++++++++++-------------------- src/nodes/scenetrigger.lua | 13 ++++++++----- src/options.lua | 2 +- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/controls.lua b/src/controls.lua index a44c922f1..c899628d7 100644 --- a/src/controls.lua +++ b/src/controls.lua @@ -1,6 +1,6 @@ local store = require 'hawk/store' -local db = store.load('controls-1') +local db = store('controls-1') local controls = {} local buttonmap = db:get('buttonmap', { diff --git a/src/hawk/gamesave.lua b/src/hawk/gamesave.lua index 07056ff69..cb07a65bb 100644 --- a/src/hawk/gamesave.lua +++ b/src/hawk/gamesave.lua @@ -6,11 +6,11 @@ local Gamesave = middle.class('Gamesave') function Gamesave:initialize(slots) self.slots = { - store.load('gamesaves-alpha-' .. SCHEMA), - store.load('gamesaves-beta-' .. SCHEMA), - store.load('gamesaves-gamma-' .. SCHEMA), + store('gamesaves-alpha-' .. SCHEMA), + store('gamesaves-beta-' .. SCHEMA), + store('gamesaves-gamma-' .. SCHEMA), } - self.active = 1 + self._active = 1 end function Gamesave:all() @@ -18,16 +18,16 @@ function Gamesave:all() end function Gamesave:active() - return self.slots[self.active] + return self.slots[self._active] end function Gamesave:activate(slot) - self.active = slot + self._active = slot return true end function Gamesave:save() - return self.slots[self.active]:flush() + return self.slots[self._active]:flush() end return Gamesave diff --git a/src/hawk/store.lua b/src/hawk/store.lua index b77b9df59..92a862954 100644 --- a/src/hawk/store.lua +++ b/src/hawk/store.lua @@ -1,27 +1,25 @@ local json = require 'hawk/json' +local middle = require 'hawk/middleclass' -local datastore = {} -datastore.__index = datastore +local Datastore = middle.class('Datastore') -function datastore.load(namespace) - local db = {} - setmetatable(db, datastore) +function Datastore:initialize(namespace) + self.path = namespace .. ".json" - db.path = namespace .. ".json" - - if not love.filesystem.exists(db.path) then - love.filesystem.write(db.path, json.encode({})) + if not love.filesystem.exists(self.path) then + love.filesystem.write(self.path, json.encode({})) end - local contents, _ = love.filesystem.read(db.path) - db.cache = json.decode(contents) - - return db + self:refresh() end +function Datastore:refresh() + local contents, _ = love.filesystem.read(self.path) + self._cache = json.decode(contents) +end -function datastore:get(key, default) - value = self.cache[key] +function Datastore:get(key, default) + value = self._cache[key] if value == nil then return default @@ -30,13 +28,13 @@ function datastore:get(key, default) return value end -function datastore:set(key, value) - self.cache[key] = value +function Datastore:set(key, value) + self._cache[key] = value end -- Save the contents of the datastore to disk -function datastore:flush() - love.filesystem.write(self.path, json.encode(self.cache)) +function Datastore:flush() + love.filesystem.write(self.path, json.encode(self._cache)) end -return datastore +return Datastore diff --git a/src/nodes/scenetrigger.lua b/src/nodes/scenetrigger.lua index afccd5853..a2f1ecc2f 100644 --- a/src/nodes/scenetrigger.lua +++ b/src/nodes/scenetrigger.lua @@ -1,4 +1,4 @@ -local store = require 'hawk/store' +local app = require 'app' local anim8 = require 'vendor/anim8' local gamestate = require 'vendor/gamestate' @@ -9,7 +9,7 @@ local machine = require 'datastructures/lsm/statemachine' local game = require 'game' local camera = require 'camera' -local KEY = 'gamesaves.1.cuttriggers.' +local NAMESPACE = 'cuttriggers.' local head = love.graphics.newImage('images/cornelius_head.png') local g = anim8.newGrid(144, 192, head:getWidth(), head:getHeight()) @@ -19,7 +19,6 @@ local timeline = { opacity=0 } -local db = store.load('gamesave1-1') local SceneTrigger = {} @@ -27,12 +26,15 @@ SceneTrigger.__index = SceneTrigger SceneTrigger.isTrigger = true function SceneTrigger.new(node, collider, layer) + assert(node.properties.cutscene, "A cutscene to trigger is required") local trigger = {} setmetatable(trigger, SceneTrigger) trigger.x = node.x trigger.y = node.y + trigger.db = app.gamesaves:active() + trigger.key = NAMESPACE .. node.properties.cutscene - if db:get(KEY .. node.properties.cutscene, false) then --already seen + if trigger.db:get(trigger.key, false) then --already seen return trigger end @@ -41,7 +43,7 @@ function SceneTrigger.new(node, collider, layer) -- Figure out how to "mix this in" trigger.state = machine.create({ - initial = db:get(KEY, 'ready'), + initial = 'ready', events = { {name = 'start', from = 'ready', to = 'playing'}, {name = 'stop', from = 'playing', to = 'finished'}, @@ -101,6 +103,7 @@ function SceneTrigger:draw(player) current.player.controlState:standard() current.trackPlayer = true current.scene = nil + self.db:set(self.key, true) end end diff --git a/src/options.lua b/src/options.lua index b59b90cfa..87316b514 100644 --- a/src/options.lua +++ b/src/options.lua @@ -10,7 +10,7 @@ local window = require 'window' local controls = require 'controls' local VerticalParticles = require "verticalparticles" -local db = store.load('options-1') +local db = store('options-1') function state:init() VerticalParticles.init()