Skip to content

Commit

Permalink
Lua API: Add inline documentation for pretty much everything and conv…
Browse files Browse the repository at this point in the history
…ert existing docs (wesnoth#6483)


The new format is EmmyLua-based and can be used with (at least) Visual Studio Code.
  • Loading branch information
CelticMinstrel authored and Asheviere committed Feb 18, 2022
1 parent 506c5bb commit b59c7a4
Show file tree
Hide file tree
Showing 37 changed files with 2,764 additions and 104 deletions.
38 changes: 24 additions & 14 deletions data/lua/core/_initial.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,34 @@ print("Loading Lua core files...")
local _ = wesnoth.textdomain "wesnoth"

-- Marks a function or subtable as deprecated.
-- Parameters:
---- elem_name: the full name of the element being deprecated (including the module)
---- replacement: the name of the element that will replace it (including the module)
---- level: deprecation level (1-4)
---- version: the version at which the element may be removed (level 2 or 3 only)
---- Set to nil if deprecation level is 1 or 4
---- elem: The actual element being deprecated, ignored if level is 4
---- detail_msg: An optional message to add to the deprecation message
function wesnoth.deprecate_api(elem_name, replacement, level, version, elem, detail_msg)
---@generic T
---@param elem_name string the full name of the element being deprecated (including the module), to be shown in the deprecation message
---@param replacement_name string the name of the element that will replace it (including the module), to be shown in the deprecation message
--- Can be nil if there is not replacement, though this should be an unlikely situation
---@param level '1'|'2'|'3'|'4' deprecation level (1-4)
---@param version string|nil the version at which the element may be removed (level 2 or 3 only)
--- Set to nil if deprecation level is 1 or 4
--- Will be shown in the deprecation message
---@param elem T Implementation of the compatibility layer, ignored if level is 4.
--- This can be the original, pre-deprecated element, but it does not have to be.
--- It could also be a wrapper that presents a different API, for example.
--- If deprecating a function, that would mean a wrapper function that calls the new API.
--- If deprecating a table, you would need to provide a table with __index and __newindex metamethods that call the new API.
--- This is the only argument that affects the functionality of the resulting deprecation wrapper.
---@param detail_msg? string An optional message to add to the deprecation message
---@return T elem_deprecated #A wrapper around the element, which triggers a deprecation message when used.
--- If it is a function, the message is triggered the first time it is called.
--- If it is a table, the message is triggered when a key is written or read on the table.
function wesnoth.deprecate_api(elem_name, replacement_name, level, version, elem, detail_msg)
if wesnoth.game_config.strict_lua then return nil end
local message = detail_msg or ''
if replacement then
message = message .. " " .. (_"(Note: You should use $replacement instead in new code)"):vformat{replacement = replacement}
if replacement_name then
message = message .. " " .. (_"(Note: You should use $replacement instead in new code)"):vformat{replacement = replacement_name}
end
if type(level) ~= "number" or level < 1 or level > 4 then
local err_params = {level = level}
-- Note: This message is duplicated in src/deprecation.cpp
-- Any changes should be mirrorred there.
-- Any changes should be mirrored there.
error((_"Invalid deprecation level $level (should be 1-4)"):vformat(err_params))
end
local msg_shown = false
Expand Down Expand Up @@ -55,7 +65,7 @@ function wesnoth.deprecate_api(elem_name, replacement, level, version, elem, det
if type(old_mt) ~= "table" then
-- See https://github.com/wesnoth/wesnoth/issues/4584#issuecomment-555788446
wesnoth.log('warn', "Attempted to deprecate a table with a masked metatable: " ..
elem_name .. " -> " .. replacement .. ", where getmetatable(" .. elem_name .. ") = " .. tostring(old_mt))
elem_name .. " -> " .. replacement_name .. ", where getmetatable(" .. elem_name .. ") = " .. tostring(old_mt))
return elem
end
local mt = {}
Expand All @@ -79,7 +89,7 @@ function wesnoth.deprecate_api(elem_name, replacement, level, version, elem, det
return setmetatable({}, mt)
else
wesnoth.log('warn', "Attempted to deprecate something that is not a table or function: " ..
elem_name .. " -> " .. replacement .. ", where " .. elem_name .. " = " .. tostring(elem))
elem_name .. " -> " .. replacement_name .. ", which is " .. tostring(elem))
end
return elem
end
Expand Down
2 changes: 2 additions & 0 deletions data/lua/core/as_text.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ local function value_to_text(obj)
end
end

---Convert an arbitrary value (especially a table) to a string for debugging
---@return string
function wesnoth.as_text(...)
local result = {}
local n = 1
Expand Down
1 change: 1 addition & 0 deletions data/lua/core/filesystem.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
--[========[File Handling]========]
print("Loading filesystem module...")

---Valid asset types, used as the type argument for have_asset and resolve_asset
filesystem.asset_type = {
IMAGE = 'images',
SOUND = 'sounds',
Expand Down
18 changes: 11 additions & 7 deletions data/lua/core/gui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
--[========[GUI2 Dialog Manipulations]========]
print("Loading GUI module...")

---Show a basic alert dialog with a single button
---@param title string Dialog title string
---@param msg string Detail message
function gui.alert(title, msg)
if not msg then
msg = title;
Expand All @@ -10,6 +13,10 @@ function gui.alert(title, msg)
gui.show_prompt(title, msg, "ok", true)
end

---Show a basic prompt dialog with two buttons
---@param title string Dialog title string
---@param msg string Detail message
---@return boolean #True if OK or Yes was clicked
function gui.confirm(title, msg)
if not msg then
msg = title;
Expand All @@ -18,13 +25,10 @@ function gui.confirm(title, msg)
return gui.show_prompt(title, msg, "yes_no", true)
end

--! Displays a WML message box with attributes from table @attr and options
--! from table @options.
--! @return the index of the selected option.
--! @code
--! local result = gui.get_user_choice({ speaker = "narrator" },
--! { "Choice 1", "Choice 2" })
--! @endcode
---Displays a WML message box with attributes from attr and options from options.
---@param attr WML The contents of a [message] tag, without any [option]s.
---@param options string[]|gui_narration_option_info[] A list of options to show in the message box
---@return integer #the index of the selected option.
function gui.get_user_choice(attr, options)
local result = 0
function gui.__user_choice_helper(i)
Expand Down
7 changes: 5 additions & 2 deletions data/lua/core/interface.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then

wesnoth.interface.select_unit = wesnoth.units.select

--! Fakes the move of a unit satisfying the given @a filter to position @a x, @a y.
--! @note Usable only during WML actions.
---Fakes the move of a unit satisfying the given filter to position x, y.
---Usable only during WML actions.
---@param filter WML
---@param to_x integer
---@param to_y integer
function wesnoth.interface.move_unit_fake(filter, to_x, to_y)
local moving_unit = wesnoth.units.find_on_map(filter)[1]
local from_x, from_y = moving_unit.x, moving_unit.y
Expand Down
123 changes: 116 additions & 7 deletions data/lua/core/map.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
--[========[Map module]========]
print("Loading map module...")

---Splits a terrain code into base and overlay
---@param code string
---@return string #The base terrain, if any - an empty string if there's no base
---@return string|nil #The overlay, if any - nil if there's no overlay
function wesnoth.map.split_terrain_code(code)
return table.unpack(code:split('^', {remove_empty = false}))
end

---Read a location from the front of a variable argument list.
---@alias read_location_count
---| '0' #Indicates no location was found.
---| '1' #A location-like object was found - either an array of two integers, or a table or userdata with x and y keys.
---| '2' #Two integer arguments were found and interpreted as the x and y coordinates respectively.
---@return location|nil #The location, if one was found, or nil otherwise
---@return read_location_count count #The number of arguments used to extract the location.
function wesnoth.map.read_location(...)
local x, y = ...
if x == nil then return nil, 0 end
Expand All @@ -28,6 +39,11 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
-- A A^ A^B ^ ^B
-- implied mode:
-- both base both overlay overlay

---Adjusts a terrain code to produce one that will replace the base terrain only,
---when the adjusted code is assigned to a terrain hex on the map
---@param code string A terrain code
---@return string #The adjusted terrain code
function wesnoth.map.replace_base(code)
local base, overlay = wesnoth.map.split_terrain_code(code)
if base == nil then -- ^ or ^B
Expand All @@ -39,6 +55,10 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
end
end

---Adjusts a terrain code to produce one that will replace the overlay terrain only,
---when the adjusted code is assigned to a terrain hex on the map
---@param code string A terrain code
---@return string #The adjusted terrain code
function wesnoth.map.replace_overlay(code)
local base, overlay = wesnoth.map.split_terrain_code(code)
if overlay == nil or overlay == '' then -- A or A^
Expand All @@ -50,6 +70,10 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
end
end

---Adjusts a terrain code to produce one that will replace both the base and overlay terrains,
---when the adjusted code is assigned to a terrain hex on the map
---@param code string A terrain code
---@return string #The adjusted terrain code
function wesnoth.map.replace_both(code)
local base, overlay = wesnoth.map.split_terrain_code(code)
if base == '' then -- ^ or ^B
Expand All @@ -66,6 +90,11 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
end
end

---Iterate over on-map hexes adjacent to a given hex.
---@param map terrain_map
---@return fun()
---@overload fun(map:terrain_map, loc:location)
---@overload fun(map:terrain_map, x:integer, y:integer)
function wesnoth.map.iter_adjacent(map, ...)
local where, n = wesnoth.map.read_location(...)
if n == 0 then error('wesnoth.map.iter_adjacent: missing location') end
Expand All @@ -86,6 +115,22 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
end

if wesnoth.kernel_type() == "Game Lua Kernel" then
---Represents a reference to a single hex on the map
---@class terrain_hex
---@field x integer
---@field y integer
---@field fogged boolean Whether the hex is fogged
---@field shrouded boolean Whether the hex is shrouded
---@field team_label? string|tstring The label on this hex visible to the current team
---@field global_label? string|tstring The label on this hex visible to teams who don't have a team label there
---@field label? string|tstring The visible label on this hex
---@field terrain string The terrain code of the hex
---@field base_terrain string The terrain code without the overlay
---@field overlay_terrain string The overlay terrain code without the base
---@field info terrain_info The properties of this terrain
---@field time_of_day time_info The base time of day on this hex from the schedule
---@field illuminated_time time_info The time of day on this hex, adjusted for illumination effects
local hex_methods = {}
local hex_mt = {__metatable = 'terrain hex reference'}

function hex_mt.__index(self, key)
Expand Down Expand Up @@ -120,7 +165,7 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
elseif key == 2 then
return self.y
elseif type(key) ~= string or (#key > 0 and key[0] ~= '_') then
return hex_mt[key]
return hex_methods[key]
end
end

Expand Down Expand Up @@ -167,48 +212,75 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
end
end

function hex_mt:fogged_for(side)
---Test if the hex is under fog for a specific side
---@param side integer|side
---@return boolean
function hex_methods:fogged_for(side)
return wesnoth.sides.is_fogged(side, self)
end

function hex_mt:shrouded_for(side)
---Test if the hex is under shroud for a specific side
---@param side integer|side
---@return boolean
function hex_methods:shrouded_for(side)
return wesnoth.sides.is_shrouded(side, self)
end

function hex_mt:set_shrouded(side, val)
---Set whether the hex is shrouded for a specific side
---@param side integer|side
---@param val boolean
function hex_methods:set_shrouded(side, val)
if val then
wesnoth.sides.place_shroud(side, {self})
else
wesnoth.sides.remove_shroud(side, {self})
end
end

function hex_mt:set_fogged(side, val)
---Set whether the hex is fogged for a specific side
---@param side integer|side
---@param val boolean
function hex_methods:set_fogged(side, val)
if val then
wesnoth.sides.place_fog(side, {self})
else
wesnoth.sides.remove_fog(side, {self})
end
end

function hex_mt:label_for(who)
---Get a label placed by a specific side
---@param who integer
---@return label_info
function hex_methods:label_for(who)
return wesnoth.map.get_label(self.x, self.y, who)
end

function hex_mt:matches(filter)
---Test if the hex matches a filter
---@param filter WML
---@return boolean
function hex_methods:matches(filter)
return wesnoth.map.matches(self.x, self.y, filter)
end

-- Backwards compatibility - length is always 2
hex_mt.__len = wesnoth.deprecate_api('#location', 'nil', 3, '1.17', function() return 2 end, 'Using the length of a location as a validity test is no longer supported. You should represent an invalid location by nil instead.')

---Get a hex reference to alias specific location
---@param x integer
---@param y integer
---@return terrain_hex
---@overload fun(loc:location):terrain_hex
function wesnoth.map.get(x, y)
local loc, n = wesnoth.map.read_location(x, y)
if n == 0 then error('Missing or invalid coordinate') end
return setmetatable(loc, hex_mt)
end

local find_locations = wesnoth.map.find
---Find a list of locations matching a filter
---@param cfg WML
---@param ref_unit? unit
---@return terrain_hex[]
function wesnoth.map.find(cfg, ref_unit)
local hexes = find_locations(cfg, ref_unit)
for i = 1, #hexes do
Expand Down Expand Up @@ -272,36 +344,73 @@ end

if wesnoth.kernel_type() == "Mapgen Lua Kernel" then
wesnoth.map.filter_tags = {
---Match specific terrains
---@param terrain string
---@return terrain_filter_tag
terrain = function(terrain)
return { "terrain", terrain }
end,
---Match all the nested filters
---@vararg terrain_filter_tag
---@return terrain_filter_tag
all = function(...)
return { "all", ... }
end,
---Match at least one of the nested filters
---@vararg terrain_filter_tag
any = function(...)
return { "any", ... }
end,
---Match none of the nested filters
---@vararg terrain_filter_tag
---@return terrain_filter_tag
none = function(...)
return { "none", ... }
end,
---Match not all of the nested filters
---@vararg terrain_filter_tag
---@return terrain_filter_tag
notall = function(...)
return { "notall", ... }
end,
---Match adjacent hexes
---@param f terrain_filter
---@param adj direction[]
---@param count integer|string A range list
---@return terrain_filter_tag
adjacent = function(f, adj, count)
return { "adjacent", f, adjacent = adj, count = count }
end,
---Match hexes from a separate list.
---Specify the list in the second argument to wesnoth.map.filter()
---@param terrain string
---@return terrain_filter_tag
find_in = function(terrain)
return { "find_in", terrain }
end,
---Match hexes within a given distance
---@param r integer
---@param f terrain_filter_tag
---@param f_r terrain_filter_tag
---@return terrain_filter_tag
radius = function(r, f, f_r)
return { "radius", r, f, filter_radius = f_r}
end,
---Match hexes by x coordinate
---@param terrain integer|string A range list
---@return terrain_filter_tag
x = function(terrain)
return { "x", terrain }
end,
---Match hexes by y coordinate
---@param terrain integer|string A range list
---@return terrain_filter_tag
y = function(terrain)
return { "y", terrain }
end,
---Match a specific location
---@param loc location
---@return terrain_filter_tag
is_loc = function(loc)
return f.all(f.x(loc[1]), f.y(loc[2]))
end
Expand Down

0 comments on commit b59c7a4

Please sign in to comment.