Properly port [music] to Lua

This commit is contained in:
Celtic Minstrel 2017-04-18 03:03:13 -04:00
parent 0a591cd424
commit 6906ced4e7
15 changed files with 343 additions and 5 deletions

View file

@ -21,6 +21,19 @@ Version 1.13.7+dev:
For example, wesnoth.scroll(5, -2)
* New is_local attribute in side proxy table allows you to detect whether or not the side is
controlled by a network player. Note that use of this has the potential for OOS errors.
* New wesnoth.music_list table which allows controlling the music playlist:
* wesnoth.music_list[1] etc returns mutable information about a specific track on the playlist
* #wesnoth.music_list counts the number of tracks on the playlist
* wesnoth.music_list.current returns mutable information about the currently-playing track
* wesnoth.music_list.current_i returns the index of the current track on the list
* wesnoth.music_list.play plays a specific track (as [music]play_once=yes)
* wesnoth.music_list.add appends a track to the playlist (as [music]append=yes)
* wesnoth.music_list.clear clears the current playlist
* wesnoth.music_list.force_refresh forces any pending playlist changes to be immediately applied
* Each track has modifiable shuffle, once, ms_before, ms_after attributes and
read-only append, immediate, name, title attributes. They are also
comparable with == or ~=
* wesnoth.set_music is now deprecated, in favour of the above new API
* Multiplayer:
* Fixed statistics being lost when reloading an MP game.
* Performance:

View file

@ -4,6 +4,10 @@
local helper = wesnoth.require "lua/helper.lua"
function wesnoth.set_music(cfg)
wesnoth.wml_actions.music(cfg)
end
-- Calling wesnoth.fire isn't the same as calling wesnoth.wml_actions[name] due to the passed vconfig userdata
-- which also provides "constness" of the passed wml object from the point of view of the caller.
-- So please don't remove since it's not deprecated.

View file

@ -266,7 +266,24 @@ function wml_actions.lua(cfg)
end
function wml_actions.music(cfg)
wesnoth.set_music(cfg)
if cfg.play_once then
wesnoth.music_list.play(cfg.name)
else
if not cfg.append then
if cfg.immediate then
wesnoth.music_list.current.once = true
end
wesnoth.music_list.clear()
end
wesnoth.music_list.add(cfg.name, not not cfg.immediate, cfg.ms_before or 0, cfg.ms_after or 0)
local n = #wesnoth.music_list
if cfg.shuffle == false then
wesnoth.music_list[n].shuffle = false
end
if cfg.title ~= nil then
wesnoth.music_list[n].title = cfg.title
end
end
end
-- This is mainly for use in unit test macros, but maybe it can be useful elsewhere too

View file

@ -155,7 +155,7 @@ function utils.handle_event_commands(cfg, scope_type)
current_exit = "none"
end
-- Apply music alterations once all the commands have been processed.
wesnoth.set_music()
wesnoth.music_list.force_refresh()
return current_exit
end

View file

@ -2581,6 +2581,12 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\scripting\lua_audio.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Scripting\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\scripting\lua_common.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Scripting\</ObjectFileName>
@ -3864,6 +3870,7 @@
<ClInclude Include="..\..\src\scripting\application_lua_kernel.hpp" />
<ClInclude Include="..\..\src\scripting\debug_lua.hpp" />
<ClInclude Include="..\..\src\scripting\game_lua_kernel.hpp" />
<ClInclude Include="..\..\src\scripting\lua_audio.hpp" />
<ClInclude Include="..\..\src\scripting\lua_common.hpp" />
<ClInclude Include="..\..\src\scripting\lua_cpp_function.hpp" />
<ClInclude Include="..\..\src\scripting\lua_fileops.hpp" />

View file

@ -1529,6 +1529,9 @@
<ClCompile Include="..\..\src\font\text_surface.cpp">
<Filter>Font</Filter>
</ClCompile>
<ClCompile Include="..\..\src\scripting\lua_audio.cpp">
<Filter>Scripting</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\addon\client.hpp">
@ -2972,6 +2975,9 @@
<ClInclude Include="..\..\src\font\pango\stream_ops.hpp">
<Filter>Font\Pango</Filter>
</ClInclude>
<ClInclude Include="..\..\src\scripting\lua_audio.hpp">
<Filter>Scripting</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="..\..\src\tests\test_sdl_utils.hpp">

View file

@ -338,6 +338,7 @@ savegame.cpp
scripting/application_lua_kernel.cpp
scripting/debug_lua.cpp
scripting/game_lua_kernel.cpp
scripting/lua_audio.cpp
scripting/lua_common.cpp
scripting/lua_cpp_function.cpp
scripting/lua_fileops.cpp

View file

@ -323,6 +323,7 @@ LEVEL_RESULT playsingle_controller::play_scenario(const config& level)
// instead should they want special music.
const std::string& end_music = select_music(is_victory);
if((!is_victory || end_level.transient.carryover_report) && !end_music.empty()) {
sound::empty_playlist();
sound::play_music_once(end_music);
}
persist_.end_transaction();

View file

@ -70,6 +70,7 @@
#include "recall_list_manager.hpp" // for recall_list_manager
#include "replay.hpp" // for get_user_choice, etc
#include "reports.hpp" // for register_generator, etc
#include "scripting/lua_audio.hpp"
#include "scripting/lua_unit.hpp"
#include "scripting/lua_unit_attacks.hpp"
#include "scripting/lua_common.hpp"
@ -2512,6 +2513,7 @@ int game_lua_kernel::intf_simulate_combat(lua_State *L)
*/
static int intf_set_music(lua_State *L)
{
lg::wml_error() << "set_music is deprecated; please use the wesnoth.playlist table instead!\n";
if (lua_isnoneornil(L, 1)) {
sound::commit_music_changes();
return 0;
@ -4122,6 +4124,9 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
lua_setfield(L, -2, "current");
lua_pop(L, 1);
// Create the playlist table with its metatable
cmd_log_ << lua_audio::register_table(L);
// Create the wml_actions table.
cmd_log_ << "Adding wml_actions table...\n";

225
src/scripting/lua_audio.cpp Normal file
View file

@ -0,0 +1,225 @@
/*
Copyright (C) 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "lua_audio.hpp"
#include "lua/lua.h"
#include "lua/lauxlib.h"
#include "scripting/lua_common.hpp"
#include "sound.hpp"
#include "sound_music_track.hpp"
#include "config_assign.hpp"
static const char* Track = "music track";
class music_track {
int i;
sound::music_track& track;
public:
explicit music_track(int i) : i(i), track(sound::get_track(i)) {}
bool valid() {
sound::music_track& current = sound::get_track(i);
return &track == &current && track.valid() && track.id() == current.id();
}
sound::music_track& operator*() {
return track;
}
sound::music_track* operator->() {
return &track;
}
int index() {
return i;
}
};
static music_track* push_track(lua_State* L, int i) {
music_track* trk = new(L) music_track(i);
luaL_setmetatable(L, Track);
return trk;
}
static music_track* get_track(lua_State* L, int i) {
return static_cast<music_track*>(luaL_checkudata(L, i, Track));
}
static int impl_music_get(lua_State* L) {
if(lua_isnumber(L, 2)) {
push_track(L, lua_tointeger(L, 2) - 1);
return 1;
}
const char* m = luaL_checkstring(L, 2);
if(strcmp(m, "current") == 0) {
push_track(L, sound::get_current_track());
return 1;
}
if(strcmp(m, "current_i") == 0) {
size_t i = sound::get_current_track();
if(i == sound::get_num_tracks()) {
lua_pushnil(L);
} else {
lua_pushinteger(L, i + 1);
}
return 1;
}
return luaW_getmetafield(L, 1, m);
}
static int impl_music_set(lua_State* L) {
if(lua_isnumber(L, 2)) {
music_track& track = *get_track(L, 3);
sound::set_track(lua_tointeger(L, 2), *track);
return 0;
}
// TODO: Set "current" and "current_i"
return 0;
}
static int impl_music_len(lua_State* L) {
lua_pushinteger(L, sound::get_num_tracks());
return 1;
}
static int intf_music_play(lua_State* L) {
sound::play_music_once(luaL_checkstring(L, 1));
return 0;
}
static int intf_music_add(lua_State* L) {
config cfg = config_of
("name", luaL_checkstring(L, 1))
("append", true);
bool found_ms_before = false, found_ms_after = false, found_imm = false;
for(int i = 2; i <= lua_gettop(L); i++) {
if(lua_isboolean(L, i)) {
if(found_imm) {
return luaL_argerror(L, i, "only one boolean argument may be passed");
} else {
cfg["immediate"] = luaW_toboolean(L, i);
}
} else if(lua_isnumber(L, i)) {
if(found_ms_after) {
return luaL_argerror(L, i, "only two integer arguments may be passed");
} else if(found_ms_before) {
cfg["ms_after"] = lua_tointeger(L, i);
found_ms_after = true;
} else {
cfg["ms_before"] = lua_tointeger(L, i);
found_ms_before = true;
}
} else {
return luaL_argerror(L, i, "unrecognized argument");
}
}
sound::play_music_config(cfg);
return 0;
}
static int intf_music_clear(lua_State*) {
sound::empty_playlist();
return 0;
}
static int intf_music_commit(lua_State*) {
sound::commit_music_changes();
return 0;
}
static int impl_track_get(lua_State* L) {
music_track& track = *get_track(L, 1);
const char* m = luaL_checkstring(L, 2);
return_bool_attrib("valid", track.valid());
if(!track.valid()) {
return luaL_error(L, "Tried to access member of track that is no longer valid.");
}
return_bool_attrib("append", track->append());
return_bool_attrib("shuffle", track->shuffle());
return_bool_attrib("immediate", track->immediate());
return_bool_attrib("once", track->play_once());
return_int_attrib("ms_before", track->ms_before());
return_int_attrib("ms_after", track->ms_after());
return_string_attrib("name", track->id());
return_string_attrib("title", track->title());
return luaW_getmetafield(L, 1, m);
}
static int impl_track_set(lua_State* L) {
music_track& track = *get_track(L, 1);
const char* m = luaL_checkstring(L, 2);
modify_bool_attrib("shuffle", track->set_shuffle(value));
modify_bool_attrib("once", track->set_play_once(value));
modify_int_attrib("ms_before", track->set_ms_before(value));
modify_int_attrib("ms_after", track->set_ms_after(value));
return 0;
}
static int impl_track_eq(lua_State* L) {
music_track* a = get_track(L, 1);
music_track* b = get_track(L, 2);
if(!a || !b) {
// This implies that one argument is not a music track, though I suspect this is dead code...?
// Does Lua ever call this if the arguments are not of the same type?
lua_pushboolean(L, false);
return 1;
}
if(!a->valid() && !b->valid()) {
lua_pushboolean(L, true);
return 1;
}
if(a->valid() && b->valid()) {
music_track& lhs = *a;
music_track& rhs = *b;
lua_pushboolean(L, lhs->id() == rhs->id() && lhs->shuffle() == rhs->shuffle() && lhs->play_once() == rhs->play_once() && lhs->ms_before() == rhs->ms_before() && lhs->ms_after() == rhs->ms_after());
return 1;
}
lua_pushboolean(L, false);
return 1;
}
namespace lua_audio {
std::string register_table(lua_State* L) {
// The music playlist metatable
lua_getglobal(L, "wesnoth");
lua_newuserdata(L, 0);
lua_createtable(L, 0, 4);
static luaL_Reg pl_callbacks[] = {
{ "__index", impl_music_get },
{ "__newindex", impl_music_set },
{ "__len", impl_music_len },
{ "play", intf_music_play },
{ "add", intf_music_add },
{ "clear", intf_music_clear },
{ "force_refresh", intf_music_commit },
{ nullptr, nullptr },
};
luaL_setfuncs(L, pl_callbacks, 0);
lua_pushstring(L, "music playlist");
lua_setfield(L, -2, "__metatable");
lua_setmetatable(L, -2);
lua_setfield(L, -2, "music_list");
lua_pop(L, 1);
// The music track metatable
luaL_newmetatable(L, Track);
static luaL_Reg track_callbacks[] = {
{ "__index", impl_track_get },
{ "__newindex", impl_track_set },
{ "__eq", impl_track_eq },
{ nullptr, nullptr },
};
luaL_setfuncs(L, track_callbacks, 0);
lua_pushstring(L, Track);
lua_setfield(L, -2, "__metatable");
return "Adding music playlist table...";
}
}

View file

@ -0,0 +1,20 @@
/*
Copyright (C) 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include <string>
struct lua_State;
namespace lua_audio {
std::string register_table(lua_State*);
}

View file

@ -207,7 +207,7 @@ namespace lua_team {
};
luaL_setfuncs(L, callbacks, 0);
lua_pushstring(L, "side");
lua_pushstring(L, Team);
lua_setfield(L, -2, "__metatable");
// Side methods
luaW_getglobal(L, "wesnoth", "match_side");

View file

@ -165,6 +165,33 @@ unsigned int current_track_index = 0;
}
namespace sound {
unsigned int get_current_track() {
return current_track_index;
}
unsigned int get_num_tracks() {
return current_track_list.size();
}
music_track& get_track(unsigned int i) {
static music_track dummy;
if(i < current_track_list.size()) {
return current_track_list[i];
}
if(i == current_track_list.size()) {
return current_track;
}
return dummy;
}
void set_track(unsigned int i, const music_track& to) {
if(i < current_track_list.size()) {
current_track_list[i] = to;
}
}
}
static bool track_ok(const std::string& id)
{
LOG_AUDIO << "Considering " << id << "\n";
@ -456,9 +483,9 @@ void stop_UI_sound() {
void play_music_once(const std::string &file)
{
// Clear list so it's not replayed.
current_track_list.clear();
current_track = music_track(file);
current_track.set_play_once(true);
current_track_index = current_track_list.size();
play_music();
}
@ -530,6 +557,7 @@ void play_music_repeatedly(const std::string &id)
// If we're already playing it, don't interrupt.
if (current_track != id) {
current_track = music_track(id);
current_track_index = 0;
play_music();
}
}
@ -545,6 +573,7 @@ void play_music_config(const config &music_node)
// If they say play once, we don't alter playlist.
if (track.play_once()) {
current_track = track;
current_track_index = current_track_list.size();
play_music();
return;
}
@ -574,6 +603,7 @@ void play_music_config(const config &music_node)
// They can tell us to start playing this list immediately.
if (track.immediate()) {
current_track = track;
current_track_index = current_track_list.size() - 1;
play_music();
} else if (!track.append()) { // Make sure the current track is finished
current_track.set_play_once(true);

View file

@ -101,6 +101,9 @@ void set_sound_volume(int vol);
void set_bell_volume(int vol);
void set_UI_volume(int vol);
unsigned int get_current_track();
unsigned int get_num_tracks();
}
#endif

View file

@ -47,6 +47,9 @@ public:
const std::string& title() const { return title_; }
void set_play_once(bool v) { once_ = v; }
void set_shuffle(bool v) { shuffle_ = v; }
void set_ms_before(int v) { ms_before_ = v; }
void set_ms_after(int v) { ms_after_ = v; }
private:
void resolve();
@ -63,6 +66,9 @@ private:
bool shuffle_;
};
music_track& get_track(unsigned int i);
void set_track(unsigned int i, const music_track& to);
} /* end namespace sound */
inline bool operator==(const sound::music_track& a, const sound::music_track& b) {