Use OE for storing client ID, remove remaining game state touch point in network thread (#5969)

This commit is contained in:
Garrett Cox
2025-11-16 08:10:07 -06:00
committed by GitHub
parent 05233487f8
commit eca9eac0cf
5 changed files with 46 additions and 27 deletions

View File

@@ -3,6 +3,7 @@
#include <libultraship/libultraship.h>
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/nametag.h"
#include "soh/ObjectExtension/ObjectExtension.h"
extern "C" {
#include "variables.h"
@@ -143,6 +144,21 @@ void Anchor::ProcessIncomingPacketQueue() {
// MARK: - Misc/Helpers
// Kills all existing anchor actors and respawns them with the new client data
struct DummyPlayerClientId {
uint32_t clientId = 0;
};
static ObjectExtension::Register<DummyPlayerClientId> DummyPlayerClientIdRegister;
uint32_t Anchor::GetDummyPlayerClientId(const Actor* actor) {
const DummyPlayerClientId* clientId = ObjectExtension::GetInstance().Get<DummyPlayerClientId>(actor);
return clientId != nullptr ? clientId->clientId : 0;
}
void Anchor::SetDummyPlayerClientId(const Actor* actor, uint32_t clientId) {
ObjectExtension::GetInstance().Set<DummyPlayerClientId>(actor, DummyPlayerClientId{ clientId });
}
void Anchor::RefreshClientActors() {
if (!IsSaveLoaded()) {
return;
@@ -158,23 +174,21 @@ void Anchor::RefreshClientActors() {
actor = actor->next;
}
actorIndexToClientId.clear();
refreshingActors = true;
for (auto& [clientId, client] : clients) {
if (!client.online || client.self) {
continue;
}
actorIndexToClientId.push_back(clientId);
spawningDummyPlayerForClientId = 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);
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, 0, false);
client.player = (Player*)dummy;
}
refreshingActors = false;
spawningDummyPlayerForClientId = 0;
}
bool Anchor::IsSaveLoaded() {

View File

@@ -63,7 +63,8 @@ typedef struct {
class Anchor : public Network {
private:
bool refreshingActors = false;
uint32_t spawningDummyPlayerForClientId = 0;
bool shouldRefreshActors = false;
bool justLoadedSave = false;
bool isHandlingUpdateTeamState = false;
bool isProcessingIncomingPacket = false;
@@ -74,6 +75,8 @@ class Anchor : public Network {
nlohmann::json PrepRoomState();
void RegisterHooks();
void RefreshClientActors();
void SetDummyPlayerClientId(const Actor* actor, uint32_t clientId);
void HandlePacket_AllClientState(nlohmann::json payload);
void HandlePacket_ConsumeAdultTradeItem(nlohmann::json payload);
void HandlePacket_DamagePlayer(nlohmann::json payload);
@@ -125,7 +128,6 @@ class Anchor : public Network {
static Anchor* Instance;
std::map<uint32_t, AnchorClient> clients;
std::vector<uint32_t> actorIndexToClientId;
RoomState roomState;
void Enable();
@@ -138,6 +140,7 @@ class Anchor : public Network {
void SendJsonToRemote(nlohmann::json packet);
bool IsSaveLoaded();
bool CanTeleportTo(uint32_t clientId);
uint32_t GetDummyPlayerClientId(const Actor* actor);
void SendPacket_ClearTeamState(std::string teamId);
void SendPacket_DamagePlayer(u32 clientId, u8 damageEffect, u8 damage);

View File

@@ -12,9 +12,6 @@ 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),
@@ -53,15 +50,14 @@ static DamageTable DummyPlayerDamageTable = {
void DummyPlayer_Init(Actor* actor, PlayState* play) {
Player* player = (Player*)actor;
uint32_t clientId = Anchor::Instance->actorIndexToClientId[actor->params];
DUMMY_CLIENT_ID = clientId;
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (!Anchor::Instance->clients.contains(DUMMY_CLIENT_ID)) {
if (!Anchor::Instance->clients.contains(clientId)) {
Actor_Kill(actor);
return;
}
AnchorClient& client = Anchor::Instance->clients[DUMMY_CLIENT_ID];
AnchorClient& client = Anchor::Instance->clients[clientId];
// Hack to account for usage of gSaveContext in Player_Init
s32 originalAge = gSaveContext.linkAge;
@@ -104,12 +100,14 @@ void Math_Vec3s_Copy(Vec3s* dest, Vec3s* src) {
void DummyPlayer_Update(Actor* actor, PlayState* play) {
Player* player = (Player*)actor;
if (!Anchor::Instance->clients.contains(DUMMY_CLIENT_ID)) {
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (!Anchor::Instance->clients.contains(clientId)) {
Actor_Kill(actor);
return;
}
AnchorClient& client = Anchor::Instance->clients[DUMMY_CLIENT_ID];
AnchorClient& client = Anchor::Instance->clients[clientId];
if (client.sceneNum != gPlayState->sceneNum || !client.online || !client.isSaveLoaded) {
actor->world.pos.x = -9999.0f;
@@ -195,12 +193,14 @@ void DummyPlayer_Update(Actor* actor, PlayState* play) {
void DummyPlayer_Draw(Actor* actor, PlayState* play) {
Player* player = (Player*)actor;
if (!Anchor::Instance->clients.contains(DUMMY_CLIENT_ID)) {
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (!Anchor::Instance->clients.contains(clientId)) {
Actor_Kill(actor);
return;
}
AnchorClient& client = Anchor::Instance->clients[DUMMY_CLIENT_ID];
AnchorClient& client = Anchor::Instance->clients[clientId];
if (client.sceneNum != gPlayState->sceneNum || !client.online || !client.isSaveLoaded) {
return;

View File

@@ -67,7 +67,9 @@ void Anchor::RegisterHooks() {
COND_ID_HOOK(ShouldActorInit, ACTOR_PLAYER, isConnected, [&](void* actorRef, bool* should) {
Actor* actor = (Actor*)actorRef;
if (refreshingActors) {
if (spawningDummyPlayerForClientId != 0) {
SetDummyPlayerClientId(actor, spawningDummyPlayerForClientId);
// 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;
@@ -84,6 +86,12 @@ void Anchor::RegisterHooks() {
justLoadedSave = false;
SendPacket_RequestTeamState();
}
if (shouldRefreshActors) {
shouldRefreshActors = false;
RefreshClientActors();
}
SendPacket_PlayerUpdate();
});

View File

@@ -77,8 +77,6 @@ void Anchor::SendPacket_PlayerUpdate() {
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];
@@ -110,8 +108,4 @@ void Anchor::HandlePacket_PlayerUpdate(nlohmann::json payload) {
client.unk_862 = payload["unk_862"].get<s16>();
client.actionVar1 = payload["actionVar1"].get<s8>();
}
if (shouldRefreshActors) {
RefreshClientActors();
}
}