Merge pull request #962 from wesnoth/wml_tag_porting

Porting WML tags to Lua (part 2)
This commit is contained in:
Celtic Minstrel 2017-04-29 02:25:10 -04:00 committed by GitHub
commit cdca6b79d8
19 changed files with 642 additions and 252 deletions

View file

@ -17,6 +17,28 @@ Version 1.13.7+dev:
required to be a space adjacent to the unit.
* New modifiable theme attribute in wesnoth.game_config
* New wesnoth.zoom() function allows changing the zoom level
* The wesnoth.scroll function scrolls the screen by an offset, similar to [scroll].
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
It is writeable, allowing you to switch to any track on the list. This respects fade values.
* wesnoth.music_list.all returns a copy of the playlist that can be stored in a variable.
* 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.next fades out the current track and moves to a new track on the playlist
* wesnoth.music_list.force_refresh forces any pending playlist changes to be immediately applied
* wesnoth.music_list.volume attribute gets/sets the current music volume, as [volume]music=
* 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
* New wesnoth.sound_volume function gets/sets the current sound volume, as [volume]sound=
* Multiplayer:
* Fixed statistics being lost when reloading an MP game.
* Performance:
@ -44,6 +66,8 @@ Version 1.13.7+dev:
* Empty tags are no longer written to the configs of [unit]s and [side]s.
* New [change_theme] tag to change the theme mid-scenario
* New [zoom] tag allows changing the zoom level from an event
* [kill]animate=yes now plays victory animations if applicable
* Fix [volume] not accepting 100% as the new volume.
Version 1.13.7:
* AI:

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,35 @@ 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
function wml_actions.volume(cfg)
if cfg.music then
local rel = tonumber(cfg.music) or 100.0
wesnoth.music_list.volume = rel
end
if cfg.sound then
local rel = tonumber(cfg.sound) or 100.0
wesnoth.sound_volume(rel)
end
end
-- This is mainly for use in unit test macros, but maybe it can be useful elsewhere too
@ -919,7 +947,16 @@ function wml_actions.replace_schedule(cfg)
end
function wml_actions.scroll(cfg)
wesnoth.scroll(cfg)
local sides = utils.get_sides(cfg)
local have_human = false
for i, side in ipairs(sides) do
if side.controller == 'human' and side.is_local then
have_human = true
end
end
if have_human or #sides == 0 then
wesnoth.scroll(cfg.x, cfg.y)
end
end
function wml_actions.color_adjust(cfg)
@ -950,8 +987,73 @@ function wml_actions.inspect(cfg)
wesnoth.gamestate_inspector(cfg)
end
local kill_recursion_preventer = wesnoth.require("lua/location_set.lua").create()
function wml_actions.kill(cfg)
wesnoth.kill(cfg)
local number_killed = 0
local secondary_unit = helper.get_child(cfg, "secondary_unit")
local killer_loc = {0, 0}
if secondary_unit then
secondary_unit = wesnoth.get_units(secondary_unit)[1]
if cfg.fire_event then
if secondary_unit then
killer_loc = {secondary_unit.loc}
else
wesnoth.log("warn", "failed to match [secondary_unit] in [kill] with a single on-board unit")
end
end
end
local dead_men_walking = wesnoth.get_units(cfg)
for i,unit in ipairs(dead_men_walking) do
local death_loc = {x = tonumber(unit.x) or 0, y = tonumber(unit.y) or 0}
if not secondary_unit then killer_loc = death_loc end
local can_fire = false
local recursion = (kill_recursion_preventer:get(death_loc.x, death_loc.y) or 0) + 1
if cfg.fire_event then
kill_recursion_preventer:insert(death_loc.x, death_loc.y, recursion)
can_fire = true
if death_loc.x == wesnoth.current.event.x1 and death_loc.y == wesnoth.current.event.y1 then
if wesnoth.current.event.name == "die" or wesnoth.current.event.name == "last breath" then
if recursion >= 10 then
can_fire = false;
wesnoth.log("error", "tried to fire 'die' or 'last breath' event on unit from the unit's 'die' or 'last breath' event with first_time_only=no!")
end
end
end
end
if can_fire then
wesnoth.fire_event("last breath", death_loc, killer_loc)
end
if cfg.animate then
wesnoth.scroll_to_tile(death_loc)
local anim = wesnoth.create_animator()
-- Possible TODO: Add weapon selection? (That's kinda a pain right now; see animate_unit defn)
anim:add(unit, "death", "kill")
if secondary_unit then
anim:add(secondary_unit, "victory", "kill")
end
anim:run()
end
wml_actions.redraw{}
if can_fire then
wesnoth.fire_event("die", death_loc, killer_loc)
end
if cfg.fire_event then
if recursion <= 1 then
kill_recursion_preventer:remove(death_loc.x, death_loc.y)
else
kill_recursion_preventer:insert(death_loc.x, death_loc.y, recursion)
end
end
-- Test that it's valid (and still on the map) first, in case the event erased (or extracted) it.
if unit.valid == "map" then unit:erase() end
number_killed = number_killed + 1
end
-- TODO: Do I need to check recall lists or was that covered by the above loop?
return number_killed
end
function wml_actions.label( cfg )

View file

@ -154,8 +154,6 @@ function utils.handle_event_commands(cfg, scope_type)
end
current_exit = "none"
end
-- Apply music alterations once all the commands have been processed.
wesnoth.set_music()
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>
@ -3866,6 +3872,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

@ -18,6 +18,7 @@
#include "variable.hpp" // vconfig
#include "game_data.hpp"
#include "units/unit.hpp"
#include "sound.hpp"
#include <cassert>
#include <iterator>
@ -113,6 +114,7 @@ namespace {
game_events::queued_event q(tag, "", map_location(x1, y1, wml_loc()), map_location(x2, y2, wml_loc()), e.data);
resources::lua_kernel->run_wml_action("command", vconfig(e.commands), q);
sound::commit_music_changes();
x1 = oldx1; y1 = oldy1;
x2 = oldx2; y2 = oldy2;

View file

@ -929,34 +929,6 @@ WML_HANDLER_FUNCTION(unit,, cfg)
}
WML_HANDLER_FUNCTION(volume,, cfg)
{
int vol;
float rel;
std::string music = cfg["music"];
std::string sound = cfg["sound"];
if(!music.empty()) {
vol = preferences::music_volume();
rel = atof(music.c_str());
if (rel >= 0.0f && rel < 100.0f) {
vol = static_cast<int>(rel*vol/100.0f);
}
sound::set_music_volume(vol);
}
if(!sound.empty()) {
vol = preferences::sound_volume();
rel = atof(sound.c_str());
if (rel >= 0.0f && rel < 100.0f) {
vol = static_cast<int>(rel*vol/100.0f);
}
sound::set_sound_volume(vol);
}
}
WML_HANDLER_FUNCTION(on_undo, event_info, cfg)
{
if(cfg["delayed_variable_substitution"].to_bool(false)) {

View file

@ -30,6 +30,7 @@
#include "reports.hpp"
#include "scripting/game_lua_kernel.hpp"
#include "serialization/string_utils.hpp"
#include "sound.hpp"
#include "soundsource.hpp"
#include <iostream>
@ -127,6 +128,7 @@ void event_handler::handle_event(const queued_event& event_info, handler_ptr& ha
// *WARNING*: At this point, dereferencing this could be a memory violation!
lk.run_wml_action("command", vcfg, event_info);
sound::commit_music_changes();
}
bool event_handler::matches_name(const std::string &name, const game_data * gd) const

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;
@ -2536,6 +2538,26 @@ int game_lua_kernel::intf_play_sound(lua_State *L)
return 0;
}
/**
* Gets/sets the current sound volume
* - Arg 1: (optional) New volume to set
* - Return: Original volume
*/
static int intf_sound_volume(lua_State* L)
{
int vol = preferences::sound_volume();
lua_pushnumber(L, sound::get_sound_volume() * 100.0f / vol);
if(lua_isnumber(L, 1)) {
float rel = lua_tonumber(L, 1);
if(rel < 0.0f || rel > 100.0f) {
return luaL_argerror(L, 1, "volume must be in range 0..100");
}
vol = static_cast<int>(rel*vol / 100.0f);
sound::set_sound_volume(vol);
}
return 1;
}
/**
* Scrolls to given tile.
* - Arg 1: location.
@ -3231,165 +3253,6 @@ int game_lua_kernel::intf_delay(lua_State *L)
return 0;
}
namespace { // Types
class recursion_preventer {
typedef std::map<map_location, int> counter;
static counter counter_;
static const int max_recursion = 10;
map_location loc_;
bool too_many_recursions_;
public:
recursion_preventer(map_location& loc) :
loc_(loc),
too_many_recursions_(false)
{
counter::iterator inserted = counter_.emplace(loc_, 0).first;
++inserted->second;
too_many_recursions_ = inserted->second >= max_recursion;
}
~recursion_preventer()
{
counter::iterator itor = counter_.find(loc_);
if (--itor->second == 0)
{
counter_.erase(itor);
}
}
bool too_many_recursions() const
{
return too_many_recursions_;
}
};
recursion_preventer::counter recursion_preventer::counter_;
typedef std::unique_ptr<recursion_preventer> recursion_preventer_ptr;
} // end anonymouse namespace (types)
int game_lua_kernel::intf_kill(lua_State *L)
{
vconfig cfg(luaW_checkvconfig(L, 1));
const game_events::queued_event &event_info = get_event_info();
size_t number_killed = 0;
bool secondary_unit = cfg.has_child("secondary_unit");
game_events::entity_location killer_loc(map_location(0, 0));
if(cfg["fire_event"].to_bool() && secondary_unit)
{
secondary_unit = false;
const unit_filter ufilt(cfg.child("secondary_unit"), &game_state_);
for(unit_map::const_unit_iterator unit = units().begin(); unit; ++unit) {
if ( ufilt( *unit) )
{
killer_loc = game_events::entity_location(*unit);
secondary_unit = true;
break;
}
}
if(!secondary_unit) {
WRN_LUA << "failed to match [secondary_unit] in [kill] with a single on-board unit" << std::endl;
}
}
//Find all the dead units first, because firing events ruins unit_map iteration
std::vector<unit *> dead_men_walking;
const unit_filter ufilt(cfg, &game_state_);
for (unit & u : units()){
if ( ufilt(u) ) {
dead_men_walking.push_back(&u);
}
}
for(unit * un : dead_men_walking) {
map_location loc(un->get_location());
bool fire_event = false;
game_events::entity_location death_loc(*un);
if(!secondary_unit) {
killer_loc = game_events::entity_location(*un);
}
if (cfg["fire_event"].to_bool())
{
// Prevent infinite recursion of 'die' events
fire_event = true;
recursion_preventer_ptr recursion_prevent;
if (event_info.loc1 == death_loc && (event_info.name == "die" || event_info.name == "last breath"))
{
recursion_prevent.reset(new recursion_preventer(death_loc));
if(recursion_prevent->too_many_recursions())
{
fire_event = false;
ERR_LUA << "tried to fire 'die' or 'last breath' event on primary_unit inside its own 'die' or 'last breath' event with 'first_time_only' set to false!" << std::endl;
}
}
}
if (fire_event) {
play_controller_.pump().fire("last_breath", death_loc, killer_loc);
}
// Visual consequences of the kill.
if (game_display_) {
if (cfg["animate"].to_bool()) {
game_display_->scroll_to_tile(loc);
if (unit_map::iterator iun = units().find(loc)) {
unit_display::unit_die(loc, *iun);
}
} else {
// Make sure the unit gets (fully) cleaned off the screen.
game_display_->invalidate(loc);
if (unit_map::iterator iun = units().find(loc)) {
iun->anim_comp().invalidate(*game_display_);
}
}
game_display_->redraw_minimap();
}
if (fire_event) {
play_controller_.pump().fire("die", death_loc, killer_loc);
unit_map::iterator iun = units().find(death_loc);
if ( death_loc.matches_unit(iun) ) {
units().erase(iun);
}
}
else units().erase(loc);
++number_killed;
}
// If the filter doesn't contain positional information,
// then it may match units on all recall lists.
const config::attribute_value cfg_x = cfg["x"];
const config::attribute_value cfg_y = cfg["y"];
if((cfg_x.empty() || cfg_x == "recall")
&& (cfg_y.empty() || cfg_y == "recall"))
{
//remove the unit from the corresponding team's recall list
for(std::vector<team>::iterator pi = teams().begin();
pi!=teams().end(); ++pi)
{
for(std::vector<unit_ptr>::iterator j = pi->recall_list().begin(); j != pi->recall_list().end();) { //TODO: This block is really messy, cleanup somehow...
scoped_recall_unit auto_store("this_unit", pi->save_id(), j - pi->recall_list().begin());
if (ufilt( *(*j), map_location() )) {
j = pi->recall_list().erase(j);
++number_killed;
} else {
++j;
}
}
}
}
lua_pushinteger(L, number_killed);
return 1;
}
int game_lua_kernel::intf_label(lua_State *L)
{
if (game_display_) {
@ -3748,21 +3611,11 @@ int game_lua_kernel::intf_set_time_of_day(lua_State * L)
int game_lua_kernel::intf_scroll(lua_State * L)
{
vconfig cfg = luaW_checkvconfig(L, 1);
int x = luaL_checkinteger(L, 1), y = luaL_checkinteger(L, 2);
if (game_display_) {
const std::vector<int> side_list = get_sides_vector(cfg);
bool side_match = false;
for (int side : side_list) {
if(board().get_team(side).is_local_human()) {
side_match = true;
break;
}
}
if ((cfg["side"].empty() && !cfg.has_child("filter_side")) || side_match) {
game_display_->scroll(cfg["x"], cfg["y"], true);
game_display_->draw(true,true);
}
game_display_->scroll(x, y, true);
game_display_->draw(true, true);
}
return 0;
@ -4110,6 +3963,7 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "modify_ai", &intf_modify_ai_old },
{ "remove_modifications", &intf_remove_modifications },
{ "set_music", &intf_set_music },
{ "sound_volume", &intf_sound_volume },
{ "transform_unit", &intf_transform_unit },
{ "unit_defense", &intf_unit_defense },
{ "unit_movement_cost", &intf_unit_movement_cost },
@ -4163,7 +4017,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "get_displayed_unit", &dispatch<&game_lua_kernel::intf_get_displayed_unit > },
{ "highlight_hex", &dispatch<&game_lua_kernel::intf_highlight_hex > },
{ "is_enemy", &dispatch<&game_lua_kernel::intf_is_enemy > },
{ "kill", &dispatch<&game_lua_kernel::intf_kill > },
{ "label", &dispatch<&game_lua_kernel::intf_label > },
{ "lock_view", &dispatch<&game_lua_kernel::intf_lock_view > },
{ "log_replay", &dispatch<&game_lua_kernel::intf_log_replay > },
@ -4292,6 +4145,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";

View file

@ -149,7 +149,6 @@ class game_lua_kernel : public lua_kernel_base
int intf_remove_event(lua_State *L);
int intf_color_adjust(lua_State *L);
int intf_delay(lua_State *L);
int intf_kill(lua_State *L);
int intf_label(lua_State *L);
int intf_redraw(lua_State *L);
int intf_replace_schedule(lua_State *l);

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

@ -0,0 +1,295 @@
/*
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 "scripting/push_check.hpp"
#include "sound.hpp"
#include "sound_music_track.hpp"
#include "config_assign.hpp"
#include "preferences.hpp"
#include <set>
static const char* Track = "music track";
class music_track {
std::shared_ptr<sound::music_track> track;
public:
explicit music_track(int i) : track(sound::get_track(i)) {}
bool valid() const {
return track && track->valid();
}
sound::music_track& operator*() {
return *track;
}
const sound::music_track& operator*() const {
return *track;
}
std::shared_ptr<sound::music_track> operator->() {
return track;
}
std::shared_ptr<const sound::music_track> operator->() const {
return track;
}
};
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;
}
if(strcmp(m, "all") == 0) {
config playlist;
sound::write_music_play_list(playlist);
const auto& range = playlist.child_range("music");
std::vector<config> tracks(range.begin(), range.end());
lua_push(L, tracks);
return 1;
}
// This calculation reverses the one used in [volume] to get back the relative volume level.
// (Which is the same calculation that's duplicated in impl_music_set.)
return_float_attrib("volume", sound::get_music_volume() * 100.0f / preferences::music_volume());
return luaW_getmetafield(L, 1, m);
}
static int impl_music_set(lua_State* L) {
if(lua_isnumber(L, 2)) {
unsigned int i = lua_tointeger(L, 2) - 1;
config cfg;
if(lua_isnil(L, 3)) {
if(i < sound::get_num_tracks()) {
sound::remove_track(i);
}
} else if(luaW_toconfig(L, 3, cfg)) {
// Don't allow play_once=yes
if(cfg["play_once"]) {
return luaL_argerror(L, 3, "For play_once, use wesnoth.music_list.play instead");
}
if(i < sound::get_num_tracks()) {
sound::play_music_config(cfg);
} else {
// Remove the track at that index and add the new one in its place
// It's a little inefficient though...
sound::remove_track(i);
sound::play_music_config(cfg, i);
}
} else {
music_track& track = *get_track(L, 3);
if(i < sound::get_num_tracks()) {
sound::set_track(i, track.operator->());
} else {
track->write(cfg, true);
sound::play_music_config(cfg);
}
}
return 0;
}
const char* m = luaL_checkstring(L, 2);
modify_float_attrib_check_range("volume", sound::set_music_volume(value * preferences::music_volume() / 100.0f), 0.0, 100.0);
modify_int_attrib_check_range("current_i", sound::play_track(value - 1), 1, static_cast<int>(sound::get_num_tracks()));
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_next(lua_State* L) {
size_t n = sound::get_num_tracks();
if(n > 0) {
sound::play_track(n);
}
return 0;
}
static int intf_music_add(lua_State* L) {
int i = -1;
if(lua_isinteger(L, 1)) {
i = lua_tointeger(L, 1);
lua_remove(L, 1);
}
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, i);
return 0;
}
static int intf_music_clear(lua_State*) {
sound::empty_playlist();
return 0;
}
static int intf_music_remove(lua_State* L) {
// Use a non-standard comparator to ensure iteration in descending order
std::set<int, std::greater<int>> to_remove;
for(int i = 1; i <= lua_gettop(L); i++) {
to_remove.insert(luaL_checkinteger(L, i));
}
for(int i : to_remove) {
sound::remove_track(i);
}
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 },
{ "remove", intf_music_remove },
{ "next", intf_music_next },
{ "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

@ -74,6 +74,7 @@ static int impl_side_get(lua_State *L)
return_tstring_attrib("faction_name", t.faction_name());
return_string_attrib("color", t.color());
return_cstring_attrib("controller", t.controller().to_string().c_str());
return_bool_attrib("is_local", t.is_local());
return_string_attrib("defeat_condition", t.defeat_condition().to_string());
return_string_attrib("share_vision", t.share_vision().to_string());
return_float_attrib("carryover_bonus", t.carryover_bonus());
@ -206,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

@ -158,11 +158,57 @@ std::vector<std::string> played_before;
// Use the music_track default constructor to avoid trying to
// invoke a log object while resolving paths.
//
std::vector<sound::music_track> current_track_list;
sound::music_track current_track;
sound::music_track last_track;
std::vector<std::shared_ptr<sound::music_track>> current_track_list;
std::shared_ptr<sound::music_track> current_track;
unsigned int current_track_index = 0;
std::vector<std::shared_ptr<sound::music_track>>::const_iterator find_track(const sound::music_track& track) {
return std::find_if(current_track_list.begin(), current_track_list.end(), [&track](const std::shared_ptr<const sound::music_track>& ptr) {
return *ptr == track;
});
}
}
namespace sound {
unsigned int get_current_track() {
return current_track_index;
}
unsigned int get_num_tracks() {
return current_track_list.size();
}
std::shared_ptr<music_track> get_track(unsigned int i) {
if(i < current_track_list.size()) {
return current_track_list[i];
}
if(i == current_track_list.size()) {
return current_track;
}
return nullptr;
}
void set_track(unsigned int i, const std::shared_ptr<music_track>& to) {
if(i < current_track_list.size() && find_track(*to) != current_track_list.end()) {
current_track_list[i] = std::make_shared<music_track>(*to);
}
}
void remove_track(unsigned int i) {
if(i >= current_track_list.size()) {
return;
}
if(i == current_track_index) {
// Let the track finish playing
current_track->set_play_once(true);
// Set current index to the new size of the list
current_track_index = current_track_list.size() - 1;
} else if(i < current_track_index) {
current_track_index--;
}
current_track_list.erase(current_track_list.begin() + i);
}
}
static bool track_ok(const std::string& id)
@ -171,7 +217,7 @@ static bool track_ok(const std::string& id)
// If they committed changes to list, we forget previous plays, but
// still *never* repeat same track twice if we have an option.
if (id == current_track.file_path())
if (id == current_track->file_path())
return false;
if (current_track_list.size() <= 3)
@ -222,7 +268,7 @@ static bool track_ok(const std::string& id)
}
static const sound::music_track &choose_track()
static std::shared_ptr<sound::music_track> choose_track()
{
assert(!current_track_list.empty());
@ -230,21 +276,21 @@ static const sound::music_track &choose_track()
current_track_index = 0;
}
if (current_track_list[current_track_index].shuffle()) {
if (current_track_list[current_track_index]->shuffle()) {
unsigned int track = 0;
if (current_track_list.size() > 1) {
do {
track = rand()%current_track_list.size();
} while (!track_ok( current_track_list[track].file_path() ));
} while (!track_ok( current_track_list[track]->file_path() ));
}
current_track_index = track;
}
//LOG_AUDIO << "Next track will be " << current_track_list[track].file_path() << "\n";
played_before.push_back( current_track_list[current_track_index].file_path() );
return current_track_list[current_track_index++];
DBG_AUDIO << "Next track will be " << current_track_list[current_track_index]->file_path() << "\n";
played_before.push_back( current_track_list[current_track_index]->file_path() );
return current_track_list[current_track_index];
}
static std::string pick_one(const std::string &files)
@ -456,9 +502,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 = std::make_shared<music_track>(file);
current_track->set_play_once(true);
current_track_index = current_track_list.size();
play_music();
}
@ -469,10 +515,23 @@ void empty_playlist()
void play_music()
{
if(!current_track) {
return;
}
music_start_time = 1; //immediate (same as effect as SDL_GetTicks())
want_new_music=true;
no_fading=false;
fadingout_time=current_track.ms_after();
fadingout_time = current_track->ms_after();
}
void play_track(unsigned int i) {
if(i >= current_track_list.size()) {
current_track = choose_track();
} else {
current_track_index = i;
current_track = current_track_list[i];
}
play_music();
}
static void play_new_music()
@ -480,11 +539,11 @@ static void play_new_music()
music_start_time = 0; //reset status: no start time
want_new_music = true;
if(!preferences::music_on() || !mix_ok || !current_track.valid()) {
if(!preferences::music_on() || !mix_ok || !current_track->valid()) {
return;
}
const std::string& filename = current_track.file_path();
const std::string& filename = current_track->file_path();
auto itor = music_cache.find(filename);
if(itor == music_cache.end()) {
@ -499,11 +558,10 @@ static void play_new_music()
return;
}
itor = music_cache.emplace(filename, music).first;
last_track=current_track;
}
LOG_AUDIO << "Playing track '" << filename << "'\n";
int fading_time=current_track.ms_before();
int fading_time = current_track->ms_before();
if(no_fading)
{
fading_time=0;
@ -525,16 +583,19 @@ void play_music_repeatedly(const std::string &id)
return;
current_track_list.clear();
current_track_list.push_back(music_track(id));
current_track_list.emplace_back(new music_track(id));
std::shared_ptr<music_track> last_track = current_track;
current_track = current_track_list.back();
current_track_index = 0;
// If we're already playing it, don't interrupt.
if (current_track != id) {
current_track = music_track(id);
if(!last_track || *last_track != *current_track) {
play_music();
}
}
void play_music_config(const config &music_node)
void play_music_config(const config &music_node, int i)
{
music_track track( music_node );
@ -544,7 +605,8 @@ 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 = std::make_shared<music_track>(track);
current_track_index = current_track_list.size();
play_music();
return;
}
@ -554,29 +616,35 @@ void play_music_config(const config &music_node)
current_track_list.clear();
}
if(track.valid()) {
// Avoid 2 tracks with the same name, since that can cause an infinite loop
// in choose_track(), 2 tracks with the same name will always return the
// current track and track_ok() doesn't allow that.
std::vector<music_track>::const_iterator itor = current_track_list.begin();
while(itor != current_track_list.end()) {
if(track == *itor) break;
++itor;
}
if(!track.valid()) {
return;
}
if(itor == current_track_list.end()) {
current_track_list.push_back(track);
auto iter = find_track(track);
// Avoid 2 tracks with the same name, since that can cause an infinite loop
// in choose_track(), 2 tracks with the same name will always return the
// current track and track_ok() doesn't allow that.
if(iter == current_track_list.end()) {
if(i < 0 || static_cast<size_t>(i) >= current_track_list.size()) {
current_track_list.emplace_back(new music_track(track));
iter = current_track_list.end() - 1;
} else {
ERR_AUDIO << "tried to add duplicate track '" << track.file_path() << "'" << std::endl;
iter = current_track_list.emplace(current_track_list.begin() + 1, new music_track(track));
if(current_track_index >= static_cast<size_t>(i)) {
current_track_index++;
}
}
} else {
ERR_AUDIO << "tried to add duplicate track '" << track.file_path() << "'" << std::endl;
}
// They can tell us to start playing this list immediately.
if (track.immediate()) {
current_track = track;
current_track = *iter;
current_track_index = iter - current_track_list.begin();
play_music();
} else if (!track.append()) { // Make sure the current track is finished
current_track.set_play_once(true);
current_track->set_play_once(true);
}
}
@ -643,13 +711,14 @@ void commit_music_changes()
played_before.clear();
// Play-once is OK if still playing.
if (current_track.play_once())
if (current_track->play_once())
return;
// If current track no longer on playlist, change it.
for (const music_track &m : current_track_list) {
if (current_track == m)
for(auto m : current_track_list) {
if(*current_track == *m) {
return;
}
}
// Victory empties playlist: if next scenario doesn't specify one...
@ -665,8 +734,8 @@ void write_music_play_list(config& snapshot)
{
// First entry clears playlist, others append to it.
bool append = false;
for (music_track &m : current_track_list) {
m.write(snapshot, append);
for(auto m : current_track_list) {
m->write(snapshot, append);
append = true;
}
}
@ -866,6 +935,14 @@ void play_UI_sound(const std::string& files)
}
}
int get_music_volume()
{
if(mix_ok) {
return Mix_VolumeMusic(-1);
}
return 0;
}
void set_music_volume(int vol)
{
if(mix_ok && vol >= 0) {
@ -876,6 +953,15 @@ void set_music_volume(int vol)
}
}
int get_sound_volume()
{
if(mix_ok) {
// Since set_sound_volume sets all main channels to the same, just return the volume of any main channel
return Mix_Volume(source_channel_start, -1);
}
return 0;
}
void set_sound_volume(int vol)
{
if(mix_ok && vol >= 0) {

View file

@ -41,7 +41,7 @@ void stop_UI_sound();
void stop_bell();
// Read config entry, alter track list accordingly.
void play_music_config(const config &music_node);
void play_music_config(const config &music_node, int i = -1);
// Act on any track list changes from above.
void commit_music_changes();
@ -96,11 +96,18 @@ public:
// Save music playlist for snapshot
void write_music_play_list(config& snapshot);
int get_music_volume();
int get_sound_volume();
void set_music_volume(int vol);
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();
void remove_track(unsigned int i);
void play_track(unsigned int i);
}
#endif

View file

@ -17,6 +17,7 @@
#define SOUND_MUSIC_TRACK_HPP_INCLUDED
#include <string>
#include <memory>
class config;
@ -30,7 +31,7 @@ class music_track
public:
music_track();
music_track(const config& node);
music_track(const std::string& v_name);
explicit music_track(const std::string& v_name);
void write(config& parent_node, bool append) const;
bool valid() const { return file_path_.empty() != true; }
@ -47,6 +48,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 +67,9 @@ private:
bool shuffle_;
};
std::shared_ptr<music_track> get_track(unsigned int i);
void set_track(unsigned int i, const std::shared_ptr<music_track>& to);
} /* end namespace sound */
inline bool operator==(const sound::music_track& a, const sound::music_track& b) {