Properly port [modify_side] to Lua

This commit is contained in:
Celtic Minstrel 2016-08-04 14:26:33 -04:00 committed by Celtic Minstrel
parent 5fdb73b92f
commit e0d07e854e
8 changed files with 273 additions and 232 deletions

View file

@ -8,8 +8,24 @@ Version 1.13.6+dev:
* Language and i18n:
* Updated translations: British English, Portuguese (Brazil)
* Lua API:
* New attributes in side proxy:
num_units, num_villages, total_upkeep, expenses, net_income
* New wesnoth.set_side_id function can change flag, color, or both; it automatically
updates cached flag info for you (but you may still need to redraw to see it).
* The wesnoth.place_shroud and wesnoth.clear_shroud functions can alter shroud data
for a single side. They accept a list of locations, a shroud data string, or the
special value "all".
* New Lua API functions for altering AI:
* wesnoth.switch_ai replaces the entire AI with a definition from a file
* wesnoth.append_ai appends AI parameters to the configuation; supports goals,
stages, and simple aspects.
(Aspect tags are not fully parsed; only the id and facet subtags are used.)
* wesnoth.add_ai_component, delete_ai_component, change_ai_component
These do the work of the [modify_ai] tag for a single side.
* Side proxy changes:
* flag and flag_icon are never an empty string
* New mutable keys: suppress_end_turn_confirmation, share_vision
* New read-only keys: share_maps, share_view
num_units, num_villages, total_upkeep, expenses, net_income
* Existing keys made mutable: shroud, fog, flag, flag_icon
* WML Engine:
* Removed LOW_MEM option when building.
* Add color= attribute to [floating_text]

View file

@ -875,8 +875,109 @@ function wml_actions.label( cfg )
end
end
local side_changes_needing_redraw = {
'shroud', 'fog', 'reset_map', 'reset_view', 'shroud_data',
'share_vision', 'share_maps', 'share_view',
'color', 'flag',
}
function wml_actions.modify_side(cfg)
wesnoth.modify_side(cfg)
local sides = utils.get_sides(cfg)
for i,side in ipairs(sides) do
if cfg.team_name then
side.team_name = cfg.team_name
end
if cfg.user_team_name then
side.user_team_name = cfg.user_team_name
end
if cfg.controller then
side.controller = cfg.controller
end
if cfg.defeat_condition then
side.defeat_condition = cfg.defeat_condition
end
if cfg.recruit then
local recruits = {}
for recruit in utils.split(cfg.recruit) do
table.insert(recruits, recruit)
end
side.recruit = recruits
end
if cfg.village_support then
side.village_support = cfg.village_support
end
if cfg.village_gold then
side.village_gold = cfg.village_gold
end
if cfg.income then
side.base_income = cfg.income
end
if cfg.gold then
side.gold = cfg.gold
end
if cfg.hidden ~= nil then
side.hidden = cfg.hidden
end
if cfg.color or cfg.flag then
wesnoth.set_team_id(side.side, cfg.flag, cfg.color)
end
if cfg.flag_icon then
side.flag_icon = cfg.flag_icon
end
if cfg.suppress_end_turn_confirmation ~= nil then
side.suppress_end_turn_confirmation = cfg.suppress_end_turn_confirmation
end
if cfg.scroll_to_leader ~= nil then
side.scroll_to_leader = cfg.scroll_to_leader
end
if cfg.shroud ~= nil then
side.shroud = cfg.shroud
end
if cfg.reset_maps then
wesnoth.clear_shroud(side.side, "all")
end
if cfg.fog ~= nil then
side.fog = cfg.fog
end
if cfg.reset_view then
wesnoth.add_fog(side.side, {}, true)
end
if cfg.shroud_data then
wesnoth.clear_shroud(side, cfg.shroud_data)
end
if cfg.share_vision then
side.share_vision = cfg.share_vision
end
-- Legacy support
if cfg.share_view ~= nil or cfg.share_maps ~= nil then
if cfg.share_view then
side.share_vision = 'all'
elseif cfg.share_maps then
side.share_vision = 'shroud'
else
side.share_vision = 'none'
end
end
if cfg.switch_ai then
wesnoth.switch_ai(side.side, cfg.switch_ai)
end
local ai = {}
for next_ai in helper.child_range(cfg, "ai") do
table.insert(ai, T.ai(next_ai))
end
if #ai > 0 then
wesnoth.append_ai(side.side, ai)
end
end
for i,key in ipairs(side_changes_needing_redraw) do
if cfg[key] ~= nil then
wml_actions.redraw{}
return
end
end
end
function wml_actions.open_help(cfg)

View file

@ -50,19 +50,27 @@ function utils.vwriter.write(self, container)
self.index = self.index + 1
end
function utils.get_sides(cfg, key_name, filter_name)
key_name = key_name or "side"
filter_name = filter_name or "filter_side"
local filter = helper.get_child(cfg, filter_name)
if filter then
if cfg[key_name] then
wesnoth.log('warn', "ignoring duplicate side filter information (inline side=)")
end
return wesnoth.get_sides(filter)
else
return wesnoth.get_sides{side = cfg[key_name]}
end
end
function utils.optional_side_filter(cfg, key_name, filter_name)
local key_name = key_name or "side"
local sides = cfg[key_name]
local filter_name = filter_name or "filter_side"
local filter_side = helper.get_child(cfg, filter_name)
if filter_side then
sides = wesnoth.get_sides(filter_side)
elseif sides then
local dummy_cfg = {side=sides}
sides = wesnoth.get_sides(dummy_cfg)
else
if cfg[key_name] == nil and helper.get_child(cfg, filter_name) == nil then
return true
end
local sides = utils.get_sides(cfg, key_name, filter_name)
for index,side in ipairs(sides) do
if side.controller == "human" then
return true

View file

@ -148,33 +148,6 @@ ai_composite& holder::get_ai_ref()
}
void holder::modify_side_ai_config(config cfg)
{
// only handle aspects
// transform ai_parameters to new-style config
configuration::expand_simplified_aspects(this->side_, cfg);
cfg = cfg.child("ai");
//at this point we have a single config which contains [aspect][facet] tags
DBG_AI_MANAGER << "after transforming [modify_side][ai] into new syntax, config contains:"<< std::endl << cfg << std::endl;
// TODO: Also add [goal] tags. And what about [stage] or [engine] tags? (Maybe they're not important.)
if (this->readonly_context_ == nullptr) {
// if not initialized, append that config to the bottom of base cfg
// then, merge aspects with the same id
cfg_.append_children(cfg);
cfg_.merge_children_by_attribute("aspect","id");
} else {
// else run 'add_facet' command on each [aspect][facet]
for (const config &cfg_a : cfg.child_range("aspect")) {
for (const config &cfg_f : cfg_a.child_range("facet")) {
readonly_context_->add_facet(cfg_a["id"],cfg_f);
}
}
}
}
void holder::modify_ai(const config &cfg)
{
if (!this->ai_) {
@ -210,6 +183,27 @@ void holder::modify_ai(const config &cfg)
}
void holder::append_ai(const config& cfg)
{
if(!this->ai_) {
get_ai_ref();
}
for(const config& aspect : cfg.child_range("aspect")) {
const std::string& id = aspect["id"];
for(const config& facet : aspect.child_range("facet")) {
ai_->add_facet(id, facet);
}
}
for(const config& goal : cfg.child_range("goal")) {
ai_->add_goal(goal);
}
for(const config& stage : cfg.child_range("stage")) {
if(stage["name"] != "empty") {
ai_->add_stage(stage);
}
}
}
config holder::to_config() const
{
if (!this->ai_) {
@ -704,20 +698,6 @@ void manager::clear_ais()
ai_map_.clear();
}
// =======================================================================
// Work with active AI parameters
// =======================================================================
void manager::modify_active_ai_config_old_for_side ( side_number side, const config::const_child_itors &ai_parameters )
{
config cfgs;
for (const config& cfg : ai_parameters) {
cfgs.add_child("ai", cfg);
}
cfgs.child_or_add("ai").add_child("stage")["name"] = "empty";
get_active_ai_holder_for_side(side).modify_side_ai_config(cfgs);
}
void manager::modify_active_ai_for_side ( side_number side, const config &cfg )
{
@ -729,6 +709,14 @@ void manager::modify_active_ai_for_side ( side_number side, const config &cfg )
}
void manager::append_active_ai_for_side(side_number side, const config& cfg)
{
if(!ai_info_) {
return;
}
get_active_ai_holder_for_side(side).append_ai(cfg);
}
std::string manager::get_active_ai_overview_for_side( side_number side)
{
return get_active_ai_holder_for_side(side).get_ai_overview();

View file

@ -64,7 +64,9 @@ public:
config to_config() const;
void modify_ai(const config& cfg);
void modify_side_ai_config(config cfg);
void append_ai(const config& cfg);
const std::string get_ai_overview();
@ -429,18 +431,6 @@ public:
// SET active AI parameters
// =======================================================================
/**
* Modifies AI parameters for active AI of the given @a side.
* This function is provided for backward-compatibility with [modify_side][ai]...[/ai][/modify_side]
* It can only add new facets to aspects
* @param side side_number (1-based, as in game_info).
* @param ai_parameters AI parameters to be modified.
*/
static void modify_active_ai_config_old_for_side ( side_number side, const config::const_child_itors &ai_parameters );
/**
* Modifies AI parameters for active AI of the given @a side.
* This function is a backend for [modify_ai] tag
@ -450,6 +440,15 @@ public:
static void modify_active_ai_for_side( ai::side_number side, const config &cfg );
/**
* Appends AI parameters to active AI of the given @a side.
* This function is a backend for [modify_side][ai] tag
* @param side side_number (1-based, as in game_info).
* @param cfg - content of [modify_side][ai] tag
*/
static void append_active_ai_for_side( ai::side_number side, const config &cfg );
// =======================================================================
// PROXY
// =======================================================================

View file

@ -107,6 +107,7 @@
#include "variable.hpp" // for vconfig, etc
#include "variable_info.hpp"
#include "wml_exception.hpp"
#include "config_assign.hpp"
#include "utils/functional.hpp" // for bind_t, bind
#include <boost/range/algorithm/copy.hpp> // boost::copy
@ -697,21 +698,29 @@ int game_lua_kernel::intf_set_next_scenario(lua_State *L)
int game_lua_kernel::intf_shroud_op(lua_State *L, bool place_shroud)
{
vconfig cfg = luaW_checkvconfig(L, 1);
// Filter the sides.
std::vector<int> sides = get_sides_vector(cfg);
size_t index;
int side_num = luaL_checkinteger(L, 1);
// Filter the locations.
std::set<map_location> locs;
const terrain_filter filter(cfg, &game_state_);
filter.get_locations(locs, true);
for (const int &side_num : sides)
{
index = side_num - 1;
team &t = teams()[index];
if(!lua_isstring(L, 2)) {
std::string data = lua_tostring(L, 2);
// Special case - using a shroud_data string, or "all"
team& side = teams()[side_num - 1];
if(place_shroud) {
side.reshroud();
}
if(data != "all") {
side.merge_shroud_map_data(data);
} else if(!place_shroud) {
bool was_shrouded = side.uses_shroud();
side.set_shroud(false);
actions::clear_shroud(side.side());
side.set_shroud(was_shrouded);
}
return 0;
} else if(lua_istable(L, 2)) {
std::vector<map_location> locs_v = lua_check<std::vector<map_location>>(L, 2);
std::set<map_location> locs(locs_v.begin(), locs_v.end());
team &t = teams()[side_num - 1];
for (map_location const &loc : locs)
{
@ -721,6 +730,8 @@ int game_lua_kernel::intf_shroud_op(lua_State *L, bool place_shroud)
t.clear_shroud(loc);
}
}
} else {
return luaL_argerror(L, 2, "expected list of locations or shroud data string");
}
game_display_->labels().recalculate_shroud();
@ -2784,6 +2795,31 @@ int game_lua_kernel::intf_match_side(lua_State *L)
return 1;
}
int game_lua_kernel::intf_set_side_id(lua_State *L)
{
int team_i = luaL_checkinteger(L, 1) - 1;
std::string flag = luaL_optlstring(L, 2, "", nullptr);
std::string color = luaL_optlstring(L, 3, "", nullptr);
if(flag.empty() && color.empty()) {
return 0;
}
if(team_i < 0 || static_cast<size_t>(team_i) >= teams().size()) {
return luaL_error(L, "set_side_id: side number %d out of range", team_i);
}
team& side = teams()[team_i];
if(!color.empty()) {
side.set_color(color);
}
if(!flag.empty()) {
side.set_flag(flag);
}
game_display_->reinit_flags_for_side(team_i);
return 0;
}
int game_lua_kernel::intf_modify_ai_wml(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
@ -2797,160 +2833,35 @@ int game_lua_kernel::intf_modify_ai_wml(lua_State *L)
return 0;
}
int game_lua_kernel::intf_modify_side(lua_State *L)
static int intf_switch_ai(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
bool invalidate_screen = false;
std::string team_name = cfg["team_name"];
std::string user_team_name = cfg["user_team_name"];
std::string controller = cfg["controller"];
std::string defeat_condition = cfg["defeat_condition"];
std::string recruit_str = cfg["recruit"];
std::string shroud_data = cfg["shroud_data"];
std::string village_support = cfg["village_support"];
const config& parsed = cfg.get_parsed_config();
const config::const_child_itors &ai = parsed.child_range("ai");
std::string switch_ai = cfg["switch_ai"];
std::vector<int> sides = get_sides_vector(cfg);
size_t team_index;
for(const int &side_num : sides)
{
team_index = side_num - 1;
team & tm = teams()[team_index];
LOG_LUA << "modifying side: " << side_num << "\n";
if(!team_name.empty()) {
LOG_LUA << "change side's team to team_name '" << team_name << "'\n";
tm.change_team(team_name,
user_team_name);
} else if(!user_team_name.empty()) {
LOG_LUA << "change side's user_team_name to '" << user_team_name << "'\n";
tm.change_team(tm.team_name(),
user_team_name);
}
// Modify recruit list (override)
if (!recruit_str.empty()) {
tm.set_recruits(utils::set_split(recruit_str));
}
// Modify income
config::attribute_value income = cfg["income"];
if (!income.empty()) {
tm.set_base_income(income.to_int() + game_config::base_income);
}
// Modify total gold
config::attribute_value gold = cfg["gold"];
if (!gold.empty()) {
tm.set_gold(gold);
}
// Set controller
if (!controller.empty()) {
tm.change_controller_by_wml(controller);
}
// Set defeat_condition
if (!defeat_condition.empty()) {
tm.set_defeat_condition_string(defeat_condition);
}
// Set shroud
config::attribute_value shroud = cfg["shroud"];
if (!shroud.empty()) {
tm.set_shroud(shroud.to_bool(true));
invalidate_screen = true;
}
// Reset shroud
if ( cfg["reset_maps"].to_bool(false) ) {
tm.reshroud();
invalidate_screen = true;
}
// Merge shroud data
if (!shroud_data.empty()) {
tm.merge_shroud_map_data(shroud_data);
invalidate_screen = true;
}
// Set whether team is hidden in status table
config::attribute_value hidden = cfg["hidden"];
if (!hidden.empty()) {
tm.set_hidden(hidden.to_bool(true));
}
// Set fog
config::attribute_value fog = cfg["fog"];
if (!fog.empty()) {
tm.set_fog(fog.to_bool(true));
invalidate_screen = true;
}
// Reset fog
if ( cfg["reset_view"].to_bool(false) ) {
tm.refog();
invalidate_screen = true;
}
// Set income per village
config::attribute_value village_gold = cfg["village_gold"];
if (!village_gold.empty()) {
tm.set_village_gold(village_gold);
}
// Set support (unit levels supported per village, for upkeep purposes)
if (!village_support.empty()) {
tm.set_village_support(lexical_cast_default<int>(village_support, game_config::village_support));
}
// Redeploy ai from location (this ignores current AI parameters)
if (!switch_ai.empty()) {
ai::manager::add_ai_for_side_from_file(side_num,switch_ai,true);
}
// Override AI parameters
if (!ai.empty()) {
ai::manager::modify_active_ai_config_old_for_side(side_num,ai);
}
// Change team color
config::attribute_value color = cfg["color"];
if(!color.empty()) {
tm.set_color(color);
invalidate_screen = true;
}
// Change flag imageset
config::attribute_value flag = cfg["flag"];
if(!flag.empty()) {
tm.set_flag(flag);
// Needed especially when map isn't animated.
invalidate_screen = true;
}
// If either the flag set or the team color changed, we need to
// rebuild the team's flag cache to reflect the changes. Note that
// this is not required for flag icons (used by the theme UI only).
if((!color.empty() || !flag.empty()) && game_display_) {
game_display_->reinit_flags_for_side(team_index);
}
// Change flag icon
config::attribute_value flag_icon = cfg["flag_icon"];
if(!flag_icon.empty()) {
tm.set_flag_icon(flag_icon);
// Not needed.
//invalidate_screen = true;
}
tm.handle_legacy_share_vision(cfg.get_parsed_config());
// Suppress end turn confirmations?
config::attribute_value setc = cfg["suppress_end_turn_confirmation"];
if ( !setc.empty() ) {
tm.set_no_turn_confirmation(setc.to_bool());
}
// Change leader scrolling options
config::attribute_value stl = cfg["scroll_to_leader"];
if ( !stl.empty()) {
tm.set_scroll_to_leader(stl.to_bool(true));
}
int side_num = luaL_checkinteger(L, 1);
std::string file = luaL_checkstring(L, 2);
if(!ai::manager::add_ai_for_side_from_file(side_num, file)) {
std::string err = formatter() << "Could not load AI for side " << side_num + 1 << " from file " << file;
lua_pushlstring(L, err.c_str(), err.length());
return lua_error(L);
}
return 0;
}
// Flag an update of the screen, if needed.
if ( invalidate_screen && game_display_) {
game_display_->recalculate_minimap();
game_display_->invalidate_all();
static int intf_append_ai(lua_State *L)
{
int side_num = luaL_checkinteger(L, 1);
config cfg = luaW_checkconfig(L, 2);
if(!cfg.has_child("ai")) {
cfg = config_of("ai", cfg);
}
bool added_dummy_stage = false;
if(!cfg.child("ai").has_child("stage")) {
added_dummy_stage = true;
cfg.child("ai").add_child("stage", config_of("name", "empty"));
}
ai::configuration::expand_simplified_aspects(side_num, cfg);
if(added_dummy_stage) {
// TODO: Delete the dummy stage
}
ai::manager::append_active_ai_for_side(side_num, cfg.child("ai"));
return 0;
}
@ -4074,6 +3985,7 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "allow_end_turn", &dispatch<&game_lua_kernel::intf_allow_end_turn > },
{ "allow_undo", &dispatch<&game_lua_kernel::intf_allow_undo > },
{ "animate_unit", &dispatch<&game_lua_kernel::intf_animate_unit > },
{ "append_ai", &intf_append_ai },
{ "clear_menu_item", &dispatch<&game_lua_kernel::intf_clear_menu_item > },
{ "clear_messages", &dispatch<&game_lua_kernel::intf_clear_messages > },
{ "color_adjust", &dispatch<&game_lua_kernel::intf_color_adjust > },
@ -4122,7 +4034,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "match_unit", &dispatch<&game_lua_kernel::intf_match_unit > },
{ "message", &dispatch<&game_lua_kernel::intf_message > },
{ "modify_ai_wml", &dispatch<&game_lua_kernel::intf_modify_ai_wml > },
{ "modify_side", &dispatch<&game_lua_kernel::intf_modify_side > },
{ "open_help", &dispatch<&game_lua_kernel::intf_open_help > },
{ "play_sound", &dispatch<&game_lua_kernel::intf_play_sound > },
{ "print", &dispatch<&game_lua_kernel::intf_print > },
@ -4147,11 +4058,13 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "set_end_campaign_text", &dispatch<&game_lua_kernel::intf_set_end_campaign_text > },
{ "set_menu_item", &dispatch<&game_lua_kernel::intf_set_menu_item > },
{ "set_next_scenario", &dispatch<&game_lua_kernel::intf_set_next_scenario > },
{ "set_side_id", &dispatch<&game_lua_kernel::intf_set_side_id > },
{ "set_terrain", &dispatch<&game_lua_kernel::intf_set_terrain > },
{ "set_variable", &dispatch<&game_lua_kernel::intf_set_variable > },
{ "set_side_variable", &dispatch<&game_lua_kernel::intf_set_side_variable > },
{ "set_village_owner", &dispatch<&game_lua_kernel::intf_set_village_owner > },
{ "simulate_combat", &dispatch<&game_lua_kernel::intf_simulate_combat > },
{ "switch_ai", &intf_switch_ai },
{ "synchronize_choice", &intf_synchronize_choice },
{ "synchronize_choices", &intf_synchronize_choices },
{ "teleport", &dispatch<&game_lua_kernel::intf_teleport > },

View file

@ -141,8 +141,8 @@ class game_lua_kernel : public lua_kernel_base
int intf_get_villages(lua_State *L);
int intf_match_location(lua_State *L);
int intf_match_side(lua_State *L);
int intf_set_side_id(lua_State *L);
int intf_modify_ai_wml(lua_State *L);
int intf_modify_side(lua_State *L);
int intf_get_sides(lua_State* L);
int intf_add_tile_overlay(lua_State *L);
int intf_remove_tile_overlay(lua_State *L);

View file

@ -66,8 +66,8 @@ static int impl_side_get(lua_State *L)
return_bool_attrib("shroud", t.uses_shroud());
return_bool_attrib("hidden", t.hidden());
return_bool_attrib("scroll_to_leader", t.get_scroll_to_leader());
return_string_attrib("flag", t.flag());
return_string_attrib("flag_icon", t.flag_icon());
return_string_attrib("flag", t.flag().empty() ? game_config::images::flag : t.flag());
return_string_attrib("flag_icon", t.flag_icon().empty() ? game_config::images::flag_icon : t.flag_icon());
return_tstring_attrib("user_team_name", t.user_team_name());
return_string_attrib("team_name", t.team_name());
return_string_attrib("faction", t.faction());
@ -81,6 +81,10 @@ static int impl_side_get(lua_State *L)
return_bool_attrib("carryover_add", t.carryover_add());
return_bool_attrib("lost", t.lost());
return_bool_attrib("persistent", t.persistent());
return_bool_attrib("suppress_end_turn_confirmation", t.no_turn_confirmation());
return_string_attrib("share_vision", t.share_vision().to_string());
return_bool_attrib("share_maps", t.share_maps());
return_bool_attrib("share_view", t.share_view());
if (strcmp(m, "recruit") == 0) {
std::set<std::string> const &recruits = t.recruits();
@ -141,6 +145,18 @@ static int impl_side_set(lua_State *L)
modify_bool_attrib("carryover_add", t.set_carryover_add(value));
modify_bool_attrib("lost", t.set_lost(value));
modify_bool_attrib("persistent", t.set_persistent(value));
modify_bool_attrib("suppress_end_turn_confirmation", t.set_no_turn_confirmation(value));
modify_bool_attrib("shroud", t.set_shroud(value));
modify_bool_attrib("fog", t.set_fog(value));
modify_string_attrib("flag_icon", t.set_flag_icon(value));
modify_string_attrib("share_vision", {
team::SHARE_VISION v;
if(v.parse(value)) {
t.set_share_vision(v);
} else {
return luaL_argerror(L, 3, "Invalid share_vision value (should be 'all', 'none', or 'shroud')");
}
});
if (strcmp(m, "carryover_bonus") == 0) {
t.set_carryover_bonus(luaL_checknumber(L, 3));