Skip to content

Commit

Permalink
Properly port [animate_unit] to Lua
Browse files Browse the repository at this point in the history
  • Loading branch information
CelticMinstrel committed Dec 11, 2016
1 parent c58e2d0 commit 010acd8
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 146 deletions.
4 changes: 4 additions & 0 deletions changelog
Expand Up @@ -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,
Expand Down
5 changes: 1 addition & 4 deletions data/lua/wml-tags.lua
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions data/lua/wml/animate_unit.lua
@@ -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
157 changes: 151 additions & 6 deletions src/scripting/game_lua_kernel.cpp
Expand Up @@ -335,16 +335,145 @@ static int intf_get_viewing_side(lua_State *L)
}
}

int game_lua_kernel::intf_animate_unit(lua_State *L)
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)
{
// if (game_display_)
{
events::command_disabler disable_commands;
unit_display::wml_animation(luaW_checkvconfig(L, 1), get_event_info().loc1);
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_) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 },
Expand All @@ -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 > },
Expand Down Expand Up @@ -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 > },
Expand Down
1 change: 1 addition & 0 deletions src/scripting/game_lua_kernel.hpp
Expand Up @@ -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();
Expand Down

0 comments on commit 010acd8

Please sign in to comment.