using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using System.Numerics; namespace MareSynchronos.UI; internal partial class CharaDataHubUi { private void DrawNearbyPoses() { _uiSharedService.BigText("Poses Nearby"); DrawHelpFoldout("This tab will show you all Shared World Poses nearby you." + Environment.NewLine + Environment.NewLine + "Shared World Poses are poses in character data that have world data attached to them and are set to shared. " + "This means that all data that is in 'Shared with You' that has a pose with world data attached to it will be shown here if you are nearby." + Environment.NewLine + "By default all poses that are shared will be shown. Poses taken in housing areas will by default only be shown on the correct world and location." + Environment.NewLine + Environment.NewLine + "Shared World Poses will appear in the world as floating wisps, as well as in the list below. You can mouse over a Shared World Pose in the list for it to get highlighted in the world." + Environment.NewLine + Environment.NewLine + "You can apply Shared World Poses to yourself or spawn the associated character to pose with them." + Environment.NewLine + Environment.NewLine + "You can adjust the filter and change further settings in the 'Settings & Filter' foldout."); UiSharedService.DrawTree("Settings & Filters", () => { string filterByUser = _charaDataNearbyManager.UserNoteFilter; if (ImGui.InputTextWithHint("##filterbyuser", "Filter by User", ref filterByUser, 50)) { _charaDataNearbyManager.UserNoteFilter = filterByUser; } bool onlyCurrent = _configService.Current.NearbyOwnServerOnly; if (ImGui.Checkbox("Only show Poses on current world", ref onlyCurrent)) { _configService.Current.NearbyOwnServerOnly = onlyCurrent; _configService.Save(); } _uiSharedService.DrawHelpText("Show the location of shared Poses with World Data from current world only"); bool showOwn = _configService.Current.NearbyShowOwnData; if (ImGui.Checkbox("Also show your own data", ref showOwn)) { _configService.Current.NearbyShowOwnData = showOwn; _configService.Save(); } _uiSharedService.DrawHelpText("Show your own Poses as well"); bool ignoreHousing = _configService.Current.NearbyIgnoreHousingLimitations; if (ImGui.Checkbox("Ignore Housing Limitations", ref ignoreHousing)) { _configService.Current.NearbyIgnoreHousingLimitations = ignoreHousing; _configService.Save(); } _uiSharedService.DrawHelpText("Display all poses in their location regardless of housing limitations. (Ignoring Ward, Plot, Room)" + UiSharedService.TooltipSeparator + "Note: Poses that utilize housing props, furniture, etc. will not be displayed correctly if not spawned in the right location."); bool showWisps = _configService.Current.NearbyDrawWisps; if (ImGui.Checkbox("Show Pose Wisps in the overworld", ref showWisps)) { _configService.Current.NearbyDrawWisps = showWisps; _configService.Save(); } _uiSharedService.DrawHelpText("Draw floating wisps where other's poses are in the world."); int poseDetectionDistance = _configService.Current.NearbyDistanceFilter; ImGui.SetNextItemWidth(100); if (ImGui.SliderInt("Detection Distance", ref poseDetectionDistance, 5, 1000)) { _configService.Current.NearbyDistanceFilter = poseDetectionDistance; _configService.Save(); } _uiSharedService.DrawHelpText("Maximum distance in which poses will be shown. Set it to the maximum if you want to see all poses on the current map."); bool alwaysShow = _configService.Current.NearbyShowAlways; if (ImGui.Checkbox("Keep active outside Poses Nearby tab", ref alwaysShow)) { _configService.Current.NearbyShowAlways = alwaysShow; _configService.Save(); } _uiSharedService.DrawHelpText("Continue the calculation of position of wisps etc. active outside of the 'Poses Nearby' tab." + UiSharedService.TooltipSeparator + "Note: The wisps etc. will disappear during combat and performing."); }); if (!_uiSharedService.IsInGpose) { ImGuiHelpers.ScaledDummy(5); UiSharedService.DrawGroupedCenteredColorText("Spawning and applying pose data is only available in GPose.", ImGuiColors.DalamudYellow); ImGuiHelpers.ScaledDummy(5); } DrawUpdateSharedDataButton(); UiSharedService.DistanceSeparator(); using var child = ImRaii.Child("nearbyPosesChild", new(0, 0), false, ImGuiWindowFlags.AlwaysAutoResize); ImGuiHelpers.ScaledDummy(3f); using var indent = ImRaii.PushIndent(5f); if (_charaDataNearbyManager.NearbyData.Count == 0) { UiSharedService.DrawGroupedCenteredColorText("No Shared World Poses found nearby.", ImGuiColors.DalamudYellow); } bool wasAnythingHovered = false; int i = 0; foreach (var pose in _charaDataNearbyManager.NearbyData.OrderBy(v => v.Value.Distance)) { using var poseId = ImRaii.PushId("nearbyPose" + (i++)); var pos = ImGui.GetCursorPos(); var circleDiameter = 60f; var circleOriginX = ImGui.GetWindowContentRegionMax().X - circleDiameter - pos.X; float circleOffsetY = 0; UiSharedService.DrawGrouped(() => { string? userNote = _serverConfigurationManager.GetNoteForUid(pose.Key.MetaInfo.Uploader.UID); var noteText = pose.Key.MetaInfo.IsOwnData ? "YOU" : (userNote == null ? pose.Key.MetaInfo.Uploader.AliasOrUID : $"{userNote} ({pose.Key.MetaInfo.Uploader.AliasOrUID})"); ImGui.TextUnformatted("Pose by"); ImGui.SameLine(); UiSharedService.ColorText(noteText, ImGuiColors.ParsedGreen); using (ImRaii.Group()) { UiSharedService.ColorText("Character Data Description", ImGuiColors.DalamudGrey); ImGui.SameLine(); _uiSharedService.IconText(FontAwesomeIcon.ExternalLinkAlt, ImGuiColors.DalamudGrey); } UiSharedService.AttachToolTip(pose.Key.MetaInfo.Description); UiSharedService.ColorText("Description", ImGuiColors.DalamudGrey); ImGui.SameLine(); UiSharedService.TextWrapped(pose.Key.Description ?? "No Pose Description was set", circleOriginX); var posAfterGroup = ImGui.GetCursorPos(); var groupHeightCenter = (posAfterGroup.Y - pos.Y) / 2; circleOffsetY = (groupHeightCenter - circleDiameter / 2); if (circleOffsetY < 0) circleOffsetY = 0; ImGui.SetCursorPos(new Vector2(circleOriginX, pos.Y)); ImGui.Dummy(new Vector2(circleDiameter, circleDiameter)); UiSharedService.AttachToolTip("Click to open corresponding map and set map marker" + UiSharedService.TooltipSeparator + pose.Key.WorldDataDescriptor); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { _dalamudUtilService.SetMarkerAndOpenMap(pose.Key.Position, pose.Key.Map); } ImGui.SetCursorPos(posAfterGroup); if (_uiSharedService.IsInGpose) { GposePoseAction(() => { if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowRight, "Apply Pose")) { _charaDataManager.ApplyFullPoseDataToGposeTarget(pose.Key); } }, $"Apply pose and position to {CharaName(_gposeTarget)}", _hasValidGposeTarget); ImGui.SameLine(); GposeMetaInfoAction((_) => { if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, "Spawn and Pose")) { _charaDataManager.SpawnAndApplyWorldTransform(pose.Key.MetaInfo, pose.Key); } }, "Spawn actor and apply pose and position", pose.Key.MetaInfo, _hasValidGposeTarget, true); } }); if (ImGui.IsItemHovered()) { wasAnythingHovered = true; _nearbyHovered = pose.Key; } var drawList = ImGui.GetWindowDrawList(); var circleRadius = circleDiameter / 2f; var windowPos = ImGui.GetWindowPos(); var scrollX = ImGui.GetScrollX(); var scrollY = ImGui.GetScrollY(); var circleCenter = new Vector2(windowPos.X + circleOriginX + circleRadius - scrollX, windowPos.Y + pos.Y + circleRadius + circleOffsetY - scrollY); var rads = pose.Value.Direction * (Math.PI / 180); float halfConeAngleRadians = 15f * (float)Math.PI / 180f; Vector2 baseDir1 = new Vector2((float)Math.Sin(rads - halfConeAngleRadians), -(float)Math.Cos(rads - halfConeAngleRadians)); Vector2 baseDir2 = new Vector2((float)Math.Sin(rads + halfConeAngleRadians), -(float)Math.Cos(rads + halfConeAngleRadians)); Vector2 coneBase1 = circleCenter + baseDir1 * circleRadius; Vector2 coneBase2 = circleCenter + baseDir2 * circleRadius; // Draw the cone as a filled triangle drawList.AddTriangleFilled(circleCenter, coneBase1, coneBase2, UiSharedService.Color(ImGuiColors.ParsedGreen)); drawList.AddCircle(circleCenter, circleDiameter / 2, UiSharedService.Color(ImGuiColors.DalamudWhite), 360, 2); var distance = pose.Value.Distance.ToString("0.0") + "y"; var textSize = ImGui.CalcTextSize(distance); drawList.AddText(new Vector2(circleCenter.X - textSize.X / 2, circleCenter.Y + textSize.Y / 3f), UiSharedService.Color(ImGuiColors.DalamudWhite), distance); ImGuiHelpers.ScaledDummy(3); } if (!wasAnythingHovered) _nearbyHovered = null; _charaDataNearbyManager.SetHoveredVfx(_nearbyHovered); } private void DrawUpdateSharedDataButton() { using (ImRaii.Disabled(_charaDataManager.GetAllDataTask != null || (_charaDataManager.GetSharedWithYouTimeoutTask != null && !_charaDataManager.GetSharedWithYouTimeoutTask.IsCompleted))) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Update Data Shared With You")) { _ = _charaDataManager.GetAllSharedData(_disposalCts.Token).ContinueWith(u => UpdateFilteredItems()); } } if (_charaDataManager.GetSharedWithYouTimeoutTask != null && !_charaDataManager.GetSharedWithYouTimeoutTask.IsCompleted) { UiSharedService.AttachToolTip("You can only refresh all character data from server every minute. Please wait."); } } }