Lua: Add a new schedule module

The primary components of this are:
- wesnoth.current.schedule
- wesnoth.map.get_time_area()
- wesnoth.schedule module
This commit is contained in:
Celtic Minstrel 2021-06-09 20:09:58 -04:00 committed by Celtic Minstrel
parent aadec0fe88
commit a03d59d7eb
14 changed files with 415 additions and 152 deletions

View File

@ -95,8 +95,8 @@ function battle_calcs.strike_damage(attacker, defender, att_weapon, def_weapon,
-- Set up a cache index. We use id+max_hitpoints+side for each unit, since the
-- unit can level up.
-- Also need to add the weapons and lawful_bonus values for each unit
local att_lawful_bonus = wesnoth.get_time_of_day({ dst[1], dst[2], true }).lawful_bonus
local def_lawful_bonus = wesnoth.get_time_of_day({ defender.x, defender.y, true }).lawful_bonus
local att_lawful_bonus = wesnoth.schedule.get_illumination(dst).lawful_bonus
local def_lawful_bonus = wesnoth.schedule.get_illumination(defender).lawful_bonus
local cind = 'SD-' .. attacker.id .. attacker.max_hitpoints .. attacker.side
cind = cind .. 'x' .. defender.id .. defender.max_hitpoints .. defender.side
@ -183,8 +183,8 @@ function battle_calcs.best_weapons(attacker, defender, dst, cache)
-- Set up a cache index. We use id+max_hitpoints+side for each unit, since the
-- unit can level up.
-- Also need to add the weapons and lawful_bonus values for each unit
local att_lawful_bonus = wesnoth.get_time_of_day({ dst[1], dst[2], true }).lawful_bonus
local def_lawful_bonus = wesnoth.get_time_of_day({ defender.x, defender.y, true }).lawful_bonus
local att_lawful_bonus = wesnoth.schedule.get_illumination(dst).lawful_bonus
local def_lawful_bonus = wesnoth.schedule.get_illumination(defender).lawful_bonus
local cind = 'BW-' .. attacker.id .. attacker.max_hitpoints .. attacker.side
cind = cind .. 'x' .. defender.id .. defender.max_hitpoints .. defender.side

View File

@ -757,7 +757,7 @@ return {
local lawful_bonus = 0
local eta_turn = wesnoth.current.turn + eta
if eta_turn <= wesnoth.scenario.turns then
lawful_bonus = wesnoth.get_time_of_day(wesnoth.current.turn + eta).lawful_bonus / eta^2
lawful_bonus = wesnoth.schedule.get_time_of_day(nil, wesnoth.current.turn + eta).lawful_bonus / eta^2
end
local damage_bonus = AH.get_unit_time_of_day_bonus(recruit_unit.alignment, lawful_bonus)
-- Estimate effectiveness on offense and defense

View File

@ -13,7 +13,7 @@ local _ = wesnoth.textdomain "wesnoth"
---- 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
---- 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)
if wesnoth.game_config.strict_lua then return nil end
@ -28,7 +28,20 @@ function wesnoth.deprecate_api(elem_name, replacement, level, version, elem, det
error((_"Invalid deprecation level $level (should be 1-4)"):vformat(err_params))
end
local msg_shown = false
if type(elem) == "function" or getmetatable(elem) == "function" then
if level == 4 then
local function show_msg(...)
if not msg_shown then
msg_shown = true
wesnoth.deprecated_message(elem_name, level, version, message)
end
end
return setmetatable({}, {
__index = show_msg,
__newindex = show_msg,
__call = show_msg,
__metatable = "removed API",
})
elseif type(elem) == "function" or getmetatable(elem) == "function" then
return function(...)
if not msg_shown then
msg_shown = true
@ -109,6 +122,43 @@ wesnoth.set_end_campaign_text = wesnoth.deprecate_api('wesnoth.set_end_campaign_
end)
if wesnoth.kernel_type() == 'Game Lua Kernel' then
local function get_time_of_day(...)
local arg_i, turn = 1, nil
if type(...) == 'number' then
turn = ...
arg_i = arg_i + 1
end
local loc, n = wesnoth.map.read_location(select(arg_i, ...))
local illum = false
if loc ~= nil then
local actual_loc = type(select(arg_i, ...))
if type(actual_loc) == 'table' and type(actual_loc[3]) == 'boolean' then
illum = actual_loc[3]
end
arg_i = arg_i + n
end
local final_arg = select(arg_i, ...)
if type(final_arg) == 'boolean' then
illum = final_arg
end
local get_tod
if illum then
get_tod = wesnoth.schedule.get_illumination
else
get_tod = wesnoth.schedule.get_time_of_day
end
return get_tod(loc, turn)
end
local function liminal_bonus(...)
return wesnoth.current.schedule.liminal_bonus
end
wesnoth.get_time_of_day = wesnoth.deprecate_api('wesnoth.get_time_of_day', 'wesnoth.schedule.get_time_of_day or wesnoth.schedule.get_illumination', 1, nil, get_time_of_day, 'The arguments have changed')
wesnoth.set_time_of_day = wesnoth.deprecate_api('wesnoth.set_time_of_day', 'wesnoth.current.schedule.time_of_day', 4, nil, nil)
wesnoth.get_max_liminal_bonus = wesnoth.deprecate_api('wesnoth.get_max_liminal_bonus', 'wesnoth.current.schedule.liminal_bonus', 1, nil, liminal_bonus, "It's now a read-write attribute")
wesnoth.replace_schedule = wesnoth.deprecate_api('wesnoth.replace_schedule', 'wesnoth.schedule.replace', 1, nil, wesnoth.schedule.replace)
wesnoth.get_traits = wesnoth.deprecate_api('wesnoth.get_traits', 'wesnoth.game_config.global_traits', 1, nil, function() return wesnoth.game_config.global_traits end)
wesnoth.end_level = wesnoth.deprecate_api('wesnoth.end_level', 'wesnoth.scenario.end_level_data assignment', 1, nil, function(cfg) wesnoth.scenario.end_level_data = cfg end)
wesnoth.get_end_level_data = wesnoth.deprecate_api('wesnoth.get_end_level_data', 'wesnoth.scenario.end_level_data', 1, nil, function() return wesnoth.scenario.end_level_data end)

View File

@ -93,6 +93,10 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
return self.terrain:split('^', {remove_empty=false})[2]
elseif key == 'info' then
return wesnoth.get_terrain_info(wesnoth.current.map[self])
elseif key == 'time_of_day' then
return wesnoth.schedule.get_time_of_day(self)
elseif key == 'illuminated_time' then
return wesnoth.schedule.get_illumination(self)
elseif key == 1 then
return self.x
elseif key == 2 then
@ -137,8 +141,8 @@ if wesnoth.kernel_type() == "Game Lua Kernel" then
self.x = val
elseif key == 2 then
self.y = val
elseif key == 'info' then
error('hex.info is read-only', 1)
elseif key == 'info' or key == 'time_of_day' or key == 'illuminated_time' then
error(string.format('hex.%s is read-only', key), 1)
else
-- If it's not a known key, just set it
rawset(self, key, val)

View File

@ -699,7 +699,7 @@ function wml_actions.remove_time_area(cfg)
end
function wml_actions.replace_schedule(cfg)
wesnoth.replace_schedule(cfg)
wesnoth.schedule.replace(cfg)
end
function wml_actions.scroll(cfg)

View File

@ -78,7 +78,7 @@ function wml_actions.harm_unit(cfg)
elseif alignment == "chaotic" then
damage_multiplier = damage_multiplier - tod_bonus
elseif alignment == "liminal" then
damage_multiplier = damage_multiplier + math.max(0, wesnoth.get_max_liminal_bonus() - math.abs(tod_bonus))
damage_multiplier = damage_multiplier + math.max(0, wesnoth.current.schedule.liminal_bonus - math.abs(tod_bonus))
else -- neutral, do nothing
end
local resistance_modified = resistance * modifier
@ -90,7 +90,7 @@ function wml_actions.harm_unit(cfg)
local damage = calculate_damage(
amount,
cfg.alignment or "neutral",
wesnoth.get_time_of_day( { unit_to_harm.x, unit_to_harm.y, true } ).lawful_bonus,
wesnoth.schedule.get_illumination(unit_to_harm).lawful_bonus,
100 - unit_to_harm:resistance_against( cfg.damage_type or "dummy" ),
resistance_multiplier
)

View File

@ -17,7 +17,7 @@ local aspect = wml.variables['test_attribute']
if ai.aspects[aspect] ~= expected[wesnoth.current.turn].value then
wml.variables['is_valid'] = false
local msg = 'Failed on turn ' .. tostring(wesnoth.current.turn)
msg = msg .. ' (time: ' .. wesnoth.get_time_of_day().id .. ')'
msg = msg .. ' (time: ' .. wesnoth.current.schedule.time_of_day .. ')'
msg = msg .. '; ' .. aspect
msg = msg .. ' was ' .. tostring(ai.aspects[aspect])
msg = msg .. ' but expected to be '

View File

@ -839,6 +839,174 @@ int game_lua_kernel::intf_lock_view(lua_State *L)
return 0;
}
static void luaW_push_tod(lua_State* L, const time_of_day& tod)
{
lua_newtable(L);
lua_pushstring(L, tod.id.c_str());
lua_setfield(L, -2, "id");
lua_pushinteger(L, tod.lawful_bonus);
lua_setfield(L, -2, "lawful_bonus");
lua_pushinteger(L, tod.bonus_modified);
lua_setfield(L, -2, "bonus_modified");
lua_pushstring(L, tod.image.c_str());
lua_setfield(L, -2, "image");
luaW_pushtstring(L, tod.name);
lua_setfield(L, -2, "name");
lua_pushstring(L, tod.sounds.c_str());
lua_setfield(L, -2, "sound");
lua_pushstring(L, tod.image_mask.c_str());
lua_setfield(L, -2, "mask");
lua_pushinteger(L, tod.color.r);
lua_setfield(L, -2, "red");
lua_pushinteger(L, tod.color.g);
lua_setfield(L, -2, "green");
lua_pushinteger(L, tod.color.b);
lua_setfield(L, -2, "blue");
}
// A schedule object is just a location with a special metatable.
// This is also valid for time area objects, which also have an "index" key identifying them.
// The index is a secondary means of identification in case it has no locations.
void game_lua_kernel::luaW_push_schedule(lua_State* L, int area_index)
{
lua_newuserdatauv(L, 0, 1);
lua_pushinteger(L, area_index);
lua_setiuservalue(L, -2, 1);
if(luaL_newmetatable(L, "schedule")) {
static luaL_Reg const schedule_meta[] {
{"__index", &dispatch<&game_lua_kernel::impl_schedule_get>},
{"__newindex", &dispatch<&game_lua_kernel::impl_schedule_set>},
{"__len", &dispatch<&game_lua_kernel::impl_schedule_len>},
{ nullptr, nullptr }
};
luaL_setfuncs(L, schedule_meta, 0);
}
lua_setmetatable(L, -2);
}
static int luaW_check_schedule(lua_State* L, int idx)
{
int save_top = lua_gettop(L);
luaL_checkudata(L, idx, "schedule");
lua_getiuservalue(L, idx, 1);
int i = luaL_checkinteger(L, -1);
lua_settop(L, save_top);
return i;
}
int game_lua_kernel::impl_schedule_get(lua_State *L)
{
int area_index = luaW_check_schedule(L, 1);
if(lua_isnumber(L, 2)) {
const auto& times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
int i = lua_tointeger(L, 2) - 1;
if(i < 0 || i >= static_cast<int>(times.size())) {
return luaL_argerror(L, 2, "invalid time of day index");
}
luaW_push_tod(L, times[i]);
return 1;
} else {
const char* m = luaL_checkstring(L, 2);
if(area_index >= 0) {
return_string_attrib("time_of_day", tod_man().get_area_time_of_day(area_index).id);
return_string_attrib("id", tod_man().get_area_id(area_index));
if(strcmp(m, "hexes") == 0) {
const auto& hexes = tod_man().get_area_by_index(area_index);
luaW_push_locationset(L, hexes);
return 1;
}
} else {
return_string_attrib("time_of_day", tod_man().get_time_of_day().id);
return_int_attrib("liminal_bonus", tod_man().get_max_liminal_bonus());
}
if(luaW_getglobal(L, "wesnoth", "schedule", m)) {
return 1;
}
}
return 0;
}
int game_lua_kernel::impl_schedule_len(lua_State *L)
{
int area_index = luaW_check_schedule(L, 1);
const auto& times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
lua_pushinteger(L, times.size());
return 1;
}
int game_lua_kernel::impl_schedule_set(lua_State *L)
{
int area_index = luaW_check_schedule(L, 1);
if(lua_isnumber(L, 2)) {
std::vector<time_of_day> times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
int i = lua_tointeger(L, 2) - 1;
if(i < 0 || i >= static_cast<int>(times.size())) {
return luaL_argerror(L, 2, "invalid time of day index");
}
config time_cfg = luaW_checkconfig(L, 3);
times[i] = time_of_day(time_cfg);
if(area_index < 0) {
tod_man().replace_schedule(times);
} else {
tod_man().replace_local_schedule(times, area_index);
}
} else {
const char* m = luaL_checkstring(L, 2);
if(strcmp(m, "time_of_day") == 0) {
std::string value = luaL_checkstring(L, 3);
const auto& times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
auto iter = std::find_if(times.begin(), times.end(), [&value](const time_of_day& tod) {
return tod.id == value;
});
if(iter == times.end()) {
std::ostringstream err;
err << "invalid time of day ID for ";
if(area_index < 0) {
err << "global schedule";
} else {
const std::string& id = tod_man().get_area_id(area_index);
if(id.empty()) {
const auto& hexes = tod_man().get_area_by_index(area_index);
if(hexes.empty()) {
err << "anonymous empty time area";
} else {
err << "anonymous time area at (" << hexes.begin()->wml_x() << ',' << hexes.begin()->wml_y() << ")";
}
} else {
err << "time area with id=" << id;
}
}
lua_push(L, err.str());
return lua_error(L);
}
int n = std::distance(times.begin(), iter);
if(area_index < 0) {
tod_man().set_current_time(n);
} else {
tod_man().set_current_time(n, area_index);
}
}
if(area_index >= 0) {
modify_string_attrib("id", tod_man().set_area_id(area_index, value));
if(strcmp(m, "hexes") == 0) {
auto hexes = luaW_check_locationset(L, 3);
tod_man().replace_area_locations(area_index, hexes);
return 0;
}
} else {
// Assign nil to reset the bonus to the default (best) value
if(lua_isnil(L, 3) && strcmp(m, "liminal_bonus") == 0) {
tod_man().reset_max_liminal_bonus();
return 0;
}
modify_int_attrib("liminal_bonus", tod_man().set_max_liminal_bonus(value));
}
}
return 0;
}
/**
* Gets details about a terrain.
* - Arg 1: terrain code string.
@ -880,79 +1048,49 @@ int game_lua_kernel::impl_get_terrain_info(lua_State *L)
/**
* Gets time of day information.
* - Arg 1: optional turn number
* - Arg 2: optional location
* - Arg 3: optional boolean (consider_illuminates)
* - Arg 1: schedule object, location, time area ID, or nil
* - Arg 2: optional turn number
* - Ret 1: table.
*/
template<bool consider_illuminates>
int game_lua_kernel::intf_get_time_of_day(lua_State *L)
{
unsigned arg = 1;
int for_turn = tod_man().turn();
map_location loc = map_location();
bool consider_illuminates = false;
if(lua_isnumber(L, arg)) {
++arg;
if(luaW_tolocation(L, 1, loc)) {
if(!board().map().on_board_with_border(loc)) {
return luaL_argerror(L, 1, "coordinates are not on board");
}
} else if(lua_isstring(L, 1)) {
auto area = tod_man().get_area_by_id(lua_tostring(L, 1));
if(area.empty()) {
return luaL_error(L, "invalid or empty time_area ID");
}
// We just need SOME location in that area, it doesn't matter which one.
loc = *area.begin();
} else if(!lua_isnil(L, 1)) {
auto area = tod_man().get_area_by_index(luaW_check_schedule(L, 1));
if(area.empty()) {
return luaL_error(L, "empty time_area");
}
// We just need SOME location in that area, it doesn't matter which one.
loc = *area.begin();
}
if(lua_isnumber(L, 2)) {
for_turn = luaL_checkinteger(L, 1);
int number_of_turns = tod_man().number_of_turns();
if(for_turn < 1 || (number_of_turns != -1 && for_turn > number_of_turns)) {
return luaL_argerror(L, 1, "turn number out of range");
}
}
else if(lua_isnil(L, arg)) ++arg;
if(luaW_tolocation(L, arg, loc)) {
if(!board().map().on_board(loc)) return luaL_argerror(L, arg, "coordinates are not on board");
if(lua_istable(L, arg)) {
lua_rawgeti(L, arg, 3);
consider_illuminates = luaW_toboolean(L, -1);
lua_pop(L, 1);
} else if(lua_isboolean(L, arg + 1)) {
consider_illuminates = luaW_toboolean(L, arg + 1);
}
}
const time_of_day& tod = consider_illuminates ?
tod_man().get_illuminated_time_of_day(board().units(), board().map(), loc, for_turn) :
tod_man().get_time_of_day(loc, for_turn);
lua_newtable(L);
lua_pushstring(L, tod.id.c_str());
lua_setfield(L, -2, "id");
lua_pushinteger(L, tod.lawful_bonus);
lua_setfield(L, -2, "lawful_bonus");
lua_pushinteger(L, tod.bonus_modified);
lua_setfield(L, -2, "bonus_modified");
lua_pushstring(L, tod.image.c_str());
lua_setfield(L, -2, "image");
luaW_pushtstring(L, tod.name);
lua_setfield(L, -2, "name");
lua_pushstring(L, tod.sounds.c_str());
lua_setfield(L, -2, "sound");
lua_pushstring(L, tod.image_mask.c_str());
lua_setfield(L, -2, "mask");
lua_pushinteger(L, tod.color.r);
lua_setfield(L, -2, "red");
lua_pushinteger(L, tod.color.g);
lua_setfield(L, -2, "green");
lua_pushinteger(L, tod.color.b);
lua_setfield(L, -2, "blue");
return 1;
}
/**
* Gets the max liminal bonus
* - Ret 1: integer.
*/
int game_lua_kernel::intf_get_max_liminal_bonus(lua_State *L)
{
int bonus = tod_man().get_max_liminal_bonus();
lua_pushinteger(L, bonus);
luaW_push_tod(L, tod);
return 1;
}
@ -1400,6 +1538,10 @@ int game_lua_kernel::impl_current_get(lua_State *L)
if(strcmp(m, "map") == 0) {
return intf_terrainmap_get(L);
}
if(strcmp(m, "schedule") == 0) {
luaW_push_schedule(L, -1);
return 1;
}
if (strcmp(m, "event_context") == 0)
{
@ -3620,70 +3762,51 @@ int game_lua_kernel::intf_remove_time_area(lua_State * L)
return 0;
}
int game_lua_kernel::intf_get_time_area(lua_State* L)
{
map_location loc;
if(luaW_tolocation(L, 1, loc)) {
int area_index = tod_man().get_area_on_hex(loc).first;
if(area_index < 0) {
lua_pushnil(L);
return 1;
}
luaW_push_schedule(L, area_index);
return 1;
} else {
std::string area_id = luaL_checkstring(L, 1);
const auto& area_ids = tod_man().get_area_ids();
if(auto iter = std::find(area_ids.begin(), area_ids.end(), area_id); iter == area_ids.end()) {
lua_pushnil(L);
return 1;
} else {
luaW_push_schedule(L, std::distance(area_ids.begin(), iter));
return 1;
}
}
}
/** Replacing the current time of day schedule. */
int game_lua_kernel::intf_replace_schedule(lua_State * L)
{
vconfig cfg = luaW_checkvconfig(L, 1);
if(cfg.get_children("time").empty()) {
ERR_LUA << "attempted to to replace ToD schedule with empty schedule" << std::endl;
map_location loc;
if(luaW_tolocation(L, 1, loc)) {
// Replace the global schedule with a time area's schedule
// The expectation is that you call schedule.replace(time_area.schedule),
// rather than passing a literal location.
} else {
tod_man().replace_schedule(cfg.get_parsed_config());
if (game_display_) {
game_display_->new_turn();
}
LOG_LUA << "replaced ToD schedule\n";
}
return 0;
}
vconfig cfg = luaW_checkvconfig(L, 1);
int game_lua_kernel::intf_set_time_of_day(lua_State * L)
{
if(!game_display_) {
return 0;
}
std::string area_id;
std::size_t area_i = 0;
if (lua_isstring(L, 2)) {
area_id = lua_tostring(L, 1);
std::vector<std::string> area_ids = tod_man().get_area_ids();
area_i = std::distance(area_ids.begin(), std::find(area_ids.begin(), area_ids.end(), area_id));
if(area_i >= area_ids.size()) {
return luaL_argerror(L, 1, "invalid time area ID");
}
}
int is_num = false;
int new_time = lua_tonumberx(L, 1, &is_num) - 1;
const std::vector<time_of_day>& times = area_id.empty()
? tod_man().times()
: tod_man().times(area_i);
int num_times = times.size();
if(!is_num) {
std::string time_id = luaL_checkstring(L, 1);
new_time = 0;
for(const time_of_day& time : times) {
if(time_id == time.id) {
break;
if(cfg.get_children("time").empty()) {
ERR_LUA << "attempted to to replace ToD schedule with empty schedule" << std::endl;
} else {
tod_man().replace_schedule(cfg.get_parsed_config());
if (game_display_) {
game_display_->new_turn();
}
new_time++;
LOG_LUA << "replaced ToD schedule\n";
}
if(new_time >= num_times) {
return luaL_argerror(L, 1, "invalid time of day ID");
}
}
if(new_time == 0 && num_times == 0) {
//ignore this case, because we don't want code like set_current_time(get_current_time()) to fail if num_times is 0.
return 0;
}
if(new_time < 0 || new_time >= num_times) {
return luaL_argerror(L, 1, "invalid time of day index");
}
if(area_id.empty()) {
tod_man().set_current_time(new_time);
} else {
tod_man().set_current_time(new_time, area_i);
}
return 0;
}
@ -4020,16 +4143,12 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "find_vision_range", &dispatch<&game_lua_kernel::intf_find_vision_range > },
{ "fire_event", &dispatch2<&game_lua_kernel::intf_fire_event, false > },
{ "fire_event_by_id", &dispatch2<&game_lua_kernel::intf_fire_event, true > },
{ "get_time_of_day", &dispatch<&game_lua_kernel::intf_get_time_of_day > },
{ "get_max_liminal_bonus", &dispatch<&game_lua_kernel::intf_get_max_liminal_bonus > },
{ "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > },
{ "log", &dispatch<&game_lua_kernel::intf_log > },
{ "message", &dispatch<&game_lua_kernel::intf_message > },
{ "print", &dispatch<&game_lua_kernel::intf_print > },
{ "redraw", &dispatch<&game_lua_kernel::intf_redraw > },
{ "remove_event_handler", &dispatch<&game_lua_kernel::intf_remove_event > },
{ "replace_schedule", &dispatch<&game_lua_kernel::intf_replace_schedule > },
{ "set_time_of_day", &dispatch<&game_lua_kernel::intf_set_time_of_day > },
{ "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > },
{ "synchronize_choice", &intf_synchronize_choice },
{ "synchronize_choices", &intf_synchronize_choices },
@ -4271,6 +4390,22 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
luaL_setfuncs(L, audio_callbacks, 0);
lua_setfield(L, -2, "audio");
lua_pop(L, 1);
// Create the schedule module
cmd_log_ << "Adding schedule module...\n";
static luaL_Reg const schedule_callbacks[] {
{ "get_time_of_day", &dispatch<&game_lua_kernel::intf_get_time_of_day<false>>},
{ "get_illumination", &dispatch<&game_lua_kernel::intf_get_time_of_day<true>>},
{ "replace", &dispatch<&game_lua_kernel::intf_replace_schedule>},
{ nullptr, nullptr }
};
lua_getglobal(L, "wesnoth");
lua_newtable(L);
luaL_setfuncs(L, schedule_callbacks, 0);
lua_createtable(L, 0, 2);
lua_setmetatable(L, -2);
lua_setfield(L, -2, "schedule");
lua_pop(L, 1);
// Create the playlist table with its metatable
cmd_log_ << lua_audio::register_table(L);

View File

@ -74,6 +74,7 @@ class game_lua_kernel : public lua_kernel_base
int intf_cancel_action(lua_State *);
int intf_add_time_area(lua_State *);
int intf_remove_time_area(lua_State *);
int intf_get_time_area(lua_State *);
int intf_animate_unit(lua_State *);
int intf_gamestate_inspector(lua_State *);
int impl_run_animation(lua_State *);
@ -93,8 +94,11 @@ class game_lua_kernel : public lua_kernel_base
int intf_view_locked(lua_State *L);
int intf_lock_view(lua_State *L);
int impl_get_terrain_info(lua_State *L);
template<bool consider_illuminates>
int intf_get_time_of_day(lua_State *L);
int intf_get_max_liminal_bonus(lua_State *L);
int impl_schedule_get(lua_State *L);
int impl_schedule_len(lua_State *L);
void luaW_push_schedule(lua_State* L, int area_index);
int intf_get_village_owner(lua_State *L);
int intf_set_village_owner(lua_State *L);
int intf_get_map_size(lua_State *L);
@ -154,7 +158,7 @@ class game_lua_kernel : public lua_kernel_base
int intf_get_label(lua_State* L);
int intf_redraw(lua_State *L);
int intf_replace_schedule(lua_State *l);
int intf_set_time_of_day(lua_State *L);
int impl_schedule_set(lua_State *L);
int intf_scroll(lua_State *L);
int intf_get_all_vars(lua_State *L);
int impl_theme_item(lua_State *L, std::string name);

View File

@ -730,6 +730,39 @@ map_location luaW_checklocation(lua_State *L, int index)
return result;
}
int luaW_push_locationset(lua_State* L, const std::set<map_location>& locs)
{
lua_createtable(L, locs.size(), 0);
int i = 1;
for(const map_location& loc : locs) {
lua_createtable(L, 2, 0);
lua_pushinteger(L, loc.wml_x());
lua_rawseti(L, -2, 1);
lua_pushinteger(L, loc.wml_y());
lua_rawseti(L, -2, 2);
lua_rawseti(L, -2, i);
++i;
}
return 1;
}
std::set<map_location> luaW_check_locationset(lua_State* L, int idx)
{
std::set<map_location> locs;
if(!lua_istable(L, idx)) {
luaW_type_error(L, idx, "array of locations");
}
lua_len(L, idx);
int len = luaL_checkinteger(L, -1);
for(int i = 1; i < len; i++) {
lua_geti(L, idx, i);
locs.insert(luaW_checklocation(L, -1));
lua_pop(L, 1);
}
return locs;
}
void luaW_pushconfig(lua_State *L, const config& cfg)
{
lua_newtable(L);

View File

@ -114,6 +114,16 @@ bool luaW_tolocation(lua_State *L, int index, map_location &loc);
*/
map_location luaW_checklocation(lua_State *L, int index);
/**
* Converts a set of map locations to a Lua table pushed at the top of the stack.
*/
int luaW_push_locationset(lua_State* L, const std::set<map_location>& locs);
/**
* Converts a table of integer pairs to a set of map location objects.
*/
std::set<map_location> luaW_check_locationset(lua_State* L, int idx);
/**
* Converts a config object to a Lua table pushed at the top of the stack.
*/

View File

@ -162,25 +162,6 @@ namespace {
} //end namespace
static int luaW_push_locationset(lua_State* L, const std::set<map_location>& locs)
{
LOG_LMG << "push_locationset\n";
lua_createtable(L, locs.size(), 0);
int i = 1;
for (const map_location& loc : locs)
{
lua_createtable(L, 2, 0);
lua_pushinteger(L, loc.wml_x());
lua_rawseti(L, -2, 1);
lua_pushinteger(L, loc.wml_y());
lua_rawseti(L, -2, 2);
lua_rawseti(L, -2, i);
++i;
}
return 1;
}
static std::set<map_location> luaW_to_locationset(lua_State* L, int index)
{
std::set<map_location> res;

View File

@ -205,6 +205,15 @@ const time_of_day& tod_manager::get_time_of_day(const map_location& loc, int n_t
return get_time_of_day_turn(times_, n_turn, currentTime_);
}
const time_of_day& tod_manager::get_area_time_of_day(int area_i, int n_turn) const
{
assert(area_i < static_cast<int>(areas_.size()));
if(n_turn == 0) {
n_turn = turn_;
}
return get_time_of_day_turn(areas_[area_i].times, n_turn, areas_[area_i].currentTime);
}
const time_of_day tod_manager::get_illuminated_time_of_day(
const unit_map& units, const gamemap& map, const map_location& loc, int for_turn) const
{
@ -324,6 +333,12 @@ void tod_manager::set_area_id(int area_index, const std::string& id)
areas_[area_index].id = id;
}
const std::string& tod_manager::get_area_id(int area_index) const
{
assert(area_index < static_cast<int>(areas_.size()));
return areas_[area_index].id;
}
std::vector<std::string> tod_manager::get_area_ids() const
{
std::vector<std::string> areas;
@ -351,6 +366,19 @@ const std::set<map_location>& tod_manager::get_area_by_index(int index) const
return areas_[index].hexes;
}
std::pair<int, std::string> tod_manager::get_area_on_hex(const map_location& loc) const
{
if(loc != map_location::null_location()) {
for(auto i = areas_.rbegin(), i_end = areas_.rend();
i != i_end; ++i) {
if(i->hexes.find(loc) != i->hexes.end() && !i->times.empty())
return {std::distance(areas_.rbegin(), i), i->id};
}
}
return {-1, ""};
}
void tod_manager::add_time_area(const gamemap& map, const config& cfg)
{
areas_.emplace_back();

View File

@ -46,6 +46,7 @@ class tod_manager
void set_current_time(int time, int area_index);
void set_current_time(int time, const std::string& area_id);
void set_area_id(int area_index, const std::string& id);
const std::string& get_area_id(int area_index) const;
/**
* Returns global time of day for the passed turn.
@ -60,8 +61,12 @@ class tod_manager
* tod areas matter, for_turn = 0 means current turn
* ignoring illumination
*/
const time_of_day& get_time_of_day(const map_location& loc,
int for_turn = 0) const;
const time_of_day& get_time_of_day(const map_location& loc, int for_turn = 0) const;
/**
* Returns time of day for the passed turn in the specified tod area.
* for_turn = 0 means current turn, ignoring illumination
*/
const time_of_day& get_area_time_of_day(int area_i, int for_turn = 0) const;
int get_current_area_time(int index) const;
@ -102,6 +107,11 @@ class tod_manager
*/
const std::set<map_location>& get_area_by_id(const std::string& id) const;
/**
* @returns the area ID and index active on the given location.
*/
std::pair<int, std::string> get_area_on_hex(const map_location& loc) const;
/**
* Adds a new local time area from config, making it follow its own
* time-of-day sequence.
@ -181,6 +191,14 @@ class tod_manager
{ return has_tod_bonus_changed_; }
int get_max_liminal_bonus() const
{ return liminal_bonus_; }
void set_max_liminal_bonus(int bonus) {
liminal_bonus_ = bonus;
has_cfg_liminal_bonus_ = true;
}
void reset_max_liminal_bonus() {
liminal_bonus_ = calculate_best_liminal_bonus(times());
has_cfg_liminal_bonus_ = false;
}
private:
/**