Added minimap icons for other players in Anchor (#6372)

Icon size for other players reduced by 25%
This commit is contained in:
Sean Latham
2026-03-23 02:34:06 +01:00
committed by GitHub
parent ef042be5ea
commit 5f0c0c8e2f
9 changed files with 186 additions and 0 deletions

View File

@@ -63,6 +63,7 @@ DEFINE_HOOK(OnDialogMessage, ());
DEFINE_HOOK(OnPresentTitleCard, ());
DEFINE_HOOK(OnInterfaceUpdate, ());
DEFINE_HOOK(OnKaleidoscopeUpdate, (int16_t inDungeonScene));
DEFINE_HOOK(OnMinimapDrawCompassIcons, ());
DEFINE_HOOK(OnPresentFileSelect, ());
DEFINE_HOOK(OnUpdateFileSelectSelection, (uint16_t optionIndex));

View File

@@ -298,6 +298,10 @@ void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnKaleidoscopeUpdate>(inDungeonScene);
}
void GameInteractor_ExecuteOnMinimapDrawCompassIcons() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnMinimapDrawCompassIcons>();
}
// MARK: - Main Menu
void GameInteractor_ExecuteOnPresentFileSelect() {

View File

@@ -69,6 +69,7 @@ void GameInteractor_ExecuteOnDialogMessage();
void GameInteractor_ExecuteOnPresentTitleCard();
void GameInteractor_ExecuteOnInterfaceUpdate();
void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene);
void GameInteractor_ExecuteOnMinimapDrawCompassIcons();
// MARK: - Main Menu
void GameInteractor_ExecuteOnPresentFileSelect();

View File

@@ -29,6 +29,7 @@ typedef struct {
bool isSaveLoaded;
bool isGameComplete;
s16 sceneNum;
s8 curRoomNum;
s32 entranceIndex;
// Only available in PLAYER_UPDATE packets

View File

@@ -1,6 +1,9 @@
#include "Anchor.h"
#include <libultraship/libultraship.h>
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/frame_interpolation.h"
#include "soh/OTRGlobals.h"
extern "C" {
#include "variables.h"
@@ -28,8 +31,10 @@ extern "C" {
#include "src/overlays/actors/ovl_Obj_Hamishi/z_obj_hamishi.h"
#include "src/overlays/actors/ovl_Bg_Hidan_Dalm/z_bg_hidan_dalm.h"
#include "src/overlays/actors/ovl_Bg_Hidan_Kowarerukabe/z_bg_hidan_kowarerukabe.h"
#include "objects/gameplay_keep/gameplay_keep.h"
extern PlayState* gPlayState;
extern MapData* gMapData;
void func_8086ED70(BgBombwall* bgBombwall, PlayState* play);
void BgBreakwall_Wait(BgBreakwall* bgBreakwall, PlayState* play);
@@ -48,6 +53,8 @@ void BgYdanSp_FloorWebIdle(BgYdanSp* bgYdanSp, PlayState* play);
void BgYdanSp_WallWebIdle(BgYdanSp* bgYdanSp, PlayState* play);
void BgYdanSp_BurnWeb(BgYdanSp* bgYdanSp, PlayState* play);
void EnDoor_Idle(EnDoor* enDoor, PlayState* play);
float OTRGetDimensionFromLeftEdge(float v);
float OTRGetDimensionFromRightEdge(float v);
}
void Anchor::RegisterHooks() {
@@ -393,4 +400,149 @@ void Anchor::RegisterHooks() {
});
// #endregion
// #region Hooks for visual effects that don't affect gameplay
struct CompassIcon {
Vec3f pos;
Vec3s rot;
float scale;
Color_RGB8 color;
};
COND_HOOK(OnMinimapDrawCompassIcons, isConnected, [&]() {
if (!CVarGetInteger(CVAR_REMOTE_ANCHOR("ShowOtherPlayersOnMinimap"), 1) ||
Anchor::Instance->roomState.showLocationsMode == 0) {
return;
}
std::vector<CompassIcon> compassIcons;
bool isInDungeon = gPlayState->sceneNum == SCENE_DEKU_TREE || gPlayState->sceneNum == SCENE_DODONGOS_CAVERN ||
gPlayState->sceneNum == SCENE_JABU_JABU || gPlayState->sceneNum == SCENE_FOREST_TEMPLE ||
gPlayState->sceneNum == SCENE_FIRE_TEMPLE || gPlayState->sceneNum == SCENE_WATER_TEMPLE ||
gPlayState->sceneNum == SCENE_SPIRIT_TEMPLE || gPlayState->sceneNum == SCENE_SHADOW_TEMPLE ||
gPlayState->sceneNum == SCENE_BOTTOM_OF_THE_WELL || gPlayState->sceneNum == SCENE_ICE_CAVERN;
std::string teamId = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
// When transitioning to a new room via a door, curRoom.num updates immediately but the minimap still shows the
// previous room while fading out
s8 displayedRoomNum =
gPlayState->roomCtx.prevRoom.num >= 0 ? gPlayState->roomCtx.prevRoom.num : gPlayState->roomCtx.curRoom.num;
for (auto& [clientId, client] : Anchor::Instance->clients) {
// Show compass icons for other players in the current scene. Also require them to be in the current room
// within dungeons. If showLocationsMode isn't all players (2), only show compass icons for players of the
// same team
if (!client.self && client.online && client.player && client.sceneNum == gPlayState->sceneNum &&
(!isInDungeon || client.curRoomNum == displayedRoomNum) &&
(Anchor::Instance->roomState.showLocationsMode == 2 || client.teamId == teamId)) {
compassIcons.push_back(
CompassIcon{ client.player->actor.world.pos, client.player->actor.shape.rot, 0.3f, client.color });
}
}
// The local player's compass icon is always last so it gets drawn above the others
Player* player = GET_PLAYER(gPlayState);
compassIcons.push_back(CompassIcon{ player->actor.world.pos, player->actor.shape.rot, 0.4f,
CVarGetColor24(CVAR_REMOTE_ANCHOR("Color.Value"), { 100, 255, 100 }) });
// Adapted internals of Minimap_DrawCompassIcons()
s16 leftMinimapMargin = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.L"), 0);
s16 rightMinimapMargin = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.R"), 0);
s16 bottomMinimapMargin = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.B"), 0);
s16 xMarginsMinimap;
s16 yMarginsMinimap;
if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.UseMargins"), 0) != 0) {
if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosType"), 0) == ORIGINAL_LOCATION) {
xMarginsMinimap = rightMinimapMargin;
}
yMarginsMinimap = bottomMinimapMargin;
} else {
xMarginsMinimap = 0;
yMarginsMinimap = 0;
}
s16 mapWidth = isInDungeon ? R_DGN_MINIMAP_X : R_OW_MINIMAP_X;
s16 mapStartPosX = isInDungeon ? 96 : gMapData->owMinimapWidth[R_MAP_INDEX];
OPEN_DISPS(gPlayState->state.gfxCtx);
Gfx_SetupDL_42Overlay(gPlayState->state.gfxCtx);
for (auto& compassIcon : compassIcons) {
gSPMatrix(OVERLAY_DISP++, &gMtxClear, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0,
PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0);
gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 255);
gDPSetCombineMode(OVERLAY_DISP++, G_CC_PRIMITIVE, G_CC_PRIMITIVE);
// The compass offset value is a factor of 10 compared to N64 screen pixels and originates in the center of
// the screen Compute the additional mirror offset value by normalizing the original offset position and
// taking it's distance to the center of the map, duplicating that result and casting back to a factor of 10
s16 mirrorOffset =
((mapWidth / 2) - ((R_COMPASS_OFFSET_X / 10) - (mapStartPosX - SCREEN_WIDTH / 2))) * 2 * 10;
s16 tempX = (s16)compassIcon.pos.x;
s16 tempZ = (s16)compassIcon.pos.z;
tempX /= R_COMPASS_SCALE_X * (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0) ? -1 : 1);
tempZ /= R_COMPASS_SCALE_Y;
s16 tempXOffset =
R_COMPASS_OFFSET_X + (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0) ? mirrorOffset : 0);
if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosType"), 0) != ORIGINAL_LOCATION) {
if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosType"), 0) == ANCHOR_LEFT) {
if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.UseMargins"), 0) != 0) {
xMarginsMinimap = leftMinimapMargin;
};
Matrix_Translate(
OTRGetDimensionFromLeftEdge((tempXOffset + (xMarginsMinimap * 10) + tempX +
(CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosX"), 0) * 10)) /
10.0f),
(R_COMPASS_OFFSET_Y + ((yMarginsMinimap * 10) * -1) - tempZ +
((CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosY"), 0) * 10) * -1)) /
10.0f,
0.0f, MTXMODE_NEW);
} else if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosType"), 0) == ANCHOR_RIGHT) {
if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.UseMargins"), 0) != 0) {
xMarginsMinimap = rightMinimapMargin;
};
Matrix_Translate(
OTRGetDimensionFromRightEdge((tempXOffset + (xMarginsMinimap * 10) + tempX +
(CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosX"), 0) * 10)) /
10.0f),
(R_COMPASS_OFFSET_Y + ((yMarginsMinimap * 10) * -1) - tempZ +
((CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosY"), 0) * 10) * -1)) /
10.0f,
0.0f, MTXMODE_NEW);
} else if (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosType"), 0) == ANCHOR_NONE) {
Matrix_Translate(
(tempXOffset + tempX + (CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosX"), 0) * 10) / 10.0f),
(R_COMPASS_OFFSET_Y + ((yMarginsMinimap * 10) * -1) - tempZ +
((CVarGetInteger(CVAR_COSMETIC("HUD.Minimap.PosY"), 0) * 10) * -1)) /
10.0f,
0.0f, MTXMODE_NEW);
}
} else {
Matrix_Translate(OTRGetDimensionFromRightEdge((tempXOffset + (xMarginsMinimap * 10) + tempX) / 10.0f),
(R_COMPASS_OFFSET_Y + ((yMarginsMinimap * 10) * -1) - tempZ) / 10.0f, 0.0f,
MTXMODE_NEW);
}
Matrix_Scale(compassIcon.scale, compassIcon.scale, compassIcon.scale, MTXMODE_APPLY);
Matrix_RotateX(-1.6f, MTXMODE_APPLY);
s16 rotation = ((0x7FFF - compassIcon.rot.y) / 0x400) *
(CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0) ? -1 : 1);
Matrix_RotateY(rotation / 10.0f, MTXMODE_APPLY);
gSPMatrix(OVERLAY_DISP++, MATRIX_NEWMTX(gPlayState->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gDPSetPrimColor(OVERLAY_DISP++, 0, 0xFF, compassIcon.color.r, compassIcon.color.g, compassIcon.color.b,
255);
gSPDisplayList(OVERLAY_DISP++, (Gfx*)gCompassArrowDL);
}
CLOSE_DISPS(gPlayState->state.gfxCtx);
});
// #endregion
}

View File

@@ -62,6 +62,7 @@ inline void from_json(const json& j, AnchorClient& client) {
client.isSaveLoaded = j.value("isSaveLoaded", false);
client.isGameComplete = j.value("isGameComplete", false);
client.sceneNum = j.value("sceneNum", (s16)SCENE_ID_MAX);
client.curRoomNum = j.value("curRoomNum", (s8)-1);
client.entranceIndex = j.value("entranceIndex", (s32)0);
client.self = j.value("self", false);
}

View File

@@ -139,6 +139,24 @@ void AnchorMainMenu(WidgetInfo& info) {
ImGui::SameLine();
UIWidgets::WindowButton("Toggle Anchor Room Window", CVAR_WINDOW("AnchorRoom"), SohGui::mAnchorRoomWindow);
ImGui::Spacing();
bool hideLocations = Anchor::Instance->roomState.showLocationsMode == 0;
ImGui::BeginDisabled(hideLocations);
UIWidgets::CVarCheckbox(
"Show Other Players on Minimap", CVAR_REMOTE_ANCHOR("ShowOtherPlayersOnMinimap"),
UIWidgets::CheckboxOptions()
.Color(THEME_COLOR)
.DefaultValue(true)
.Tooltip(!hideLocations
? "Other players will appear on the minimap in areas where you have the compass. "
"Visibility is restricted according to the Show Locations mode for the room."
: "Cannot show other players because the room's Show Locations mode is set to None."));
ImGui::EndDisabled();
ImGui::Spacing();
if (!SohGui::mAnchorRoomWindow->IsVisible()) {
SohGui::mAnchorRoomWindow->DrawElement();
}

View File

@@ -33,12 +33,14 @@ nlohmann::json Anchor::PrepClientState() {
payload["isSaveLoaded"] = true;
payload["isGameComplete"] = gSaveContext.ship.stats.gameComplete;
payload["sceneNum"] = gPlayState->sceneNum;
payload["curRoomNum"] = gPlayState->roomCtx.curRoom.num;
payload["entranceIndex"] = gSaveContext.entranceIndex;
} else {
payload["seed"] = 0;
payload["isSaveLoaded"] = false;
payload["isGameComplete"] = false;
payload["sceneNum"] = SCENE_ID_MAX;
payload["curRoomNum"] = -1;
payload["entranceIndex"] = 0x00;
}
@@ -68,6 +70,7 @@ void Anchor::HandlePacket_UpdateClientState(nlohmann::json payload) {
clients[clientId].isSaveLoaded = client.isSaveLoaded;
clients[clientId].isGameComplete = client.isGameComplete;
clients[clientId].sceneNum = client.sceneNum;
clients[clientId].curRoomNum = client.curRoomNum;
clients[clientId].entranceIndex = client.entranceIndex;
}
}

View File

@@ -7,6 +7,7 @@
#include <assert.h>
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
MapData* gMapData;
@@ -761,6 +762,10 @@ void Minimap_DrawCompassIcons(PlayState* play) {
}
CLOSE_DISPS(play->state.gfxCtx);
if (play->interfaceCtx.minimapAlpha >= 0xAA) {
GameInteractor_ExecuteOnMinimapDrawCompassIcons();
}
}
void Minimap_Draw(PlayState* play) {