mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-01-30 15:00:13 -05:00
Anchor Beta
This commit is contained in:
parent
7df9641297
commit
468929e9bd
@ -21,8 +21,11 @@ DEFINE_HOOK(OnFlagSet, (int16_t flagType, int16_t flag));
|
||||
DEFINE_HOOK(OnFlagUnset, (int16_t flagType, int16_t flag));
|
||||
DEFINE_HOOK(OnSceneSpawnActors, ());
|
||||
DEFINE_HOOK(OnPlayerUpdate, ());
|
||||
DEFINE_HOOK(OnPlayerSfx, (u16 sfxId));
|
||||
DEFINE_HOOK(OnOcarinaSongAction, ());
|
||||
DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price));
|
||||
DEFINE_HOOK(OnDungeonKeyUsed, (uint16_t mapIndex));
|
||||
DEFINE_HOOK(ShouldActorInit, (void* actor, bool* result));
|
||||
DEFINE_HOOK(OnActorInit, (void* actor));
|
||||
DEFINE_HOOK(OnActorUpdate, (void* actor));
|
||||
DEFINE_HOOK(OnActorKill, (void* actor));
|
||||
@ -35,7 +38,7 @@ DEFINE_HOOK(OnPlayerBottleUpdate, (int16_t contents));
|
||||
DEFINE_HOOK(OnPlayDestroy, ());
|
||||
DEFINE_HOOK(OnPlayDrawEnd, ());
|
||||
DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, va_list originalArgs));
|
||||
DEFINE_HOOK(OnSaveFile, (int32_t fileNum));
|
||||
DEFINE_HOOK(OnSaveFile, (int32_t fileNum, int32_t sectionID));
|
||||
DEFINE_HOOK(OnLoadFile, (int32_t fileNum));
|
||||
DEFINE_HOOK(OnDeleteFile, (int32_t fileNum));
|
||||
|
||||
@ -62,3 +65,7 @@ DEFINE_HOOK(OnSetGameLanguage, ());
|
||||
DEFINE_HOOK(OnFileDropped, (std::string filePath));
|
||||
DEFINE_HOOK(OnAssetAltChange, ());
|
||||
DEFINE_HOOK(OnKaleidoUpdate, ());
|
||||
|
||||
DEFINE_HOOK(OnRandoSetCheckStatus, (RandomizerCheck rc, RandomizerCheckStatus status));
|
||||
DEFINE_HOOK(OnRandoSetIsSkipped, (RandomizerCheck rc, bool isSkipped));
|
||||
DEFINE_HOOK(OnRandoEntranceDiscovered, (u16 entranceIndex, u8 isReversedEntrance));
|
||||
|
@ -76,6 +76,10 @@ void GameInteractor_ExecuteOnPlayerUpdate() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerUpdate>();
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerSfx>(sfxId);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnOcarinaSongAction() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnOcarinaSongAction>();
|
||||
}
|
||||
@ -84,6 +88,19 @@ void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t pr
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnShopSlotChange>(cursorIndex, price);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnDungeonKeyUsedHooks(uint16_t mapIndex) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnDungeonKeyUsed>(mapIndex);
|
||||
}
|
||||
|
||||
bool GameInteractor_ShouldActorInit(void* actor) {
|
||||
bool result = true;
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::ShouldActorInit>(actor, &result);
|
||||
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::ShouldActorInit>(((Actor*)actor)->id, actor, &result);
|
||||
GameInteractor::Instance->ExecuteHooksForPtr<GameInteractor::ShouldActorInit>((uintptr_t)actor, actor, &result);
|
||||
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::ShouldActorInit>(actor, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnActorInit(void* actor) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorInit>(actor);
|
||||
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorInit>(((Actor*)actor)->id, actor);
|
||||
@ -166,8 +183,8 @@ bool GameInteractor_Should(GIVanillaBehavior flag, u32 result, ...) {
|
||||
|
||||
// MARK: - Save Files
|
||||
|
||||
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
|
||||
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum, int32_t sectionID) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum, sectionID);
|
||||
}
|
||||
|
||||
void GameInteractor_ExecuteOnLoadFile(int32_t fileNum) {
|
||||
@ -267,3 +284,8 @@ void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) {
|
||||
void GameInteractor_ExecuteOnKaleidoUpdate() {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnKaleidoUpdate>();
|
||||
}
|
||||
|
||||
// MARK: - Rando
|
||||
void GameInteractor_ExecuteOnRandoEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) {
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnRandoEntranceDiscovered>(entranceIndex, isReversedEntrance);
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ void GameInteractor_ExecuteOnFlagSet(int16_t flagType, int16_t flag);
|
||||
void GameInteractor_ExecuteOnFlagUnset(int16_t flagType, int16_t flag);
|
||||
void GameInteractor_ExecuteOnSceneSpawnActors();
|
||||
void GameInteractor_ExecuteOnPlayerUpdate();
|
||||
void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId);
|
||||
void GameInteractor_ExecuteOnOcarinaSongAction();
|
||||
bool GameInteractor_ShouldActorInit(void* actor);
|
||||
void GameInteractor_ExecuteOnActorInit(void* actor);
|
||||
void GameInteractor_ExecuteOnActorUpdate(void* actor);
|
||||
void GameInteractor_ExecuteOnActorKill(void* actor);
|
||||
@ -35,12 +37,13 @@ void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount);
|
||||
void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents);
|
||||
void GameInteractor_ExecuteOnOcarinaSongAction();
|
||||
void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price);
|
||||
void GameInteractor_ExecuteOnDungeonKeyUsedHooks(uint16_t mapIndex);
|
||||
void GameInteractor_ExecuteOnPlayDestroy();
|
||||
void GameInteractor_ExecuteOnPlayDrawEnd();
|
||||
bool GameInteractor_Should(GIVanillaBehavior flag, uint32_t result, ...);
|
||||
|
||||
// MARK: - Save Files
|
||||
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum);
|
||||
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum, int32_t sectionID);
|
||||
void GameInteractor_ExecuteOnLoadFile(int32_t fileNum);
|
||||
void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum);
|
||||
|
||||
@ -74,6 +77,9 @@ void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void));
|
||||
//Mark: - Pause Menu
|
||||
void GameInteractor_ExecuteOnKaleidoUpdate();
|
||||
|
||||
// MARK: - Rando
|
||||
void GameInteractor_ExecuteOnRandoEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1023,12 +1023,12 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
|
||||
if (item00->itemEntry.modIndex == MOD_NONE) {
|
||||
Notification::Emit({
|
||||
.itemIcon = GetTextureForItemId(item00->itemEntry.itemId),
|
||||
.message = "You found ",
|
||||
.message = "You found",
|
||||
.suffix = SohUtils::GetItemName(item00->itemEntry.itemId),
|
||||
});
|
||||
} else if (item00->itemEntry.modIndex == MOD_RANDOMIZER) {
|
||||
Notification::Emit({
|
||||
.message = "You found ",
|
||||
.message = "You found",
|
||||
.suffix = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId).GetName().english,
|
||||
});
|
||||
}
|
||||
|
@ -131,6 +131,7 @@ bool ItemLocation::HasObtained() const {
|
||||
|
||||
void ItemLocation::SetCheckStatus(RandomizerCheckStatus status_) {
|
||||
status = status_;
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnRandoSetCheckStatus>(rc, status);
|
||||
}
|
||||
|
||||
RandomizerCheckStatus ItemLocation::GetCheckStatus() {
|
||||
@ -139,6 +140,7 @@ RandomizerCheckStatus ItemLocation::GetCheckStatus() {
|
||||
|
||||
void ItemLocation::SetIsSkipped(bool isSkipped_) {
|
||||
isSkipped = isSkipped_;
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnRandoSetIsSkipped>(rc, isSkipped);
|
||||
}
|
||||
|
||||
bool ItemLocation::GetIsSkipped() {
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "global.h"
|
||||
#include "entrance.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
|
||||
extern PlayState* gPlayState;
|
||||
|
||||
@ -778,6 +779,8 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameInteractor_ExecuteOnRandoEntranceDiscovered(entranceIndex, isReversedEntrance);
|
||||
|
||||
u16 bitsPerIndex = sizeof(u32) * 8;
|
||||
u32 idx = entranceIndex / bitsPerIndex;
|
||||
if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) {
|
||||
|
405
soh/soh/Network/Anchor/Anchor.cpp
Normal file
405
soh/soh/Network/Anchor/Anchor.cpp
Normal file
@ -0,0 +1,405 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/Enhancements/nametag.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
#include "functions.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
// MARK: - Overrides
|
||||
|
||||
void Anchor::Enable() {
|
||||
Network::Enable(CVarGetString(CVAR_REMOTE_ANCHOR("Host"), "anchor.proxysaw.dev"), CVarGetInteger(CVAR_REMOTE_ANCHOR("Port"), 43383));
|
||||
ownClientId = CVarGetInteger(CVAR_REMOTE_ANCHOR("LastClientId"), 0);
|
||||
roomState.ownerClientId = 0;
|
||||
}
|
||||
|
||||
void Anchor::Disable() {
|
||||
Network::Disable();
|
||||
|
||||
clients.clear();
|
||||
RefreshClientActors();
|
||||
}
|
||||
|
||||
void Anchor::OnConnected() {
|
||||
SendPacket_Handshake();
|
||||
RegisterHooks();
|
||||
|
||||
if (IsSaveLoaded()) {
|
||||
SendPacket_RequestTeamState();
|
||||
}
|
||||
}
|
||||
|
||||
void Anchor::OnDisconnected() {
|
||||
RegisterHooks();
|
||||
}
|
||||
|
||||
void Anchor::SendJsonToRemote(nlohmann::json payload) {
|
||||
if (!isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
payload["clientId"] = ownClientId;
|
||||
if (!payload.contains("quiet")) {
|
||||
SPDLOG_INFO("[Anchor] Sending payload:\n{}", payload.dump());
|
||||
}
|
||||
Network::SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::OnIncomingJson(nlohmann::json payload) {
|
||||
// If it doesn't contain a type, it's not a valid payload
|
||||
if (!payload.contains("type")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not a quiet payload, log it
|
||||
if (!payload.contains("quiet")) {
|
||||
SPDLOG_INFO("[Anchor] Received payload:\n{}", payload.dump());
|
||||
}
|
||||
|
||||
std::string packetType = payload["type"].get<std::string>();
|
||||
|
||||
// Ignore packets from mismatched clients, except for ALL_CLIENT_STATE or UPDATE_CLIENT_STATE
|
||||
if (packetType != ALL_CLIENT_STATE && packetType != UPDATE_CLIENT_STATE) {
|
||||
if (payload.contains("clientId")) {
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
if (clients.contains(clientId) && clients[clientId].clientVersion != clientVersion) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// packetType here is a string so we can't use a switch statement
|
||||
if (packetType == ALL_CLIENT_STATE) HandlePacket_AllClientState(payload);
|
||||
else if (packetType == CONSUME_ADULT_TRADE_ITEM) HandlePacket_ConsumeAdultTradeItem(payload);
|
||||
else if (packetType == DAMAGE_PLAYER) HandlePacket_DamagePlayer(payload);
|
||||
else if (packetType == DISABLE_ANCHOR) HandlePacket_DisableAnchor(payload);
|
||||
else if (packetType == ENTRANCE_DISCOVERED) HandlePacket_EntranceDiscovered(payload);
|
||||
else if (packetType == GAME_COMPLETE) HandlePacket_GameComplete(payload);
|
||||
else if (packetType == GIVE_ITEM) HandlePacket_GiveItem(payload);
|
||||
else if (packetType == PLAYER_SFX) HandlePacket_PlayerSfx(payload);
|
||||
else if (packetType == PLAYER_UPDATE) HandlePacket_PlayerUpdate(payload);
|
||||
else if (packetType == UPDATE_TEAM_STATE) HandlePacket_UpdateTeamState(payload);
|
||||
else if (packetType == REQUEST_TEAM_STATE) HandlePacket_RequestTeamState(payload);
|
||||
else if (packetType == REQUEST_TELEPORT) HandlePacket_RequestTeleport(payload);
|
||||
else if (packetType == SERVER_MESSAGE) HandlePacket_ServerMessage(payload);
|
||||
else if (packetType == SET_CHECK_STATUS) HandlePacket_SetCheckStatus(payload);
|
||||
else if (packetType == SET_FLAG) HandlePacket_SetFlag(payload);
|
||||
else if (packetType == TELEPORT_TO) HandlePacket_TeleportTo(payload);
|
||||
else if (packetType == UNSET_FLAG) HandlePacket_UnsetFlag(payload);
|
||||
else if (packetType == UPDATE_BEANS_COUNT) HandlePacket_UpdateBeansCount(payload);
|
||||
else if (packetType == UPDATE_CLIENT_STATE) HandlePacket_UpdateClientState(payload);
|
||||
else if (packetType == UPDATE_ROOM_STATE) HandlePacket_UpdateRoomState(payload);
|
||||
else if (packetType == UPDATE_DUNGEON_ITEMS) HandlePacket_UpdateDungeonItems(payload);
|
||||
}
|
||||
|
||||
// Macros to let us easily register and unregister functions when the anchor is enabled/disabled
|
||||
#define HOOK(hook, condition, body) \
|
||||
static HOOK_ID hook = 0; \
|
||||
GameInteractor::Instance->UnregisterGameHook<GameInteractor::hook>(hook); \
|
||||
hook = 0; \
|
||||
if (condition) { \
|
||||
hook = GameInteractor::Instance->RegisterGameHook<GameInteractor::hook>(body); \
|
||||
}
|
||||
|
||||
#define HOOK_FOR_ID(hook, condition, id, body) \
|
||||
static HOOK_ID hook = 0; \
|
||||
GameInteractor::Instance->UnregisterGameHookForID<GameInteractor::hook>(hook); \
|
||||
hook = 0; \
|
||||
if (condition) { \
|
||||
hook = GameInteractor::Instance->RegisterGameHookForID<GameInteractor::hook>(id, body); \
|
||||
}
|
||||
|
||||
void Anchor::RegisterHooks() {
|
||||
HOOK(OnSceneSpawnActors, isConnected, [&]() {
|
||||
SendPacket_UpdateClientState();
|
||||
|
||||
if (IsSaveLoaded()) {
|
||||
RefreshClientActors();
|
||||
}
|
||||
});
|
||||
|
||||
HOOK(OnPresentFileSelect, isConnected, [&]() {
|
||||
SendPacket_UpdateClientState();
|
||||
});
|
||||
|
||||
HOOK_FOR_ID(ShouldActorInit, isConnected, ACTOR_PLAYER, [&](void* actorRef, bool* should) {
|
||||
Actor* actor = (Actor*)actorRef;
|
||||
|
||||
if (refreshingActors) {
|
||||
// By the time we get here, the actor was already added to the ACTORCAT_PLAYER list, so we need to move it
|
||||
Actor_ChangeCategory(gPlayState, &gPlayState->actorCtx, actor, ACTORCAT_NPC);
|
||||
actor->id = ACTOR_EN_OE2;
|
||||
actor->category = ACTORCAT_NPC;
|
||||
actor->init = DummyPlayer_Init;
|
||||
actor->update = DummyPlayer_Update;
|
||||
actor->draw = DummyPlayer_Draw;
|
||||
actor->destroy = DummyPlayer_Destroy;
|
||||
}
|
||||
});
|
||||
|
||||
HOOK(OnPlayerUpdate, isConnected, [&]() {
|
||||
if (justLoadedSave) {
|
||||
justLoadedSave = false;
|
||||
SendPacket_RequestTeamState();
|
||||
}
|
||||
SendPacket_PlayerUpdate();
|
||||
});
|
||||
|
||||
HOOK(OnPlayerSfx, isConnected, [&](u16 sfxId) {
|
||||
SendPacket_PlayerSfx(sfxId);
|
||||
});
|
||||
|
||||
HOOK(OnLoadGame, isConnected, [&](s16 fileNum) {
|
||||
justLoadedSave = true;
|
||||
});
|
||||
|
||||
HOOK(OnSaveFile, isConnected, [&](s16 fileNum, int sectionID) {
|
||||
if (sectionID == 0) {
|
||||
SendPacket_UpdateTeamState();
|
||||
}
|
||||
});
|
||||
|
||||
HOOK(OnFlagSet, isConnected, [&](s16 flagType, s16 flag) {
|
||||
SendPacket_SetFlag(SCENE_ID_MAX, flagType, flag);
|
||||
});
|
||||
|
||||
HOOK(OnFlagUnset, isConnected, [&](s16 flagType, s16 flag) {
|
||||
SendPacket_UnsetFlag(SCENE_ID_MAX, flagType, flag);
|
||||
});
|
||||
|
||||
HOOK(OnSceneFlagSet, isConnected, [&](s16 sceneNum, s16 flagType, s16 flag) {
|
||||
SendPacket_SetFlag(sceneNum, flagType, flag);
|
||||
});
|
||||
|
||||
HOOK(OnSceneFlagUnset, isConnected, [&](s16 sceneNum, s16 flagType, s16 flag) {
|
||||
SendPacket_UnsetFlag(sceneNum, flagType, flag);
|
||||
});
|
||||
|
||||
HOOK(OnRandoSetCheckStatus, isConnected, [&](RandomizerCheck rc, RandomizerCheckStatus status) {
|
||||
if (!isHandlingUpdateTeamState) {
|
||||
SendPacket_SetCheckStatus(rc);
|
||||
}
|
||||
});
|
||||
|
||||
HOOK(OnRandoSetIsSkipped, isConnected, [&](RandomizerCheck rc, bool isSkipped) {
|
||||
if (!isHandlingUpdateTeamState) {
|
||||
SendPacket_SetCheckStatus(rc);
|
||||
}
|
||||
});
|
||||
|
||||
HOOK(OnRandoEntranceDiscovered, isConnected, [&](u16 entranceIndex, u8 isReversedEntrance) {
|
||||
SendPacket_EntranceDiscovered(entranceIndex);
|
||||
});
|
||||
|
||||
HOOK_FOR_ID(OnBossDefeat, isConnected, ACTOR_BOSS_GANON2, [&](void* refActor) {
|
||||
SendPacket_GameComplete();
|
||||
});
|
||||
|
||||
HOOK(OnItemReceive, isConnected, [&](GetItemEntry itemEntry) {
|
||||
// Handle vanilla dungeon items a bit differently
|
||||
if (itemEntry.modIndex == MOD_NONE && (itemEntry.itemId >= ITEM_KEY_BOSS && itemEntry.itemId <= ITEM_KEY_SMALL)) {
|
||||
SendPacket_UpdateDungeonItems();
|
||||
return;
|
||||
}
|
||||
|
||||
SendPacket_GiveItem(itemEntry.tableId, itemEntry.getItemId);
|
||||
});
|
||||
|
||||
HOOK(OnDungeonKeyUsed, isConnected, [&](uint16_t mapIndex) {
|
||||
// Handle vanilla dungeon items a bit differently
|
||||
SendPacket_UpdateDungeonItems();
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Misc/Helpers
|
||||
|
||||
// Kills all existing anchor actors and respawns them with the new client data
|
||||
void Anchor::RefreshClientActors() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_NPC].head;
|
||||
|
||||
while (actor != NULL) {
|
||||
if (actor->id == ACTOR_EN_OE2 && actor->update == DummyPlayer_Update) {
|
||||
NameTag_RemoveAllForActor(actor);
|
||||
Actor_Kill(actor);
|
||||
}
|
||||
actor = actor->next;
|
||||
}
|
||||
|
||||
actorIndexToClientId.clear();
|
||||
refreshingActors = true;
|
||||
for (auto& [clientId, client] : clients) {
|
||||
if (!client.online || client.self) {
|
||||
continue;
|
||||
}
|
||||
|
||||
actorIndexToClientId.push_back(clientId);
|
||||
// We are using a hook `ShouldActorInit` to override the init/update/draw/destroy functions of the Player we spawn
|
||||
// We quickly store a mapping of "index" to clientId, then within the init function we use this to get the clientId
|
||||
// and store it on player->zTargetActiveTimer (unused s32 for the dummy) for convenience
|
||||
auto dummy = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_PLAYER, client.posRot.pos.x,
|
||||
client.posRot.pos.y, client.posRot.pos.z, client.posRot.rot.x, client.posRot.rot.y,
|
||||
client.posRot.rot.z, actorIndexToClientId.size() - 1, false);
|
||||
client.player = (Player*)dummy;
|
||||
}
|
||||
refreshingActors = false;
|
||||
}
|
||||
|
||||
bool Anchor::IsSaveLoaded() {
|
||||
if (gPlayState == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GET_PLAYER(gPlayState) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gSaveContext.gameMode != GAMEMODE_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
void Anchor::DrawMenu() {
|
||||
ImGui::PushID("Anchor");
|
||||
|
||||
std::string host = CVarGetString(CVAR_REMOTE_ANCHOR("Host"), "anchor.proxysaw.dev");
|
||||
uint16_t port = CVarGetInteger(CVAR_REMOTE_ANCHOR("Port"), 43383);
|
||||
std::string anchorTeamId = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
std::string anchorRoomId = CVarGetString(CVAR_REMOTE_ANCHOR("RoomId"), "");
|
||||
std::string anchorName = CVarGetString(CVAR_REMOTE_ANCHOR("Name"), "");
|
||||
bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535 &&
|
||||
!SohUtils::IsStringEmpty(anchorRoomId) && !SohUtils::IsStringEmpty(anchorName);
|
||||
|
||||
ImGui::SeparatorText("Anchor");
|
||||
// UIWidgets::Tooltip("Anchor Stuff");
|
||||
if (ImGui::IsItemClicked()) {
|
||||
// ImGui::SetClipboardText("https://github.com/garrettjoecox/anchor");
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(isEnabled);
|
||||
ImGui::Text("Host & Port");
|
||||
if (UIWidgets::InputString("##Host", &host)) {
|
||||
CVarSetString(CVAR_REMOTE_ANCHOR("Host"), host.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) {
|
||||
CVarSetInteger(CVAR_REMOTE_ANCHOR("Port"), port);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
|
||||
ImGui::Text("Tunic Color & Name");
|
||||
static Color_RGBA8 color = CVarGetColor(CVAR_REMOTE_ANCHOR("Color"), { 100, 255, 100, 255 });
|
||||
static ImVec4 colorVec = ImVec4(color.r / 255.0, color.g / 255.0, color.b / 255.0, 1);
|
||||
if (ImGui::ColorEdit3("##Color", (float*)&colorVec,
|
||||
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) {
|
||||
color.r = colorVec.x * 255.0;
|
||||
color.g = colorVec.y * 255.0;
|
||||
color.b = colorVec.z * 255.0;
|
||||
|
||||
CVarSetColor(CVAR_REMOTE_ANCHOR("Color"), color);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (UIWidgets::InputString("##Name", &anchorName)) {
|
||||
CVarSetString(CVAR_REMOTE_ANCHOR("Name"), anchorName.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
ImGui::Text("Room ID");
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (UIWidgets::InputString("##RoomId", &anchorRoomId, isEnabled ? ImGuiInputTextFlags_Password : 0)) {
|
||||
CVarSetString(CVAR_REMOTE_ANCHOR("RoomId"), anchorRoomId.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
ImGui::Text("Team ID (Items & Flags Shared)");
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (UIWidgets::InputString("##TeamId", &anchorTeamId)) {
|
||||
CVarSetString(CVAR_REMOTE_ANCHOR("TeamId"), anchorTeamId.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::BeginDisabled(!isFormValid);
|
||||
const char* buttonLabel = isEnabled ? "Disable" : "Enable";
|
||||
if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) {
|
||||
if (isEnabled) {
|
||||
CVarClear(CVAR_REMOTE_ANCHOR("Enabled"));
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
Disable();
|
||||
} else {
|
||||
CVarSetInteger(CVAR_REMOTE_ANCHOR("Enabled"), 1);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
Enable();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (isEnabled) {
|
||||
ImGui::Spacing();
|
||||
if (isConnected) {
|
||||
ImGui::Text("Connected");
|
||||
|
||||
if (roomState.ownerClientId == ownClientId) {
|
||||
if (ImGui::BeginMenu("Room Settings")) {
|
||||
ImGui::Text("PvP Mode:");
|
||||
static const char* pvpModes[3] = { "Off", "On", "On + Friendly Fire" };
|
||||
if (UIWidgets::EnhancementCombobox(CVAR_REMOTE_ANCHOR("RoomSettings.PvpMode"), pvpModes, 1)) {
|
||||
SendPacket_UpdateRoomState();
|
||||
}
|
||||
ImGui::Text("Show Locations For:");
|
||||
static const char* showLocationsModes[3] = { "None", "Team Only", "All" };
|
||||
if (UIWidgets::EnhancementCombobox(CVAR_REMOTE_ANCHOR("RoomSettings.ShowLocationsMode"), showLocationsModes, 1)) {
|
||||
SendPacket_UpdateRoomState();
|
||||
}
|
||||
ImGui::Text("Allow Teleporting To:");
|
||||
static const char* teleportModes[3] = { "None", "Team Only", "All" };
|
||||
if (UIWidgets::EnhancementCombobox(CVAR_REMOTE_ANCHOR("RoomSettings.TeleportMode"), teleportModes, 1)) {
|
||||
SendPacket_UpdateRoomState();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("Request Team State", ImVec2(ImGui::GetContentRegionAvail().x - 25.0f, 0.0f))) {
|
||||
SendPacket_RequestTeamState();
|
||||
}
|
||||
if (roomState.ownerClientId == ownClientId) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_TRASH)) {
|
||||
SendPacket_ClearTeamState();
|
||||
}
|
||||
UIWidgets::Tooltip("Clear Team State");
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("Connecting...");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
#endif
|
177
soh/soh/Network/Anchor/Anchor.h
Normal file
177
soh/soh/Network/Anchor/Anchor.h
Normal file
@ -0,0 +1,177 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#ifndef NETWORK_ANCHOR_H
|
||||
#define NETWORK_ANCHOR_H
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "soh/Network/Network.h"
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
#include "z64.h"
|
||||
}
|
||||
|
||||
void DummyPlayer_Init(Actor* actor, PlayState* play);
|
||||
void DummyPlayer_Update(Actor* actor, PlayState* play);
|
||||
void DummyPlayer_Draw(Actor* actor, PlayState* play);
|
||||
void DummyPlayer_Destroy(Actor* actor, PlayState* play);
|
||||
|
||||
typedef struct {
|
||||
uint32_t clientId;
|
||||
std::string name;
|
||||
Color_RGB8 color;
|
||||
std::string clientVersion;
|
||||
std::string teamId;
|
||||
bool online;
|
||||
bool self;
|
||||
uint32_t seed;
|
||||
bool isSaveLoaded;
|
||||
bool isGameComplete;
|
||||
s16 sceneNum;
|
||||
s32 entranceIndex;
|
||||
|
||||
// Only available in PLAYER_UPDATE packets
|
||||
s32 linkAge;
|
||||
PosRot posRot;
|
||||
Vec3s jointTable[24];
|
||||
Vec3s upperLimbRot;
|
||||
s8 currentBoots;
|
||||
s8 currentShield;
|
||||
s8 currentTunic;
|
||||
u32 stateFlags1;
|
||||
u32 stateFlags2;
|
||||
u8 buttonItem0;
|
||||
s8 itemAction;
|
||||
s8 heldItemAction;
|
||||
u8 modelGroup;
|
||||
s8 invincibilityTimer;
|
||||
s16 unk_862;
|
||||
s8 actionVar1;
|
||||
|
||||
// Ptr to the dummy player
|
||||
Player* player;
|
||||
} AnchorClient;
|
||||
|
||||
typedef struct {
|
||||
uint32_t ownerClientId;
|
||||
u8 pvpMode; // 0 = off, 1 = on, 2 = on with friendly fire
|
||||
u8 showLocationsMode; // 0 = none, 1 = team, 2 = all
|
||||
u8 teleportMode; // 0 = off, 1 = team, 2 = all
|
||||
} RoomState;
|
||||
|
||||
class Anchor : public Network {
|
||||
private:
|
||||
bool refreshingActors = false;
|
||||
bool justLoadedSave = false;
|
||||
bool isHandlingUpdateTeamState = false;
|
||||
uint32_t ownClientId;
|
||||
|
||||
nlohmann::json PrepClientState();
|
||||
nlohmann::json PrepRoomState();
|
||||
void RegisterHooks();
|
||||
void RefreshClientActors();
|
||||
void HandlePacket_AllClientState(nlohmann::json payload);
|
||||
void HandlePacket_ConsumeAdultTradeItem(nlohmann::json payload);
|
||||
void HandlePacket_DamagePlayer(nlohmann::json payload);
|
||||
void HandlePacket_DisableAnchor(nlohmann::json payload);
|
||||
void HandlePacket_EntranceDiscovered(nlohmann::json payload);
|
||||
void HandlePacket_GameComplete(nlohmann::json payload);
|
||||
void HandlePacket_GiveItem(nlohmann::json payload);
|
||||
void HandlePacket_PlayerSfx(nlohmann::json payload);
|
||||
void HandlePacket_PlayerUpdate(nlohmann::json payload);
|
||||
void HandlePacket_RequestTeamState(nlohmann::json payload);
|
||||
void HandlePacket_RequestTeleport(nlohmann::json payload);
|
||||
void HandlePacket_ServerMessage(nlohmann::json payload);
|
||||
void HandlePacket_SetCheckStatus(nlohmann::json payload);
|
||||
void HandlePacket_SetFlag(nlohmann::json payload);
|
||||
void HandlePacket_TeleportTo(nlohmann::json payload);
|
||||
void HandlePacket_UnsetFlag(nlohmann::json payload);
|
||||
void HandlePacket_UpdateBeansCount(nlohmann::json payload);
|
||||
void HandlePacket_UpdateClientState(nlohmann::json payload);
|
||||
void HandlePacket_UpdateDungeonItems(nlohmann::json payload);
|
||||
void HandlePacket_UpdateRoomState(nlohmann::json payload);
|
||||
void HandlePacket_UpdateTeamState(nlohmann::json payload);
|
||||
|
||||
public:
|
||||
inline static const std::string clientVersion = (char*)gBuildVersion;
|
||||
|
||||
// Packet types //
|
||||
inline static const std::string ALL_CLIENT_STATE = "ALL_CLIENT_STATE";
|
||||
inline static const std::string CONSUME_ADULT_TRADE_ITEM = "CONSUME_ADULT_TRADE_ITEM";
|
||||
inline static const std::string DAMAGE_PLAYER = "DAMAGE_PLAYER";
|
||||
inline static const std::string DISABLE_ANCHOR = "DISABLE_ANCHOR";
|
||||
inline static const std::string ENTRANCE_DISCOVERED = "ENTRANCE_DISCOVERED";
|
||||
inline static const std::string GAME_COMPLETE = "GAME_COMPLETE";
|
||||
inline static const std::string GIVE_ITEM = "GIVE_ITEM";
|
||||
inline static const std::string HANDSHAKE = "HANDSHAKE";
|
||||
inline static const std::string PLAYER_SFX = "PLAYER_SFX";
|
||||
inline static const std::string PLAYER_UPDATE = "PLAYER_UPDATE";
|
||||
inline static const std::string REQUEST_TEAM_STATE = "REQUEST_TEAM_STATE";
|
||||
inline static const std::string REQUEST_TELEPORT = "REQUEST_TELEPORT";
|
||||
inline static const std::string SERVER_MESSAGE = "SERVER_MESSAGE";
|
||||
inline static const std::string SET_CHECK_STATUS = "SET_CHECK_STATUS";
|
||||
inline static const std::string SET_FLAG = "SET_FLAG";
|
||||
inline static const std::string TELEPORT_TO = "TELEPORT_TO";
|
||||
inline static const std::string UNSET_FLAG = "UNSET_FLAG";
|
||||
inline static const std::string UPDATE_BEANS_COUNT = "UPDATE_BEANS_COUNT";
|
||||
inline static const std::string UPDATE_CLIENT_STATE = "UPDATE_CLIENT_STATE";
|
||||
inline static const std::string UPDATE_DUNGEON_ITEMS = "UPDATE_DUNGEON_ITEMS";
|
||||
inline static const std::string UPDATE_ROOM_STATE = "UPDATE_ROOM_STATE";
|
||||
inline static const std::string UPDATE_TEAM_STATE = "UPDATE_TEAM_STATE";
|
||||
|
||||
static Anchor* Instance;
|
||||
std::map<uint32_t, AnchorClient> clients;
|
||||
std::vector<uint32_t> actorIndexToClientId;
|
||||
RoomState roomState;
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
void OnIncomingJson(nlohmann::json payload);
|
||||
void OnConnected();
|
||||
void OnDisconnected();
|
||||
void DrawMenu();
|
||||
void SendJsonToRemote(nlohmann::json packet);
|
||||
bool IsSaveLoaded();
|
||||
|
||||
void SendPacket_ClearTeamState();
|
||||
void SendPacket_ConsumeAdultTradeItem(u8 itemId);
|
||||
void SendPacket_DamagePlayer(u32 clientId, u8 damageEffect, u8 damage);
|
||||
void SendPacket_EntranceDiscovered(u16 entranceIndex);
|
||||
void SendPacket_GameComplete();
|
||||
void SendPacket_GiveItem(u16 modId, s16 getItemId);
|
||||
void SendPacket_Handshake();
|
||||
void SendPacket_PlayerSfx(u16 sfxId);
|
||||
void SendPacket_PlayerUpdate();
|
||||
void SendPacket_RequestTeamState();
|
||||
void SendPacket_RequestTeleport(u32 clientId);
|
||||
void SendPacket_SetCheckStatus(RandomizerCheck rc);
|
||||
void SendPacket_SetFlag(s16 sceneNum, s16 flagType, s16 flag);
|
||||
void SendPacket_TeleportTo(u32 clientId);
|
||||
void SendPacket_UnsetFlag(s16 sceneNum, s16 flagType, s16 flag);
|
||||
void SendPacket_UpdateBeansCount();
|
||||
void SendPacket_UpdateClientState();
|
||||
void SendPacket_UpdateDungeonItems();
|
||||
void SendPacket_UpdateRoomState();
|
||||
void SendPacket_UpdateTeamState();
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
// Starting at 5 to continue from the last value in the PlayerDamageResponseType enum
|
||||
DUMMY_PLAYER_HIT_RESPONSE_STUN = 5,
|
||||
DUMMY_PLAYER_HIT_RESPONSE_FIRE,
|
||||
DUMMY_PLAYER_HIT_RESPONSE_NORMAL,
|
||||
} DummyPlayerDamageResponseType;
|
||||
|
||||
class AnchorRoomWindow : public Ship::GuiWindow {
|
||||
public:
|
||||
using GuiWindow::GuiWindow;
|
||||
|
||||
void InitElement() override {};
|
||||
void DrawElement() override {};
|
||||
void Draw() override;
|
||||
void UpdateElement() override {};
|
||||
};
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // NETWORK_ANCHOR_H
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
122
soh/soh/Network/Anchor/AnchorRoomWindow.cpp
Normal file
122
soh/soh/Network/Anchor/AnchorRoomWindow.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "Anchor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
#include "functions.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
void AnchorRoomWindow::Draw() {
|
||||
if (!Anchor::Instance->isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, CVarGetFloat(CVAR_SETTING("Notifications.BgOpacity"), 0.5f)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
|
||||
|
||||
auto vp = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowViewport(vp->ID);
|
||||
|
||||
ImGui::Begin("Anchor Room", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoNav |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoDocking |
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse |
|
||||
ImGuiWindowFlags_NoScrollbar
|
||||
);
|
||||
|
||||
// First build a list of teams
|
||||
std::set<std::string> teams;
|
||||
for (auto& [clientId, client] : Anchor::Instance->clients) {
|
||||
teams.insert(client.teamId);
|
||||
}
|
||||
|
||||
for (auto& team : teams) {
|
||||
if (teams.size() > 1) {
|
||||
ImGui::SeparatorText(team.c_str());
|
||||
}
|
||||
bool isOwnTeam = team == CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
for (auto& [clientId, client] : Anchor::Instance->clients) {
|
||||
if (client.teamId != team) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::PushID(clientId);
|
||||
|
||||
if (client.clientId == Anchor::Instance->roomState.ownerClientId) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "%s", ICON_FA_GAVEL);
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (client.self) {
|
||||
ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f), "%s", CVarGetString(CVAR_REMOTE_ANCHOR("Name"), ""));
|
||||
} else if (!client.online) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 1, 0.3f), "%s - offline", client.name.c_str());
|
||||
ImGui::PopID();
|
||||
continue;
|
||||
} else {
|
||||
ImGui::Text("%s", client.name.c_str());
|
||||
}
|
||||
|
||||
if (Anchor::Instance->roomState.showLocationsMode == 2 || (Anchor::Instance->roomState.showLocationsMode == 1 && isOwnTeam)) {
|
||||
if ((client.self ? Anchor::Instance->IsSaveLoaded() : client.isSaveLoaded)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1, 1, 1, 0.5f), "- %s", SohUtils::GetSceneName(client.self ? gPlayState->sceneNum : client.sceneNum).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Anchor::Instance->IsSaveLoaded() && !client.self && client.isSaveLoaded &&
|
||||
(Anchor::Instance->roomState.teleportMode == 2 || (Anchor::Instance->roomState.teleportMode == 1 && isOwnTeam))
|
||||
) {
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
if (ImGui::Button(ICON_FA_LOCATION_ARROW, ImVec2(15.0f, 15.0f))) {
|
||||
Anchor::Instance->SendPacket_RequestTeleport(client.clientId);
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
if (client.clientVersion != Anchor::clientVersion) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), ICON_FA_EXCLAMATION_TRIANGLE);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("Incompatible version! Will not work together!");
|
||||
ImGui::Text("Yours: %s", Anchor::clientVersion.c_str());
|
||||
ImGui::Text("Theirs: %s", client.clientVersion.c_str());
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
uint32_t seed = IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : 0;
|
||||
if (client.isSaveLoaded && Anchor::Instance->IsSaveLoaded() && client.seed != seed && client.online && !client.self) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), ICON_FA_EXCLAMATION_TRIANGLE);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("Seed mismatch! Continuing will break things!");
|
||||
ImGui::Text("Yours: %u", seed);
|
||||
ImGui::Text("Theirs: %u", client.seed);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
216
soh/soh/Network/Anchor/DummyPlayer.cpp
Normal file
216
soh/soh/Network/Anchor/DummyPlayer.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "Anchor.h"
|
||||
#include "soh/Enhancements/nametag.h"
|
||||
#include "soh/frame_interpolation.h"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
#include "variables.h"
|
||||
#include "functions.h"
|
||||
extern PlayState* gPlayState;
|
||||
|
||||
void Player_UseItem(PlayState* play, Player* player, s32 item);
|
||||
void Player_Draw(Actor* actor, PlayState* play);
|
||||
}
|
||||
|
||||
// Hijacking player->zTargetActiveTimer (unused s32 for the dummy) to store the clientId for convenience
|
||||
#define DUMMY_CLIENT_ID player->zTargetActiveTimer
|
||||
|
||||
static DamageTable DummyPlayerDamageTable = {
|
||||
/* Deku nut */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_STUN),
|
||||
/* Deku stick */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Slingshot */ DMG_ENTRY(1, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Explosive */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Boomerang */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_STUN),
|
||||
/* Normal arrow */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Hammer swing */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_KNOCKBACK_LARGE),
|
||||
/* Hookshot */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_STUN),
|
||||
/* Kokiri sword */ DMG_ENTRY(1, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Master sword */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Giant's Knife */ DMG_ENTRY(4, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Fire arrow */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_FIRE),
|
||||
/* Ice arrow */ DMG_ENTRY(4, PLAYER_HIT_RESPONSE_ICE_TRAP),
|
||||
/* Light arrow */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_ELECTRIC_SHOCK),
|
||||
/* Unk arrow 1 */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Unk arrow 2 */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Unk arrow 3 */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Fire magic */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_FIRE),
|
||||
/* Ice magic */ DMG_ENTRY(3, PLAYER_HIT_RESPONSE_ICE_TRAP),
|
||||
/* Light magic */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_ELECTRIC_SHOCK),
|
||||
/* Shield */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Mirror Ray */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Kokiri spin */ DMG_ENTRY(1, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Giant spin */ DMG_ENTRY(4, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Master spin */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Kokiri jump */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Giant jump */ DMG_ENTRY(8, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Master jump */ DMG_ENTRY(4, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
|
||||
/* Unknown 1 */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Unblockable */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
|
||||
/* Hammer jump */ DMG_ENTRY(4, PLAYER_HIT_RESPONSE_KNOCKBACK_LARGE),
|
||||
/* Unknown 2 */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
|
||||
};
|
||||
|
||||
void DummyPlayer_Init(Actor* actor, PlayState* play) {
|
||||
Player* player = (Player*)actor;
|
||||
|
||||
uint32_t clientId = Anchor::Instance->actorIndexToClientId[actor->params];
|
||||
DUMMY_CLIENT_ID = clientId;
|
||||
|
||||
if (!Anchor::Instance->clients.contains(DUMMY_CLIENT_ID)) {
|
||||
Actor_Kill(actor);
|
||||
return;
|
||||
}
|
||||
|
||||
AnchorClient& client = Anchor::Instance->clients[DUMMY_CLIENT_ID];
|
||||
|
||||
// Hack to account for usage of gSaveContext in Player_Init
|
||||
s32 originalAge = gSaveContext.linkAge;
|
||||
gSaveContext.linkAge = client.linkAge;
|
||||
|
||||
// #region modeled after EnTorch2_Init and Player_Init
|
||||
actor->room = -1;
|
||||
player->itemAction = player->heldItemAction = -1;
|
||||
player->heldItemId = ITEM_NONE;
|
||||
Player_UseItem(play, player, ITEM_NONE);
|
||||
Player_SetModelGroup(player, Player_ActionToModelGroup(player, player->heldItemAction));
|
||||
play->playerInit(player, play, gPlayerSkelHeaders[client.linkAge]);
|
||||
|
||||
play->func_11D54(player, play);
|
||||
// #endregion
|
||||
|
||||
player->cylinder.base.acFlags = AC_ON | AC_TYPE_PLAYER;
|
||||
player->cylinder.base.ocFlags2 = OC2_TYPE_1;
|
||||
player->cylinder.info.bumperFlags = BUMP_ON | BUMP_HOOKABLE | BUMP_NO_HITMARK;
|
||||
player->actor.flags |= ACTOR_FLAG_HOOKSHOT_PULLS_PLAYER;
|
||||
player->cylinder.dim.radius = 30;
|
||||
player->actor.colChkInfo.damageTable = &DummyPlayerDamageTable;
|
||||
|
||||
gSaveContext.linkAge = originalAge;
|
||||
|
||||
NameTag_RegisterForActorWithOptions(actor, client.name.c_str(), { .yOffset = 30 });
|
||||
}
|
||||
|
||||
void Math_Vec3s_Copy(Vec3s* dest, Vec3s* src) {
|
||||
dest->x = src->x;
|
||||
dest->y = src->y;
|
||||
dest->z = src->z;
|
||||
}
|
||||
|
||||
// Update the actor with new data from the client
|
||||
void DummyPlayer_Update(Actor* actor, PlayState* play) {
|
||||
Player* player = (Player*)actor;
|
||||
|
||||
if (!Anchor::Instance->clients.contains(DUMMY_CLIENT_ID)) {
|
||||
Actor_Kill(actor);
|
||||
return;
|
||||
}
|
||||
|
||||
AnchorClient& client = Anchor::Instance->clients[DUMMY_CLIENT_ID];
|
||||
|
||||
if (client.sceneNum != gPlayState->sceneNum || !client.online || !client.isSaveLoaded) {
|
||||
actor->world.pos.x = -9999.0f;
|
||||
actor->world.pos.y = -9999.0f;
|
||||
actor->world.pos.z = -9999.0f;
|
||||
actor->shape.shadowAlpha = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
actor->shape.shadowAlpha = 255;
|
||||
Math_Vec3s_Copy(&player->upperLimbRot, &client.upperLimbRot);
|
||||
Math_Vec3s_Copy(&actor->shape.rot, &client.posRot.rot);
|
||||
Math_Vec3f_Copy(&actor->world.pos, &client.posRot.pos);
|
||||
player->skelAnime.jointTable = client.jointTable;
|
||||
player->currentBoots = client.currentBoots;
|
||||
player->currentShield = client.currentShield;
|
||||
player->currentTunic = client.currentTunic;
|
||||
player->stateFlags1 = client.stateFlags1;
|
||||
player->stateFlags2 = client.stateFlags2;
|
||||
player->itemAction = client.itemAction;
|
||||
player->heldItemAction = client.heldItemAction;
|
||||
player->invincibilityTimer = client.invincibilityTimer;
|
||||
player->unk_862 = client.unk_862;
|
||||
player->av1.actionVar1 = client.actionVar1;
|
||||
|
||||
if (player->modelGroup != client.modelGroup) {
|
||||
// Hack to account for usage of gSaveContext
|
||||
s32 originalAge = gSaveContext.linkAge;
|
||||
gSaveContext.linkAge = client.linkAge;
|
||||
u8 originalButtonItem0 = gSaveContext.equips.buttonItems[0];
|
||||
gSaveContext.equips.buttonItems[0] = client.buttonItem0;
|
||||
Player_SetModelGroup(player, client.modelGroup);
|
||||
gSaveContext.linkAge = originalAge;
|
||||
gSaveContext.equips.buttonItems[0] = originalButtonItem0;
|
||||
}
|
||||
|
||||
if (
|
||||
Anchor::Instance->roomState.pvpMode == 0 ||
|
||||
(Anchor::Instance->roomState.pvpMode == 1 && client.teamId == CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player->cylinder.base.acFlags & AC_HIT && player->invincibilityTimer == 0) {
|
||||
Anchor::Instance->SendPacket_DamagePlayer(client.clientId, player->actor.colChkInfo.damageEffect, player->actor.colChkInfo.damage);
|
||||
if (player->actor.colChkInfo.damageEffect == DUMMY_PLAYER_HIT_RESPONSE_STUN) {
|
||||
Actor_SetColorFilter(&player->actor, 0, 0xFF, 0, 24);
|
||||
} else {
|
||||
player->invincibilityTimer = 20;
|
||||
}
|
||||
}
|
||||
|
||||
Collider_UpdateCylinder(&player->actor, &player->cylinder);
|
||||
|
||||
if (!(player->stateFlags2 & PLAYER_STATE2_FROZEN)) {
|
||||
if (!(player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_ON_HORSE))) {
|
||||
CollisionCheck_SetOC(play, &play->colChkCtx, &player->cylinder.base);
|
||||
}
|
||||
|
||||
if (!(player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_DAMAGED)) && (player->invincibilityTimer <= 0)) {
|
||||
CollisionCheck_SetAC(play, &play->colChkCtx, &player->cylinder.base);
|
||||
|
||||
if (player->invincibilityTimer < 0) {
|
||||
CollisionCheck_SetAT(play, &play->colChkCtx, &player->cylinder.base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS | PLAYER_STATE1_IN_CUTSCENE)) {
|
||||
player->actor.colChkInfo.mass = MASS_IMMOVABLE;
|
||||
} else {
|
||||
player->actor.colChkInfo.mass = 50;
|
||||
}
|
||||
|
||||
Collider_ResetCylinderAC(play, &player->cylinder.base);
|
||||
}
|
||||
|
||||
void DummyPlayer_Draw(Actor* actor, PlayState* play) {
|
||||
Player* player = (Player*)actor;
|
||||
|
||||
if (!Anchor::Instance->clients.contains(DUMMY_CLIENT_ID)) {
|
||||
Actor_Kill(actor);
|
||||
return;
|
||||
}
|
||||
|
||||
AnchorClient& client = Anchor::Instance->clients[DUMMY_CLIENT_ID];
|
||||
|
||||
if (client.sceneNum != gPlayState->sceneNum || !client.online || !client.isSaveLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hack to account for usage of gSaveContext in Player_Draw
|
||||
s32 originalAge = gSaveContext.linkAge;
|
||||
gSaveContext.linkAge = client.linkAge;
|
||||
u8 originalButtonItem0 = gSaveContext.equips.buttonItems[0];
|
||||
gSaveContext.equips.buttonItems[0] = client.buttonItem0;
|
||||
|
||||
Player_Draw((Actor*)player, play);
|
||||
gSaveContext.linkAge = originalAge;
|
||||
gSaveContext.equips.buttonItems[0] = originalButtonItem0;
|
||||
}
|
||||
|
||||
void DummyPlayer_Destroy(Actor* actor, PlayState* play) {
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
226
soh/soh/Network/Anchor/JsonConversions.hpp
Normal file
226
soh/soh/Network/Anchor/JsonConversions.hpp
Normal file
@ -0,0 +1,226 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#ifndef NETWORK_ANCHOR_JSON_CONVERSIONS_H
|
||||
#define NETWORK_ANCHOR_JSON_CONVERSIONS_H
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "Anchor.h"
|
||||
|
||||
extern "C" {
|
||||
#include "z64.h"
|
||||
}
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
inline void from_json(const json& j, Color_RGB8& color) {
|
||||
j.at("r").get_to(color.r);
|
||||
j.at("g").get_to(color.g);
|
||||
j.at("b").get_to(color.b);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const Color_RGB8& color) {
|
||||
j = json{
|
||||
{"r", color.r},
|
||||
{"g", color.g},
|
||||
{"b", color.b}
|
||||
};
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const Vec3f& vec) {
|
||||
j = json{
|
||||
{"x", vec.x},
|
||||
{"y", vec.y},
|
||||
{"z", vec.z}
|
||||
};
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const Vec3s& vec) {
|
||||
j = json{
|
||||
{"x", vec.x},
|
||||
{"y", vec.y},
|
||||
{"z", vec.z}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, Vec3f& vec) {
|
||||
j.at("x").get_to(vec.x);
|
||||
j.at("y").get_to(vec.y);
|
||||
j.at("z").get_to(vec.z);
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, Vec3s& vec) {
|
||||
j.at("x").get_to(vec.x);
|
||||
j.at("y").get_to(vec.y);
|
||||
j.at("z").get_to(vec.z);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const PosRot& posRot) {
|
||||
j = json{
|
||||
{"pos", posRot.pos},
|
||||
{"rot", posRot.rot}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, PosRot& posRot) {
|
||||
j.at("pos").get_to(posRot.pos);
|
||||
j.at("rot").get_to(posRot.rot);
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, AnchorClient& client) {
|
||||
j.contains("clientId") ? j.at("clientId").get_to(client.clientId) : client.clientId = 0;
|
||||
j.contains("name") ? j.at("name").get_to(client.name) : client.name = "???";
|
||||
j.contains("color") ? j.at("color").get_to(client.color) : client.color = { 255, 255, 255 };
|
||||
j.contains("clientVersion") ? j.at("clientVersion").get_to(client.clientVersion) : client.clientVersion = "???";
|
||||
j.contains("teamId") ? j.at("teamId").get_to(client.teamId) : client.teamId = "default";
|
||||
j.contains("online") ? j.at("online").get_to(client.online) : client.online = false;
|
||||
j.contains("seed") ? j.at("seed").get_to(client.seed) : client.seed = 0;
|
||||
j.contains("isSaveLoaded") ? j.at("isSaveLoaded").get_to(client.isSaveLoaded) : client.isSaveLoaded = false;
|
||||
j.contains("isGameComplete") ? j.at("isGameComplete").get_to(client.isGameComplete) : client.isGameComplete = false;
|
||||
j.contains("sceneNum") ? j.at("sceneNum").get_to(client.sceneNum) : client.sceneNum = SCENE_ID_MAX;
|
||||
j.contains("entranceIndex") ? j.at("entranceIndex").get_to(client.entranceIndex) : client.entranceIndex = 0;
|
||||
j.contains("self") ? j.at("self").get_to(client.self) : client.self = false;
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const Inventory& inventory) {
|
||||
j = json{
|
||||
{"items", inventory.items},
|
||||
{"ammo", inventory.ammo},
|
||||
{"equipment", inventory.equipment},
|
||||
{"upgrades", inventory.upgrades},
|
||||
{"questItems", inventory.questItems},
|
||||
{"dungeonItems", inventory.dungeonItems},
|
||||
{"dungeonKeys", inventory.dungeonKeys},
|
||||
{"defenseHearts", inventory.defenseHearts},
|
||||
{"gsTokens", inventory.gsTokens}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, Inventory& inventory) {
|
||||
j.at("items").get_to(inventory.items);
|
||||
j.at("ammo").get_to(inventory.ammo);
|
||||
j.at("equipment").get_to(inventory.equipment);
|
||||
j.at("upgrades").get_to(inventory.upgrades);
|
||||
j.at("questItems").get_to(inventory.questItems);
|
||||
j.at("dungeonItems").get_to(inventory.dungeonItems);
|
||||
j.at("dungeonKeys").get_to(inventory.dungeonKeys);
|
||||
j.at("defenseHearts").get_to(inventory.defenseHearts);
|
||||
j.at("gsTokens").get_to(inventory.gsTokens);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SohStats& sohStats) {
|
||||
j = json{
|
||||
{"entrancesDiscovered", sohStats.entrancesDiscovered},
|
||||
{"fileCreatedAt", sohStats.fileCreatedAt},
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SohStats& sohStats) {
|
||||
j.at("entrancesDiscovered").get_to(sohStats.entrancesDiscovered);
|
||||
j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const ShipRandomizerSaveContextData& shipRandomizerSaveContextData) {
|
||||
j = json{
|
||||
{"adultTradeItems", shipRandomizerSaveContextData.adultTradeItems},
|
||||
{"triforcePiecesCollected", shipRandomizerSaveContextData.triforcePiecesCollected},
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, ShipRandomizerSaveContextData& shipRandomizerSaveContextData) {
|
||||
j.at("adultTradeItems").get_to(shipRandomizerSaveContextData.adultTradeItems);
|
||||
j.at("triforcePiecesCollected").get_to(shipRandomizerSaveContextData.triforcePiecesCollected);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const ShipQuestSpecificSaveContextData& shipQuestSpecificSaveContextData) {
|
||||
j = json{
|
||||
{"randomizer", shipQuestSpecificSaveContextData.randomizer},
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, ShipQuestSpecificSaveContextData& shipQuestSpecificSaveContextData) {
|
||||
j.at("randomizer").get_to(shipQuestSpecificSaveContextData.randomizer);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const ShipQuestSaveContextData& shipQuestSaveContextData) {
|
||||
j = json{
|
||||
{"id", shipQuestSaveContextData.id},
|
||||
{"data", shipQuestSaveContextData.data},
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, ShipQuestSaveContextData& shipQuestSaveContextData) {
|
||||
j.at("id").get_to(shipQuestSaveContextData.id);
|
||||
j.at("data").get_to(shipQuestSaveContextData.data);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const ShipSaveContextData& shipSaveContextData) {
|
||||
j = json{
|
||||
{"stats", shipSaveContextData.stats},
|
||||
{"quest", shipSaveContextData.quest},
|
||||
{"randomizerInf", shipSaveContextData.randomizerInf},
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, ShipSaveContextData& shipSaveContextData) {
|
||||
j.at("stats").get_to(shipSaveContextData.stats);
|
||||
j.at("quest").get_to(shipSaveContextData.quest);
|
||||
j.at("randomizerInf").get_to(shipSaveContextData.randomizerInf);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SaveContext& saveContext) {
|
||||
std::vector<u32> sceneFlagsArray;
|
||||
for (const auto& sceneFlags : saveContext.sceneFlags) {
|
||||
sceneFlagsArray.push_back(sceneFlags.chest);
|
||||
sceneFlagsArray.push_back(sceneFlags.swch);
|
||||
sceneFlagsArray.push_back(sceneFlags.clear);
|
||||
sceneFlagsArray.push_back(sceneFlags.collect);
|
||||
}
|
||||
|
||||
j = json{
|
||||
{"healthCapacity", saveContext.healthCapacity},
|
||||
{"magicLevel", saveContext.magicLevel},
|
||||
{"magicCapacity", saveContext.magicCapacity},
|
||||
{"isMagicAcquired", saveContext.isMagicAcquired},
|
||||
{"isDoubleMagicAcquired", saveContext.isDoubleMagicAcquired},
|
||||
{"isDoubleDefenseAcquired", saveContext.isDoubleDefenseAcquired},
|
||||
{"bgsFlag", saveContext.bgsFlag},
|
||||
{"swordHealth", saveContext.swordHealth},
|
||||
{"sceneFlags", sceneFlagsArray},
|
||||
{"eventChkInf", saveContext.eventChkInf},
|
||||
{"itemGetInf", saveContext.itemGetInf},
|
||||
{"infTable", saveContext.infTable},
|
||||
{"gsFlags", saveContext.gsFlags},
|
||||
{"inventory", saveContext.inventory},
|
||||
{"ship", saveContext.ship},
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SaveContext& saveContext) {
|
||||
j.at("healthCapacity").get_to(saveContext.healthCapacity);
|
||||
j.at("magicLevel").get_to(saveContext.magicLevel);
|
||||
j.at("magicCapacity").get_to(saveContext.magicCapacity);
|
||||
j.at("isMagicAcquired").get_to(saveContext.isMagicAcquired);
|
||||
j.at("isDoubleMagicAcquired").get_to(saveContext.isDoubleMagicAcquired);
|
||||
j.at("isDoubleDefenseAcquired").get_to(saveContext.isDoubleDefenseAcquired);
|
||||
j.at("bgsFlag").get_to(saveContext.bgsFlag);
|
||||
j.at("swordHealth").get_to(saveContext.swordHealth);
|
||||
std::vector<u32> sceneFlagsArray;
|
||||
j.at("sceneFlags").get_to(sceneFlagsArray);
|
||||
for (int i = 0; i < 124; i++) {
|
||||
saveContext.sceneFlags[i].chest = sceneFlagsArray[i * 4];
|
||||
saveContext.sceneFlags[i].swch = sceneFlagsArray[i * 4 + 1];
|
||||
saveContext.sceneFlags[i].clear = sceneFlagsArray[i * 4 + 2];
|
||||
saveContext.sceneFlags[i].collect = sceneFlagsArray[i * 4 + 3];
|
||||
}
|
||||
j.at("eventChkInf").get_to(saveContext.eventChkInf);
|
||||
j.at("itemGetInf").get_to(saveContext.itemGetInf);
|
||||
j.at("infTable").get_to(saveContext.infTable);
|
||||
j.at("gsFlags").get_to(saveContext.gsFlags);
|
||||
j.at("inventory").get_to(saveContext.inventory);
|
||||
j.at("ship").get_to(saveContext.ship);
|
||||
}
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // NETWORK_ANCHOR_JSON_CONVERSIONS_H
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
74
soh/soh/Network/Anchor/Packets/AllClientState.cpp
Normal file
74
soh/soh/Network/Anchor/Packets/AllClientState.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
|
||||
/**
|
||||
* ALL_CLIENT_STATE
|
||||
*
|
||||
* Contains a list of all clients and their CLIENT_STATE currently connected to the server
|
||||
*
|
||||
* The server itself sends this packet to all clients when a client connects or disconnects
|
||||
*/
|
||||
|
||||
void Anchor::HandlePacket_AllClientState(nlohmann::json payload) {
|
||||
std::vector<AnchorClient> newClients = payload["state"].get<std::vector<AnchorClient>>();
|
||||
|
||||
// add new clients
|
||||
for (auto& client : newClients) {
|
||||
if (client.self) {
|
||||
ownClientId = client.clientId;
|
||||
CVarSetInteger(CVAR_REMOTE_ANCHOR("LastClientId"), ownClientId);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||
clients[client.clientId].self = true;
|
||||
} else {
|
||||
clients[client.clientId].self = false;
|
||||
if (clients.contains(client.clientId)) {
|
||||
if (clients[client.clientId].online != client.online) {
|
||||
Notification::Emit({
|
||||
.prefix = client.name,
|
||||
.message = client.online ? "Connected" : "Disconnected",
|
||||
});
|
||||
}
|
||||
} else if (client.online) {
|
||||
Notification::Emit({
|
||||
.prefix = client.name,
|
||||
.message = "Connected",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clients[client.clientId].clientId = client.clientId;
|
||||
clients[client.clientId].name = client.name;
|
||||
clients[client.clientId].color = client.color;
|
||||
clients[client.clientId].clientVersion = client.clientVersion;
|
||||
clients[client.clientId].teamId = client.teamId;
|
||||
clients[client.clientId].online = client.online;
|
||||
clients[client.clientId].seed = client.seed;
|
||||
clients[client.clientId].isSaveLoaded = client.isSaveLoaded;
|
||||
clients[client.clientId].isGameComplete = client.isGameComplete;
|
||||
clients[client.clientId].sceneNum = client.sceneNum;
|
||||
clients[client.clientId].entranceIndex = client.entranceIndex;
|
||||
}
|
||||
|
||||
// remove clients that are no longer in the list
|
||||
std::vector<uint32_t> clientsToRemove;
|
||||
for (auto& [clientId, client] : clients) {
|
||||
if (std::find_if(newClients.begin(), newClients.end(),
|
||||
[clientId](AnchorClient& c) { return c.clientId == clientId; }) == newClients.end()) {
|
||||
clientsToRemove.push_back(clientId);
|
||||
}
|
||||
}
|
||||
// (seperate loop to avoid iterator invalidation)
|
||||
for (auto& clientId : clientsToRemove) {
|
||||
clients.erase(clientId);
|
||||
}
|
||||
|
||||
RefreshClientActors();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
46
soh/soh/Network/Anchor/Packets/ConsumeAdultTradeItem.cpp
Normal file
46
soh/soh/Network/Anchor/Packets/ConsumeAdultTradeItem.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "functions.h"
|
||||
#include "soh/Enhancements/randomizer/adult_trade_shuffle.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* CONSUME_ADULT_TRADE_ITEM
|
||||
*
|
||||
* This is primarily to just get rid of used adult trade items to prevent confusion for other players.
|
||||
* Whatever flags/items are given from adult trade checks are synced by other packets.
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_ConsumeAdultTradeItem(u8 itemId) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = CONSUME_ADULT_TRADE_ITEM;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["itemId"] = itemId;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_ConsumeAdultTradeItem(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t itemId = payload["itemId"].get<uint8_t>();
|
||||
gSaveContext.ship.quest.data.randomizer.adultTradeItems &= ~ADULT_TRADE_FLAG(itemId);
|
||||
Inventory_ReplaceItem(gPlayState, itemId, Randomizer_GetNextAdultTradeItem());
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
62
soh/soh/Network/Anchor/Packets/DamagePlayer.cpp
Normal file
62
soh/soh/Network/Anchor/Packets/DamagePlayer.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
#include "functions.h"
|
||||
extern PlayState* gPlayState;
|
||||
void func_80838280(Player* player);
|
||||
}
|
||||
|
||||
/**
|
||||
* DAMAGE_PLAYER
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_DamagePlayer(u32 clientId, u8 damageEffect, u8 damage) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = DAMAGE_PLAYER;
|
||||
payload["targetClientId"] = clientId;
|
||||
payload["damageEffect"] = damageEffect;
|
||||
payload["damage"] = damage;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_DamagePlayer(nlohmann::json payload) {
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
if (!clients.contains(clientId) || clients[clientId].player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnchorClient& anchorClient = clients[clientId];
|
||||
Player* otherPlayer = anchorClient.player;
|
||||
Player* self = GET_PLAYER(gPlayState);
|
||||
|
||||
u8 damageEffect = payload["damageEffect"].get<u8>();
|
||||
u8 damage = payload["damage"].get<u8>();
|
||||
|
||||
self->actor.colChkInfo.damage = damage * 8; // Arbitrary number currently, need to fine tune
|
||||
|
||||
if (damageEffect == DUMMY_PLAYER_HIT_RESPONSE_FIRE) {
|
||||
for (int i = 0; i < ARRAY_COUNT(self->bodyFlameTimers); i++) {
|
||||
self->bodyFlameTimers[i] = Rand_S16Offset(0, 200);
|
||||
}
|
||||
self->bodyIsBurning = true;
|
||||
} else if (damageEffect == DUMMY_PLAYER_HIT_RESPONSE_STUN) {
|
||||
self->actor.freezeTimer = 20;
|
||||
Actor_SetColorFilter(&self->actor, 0, 0xFF, 0, 24);
|
||||
return;
|
||||
}
|
||||
|
||||
func_80837C0C(gPlayState, self, damageEffect, 4.0f, 5.0f, Actor_WorldYawTowardActor(&otherPlayer->actor, &self->actor), 20);
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
18
soh/soh/Network/Anchor/Packets/DisableAnchor.cpp
Normal file
18
soh/soh/Network/Anchor/Packets/DisableAnchor.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
/**
|
||||
* DISABLE_ANCHOR
|
||||
*
|
||||
* No current use, potentially will be used for a future feature.
|
||||
*/
|
||||
|
||||
void Anchor::HandlePacket_DisableAnchor(nlohmann::json payload) {
|
||||
Disable();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
41
soh/soh/Network/Anchor/Packets/EntranceDiscovered.cpp
Normal file
41
soh/soh/Network/Anchor/Packets/EntranceDiscovered.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
static bool isResultOfHandling = false;
|
||||
|
||||
/**
|
||||
* ENTRANCE_DISCOVERED
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_EntranceDiscovered(u16 entranceIndex) {
|
||||
if (!IsSaveLoaded() || isResultOfHandling) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = ENTRANCE_DISCOVERED;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["entranceIndex"] = entranceIndex;
|
||||
payload["quiet"] = true;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_EntranceDiscovered(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isResultOfHandling = true;
|
||||
u16 entranceIndex = payload["entranceIndex"].get<u16>();
|
||||
Entrance_SetEntranceDiscovered(entranceIndex, 1);
|
||||
isResultOfHandling = false;
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
48
soh/soh/Network/Anchor/Packets/GameComplete.cpp
Normal file
48
soh/soh/Network/Anchor/Packets/GameComplete.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
#include "soh/Enhancements/randomizer/3drando/random.hpp"
|
||||
|
||||
const std::string gameCompleteMessages[] = {
|
||||
"killed Ganon",
|
||||
"saved Zelda",
|
||||
"proved their Courage",
|
||||
"collected the Triforce",
|
||||
"is the Hero of Time",
|
||||
"proved Mido wrong",
|
||||
};
|
||||
|
||||
/**
|
||||
* GAME_COMPLETE
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_GameComplete() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = GAME_COMPLETE;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_GameComplete(nlohmann::json payload) {
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
if (!clients.contains(clientId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnchorClient& anchorClient = clients[clientId];
|
||||
anchorClient.isGameComplete = true;
|
||||
Notification::Emit({
|
||||
.prefix = anchorClient.name,
|
||||
.message = RandomElement(gameCompleteMessages),
|
||||
});
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
104
soh/soh/Network/Anchor/Packets/GiveItem.cpp
Normal file
104
soh/soh/Network/Anchor/Packets/GiveItem.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer.h"
|
||||
#include "soh/SohGui/ImGuiUtils.h"
|
||||
#include "soh/Enhancements/item-tables/ItemTableManager.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "functions.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* GIVE_ITEM
|
||||
*/
|
||||
|
||||
static bool gettingItem;
|
||||
static uint8_t incomingIceTraps;
|
||||
|
||||
void Anchor::SendPacket_GiveItem(u16 modId, s16 getItemId) {
|
||||
if (!IsSaveLoaded() || gettingItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modId == MOD_RANDOMIZER && getItemId == RG_ICE_TRAP && incomingIceTraps > 0) {
|
||||
incomingIceTraps = MAX(incomingIceTraps - 1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = GIVE_ITEM;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["modId"] = modId;
|
||||
payload["getItemId"] = getItemId;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_GiveItem(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
AnchorClient& client = clients[clientId];
|
||||
|
||||
GetItemEntry getItemEntry;
|
||||
if (payload["modId"].get<u16>() == MOD_NONE) {
|
||||
getItemEntry = ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, payload["getItemId"].get<s16>());
|
||||
} else {
|
||||
getItemEntry = Rando::StaticData::RetrieveItem(payload["getItemId"].get<RandomizerGet>()).GetGIEntry_Copy();
|
||||
}
|
||||
|
||||
gettingItem = true;
|
||||
if (getItemEntry.modIndex == MOD_NONE) {
|
||||
if (getItemEntry.getItemId == GI_SWORD_BGS) {
|
||||
gSaveContext.bgsFlag = true;
|
||||
}
|
||||
Item_Give(gPlayState, getItemEntry.itemId);
|
||||
} else if (getItemEntry.modIndex == MOD_RANDOMIZER) {
|
||||
if (getItemEntry.getItemId == RG_ICE_TRAP) {
|
||||
gSaveContext.ship.pendingIceTrapCount++;
|
||||
incomingIceTraps++;
|
||||
} else {
|
||||
Randomizer_Item_Give(gPlayState, getItemEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle if the player gets a 4th heart piece (usually handled in z_message)
|
||||
s32 heartPieces = (s32)(gSaveContext.inventory.questItems & 0xF0000000) >> (QUEST_HEART_PIECE + 4);
|
||||
if (heartPieces >= 4) {
|
||||
gSaveContext.inventory.questItems &= ~0xF0000000;
|
||||
gSaveContext.inventory.questItems += (heartPieces % 4) << (QUEST_HEART_PIECE + 4);
|
||||
gSaveContext.healthCapacity += 0x10 * (heartPieces / 4);
|
||||
gSaveContext.health += 0x10 * (heartPieces / 4);
|
||||
gSaveContext.healthAccumulator = 0x140;
|
||||
}
|
||||
gettingItem = false;
|
||||
|
||||
if (getItemEntry.getItemCategory != ITEM_CATEGORY_JUNK) {
|
||||
if (getItemEntry.modIndex == MOD_NONE) {
|
||||
Notification::Emit({
|
||||
.itemIcon = GetTextureForItemId(getItemEntry.itemId),
|
||||
.prefix = client.name,
|
||||
.message = "found",
|
||||
.suffix = SohUtils::GetItemName(getItemEntry.itemId),
|
||||
});
|
||||
} else if (getItemEntry.modIndex == MOD_RANDOMIZER) {
|
||||
Notification::Emit({
|
||||
.prefix = client.name,
|
||||
.message = "found",
|
||||
.suffix = Rando::StaticData::RetrieveItem((RandomizerGet)getItemEntry.getItemId).GetName().english,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
26
soh/soh/Network/Anchor/Packets/Handshake.cpp
Normal file
26
soh/soh/Network/Anchor/Packets/Handshake.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
/**
|
||||
* HANDSHAKE
|
||||
*
|
||||
* Sent by the client to the server when it first connects to the server, sends over both the local room settings
|
||||
* in case the room needs to be created, along with the current client state
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_Handshake() {
|
||||
nlohmann::json payload;
|
||||
payload["type"] = HANDSHAKE;
|
||||
payload["roomId"] = CVarGetString(CVAR_REMOTE_ANCHOR("RoomId"), "");
|
||||
payload["roomState"] = PrepRoomState();
|
||||
payload["clientState"] = PrepClientState();
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
51
soh/soh/Network/Anchor/Packets/PlayerSfx.cpp
Normal file
51
soh/soh/Network/Anchor/Packets/PlayerSfx.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
#include "functions.h"
|
||||
#include "variables.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* PLAYER_SFX
|
||||
*
|
||||
* Sound effects, only sent to other clients in the same scene as the player
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_PlayerSfx(u16 sfxId) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
|
||||
payload["type"] = PLAYER_SFX;
|
||||
payload["sfxId"] = sfxId;
|
||||
payload["quiet"] = true;
|
||||
|
||||
for (auto& [clientId, client] : clients) {
|
||||
if (client.sceneNum == gPlayState->sceneNum && client.online && client.isSaveLoaded && !client.self) {
|
||||
payload["targetClientId"] = clientId;
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_PlayerSfx(nlohmann::json payload) {
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
u16 sfxId = payload["sfxId"].get<u16>();
|
||||
|
||||
if (!clients.contains(clientId) || !clients[clientId].player) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player_PlaySfx((Actor*)clients[clientId].player, sfxId);
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
120
soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp
Normal file
120
soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
#include "variables.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* PLAYER_UPDATE
|
||||
*
|
||||
* Contains real-time data necessary to update other clients in the same scene as the player
|
||||
*
|
||||
* Sent every frame to other clients within the same scene
|
||||
*
|
||||
* Note: This packet is sent _a lot_, so please do not include any unnecessary data in it
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_PlayerUpdate() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t currentPlayerCount = 0;
|
||||
for (auto& [clientId, client] : clients) {
|
||||
if (client.sceneNum == gPlayState->sceneNum && client.online && client.isSaveLoaded && !client.self) {
|
||||
currentPlayerCount++;
|
||||
}
|
||||
}
|
||||
if (currentPlayerCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
nlohmann::json payload;
|
||||
|
||||
payload["type"] = PLAYER_UPDATE;
|
||||
payload["sceneNum"] = gPlayState->sceneNum;
|
||||
payload["entranceIndex"] = gSaveContext.entranceIndex;
|
||||
payload["linkAge"] = gSaveContext.linkAge;
|
||||
payload["posRot"]["pos"] = player->actor.world.pos;
|
||||
payload["posRot"]["rot"] = player->actor.shape.rot;
|
||||
std::vector<int> jointArray;
|
||||
for (const auto& joint : player->jointTable) {
|
||||
jointArray.push_back(joint.x);
|
||||
jointArray.push_back(joint.y);
|
||||
jointArray.push_back(joint.z);
|
||||
}
|
||||
payload["jointTable"] = jointArray;
|
||||
payload["upperLimbRot"] = player->upperLimbRot;
|
||||
payload["currentBoots"] = player->currentBoots;
|
||||
payload["currentShield"] = player->currentShield;
|
||||
payload["currentTunic"] = player->currentTunic;
|
||||
payload["stateFlags1"] = player->stateFlags1;
|
||||
payload["stateFlags2"] = player->stateFlags2;
|
||||
payload["buttonItem0"] = gSaveContext.equips.buttonItems[0];
|
||||
payload["itemAction"] = player->itemAction;
|
||||
payload["heldItemAction"] = player->heldItemAction;
|
||||
payload["modelGroup"] = player->modelGroup;
|
||||
payload["invincibilityTimer"] = player->invincibilityTimer;
|
||||
payload["unk_862"] = player->unk_862;
|
||||
payload["actionVar1"] = player->av1.actionVar1;
|
||||
payload["quiet"] = true;
|
||||
|
||||
for (auto& [clientId, client] : clients) {
|
||||
if (client.sceneNum == gPlayState->sceneNum && client.online && client.isSaveLoaded && !client.self) {
|
||||
payload["targetClientId"] = clientId;
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_PlayerUpdate(nlohmann::json payload) {
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
|
||||
bool shouldRefreshActors = false;
|
||||
|
||||
if (clients.contains(clientId)) {
|
||||
auto& client = clients[clientId];
|
||||
|
||||
if (client.linkAge != payload["linkAge"].get<s32>()) {
|
||||
shouldRefreshActors = true;
|
||||
}
|
||||
|
||||
client.sceneNum = payload["sceneNum"].get<s16>();
|
||||
client.entranceIndex = payload["entranceIndex"].get<s32>();
|
||||
client.linkAge = payload["linkAge"].get<s32>();
|
||||
client.posRot = payload["posRot"].get<PosRot>();
|
||||
std::vector<int> jointArray = payload["jointTable"];
|
||||
for (int i = 0; i < 24; i++) {
|
||||
client.jointTable[i].x = jointArray[i * 3];
|
||||
client.jointTable[i].y = jointArray[i * 3 + 1];
|
||||
client.jointTable[i].z = jointArray[i * 3 + 2];
|
||||
}
|
||||
client.upperLimbRot = payload["upperLimbRot"].get<Vec3s>();
|
||||
client.currentBoots = payload["currentBoots"].get<s8>();
|
||||
client.currentShield = payload["currentShield"].get<s8>();
|
||||
client.currentTunic = payload["currentTunic"].get<s8>();
|
||||
client.stateFlags1 = payload["stateFlags1"].get<u32>();
|
||||
client.stateFlags2 = payload["stateFlags2"].get<u32>();
|
||||
client.buttonItem0 = payload["buttonItem0"].get<u8>();
|
||||
client.itemAction = payload["itemAction"].get<s8>();
|
||||
client.heldItemAction = payload["heldItemAction"].get<s8>();
|
||||
client.modelGroup = payload["modelGroup"].get<u8>();
|
||||
client.invincibilityTimer = payload["invincibilityTimer"].get<s8>();
|
||||
client.unk_862 = payload["unk_862"].get<s16>();
|
||||
client.actionVar1 = payload["actionVar1"].get<s8>();
|
||||
}
|
||||
|
||||
if (shouldRefreshActors) {
|
||||
RefreshClientActors();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
41
soh/soh/Network/Anchor/Packets/RequestTeamState.cpp
Normal file
41
soh/soh/Network/Anchor/Packets/RequestTeamState.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
/**
|
||||
* REQUEST_TEAM_STATE
|
||||
*
|
||||
* Requests team state from the server, which will pass on the request to any connected teammates, or send the last known
|
||||
* state if no teammates are connected.
|
||||
*
|
||||
* This fires when loading into a file while Anchor is connected, or when Anchor is connected while a file is already
|
||||
* loaded
|
||||
*
|
||||
* Note: This can additionally be fired with a button in the menus to fix any desyncs that may have occurred in the save
|
||||
* state
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_RequestTeamState() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = REQUEST_TEAM_STATE;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_RequestTeamState(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendPacket_UpdateTeamState();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
36
soh/soh/Network/Anchor/Packets/RequestTeleport.cpp
Normal file
36
soh/soh/Network/Anchor/Packets/RequestTeleport.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
/**
|
||||
* REQUEST_TELEPORT
|
||||
*
|
||||
* Because we don't have all the necessary information to directly teleport to a player, we emit a request,
|
||||
* in which they will respond with a TELEPORT_TO packet, with the necessary information.
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_RequestTeleport(uint32_t clientId) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = REQUEST_TELEPORT;
|
||||
payload["targetClientId"] = clientId;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_RequestTeleport(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
SendPacket_TeleportTo(clientId);
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
21
soh/soh/Network/Anchor/Packets/ServerMessage.cpp
Normal file
21
soh/soh/Network/Anchor/Packets/ServerMessage.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
|
||||
/**
|
||||
* SERVER_MESSAGE
|
||||
*/
|
||||
|
||||
void Anchor::HandlePacket_ServerMessage(nlohmann::json payload) {
|
||||
Notification::Emit({
|
||||
.prefix = "Server:",
|
||||
.prefixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f),
|
||||
.message = payload["message"].get<std::string>(),
|
||||
});
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
59
soh/soh/Network/Anchor/Packets/SetCheckStatus.cpp
Normal file
59
soh/soh/Network/Anchor/Packets/SetCheckStatus.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
static bool isResultOfHandling = false;
|
||||
|
||||
/**
|
||||
* SET_CHECK_STATUS
|
||||
*
|
||||
* Fired when a check status is updated or skipped
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_SetCheckStatus(RandomizerCheck rc) {
|
||||
if (!IsSaveLoaded() || isResultOfHandling) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto randoContext = Rando::Context::GetInstance();
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = SET_CHECK_STATUS;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["rc"] = rc;
|
||||
payload["status"] = randoContext->GetItemLocation(rc)->GetCheckStatus();
|
||||
payload["skipped"] = randoContext->GetItemLocation(rc)->GetIsSkipped();
|
||||
payload["quiet"] = true;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_SetCheckStatus(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto randoContext = Rando::Context::GetInstance();
|
||||
|
||||
RandomizerCheck rc = payload["rc"].get<RandomizerCheck>();
|
||||
RandomizerCheckStatus status = payload["status"].get<RandomizerCheckStatus>();
|
||||
bool skipped = payload["skipped"].get<bool>();
|
||||
|
||||
isResultOfHandling = true;
|
||||
|
||||
if (randoContext->GetItemLocation(rc)->GetCheckStatus() != status) {
|
||||
randoContext->GetItemLocation(rc)->SetCheckStatus(status);
|
||||
}
|
||||
if (randoContext->GetItemLocation(rc)->GetIsSkipped() != skipped) {
|
||||
randoContext->GetItemLocation(rc)->SetIsSkipped(skipped);
|
||||
}
|
||||
|
||||
isResultOfHandling = false;
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
54
soh/soh/Network/Anchor/Packets/SetFlag.cpp
Normal file
54
soh/soh/Network/Anchor/Packets/SetFlag.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
/**
|
||||
* SET_FLAG
|
||||
*
|
||||
* Fired when a flag is set in the save context
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_SetFlag(s16 sceneNum, s16 flagType, s16 flag) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = SET_FLAG;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["sceneNum"] = sceneNum;
|
||||
payload["flagType"] = flagType;
|
||||
payload["flag"] = flag;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_SetFlag(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
s16 sceneNum = payload["sceneNum"].get<s16>();
|
||||
s16 flagType = payload["flagType"].get<s16>();
|
||||
s16 flag = payload["flag"].get<s16>();
|
||||
|
||||
if (sceneNum == SCENE_ID_MAX) {
|
||||
auto effect = new GameInteractionEffect::SetFlag();
|
||||
effect->parameters[0] = payload["flagType"].get<int16_t>();
|
||||
effect->parameters[1] = payload["flag"].get<int16_t>();
|
||||
effect->Apply();
|
||||
} else {
|
||||
auto effect = new GameInteractionEffect::SetSceneFlag();
|
||||
effect->parameters[0] = payload["sceneNum"].get<int16_t>();
|
||||
effect->parameters[1] = payload["flagType"].get<int16_t>();
|
||||
effect->parameters[2] = payload["flag"].get<int16_t>();
|
||||
effect->Apply();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
63
soh/soh/Network/Anchor/Packets/TeleportTo.cpp
Normal file
63
soh/soh/Network/Anchor/Packets/TeleportTo.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* TELEPORT_TO
|
||||
*
|
||||
* See REQUEST_TELEPORT for more information, this is the second part of the process.
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_TeleportTo(uint32_t clientId) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = TELEPORT_TO;
|
||||
payload["targetClientId"] = clientId;
|
||||
payload["entranceIndex"] = gSaveContext.entranceIndex;
|
||||
payload["roomIndex"] = gPlayState->roomCtx.curRoom.num;
|
||||
payload["posRot"] = player->actor.world;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_TeleportTo(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
s32 entranceIndex = payload["entranceIndex"].get<s32>();
|
||||
s8 roomIndex = payload["roomIndex"].get<s8>();
|
||||
PosRot posRot = payload["posRot"].get<PosRot>();
|
||||
|
||||
gPlayState->nextEntranceIndex = entranceIndex;
|
||||
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
|
||||
gPlayState->transitionType = TRANS_TYPE_INSTANT;
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].entranceIndex = entranceIndex;
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = roomIndex;
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = posRot.pos;
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = posRot.rot.y;
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0xDFF;
|
||||
gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST;
|
||||
gSaveContext.respawnFlag = 1;
|
||||
static HOOK_ID hookId = 0;
|
||||
hookId = REGISTER_VB_SHOULD(VB_INFLICT_VOID_DAMAGE, {
|
||||
*should = false;
|
||||
GameInteractor::Instance->UnregisterGameHookForID<GameInteractor::OnVanillaBehavior>(hookId);
|
||||
});
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
55
soh/soh/Network/Anchor/Packets/UnsetFlag.cpp
Normal file
55
soh/soh/Network/Anchor/Packets/UnsetFlag.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
/**
|
||||
* UNSET_FLAG
|
||||
*
|
||||
* Fired when a flag is unset in the save context
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_UnsetFlag(s16 sceneNum, s16 flagType, s16 flag) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = UNSET_FLAG;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["sceneNum"] = sceneNum;
|
||||
payload["flagType"] = flagType;
|
||||
payload["flag"] = flag;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_UnsetFlag(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
s16 sceneNum = payload["sceneNum"].get<s16>();
|
||||
s16 flagType = payload["flagType"].get<s16>();
|
||||
s16 flag = payload["flag"].get<s16>();
|
||||
|
||||
if (sceneNum == SCENE_ID_MAX) {
|
||||
auto effect = new GameInteractionEffect::UnsetFlag();
|
||||
effect->parameters[0] = payload["flagType"].get<int16_t>();
|
||||
effect->parameters[1] = payload["flag"].get<int16_t>();
|
||||
effect->Apply();
|
||||
} else {
|
||||
auto effect = new GameInteractionEffect::UnsetSceneFlag();
|
||||
effect->parameters[0] = payload["sceneNum"].get<int16_t>();
|
||||
effect->parameters[1] = payload["flagType"].get<int16_t>();
|
||||
effect->parameters[2] = payload["flag"].get<int16_t>();
|
||||
effect->Apply();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
43
soh/soh/Network/Anchor/Packets/UpdateBeansCount.cpp
Normal file
43
soh/soh/Network/Anchor/Packets/UpdateBeansCount.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE_BEANS_COUNT
|
||||
*
|
||||
* Keeps the client's bean count in sync as they buy/use them
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_UpdateBeansCount() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = UPDATE_BEANS_COUNT;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["amount"] = AMMO(ITEM_BEAN);
|
||||
payload["amountBought"] = BEANS_BOUGHT;
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_UpdateBeansCount(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AMMO(ITEM_BEAN) = payload["amount"].get<s8>();
|
||||
BEANS_BOUGHT = payload["amountBought"].get<s8>();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
77
soh/soh/Network/Anchor/Packets/UpdateClientState.cpp
Normal file
77
soh/soh/Network/Anchor/Packets/UpdateClientState.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE_CLIENT_STATE
|
||||
*
|
||||
* Contains a small subset of data that is cached on the server and important for the client to know for various reasons
|
||||
*
|
||||
* Sent on various events, such as changing scenes, soft resetting, finishing the game, opening file select, etc.
|
||||
*
|
||||
* Note: This packet should be cross version compatible, so if you add anything here don't assume all clients will be
|
||||
* providing it, consider doing a `contains` check before accessing any version specific data
|
||||
*/
|
||||
|
||||
nlohmann::json Anchor::PrepClientState() {
|
||||
nlohmann::json payload;
|
||||
payload["name"] = CVarGetString(CVAR_REMOTE_ANCHOR("Name"), "");
|
||||
payload["color"] = CVarGetColor24(CVAR_REMOTE_ANCHOR("Color"), { 100, 255, 100 });
|
||||
payload["clientVersion"] = clientVersion;
|
||||
payload["teamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["online"] = true;
|
||||
|
||||
if (IsSaveLoaded()) {
|
||||
payload["seed"] = IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : 0;
|
||||
payload["isSaveLoaded"] = true;
|
||||
payload["isGameComplete"] = gSaveContext.ship.stats.gameComplete;
|
||||
payload["sceneNum"] = gPlayState->sceneNum;
|
||||
payload["entranceIndex"] = gSaveContext.entranceIndex;
|
||||
} else {
|
||||
payload["seed"] = 0;
|
||||
payload["isSaveLoaded"] = false;
|
||||
payload["isGameComplete"] = false;
|
||||
payload["sceneNum"] = SCENE_ID_MAX;
|
||||
payload["entranceIndex"] = 0x00;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
void Anchor::SendPacket_UpdateClientState() {
|
||||
nlohmann::json payload;
|
||||
payload["type"] = UPDATE_CLIENT_STATE;
|
||||
payload["state"] = PrepClientState();
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_UpdateClientState(nlohmann::json payload) {
|
||||
uint32_t clientId = payload["clientId"].get<uint32_t>();
|
||||
|
||||
if (clients.contains(clientId)) {
|
||||
AnchorClient client = payload["state"].get<AnchorClient>();
|
||||
clients[clientId].clientId = clientId;
|
||||
clients[clientId].name = client.name;
|
||||
clients[clientId].color = client.color;
|
||||
clients[clientId].clientVersion = client.clientVersion;
|
||||
clients[clientId].teamId = client.teamId;
|
||||
clients[clientId].online = client.online;
|
||||
clients[clientId].seed = client.seed;
|
||||
clients[clientId].isSaveLoaded = client.isSaveLoaded;
|
||||
clients[clientId].isGameComplete = client.isGameComplete;
|
||||
clients[clientId].sceneNum = client.sceneNum;
|
||||
clients[clientId].entranceIndex = client.entranceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
42
soh/soh/Network/Anchor/Packets/UpdateDungeonItems.cpp
Normal file
42
soh/soh/Network/Anchor/Packets/UpdateDungeonItems.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
/**
|
||||
* UPDATE_DUNGEON_ITEMS
|
||||
*
|
||||
* This is for 2 things, first is updating the dungeon items in vanilla saves, and second is
|
||||
* for ensuring the amount of keys used is synced as players are using them.
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_UpdateDungeonItems() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["type"] = UPDATE_DUNGEON_ITEMS;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["addToQueue"] = true;
|
||||
payload["mapIndex"] = gSaveContext.mapIndex;
|
||||
payload["dungeonItems"] = gSaveContext.inventory.dungeonItems[gSaveContext.mapIndex];
|
||||
payload["dungeonKeys"] = gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex];
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_UpdateDungeonItems(nlohmann::json payload) {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
u16 mapIndex = payload["mapIndex"].get<u16>();
|
||||
gSaveContext.inventory.dungeonItems[mapIndex] = payload["dungeonItems"].get<u8>();
|
||||
gSaveContext.inventory.dungeonKeys[mapIndex] = payload["dungeonKeys"].get<s8>();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
47
soh/soh/Network/Anchor/Packets/UpdateRoomState.cpp
Normal file
47
soh/soh/Network/Anchor/Packets/UpdateRoomState.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE_ROOM_STATE
|
||||
*/
|
||||
|
||||
nlohmann::json Anchor::PrepRoomState() {
|
||||
nlohmann::json payload;
|
||||
payload["ownerClientId"] = ownClientId;
|
||||
payload["pvpMode"] = CVarGetInteger(CVAR_REMOTE_ANCHOR("RoomSettings.PvpMode"), 1);
|
||||
payload["showLocationsMode"] = CVarGetInteger(CVAR_REMOTE_ANCHOR("RoomSettings.ShowLocationsMode"), 1);
|
||||
payload["teleportMode"] = CVarGetInteger(CVAR_REMOTE_ANCHOR("RoomSettings.TeleportMode"), 1);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
void Anchor::SendPacket_UpdateRoomState() {
|
||||
nlohmann::json payload;
|
||||
payload["type"] = UPDATE_ROOM_STATE;
|
||||
payload["state"] = PrepRoomState();
|
||||
|
||||
Network::SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_UpdateRoomState(nlohmann::json payload) {
|
||||
if (!payload.contains("state")) {
|
||||
return;
|
||||
}
|
||||
|
||||
roomState.ownerClientId = payload["state"]["ownerClientId"].get<uint32_t>();
|
||||
roomState.pvpMode = payload["state"]["pvpMode"].get<u8>();
|
||||
roomState.showLocationsMode = payload["state"]["showLocationsMode"].get<u8>();
|
||||
roomState.teleportMode = payload["state"]["teleportMode"].get<u8>();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
266
soh/soh/Network/Anchor/Packets/UpdateTeamState.cpp
Normal file
266
soh/soh/Network/Anchor/Packets/UpdateTeamState.cpp
Normal file
@ -0,0 +1,266 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include "soh/Network/Anchor/JsonConversions.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/randomizer/entrance.h"
|
||||
#include "soh/Enhancements/randomizer/dungeon.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE_TEAM_STATE
|
||||
*
|
||||
* Pushes the current save state to the server for other teammates to use.
|
||||
*
|
||||
* Fires when the server passes on a REQUEST_TEAM_STATE packet, or when this client saves the game
|
||||
*
|
||||
* When sending this packet we will assume that the team queue has been emptied for this client, so the queue
|
||||
* stored in the server will be cleared.
|
||||
*
|
||||
* When receiving this packet, if there is items in the team queue, we will play them back in order.
|
||||
*/
|
||||
|
||||
void Anchor::SendPacket_UpdateTeamState() {
|
||||
if (!IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
json payload;
|
||||
payload["type"] = UPDATE_TEAM_STATE;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
|
||||
// Assume the team queue has been emptied, so clear it
|
||||
payload["queue"] = json::array();
|
||||
|
||||
payload["state"] = gSaveContext;
|
||||
// manually update current scene flags
|
||||
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4] = gPlayState->actorCtx.flags.chest;
|
||||
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4 + 1] = gPlayState->actorCtx.flags.swch;
|
||||
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4 + 2] = gPlayState->actorCtx.flags.clear;
|
||||
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4 + 3] = gPlayState->actorCtx.flags.collect;
|
||||
|
||||
// The commented out code below is an attempt at sending the entire randomizer seed over, in hopes that a player doesn't have to generate the seed themselves
|
||||
// Currently it doesn't work :)
|
||||
if (IS_RANDO) {
|
||||
auto randoContext = Rando::Context::GetInstance();
|
||||
|
||||
payload["state"]["rando"] = json::object();
|
||||
payload["state"]["rando"]["itemLocations"] = json::array();
|
||||
for (int i = 0; i < RC_MAX; i++) {
|
||||
payload["state"]["rando"]["itemLocations"][i] = json::array();
|
||||
// payload["state"]["rando"]["itemLocations"][i]["rgID"] = randoContext->GetItemLocation(i)->GetPlacedRandomizerGet();
|
||||
payload["state"]["rando"]["itemLocations"][i][0] = randoContext->GetItemLocation(i)->GetCheckStatus();
|
||||
payload["state"]["rando"]["itemLocations"][i][1] = (u8)randoContext->GetItemLocation(i)->GetIsSkipped();
|
||||
|
||||
// if (randoContext->GetItemLocation(i)->GetPlacedRandomizerGet() == RG_ICE_TRAP) {
|
||||
// payload["state"]["rando"]["itemLocations"][i]["fakeRgID"] = randoContext->GetItemOverride(i).LooksLike();
|
||||
// payload["state"]["rando"]["itemLocations"][i]["trickName"] = json::object();
|
||||
// payload["state"]["rando"]["itemLocations"][i]["trickName"]["english"] = randoContext->GetItemOverride(i).GetTrickName().GetEnglish();
|
||||
// payload["state"]["rando"]["itemLocations"][i]["trickName"]["french"] = randoContext->GetItemOverride(i).GetTrickName().GetFrench();
|
||||
// }
|
||||
// if (randoContext->GetItemLocation(i)->HasCustomPrice()) {
|
||||
// payload["state"]["rando"]["itemLocations"][i]["price"] = randoContext->GetItemLocation(i)->GetPrice();
|
||||
// }
|
||||
}
|
||||
|
||||
// auto entranceCtx = randoContext->GetEntranceShuffler();
|
||||
// for (int i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
|
||||
// payload["state"]["rando"]["entrances"][i] = json::object();
|
||||
// payload["state"]["rando"]["entrances"][i]["type"] = entranceCtx->entranceOverrides[i].type;
|
||||
// payload["state"]["rando"]["entrances"][i]["index"] = entranceCtx->entranceOverrides[i].index;
|
||||
// payload["state"]["rando"]["entrances"][i]["destination"] = entranceCtx->entranceOverrides[i].destination;
|
||||
// payload["state"]["rando"]["entrances"][i]["override"] = entranceCtx->entranceOverrides[i].override;
|
||||
// payload["state"]["rando"]["entrances"][i]["overrideDestination"] = entranceCtx->entranceOverrides[i].overrideDestination;
|
||||
// }
|
||||
|
||||
// payload["state"]["rando"]["seed"] = json::array();
|
||||
// for (int i = 0; i < randoContext->hashIconIndexes.size(); i++) {
|
||||
// payload["state"]["rando"]["seed"][i] = randoContext->hashIconIndexes[i];
|
||||
// }
|
||||
// payload["state"]["rando"]["inputSeed"] = randoContext->GetSeedString();
|
||||
// payload["state"]["rando"]["finalSeed"] = randoContext->GetSeed();
|
||||
|
||||
// payload["state"]["rando"]["randoSettings"] = json::array();
|
||||
// for (int i = 0; i < RSK_MAX; i++) {
|
||||
// payload["state"]["rando"]["randoSettings"][i] = randoContext->GetOption((RandomizerSettingKey(i))).GetSelectedOptionIndex();
|
||||
// }
|
||||
|
||||
// payload["state"]["rando"]["masterQuestDungeonCount"] = randoContext->GetDungeons()->CountMQ();
|
||||
// payload["state"]["rando"]["masterQuestDungeons"] = json::array();
|
||||
// for (int i = 0; i < randoContext->GetDungeons()->GetDungeonListSize(); i++) {
|
||||
// payload["state"]["rando"]["masterQuestDungeons"][i] = randoContext->GetDungeon(i)->IsMQ();
|
||||
// }
|
||||
// for (int i = 0; i < randoContext->GetTrials()->GetTrialListSize(); i++) {
|
||||
// payload["state"]["rando"]["requiredTrials"][i] = randoContext->GetTrial(i)->IsRequired();
|
||||
// }
|
||||
}
|
||||
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::SendPacket_ClearTeamState() {
|
||||
json payload;
|
||||
payload["type"] = UPDATE_TEAM_STATE;
|
||||
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
||||
payload["queue"] = json::array();
|
||||
payload["state"] = json::object();
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
void Anchor::HandlePacket_UpdateTeamState(nlohmann::json payload) {
|
||||
isHandlingUpdateTeamState = true;
|
||||
// This can happen in between file select and the game starting, so we cant use this check, but we need to ensure we
|
||||
// be careful to wrap PlayState usage in this check
|
||||
// if (!IsSaveLoaded()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (payload.contains("state")) {
|
||||
SaveContext loadedData = payload["state"].get<SaveContext>();
|
||||
|
||||
gSaveContext.healthCapacity = loadedData.healthCapacity;
|
||||
gSaveContext.magicLevel = loadedData.magicLevel;
|
||||
gSaveContext.magicCapacity = gSaveContext.magic = loadedData.magicCapacity;
|
||||
gSaveContext.isMagicAcquired = loadedData.isMagicAcquired;
|
||||
gSaveContext.isDoubleMagicAcquired = loadedData.isDoubleMagicAcquired;
|
||||
gSaveContext.isDoubleDefenseAcquired = loadedData.isDoubleDefenseAcquired;
|
||||
gSaveContext.bgsFlag = loadedData.bgsFlag;
|
||||
gSaveContext.swordHealth = loadedData.swordHealth;
|
||||
gSaveContext.ship.quest = loadedData.ship.quest;
|
||||
|
||||
for (int i = 0; i < 124; i++) {
|
||||
gSaveContext.sceneFlags[i] = loadedData.sceneFlags[i];
|
||||
if (IsSaveLoaded() && gPlayState->sceneNum == i) {
|
||||
gPlayState->actorCtx.flags.chest = loadedData.sceneFlags[i].chest;
|
||||
gPlayState->actorCtx.flags.swch = loadedData.sceneFlags[i].swch;
|
||||
gPlayState->actorCtx.flags.clear = loadedData.sceneFlags[i].clear;
|
||||
gPlayState->actorCtx.flags.collect = loadedData.sceneFlags[i].collect;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 14; i++) {
|
||||
gSaveContext.eventChkInf[i] = loadedData.eventChkInf[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
gSaveContext.itemGetInf[i] = loadedData.itemGetInf[i];
|
||||
}
|
||||
|
||||
// Skip last row of infTable, don't want to sync swordless flag
|
||||
for (int i = 0; i < 29; i++) {
|
||||
gSaveContext.infTable[i] = loadedData.infTable[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < ceil((RAND_INF_MAX + 15) / 16); i++) {
|
||||
gSaveContext.ship.randomizerInf[i] = loadedData.ship.randomizerInf[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
gSaveContext.gsFlags[i] = loadedData.gsFlags[i];
|
||||
}
|
||||
|
||||
gSaveContext.ship.stats.fileCreatedAt = loadedData.ship.stats.fileCreatedAt;
|
||||
|
||||
// Restore master sword state
|
||||
u8 hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, 1);
|
||||
if (hasMasterSword) {
|
||||
loadedData.inventory.equipment |= 0x2;
|
||||
} else {
|
||||
loadedData.inventory.equipment &= ~0x2;
|
||||
}
|
||||
|
||||
// Restore bottle contents (unless it's ruto's letter)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (gSaveContext.inventory.items[SLOT_BOTTLE_1 + i] != ITEM_NONE && gSaveContext.inventory.items[SLOT_BOTTLE_1 + i] != ITEM_LETTER_RUTO) {
|
||||
loadedData.inventory.items[SLOT_BOTTLE_1 + i] = gSaveContext.inventory.items[SLOT_BOTTLE_1 + i];
|
||||
}
|
||||
}
|
||||
|
||||
// Restore ammo if it's non-zero, unless it's beans
|
||||
for (int i = 0; i < ARRAY_COUNT(gSaveContext.inventory.ammo); i++) {
|
||||
if (gSaveContext.inventory.ammo[i] != 0 && i != SLOT(ITEM_BEAN) && i != SLOT(ITEM_BEAN + 1)) {
|
||||
loadedData.inventory.ammo[i] = gSaveContext.inventory.ammo[i];
|
||||
}
|
||||
}
|
||||
|
||||
gSaveContext.inventory = loadedData.inventory;
|
||||
|
||||
// The commented out code below is an attempt at sending the entire randomizer seed over, in hopes that a player doesn't have to generate the seed themselves
|
||||
// Currently it doesn't work :)
|
||||
if (IS_RANDO && payload["state"].contains("rando")) {
|
||||
auto randoContext = Rando::Context::GetInstance();
|
||||
|
||||
for (int i = 0; i < RC_MAX; i++) {
|
||||
// randoContext->GetItemLocation(i)->RefPlacedItem() = payload["state"]["rando"]["itemLocations"][i]["rgID"].get<RandomizerGet>();
|
||||
OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetCheckStatus(payload["state"]["rando"]["itemLocations"][i][0].get<RandomizerCheckStatus>());
|
||||
OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetIsSkipped(payload["state"]["rando"]["itemLocations"][i][0].get<u8>());
|
||||
|
||||
// if (payload["state"]["rando"]["itemLocations"][i].contains("fakeRgID")) {
|
||||
// randoContext->overrides.emplace(static_cast<RandomizerCheck>(i), Rando::ItemOverride(static_cast<RandomizerCheck>(i), payload["state"]["rando"]["itemLocations"][i]["fakeRgID"].get<RandomizerGet>()));
|
||||
// randoContext->GetItemOverride(i).GetTrickName().english = payload["state"]["rando"]["itemLocations"][i]["trickName"]["english"].get<std::string>();
|
||||
// randoContext->GetItemOverride(i).GetTrickName().french = payload["state"]["rando"]["itemLocations"][i]["trickName"]["french"].get<std::string>();
|
||||
// }
|
||||
// if (payload["state"]["rando"]["itemLocations"][i].contains("price")) {
|
||||
// u16 price = payload["state"]["rando"]["itemLocations"][i]["price"].get<u16>();
|
||||
// if (price > 0) {
|
||||
// randoContext->GetItemLocation(i)->SetCustomPrice(price);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// auto entranceCtx = randoContext->GetEntranceShuffler();
|
||||
// for (int i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
|
||||
// entranceCtx->entranceOverrides[i].type = payload["state"]["rando"]["entrances"][i]["type"].get<u16>();
|
||||
// entranceCtx->entranceOverrides[i].index = payload["state"]["rando"]["entrances"][i]["index"].get<s16>();
|
||||
// entranceCtx->entranceOverrides[i].destination = payload["state"]["rando"]["entrances"][i]["destination"].get<s16>();
|
||||
// entranceCtx->entranceOverrides[i].override = payload["state"]["rando"]["entrances"][i]["override"].get<s16>();
|
||||
// entranceCtx->entranceOverrides[i].overrideDestination = payload["state"]["rando"]["entrances"][i]["overrideDestination"].get<s16>();
|
||||
// }
|
||||
|
||||
// for (int i = 0; i < randoContext->hashIconIndexes.size(); i++) {
|
||||
// randoContext->hashIconIndexes[i] = payload["state"]["rando"]["seed"][i].get<u8>();
|
||||
// }
|
||||
// randoContext->GetSettings()->SetSeedString(payload["state"]["rando"]["inputSeed"].get<std::string>());
|
||||
// randoContext->GetSettings()->SetSeed(payload["state"]["rando"]["finalSeed"].get<u32>());
|
||||
|
||||
// for (int i = 0; i < RSK_MAX; i++) {
|
||||
// randoContext->GetOption(RandomizerSettingKey(i)).SetSelectedIndex(payload["state"]["rando"]["randoSettings"][i].get<u8>());
|
||||
// }
|
||||
|
||||
// randoContext->GetDungeons()->ClearAllMQ();
|
||||
// for (int i = 0; i < randoContext->GetDungeons()->GetDungeonListSize(); i++) {
|
||||
// if (payload["state"]["rando"]["masterQuestDungeons"][i].get<bool>()) {
|
||||
// randoContext->GetDungeon(i)->SetMQ();
|
||||
// }
|
||||
// }
|
||||
|
||||
// randoContext->GetTrials()->SkipAll();
|
||||
// for (int i = 0; i < randoContext->GetTrials()->GetTrialListSize(); i++) {
|
||||
// if (payload["state"]["rando"]["requiredTrials"][i].get<bool>()) {
|
||||
// randoContext->GetTrial(i)->SetAsRequired();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
Notification::Emit({
|
||||
.message = "Save updated from team",
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.contains("queue")) {
|
||||
for (auto& item : payload["queue"]) {
|
||||
nlohmann::json itemPayload = nlohmann::json::parse(item.get<std::string>());
|
||||
Anchor::Instance->OnIncomingJson(itemPayload);
|
||||
}
|
||||
}
|
||||
isHandlingUpdateTeamState = false;
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
@ -20,7 +20,7 @@ void Window::Draw() {
|
||||
const float margin = 30.0f;
|
||||
const float padding = 10.0f;
|
||||
|
||||
int position = CVarGetInteger(CVAR_SETTING("Notifications.Position"), 0);
|
||||
int position = CVarGetInteger(CVAR_SETTING("Notifications.Position"), 3);
|
||||
|
||||
// Top Left
|
||||
ImVec2 basePosition;
|
||||
|
@ -77,8 +77,10 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include "soh/Network/CrowdControl/CrowdControl.h"
|
||||
#include "soh/Network/Sail/Sail.h"
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
CrowdControl* CrowdControl::Instance;
|
||||
Sail* Sail::Instance;
|
||||
Anchor* Anchor::Instance;
|
||||
#endif
|
||||
|
||||
#include "Enhancements/mods.h"
|
||||
@ -1161,6 +1163,7 @@ extern "C" void InitOTR() {
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
CrowdControl::Instance = new CrowdControl();
|
||||
Sail::Instance = new Sail();
|
||||
Anchor::Instance = new Anchor();
|
||||
#endif
|
||||
|
||||
OTRMessage_Init();
|
||||
@ -1196,6 +1199,9 @@ extern "C" void InitOTR() {
|
||||
if (CVarGetInteger(CVAR_REMOTE_SAIL("Enabled"), 0)) {
|
||||
Sail::Instance->Enable();
|
||||
}
|
||||
if (CVarGetInteger(CVAR_REMOTE_ANCHOR("Enabled"), 0)) {
|
||||
Anchor::Instance->Enable();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1213,6 +1219,9 @@ extern "C" void DeinitOTR() {
|
||||
if (CVarGetInteger(CVAR_REMOTE_SAIL("Enabled"), 0)) {
|
||||
Sail::Instance->Disable();
|
||||
}
|
||||
if (CVarGetInteger(CVAR_REMOTE_ANCHOR("Enabled"), 0)) {
|
||||
Anchor::Instance->Disable();
|
||||
}
|
||||
SDLNet_Quit();
|
||||
#endif
|
||||
|
||||
|
@ -1223,7 +1223,7 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se
|
||||
|
||||
delete saveContext;
|
||||
InitMeta(fileNum);
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
|
||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum, sectionID);
|
||||
SPDLOG_INFO("Save File Finish - fileNum: {}", fileNum);
|
||||
saveMtx.unlock();
|
||||
}
|
||||
|
@ -36,6 +36,9 @@
|
||||
#include "soh/Enhancements/debugger/MessageViewer.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
#include "soh/Enhancements/TimeDisplay/TimeDisplay.h"
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#endif
|
||||
|
||||
bool isBetaQuestEnabled = false;
|
||||
|
||||
@ -135,6 +138,9 @@ namespace SohGui {
|
||||
std::shared_ptr<Notification::Window> mNotificationWindow;
|
||||
std::shared_ptr<TimeDisplayWindow> mTimeDisplayWindow;
|
||||
std::shared_ptr<AboutWindow> mAboutWindow;
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
std::shared_ptr<AnchorRoomWindow> mAnchorRoomWindow;
|
||||
#endif
|
||||
|
||||
void SetupGuiElements() {
|
||||
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
|
||||
@ -224,6 +230,10 @@ namespace SohGui {
|
||||
gui->AddGuiWindow(mTimeDisplayWindow);
|
||||
mAboutWindow = std::make_shared<AboutWindow>(CVAR_WINDOW("AboutWindow"), "About");
|
||||
gui->AddGuiWindow(mAboutWindow);
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
mAnchorRoomWindow = std::make_shared<AnchorRoomWindow>(CVAR_WINDOW("AnchorRoom"), "Anchor Room");
|
||||
gui->AddGuiWindow(mAnchorRoomWindow);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
@ -261,6 +271,9 @@ namespace SohGui {
|
||||
mPlandomizerWindow = nullptr;
|
||||
mTimeDisplayWindow = nullptr;
|
||||
mAboutWindow = nullptr;
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
mAnchorRoomWindow = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function<void()> button1callback, std::function<void()> button2callback) {
|
||||
|
@ -21,6 +21,7 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include "soh/Network/CrowdControl/CrowdControl.h"
|
||||
#include "soh/Network/Sail/Sail.h"
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#endif
|
||||
|
||||
|
||||
@ -2127,6 +2128,7 @@ void DrawRemoteControlMenu() {
|
||||
if (ImGui::BeginMenu("Network")) {
|
||||
Sail::Instance->DrawMenu();
|
||||
CrowdControl::Instance->DrawMenu();
|
||||
Anchor::Instance->DrawMenu();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
@ -830,7 +830,7 @@ namespace UIWidgets {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool InputString(const char* label, std::string* value) {
|
||||
return ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value);
|
||||
bool InputString(const char* label, std::string* value, ImGuiInputTextFlags flags) {
|
||||
return ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize | flags, InputTextResizeCallback, value);
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ namespace UIWidgets {
|
||||
void DrawFlagArray16(const std::string& name, uint16_t& flags);
|
||||
void DrawFlagArray8(const std::string& name, uint8_t& flags);
|
||||
bool StateButton(const char* str_id, const char* label);
|
||||
bool InputString(const char* label, std::string* value);
|
||||
bool InputString(const char* label, std::string* value, ImGuiInputTextFlags flags = 0);
|
||||
}
|
||||
|
||||
#endif /* UIWidgets_hpp */
|
||||
|
@ -14,4 +14,5 @@
|
||||
#define CVAR_GENERAL(var) CVAR_PREFIX_GENERAL "." var
|
||||
#define CVAR_REMOTE(var) CVAR_PREFIX_REMOTE "." var
|
||||
#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE(".CrowdControl." var)
|
||||
#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE(".Sail." var)
|
||||
#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE(".Sail." var)
|
||||
#define CVAR_REMOTE_ANCHOR(var) CVAR_REMOTE(".Anchor." var)
|
||||
|
@ -117,6 +117,7 @@ std::vector<std::string> sceneNames = {
|
||||
"Castle Hedge Maze (Early)",
|
||||
"Sasa Test",
|
||||
"Treasure Chest Room",
|
||||
"Unknown",
|
||||
};
|
||||
|
||||
std::vector<std::string> itemNames = {
|
||||
|
@ -1231,14 +1231,20 @@ void Actor_Init(Actor* actor, PlayState* play) {
|
||||
ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f);
|
||||
if (Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) {
|
||||
Actor_SetObjectDependency(play, actor);
|
||||
actor->init(actor, play);
|
||||
actor->init = NULL;
|
||||
|
||||
GameInteractor_ExecuteOnActorInit(actor);
|
||||
if (GameInteractor_ShouldActorInit(actor)) {
|
||||
actor->init(actor, play);
|
||||
actor->init = NULL;
|
||||
|
||||
// For enemy health bar we need to know the max health during init
|
||||
if (actor->category == ACTORCAT_ENEMY) {
|
||||
actor->maximumHealth = actor->colChkInfo.health;
|
||||
GameInteractor_ExecuteOnActorInit(actor);
|
||||
|
||||
// For enemy health bar we need to know the max health during init
|
||||
if (actor->category == ACTORCAT_ENEMY) {
|
||||
actor->maximumHealth = actor->colChkInfo.health;
|
||||
}
|
||||
} else {
|
||||
actor->init = NULL;
|
||||
Actor_Kill(actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2210,6 +2216,10 @@ void Player_PlaySfx(Actor* actor, u16 sfxId) {
|
||||
// Audio_PlaySoundGeneral(sfxId, &actor->projectedPos, 4, &D_801333E0 , &D_801333E0, &D_801333E8);
|
||||
Audio_PlaySoundGeneral(sfxId, &actor->projectedPos, 4, &freqMultiplier, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
||||
}
|
||||
|
||||
if (actor->id == ACTOR_PLAYER) {
|
||||
GameInteractor_ExecuteOnPlayerSfx(sfxId);
|
||||
}
|
||||
}
|
||||
|
||||
void Audio_PlayActorSound2(Actor* actor, u16 sfxId) {
|
||||
@ -2589,14 +2599,20 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) {
|
||||
if (Object_IsLoaded(&play->objectCtx, actor->objBankIndex))
|
||||
{
|
||||
Actor_SetObjectDependency(play, actor);
|
||||
actor->init(actor, play);
|
||||
actor->init = NULL;
|
||||
|
||||
GameInteractor_ExecuteOnActorInit(actor);
|
||||
if (GameInteractor_ShouldActorInit(actor)) {
|
||||
actor->init(actor, play);
|
||||
actor->init = NULL;
|
||||
|
||||
// For enemy health bar we need to know the max health during init
|
||||
if (actor->category == ACTORCAT_ENEMY) {
|
||||
actor->maximumHealth = actor->colChkInfo.health;
|
||||
GameInteractor_ExecuteOnActorInit(actor);
|
||||
|
||||
// For enemy health bar we need to know the max health during init
|
||||
if (actor->category == ACTORCAT_ENEMY) {
|
||||
actor->maximumHealth = actor->colChkInfo.health;
|
||||
}
|
||||
} else {
|
||||
actor->init = NULL;
|
||||
Actor_Kill(actor);
|
||||
}
|
||||
}
|
||||
actor = actor->next;
|
||||
|
@ -767,6 +767,13 @@ void EnItem00_Update(Actor* thisx, PlayState* play) {
|
||||
EnItem00* this = (EnItem00*)thisx;
|
||||
s32 pad;
|
||||
|
||||
// #region SOH [Co-op]
|
||||
if (Flags_GetCollectible(play, this->collectibleFlag)) {
|
||||
Actor_Kill(&this->actor);
|
||||
return;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// Rotate some drops when 3D drops are on, otherwise reset rotation back to 0 for billboard effect
|
||||
if (
|
||||
(this->actor.params == ITEM00_HEART && this->unk_15A >= 0) ||
|
||||
|
@ -211,7 +211,9 @@ void func_8086ED50(BgBombwall* this, PlayState* play) {
|
||||
}
|
||||
|
||||
void func_8086ED70(BgBombwall* this, PlayState* play) {
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
this->collider.base.acFlags &= ~AC_HIT;
|
||||
func_8086EDFC(this, play);
|
||||
Flags_SetSwitch(play, this->dyna.actor.params & 0x3F);
|
||||
|
@ -277,7 +277,9 @@ void BgBreakwall_Wait(BgBreakwall* this, PlayState* play) {
|
||||
}
|
||||
}
|
||||
|
||||
if (GameInteractor_Should(VB_BG_BREAKWALL_BREAK, this->collider.base.acFlags & 2 || blueFireArrowHit)) {
|
||||
// #region SOH [Co-op]
|
||||
if (GameInteractor_Should(VB_BG_BREAKWALL_BREAK, this->collider.base.acFlags & 2 || blueFireArrowHit) || Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
Vec3f effectPos;
|
||||
s32 wallType = ((this->dyna.actor.params >> 13) & 3) & 0xFF;
|
||||
|
||||
|
@ -268,7 +268,9 @@ void func_80882E54(BgHakaZou* this, PlayState* play) {
|
||||
}
|
||||
|
||||
void func_80883000(BgHakaZou* this, PlayState* play) {
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, this->switchFlag)) {
|
||||
// #endregion
|
||||
Flags_SetSwitch(play, this->switchFlag);
|
||||
|
||||
if (this->dyna.actor.params == STA_GIANT_BIRD_STATUE) {
|
||||
|
@ -126,8 +126,10 @@ void BgHidanDalm_Destroy(Actor* thisx, PlayState* play) {
|
||||
void BgHidanDalm_Wait(BgHidanDalm* this, PlayState* play) {
|
||||
Player* player = GET_PLAYER(play);
|
||||
|
||||
if ((this->collider.base.acFlags & AC_HIT) && !Player_InCsMode(play) &&
|
||||
(player->meleeWeaponAnimation == 22 || player->meleeWeaponAnimation == 23)) {
|
||||
// #region SOH [Co-op]
|
||||
if (((this->collider.base.acFlags & AC_HIT) && !Player_InCsMode(play) &&
|
||||
(player->meleeWeaponAnimation == 22 || player->meleeWeaponAnimation == 23)) || Flags_GetSwitch(play, this->switchFlag)) {
|
||||
// #endregion
|
||||
this->collider.base.acFlags &= ~AC_HIT;
|
||||
if ((this->collider.elements[0].info.bumperFlags & BUMP_HIT) ||
|
||||
(this->collider.elements[1].info.bumperFlags & BUMP_HIT)) {
|
||||
|
@ -278,7 +278,9 @@ void func_80888734(BgHidanHamstep* this) {
|
||||
}
|
||||
|
||||
void func_808887C4(BgHidanHamstep* this, PlayState* play) {
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, (this->dyna.actor.params >> 8) & 0xFF)) {
|
||||
// #endregion
|
||||
OnePointCutscene_Init(play, 3310, 100, &this->dyna.actor, MAIN_CAM);
|
||||
Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_HAMMER_SWITCH);
|
||||
this->collider.base.acFlags = AC_NONE;
|
||||
|
@ -201,7 +201,9 @@ void func_8088960C(BgHidanHrock* this, PlayState* play) {
|
||||
}
|
||||
|
||||
void func_808896B8(BgHidanHrock* this, PlayState* play) {
|
||||
if (this->collider.base.acFlags & 2) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & 2) || Flags_GetSwitch(play, this->unk_16A)) {
|
||||
// #endregion
|
||||
this->collider.base.acFlags &= ~2;
|
||||
this->actionFunc = func_808894B0;
|
||||
this->dyna.actor.flags |= ACTOR_FLAG_UPDATE_CULLING_DISABLED;
|
||||
|
@ -303,7 +303,9 @@ void BgHidanKowarerukabe_Update(Actor* thisx, PlayState* play) {
|
||||
BgHidanKowarerukabe* this = (BgHidanKowarerukabe*)thisx;
|
||||
s32 pad;
|
||||
|
||||
if (Actor_GetCollidedExplosive(play, &this->collider.base) != NULL) {
|
||||
// #region SOH [Co-op]
|
||||
if ((Actor_GetCollidedExplosive(play, &this->collider.base) != NULL) || Flags_GetSwitch(play, (this->dyna.actor.params >> 8) & 0x3F)) {
|
||||
// #endregion
|
||||
BgHidanKowarerukabe_Break(this, play);
|
||||
Flags_SetSwitch(play, (this->dyna.actor.params >> 8) & 0x3F);
|
||||
|
||||
|
@ -333,10 +333,12 @@ void func_8089107C(BgIceShelter* this, PlayState* play) {
|
||||
MeltOnIceArrowHit(this, this->cylinder2, type, play);
|
||||
}
|
||||
// Default blue fire check
|
||||
if (this->cylinder1.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->cylinder1.base.acFlags & AC_HIT) || Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
this->cylinder1.base.acFlags &= ~AC_HIT;
|
||||
|
||||
if ((this->cylinder1.base.ac != NULL) && (this->cylinder1.base.ac->id == ACTOR_EN_ICE_HONO)) {
|
||||
if (((this->cylinder1.base.ac != NULL) && (this->cylinder1.base.ac->id == ACTOR_EN_ICE_HONO)) || Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
if (type == 4) {
|
||||
if (this->dyna.actor.parent != NULL) {
|
||||
this->dyna.actor.parent->freezeTimer = 50;
|
||||
|
@ -142,7 +142,9 @@ void BgJyaBombchuiwa_SetupWaitForExplosion(BgJyaBombchuiwa* this, PlayState* pla
|
||||
}
|
||||
|
||||
void BgJyaBombchuiwa_WaitForExplosion(BgJyaBombchuiwa* this, PlayState* play) {
|
||||
if ((this->collider.base.acFlags & AC_HIT) || (this->timer > 0)) {
|
||||
// #region SOH [Co-op]
|
||||
if (((this->collider.base.acFlags & AC_HIT) || (this->timer > 0)) || Flags_GetSwitch(play, this->actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
if (this->timer == 0) {
|
||||
OnePointCutscene_Init(play, 3410, -99, &this->actor, MAIN_CAM);
|
||||
}
|
||||
|
@ -163,7 +163,9 @@ void BgJyaBombiwa_Break(BgJyaBombiwa* this, PlayState* play) {
|
||||
void BgJyaBombiwa_Update(Actor* thisx, PlayState* play) {
|
||||
BgJyaBombiwa* this = (BgJyaBombiwa*)thisx;
|
||||
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
BgJyaBombiwa_Break(this, play);
|
||||
Flags_SetSwitch(play, this->dyna.actor.params & 0x3F);
|
||||
SoundSource_PlaySfxAtFixedWorldPos(play, &this->dyna.actor.world.pos, 40, NA_SE_EV_WALL_BROKEN);
|
||||
|
@ -467,7 +467,9 @@ void BgMizuBwall_SpawnDebris(BgMizuBwall* this, PlayState* play) {
|
||||
|
||||
void BgMizuBwall_Idle(BgMizuBwall* this, PlayState* play) {
|
||||
BgMizuBwall_SetAlpha(this, play);
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, ((u16)this->dyna.actor.params >> 8) & 0x3F)) {
|
||||
// #endregion
|
||||
this->collider.base.acFlags &= ~AC_HIT;
|
||||
Flags_SetSwitch(play, ((u16)this->dyna.actor.params >> 8) & 0x3F);
|
||||
this->breakTimer = 1;
|
||||
|
@ -183,7 +183,9 @@ void BgSpot08Bakudankabe_Destroy(Actor* thisx, PlayState* play) {
|
||||
void BgSpot08Bakudankabe_Update(Actor* thisx, PlayState* play) {
|
||||
BgSpot08Bakudankabe* this = (BgSpot08Bakudankabe*)thisx;
|
||||
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, (this->dyna.actor.params & 0x3F))) {
|
||||
// #endregion
|
||||
func_808B0324(this, play);
|
||||
Flags_SetSwitch(play, (this->dyna.actor.params & 0x3F));
|
||||
SoundSource_PlaySfxAtFixedWorldPos(play, &this->dyna.actor.world.pos, 40, NA_SE_EV_WALL_BROKEN);
|
||||
|
@ -135,7 +135,9 @@ void BgSpot11Bakudankabe_Destroy(Actor* thisx, PlayState* play) {
|
||||
void BgSpot11Bakudankabe_Update(Actor* thisx, PlayState* play) {
|
||||
BgSpot11Bakudankabe* this = (BgSpot11Bakudankabe*)thisx;
|
||||
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, (this->dyna.actor.params & 0x3F))) {
|
||||
// #endregion
|
||||
func_808B2218(this, play);
|
||||
Flags_SetSwitch(play, (this->dyna.actor.params & 0x3F));
|
||||
SoundSource_PlaySfxAtFixedWorldPos(play, &D_808B2738, 40, NA_SE_EV_WALL_BROKEN);
|
||||
|
@ -114,7 +114,9 @@ void BgSpot17Bakudankabe_Destroy(Actor* thisx, PlayState* play) {
|
||||
|
||||
void BgSpot17Bakudankabe_Update(Actor* thisx, PlayState* play) {
|
||||
BgSpot17Bakudankabe* this = (BgSpot17Bakudankabe*)thisx;
|
||||
if (this->dyna.actor.xzDistToPlayer < 650.0f && func_80033684(play, &this->dyna.actor) != NULL) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->dyna.actor.xzDistToPlayer < 650.0f && func_80033684(play, &this->dyna.actor) != NULL) || Flags_GetSwitch(play, (this->dyna.actor.params & 0x3F))) {
|
||||
// #endregion
|
||||
func_808B6BC0(this, play);
|
||||
Flags_SetSwitch(play, (this->dyna.actor.params & 0x3F));
|
||||
SoundSource_PlaySfxAtFixedWorldPos(play, &this->dyna.actor.world.pos, 40, NA_SE_EV_WALL_BROKEN);
|
||||
|
@ -146,7 +146,9 @@ void func_808BEFF4(BgYdanMaruta* this, PlayState* play) {
|
||||
}
|
||||
|
||||
void func_808BF078(BgYdanMaruta* this, PlayState* play) {
|
||||
if (this->collider.base.acFlags & AC_HIT) {
|
||||
// #region SOH [Co-op]
|
||||
if ((this->collider.base.acFlags & AC_HIT) || Flags_GetSwitch(play, this->switchFlag)) {
|
||||
// #endregion
|
||||
this->unk_16A = 20;
|
||||
Flags_SetSwitch(play, this->switchFlag);
|
||||
Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME);
|
||||
|
@ -282,6 +282,12 @@ void BgYdanSp_FloorWebIdle(BgYdanSp* this, PlayState* play) {
|
||||
webPos.x = this->dyna.actor.world.pos.x;
|
||||
webPos.y = this->dyna.actor.world.pos.y - 50.0f;
|
||||
webPos.z = this->dyna.actor.world.pos.z;
|
||||
// #region SOH [Co-op]
|
||||
if (Flags_GetSwitch(play, this->isDestroyedSwitchFlag)) {
|
||||
BgYdanSp_BurnWeb(this, play);
|
||||
return;
|
||||
}
|
||||
// #endregion
|
||||
if (Player_IsBurningStickInRange(play, &webPos, 70.0f, 50.0f) != 0) {
|
||||
this->dyna.actor.home.pos.x = player->meleeWeaponInfo[0].tip.x;
|
||||
this->dyna.actor.home.pos.z = player->meleeWeaponInfo[0].tip.z;
|
||||
@ -411,6 +417,11 @@ void BgYdanSp_WallWebIdle(BgYdanSp* this, PlayState* play) {
|
||||
BgYdanSp_BurnWeb(this, play);
|
||||
}
|
||||
}
|
||||
// #region SOH [Co-op]
|
||||
if (Flags_GetSwitch(play, this->isDestroyedSwitchFlag)) {
|
||||
BgYdanSp_BurnWeb(this, play);
|
||||
}
|
||||
// #endregion
|
||||
CollisionCheck_SetAC(play, &play->colChkCtx, &this->trisCollider.base);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "z_door_gerudo.h"
|
||||
#include "objects/object_door_gerudo/object_door_gerudo.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
|
||||
#define FLAGS 0
|
||||
|
||||
@ -103,6 +104,7 @@ void func_8099485C(DoorGerudo* this, PlayState* play) {
|
||||
gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] -= 1;
|
||||
Flags_SetSwitch(play, this->dyna.actor.params & 0x3F);
|
||||
Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_CHAIN_KEY_UNLOCK);
|
||||
GameInteractor_ExecuteOnDungeonKeyUsedHooks(gSaveContext.mapIndex);
|
||||
} else {
|
||||
s32 direction = func_80994750(this, play);
|
||||
|
||||
|
@ -379,6 +379,7 @@ void func_80996B0C(DoorShutter* this, PlayState* play) {
|
||||
if (this->doorType != SHUTTER_BOSS) {
|
||||
gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]--;
|
||||
Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_CHAIN_KEY_UNLOCK);
|
||||
GameInteractor_ExecuteOnDungeonKeyUsedHooks(gSaveContext.mapIndex);
|
||||
} else {
|
||||
Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_CHAIN_KEY_UNLOCK_B);
|
||||
}
|
||||
@ -645,6 +646,11 @@ void DoorShutter_Update(Actor* thisx, PlayState* play) {
|
||||
if (!(player->stateFlags1 & (PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD | PLAYER_STATE1_GETTING_ITEM | PLAYER_STATE1_IN_ITEM_CS)) || (this->actionFunc == DoorShutter_SetupType)) {
|
||||
this->actionFunc(this, play);
|
||||
}
|
||||
// #region SOH [Co-op]
|
||||
if (Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) {
|
||||
DECR(this->unk_16E);
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
|
||||
Gfx* func_80997838(PlayState* play, DoorShutter* this, Gfx* p) {
|
||||
|
@ -207,6 +207,7 @@ void EnDoor_Idle(EnDoor* this, PlayState* play) {
|
||||
Flags_SetSwitch(play, this->actor.params & 0x3F);
|
||||
}
|
||||
Audio_PlayActorSound2(&this->actor, NA_SE_EV_CHAIN_KEY_UNLOCK);
|
||||
GameInteractor_ExecuteOnDungeonKeyUsedHooks(gSaveContext.mapIndex);
|
||||
}
|
||||
} else if (!Player_InCsMode(play)) {
|
||||
if (fabsf(playerPosRelToDoor.y) < 20.0f && fabsf(playerPosRelToDoor.x) < 20.0f &&
|
||||
@ -234,6 +235,11 @@ void EnDoor_Idle(EnDoor* this, PlayState* play) {
|
||||
this->actionFunc = EnDoor_AjarOpen;
|
||||
}
|
||||
}
|
||||
// #region SOH [Co-op]
|
||||
if (Flags_GetSwitch(play, this->actor.params & 0x3F)) {
|
||||
DECR(this->lockTimer);
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
|
||||
void EnDoor_WaitForCheck(EnDoor* this, PlayState* play) {
|
||||
|
@ -149,6 +149,13 @@ void func_80AFB950(EnSi* this, PlayState* play) {
|
||||
void EnSi_Update(Actor* thisx, PlayState* play) {
|
||||
EnSi* this = (EnSi*)thisx;
|
||||
|
||||
// #region SOH [Co-op]
|
||||
if (GET_GS_FLAGS((thisx->params & 0x1F00) >> 8) & (thisx->params & 0xFF)) {
|
||||
Actor_Kill(&this->actor);
|
||||
return;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
Actor_MoveXZGravity(&this->actor);
|
||||
Actor_UpdateBgCheckInfo(play, &this->actor, 0.0f, 0.0f, 0.0f, 4);
|
||||
this->actionFunc(this, play);
|
||||
|
@ -898,6 +898,13 @@ void func_80B0E9BC(EnSw* this, PlayState* play) {
|
||||
void EnSw_Update(Actor* thisx, PlayState* play) {
|
||||
EnSw* this = (EnSw*)thisx;
|
||||
|
||||
// #region SOH [Co-op]
|
||||
if (GET_GS_FLAGS((thisx->params & 0x1F00) >> 8) & (thisx->params & 0xFF)) {
|
||||
Actor_Kill(&this->actor);
|
||||
return;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
SkelAnime_Update(&this->skelAnime);
|
||||
func_80B0C9F0(this, play);
|
||||
this->actionFunc(this, play);
|
||||
|
@ -55,6 +55,13 @@ void ItemBHeart_Destroy(Actor* thisx, PlayState* play) {
|
||||
void ItemBHeart_Update(Actor* thisx, PlayState* play) {
|
||||
ItemBHeart* this = (ItemBHeart*)thisx;
|
||||
|
||||
// #region SOH [Co-op]
|
||||
if (Flags_GetCollectible(play, 0x1F)) {
|
||||
Actor_Kill(&this->actor);
|
||||
return;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
func_80B85264(this, play);
|
||||
Actor_UpdateBgCheckInfo(play, &this->actor, 0.0f, 0.0f, 0.0f, 4);
|
||||
if (Actor_HasParent(&this->actor, play)) {
|
||||
|
@ -125,8 +125,10 @@ void ObjBombiwa_Update(Actor* thisx, PlayState* play) {
|
||||
ObjBombiwa* this = (ObjBombiwa*)thisx;
|
||||
s32 pad;
|
||||
|
||||
// #region SOH [Co-op]
|
||||
if ((func_80033684(play, &this->actor) != NULL) ||
|
||||
((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040))) {
|
||||
((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040)) || Flags_GetSwitch(play, this->actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
ObjBombiwa_Break(this, play);
|
||||
Flags_SetSwitch(play, this->actor.params & 0x3F);
|
||||
SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 80, NA_SE_EV_WALL_BROKEN);
|
||||
|
@ -171,10 +171,12 @@ void ObjHamishi_Update(Actor* thisx, PlayState* play) {
|
||||
|
||||
ObjHamishi_Shake(this);
|
||||
|
||||
if ((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040)) {
|
||||
// #region SOH [Co-op]
|
||||
if (((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040)) || Flags_GetSwitch(play, this->actor.params & 0x3F)) {
|
||||
this->collider.base.acFlags &= ~AC_HIT;
|
||||
this->hitCount++;
|
||||
if (this->hitCount < 2) {
|
||||
if (this->hitCount < 2 && !Flags_GetSwitch(play, this->actor.params & 0x3F)) {
|
||||
// #endregion
|
||||
this->shakeFrames = 15;
|
||||
this->shakePosSize = 2.0f;
|
||||
this->shakeRotSize = 400.0f;
|
||||
|
Loading…
Reference in New Issue
Block a user