commit b19ed8d29075ab55ec00b24e8e622f69b1b22939
Author: JASheppard <J_Sheppard@live.co.uk>
Date: Thu Mar 13 20:29:44 2014 +0000
New function: playSoundsAtEntityInRandomSequence()
This function is required to play the lava hole's sounds because its sounds are meant to be played in a random sequence and are therefore not associated with animation frames, so the animation code can't play these sounds.
Change list
-----------
1. app.lua:
a) SAVEGAME_VERSION = 87: Required in this commit because a new afterLoad() sound playing function has been added.
b) +afterLoad() call to world:playLoadedEntitySounds()
2. audio.lua:
a) +self.entities_waiting_for_sound_to_be_enabled{}: Required to support the resumption of sounds being played by this commit's new audio playing function.
b) +playSoundsAtEntityInRandomSequence()
c) +playSoundAtEntityInRandomSequenceRecursionHandler()
d) +getRandomSilenceLengths(): Required to provide different durations for the sound seperating pauses in this commit's new audio playing function.
e) Modified playSound(): Moved its sound caching statements into a new function to prevent them from having to be duplicated in this commit's new audio playing function.
f) +canSoundsBePlayed(): Required to support the resumption of sounds being played by this commit's new audio playing function.
g) +onEndPause(paused): Required to support the resumption of sounds being played by this commit's new audio playing function.
h) +tellInterestedEntitiesTheyCanNowPlaySounds(): Required to support the resumption of sounds being played by this commit's new audio playing function.
i) +calls to tellInterestedEntitiesTheyCanNowPlaySounds()
j) +entityNoLongerWaitingForSoundsToBeTurnedOn(entity): Required to prevent the resumption of sounds being played for an entity which no longer exists or no longer wants its sounds to be resumed.
3. entity.lua:
a) +Variables for this commit's new audio playing function.
b) +playSoundsAtEntityInRandomSequence()
c) +playAfterLoadSound(): Required to support the resumption of sounds being played by this commit's new audio playing function.
d) +setWaitingForSoundEffectsToBeTurnedOn(state): Required to call audio:entityNoLongerWaitingForSoundsToBeTurnedOn()
4. game_ui.lua:
Modified togglePlaySounds() so that the keyboard shortcut will have the same response as clicking the menu option allowing it to disable and enable sounds being played by this commit's new audio playing function.
5. world.lua:
a) +Call to audio:onEndPause(): Required to support the resumption of sounds being played by this commit's new audio playing function.
b) +isPaused(): Required to support the resumption of sounds being played by this commit's new audio playing function.
c) Modified afterLoad() to make it support the resumption of sounds being played by this commit's new audio playing function.
d) +playLoadedEntitySounds(): Required to support the resumption of sounds being played by this commit's new audio playing function.
diff --git a/CorsixTH/Lua/app.lua b/CorsixTH/Lua/app.lua
index e806dd7..e94fcb5 100644
--- a/CorsixTH/Lua/app.lua
+++ b/CorsixTH/Lua/app.lua
@@ -29,7 +29,7 @@ local assert, io, type, dofile, loadfile, pcall, tonumber, print, setmetatable
-- Increment each time a savegame break would occur
-- and add compatibility code in afterLoad functions
-local SAVEGAME_VERSION = 86
+local SAVEGAME_VERSION = 87
class "App"
@@ -1248,6 +1248,7 @@ function App:afterLoad()
if new == old then
self.world:gameLog("Savegame version is " .. new .. " (" .. self:getVersion()
.. "), originally it was " .. first .. " (" .. self:getVersion(first) .. ")")
+ self.world:playLoadedEntitySounds()
return
elseif new > old then
self.world:gameLog("Savegame version changed from " .. old .. " (" .. self:getVersion(old) ..
diff --git a/CorsixTH/Lua/audio.lua b/CorsixTH/Lua/audio.lua
index b28c4b1..4aa6dd8 100644
--- a/CorsixTH/Lua/audio.lua
+++ b/CorsixTH/Lua/audio.lua
@@ -1,4 +1,4 @@
- --[[ Copyright (c) 2009 Peter "Corsix" Cawley
+--[[ Copyright (c) 2009 Peter "Corsix" Cawley
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@@ -36,11 +36,13 @@ function Audio:Audio(app)
self.not_loaded = not app.config.audio
self.unused_played_callback_id = 0
self.played_sound_callbacks = {}
+ self.entities_waiting_for_sound_to_be_enabled = {}
end
function Audio:clearCallbacks()
self.unused_played_callback_id = 0
self.played_sound_callbacks = {}
+ self.entities_waiting_for_sound_to_be_enabled = {}
end
local function GetFileData(path)
@@ -264,18 +266,7 @@ function Audio:playSound(name, where, is_announcement, played_callback, played_c
if sound_fx then
if name:find("*") then
-- Resolve wildcard to one particular sound
- local list = wilcard_cache[name]
- if not list then
- list = {}
- wilcard_cache[name] = list
- local pattern = ("^" .. name:gsub("%*",".*") .. "$"):upper()
- for i = 1, #self.sound_archive - 1 do
- local filename = self.sound_archive:getFilename(i):upper()
- if filename:find(pattern) then
- list[#list + 1] = filename
- end
- end
- end
+ local list = self:cacheSoundFilenamesAssociatedWithName(name)
name = list[1] and list[math.random(1, #list)] or name
end
local _, warning
@@ -303,6 +294,138 @@ function Audio:playSound(name, where, is_announcement, played_callback, played_c
end
end
+function Audio:cacheSoundFilenamesAssociatedWithName(name)
+ local list = wilcard_cache[name]
+ if not list then
+ local filename
+ list = {}
+ wilcard_cache[name] = list
+ local pattern = ("^" .. name:gsub("%*",".*") .. "$"):upper()
+ for i = 1, #self.sound_archive - 1 do
+ filename = self.sound_archive:getFilename(i):upper()
+ if filename:find(pattern) then
+ list[#list + 1] = filename
+ end
+ end
+ end
+ return list
+end
+
+--[[
+Plays related sounds at an entity in a random sequence, with random length silences between the sounds.
+
+This function's integer array parameters for the min and max silence lengths should provide lengths
+for this game's different speeds, indexed as follows:
+[1] Slowest [2] Slow [3] Normal [4] Fast [5] Maximum Speed
+
+!param names (string) A name pattern for the sequence of related sounds to be played for example: LAVA00*.wav
+!param entity : Where the sounds will be played at, the player won't hear the sounds being played at the entity
+when it isn't in their view.
+!param min_silence_lengths (integer array) The desired minimum silence lengths for this game's different speeds.
+!param max_silence_lengths (integer array) The desired maximum silence lengths for this game's different speeds.
+!param num_silences (integer) How many different silence lengths should be used, this can be a nil parameter.
+--]]
+function Audio:playSoundsAtEntityInRandomSequence(names, entity, min_silence_lengths, max_silence_lengths, num_silences)
+ if self.sound_fx then
+ self:cacheSoundFilenamesAssociatedWithName(names)
+ self:playSoundsAtEntityInRandomSequenceRecursionHandler(wilcard_cache[names],
+ entity,
+ self:getRandomSilenceLengths(min_silence_lengths,
+ max_silence_lengths,
+ num_silences),
+ 1)
+ end
+end
+
+--[[
+Called by the above function.
+
+This function's integer array parameters for the min and max silence lengths should provide lengths
+for this game's different speeds, indexed as follows:
+[1] Slowest [2] Slow [3] Normal [4] Fast [5] Maximum Speed
+
+!param sounds (string) A name pattern for the sequence of related sounds to be played for example: LAVA00*.wav
+!param entity : Where the sounds will be played at, the player won't hear the sounds being played at the entity
+when it isn't in their view.
+!param silences (integer array) the different pause durations to be used between the played sounds.
+!param silences_pointer (integer) the index for the pause duration which should be used after this call's sound has been played.
+--]]
+function Audio:playSoundsAtEntityInRandomSequenceRecursionHandler(sounds, entity, silences, silences_pointer)
+ if entity.playing_sounds_in_random_sequence then
+ local sound_played_callback = function()
+ self:playSoundsAtEntityInRandomSequenceRecursionHandler(sounds,
+ entity,
+ silences,
+ silences_pointer)
+ end
+
+ if self:canSoundsBePlayed() then
+ local _, warning
+ local x, y = Map:WorldToScreen(entity.tile_x, entity.tile_y)
+ local dx, dy = entity.th:getPosition()
+ x = x + dx - self.app.ui.screen_offset_x
+ y = y + dy - self.app.ui.screen_offset_y
+
+ self.played_sound_callbacks[tostring(self.unused_played_callback_id)] = sound_played_callback
+ _, warning = self.sound_fx:play(sounds[math.random(1,#sounds)],
+ self.app.config.sound_volume,
+ x,
+ y,
+ self.unused_played_callback_id,
+ silences_pointer)
+
+ self.unused_played_callback_id = self.unused_played_callback_id + 1
+ if #silences > 1 then
+ silences_pointer = (silences_pointer % #silences) + 1
+ end
+ --If the sound can't be played now:
+ else
+ self.entities_waiting_for_sound_to_be_enabled[entity] = sound_played_callback
+ entity:setWaitingForSoundEffectsToBeTurnedOn(true)
+ end
+ else
+ if self.entities_waiting_for_sound_to_be_enabled[entity] then
+ self.entities_waiting_for_sound_to_be_enabled[entity] = nil
+ end
+ end
+end
+
+function Audio:canSoundsBePlayed()
+ return TheApp.config.play_sounds and not TheApp.world:isPaused()
+end
+
+--[[
+This function's integer array parameters for the min and max silence lengths should provide lengths
+for this game's different speeds, indexed as follows:
+[1] Slowest [2] Slow [3] Normal [4] Fast [5] Maximum
+
+!param min_silence_lengths (integer array) The desired minimum silence lengths for this game's different speeds.
+!param max_silence_lengths (integer array) The desired maximum silence lengths for this game's different speeds.
+!param num_silences (integer) How many silence lengths should be in the returned table of generated lengths.
+!return (table) A table of randomly ordered integers for the generated silence lengths.
+--]]
+function Audio:getRandomSilenceLengths(min_silence_lengths, max_silence_lengths, num_silences)
+ local min_silence = min_silence_lengths[TheApp.world.tick_rate]
+ local max_silence = max_silence_lengths[TheApp.world.tick_rate]
+
+ local silences = {}
+ if min_silence == max_silence then
+ silences[1] = min_silence
+ else
+ for i = 1, num_silences do
+ silences[i] = math.random(min_silence,max_silence)
+ end
+ end
+
+ return silences
+end
+
+function Audio:onEndPause()
+ if TheApp.config.play_sounds then
+ self:tellInterestedEntitiesTheyCanNowPlaySounds()
+ end
+end
+
function Audio:onSoundPlayed(played_callbacks_id)
if TheApp.world ~= nil then
if self.played_sound_callbacks[tostring(played_callbacks_id)] then
@@ -525,6 +648,23 @@ function Audio:playSoundEffects(play_effects)
-- As above.
self.sound_fx:setSoundEffectsOn(play_effects)
end
+
+ if self:canSoundsBePlayed() then
+ self:tellInterestedEntitiesTheyCanNowPlaySounds()
+ end
+end
+
+function Audio:tellInterestedEntitiesTheyCanNowPlaySounds()
+ if table_length(self.entities_waiting_for_sound_to_be_enabled) > 0 then
+ for entity,callback in pairs(self.entities_waiting_for_sound_to_be_enabled) do
+ callback()
+ self.entities_waiting_for_sound_to_be_enabled[entity] = nil
+ end
+ end
+end
+
+function Audio:entityNoLongerWaitingForSoundsToBeTurnedOn(entity)
+ self.entities_waiting_for_sound_to_be_enabled[entity] = nil
end
function Audio:setAnnouncementVolume(volume)
diff --git a/CorsixTH/Lua/entity.lua b/CorsixTH/Lua/entity.lua
index fa08ecd..fdbc5c0 100644
--- a/CorsixTH/Lua/entity.lua
+++ b/CorsixTH/Lua/entity.lua
@@ -28,22 +28,57 @@ function Entity:Entity(animation)
self.layers = {}
animation:setHitTestResult(self)
self.ticks = true
+ self.playing_sounds_in_random_sequence = false
+ self.waiting_for_sound_effects_to_be_turned_on = false
+ self.random_sound_sequence_parameters = nil
self.dynamic_info = nil;
end
--- This plays a sound "at" the entity, meaning the sound will not be played
--- if the entity is off-screen, and the volume will be quieter the further
--- the entity is from the center of the screen. If this is not what you want
--- then use UI:playSound instead.
--- !param name (string, integer) The filename or ordinal of the sound to play.
--- !param played_callback (function) a optional parameter.
--- !param played_callback_delay (integer) a optional milliseconds parameter.
+--[[
+This plays a sound "at" the entity, meaning the sound will not be played
+if the entity is off-screen, and the volume will be quieter the further
+the entity is from the center of the screen. If this is not what you want
+then use UI:playSound instead.
+!param name (string, integer) The filename or ordinal of the sound to play.
+!param played_callback (function) a optional parameter.
+!param played_callback_delay (integer) a optional milliseconds parameter.
+--]]
function Entity:playSound(name, played_callback, played_callback_delay)
if TheApp.config.play_sounds then
TheApp.audio:playSound(name, self, false, played_callback, played_callback_delay)
end
end
+function Entity:setWaitingForSoundEffectsToBeTurnedOn(state)
+ self.waiting_for_sound_effects_to_be_turned_on = true
+end
+
+--[[
+Plays a sequence of related sounds at an entity while entity.playing_sounds_in_random_sequence = true.
+
+The silences between these sounds can either have randomly generated lengths between the min and max length parameters or
+they can all be a specified length by providing min and max tables with one value for the desired pause duration.
+
+!param name_pattern (String) example: LAVA00*.WAV
+!param min_silence_lengths (table) the desired mininum silences length for the different tick rates, [3] = Normal
+!param max_silence_lengths (table) the desired maximum silences length for the different tick rates, [3] = Normal
+!param num_silences (integer) how many different silence lengths should be used, this can be a nil parameter.
+-]]
+function Entity:playSoundsAtEntityInRandomSequence(name_pattern, min_silence_lengths, max_silence_lengths, num_silences)
+ self.playing_sounds_in_random_sequence = true
+ self.random_sound_sequence_parameters = {}
+ self.random_sound_sequence_parameters["namePattern"] = name_pattern
+ self.random_sound_sequence_parameters["minSilence"] = min_silence_lengths
+ self.random_sound_sequence_parameters["maxSilence"] = max_silence_lengths
+ self.random_sound_sequence_parameters["numSilences"] = num_silences
+
+ TheApp.audio:playSoundsAtEntityInRandomSequence(name_pattern,
+ self,
+ min_silence_lengths,
+ max_silence_lengths,
+ num_silences)
+end
+
--[[ Set which animation is used to give the entity a visual appearance.
! Until an entity is given an animation, it is invisible to the player. Note
that some "animations" consist of a single frame, and hence the term animation
@@ -234,6 +269,9 @@ function Entity:onDestroy()
getmetatable(self.gc_dummy).__gc = function()
print("Entity " .. tostring(self) .. " has been garbage collected.")
end --]]
+ if self.waiting_for_sound_effects_to_be_turned_on then
+ TheApp.audio:entityNoLongerWaitingForSoundsToBeTurnedOn(self)
+ end
end
-- Function which is called at the end of each ingame day. Should be used to
@@ -275,6 +313,16 @@ end
function Entity:afterLoad(old, new)
end
+function Entity:playAfterLoadSound()
+ if self.random_sound_sequence_parameters then
+ self.playing_sounds_in_random_sequence = true
+ self:playSoundsAtEntityInRandomSequence(self.random_sound_sequence_parameters["namePattern"],
+ self.random_sound_sequence_parameters["minSilence"],
+ self.random_sound_sequence_parameters["maxSilence"],
+ self.random_sound_sequence_parameters["numSilences"])
+ end
+end
+
function Entity:resetAnimation()
self.th:setDrawingLayer(self:getDrawingLayer())
local x, y = self.tile_x, self.tile_y
diff --git a/CorsixTH/Lua/game_ui.lua b/CorsixTH/Lua/game_ui.lua
index 6bb7a55..69a4027 100644
--- a/CorsixTH/Lua/game_ui.lua
+++ b/CorsixTH/Lua/game_ui.lua
@@ -725,7 +725,7 @@ end
function UI:togglePlaySounds()
self.app.config.play_sounds = not self.app.config.play_sounds
-
+ self.app.audio:playSoundEffects(self.app.config.play_sounds)
self.app:saveConfig()
end
diff --git a/CorsixTH/Lua/world.lua b/CorsixTH/Lua/world.lua
index e17e398..78499fd 100644
--- a/CorsixTH/Lua/world.lua
+++ b/CorsixTH/Lua/world.lua
@@ -949,6 +949,7 @@ function World:setSpeed(speed)
if self:isCurrentSpeed(speed) then
return
end
+ local pause_state_changed = nil
if speed == "Pause" then
-- stop screen shaking if there was an earthquake in progress
if self.active_earthquake then
@@ -956,20 +957,32 @@ function World:setSpeed(speed)
end
-- By default actions are not allowed when the game is paused.
self.user_actions_allowed = TheApp.config.allow_user_actions_while_paused
+ pause_state_changed = true
elseif self:getCurrentSpeed() == "Pause" then
self.user_actions_allowed = true
end
-
+
local currentSpeed = self:getCurrentSpeed()
if currentSpeed ~= "Pause" and currentSpeed ~= "Speed Up" then
self.prev_speed = self:getCurrentSpeed()
end
+ local was_paused = currentSpeed == "Pause"
local numerator, denominator = unpack(tick_rates[speed])
self.hours_per_tick = numerator
self.tick_rate = denominator
+
+ if was_paused then
+ TheApp.audio:onEndPause()
+ end
+
-- Set the blue filter according to whether the user can build or not.
TheApp.video:setBlueFilterActive(not self.user_actions_allowed)
+ return false
+end
+
+function World:isPaused()
+ return self:isCurrentSpeed("Pause")
end
--! Dedicated function to allow unpausing by pressing 'p' again
@@ -2358,10 +2371,18 @@ function World:afterLoad(old, new)
if old < 80 then
self:determineWinningConditions()
end
-
+ if old >= 87 then
+ self:playLoadedEntitySounds()
+ end
self.savegame_version = new
end
+function World:playLoadedEntitySounds()
+ for _, entity in pairs(self.entities) do
+ entity:playAfterLoadSound()
+ end
+end
+
--[[ There is a problem with room editing in that it resets all the partial passable flags
(travelNorth, travelSouth etc.) in the corridor, a workaround is calling this function
after the room was edited so that all edge only objects, that set partial passable flags set