summaryrefslogblamecommitdiffstats
path: root/src/ChannelManager.cpp
blob: 3d0f326b79a746e516547339eee69f2ed472a0fb (plain) (tree)





























































































































































































































                                                                                                                                                           
// ChannelManager.cpp

// Implements the cChannelManager class which stores and calls channel callbacks

#include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules

#include <algorithm>

#include "ByteBuffer.h"
#include "ChannelManager.h"
#include "ChannelCallback.h"
#include "ClientHandle.h"
#include "Entities/Player.h"
#include "OSSupport/CriticalSection.h"
#include "World.h"





class cChannelBroadcastCallback : public cPlayerListCallback
{
public:
	cChannelBroadcastCallback(const AString & a_Channel, const AString & a_Data)
	: m_Channel(a_Channel), m_Data(a_Data){}

	virtual bool Item(cPlayer * a_Type)
	{
		cClientHandle * Handle = a_Type->GetClientHandle();
		if (Handle->HasPluginChannel(m_Channel))
		{
			Handle->SendPluginMessage(m_Channel, m_Data);
		}
		return false;
	}
private:
	AString m_Channel;
	AString m_Data;
};





bool cChannelManager::RegisterChannel(const AString & a_Channel, cChannelCallbackPtr a_Callback)
{
	cCSLock Lock(m_CSCallbacks);
	auto Result = m_ChannelCallbacks.emplace(a_Channel, a_Callback);

	return Result.second;
}





bool cChannelManager::RemoveChannel(const AString & a_Channel)
{
	cCSLock Lock(m_CSCallbacks);
	auto callback = m_ChannelCallbacks.find(a_Channel);
	if (callback != m_ChannelCallbacks.end())
	{
		m_ChannelCallbacks.erase(callback);
		return true;
	}
	return false;
}





void cChannelManager::AddClientToChannel(cClientHandle & a_Client, const AString & a_Channel)
{
	a_Client.RegisterChannel(a_Channel);
	a_Client.SendPluginMessage("REGISTER", a_Channel);
}





void cChannelManager::AddClientToChannels(cClientHandle & a_Client, const AString & a_Channels)
{
	auto channels = BreakApartChannels(a_Channels);
	a_Client.RegisterChannels(channels);
}





void cChannelManager::RemoveClientFromChannel(cClientHandle & a_Client, const AString & a_Channel)
{
	a_Client.UnregisterChannel(a_Channel);
	a_Client.SendPluginMessage("UNREGISTER", a_Channel);
}





void cChannelManager::RemoveClientFromChannels(cClientHandle & a_Client, const AString & a_Channels)
{
	auto channels = BreakApartChannels(a_Channels);
	a_Client.UnregisterChannels(channels);
}





bool cChannelManager::ClientHasChannel(cClientHandle & a_Client, const AString & a_Channel)
{
	return a_Client.HasPluginChannel(a_Channel);
}





void cChannelManager::HandleChannelMessage(cClientHandle & a_Client, const AString & a_Channel, cByteBuffer & a_Data)
{
	bool HasChannel = ClientHasChannel(a_Client, a_Channel);

	// If the client has not registered to send and recieve this channel, log it and send an unregister message.
	// Otherwise try to use it.
	if (HasChannel)
	{
		cCSLock Lock(m_CSCallbacks);

		auto Callback = m_ChannelCallbacks.find(a_Channel);

		// If a callback has been registered for this channel, call it.
		// Otherwise, call the hook
		if (HasChannel && (Callback != m_ChannelCallbacks.end()))
		{
			Callback->second->Call(a_Client, a_Data);
		}
		else
		{
			cPluginManager::Get()->CallHookPluginMessage(a_Client, a_Channel, a_Data);
		}
	}
	else
	{
		// Ignore if client sent something but didn't register the channel first
		LOGD("Player %s sent a plugin message on channel \"%s\", but didn't REGISTER it first", a_Client.GetUsername().c_str(), a_Channel.c_str());
		a_Client.SendPluginMessage("UNREGISTER", a_Channel);
	}

}





void cChannelManager::HandlePluginUnloading(const cPluginLua * a_Plugin)
{
	cCSLock Lock(m_CSCallbacks);

	for (auto it = m_ChannelCallbacks.begin(); it != m_ChannelCallbacks.end();)
	{
		if (!it->second->BelongsTo(a_Plugin))
		{
			++it;
		}
		else
		{
			it = m_ChannelCallbacks.erase(it);
		}
	}
}





void cChannelManager::BroadcastChannelMessage(const AString & a_Channel, cByteBuffer & a_Data, cWorld * a_World)
{
	AString Temp;
	a_Data.ReadAll(Temp);
	if (a_World != nullptr)
	{
		cChannelBroadcastCallback Callback(a_Channel, Temp);
		a_World->ForEachPlayer(Callback);
	}
	else
	{
		// TODO: Implement server-wide broadcast
	}
}





AStringVector cChannelManager::BreakApartChannels(const AString & a_Channels)
{
	// Break the string on each NUL character.
	// Note that StringSplit() doesn't work on this because NUL is a special char - string terminator
	size_t len = a_Channels.size();
	size_t first = 0;
	AStringVector res;
	for (size_t i = 0; i < len; i++)
	{
		if (a_Channels[i] != 0)
		{
			continue;
		}
		if (i > first)
		{
			res.push_back(a_Channels.substr(first, i - first));
		}
		first = i + 1;
	}  // for i - a_PluginChannels[]
	if (first < len)
	{
		res.push_back(a_Channels.substr(first, len - first));
	}
	return res;
}