This commit is contained in:
Celtic Minstrel 2025-03-25 20:02:39 -04:00 committed by GitHub
commit 2813779e94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 862 additions and 101 deletions

View File

@ -31,7 +31,7 @@ local function wct_map_enemy_themed(race, pet, castle, village, chance)
return
end
--give themed castle
wesnoth.current.map[boss] = wesnoth.map.replace_base("K" .. castle)
wesnoth.current.map[boss] = wesnoth.map.replace.base("K" .. castle)
wesnoth.wml_actions.terrain {
terrain="C" .. castle,
wml.tag["and"] {

View File

@ -34,7 +34,7 @@ function set_terrain_impl(data)
for j = 1, num_tiles do
local loc = locs[i][j]
if chance >= 1000 or chance >= mathx.random(1000) then
map[loc] = wesnoth.map['replace_' .. layer](mathx.random_choice(terrains))
map[loc] = wesnoth.map.replace[layer](mathx.random_choice(terrains))
nlocs_changed = nlocs_changed + 1
end
end

View File

@ -5,32 +5,65 @@ local random = mathx.random
local callbacks = {}
function callbacks.generate_map(params)
local map = MG.create_map(params.map_width, params.map_height, params.terrain_wall)
local map = wesnoth.map.create(params.map_width, params.map_height, params.terrain_wall)
local function build_chamber(x, y, locs_set, size, jagged)
if locs_set:get(x,y) or not map:on_board(x, y) or size == 0 then
if locs_set:get(x,y) or not map:on_board(x, y, true) or size == 0 then
return
end
locs_set:insert(x,y)
for xn, yn in MG.adjacent_tiles(x, y) do
for xn, yn in map:iter_adjacent(x, y) do
if random(100) <= 100 - jagged then
build_chamber(xn, yn, locs_set, size - 1, jagged)
end
end
end
local function clear_tile(x, y)
if not map:on_board(x,y) then
local function clear_tile(x, y, terrain_clear)
if not map:on_board(x,y,true) then
return
end
if map:get_tile(x,y) == params.terrain_castle or map:get_tile(x,y) == params.terrain_keep then
if map[{x,y}] == params.terrain_castle or map[{x,y}] == params.terrain_keep then
return
end
local tile = mathx.random_choice(terrain_clear or params.terrain_clear)
map[{x, y}] = wesnoth.map.replace.both(tile)
local r = random(1000)
if r <= params.village_density then
map:set_tile(x, y, params.terrain_village)
local village = mathx.random_choice(params.terrain_village)
map[{x, y}] = wesnoth.map.replace.if_failed(village, 'overlay')
end
end
local function place_road(to_x, to_y, from_x, from_y, road_ops, terrain_clear)
if not map:on_board(to_x, to_y, true) then
return
end
if map[{to_x, to_y}] == params.terrain_castle or map[{to_x, to_y}] == params.terrain_keep then
return
end
local tile_op = road_ops[map[{to_x, to_y}]]
if tile_op then
if tile_op.convert_to_bridge and from_x and from_y then
local bridges = {}
for elem in tile_op.convert_to_bridge:gmatch("[^%s,][^,]*") do
table.insert(bridges, elem)
end
local dir = wesnoth.map.get_relative_dir(from_x, from_y, to_x, to_y)
if dir == 'n' or dir == 's' then
map[{to_x, to_y}] = wesnoth.map.replace.if_failed(bridges[1], 'overlay')
elseif dir == 'sw' or dir == 'ne' then
map[{to_x, to_y}] = wesnoth.map.replace.if_failed(bridges[2], 'overlay')
elseif dir == 'se' or dir == 'nw' then
map[{to_x, to_y}] = wesnoth.map.replace.if_failed(bridges[3], 'overlay')
end
elseif tile_op.convert_to then
local tile = mathx.random_choice(tile_op.convert_to)
map[{to_x, to_y}] = wesnoth.map.replace.both(tile)
end
else
map:set_tile(x, y, params.terrain_clear)
local tile = mathx.random_choice(terrain_clear or params.terrain_clear)
map[{to_x, to_y}] = wesnoth.map.replace.both(tile)
end
end
@ -39,25 +72,43 @@ function callbacks.generate_map(params)
local passages = {}
for chamber in wml.child_range(params, "chamber") do
if chamber.ignore then goto continue end
local chance = tonumber(chamber.chance) or 100
local x = chamber.x
local y = chamber.y
local x, y = MG.random_location(chamber.x, chamber.y)
-- Note: x,y run from (0,0) to (w+1,h+1)
if chamber.relative_to == "top-right" then
x = map.width - x - 1
elseif chamber.relative_to == "bottom-right" then
x = map.width - x - 1
y = map.height - y - 1
elseif chamber.relative_to == "bottom-left" then
y = map.height - y - 1
elseif chamber.relative_to == "top-middle" then
x = math.ceil(map.width / 2) + x
elseif chamber.relative_to == "bottom-middle" then
x = math.ceil(map.width / 2) + x
y = map.height - y - 1
elseif chamber.relative_to == "middle-left" then
y = math.ceil(map.height / 2) + y
elseif chamber.relative_to == "middle-right" then
y = math.ceil(map.height / 2) + y
x = map.width - x - 1
elseif chamber.relative_to == "center" then
x = math.ceil(map.width / 2) + x
y = math.ceil(map.height / 2) + y
end -- Default is "top-left" which means no adjustments needed
local id = chamber.id
if chance == 0 or random(100) > chance then
-- Set chance to 0 so that the scenario generator can tell which chambers were used
params.chance = 0
goto continue
end
if type(chamber.require_player) == "number" and chamber.require_player > params.nplayers then
params.chance = 0
goto continue
end
-- Ditto, set it to 100
params.chance = 100
if type(x) == "string" then
local x_min, x_max = x:match("(%d+)-(%d+)")
x = random(tonumber(x_min), tonumber(x_max))
end
if type(y) == "string" then
local y_min, y_max = y:match("(%d+)-(%d+)")
y = random(tonumber(y_min), tonumber(y_max))
end
local locs_set = LS.create()
build_chamber(x, y, locs_set, chamber.size or 3, chamber.jagged or 0)
local items = {}
@ -71,19 +122,29 @@ function callbacks.generate_map(params)
locs_set = locs_set,
id = id,
items = items,
data = chamber,
})
chambers_by_id[id] = chambers[#chambers]
for passage in wml.child_range(chamber, "passage") do
if passage.ignore then goto continue end
local dst = chambers_by_id[passage.destination]
if dst ~= nil then
local road_costs, road_ops = {}, {}
for road in wml.child_range(passage, "road_cost") do
road_costs[road.terrain] = road.cost
road_ops[road.terrain] = road
end
table.insert(passages, {
start_x = x,
start_y = y,
dest_x = dst.center_x,
dest_y = dst.center_y,
data = passage,
costs = road_costs,
roads = road_ops,
})
end
::continue::
end
::continue::
end
@ -91,8 +152,8 @@ function callbacks.generate_map(params)
for i,v in ipairs(chambers) do
local locs_list = {}
for x, y in v.locs_set:stable_iter() do
clear_tile(x, y)
if map:on_inner_board(x, y) then
clear_tile(x, y, v.data.terrain_clear)
if map:on_board(x, y, false) then
table.insert(locs_list, {x,y})
end
end
@ -103,13 +164,13 @@ function callbacks.generate_map(params)
local x, y = table.unpack(loc)
if item.id then
map:add_location(x, y, item.id)
map.special_locations[item.id] = {x, y}
end
if item.place_castle then
map:set_tile(x, y, params.terrain_keep)
for x2, y2 in MG.adjacent_tiles(x, y) do
map:set_tile(x2, y2, params.terrain_castle)
map[{x, y}] = wesnoth.map.replace.both(params.terrain_keep)
for x2, y2 in map:iter_adjacent(x, y) do
map[{x2, y2}] = wesnoth.map.replace.both(params.terrain_castle)
end
end
end
@ -127,8 +188,10 @@ function callbacks.generate_map(params)
return math.huge
end
local res = 1.0
if map:get_tile(x, y) == params.terrain_wall then
res = laziness
local tile = map[{x, y}]
res = v.costs[tile] or 1.0
if tile == params.terrain_wall then
res = laziness * res
end
if windiness > 1 then
res = res * random(windiness)
@ -140,8 +203,18 @@ function callbacks.generate_map(params)
for j, loc in ipairs(path) do
local locs_set = LS.create()
build_chamber(loc[1], loc[2], locs_set, width, jagged)
local prev_x, prev_y
for x,y in locs_set:stable_iter() do
clear_tile(x, y)
local r = 1000
local ter_to_place
if v.data.place_villages then r = random(1000) end
if r <= params.village_density then
ter_to_place = v.data.terrain_village or params.terrain_village
else
ter_to_place = v.data.terrain_clear or params.terrain_clear
end
place_road(x, y, prev_x, prev_y, v.roads, ter_to_place)
prev_x, prev_y = x, y
end
end
end
@ -159,11 +232,11 @@ function callbacks.generate_map(params)
wml.error("Unknown transformation '" .. t .. "'")
end
end
map[transforms[random(#transforms)]](map)
MG[transforms[random(#transforms)]](map)
end
end
return tostring(map)
return map.data
end
function callbacks.generate_scenario(params)
@ -172,9 +245,16 @@ function callbacks.generate_scenario(params)
scenario.map_data = callbacks.generate_map(params)
for chamber in wml.child_range(params, "chamber") do
local chamber_items = wml.get_child(chamber, "items")
if chamber.chance == 100 and chamber_items then
-- TODO: Should we support [event]same_location_as_previous=yes?
if (chamber.chance or 100) == 100 and chamber_items then
for i,tag in ipairs(chamber_items) do
if tag.tag == 'event' and tag.contents.same_location_as_previous then
local evt_data = tag.contents;
evt_data.same_location_as_previous = nil
table.insert(evt_data, wml.tag.filter{
x = chamber.x,
y = chamber.y
})
end
table.insert(scenario, tag)
end
end

View File

@ -34,6 +34,13 @@ function wesnoth.map.read_location(...)
return nil, 0
end
function wesnoth.map.nearest_loc(to, candidates)
local F = wesnoth.require "functional"
return F.choose(candidates, function(loc)
return -wesnoth.map.distance_between(to, loc)
end)
end
if wesnoth.kernel_type() ~= "Application Lua Kernel" then
-- possible terrain string inputs:
-- A A^ A^B ^ ^B
@ -79,7 +86,7 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
if base == '' then -- ^ or ^B
-- There's no way to find a base to replace with in this case.
-- Could use the existing base, but that's not really replacing both, is it?
error('replace_both: no base terrain specified')
error('replace.both: no base terrain specified')
elseif overlay == '' then -- A^
-- This would normally mean replace base while preserving overlay,
-- but we actually want to replace base and clear overlay.
@ -89,6 +96,13 @@ if wesnoth.kernel_type() ~= "Application Lua Kernel" then
return code
end
end
wesnoth.map.replace = {
both = wesnoth.map.replace_both,
base = wesnoth.map.replace_base,
overlay = wesnoth.map.replace_overlay,
if_failed = wesnoth.map.replace_if_failed,
}
---Iterate over on-map hexes adjacent to a given hex.
---@param map terrain_map
@ -195,9 +209,9 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
elseif key == 'terrain' then
wesnoth.current.map[self] = val
elseif key == 'base_terrain' then
wesnoth.current.map[self] = wesnoth.map.replace_base(val)
wesnoth.current.map[self] = wesnoth.map.replace.base(val)
elseif key == 'overlay_terrain' then
wesnoth.current.map[self] = wesnoth.map.replace_overlay(val)
wesnoth.current.map[self] = wesnoth.map.replace.overlay(val)
elseif key == 1 then
self.x = val
elseif key == 2 then
@ -306,9 +320,9 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
if new_ter == '' or type(new_ter) ~= 'string' then error('set_terrain: expected terrain string') end
if replace_if_failed then
mode = mode or 'both'
new_ter = wesnoth.map.replace_if_failed(new_ter, mode)
new_ter = wesnoth.map.replace.if_failed(new_ter, mode)
elseif mode == 'both' or mode == 'base' or mode == 'overlay' then
new_ter = wesnoth.map['replace_' .. mode](new_ter)
new_ter = wesnoth.map.replace[mode](new_ter)
elseif mode ~= nil then
error('set_terrain: invalid mode')
end

View File

@ -1,13 +1,37 @@
local LS = wesnoth.require "location_set"
---@class mapgen_helper
local mapgen_helper, map_mt = {}, {__index = {}}
function mapgen_helper.create_map(width,height,default_terrain)
local map = setmetatable({w = width, h = height}, map_mt)
for i = 1, width * height do
table.insert(map, default_terrain or 'Gg')
---@class map_wrapper
---@field __map terrain_map
---Create a map.
---@deprecated Use wesnoth.map.create instead
---@param width integer
---@param height integer
---@param default_terrain string
---@return map_wrapper
local function create_map(width,height,default_terrain)
return setmetatable({__map = wesnoth.map.create(width, height, default_terrain)}, map_mt)
end
local function rand_from_ranges(list)
if type(list) == 'number' then return math.tointeger(list) end
local choices = {}
for n in stringx.iter_ranges(list) do
table.insert(choices, n)
end
return map
return math.tointeger(mathx.random_choice(choices)) or 0
end
---Select a random location from lists of coordinates.
---@param x_list string
---@param y_list string
---@return integer
---@return integer
function mapgen_helper.random_location(x_list, y_list)
return rand_from_ranges(x_list), rand_from_ranges(y_list)
end
local valid_transforms = {
@ -16,91 +40,120 @@ local valid_transforms = {
flip_xy = true,
}
---Test whether a string is a valid transform
---@param t string
---@return boolean
function mapgen_helper.is_valid_transform(t)
return valid_transforms[t]
end
local function loc_to_index(map,x,y)
return x + 1 + y * map.w
end
---Set the tile at the specified location
---@param map map_wrapper
---@param x integer
---@param y integer
---@param val string
function map_mt.__index.set_tile(map, x, y, val)
map[loc_to_index(map, x, y)] = val
map.__map[{x, y}] = val
end
---Set the tile at the specified location
---@param map map_wrapper
---@param x integer
---@param y integer
---@return string
function map_mt.__index.get_tile(map, x, y)
return map[loc_to_index(map,x,y)]
return map.__map[{x, y}]
end
---Test if a tile is on the board (including the border)
---@param map map_wrapper
---@param x integer
---@param y integer
---@return boolean
function map_mt.__index.on_board(map, x, y)
return x >= 0 and y >= 0 and x < map.w and y < map.h
return map.__map:on_board(x, y, true)
end
---Test if a tile is on the board (excluding the border)
---@param map map_wrapper
---@param x integer
---@param y integer
---@return boolean
function map_mt.__index.on_inner_board(map, x, y)
return x >= 1 and y >= 1 and x < map.w - 1 and y < map.h - 1
return map.__map:on_board(x, y, false)
end
---Add a named location at the given coordinates
---@param map map_wrapper
---@param x integer
---@param y integer
---@param name string
function map_mt.__index.add_location(map, x, y, name)
if not map.locations then
map.locations = LS.create()
end
if map.locations:get(x, y) then
table.insert(map.locations:get(x, y), name)
else
map.locations:insert(x, y, {name})
end
map.__map.special_locations[name] = {x, y}
end
function map_mt.__index.flip_x(map)
for y = 0, map.h - 1 do
for x = 0, map.w - 1 do
local i = loc_to_index(map, x, y)
local j = loc_to_index(map, map.w - x - 1, y)
map[i], map[j] = map[j], map[i]
----Flip the map horizontally
---@param map terrain_map
function mapgen_helper.flip_x(map)
for x, y in map:iter(true) do
if x <= map.width / 2 or y <= map.height / 2 then
local x_opp = map.width - x - 1
map[{x,y}], map[{x_opp,y}] = map[{x_opp,y}], map[{x,y}]
end
end
end
function map_mt.__index.flip_y(map)
for x = 0, map.w - 1 do
for y = 0, map.h - 1 do
local i = loc_to_index(map, x, y)
local j = loc_to_index(map, x, map.h - y - 1)
map[i], map[j] = map[j], map[i]
end
for id, loc in map.special_locations do
loc.x = map.width - loc.x - 1
map.special_locations[id] = loc
end
end
function map_mt.__index.flip_xy(map)
map:flip_x()
map:flip_y()
---Flip the map vertically
---@param map terrain_map
function mapgen_helper.flip_y(map)
for x, y in map:iter(true) do
if x <= map.width / 2 or y <= map.height / 2 then
local y_opp = map.height - y - 1
map[{x,y}], map[{x,y_opp}] = map[{x,y_opp}], map[{x,y}]
end
end
for id, loc in map.special_locations do
loc.y = map.height - loc.y - 1
map.special_locations[id] = loc
end
end
---Flip the map diagonally
---@param map terrain_map
function mapgen_helper.flip_xy(map)
for x, y in map:iter(true) do
if x <= map.width / 2 or y <= map.height / 2 then
local x_opp = map.width - x - 1
local y_opp = map.height - y - 1
map[{x,y}], map[{x_opp,y_opp}] = map[{x_opp,y_opp}], map[{x,y}]
end
end
for id, loc in map.special_locations do
loc.x = map.width - loc.x - 1
loc.y = map.height - loc.y - 1
map.special_locations[id] = loc
end
end
---Convert to string
---@param map map_wrapper
---@return string
function map_mt.__tostring(map)
local map_builder = {}
-- The coordinates are 0-based to match the in-game coordinates
for y = 0, map.h - 1 do
local string_builder = {}
for x = 0, map.w - 1 do
local tile_string = map:get_tile(x, y)
if map.locations and map.locations:get(x,y) then
for i,v in ipairs(map.locations:get(x,y)) do
tile_string = v .. ' ' .. tile_string
end
end
table.insert(string_builder, tile_string)
end
assert(#string_builder == map.w)
table.insert(map_builder, table.concat(string_builder, ', '))
end
assert(#map_builder == map.h)
return table.concat(map_builder, '\n')
return map.__map.data
end
local adjacent_offset = {
{ {0,-1}, {1,-1}, {1,0}, {0,1}, {-1,0}, {-1,-1} },
{ {0,-1}, {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0} }
}
---Iterates over adjacent tiles
---@param x integer
---@param y integer
---@return fun():integer,integer
function mapgen_helper.adjacent_tiles(x, y)
local offset = adjacent_offset[2 - (x % 2)]
local i = 0
@ -114,4 +167,84 @@ function mapgen_helper.adjacent_tiles(x, y)
end
end
---@alias relative_anchor
---|'center'
---|'top-left'
---|'top-middle'
---|'top-right'
---|'bottom-left'
---|'bottom-middle'
---|'bottom-right'
---|'middle-left'
---|'middle-right'
---@class map_chamber : WMLTable
---@field ignore boolean
---@field id string
---@field x string
---@field y string
---@field terrain_clear string
---@field size integer
---@field jagged integer
---@field chance integer
---@field side integer
---@field relative_to relative_anchor
---@field require_player integer
---@class map_passage : WMLTable
---@field ignore boolean
---@field id string
---@field place_villages boolean
---@field destination string
---@field terrain_clear string
---@field windiness integer
---@field laziness integer
---@field width integer
---@field jagged integer
---@field chance integer
---Get a chamber by ID or index from the map generator settings.
---@param params WMLTable
---@param id_or_idx string|integer
---@return map_chamber?
function mapgen_helper.get_chamber(params, id_or_idx)
if type(id_or_idx) == 'number' then
local cfg, i = wml.get_nth_child(params, 'chamber', id_or_idx)
if not cfg then return nil end
return params[i].contents
elseif type(id_or_idx) == 'string' then
local cfg, i = wml.get_child(params, 'chamber', id_or_idx)
if not cfg then return nil end
return params[i].contents
end
end
---Get a passage by ID or index from the map generator settings.
---@param chamber map_chamber
---@param id_or_idx string|integer
---@return map_passage?
function mapgen_helper.get_passage(chamber, id_or_idx)
if type(id_or_idx) == 'number' then
local cfg, i = wml.get_nth_child(chamber, 'passage', id_or_idx)
if not cfg then return nil end
return chamber[i].contents
elseif type(id_or_idx) == 'string' then
local cfg, i = wml.get_child(chamber, 'passage', id_or_idx)
if not cfg then return nil end
return chamber[i].contents
end
end
mapgen_helper.create_map = wesnoth.deprecate_api('mapgen_helper.create_map', 'wesnoth.map.create', 1, nil, create_map)
mapgen_helper.adjacent_tiles = wesnoth.deprecate_api('mapgen_helper.adjacent_tiles', 'wesnoth.map.iter_adjacent', 1, nil, mapgen_helper.adjacent_tiles)
map_mt.__index.set_tile = wesnoth.deprecate_api('oldmap:set_tile(x,y,ter)', 'map[{x,y}]=ter', 1, nil, map_mt.__index.set_tile)
map_mt.__index.get_tile = wesnoth.deprecate_api('oldmap:get_tile(x,y)', 'map[{x,y}]', 1, nil, map_mt.__index.get_tile)
map_mt.__index.on_board = wesnoth.deprecate_api('oldmap:on_board(x,y)', 'map:on_board(x,y,true)', 1, nil, map_mt.__index.on_board)
map_mt.__index.on_inner_board = wesnoth.deprecate_api('oldmap:on_inner_board(x,y)', 'map:on_board(x,y,false)', 1, nil, map_mt.__index.on_inner_board)
map_mt.__index.add_location = wesnoth.deprecate_api('oldmap:add_location(x,y,name)', 'map.special_locations[name]={x,y}', 1, nil, map_mt.__index.add_location)
map_mt.__index.flip_x = wesnoth.deprecate_api('oldmap:flip_x()', 'mapgen_helper.flip_x(map)', 1, nil, function(m) mapgen_helper.flip_x(m) end)
map_mt.__index.flip_y = wesnoth.deprecate_api('oldmap:flip_y()', 'mapgen_helper.flip_y(map)', 1, nil, function(m) mapgen_helper.flip_y(m) end)
map_mt.__index.flip_xy = wesnoth.deprecate_api('oldmap:flip_xy()', 'mapgen_helper.flip_xy(map)', 1, nil, function(m) mapgen_helper.flip_xy(m) end)
map_mt.__tostring = wesnoth.deprecate_api('tostring(map)', 'map.data', 1, nil, map_mt.__tostring)
return mapgen_helper

View File

@ -476,8 +476,8 @@ function wml_actions.terrain(cfg)
cfg.terrain = nil
for i, loc in ipairs(wesnoth.map.find(cfg)) do
local replacement = cfg.replace_if_failed
and wesnoth.map.replace_if_failed(terrain, layer)
or wesnoth.map['replace_' .. layer](terrain)
and wesnoth.map.replace.if_failed(terrain, layer)
or wesnoth.map.replace[layer](terrain)
wesnoth.current.map[loc] = replacement
end
end

View File

@ -0,0 +1,141 @@
#textdomain wesnoth-lib
{gui/macros}
#define MAP_OPTION_CONTROL ID LABEL TYPE WML
[row]
[column]
grow_factor=0
horizontal_grow=true
border=all
border_size=5
[label]
definition=default
label={LABEL}
text_alignment=right
[/label]
[/column]
[column]
grow_factor=1
horizontal_grow=true
border=all
border_size=10
[{TYPE}]
id={ID}
{WML}
[/{TYPE}]
[/column]
[column]
grow_factor=0
horizontal_grow=true
border=all
border_size=5
{GUI_FORCE_WIDGET_MINIMUM_SIZE 100 0 (
[label]
id={ID}_label
definition=default
[/label]
)}
[/column]
[/row]
#enddef
[resolution]
definition = "default"
automatic_placement = true
vertical_placement = "center"
horizontal_placement = "center"
maximum_height = 600
[tooltip]
id=tooltip
[/tooltip]
[helptip]
id=tooltip
[/helptip]
[grid]
[row]
grow_factor=0
[column]
grow_factor=1
border=all
border_size=5
horizontal_alignment=left
[label]
definition=title
label=_"Map Generator Settings"
[/label]
[/column]
[/row]
[row]
grow_factor=1
[column]
horizontal_grow=true
vertical_grow=true
[grid]
{MAP_OPTION_CONTROL players _"Players:" slider (
definition=minimal
minimum_value=2
maximum_value=8
step_size=1
)}
{MAP_OPTION_CONTROL width _"Width:" slider (
definition=minimal
minimum_value=20
maximum_value=100
step_size=1
)}
{MAP_OPTION_CONTROL height _"Height:" slider (
definition=minimal
minimum_value=20
maximum_value=100
step_size=1
)}
{MAP_OPTION_CONTROL village_density _"Villages:" slider (
definition=minimal
minimum_value=0
maximum_value=50
step_size=1
)}
#textdomain wesnoth-multiplayer
{MAP_OPTION_CONTROL jagged _"Chamber Jaggedness:" slider (
definition=minimal
minimum_value=10
maximum_value=50
step_size=5
)}
{MAP_OPTION_CONTROL lake_size _"Lake Size:" slider (
definition=minimal
minimum_value=5
maximum_value=50
step_size=5
)}
{MAP_OPTION_CONTROL windiness _"Passage Windiness:" slider (
definition=minimal
minimum_value=1
maximum_value=20
step_size=1
)}
#textdomain wesnoth-lib
{MAP_OPTION_CONTROL roads "" toggle_button (
definition=checkbox
label=_"Roads Between Castles"
)}
[/grid]
[/column]
[/row]
[row]
grow_factor=0
[column]
border=all
border_size=5
horizontal_alignment=right
#textdomain wesnoth-lib
[button]
definition=default
id=ok
label=_"Close"
[/button]
[/column]
[/row]
[/grid]
[/resolution]

View File

@ -0,0 +1,119 @@
local params = ...
local MG = wesnoth.require "mapgen_helper"
local function pre_show(window)
window.players.value = params.nplayers
window.width.value = params.map_width
window.height.value = params.map_height
window.village_density.value = params.village_density
local central_chamber = MG.get_chamber(params, 'central_chamber')
if central_chamber then
window.jagged.value = central_chamber.jagged
else
error('cave_map_settings requires a [chamber] with id=central_chamber')
end
local lake = MG.get_chamber(params, 'lake')
if lake then
window.lake_size.value = lake.size
else
error('cave_map_settings requires a [chamber] with id=lake')
end
local first_player = MG.get_chamber(params, 'player_1')
if first_player then
local tunnel = MG.get_passage(first_player, 1)
if tunnel then
window.windiness.value = tunnel.windiness
else
error('cave_map_settings requires that each player [chamber] contains at least two [passage] tags')
end
local road = MG.get_passage(first_player, 2)
if road then
window.roads.selected = not road.ignore
else
error('cave_map_settings requires that each player [chamber] contains at least two [passage] tags')
end
else
error('cave_map_settings requires a [chamber] for each player with id=player_n where n is the player number')
end
local all_players = {first_player}
for i = 2, #params do
local next_player = MG.get_chamber(params, 'player_' .. i)
if next_player then
table.insert(all_players, next_player)
else
break
end
end
-- Init labels
window.players_label.label = window.players.value
window.width_label.label = window.width.value
window.height_label.label = window.height.value
window.village_density_label.label = window.village_density.value
window.jagged_label.label = window.jagged.value
window.lake_size_label.label = window.lake_size.value
window.windiness_label.label = window.windiness.value
-- Callbacks...
function window.players.on_modified()
params.nplayers = window.players.value
window.players_label.label = params.nplayers
end
function window.width.on_modified()
params.map_width = window.width.value
window.width_label.label = params.map_width
central_chamber.size = mathx.round((params.map_height + params.map_width) / 4)
end
function window.height.on_modified()
params.map_height = window.height.value
window.height_label.label = params.map_height
central_chamber.size = mathx.round((params.map_height + params.map_width) / 4)
end
function window.village_density.on_modified()
params.village_density = window.village_density.value
-- Need wesnoth-lib for the village density label
local _ = wesnoth.textdomain "wesnoth-lib"
window.village_density_label.label = (_"$villages/1000 tiles"):vformat{villages = params.village_density}
end
function window.jagged.on_modified()
local val = window.jagged.value
central_chamber.jagged = val
window.jagged_label.label = val
end
function window.lake_size.on_modified()
local val = window.lake_size.value
lake.size = val
window.lake_size_label.label = val
end
function window.windiness.on_modified()
local val = window.windiness.value
for i = 1, #all_players do
local tunnel = MG.get_passage(all_players[i], 1)
tunnel.windiness = val
end
window.windiness_label.label = val
end
function window.roads.on_modified()
local val = not window.roads.selected
for i = 1, #all_players do
local road = MG.get_passage(all_players[i], 2)
road.ignore = val
end
end
end
local dialog = wml.load "multiplayer/gui/cave_map_settings.cfg"
gui.show_dialog(wml.get_child(dialog, 'resolution'), pre_show)
return params

View File

@ -0,0 +1,212 @@
#textdomain wesnoth-multiplayer
#define CLEAR_TERRAINS
Rb,Rb,Rb,Rb,Rb,Rb,Rb,Rb^Tf,Rb^Ii,Sm,Sm,Uue,Rb^Fetd,Rb^Fdw#enddef
#define ROAD_COSTS
[road_cost]
terrain=Rb
cost=2
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Rb^Tf
cost=20
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Rb^Fdw
cost=20
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Uue
cost=10
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Rb^Ii
cost=2
convert_to=Ur^Ii
[/road_cost]
[road_cost]
terrain=Uue
cost=50
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Sm
cost=75
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Rb^Fetd
cost=75
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Rb^Fdw
cost=75
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Wwg
cost=100
convert_to_bridge=Wwg^Bw|,Wwg^Bw/,Wwg^Bw\
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Wwf
cost=100
convert_to_bridge=Wwf^Bw|,Wwf^Bw/,Wwf^Bw\
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Ur
cost=2
convert_to=Ur
[/road_cost]
[road_cost]
terrain=Ur^Ii
cost=2
convert_to=Ur^Ii
[/road_cost]
[road_cost]
terrain=Rb^Vu
cost=5
convert_to=Ur^Vu
[/road_cost]
[road_cost]
terrain=Rb^Vud
cost=5
convert_to=Ur^Vud
[/road_cost]
[road_cost]
terrain=Uue^Vud
cost=55
convert_to=Ur^Vud
[/road_cost]
# We don't want to carve a new tunnel now,
# so give walls a ridiculously high cost.
[road_cost]
terrain=Xue
cost=1000
convert_to=Uue
[/road_cost]
#enddef
#define PLAYER_CHAMBER NUMBER X Y REL
[chamber]
id=player_{NUMBER}
require_player={NUMBER}
x={X}
y={Y}
relative_to={REL}
size=5
jagged=2
[item_location]
id={NUMBER}
place_castle=yes
[/item_location]
[items]
[side]
side={NUMBER}
[/side]
[/items]
[passage]
destination=central_chamber
windiness=3
laziness=2
jagged=3
width=3
terrain_clear={CLEAR_TERRAINS}
place_villages=yes
# Try not to plow thru the lake, but if we must, use ford
[road_cost]
terrain=Wwg
cost=100
convert_to=Wwf
[/road_cost]
[/passage]
[passage]
ignore=no
destination=central_chamber
windiness=2
jagged=2
width=2
{ROAD_COSTS}
[/passage]
[/chamber]
#enddef
[multiplayer]
# This id is currently hardcoded by the random map generator of the editor
id=multiplayer_Random_Map_Cave
name= _ "Random map (Cave)"
description= _ "A random map set in a cave. Note: random maps are often unbalanced, but if you have time, you can regenerate them until you get a good one."
scenario_generation=lua
[generator]
id="cavegen"
config_name=_"Lua Cave Generator"
create_scenario=<<
return wesnoth.require("lua/cave_map_generator.lua").generate_scenario(...)
>>
user_config=<<
return wesnoth.dofile("multiplayer/gui/cave_map_settings.lua", ...)
>>
[scenario]
name= _ "Random map (Cave)"
id=multiplayer_Random_Map_Cave
{DEFAULT_MUSIC_PLAYLIST}
{DEFAULT_SCHEDULE}
[/scenario]
terrain_clear={CLEAR_TERRAINS}
terrain_wall=Xue
terrain_castle=Co
terrain_keep=Ko
terrain_village=Rb^Vu,Rb^Vu,Rb^Vu,Rb^Vu,Rb^Vu,Rb^Vud,Rb^Vud,Uue^Vud
map_width=40
map_height=40
village_density=25
nplayers=4
[chamber]
id=central_chamber
x=14-26
y=14-26
size=20
jagged=30
[/chamber]
[chamber]
id=lake
x=14-26
y=14-26
size=5
jagged=12
terrain_clear=Wwg
# Without this, there is a chance that the lake is entirely disconnected from everything else
[passage]
destination=central_chamber
windiness=5
laziness=3
jagged=4
width=1
terrain_clear=Wwg
[/passage]
[/chamber]
{PLAYER_CHAMBER 1 3-10 3-10 top-left}
{PLAYER_CHAMBER 2 3-10 3-10 bottom-right}
{PLAYER_CHAMBER 3 3-10 3-10 bottom-left}
{PLAYER_CHAMBER 4 3-10 3-10 top-right}
{PLAYER_CHAMBER 5 3-10 3-10 top-middle}
{PLAYER_CHAMBER 6 3-10 3-10 bottom-middle}
{PLAYER_CHAMBER 7 3-10 3-10 middle-left}
{PLAYER_CHAMBER 8 3-10 3-10 middle-right}
[/generator]
[/multiplayer]
#undef CLEAR_TERRAINS
#undef ROAD_COSTS
#undef PLAYER_CHAMBER
#undef MAP_OPTION_CONTROL

View File

@ -1,22 +1,39 @@
#define CHAMBER_TAG_CONTENTS
max=infinite
{DEFAULT_KEY ignore bool no}
{SIMPLE_KEY id string}
{SIMPLE_KEY x unsigned_range_list}
{SIMPLE_KEY y unsigned_range_list}
{SIMPLE_KEY terrain_clear terrain_list}
{SIMPLE_KEY size unsigned}
{SIMPLE_KEY jagged unsigned}
{DEFAULT_KEY chance unsigned 100}
{SIMPLE_KEY side unsigned}
{SIMPLE_KEY relative_to relative_anchor}
{SIMPLE_KEY require_player unsigned}
[tag]
name="passage"
max=infinite
{DEFAULT_KEY ignore bool no}
{SIMPLE_KEY id string}
{SIMPLE_KEY place_villages bool}
{SIMPLE_KEY destination string}
{SIMPLE_KEY terrain_clear terrain_list}
{SIMPLE_KEY windiness unsigned}
{SIMPLE_KEY laziness unsigned}
{SIMPLE_KEY width unsigned}
{SIMPLE_KEY jagged unsigned}
{DEFAULT_KEY chance unsigned 100}
[tag]
name="road_cost"
max=infinite
{REQUIRED_KEY terrain terrain_code}
{SIMPLE_KEY cost unsigned}
{SIMPLE_KEY convert_to terrain_code}
# TODO: This is not quite right, it has to be exactly 3 terrain codes (but default generator schema also gets that wrong)
{SIMPLE_KEY convert_to_bridge terrain_list}
[/tag]
[/tag]
[tag]
name="item_location"
@ -47,13 +64,20 @@
{SIMPLE_KEY map_width unsigned}
{SIMPLE_KEY map_height unsigned}
{SIMPLE_KEY village_density unsigned}
{SIMPLE_KEY nplayers unsigned}
{SIMPLE_KEY transform map_transform}
{SIMPLE_KEY transform_chance unsigned}
{SIMPLE_KEY terrain_wall terrain_code}
{SIMPLE_KEY terrain_castle terrain_code}
{SIMPLE_KEY terrain_keep terrain_code}
{SIMPLE_KEY terrain_village terrain_code}
{SIMPLE_KEY terrain_clear terrain_code}
{SIMPLE_KEY terrain_village terrain_list}
{SIMPLE_KEY terrain_clear terrain_list}
{LINK_TAG "scenario"}
[/then]
[/if]
[if]
glob_on_create_map=*cave_map_generator*
[then]
[tag]
name="chamber"
{CHAMBER_TAG_CONTENTS}
@ -70,6 +94,11 @@
[tag]
name="items"
super="scenario"
[tag]
name="event"
super="event"
{SIMPLE_KEY same_location_as_previous bool}
[/tag]
[/tag]
[/tag]
[tag]

View File

@ -508,6 +508,10 @@
name="theme_action"
value="checkbox|radiobox|image|turbo"
[/type]
[type]
name="relative_anchor"
value="center|top-(left|middle|right)|bottom-(left|middle|right)|middle-(left|right)"
[/type]
[tag]
name="root"
min=1

View File

@ -21,6 +21,7 @@
#include "gui/widgets/slider.hpp"
#include "gui/widgets/status_label_helper.hpp"
#include "gettext.hpp"
#include "formula/string_utils.hpp"
#include <functional>
@ -67,7 +68,12 @@ void generator_settings::pre_show()
// Do this *after* assigning the 'update_*_label_` functions or the game will crash!
adjust_minimum_size_by_players();
gui2::bind_status_label<slider>(this, "villages", [](const slider& s) { return t_string(formatter() << s.get_value() << _("/1000 tiles")); });
gui2::bind_status_label<slider>(this, "villages", [](const slider& s) {
std::string val = std::to_string(s.get_value());
utils::string_map args;
args["villages"] = val;
return t_string(VGETTEXT("$villages/1000 tiles", args));
});
gui2::bind_status_label<slider>(this, "castle_size");
gui2::bind_status_label<slider>(this, "landform", [](const slider& s) {
return s.get_value() == 0 ? _("Inland") : (s.get_value() < max_coastal ? _("Coastal") : _("Island")); });

View File

@ -298,6 +298,18 @@ static int impl_terrainmap_get(lua_State *L)
luaL_setmetatable(L, maplocationKey);
return 1;
}
if(strcmp(m, "size") == 0) {
lua_pushinteger(L, tm.total_width());
lua_pushinteger(L, tm.total_height());
lua_pushinteger(L, tm.border_size());
return 3;
}
if(strcmp(m, "playable_size") == 0) {
lua_pushinteger(L, tm.w());
lua_pushinteger(L, tm.h());
lua_pushinteger(L, tm.border_size());
return 3;
}
if(luaW_getglobal(L, "wesnoth", "map", m)) {
return 1;
}

View File

@ -253,11 +253,16 @@ mapgen_lua_kernel::mapgen_lua_kernel(const config* vars)
// Map methods
{ "find", &intf_mg_get_locations },
{ "find_in_radius", &intf_mg_get_tiles_radius },
{ "on_board", &intf_on_board },
{ "on_border", &intf_on_border },
{ "iter", &intf_terrainmap_iter },
{ "terrain_mask", &intf_terrain_mask },
// Static functions
{ "filter", &intf_terrainfilter_create },
{ "create", &intf_terrainmap_create },
{ "generate_height_map", &intf_default_generate_height_map },
{ "generate", &intf_default_generate },
{ "replace_if_failed", &intf_replace_if_failed },
{ nullptr, nullptr }
};
@ -302,9 +307,15 @@ void mapgen_lua_kernel::run_generator(const char * prog, const config & generato
protected_call(1, 1, std::bind(&lua_kernel_base::throw_exception, this, std::placeholders::_1, std::placeholders::_2));
}
void mapgen_lua_kernel::user_config(const char * prog, const config & generator)
void mapgen_lua_kernel::user_config(const char * prog, config & generator)
{
run_generator(prog, generator);
if(!lua_isnoneornil(mState, -1) && !luaW_toconfig(mState, -1, generator)) {
std::string msg = "expected a string, found a ";
msg += lua_typename(mState, lua_type(mState, -1));
lua_pop(mState, 1);
throw game::lua_error(msg.c_str(),"bad return value");
}
}
int mapgen_lua_kernel::intf_get_variable(lua_State *L)