Skip to content

Commit

Permalink
Merge pull request #357 from Ruin0x11/feature/schema-validation
Browse files Browse the repository at this point in the history
Typechecker; typecheck data entries
  • Loading branch information
Ruin0x11 committed Sep 12, 2021
2 parents 89470e0 + ac47cc7 commit 25ceb45
Show file tree
Hide file tree
Showing 193 changed files with 5,242 additions and 3,810 deletions.
4 changes: 2 additions & 2 deletions editor/emacs/open-nefia.el
Original file line number Diff line number Diff line change
Expand Up @@ -607,14 +607,14 @@ removed. Return the new string. If STRING is nil, return nil."
(defun open-nefia--run-lua (file switches)
(with-current-buffer (get-buffer-create (open-nefia--repl-buffer-name))
(erase-buffer)
(compilation-shell-minor-mode 1)
(let ((process (apply 'start-process (buffer-name)
(current-buffer)
(open-nefia--lua-headless-executable-name)
(append (list (open-nefia--repl-file file)) switches))))
(ansi-color-for-comint-mode-on)
(comint-mode)
(set-process-filter process 'comint-output-filter))))
(set-process-filter process 'comint-output-filter)
(compilation-shell-minor-mode 1))))

(defun open-nefia--start-repl-1 (file &rest switches)
(save-some-buffers (not compilation-ask-about-save)
Expand Down
1 change: 1 addition & 0 deletions src/.luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ globals = {
"_IS_LOVEJS",
"love",
"class",
"types",
"inspect",
"fun",
"_ppr",
Expand Down
17 changes: 3 additions & 14 deletions src/api/Activity.lua
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
local Object = require("api.Object")
local data = require("internal.data")

local Activity = {}

function Activity.create(id, params)
local obj = Object.generate_from("base.activity", id)
function Activity.create(_id, params)
local obj = Object.generate_from("base.activity", _id)
Object.finalize(obj)

obj.params = {}
local activity = data["base.activity"]:ensure(id)
for property, ty in pairs(activity.params or {}) do
local value = params[property]
-- everything is implicitly optional for now, until we get a better
-- typechecker
if value ~= nil and type(value) ~= ty then
error(("Activity '%s' requires parameter '%s' of type %s, got '%s'"):format(id, property, ty, value))
end
obj.params[property] = value
end
obj.params = Object.copy_params(obj.proto.params, params, "base.activity", _id)

return obj
end
Expand Down
2 changes: 2 additions & 0 deletions src/api/Enum.lua
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ Enum.AiBehavior = Enum.new("AiBehavior", {

-- >>>>>>>> shade2/init.hsp:825 #enum global fltGoblin=1 ..
Enum.CharaCategory = Enum.new("CharaCategory", {
None = 0,
Goblin = 1,
Orc = 2,
Slime = 3,
Expand Down Expand Up @@ -172,6 +173,7 @@ Enum.CharaCategory = Enum.new("CharaCategory", {

-- >>>>>>>> shade2/init.hsp:815 #enum global fltSp=1 ..
Enum.FltSelect = Enum.new("FltSelect", {
None = 0,
Sp = 1,
Unique = 2,
SpUnique = 3,
Expand Down
31 changes: 2 additions & 29 deletions src/api/Feat.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
--- @module Feat
local data = require("internal.data")
local Log = require("api.Log")

local Event = require("api.Event")
local Map = require("api.Map")
local Object = require("api.Object")
local MapObject = require("api.MapObject")
local ILocation = require("api.ILocation")
local field = require("game.field")
Expand Down Expand Up @@ -55,29 +54,6 @@ function Feat.is_alive(feat, map)
return their_map.uid == map.uid
end

local function copy_params(feat, params)
local proto = data["base.feat"]:ensure(feat._id)
local found = table.set{}
for property, ty in pairs(proto.params or {}) do
local value = params[property]
-- everything is implicitly optional for now, until we get a better
-- typechecker
if value ~= nil and type(value) ~= ty then
error(("Feat '%s' requires parameter '%s' of type %s, got '%s'"):format(feat._id, property, ty, value))
found[property] = true
end
feat.params[property] = value
end

if table.count(found) ~= table.count(params) then
for k, v in pairs(params) do
if not proto.params[k] then
error(("Feat '%s' does not accept parameter '%s'"):format(feat._id, k))
end
end
end
end

--- Creates a new feat. Returns the feat on success, or nil if
--- creation failed.
---
Expand Down Expand Up @@ -134,10 +110,7 @@ function Feat.create(id, x, y, params, where)
}
local feat = MapObject.generate_from("base.feat", id)

feat.params = {}
if params.params then
copy_params(feat, params.params)
end
feat.params = Object.copy_params(feat.proto.params, params.params, "base.feat", id)

if where then
feat = where:take_object(feat, x, y)
Expand Down
2 changes: 1 addition & 1 deletion src/api/InstancedArea.lua
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ end

function InstancedArea:starting_floor()
local archetype = self:archetype()
if archetype and archetype.floors then
if archetype and #archetype.floors > 0 then
return fun.iter(table.keys(archetype.floors)):min()
end

Expand Down
2 changes: 1 addition & 1 deletion src/api/InstancedMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ local fallbacks = {
appearance = "",
tileset = "elona.dungeon",
tile_type = 2,
default_ai_calm = 1,
default_ai_calm = "base.calm_roam",
crowd_density = 0,
max_crowd_density = 0,
is_user_map = false,
Expand Down
52 changes: 52 additions & 0 deletions src/api/Object.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ function Object.generate(proto)
local obj = object.deserialize(proto)
assert(obj.proto)

local fallbacks = data.fallbacks[obj._type]
obj:mod_base_with(table.deepcopy(fallbacks), "merge")

return obj
end

Expand Down Expand Up @@ -137,4 +140,53 @@ function Object.mock(mt, tbl)
return obj
end

-- Validates a params table against the type definitions in an object's data
-- entry.
--
-- For example, a door feat will be able to have an unlock difficulty set on it.
-- `proto_params` is the `params` table in the `base.feat`'s data definition and
-- looks like { difficulty = types.number }, and `passed_params` would have been
-- passed to `Feat.create()` and looks something like { difficulty = 25 }.
function Object.copy_params(proto_params, passed_params, _type, _id)
proto_params = proto_params or {}
passed_params = passed_params or {}

local result = {}

local found = table.set{}

for property, entry in pairs(proto_params) do
local value = passed_params[property]
local error_msg = "%s '%s' received invalid value for parameter '%s': %s"
local checker
if types.is_type_checker(entry) then
checker = entry
else
checker = entry.type
if not types.is_type_checker(checker) then
error(("%s '%s' has invalid type checker for parameter '%s': %s"):format(_type, _id, property, checker))
end
if value == nil then
value = entry.default
error_msg = "%s '%s' is missing required parameter '%s': %s"
end
end
local ok, err = types.check(value, checker)
if not ok then
error((error_msg):format(_type, _id, property, err))
end
result[property] = value
end

if table.count(found) ~= table.count(passed_params) then
for k, v in pairs(passed_params) do
if not proto_params[k] then
error(("%s '%s' does not accept parameter '%s'"):format(_type, _id, k))
end
end
end

return result
end

return Object
3 changes: 0 additions & 3 deletions src/api/chara/IChara.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,6 @@ end
--- Finishes initializing this character. All characters must run this
--- function sometime after running pre_build() before being used.
function IChara:build()
local fallbacks = data.fallbacks["base.chara"]
self:mod_base_with(table.deepcopy(fallbacks), "merge")

self.state = "Alive"

self.target = nil
Expand Down
25 changes: 0 additions & 25 deletions src/api/feat/ITrap.lua

This file was deleted.

14 changes: 8 additions & 6 deletions src/api/gui/IUiLayer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,14 @@ function IUiLayer:query(z_order)
success, res, canceled = xpcall(
function()
if config.base.update_unfocused_ui_layers then
local layers = draw.get_layers()
for i = 1, #layers-1 do
local layer = layers[i].layer
-- HACK
if not class.is_an(IHud, layer) then
layer:update(dt, false)
if self:is_querying() then
local layers = draw.get_layers()
for i = 1, #layers-1 do
local layer = layers[i].layer
-- HACK
if not class.is_an(IHud, layer) then
layer:update(dt, false)
end
end
end
end
Expand Down
4 changes: 0 additions & 4 deletions src/api/gui/hud/UiStatusEffects.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ end
local function make_status_indicators(_, params, result)
local chara = params.chara

-- TODO ordering
for _, v in data["base.ui_indicator"]:iter() do
if v.indicator then
local raw = v.indicator(chara)
if type(raw) == "table" then
raw.text = I18N.get_optional(raw.text) or raw.text
raw.ordering = v.ordering
result[#result+1] = raw
end
end
Expand All @@ -50,12 +48,10 @@ function UiStatusEffects:set_data(player)

for _, ind in ipairs(raw) do
if type(ind) == "table" then
ind.ordering = ind.ordering or 100000
self.indicators[#self.indicators + 1] = ind
end
end

table.sort(raw, function(a, b) return a.ordering < b.ordering end)
if self.t then
self:calc_max_width()
end
Expand Down
5 changes: 3 additions & 2 deletions src/api/gui/menu/ChooseNpcMenu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ function ChooseNpcMenu.generate_list(charas, topic)
return true
end

local sort = function(a, b) return a.ordering > b.ordering end

local list = fun.iter(charas):filter(filter_)
:map(function(chara)
local gender = I18N.capitalize(I18N.get("ui.sex3." .. chara:calc("gender")))
Expand All @@ -83,10 +85,9 @@ function ChooseNpcMenu.generate_list(charas, topic)
ordering = chara:calc("level")
}
end)
:into_sorted(sort)
:to_list()

table.insertion_sort(list, function(a, b) return a.ordering > b.ordering end)

return list
end

Expand Down
14 changes: 9 additions & 5 deletions src/api/gui/menu/FeatsMenu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,10 @@ end
function FeatsMenu.generate_list(chara)
local list = {}

local sort = function(a, b) return (a.ordering or a.elona_id or 0) < (b.ordering or b.elona_id or 0) end

if chara.feats_acquirable > 0 then
list[#list+1] = { type = "header", text = I18N.get("trait.window.available_feats") }

for _, trait in data["base.trait"]:iter():filter(function(t) return t.type == "feat" end):into_sorted(sort) do
for _, trait in data["base.trait"]:iter():filter(function(t) return t.type == "feat" end) do
local level = chara:trait_level(trait._id)
if can_acquire_trait(trait, level, chara) then
local delta = 1
Expand All @@ -160,7 +158,7 @@ function FeatsMenu.generate_list(chara)

list[#list+1] = { type = "header", text = I18N.get("trait.window.feats_and_traits") }

for _, trait in data["base.trait"]:iter():into_sorted(sort) do
for _, trait in data["base.trait"]:iter() do
local level = chara:trait_level(trait._id)
if level ~= 0 then
local color = trait_color(level)
Expand All @@ -169,7 +167,7 @@ function FeatsMenu.generate_list(chara)
end
end

for _, trait_ind in data["base.trait_indicator"]:iter():into_sorted(sort) do
for _, trait_ind in data["base.trait_indicator"]:iter() do
if trait_ind.applies_to(chara) then
local indicator = trait_ind.make_indicator(chara)
list[#list+1] = {
Expand Down Expand Up @@ -317,12 +315,18 @@ function FeatsMenu:update()
-- Because most callbacks expect the given character to be built, you cannot call `refresh` during character making.
self.chara:refresh()
end

if self.chara_make and self.chara.feats_acquirable <= 0 then
return true
end

self.data = FeatsMenu.generate_list(self.chara)
self.pages:set_data(self.data)

local filter = function(_, i)
return i.type == "have" and i.trait._id == item.trait._id
end

local index = self.pages:iter_all_pages():enumerate():filter(filter):nth(1)
self.pages:select(index)

Expand Down
Loading

0 comments on commit 25ceb45

Please sign in to comment.