mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
343 lines
8.7 KiB
C++
343 lines
8.7 KiB
C++
#include "extensions/multiplayer.h"
|
|
|
|
#include "extensions/multiplayer/charactercustomizer.h"
|
|
#include "extensions/multiplayer/networkmanager.h"
|
|
#include "extensions/multiplayer/networktransport.h"
|
|
#include "extensions/multiplayer/protocol.h"
|
|
#include "isle_actions.h"
|
|
#include "islepathactor.h"
|
|
#include "legoactor.h"
|
|
#include "legoactors.h"
|
|
#include "legoentity.h"
|
|
#include "legoeventnotificationparam.h"
|
|
#include "legogamestate.h"
|
|
#include "legopathactor.h"
|
|
#include "misc.h"
|
|
#include "roi/legoroi.h"
|
|
|
|
#include <SDL3/SDL_stdinc.h>
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include "extensions/multiplayer/platforms/emscripten/callbacks.h"
|
|
#include "extensions/multiplayer/platforms/emscripten/websockettransport.h"
|
|
|
|
#include <emscripten.h>
|
|
#endif
|
|
|
|
using namespace Extensions;
|
|
|
|
static uint8_t ResolveDisplayActorIndex(const char* p_name)
|
|
{
|
|
for (int i = 0; i < static_cast<int>(sizeOfArray(g_actorInfoInit)); i++) {
|
|
if (!SDL_strcasecmp(g_actorInfoInit[i].m_name, p_name)) {
|
|
return static_cast<uint8_t>(i);
|
|
}
|
|
}
|
|
return Multiplayer::DISPLAY_ACTOR_NONE;
|
|
}
|
|
|
|
std::map<std::string, std::string> MultiplayerExt::options;
|
|
bool MultiplayerExt::enabled = false;
|
|
std::string MultiplayerExt::relayUrl;
|
|
std::string MultiplayerExt::room;
|
|
Multiplayer::NetworkManager* MultiplayerExt::s_networkManager = nullptr;
|
|
Multiplayer::NetworkTransport* MultiplayerExt::s_transport = nullptr;
|
|
Multiplayer::PlatformCallbacks* MultiplayerExt::s_callbacks = nullptr;
|
|
|
|
void MultiplayerExt::Initialize()
|
|
{
|
|
relayUrl = options["multiplayer:relay url"];
|
|
room = options["multiplayer:room"];
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
s_transport = new Multiplayer::WebSocketTransport(relayUrl);
|
|
s_callbacks = new Multiplayer::EmscriptenCallbacks();
|
|
|
|
s_networkManager = new Multiplayer::NetworkManager();
|
|
s_networkManager->Initialize(s_transport, s_callbacks);
|
|
|
|
// Third-person camera enabled by default, toggled via WASM export
|
|
s_networkManager->GetThirdPersonCamera().Enable();
|
|
|
|
std::string actor = options["multiplayer:actor"];
|
|
if (!actor.empty()) {
|
|
uint8_t displayIndex = ResolveDisplayActorIndex(actor.c_str());
|
|
if (displayIndex != Multiplayer::DISPLAY_ACTOR_NONE) {
|
|
s_networkManager->SetDisplayActorIndex(displayIndex);
|
|
}
|
|
}
|
|
|
|
if (!relayUrl.empty() && !room.empty()) {
|
|
s_networkManager->Connect(room.c_str());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MultiplayerExt::HandleCreate()
|
|
{
|
|
if (s_networkManager) {
|
|
s_networkManager->HandleCreate();
|
|
}
|
|
}
|
|
|
|
MxBool MultiplayerExt::HandleWorldEnable(LegoWorld* p_world, MxBool p_enable)
|
|
{
|
|
if (!s_networkManager) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (p_enable) {
|
|
s_networkManager->OnWorldEnabled(p_world);
|
|
}
|
|
else {
|
|
s_networkManager->OnWorldDisabled(p_world);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
MxBool MultiplayerExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationParam& p_param)
|
|
{
|
|
if (!s_networkManager) {
|
|
return FALSE;
|
|
}
|
|
|
|
Multiplayer::NetworkManager* mgr = s_networkManager;
|
|
|
|
// Check if it's a remote player
|
|
Multiplayer::RemotePlayer* remote = mgr->FindPlayerByROI(p_rootROI);
|
|
|
|
// Check if it's our own 3rd-person display actor override
|
|
bool isSelf =
|
|
(mgr->GetThirdPersonCamera().GetDisplayROI() != nullptr &&
|
|
mgr->GetThirdPersonCamera().GetDisplayROI() == p_rootROI);
|
|
|
|
if (!remote && !isSelf) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Remote player permission check
|
|
if (remote && !remote->GetAllowRemoteCustomize()) {
|
|
return TRUE; // Consume click, no effect
|
|
}
|
|
|
|
// Determine change type from clicker's actor ID
|
|
uint8_t changeType;
|
|
int partIndex = -1;
|
|
switch (GameState()->GetActorId()) {
|
|
case LegoActor::c_pepper:
|
|
if (GameState()->GetCurrentAct() == LegoGameState::e_act2 ||
|
|
GameState()->GetCurrentAct() == LegoGameState::e_act3) {
|
|
return TRUE;
|
|
}
|
|
changeType = Multiplayer::CHANGE_VARIANT;
|
|
break;
|
|
case LegoActor::c_mama:
|
|
changeType = Multiplayer::CHANGE_SOUND;
|
|
break;
|
|
case LegoActor::c_papa:
|
|
changeType = Multiplayer::CHANGE_MOVE;
|
|
break;
|
|
case LegoActor::c_nick:
|
|
changeType = Multiplayer::CHANGE_COLOR;
|
|
if (p_param.GetROI()) {
|
|
partIndex = Multiplayer::CharacterCustomizer::MapClickedPartIndex(p_param.GetROI()->GetName());
|
|
}
|
|
if (partIndex < 0) {
|
|
return TRUE;
|
|
}
|
|
break;
|
|
case LegoActor::c_laura:
|
|
changeType = Multiplayer::CHANGE_MOOD;
|
|
break;
|
|
case LegoActor::c_brickster:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
// Send a customize request to the server. The server echoes it back to all peers
|
|
// (including the sender). HandleCustomize then applies the change and plays effects.
|
|
// For remote targets this avoids flip-flop from stale state messages; for self targets
|
|
// it keeps the code path uniform.
|
|
uint32_t targetPeerId = remote ? remote->GetPeerId() : mgr->GetLocalPeerId();
|
|
mgr->SendCustomize(targetPeerId, changeType, static_cast<uint8_t>(partIndex >= 0 ? partIndex : 0xFF));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
MxBool MultiplayerExt::HandleEntityNotify(LegoEntity* p_entity)
|
|
{
|
|
if (!s_networkManager) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Only intercept plants and buildings
|
|
MxU8 type = p_entity->GetType();
|
|
if (type != LegoEntity::e_plant && type != LegoEntity::e_building) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Determine the change type based on the active character,
|
|
// mirroring the logic in LegoEntity::Notify().
|
|
MxU8 changeType;
|
|
switch (GameState()->GetActorId()) {
|
|
case LegoActor::c_pepper:
|
|
if (GameState()->GetCurrentAct() == LegoGameState::e_act2 ||
|
|
GameState()->GetCurrentAct() == LegoGameState::e_act3) {
|
|
return FALSE;
|
|
}
|
|
changeType = Multiplayer::CHANGE_VARIANT;
|
|
break;
|
|
case LegoActor::c_mama:
|
|
changeType = Multiplayer::CHANGE_SOUND;
|
|
break;
|
|
case LegoActor::c_papa:
|
|
changeType = Multiplayer::CHANGE_MOVE;
|
|
break;
|
|
case LegoActor::c_nick:
|
|
changeType = Multiplayer::CHANGE_COLOR;
|
|
break;
|
|
case LegoActor::c_laura:
|
|
changeType = Multiplayer::CHANGE_MOOD;
|
|
break;
|
|
case LegoActor::c_brickster:
|
|
changeType = Multiplayer::CHANGE_DECREMENT;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return s_networkManager->HandleEntityMutation(p_entity, changeType);
|
|
}
|
|
|
|
MxBool MultiplayerExt::HandleSkyLightControl(MxU32 p_controlId)
|
|
{
|
|
if (!s_networkManager) {
|
|
return FALSE;
|
|
}
|
|
|
|
uint8_t entityType;
|
|
uint8_t changeType;
|
|
|
|
switch (p_controlId) {
|
|
case IsleScript::c_Observe_SkyColor_Ctl:
|
|
entityType = Multiplayer::ENTITY_SKY;
|
|
changeType = Multiplayer::SKY_TOGGLE_COLOR;
|
|
break;
|
|
case IsleScript::c_Observe_Sun_Ctl:
|
|
entityType = Multiplayer::ENTITY_SKY;
|
|
changeType = Multiplayer::SKY_DAY;
|
|
break;
|
|
case IsleScript::c_Observe_Moon_Ctl:
|
|
entityType = Multiplayer::ENTITY_SKY;
|
|
changeType = Multiplayer::SKY_NIGHT;
|
|
break;
|
|
case IsleScript::c_Observe_GlobeRArrow_Ctl:
|
|
entityType = Multiplayer::ENTITY_LIGHT;
|
|
changeType = Multiplayer::LIGHT_INCREMENT;
|
|
break;
|
|
case IsleScript::c_Observe_GlobeLArrow_Ctl:
|
|
entityType = Multiplayer::ENTITY_LIGHT;
|
|
changeType = Multiplayer::LIGHT_DECREMENT;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return s_networkManager->HandleSkyLightMutation(entityType, changeType);
|
|
}
|
|
|
|
void MultiplayerExt::HandleBeforeSaveLoad()
|
|
{
|
|
if (s_networkManager) {
|
|
s_networkManager->OnBeforeSaveLoad();
|
|
}
|
|
}
|
|
|
|
void MultiplayerExt::HandleSaveLoaded()
|
|
{
|
|
if (s_networkManager) {
|
|
s_networkManager->OnSaveLoaded();
|
|
}
|
|
}
|
|
|
|
void MultiplayerExt::HandleActorEnter(IslePathActor* p_actor)
|
|
{
|
|
if (s_networkManager) {
|
|
s_networkManager->GetThirdPersonCamera().OnActorEnter(p_actor);
|
|
}
|
|
}
|
|
|
|
void MultiplayerExt::HandleActorExit(IslePathActor* p_actor)
|
|
{
|
|
if (s_networkManager) {
|
|
s_networkManager->GetThirdPersonCamera().OnActorExit(p_actor);
|
|
}
|
|
}
|
|
|
|
void MultiplayerExt::HandleCamAnimEnd(LegoPathActor* p_actor)
|
|
{
|
|
if (s_networkManager) {
|
|
s_networkManager->GetThirdPersonCamera().OnCamAnimEnd(p_actor);
|
|
}
|
|
}
|
|
|
|
MxBool MultiplayerExt::ShouldInvertMovement(LegoPathActor* p_actor)
|
|
{
|
|
if (s_networkManager && UserActor() == p_actor) {
|
|
return s_networkManager->GetThirdPersonCamera().IsActive();
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
MxBool MultiplayerExt::IsClonedCharacter(const char* p_name)
|
|
{
|
|
if (!s_networkManager) {
|
|
return FALSE;
|
|
}
|
|
|
|
return s_networkManager->IsClonedCharacter(p_name) ? TRUE : FALSE;
|
|
}
|
|
|
|
void MultiplayerExt::HandleSDLEvent(SDL_Event* p_event)
|
|
{
|
|
if (s_networkManager && s_networkManager->GetThirdPersonCamera().IsActive()) {
|
|
s_networkManager->GetThirdPersonCamera().HandleSDLEvent(p_event);
|
|
}
|
|
}
|
|
|
|
MxBool MultiplayerExt::IsTouchInputSuppressed()
|
|
{
|
|
if (s_networkManager && s_networkManager->GetThirdPersonCamera().IsActive()) {
|
|
return s_networkManager->GetThirdPersonCamera().IsTouchGestureActive() ? TRUE : FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
MxBool MultiplayerExt::CheckRejected()
|
|
{
|
|
if (s_networkManager && s_networkManager->WasRejected()) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void MultiplayerExt::SetNetworkManager(Multiplayer::NetworkManager* p_networkManager)
|
|
{
|
|
s_networkManager = p_networkManager;
|
|
}
|
|
|
|
Multiplayer::NetworkManager* MultiplayerExt::GetNetworkManager()
|
|
{
|
|
return s_networkManager;
|
|
}
|
|
|
|
bool Extensions::IsMultiplayerRejected()
|
|
{
|
|
return Extension<MultiplayerExt>::Call(CheckRejected).value_or(FALSE);
|
|
}
|