Browse Source

Implement configurable object sounds: ambient, visit and removal

* If there more than one sound for visit or removal random is played
* At moment only the first ambient sound will be used
Arseniy Shestakov 8 years ago
parent
commit
f15cadc87b

+ 5 - 0
ChangeLog

@@ -26,6 +26,11 @@ SPELLS:
 MODS:
 * Improve support for WoG commander artifacts and skill descriptions
 * Added basic support for secondary skill modding
+* Map object sounds can now be configured via json
+
+SOUND:
+* Fixed many mising or wrong pickup and visit sounds for map objects
+* All map objects now have ambient sounds identical to OH3
 
 0.98 -> 0.99
 

+ 4 - 0
client/CMT.cpp

@@ -1280,6 +1280,10 @@ static void handleEvent(SDL_Event & ev)
 		case FULLSCREEN_TOGGLED:
 			fullScreenChanged();
 			break;
+		case INTERFACE_CHANGED:
+			if(LOCPLINT)
+				LOCPLINT->updateAmbientSounds();
+			break;
 		default:
 			logGlobal->error("Unknown user event. Code %d", ev.user.code);
 			break;

+ 75 - 2
client/CMusicHandler.cpp

@@ -82,8 +82,10 @@ void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
 }
 
 CSoundHandler::CSoundHandler():
-	listener(settings.listen["general"]["sound"])
+	listener(settings.listen["general"]["sound"]),
+	ambientConfig(JsonNode(ResourceID("config/ambientSounds.json")))
 {
+	allTilesSource = ambientConfig["allTilesSource"].Bool();
 	listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
 
 	// Vectors for helper(s)
@@ -112,6 +114,8 @@ CSoundHandler::CSoundHandler():
 void CSoundHandler::init()
 {
 	CAudioBase::init();
+	if(ambientConfig["allocateChannels"].isNumber())
+		Mix_AllocateChannels(ambientConfig["allocateChannels"].Integer());
 
 	if (initialized)
 	{
@@ -160,6 +164,21 @@ Mix_Chunk *CSoundHandler::GetSoundChunk(std::string &sound, bool cache)
 	}
 }
 
+int CSoundHandler::ambientDistToVolume(int distance) const
+{
+	if(distance >= ambientConfig["distances"].Vector().size())
+		return 0;
+
+	int volume = ambientConfig["distances"].Vector()[distance].Integer();
+	return volume * ambientConfig["volume"].Integer() * getVolume() / 10000;
+}
+
+void CSoundHandler::ambientStopSound(std::string soundId)
+{
+	stopSound(ambientChannels[soundId]);
+	setChannelVolume(ambientChannels[soundId], 100);
+}
+
 // Plays a sound, and return its channel so we can fade it out later
 int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
 {
@@ -216,7 +235,13 @@ void CSoundHandler::setVolume(ui32 percent)
 	CAudioBase::setVolume(percent);
 
 	if (initialized)
-		Mix_Volume(-1, (MIX_MAX_VOLUME * volume)/100);
+		setChannelVolume(-1, volume);
+}
+
+// Sets the sound volume, from 0 (mute) to 100
+void CSoundHandler::setChannelVolume(int channel, ui32 percent)
+{
+	Mix_Volume(channel, (MIX_MAX_VOLUME * percent)/100);
 }
 
 void CSoundHandler::setCallback(int channel, std::function<void()> function)
@@ -244,6 +269,54 @@ void CSoundHandler::soundFinishedCallback(int channel)
 	callbacks.erase(iter);
 }
 
+int CSoundHandler::ambientGetRange() const
+{
+	return ambientConfig["range"].Integer();
+}
+
+bool CSoundHandler::ambientCheckVisitable() const
+{
+	return !allTilesSource;
+}
+
+void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> sounds)
+{
+	std::vector<std::string> stoppedSounds;
+	for(auto & pair : ambientChannels)
+	{
+		if(!vstd::contains(sounds, pair.first))
+		{
+			ambientStopSound(pair.first);
+			stoppedSounds.push_back(pair.first);
+		}
+		else
+		{
+			CCS->soundh->setChannelVolume(pair.second, ambientDistToVolume(sounds[pair.first]));
+		}
+	}
+	for(auto soundId : stoppedSounds)
+		ambientChannels.erase(soundId);
+
+	for(auto & pair : sounds)
+	{
+		if(!vstd::contains(ambientChannels, pair.first))
+		{
+			int channel = CCS->soundh->playSound(pair.first, -1);
+			CCS->soundh->setChannelVolume(channel, ambientDistToVolume(pair.second));
+			CCS->soundh->ambientChannels.insert(std::make_pair(pair.first, channel));
+		}
+	}
+}
+
+void CSoundHandler::ambientStopAllChannels()
+{
+	for(auto ch : ambientChannels)
+	{
+		ambientStopSound(ch.first);
+	}
+	ambientChannels.clear();
+}
+
 void CMusicHandler::onVolumeChange(const JsonNode &volumeNode)
 {
 	setVolume(volumeNode.Float());

+ 14 - 1
client/CMusicHandler.h

@@ -29,7 +29,7 @@ public:
 	virtual void release() = 0;
 
 	virtual void setVolume(ui32 percent);
-	ui32 getVolume() { return volume; };
+	ui32 getVolume() const { return volume; };
 };
 
 class CSoundHandler: public CAudioBase
@@ -49,6 +49,13 @@ private:
 	//std::function will be nullptr if callback was not set
 	std::map<int, std::function<void()> > callbacks;
 
+	int ambientDistToVolume(int distance) const;
+	void ambientStopSound(std::string soundId);
+
+	const JsonNode ambientConfig;
+	bool allTilesSource;
+	std::map<std::string, int> ambientChannels;
+
 public:
 	CSoundHandler();
 
@@ -56,6 +63,7 @@ public:
 	void release() override;
 
 	void setVolume(ui32 percent) override;
+	void setChannelVolume(int channel, ui32 percent);
 
 	// Sounds
 	int playSound(soundBase::soundID soundID, int repeats=0);
@@ -66,6 +74,11 @@ public:
 	void setCallback(int channel, std::function<void()> function);
 	void soundFinishedCallback(int channel);
 
+	int ambientGetRange() const;
+	bool ambientCheckVisitable() const;
+	void ambientUpdateChannels(std::map<std::string, int> currentSounds);
+	void ambientStopAllChannels();
+
 	// Sets
 	std::vector<soundBase::soundID> pickupSounds;
 	std::vector<soundBase::soundID> horseSounds;

+ 72 - 14
client/CPlayerInterface.cpp

@@ -136,6 +136,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 
 CPlayerInterface::~CPlayerInterface()
 {
+	CCS->soundh->ambientStopAllChannels();
 	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
 	//howManyPeople--;
 	delete showingDialog;
@@ -266,6 +267,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 
 	if (makingTurn  &&  hero->tempOwner == playerID) //we are moving our hero - we may need to update assigned path
 	{
+		updateAmbientSounds();
 		//We may need to change music - select new track, music handler will change it if needed
 		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType, true);
 
@@ -446,6 +448,15 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 		adventureInt->selection = nullptr;
 }
 
+void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+	if(start && visitedObj->getVisitSound())
+	{
+		CCS->soundh->playSound(visitedObj->getVisitSound().get());
+	}
+}
+
 void CPlayerInterface::heroCreated(const CGHeroInstance * hero)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -456,6 +467,7 @@ void CPlayerInterface::openTownWindow(const CGTownInstance * town)
 {
 	if (castleInt)
 		castleInt->close();
+
 	castleInt = new CCastleInterface(town);
 	GH.pushInt(castleInt);
 }
@@ -1644,22 +1656,17 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 	CCS->curh->show();
 }
 
-void CPlayerInterface::objectRemoved( const CGObjectInstance *obj )
+void CPlayerInterface::objectRemoved(const CGObjectInstance * obj)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (LOCPLINT->cb->getCurrentPlayer() == playerID) {
-		std::string handlerName = VLC->objtypeh->getObjectHandlerName(obj->ID);
-		if ((handlerName == "pickable") || (handlerName == "scholar") || (handlerName== "artifact") || (handlerName == "pandora")) {
-			waitWhileDialog();
-			CCS->soundh->playSoundFromSet(CCS->soundh->pickupSounds);
-		} else if ((handlerName == "monster") || (handlerName == "hero")) {
-			waitWhileDialog();
-			CCS->soundh->playSound(soundBase::KillFade);
-		}
+	if(LOCPLINT->cb->getCurrentPlayer() == playerID && obj->getRemovalSound())
+	{
+		waitWhileDialog();
+		CCS->soundh->playSound(obj->getRemovalSound().get());
 	}
-	if (obj->ID == Obj::HERO  &&  obj->tempOwner == playerID)
+	if(obj->ID == Obj::HERO && obj->tempOwner == playerID)
 	{
-		const CGHeroInstance *h = static_cast<const CGHeroInstance*>(obj);
+		const CGHeroInstance * h = static_cast<const CGHeroInstance *>(obj);
 		heroKilled(h);
 	}
 }
@@ -1677,6 +1684,7 @@ const CArmedInstance * CPlayerInterface::getSelection()
 void CPlayerInterface::setSelection(const CArmedInstance * obj)
 {
 	currentSelection = obj;
+	updateAmbientSounds(true);
 }
 
 void CPlayerInterface::update()
@@ -2373,9 +2381,9 @@ void CPlayerInterface::acceptTurn()
 	adventureInt->updateNextHero(nullptr);
 	adventureInt->showAll(screen);
 
-	if (settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed())
+	if(settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed())
 	{
-		if (CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt()))
+		if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt()))
 			iw->close();
 
 		adventureInt->fendTurn();
@@ -2537,6 +2545,7 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
 void CPlayerInterface::requestReturningToMainMenu()
 {
 	sendCustomEvent(RETURN_TO_MAIN_MENU);
+	CCS->soundh->ambientStopAllChannels();
 	cb->unregisterAllInterfaces();
 }
 
@@ -2903,3 +2912,52 @@ void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectP
 
 	viewWorldMap();
 }
+
+void CPlayerInterface::updateAmbientSounds(bool resetAll)
+{
+	if(castleInt || battleInt || !makingTurn || !currentSelection)
+	{
+		CCS->soundh->ambientStopAllChannels();
+		return;
+	}
+	else if(!dynamic_cast<CAdvMapInt *>(GH.topInt()))
+	{
+		return;
+	}
+	if(resetAll)
+		CCS->soundh->ambientStopAllChannels();
+
+	std::map<std::string, int> currentSounds;
+	auto updateSounds = [&](std::string soundId, int distance) -> void
+	{
+		if(vstd::contains(currentSounds, soundId))
+			currentSounds[soundId] = std::max(currentSounds[soundId], distance);
+		else
+			currentSounds.insert(std::make_pair(soundId, distance));
+	};
+
+	int3 pos = currentSelection->getSightCenter();
+	std::unordered_set<int3, ShashInt3> tiles;
+	cb->getVisibleTilesInRange(tiles, pos, CCS->soundh->ambientGetRange(), int3::DIST_CHEBYSHEV);
+	for(int3 tile : tiles)
+	{
+		int dist = pos.dist(tile, int3::DIST_CHEBYSHEV);
+		// We want sound for every special terrain on tile and not just one on top
+		for(auto & ttObj : CGI->mh->ttiles[tile.x][tile.y][tile.z].objects)
+		{
+			auto obj = ttObj.obj;
+			if(!obj || !obj->getAmbientSound())
+				continue;
+
+			// All tiles of static objects are sound sources. E.g Volcanos and special terrains
+			// For visitable object only their visitable tile is sound source
+			if(CCS->soundh->ambientCheckVisitable() && obj->isVisitable() && !obj->visitableAt(tile.x, tile.y))
+				continue;
+
+			updateSounds(obj->getAmbientSound().get(), dist);
+		}
+		if(CGI->mh->map->isCoastalTile(tile))
+			updateSounds("LOOPOCEA", dist);
+	}
+	CCS->soundh->ambientUpdateChannels(currentSounds);
+}

+ 6 - 1
client/CPlayerInterface.h

@@ -75,7 +75,8 @@ enum
 	RETURN_TO_MENU_LOAD,
 	FULLSCREEN_TOGGLED,
 	PREPARE_RESTART_CAMPAIGN,
-	FORCE_QUIT //quit client without question
+	FORCE_QUIT, //quit client without question
+	INTERFACE_CHANGED
 };
 
 /// Central class for managing user interface logic
@@ -151,6 +152,7 @@ public:
 	void artifactAssembled(const ArtifactLocation &al) override;
 	void artifactDisassembled(const ArtifactLocation &al) override;
 
+	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
 	void heroCreated(const CGHeroInstance* hero) override;
 	void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
 	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
@@ -263,6 +265,9 @@ public:
 	void sendCustomEvent(int code);
 	void proposeLoadingGame();
 
+	// Ambient sounds
+	void updateAmbientSounds(bool resetAll = false);
+
 	///returns true if all events are processed internally
 	bool capturedAllEvents();
 

+ 6 - 0
client/gui/CGuiHandler.cpp

@@ -105,6 +105,8 @@ void CGuiHandler::popInt(IShowActivatable *top)
 	if(!listInt.empty())
 		listInt.front()->activate();
 	totalRedraw();
+
+	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
 }
 
 void CGuiHandler::popIntTotally(IShowActivatable *top)
@@ -129,6 +131,8 @@ void CGuiHandler::pushInt(IShowActivatable *newInt)
 	newInt->activate();
 	objsToBlit.push_back(newInt);
 	totalRedraw();
+
+	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
 }
 
 void CGuiHandler::popInts(int howMany)
@@ -150,6 +154,8 @@ void CGuiHandler::popInts(int howMany)
 		totalRedraw();
 	}
 	fakeMouseMove();
+
+	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
 }
 
 IShowActivatable * CGuiHandler::topInt()

+ 1 - 0
client/windows/CAdvmapInterface.cpp

@@ -1512,6 +1512,7 @@ void CAdvMapInt::endingTurn()
 		LOCPLINT->cingconsole->deactivate();
 	LOCPLINT->makingTurn = false;
 	LOCPLINT->cb->endTurn();
+	CCS->soundh->ambientStopAllChannels();
 }
 
 const CGObjectInstance* CAdvMapInt::getActiveObject(const int3 &mapPos)

+ 14 - 0
config/ambientSounds.json

@@ -0,0 +1,14 @@
+{
+  // By default visitable objects only have ambient sound source on visitable tile
+  // Static objects and special terrains that have ambient sound have source on all tiles
+  // If set to true then all tiles will become source for visitable objects
+  "allTilesSource": false,
+  // By default SDL2_Mixer allocate 8 channels, but more sounds require more channels
+  "allocateChannels" : 16,
+  // Maximal ambient sounds volume must be about 20% of global effects volume
+  "volume" : 20,
+  // Maximal distance to objects using chebyshev distance
+  "range" : 3,
+  // Volume depend on distance, e.g 100% on same tile, 90% one tile away, etc
+  "distances": [100, 90, 60, 30]
+}

+ 5 - 0
lib/CGameInfoCallback.cpp

@@ -908,6 +908,11 @@ bool CGameInfoCallback::isInTheMap(const int3 &pos) const
 	return gs->map->isInTheMap(pos);
 }
 
+void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula) const
+{
+	gs->getTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula);
+}
+
 const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
 {
 	return gs->map->artInstances[aid.num];

+ 2 - 0
lib/CGameInfoCallback.h

@@ -29,6 +29,7 @@ class CMapHeader;
 struct TeamState;
 struct QuestInfo;
 class int3;
+struct ShashInt3;
 
 
 class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
@@ -92,6 +93,7 @@ public:
 	const TerrainTile * getTile(int3 tile, bool verbose = true) const;
 	std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
 	bool isInTheMap(const int3 &pos) const;
+	void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
 
 	//town
 	const CGTownInstance* getTown(ObjectInstanceID objid) const;

+ 15 - 0
lib/CSoundBase.h

@@ -1043,5 +1043,20 @@ public:
 		sound_after_last
 	};
 #undef VCMI_SOUND_NAME
+#define VCMI_SOUND_NAME(sequence) sounds.push_back("" #sequence "");
+
+	static std::vector<std::string> & stringsList()
+	{
+		static std::vector<std::string> sounds;
+		if(!sounds.size())
+		{
+			sounds.push_back("invalid");
+			sounds.push_back("sound_todo");
+			VCMI_SOUND_LIST
+			sounds.push_back("sound_after_last");
+		}
+		return sounds;
+	}
 #undef VCMI_SOUND_FILE
+#undef VCMI_SOUND_NAME
 };

+ 3 - 3
lib/mapObjects/CGHeroInstance.cpp

@@ -30,7 +30,7 @@
 #include "../StringConstants.h"
 
 ///helpers
-static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID = 0)
 {
 	InfoWindow iw;
 	iw.soundID = soundID;
@@ -39,7 +39,7 @@ static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const u
 	IObjectInterface::cb->sendAndApply(&iw);
 }
 
-static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID = 0)
 {
 	const PlayerColor playerID = h->getOwner();
 	showInfoDialog(playerID,txtID,soundID);
@@ -443,7 +443,7 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
 			txt_id = 103;
 		}
 
-		showInfoDialog(h,txt_id,soundBase::ROGUE);
+		showInfoDialog(h,txt_id);
 	}
 }
 

+ 0 - 1
lib/mapObjects/CGPandoraBox.cpp

@@ -53,7 +53,6 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const
 {
 		BlockingDialog bd (true, false);
 		bd.player = h->getOwner();
-		bd.soundID = soundBase::QUEST;
 		bd.text.addTxt (MetaString::ADVOB_TXT, 14);
 		cb->showBlockingDialog (&bd);
 }

+ 32 - 0
lib/mapObjects/CObjectClassesHandler.cpp

@@ -18,6 +18,7 @@
 #include "../CGeneralTextHandler.h"
 #include "../CModHandler.h"
 #include "../JsonNode.h"
+#include "../CSoundBase.h"
 
 #include "CRewardableConstructor.h"
 #include "CommonConstructors.h"
@@ -201,6 +202,7 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co
 	{
 		loadObjectEntry(entry.first, entry.second, obj);
 	}
+
 	return obj;
 }
 
@@ -348,6 +350,22 @@ std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const
 	return getObjectName(type);
 }
 
+SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type) const
+{
+	if(objects.count(type))
+		return objects.at(type)->sounds;
+	logGlobal->error("Access to non existing object of type %d", type);
+	return SObjectSounds();
+}
+
+SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) const
+{
+	if(knownSubObjects(type).count(subtype))
+		return getHandlerFor(type, subtype)->getSounds();
+	else
+		return getObjectSounds(type);
+}
+
 std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const
 {
 	return objects.at(type)->handlerName;
@@ -414,6 +432,15 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
 	else
 		objectName.reset(input["name"].String());
 
+	for(const JsonNode & node : input["sounds"]["ambient"].Vector())
+		sounds.ambient.push_back(node.String());
+
+	for(const JsonNode & node : input["sounds"]["visit"].Vector())
+		sounds.visit.push_back(node.String());
+
+	for(const JsonNode & node : input["sounds"]["removal"].Vector())
+		sounds.removal.push_back(node.String());
+
 	initTypeData(input);
 }
 
@@ -440,6 +467,11 @@ boost::optional<std::string> AObjectTypeHandler::getCustomName() const
 	return objectName;
 }
 
+SObjectSounds AObjectTypeHandler::getSounds() const
+{
+	return sounds;
+}
+
 void AObjectTypeHandler::addTemplate(const ObjectTemplate & templ)
 {
 	templates.push_back(templ);

+ 32 - 1
lib/mapObjects/CObjectClassesHandler.h

@@ -1,4 +1,4 @@
-/*
+/*
  * CObjectClassesHandler.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
@@ -19,6 +19,21 @@
 class JsonNode;
 class CRandomGenerator;
 
+
+struct SObjectSounds
+{
+	std::vector<std::string> ambient;
+	std::vector<std::string> visit;
+	std::vector<std::string> removal;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & ambient;
+		h & visit;
+		h & removal;
+	}
+};
+
 /// Structure that describes placement rules for this object in random map
 struct DLL_LINKAGE RandomMapInfo
 {
@@ -108,6 +123,8 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
 	JsonNode base; /// describes base template
 
 	std::vector<ObjectTemplate> templates;
+
+	SObjectSounds sounds;
 protected:
 	void preInitObject(CGObjectInstance * obj) const;
 	virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
@@ -131,6 +148,7 @@ public:
 
 	/// Returns object-specific name, if set
 	boost::optional<std::string> getCustomName() const;
+	SObjectSounds getSounds() const;
 
 	void addTemplate(const ObjectTemplate & templ);
 	void addTemplate(JsonNode config);
@@ -172,6 +190,10 @@ public:
 			h & typeName;
 			h & subTypeName;
 		}
+		if(version >= 778)
+		{
+			h & sounds;
+		}
 	}
 };
 
@@ -192,6 +214,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 		std::map<si32, TObjectTypeHandler> subObjects;
 		std::map<std::string, si32> subIds;//full id from core scope -> subtype
 
+		SObjectSounds sounds;
+
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 			h & name;
@@ -203,6 +227,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 				h & identifier;
 				h & subIds;
 			}
+			if(version >= 778)
+			{
+				h & sounds;
+			}
 		}
 	};
 
@@ -250,6 +278,9 @@ public:
 	std::string getObjectName(si32 type) const;
 	std::string getObjectName(si32 type, si32 subtype) const;
 
+	SObjectSounds getObjectSounds(si32 type) const;
+	SObjectSounds getObjectSounds(si32 type, si32 subtype) const;
+
 	/// Returns handler string describing the handler (for use in client)
 	std::string getObjectHandlerName(si32 type) const;
 

+ 29 - 2
lib/mapObjects/CObjectHandler.cpp

@@ -53,7 +53,7 @@ static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const u
 	showInfoDialog(playerID,txtID,soundID);
 }*/
 
-static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID = 0)
 {
 	const PlayerColor playerID = h->getOwner();
 	showInfoDialog(playerID,txtID,soundID);
@@ -271,6 +271,33 @@ std::string CGObjectInstance::getObjectName() const
 	return VLC->objtypeh->getObjectName(ID, subID);
 }
 
+boost::optional<std::string> CGObjectInstance::getAmbientSound() const
+{
+	const auto & sounds = VLC->objtypeh->getObjectSounds(ID, subID).ambient;
+	if(sounds.size())
+		return sounds.front(); // TODO: Support randomization of ambient sounds
+
+	return boost::none;
+}
+
+boost::optional<std::string> CGObjectInstance::getVisitSound() const
+{
+	const auto & sounds = VLC->objtypeh->getObjectSounds(ID, subID).visit;
+	if(sounds.size())
+		return *RandomGeneratorUtil::nextItem(sounds, CRandomGenerator::getDefault());
+
+	return boost::none;
+}
+
+boost::optional<std::string> CGObjectInstance::getRemovalSound() const
+{
+	const auto & sounds = VLC->objtypeh->getObjectSounds(ID, subID).removal;
+	if(sounds.size())
+		return *RandomGeneratorUtil::nextItem(sounds, CRandomGenerator::getDefault());
+
+	return boost::none;
+}
+
 std::string CGObjectInstance::getHoverText(PlayerColor player) const
 {
 	return getObjectName();
@@ -293,7 +320,7 @@ void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const
 	case Obj::SANCTUARY:
 		{
 			//You enter the sanctuary and immediately feel as if a great weight has been lifted off your shoulders.  You feel safe here.
-			showInfoDialog(h,114,soundBase::GETPROTECTION);
+			showInfoDialog(h, 114);
 		}
 		break;
 	case Obj::TAVERN:

+ 5 - 0
lib/mapObjects/CObjectHandler.h

@@ -144,6 +144,11 @@ public:
 	std::set<int3> getBlockedOffsets() const; //returns set of relative positions blocked by this object
 	bool isVisitable() const; //returns true if object is visitable
 
+	boost::optional<std::string> getAmbientSound() const;
+	boost::optional<std::string> getVisitSound() const;
+	boost::optional<std::string> getRemovalSound() const;
+
+
 	/** VIRTUAL METHODS **/
 
 	/// Returns true if player can pass through visitable tiles of this object

+ 4 - 6
lib/mapObjects/CQuest.cpp

@@ -37,7 +37,7 @@ CQuest::CQuest()
 }
 
 ///helpers
-static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID = 0)
 {
 	InfoWindow iw;
 	iw.soundID = soundID;
@@ -46,7 +46,7 @@ static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const u
 	IObjectInterface::cb->sendAndApply(&iw);
 }
 
-static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID = 0)
 {
 	const PlayerColor playerID = h->getOwner();
 	showInfoDialog(playerID,txtID,soundID);
@@ -685,7 +685,6 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 		{
 			BlockingDialog bd (true, false);
 			bd.player = h->getOwner();
-			bd.soundID = soundBase::QUEST;
 
 			getCompletionText (bd.text, bd.components, isCustom, h);
 
@@ -1085,7 +1084,7 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
 	}
 	else
 		txt_id=20;
-	showInfoDialog(h,txt_id,soundBase::CAVEHEAD);
+	showInfoDialog(h, txt_id);
 }
 
 void CGBorderGuard::initObj(CRandomGenerator & rand)
@@ -1116,13 +1115,12 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const
 	{
 		BlockingDialog bd (true, false);
 		bd.player = h->getOwner();
-		bd.soundID = soundBase::QUEST;
 		bd.text.addTxt (MetaString::ADVOB_TXT, 17);
 		cb->showBlockingDialog (&bd);
 	}
 	else
 	{
-		showInfoDialog(h,18,soundBase::CAVEHEAD);
+		showInfoDialog(h, 18);
 
 		AddQuest aq;
 		aq.quest = QuestInfo (quest, this, visitablePos());

+ 11 - 41
lib/mapObjects/CRewardableObject.cpp

@@ -500,7 +500,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 	{
 	case Obj::CAMPFIRE:
 		{
-			soundID = soundBase::experience;
 			int givenRes = rand.nextInt(5);
 			int givenAmm = rand.nextInt(4, 6);
 
@@ -514,7 +513,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 	case Obj::FLOTSAM:
 		{
 			int type = rand.nextInt(3);
-			soundID = soundBase::GENIE;
 			switch(type)
 			{
 			case 0:
@@ -553,7 +551,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 		}
 	case Obj::SEA_CHEST:
 		{
-			soundID = soundBase::chest;
 			int hlp = rand.nextInt(99);
 			if(hlp < 20)
 			{
@@ -581,7 +578,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 		break;
 	case Obj::SHIPWRECK_SURVIVOR:
 		{
-			soundID = soundBase::experience;
 			info.resize(1);
 			loadRandomArtifact(rand, info[0], 55, 20, 20, 5);
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 125);
@@ -594,7 +590,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 			int hlp = rand.nextInt(99);
 			if(hlp >= 95)
 			{
-				soundID = soundBase::treasure;
 				info.resize(1);
 				loadRandomArtifact(rand, info[0], 100, 0, 0, 0);
 				info[0].message.addTxt(MetaString::ADVOB_TXT,145);
@@ -604,7 +599,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 			}
 			else if (hlp >= 65)
 			{
-				soundID = soundBase::chest;
 				onSelect.addTxt(MetaString::ADVOB_TXT,146);
 				info.resize(2);
 				info[0].reward.resources[Res::GOLD] = 2000;
@@ -614,7 +608,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 			}
 			else if(hlp >= 33)
 			{
-				soundID = soundBase::chest;
 				onSelect.addTxt(MetaString::ADVOB_TXT,146);
 				info.resize(2);
 				info[0].reward.resources[Res::GOLD] = 1500;
@@ -624,7 +617,6 @@ void CGPickable::initObj(CRandomGenerator & rand)
 			}
 			else
 			{
-				soundID = soundBase::chest;
 				onSelect.addTxt(MetaString::ADVOB_TXT,146);
 				info.resize(2);
 				info[0].reward.resources[Res::GOLD] = 1000;
@@ -662,11 +654,10 @@ void CGBonusingObject::initObj(CRandomGenerator & rand)
 		configureBonusDuration(visit, Bonus::ONE_BATTLE, type, value, descrID);
 	};
 
-	auto configureMessage = [&](CVisitInfo & visit, int onGrantID, int onVisitedID, soundBase::soundID sound)
+	auto configureMessage = [&](CVisitInfo & visit, int onGrantID, int onVisitedID)
 	{
 		visit.message.addTxt(MetaString::ADVOB_TXT, onGrantID);
 		onVisited.addTxt(MetaString::ADVOB_TXT, onVisitedID);
-		soundID = sound;
 	};
 
 	info.resize(1);
@@ -675,16 +666,16 @@ void CGBonusingObject::initObj(CRandomGenerator & rand)
 	{
 	case Obj::BUOY:
 			blockVisit = true;
-		configureMessage(info[0], 21, 22, soundBase::MORALE);
+		configureMessage(info[0], 21, 22);
 		configureBonus(info[0], Bonus::MORALE, +1, 94);
 		break;
 	case Obj::SWAN_POND:
-		configureMessage(info[0], 29, 30, soundBase::LUCK);
+		configureMessage(info[0], 29, 30);
 		configureBonus(info[0], Bonus::LUCK, 2, 67);
 		info[0].reward.movePercentage = 0;
 		break;
 	case Obj::FAERIE_RING:
-		configureMessage(info[0], 49, 50, soundBase::LUCK);
+		configureMessage(info[0], 49, 50);
 		configureBonus(info[0], Bonus::LUCK, 1, 71);
 		break;
 	case Obj::FOUNTAIN_OF_FORTUNE:
@@ -694,7 +685,6 @@ void CGBonusingObject::initObj(CRandomGenerator & rand)
 		{
 			configureBonus(info[i], Bonus::LUCK, i-1, 69); //NOTE: description have %d that should be replaced with value
 			info[i].message.addTxt(MetaString::ADVOB_TXT, 55);
-			soundID = soundBase::LUCK;
 		}
 		onVisited.addTxt(MetaString::ADVOB_TXT, 56);
 		break;
@@ -706,27 +696,26 @@ void CGBonusingObject::initObj(CRandomGenerator & rand)
 			info[i].limiter.dayOfWeek = i+1;
 			configureBonus(info[i], (i%2) ? Bonus::MORALE : Bonus::LUCK, 1, 68);
 			info[i].message.addTxt(MetaString::ADVOB_TXT, 62);
-			soundID = soundBase::experience;
 		}
 		info.back().limiter.dayOfWeek = 7;
 		configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week
 		configureBonus(info.back(), Bonus::LUCK,   1, 68);
-		configureMessage(info.back(), 62, 63, soundBase::experience);
+		configureMessage(info.back(), 62, 63);
 
 		break;
 	case Obj::MERMAID:
 		blockVisit = true;
-		configureMessage(info[0], 83, 82, soundBase::LUCK);
+		configureMessage(info[0], 83, 82);
 		configureBonus(info[0], Bonus::LUCK, 1, 72);
 		break;
 	case Obj::RALLY_FLAG:
-		configureMessage(info[0], 111, 110, soundBase::MORALE);
+		configureMessage(info[0], 111, 110);
 		configureBonus(info[0], Bonus::MORALE, 1, 102);
 		configureBonus(info[0], Bonus::LUCK,   1, 102);
 		info[0].reward.movePoints = 400;
 		break;
 	case Obj::OASIS:
-		configureMessage(info[0], 95, 94, soundBase::MORALE);
+		configureMessage(info[0], 95, 94);
 		configureBonus(info[0], Bonus::MORALE, 1, 95);
 		info[0].reward.movePoints = 800;
 		break;
@@ -739,20 +728,19 @@ void CGBonusingObject::initObj(CRandomGenerator & rand)
 		info[0].message.addTxt(MetaString::ADVOB_TXT, 140);
 		info[1].message.addTxt(MetaString::ADVOB_TXT, 140);
 		onVisited.addTxt(MetaString::ADVOB_TXT, 141);
-		soundID = soundBase::temple;
 		break;
 	case Obj::WATERING_HOLE:
-		configureMessage(info[0], 166, 167, soundBase::MORALE);
+		configureMessage(info[0], 166, 167);
 		configureBonus(info[0], Bonus::MORALE, 1, 100);
 		info[0].reward.movePoints = 400;
 		break;
 	case Obj::FOUNTAIN_OF_YOUTH:
-		configureMessage(info[0], 57, 58, soundBase::MORALE);
+		configureMessage(info[0], 57, 58);
 		configureBonus(info[0], Bonus::MORALE, 1, 103);
 		info[0].reward.movePoints = 400;
 		break;
 	case Obj::STABLES:
-		configureMessage(info[0], 137, 136, soundBase::STORE);
+		configureMessage(info[0], 137, 136);
 		configureBonusDuration(info[0], Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 400, 0);
 		info[0].reward.movePoints = 400;
 		break;
@@ -838,7 +826,6 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand)
 	case Obj::CORPSE:
 		{
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 38);
-			soundID = soundBase::MYSTERY;
 			blockVisit = true;
 			if(rand.nextInt(99) < 20)
 			{
@@ -851,7 +838,6 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand)
 		break;
 	case Obj::LEAN_TO:
 		{
-			soundID = soundBase::GENIE;
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 65);
 			info.resize(1);
 			int type =  rand.nextInt(5); //any basic resource without gold
@@ -863,7 +849,6 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand)
 		break;
 	case Obj::WARRIORS_TOMB:
 		{
-			soundID = soundBase::GRAVEYARD;
 			onSelect.addTxt(MetaString::ADVOB_TXT, 161);
 
 			info.resize(2);
@@ -880,7 +865,6 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand)
 		break;
 	case Obj::WAGON:
 		{
-			soundID = soundBase::GENIE;
 			onVisited.addTxt(MetaString::ADVOB_TXT, 156);
 
 			int hlp = rand.nextInt(99);
@@ -921,7 +905,6 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
 	switch(ID)
 	{
 		case Obj::ARENA:
-			soundID = soundBase::NOMAD;
 			info.resize(2);
 			info[0].reward.primary[PrimarySkill::ATTACK] = 2;
 			info[1].reward.primary[PrimarySkill::DEFENSE] = 2;
@@ -932,40 +915,34 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
 		case Obj::MERCENARY_CAMP:
 			info.resize(1);
 			info[0].reward.primary[PrimarySkill::ATTACK] = 1;
-			soundID = soundBase::NOMAD;
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 80);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 81);
 			break;
 		case Obj::MARLETTO_TOWER:
 			info.resize(1);
 			info[0].reward.primary[PrimarySkill::DEFENSE] = 1;
-			soundID = soundBase::NOMAD;
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 39);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 40);
 			break;
 		case Obj::STAR_AXIS:
 			info.resize(1);
 			info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1;
-			soundID = soundBase::gazebo;
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 100);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 101);
 			break;
 		case Obj::GARDEN_OF_REVELATION:
 			info.resize(1);
 			info[0].reward.primary[PrimarySkill::KNOWLEDGE] = 1;
-			soundID = soundBase::GETPROTECTION;
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 59);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 60);
 			break;
 		case Obj::LEARNING_STONE:
 			info.resize(1);
 			info[0].reward.gainedExp = 1000;
-			soundID = soundBase::gazebo;
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 143);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 144);
 			break;
 		case Obj::TREE_OF_KNOWLEDGE:
-			soundID = soundBase::gazebo;
 			info.resize(1);
 			canRefuse = true;
 			info[0].reward.gainedLevels = 1;
@@ -1010,7 +987,6 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
 				visit.message.addTxt(MetaString::ADVOB_TXT, 66);
 				info.push_back(visit);
 			}
-			soundID = soundBase::gazebo;
 			break;
 		}
 		case Obj::SCHOOL_OF_MAGIC:
@@ -1022,7 +998,6 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
 			onSelect.addTxt(MetaString::ADVOB_TXT, 71);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 72);
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 73);
-			soundID = soundBase::faerie;
 			canRefuse = true;
 			break;
 		case Obj::SCHOOL_OF_WAR:
@@ -1034,7 +1009,6 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
 			onSelect.addTxt(MetaString::ADVOB_TXT, 158);
 			onVisited.addTxt(MetaString::ADVOB_TXT, 159);
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 160);
-			soundID = soundBase::MILITARY;
 			canRefuse = true;
 			break;
 	}
@@ -1063,17 +1037,14 @@ void CGVisitableOPW::initObj(CRandomGenerator & rand)
 	switch (ID)
 	{
 	case Obj::MYSTICAL_GARDEN:
-		soundID = soundBase::experience;
 		onEmpty.addTxt(MetaString::ADVOB_TXT, 93);
 		info[0].message.addTxt(MetaString::ADVOB_TXT, 92);
 		break;
 	case Obj::WINDMILL:
-		soundID = soundBase::GENIE;
 		onEmpty.addTxt(MetaString::ADVOB_TXT, 169);
 		info[0].message.addTxt(MetaString::ADVOB_TXT, 170);
 		break;
 	case Obj::WATER_WHEEL:
-		soundID = soundBase::GENIE;
 		onEmpty.addTxt(MetaString::ADVOB_TXT, 165);
 		info[0].message.addTxt(MetaString::ADVOB_TXT, 164);
 		break;
@@ -1146,7 +1117,6 @@ void CGMagicSpring::initObj(CRandomGenerator & rand)
 	info.push_back(visit); // two rewards, one for each entrance
 	info.push_back(visit);
 	onEmpty.addTxt(MetaString::ADVOB_TXT, 75);
-	soundID = soundBase::GENIE;
 }
 
 std::vector<int3> CGMagicSpring::getVisitableOffsets() const

+ 10 - 17
lib/mapObjects/MiscObjects.cpp

@@ -40,16 +40,17 @@ static void openWindow(const OpenWindow::EWindow type, const int id1, const int
 	IObjectInterface::cb->sendAndApply(&ow);
 }
 
-static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID = 0)
 {
 	InfoWindow iw;
-	iw.soundID = soundID;
+	if(soundID)
+		iw.soundID = soundID;
 	iw.player = playerID;
 	iw.text.addTxt(MetaString::ADVOB_TXT,txtID);
 	IObjectInterface::cb->sendAndApply(&iw);
 }
 
-static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID = 0)
 {
 	const PlayerColor playerID = h->getOwner();
 	showInfoDialog(playerID,txtID,soundID);
@@ -1324,7 +1325,6 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
 		{
 		case Obj::ARTIFACT:
 			{
-				iw.soundID = soundBase::treasure; //play sound only for non-scroll arts
 				iw.components.push_back(Component(Component::ARTIFACT, subID, 0, 0));
 				if(message.length())
 					iw.text << message;
@@ -1446,7 +1446,6 @@ void CGWitchHut::initObj(CRandomGenerator & rand)
 void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
 {
 	InfoWindow iw;
-	iw.soundID = soundBase::gazebo;
 	iw.player = h->getOwner();
 	if(!wasVisited(h->tempOwner))
 		cb->setObjProperty(id, CGWitchHut::OBJPROP_VISITED, h->tempOwner.getNum());
@@ -1535,7 +1534,7 @@ void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const
 	{
 		message = 79;
 	}
-	showInfoDialog(h,message,soundBase::faerie);
+	showInfoDialog(h, message);
 }
 
 std::string CGMagicWell::getHoverText(const CGHeroInstance * hero) const
@@ -1552,7 +1551,6 @@ void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const
 	case Obj::REDWOOD_OBSERVATORY:
 	case Obj::PILLAR_OF_FIRE:
 	{
-		iw.soundID = soundBase::LIGHTHOUSE;
 		iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE));
 
 		FoWChange fw;
@@ -1589,7 +1587,6 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 		cb->setObjProperty(id, CGShrine::OBJPROP_VISITED, h->tempOwner.getNum());
 
 	InfoWindow iw;
-	iw.soundID = soundBase::temple;
 	iw.player = h->getOwner();
 	iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88);
 	iw.text.addTxt(MetaString::SPELL_NAME,spell);
@@ -1678,7 +1675,6 @@ void CGSignBottle::initObj(CRandomGenerator & rand)
 void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const
 {
 	InfoWindow iw;
-	iw.soundID = soundBase::STORE;
 	iw.player = h->getOwner();
 	iw.text << message;
 	cb->showInfoDialog(&iw);
@@ -1706,7 +1702,6 @@ void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
 	}
 
 	InfoWindow iw;
-	iw.soundID = soundBase::gazebo;
 	iw.player = h->getOwner();
 	iw.text.addTxt(MetaString::ADVOB_TXT,115);
 
@@ -1874,7 +1869,7 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
 {
 	if (ID == Obj::HUT_OF_MAGI)
 	{
-		showInfoDialog(h, 61, soundBase::LIGHTHOUSE);
+		showInfoDialog(h, 61);
 
 		if (!eyelist[subID].empty())
 		{
@@ -1904,7 +1899,7 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
 	}
 	else if (ID == Obj::EYE_OF_MAGI)
 	{
-		showInfoDialog(h,48,soundBase::invalid);
+		showInfoDialog(h, 48);
 	}
 
 }
@@ -1926,7 +1921,6 @@ std::string CGSirens::getHoverText(const CGHeroInstance * hero) const
 void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
 {
 	InfoWindow iw;
-	iw.soundID = soundBase::DANGER;
 	iw.player = h->tempOwner;
 	if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens
 	{
@@ -2030,18 +2024,17 @@ void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
 			assert(text);
 			BlockingDialog bd (true, false);
 			bd.player = h->getOwner();
-			bd.soundID = soundBase::LIGHTHOUSE;
 			bd.text.addTxt (MetaString::ADVOB_TXT, text);
 			cb->showBlockingDialog (&bd);
 		}
 		else //if he cannot afford
 		{
-			showInfoDialog(h,28,soundBase::CAVEHEAD);
+			showInfoDialog(h, 28);
 		}
 	}
 	else //if he already visited carographer
 	{
-		showInfoDialog(h,24,soundBase::CAVEHEAD);
+		showInfoDialog(h, 24);
 	}
 }
 
@@ -2144,7 +2137,7 @@ void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
 	{
 		PlayerColor oldOwner = tempOwner;
 		cb->setOwner(this,h->tempOwner); //not ours? flag it!
-		showInfoDialog(h,69,soundBase::LIGHTHOUSE);
+		showInfoDialog(h, 69);
 		giveBonusTo(h->tempOwner);
 
 		if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner

+ 1 - 1
lib/serializer/CSerializer.h

@@ -12,7 +12,7 @@
 #include "../ConstTransitivePtr.h"
 #include "../GameConstants.h"
 
-const ui32 SERIALIZATION_VERSION = 777;
+const ui32 SERIALIZATION_VERSION = 778;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
 const std::string SAVEGAME_MAGIC = "VCMISVG";