Properly port [animate_unit] to Lua
This commit is contained in:
parent
c58e2d0095
commit
010acd870d
7 changed files with 273 additions and 148 deletions
|
@ -13,6 +13,10 @@ Version 1.13.6+dev:
|
|||
* 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 wesnoth.is_fogged and wesnoth.is_shrouded calls test the visibility level
|
||||
of a hex for a particular side.
|
||||
* New wesnoth.create_animator call produces an object that can be used to run
|
||||
animations for a group of units
|
||||
* 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,
|
||||
|
|
|
@ -13,6 +13,7 @@ end
|
|||
|
||||
wesnoth.require "lua/wml-flow.lua"
|
||||
wesnoth.require "lua/wml/objectives.lua"
|
||||
wesnoth.require "lua/wml/animate_unit.lua"
|
||||
wesnoth.require "lua/wml/items.lua"
|
||||
wesnoth.require "lua/wml/message.lua"
|
||||
wesnoth.require "lua/wml/object.lua"
|
||||
|
@ -916,10 +917,6 @@ function wml_actions.scroll(cfg)
|
|||
wesnoth.scroll(cfg)
|
||||
end
|
||||
|
||||
function wml_actions.animate_unit(cfg)
|
||||
wesnoth.animate_unit(cfg)
|
||||
end
|
||||
|
||||
function wml_actions.color_adjust(cfg)
|
||||
wesnoth.color_adjust(cfg)
|
||||
end
|
||||
|
|
114
data/lua/wml/animate_unit.lua
Normal file
114
data/lua/wml/animate_unit.lua
Normal file
|
@ -0,0 +1,114 @@
|
|||
local helper = wesnoth.require "lua/helper.lua"
|
||||
local T = helper.set_wml_tag_metatable{}
|
||||
|
||||
local function get_fake_attack(unit, cfg)
|
||||
-- This hacky-looking code is because the only way to create an
|
||||
-- attack object in Lua is by adding the attack to a unit.
|
||||
-- In this case, it's immediately deleted afterwards.
|
||||
local n = #unit.attacks + 1
|
||||
unit.attacks[n] = cfg
|
||||
local atk = unit.attacks[n]
|
||||
unit.attacks[n] = nil
|
||||
return atk
|
||||
end
|
||||
|
||||
local function get_real_attack(unit, filter)
|
||||
for i, atk in ipairs(unit.attacks) do
|
||||
if atk:matches(filter) then return atk end
|
||||
end
|
||||
end
|
||||
|
||||
local function add_animation(anim, cfg)
|
||||
cfg = helper.shallow_parsed(cfg)
|
||||
local filter = helper.get_child(cfg, "filter")
|
||||
local unit
|
||||
if filter then
|
||||
unit = wesnoth.get_units{
|
||||
limit = 1,
|
||||
T["and"](filter)
|
||||
}[1]
|
||||
else
|
||||
unit = wesnoth.get_unit(
|
||||
wesnoth.current.event_context.x1,
|
||||
wesnoth.current.event_context.y1
|
||||
)
|
||||
end
|
||||
|
||||
if unit and not wesnoth.is_fogged(wesnoth.current.side, u.loc) then
|
||||
local primary = helper.get_child(cfg, "primary_attack")
|
||||
local secondary = helper.get_child(cfg, "secondary_attack")
|
||||
local get_attack = get_real_attack
|
||||
if cfg.flag == "death" or cfg.flag == "victory" then
|
||||
-- death and victory animations need a special case
|
||||
-- In order to correctly fire certain animations, a dummy attack
|
||||
-- is required. This is especially evident in Wose death animations.
|
||||
get_attack = get_fake_attack
|
||||
end
|
||||
if primary then
|
||||
primary = get_attack(unit, primary)
|
||||
end
|
||||
if secondary then
|
||||
secondary = get_attack(unit, secondary)
|
||||
end
|
||||
|
||||
local hits = cfg.hits
|
||||
if hits == true then
|
||||
hits = 'hit'
|
||||
elseif hits == false then
|
||||
hits = 'miss'
|
||||
end
|
||||
|
||||
local color = {0xff, 0xff, 0xff}
|
||||
if cfg.red or cfg.green or cfg.blue then
|
||||
-- This tonumber() or 0 is to ensure they're all definitely numbers
|
||||
-- It works because tonumber() returns nil if its argument is not a number
|
||||
color = {
|
||||
tonumber(cfg.red) or 0,
|
||||
tonumber(cfg.green) or 0,
|
||||
tonumber(cfg.blue) or 0
|
||||
}
|
||||
end
|
||||
|
||||
-- TODO: The last argument is currently unused
|
||||
-- (should make the game not scroll if view locked or prefs disables it)
|
||||
wesnoth.scroll_to_tile(unit.x, unit.y, true, false, true, false)
|
||||
|
||||
local facing = helper.get_child(cfg, "facing")
|
||||
if facing then
|
||||
local facing_loc = wesnoth.get_locations(facing)[1]
|
||||
if facing_loc then
|
||||
local dir = wesnoth.map_location_ops.get_relative_dir(unit.x, unit.y, facing_loc[1], facing_loc[2])
|
||||
facing = wesnoth.map_location_ops.get_direction(unit.x, unit.y, dir)
|
||||
else
|
||||
facing = nil
|
||||
end
|
||||
end
|
||||
|
||||
local text = cfg.text
|
||||
if cfg.female_text and unit.gender == 'female' then
|
||||
text = cfg.female_text
|
||||
elseif cfg.male_text and unit.gender == 'male' then
|
||||
text = cfg.male_text
|
||||
end
|
||||
|
||||
anim:add(unit, cfg.flag, hits, {
|
||||
facing = facing,
|
||||
value = {tonumber(cfg.value) or 0, tonumber(cfg.value_second) or 0},
|
||||
with_bars = not not cfg.with_bars,
|
||||
text = text,
|
||||
color = color,
|
||||
primary = primary,
|
||||
secondary = secondary
|
||||
})
|
||||
end
|
||||
|
||||
for c in helper.child_range("animate") do
|
||||
add_animation(anim, c)
|
||||
end
|
||||
end
|
||||
|
||||
function wesnoth.wml_actions.animate_unit(cfg)
|
||||
local anim = wesnoth.create_animator()
|
||||
add_animation(anim, cfg)
|
||||
anim:run()
|
||||
end
|
|
@ -335,16 +335,145 @@ static int intf_get_viewing_side(lua_State *L)
|
|||
}
|
||||
}
|
||||
|
||||
int game_lua_kernel::intf_animate_unit(lua_State *L)
|
||||
{
|
||||
// if (game_display_)
|
||||
{
|
||||
events::command_disabler disable_commands;
|
||||
unit_display::wml_animation(luaW_checkvconfig(L, 1), get_event_info().loc1);
|
||||
}
|
||||
static const char animatorKey[] = "unit animator";
|
||||
|
||||
static int impl_animator_collect(lua_State* L) {
|
||||
unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
|
||||
anim.set_all_standing();
|
||||
anim.~unit_animator();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_add_animation(lua_State* L)
|
||||
{
|
||||
unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
|
||||
unit& u = luaW_checkunit(L, 2);
|
||||
std::string which = luaL_checkstring(L, 3);
|
||||
|
||||
using hit_type = unit_animation::hit_type;
|
||||
std::string hits_str = luaL_checkstring(L, 4);
|
||||
hit_type hits = hit_type::string_to_enum(hits_str, hit_type::INVALID);
|
||||
|
||||
map_location dest;
|
||||
int v1 = 0, v2 = 0;
|
||||
bool bars = false;
|
||||
std::string text;
|
||||
color_t color{255, 255, 255};
|
||||
const_attack_ptr primary, secondary;
|
||||
|
||||
if(lua_istable(L, 5)) {
|
||||
lua_getfield(L, 5, "facing");
|
||||
if(!luaW_tolocation(L, -1, dest)) {
|
||||
// luaW_tolocation may set the location to (0,0) if it fails
|
||||
dest = map_location();
|
||||
if(!lua_isnoneornil(L, -1)) {
|
||||
return luaW_type_error(L, -1, "location table");
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 5, "value");
|
||||
if(lua_isnumber(L, -1)) {
|
||||
v1 = lua_tonumber(L, -1);
|
||||
} else if(lua_istable(L, -1)) {
|
||||
lua_rawgeti(L, 1, 1);
|
||||
v1 = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_rawgeti(L, 1, 2);
|
||||
v2 = lua_tonumber(L, -1);
|
||||
lua_pop(L, 2);
|
||||
} else if(!lua_isnoneornil(L, -1)) {
|
||||
return luaW_type_error(L, -1, "number or array of two numbers");
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 5, "with_bars");
|
||||
if(lua_isboolean(L, -1)) {
|
||||
bars = luaW_toboolean(L, -1);
|
||||
} else if(!lua_isnoneornil(L, -1)) {
|
||||
return luaW_type_error(L, -1, lua_typename(L, LUA_TBOOLEAN));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 5, "text");
|
||||
if(lua_isstring(L, -1)) {
|
||||
text = lua_tostring(L, -1);
|
||||
} else if(!lua_isnoneornil(L, 01)) {
|
||||
return luaW_type_error(L, -1, lua_typename(L, LUA_TSTRING));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 5, "color");
|
||||
if(lua_istable(L, -1) && lua_rawlen(L, -1) == 3) {
|
||||
lua_rawgeti(L, 1, 1); // red @ -3
|
||||
lua_rawgeti(L, 1, 2); // green @ -2
|
||||
lua_rawgeti(L, 1, 3); // blue @ -1
|
||||
color = color_t(lua_tonumber(L, -3), lua_tonumber(L, -2), lua_tonumber(L, -1));
|
||||
lua_pop(L, 3);
|
||||
} else if(!lua_isnoneornil(L, -1)) {
|
||||
return luaW_type_error(L, -1, "array of three numbers");
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 5, "primary");
|
||||
primary = luaW_toweapon(L, -1);
|
||||
if(!primary && !lua_isnoneornil(L, -1)) {
|
||||
return luaW_type_error(L, -1, "weapon");
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 5, "secondary");
|
||||
secondary = luaW_toweapon(L, -1);
|
||||
if(!secondary && !lua_isnoneornil(L, -1)) {
|
||||
return luaW_type_error(L, -1, "weapon");
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
anim.add_animation(&u, which, u.get_location(), dest, v1, bars, text, color, hits, primary.get(), secondary.get(), v2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_run_animation(lua_State* L)
|
||||
{
|
||||
unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
|
||||
anim.start_animations();
|
||||
anim.wait_for_end();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_clear_animation(lua_State* L)
|
||||
{
|
||||
unit_animator& anim = *static_cast<unit_animator*>(luaL_checkudata(L, 1, animatorKey));
|
||||
anim.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_animator_get(lua_State* L)
|
||||
{
|
||||
const char* m = lua_tostring(L, 2);
|
||||
return luaW_getmetafield(L, 1, m);
|
||||
}
|
||||
|
||||
static int intf_create_animator(lua_State* L)
|
||||
{
|
||||
new(L) unit_animator;
|
||||
if(luaL_newmetatable(L, animatorKey)) {
|
||||
luaL_Reg metafuncs[] = {
|
||||
{"__gc", impl_animator_collect},
|
||||
{"__index", impl_animator_get},
|
||||
{"add", impl_add_animation},
|
||||
{"run", impl_run_animation},
|
||||
{"clear", impl_clear_animation},
|
||||
};
|
||||
luaL_setfuncs(L, metafuncs, 0);
|
||||
lua_pushstring(L, "__metatable");
|
||||
lua_setfield(L, -2, animatorKey);
|
||||
}
|
||||
lua_setmetatable(L, -2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int game_lua_kernel::intf_gamestate_inspector(lua_State *L)
|
||||
{
|
||||
if (game_display_) {
|
||||
|
@ -3774,6 +3903,20 @@ int game_lua_kernel::intf_log(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int game_lua_kernel::intf_get_fog_or_shroud(lua_State *L, bool fog)
|
||||
{
|
||||
int side = luaL_checknumber(L, 1);
|
||||
map_location loc = luaW_checklocation(L, 2);
|
||||
if(side < 1 || static_cast<size_t>(side) > teams().size()) {
|
||||
std::string error = "side " + std::to_string(side) + " does not exist";
|
||||
return luaL_argerror(L, 1, error.c_str());
|
||||
}
|
||||
|
||||
team& t = teams()[side - 1];
|
||||
lua_pushboolean(L, fog ? t.fogged(loc) : t.shrouded(loc));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the lifting and resetting of fog via WML.
|
||||
* Keeping affect_normal_fog as false causes only the fog override to be affected.
|
||||
|
@ -3888,6 +4031,7 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
|
|||
{ "add_modification", &intf_add_modification },
|
||||
{ "advance_unit", &intf_advance_unit },
|
||||
{ "copy_unit", &intf_copy_unit },
|
||||
{ "create_animator", &intf_create_animator },
|
||||
{ "create_unit", &intf_create_unit },
|
||||
{ "debug", &intf_debug },
|
||||
{ "debug_ai", &intf_debug_ai },
|
||||
|
@ -3914,7 +4058,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
|
|||
{ "add_sound_source", &dispatch<&game_lua_kernel::intf_add_sound_source > },
|
||||
{ "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 > },
|
||||
|
@ -3981,6 +4124,8 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
|
|||
{ "deselect_hex", &dispatch<&game_lua_kernel::intf_deselect_hex > },
|
||||
{ "select_unit", &dispatch<&game_lua_kernel::intf_select_unit > },
|
||||
{ "skip_messages", &dispatch<&game_lua_kernel::intf_skip_messages > },
|
||||
{ "is_fogged", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, true > },
|
||||
{ "is_shrouded", &dispatch2<&game_lua_kernel::intf_get_fog_or_shroud, false > },
|
||||
{ "is_skipping_messages", &dispatch<&game_lua_kernel::intf_is_skipping_messages > },
|
||||
{ "set_end_campaign_credits", &dispatch<&game_lua_kernel::intf_set_end_campaign_credits > },
|
||||
{ "set_end_campaign_text", &dispatch<&game_lua_kernel::intf_set_end_campaign_text > },
|
||||
|
|
|
@ -170,6 +170,7 @@ class game_lua_kernel : public lua_kernel_base
|
|||
int intf_get_sound_source(lua_State *L);
|
||||
int intf_log(lua_State *L);
|
||||
int intf_toggle_fog(lua_State *L, const bool clear);
|
||||
int intf_get_fog_or_shroud(lua_State *L, bool fog);
|
||||
|
||||
//private helpers
|
||||
std::string synced_state();
|
||||
|
|
|
@ -767,129 +767,4 @@ void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
|
|||
animator.set_all_standing();
|
||||
}
|
||||
|
||||
void wml_animation_internal(unit_animator &animator, const vconfig &cfg, const map_location &default_location = map_location::null_location());
|
||||
|
||||
void wml_animation(const vconfig &cfg, const map_location &default_location)
|
||||
{
|
||||
game_display &disp = *resources::screen;
|
||||
if (disp.video().update_locked() || disp.video().faked()) return;
|
||||
unit_animator animator;
|
||||
wml_animation_internal(animator, cfg, default_location);
|
||||
animator.start_animations();
|
||||
animator.wait_for_end();
|
||||
animator.set_all_standing();
|
||||
}
|
||||
|
||||
void wml_animation_internal(unit_animator &animator, const vconfig &cfg, const map_location &default_location)
|
||||
{
|
||||
unit_const_ptr u;
|
||||
|
||||
unit_map::const_iterator u_it = resources::gameboard->units().find(default_location);
|
||||
if (u_it.valid()) {
|
||||
u = u_it.get_shared_ptr();
|
||||
}
|
||||
|
||||
// Search for a valid unit filter,
|
||||
// and if we have one, look for the matching unit
|
||||
vconfig filter = cfg.child("filter");
|
||||
if(!filter.null()) {
|
||||
const unit_filter ufilt(filter, resources::filter_con);
|
||||
u = ufilt.first_match_on_map();
|
||||
}
|
||||
|
||||
// We have found a unit that matches the filter
|
||||
if (u && !resources::screen->fogged(u->get_location()))
|
||||
{
|
||||
attack_type *primary = nullptr;
|
||||
attack_type *secondary = nullptr;
|
||||
color_t text_color;
|
||||
unit_animation::hit_type hits= unit_animation::hit_type::INVALID;
|
||||
const_attack_itors attacks = u->attacks();
|
||||
const_attack_itors::const_iterator itor;
|
||||
attack_ptr dummy_primary;
|
||||
attack_ptr dummy_secondary;
|
||||
|
||||
// death and victory animations are handled here because usually
|
||||
// the code iterates through all the unit's attacks
|
||||
// but in these two specific cases we need to create dummy attacks
|
||||
// to fire correctly certain animations
|
||||
// this is especially evident with the Wose's death animations
|
||||
if (cfg["flag"] == "death" || cfg["flag"] == "victory") {
|
||||
filter = cfg.child("primary_attack");
|
||||
if(!filter.null()) {
|
||||
dummy_primary.reset(new attack_type(filter.get_config()));
|
||||
primary = dummy_primary.get();
|
||||
}
|
||||
filter = cfg.child("secondary_attack");
|
||||
if(!filter.null()) {
|
||||
dummy_secondary.reset(new attack_type(filter.get_config()));
|
||||
secondary = dummy_secondary.get();
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
filter = cfg.child("primary_attack");
|
||||
if(!filter.null()) {
|
||||
for(itor = attacks.begin(); itor != attacks.end(); ++itor){
|
||||
if(itor->matches_filter(filter.get_parsed_config())) {
|
||||
primary = &*itor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter = cfg.child("secondary_attack");
|
||||
if(!filter.null()) {
|
||||
for(itor = attacks.begin(); itor != attacks.end(); ++itor){
|
||||
if(itor->matches_filter(filter.get_parsed_config())) {
|
||||
secondary = &*itor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(cfg["hits"] == "yes" || cfg["hits"] == "hit") {
|
||||
hits = unit_animation::hit_type::HIT;
|
||||
}
|
||||
if(cfg["hits"] == "no" || cfg["hits"] == "miss") {
|
||||
hits = unit_animation::hit_type::MISS;
|
||||
}
|
||||
if( cfg["hits"] == "kill" ) {
|
||||
hits = unit_animation::hit_type::KILL;
|
||||
}
|
||||
if(cfg["red"].empty() && cfg["green"].empty() && cfg["blue"].empty()) {
|
||||
text_color = color_t(0xff,0xff,0xff);
|
||||
} else {
|
||||
text_color = color_t(cfg["red"], cfg["green"], cfg["blue"]);
|
||||
}
|
||||
resources::screen->scroll_to_tile(u->get_location(), game_display::ONSCREEN, true, false);
|
||||
vconfig t_filter_data = cfg.child("facing");
|
||||
map_location secondary_loc = map_location::null_location();
|
||||
if(!t_filter_data.empty()) {
|
||||
terrain_filter t_filter(t_filter_data, resources::filter_con);
|
||||
std::set<map_location> locs;
|
||||
t_filter.get_locations(locs);
|
||||
if (!locs.empty() && u->get_location() != *locs.begin()) {
|
||||
map_location::DIRECTION dir =u->get_location().get_relative_dir(*locs.begin());
|
||||
u->set_facing(dir);
|
||||
secondary_loc = u->get_location().get_direction(dir);
|
||||
}
|
||||
}
|
||||
config::attribute_value text = u->gender() == unit_race::FEMALE ? cfg["female_text"] : cfg["male_text"];
|
||||
if(text.blank()) {
|
||||
text = cfg["text"];
|
||||
}
|
||||
animator.add_animation(&*u, cfg["flag"], u->get_location(),
|
||||
secondary_loc, cfg["value"], cfg["with_bars"].to_bool(),
|
||||
text.str(), text_color, hits, primary, secondary,
|
||||
cfg["value_second"]);
|
||||
}
|
||||
const vconfig::child_list sub_anims = cfg.get_children("animate");
|
||||
vconfig::child_list::const_iterator anim_itor;
|
||||
for(anim_itor = sub_anims.begin(); anim_itor != sub_anims.end();++anim_itor) {
|
||||
wml_animation_internal(animator, *anim_itor);
|
||||
}
|
||||
|
||||
}
|
||||
} // end unit_display namespace
|
||||
|
|
|
@ -132,17 +132,6 @@ void unit_recruited(const map_location& loc,
|
|||
void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
|
||||
const std::string & extra_text="");
|
||||
|
||||
|
||||
/**
|
||||
* Parse a standard WML for animations and play the corresponding animation.
|
||||
* Returns once animation is played.
|
||||
*
|
||||
* This is used for the animate_unit action, but can easily be generalized if
|
||||
* other wml-described animations are needed.
|
||||
*/
|
||||
void wml_animation(const vconfig &cfg,
|
||||
const map_location& default_location=map_location::null_location());
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue