Merge remote-tracking branch 'origin/2.0.2-Location' into 2.0.0-crashing-bugfixes

# Conflicts:
#	LightlessSync/Services/DalamudUtilService.cs
This commit is contained in:
choco
2025-12-30 14:55:42 +01:00
15 changed files with 226 additions and 147 deletions

View File

@@ -37,6 +37,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
<PackageReference Include="Glamourer.Api" Version="2.8.0" /> <PackageReference Include="Glamourer.Api" Version="2.8.0" />
<PackageReference Include="NReco.Logging.File" Version="1.3.1" /> <PackageReference Include="NReco.Logging.File" Version="1.3.1" />

View File

@@ -10,7 +10,6 @@ using LightlessSync.UI;
using LightlessSync.UI.Services; using LightlessSync.UI.Services;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -172,9 +171,8 @@ internal class ContextMenuService : IHostedService
_logger.LogTrace("Cannot send pair request to {TargetName}@{World} while in PvP or GPose.", target.TargetName, target.TargetHomeWorld.RowId); _logger.LogTrace("Cannot send pair request to {TargetName}@{World} while in PvP or GPose.", target.TargetName, target.TargetHomeWorld.RowId);
return; return;
} }
var world = GetWorld(target.TargetHomeWorld.RowId); if (!IsWorldValid(target.TargetHomeWorld.RowId))
if (!IsWorldValid(world))
{ {
_logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId); _logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId);
return; return;
@@ -226,9 +224,8 @@ internal class ContextMenuService : IHostedService
{ {
if (args.Target is not MenuTargetDefault target) if (args.Target is not MenuTargetDefault target)
return; return;
var world = GetWorld(target.TargetHomeWorld.RowId); if (!target.TargetHomeWorld.IsValid || !IsWorldValid(target.TargetHomeWorld.RowId))
if (!IsWorldValid(world))
return; return;
try try
@@ -237,7 +234,7 @@ internal class ContextMenuService : IHostedService
if (targetData == null || targetData.Address == nint.Zero) if (targetData == null || targetData.Address == nint.Zero)
{ {
_logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name); _logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, target.TargetHomeWorld.Value.Name);
return; return;
} }
@@ -252,7 +249,7 @@ internal class ContextMenuService : IHostedService
} }
// Notify in chat when NotificationService is disabled // Notify in chat when NotificationService is disabled
NotifyInChat($"Pair request sent to {target.TargetName}@{world.Name}.", NotificationType.Info); NotifyInChat($"Pair request sent to {target.TargetName}@{target.TargetHomeWorld.Value.Name}.", NotificationType.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -312,37 +309,8 @@ internal class ContextMenuService : IHostedService
p.HomeWorld.RowId == target.TargetHomeWorld.RowId); p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
} }
private World GetWorld(uint worldId) private bool IsWorldValid(uint worldId)
{ {
var sheet = _gameData.GetExcelSheet<World>()!; return _dalamudUtil.WorldData.Value.ContainsKey((ushort)worldId);
var luminaWorlds = sheet.Where(x =>
{
var dc = x.DataCenter.ValueNullable;
var name = x.Name.ExtractText();
var internalName = x.InternalName.ExtractText();
if (dc == null || dc.Value.Region == 0 || string.IsNullOrWhiteSpace(dc.Value.Name.ExtractText()))
return false;
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(internalName))
return false;
if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal))
return false;
return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name);
});
return luminaWorlds.FirstOrDefault(x => x.RowId == worldId);
}
private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter);
private static bool IsChineseJapaneseKoreanCharacter(char c) => c >= 0x4E00 && c <= 0x9FFF;
public static bool IsWorldValid(World world)
{
var name = world.Name.ToString();
return !string.IsNullOrWhiteSpace(name) && char.IsUpper(name[0]);
} }
} }

View File

@@ -1,11 +1,13 @@
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using LightlessSync.API.Dto.CharaData; using LightlessSync.API.Dto.CharaData;
@@ -26,6 +28,7 @@ using System.Text;
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind; using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
using Map = Lumina.Excel.Sheets.Map;
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags; using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
namespace LightlessSync.Services; namespace LightlessSync.Services;
@@ -57,6 +60,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
private string _lastGlobalBlockReason = string.Empty; private string _lastGlobalBlockReason = string.Empty;
private ushort _lastZone = 0; private ushort _lastZone = 0;
private ushort _lastWorldId = 0; private ushort _lastWorldId = 0;
private uint _lastMapId = 0;
private bool _sentBetweenAreas = false; private bool _sentBetweenAreas = false;
private Lazy<ulong> _cid; private Lazy<ulong> _cid;
@@ -86,7 +90,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
WorldData = new(() => WorldData = new(() =>
{ {
return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(clientLanguage)! return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(clientLanguage)!
.Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0]))) .Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0])
|| w is { RowId: > 1000, Region: 101 or 201 }))
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
}); });
JobData = new(() => JobData = new(() =>
@@ -659,7 +664,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
var location = new LocationInfo(); var location = new LocationInfo();
location.ServerId = _playerState.CurrentWorld.RowId; location.ServerId = _playerState.CurrentWorld.RowId;
//location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; //TODO:Need API update first location.InstanceId = UIState.Instance()->PublicInstance.InstanceId;
location.TerritoryId = _clientState.TerritoryType; location.TerritoryId = _clientState.TerritoryType;
location.MapId = _clientState.MapId; location.MapId = _clientState.MapId;
if (houseMan != null) if (houseMan != null)
@@ -685,7 +690,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
var outside = houseMan->OutdoorTerritory; var outside = houseMan->OutdoorTerritory;
var house = outside->HouseId; var house = outside->HouseId;
location.WardId = house.WardIndex + 1u; location.WardId = house.WardIndex + 1u;
location.HouseId = (uint)houseMan->GetCurrentPlot() + 1; //location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
location.DivisionId = houseMan->GetCurrentDivision(); location.DivisionId = houseMan->GetCurrentDivision();
} }
//_logger.LogWarning(LocationToString(location)); //_logger.LogWarning(LocationToString(location));
@@ -713,10 +718,10 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
str += $" - {MapData.Value[(ushort)location.MapId].MapName}"; str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
} }
// if (location.InstanceId is not 0) if (location.InstanceId is not 0)
// { {
// str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString(); str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
// } }
if (location.WardId is not 0) if (location.WardId is not 0)
{ {
@@ -1157,6 +1162,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new ZoneSwitchEndMessage()); Mediator.Publish(new ZoneSwitchEndMessage());
Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas)));
} }
//Map
if (!_sentBetweenAreas)
{
var mapid = _clientState.MapId;
if (mapid != _lastMapId)
{
_lastMapId = mapid;
Mediator.Publish(new MapChangedMessage(mapid));
}
}
var localPlayer = _objectTable.LocalPlayer; var localPlayer = _objectTable.LocalPlayer;
if (localPlayer != null) if (localPlayer != null)

View File

@@ -3,7 +3,9 @@ using LightlessSync.API.Dto.CharaData;
using LightlessSync.API.Dto.User; using LightlessSync.API.Dto.User;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace LightlessSync.Services namespace LightlessSync.Services
{ {
@@ -11,7 +13,9 @@ namespace LightlessSync.Services
{ {
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private readonly ApiController _apiController; private readonly ApiController _apiController;
private Dictionary<string, LocationInfo> _locations = []; private IMemoryCache _locations = new MemoryCache(new MemoryCacheOptions());
private IMemoryCache _sharingStatus = new MemoryCache(new MemoryCacheOptions());
private CancellationTokenSource _resetToken = new CancellationTokenSource();
public LocationShareService(ILogger<LocationShareService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator) public LocationShareService(ILogger<LocationShareService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator)
{ {
@@ -19,37 +23,58 @@ namespace LightlessSync.Services
_apiController = apiController; _apiController = apiController;
Mediator.Subscribe<DisconnectedMessage>(this, (msg) => _locations.Clear()); Mediator.Subscribe<DisconnectedMessage>(this, (msg) =>
{
_resetToken.Cancel();
_resetToken.Dispose();
_resetToken = new CancellationTokenSource();
});
Mediator.Subscribe<ConnectedMessage>(this, (msg) => Mediator.Subscribe<ConnectedMessage>(this, (msg) =>
{ {
_ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, apiController.DisplayName), _dalamudUtilService.GetMapData())); _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, apiController.DisplayName), _dalamudUtilService.GetMapData()));
_ = RequestAllLocation(); _ = RequestAllLocation();
} ); } );
Mediator.Subscribe<LocationMessage>(this, UpdateLocationList); Mediator.Subscribe<LocationSharingMessage>(this, UpdateLocationList);
Mediator.Subscribe<ZoneSwitchEndMessage>(this, Mediator.Subscribe<MapChangedMessage>(this,
msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData()))); msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData())));
} }
private void UpdateLocationList(LocationMessage msg) private void UpdateLocationList(LocationSharingMessage msg)
{ {
if (_locations.ContainsKey(msg.Uid) && msg.LocationInfo.ServerId is 0) if (_locations.TryGetValue(msg.User.UID, out _) && msg.LocationInfo.ServerId is 0)
{ {
_locations.Remove(msg.Uid); _locations.Remove(msg.User.UID);
return; return;
} }
if ( msg.LocationInfo.ServerId is not 0 && !_locations.TryAdd(msg.Uid, msg.LocationInfo)) if ( msg.LocationInfo.ServerId is not 0 && msg.ExpireAt > DateTime.UtcNow)
{ {
_locations[msg.Uid] = msg.LocationInfo; AddLocationInfo(msg.User.UID, msg.LocationInfo, msg.ExpireAt);
} }
} }
private void AddLocationInfo(string uid, LocationInfo location, DateTimeOffset expireAt)
{
var options = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(expireAt)
.AddExpirationToken(new CancellationChangeToken(_resetToken.Token));
_locations.Set(uid, location, options);
}
private async Task RequestAllLocation() private async Task RequestAllLocation()
{ {
try try
{ {
var data = await _apiController.RequestAllLocationInfo().ConfigureAwait(false); var (data, status) = await _apiController.RequestAllLocationInfo().ConfigureAwait(false);
_locations = data.ToDictionary(x => x.user.UID, x => x.location, StringComparer.Ordinal); foreach (var dto in data)
{
AddLocationInfo(dto.LocationDto.User.UID, dto.LocationDto.Location, dto.ExpireAt);
}
foreach (var dto in status)
{
AddStatus(dto.User.UID, dto.ExpireAt);
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -58,11 +83,19 @@ namespace LightlessSync.Services
} }
} }
private void AddStatus(string uid, DateTimeOffset expireAt)
{
var options = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(expireAt)
.AddExpirationToken(new CancellationChangeToken(_resetToken.Token));
_sharingStatus.Set(uid, expireAt, options);
}
public string GetUserLocation(string uid) public string GetUserLocation(string uid)
{ {
try try
{ {
if (_locations.TryGetValue(uid, out var location)) if (_locations.TryGetValue<LocationInfo>(uid, out var location))
{ {
return _dalamudUtilService.LocationToString(location); return _dalamudUtilService.LocationToString(location);
} }
@@ -74,5 +107,31 @@ namespace LightlessSync.Services
throw; throw;
} }
} }
public DateTimeOffset GetSharingStatus(string uid)
{
try
{
if (_sharingStatus.TryGetValue<DateTimeOffset>(uid, out var expireAt))
{
return expireAt;
}
return DateTimeOffset.MinValue;
}
catch (Exception e)
{
Logger.LogError(e,"GetSharingStatus error : ");
throw;
}
}
public void UpdateSharingStatus(List<string> users, DateTimeOffset expireAt)
{
foreach (var user in users)
{
AddStatus(user, expireAt);
}
}
} }
} }

View File

@@ -135,7 +135,7 @@ public record ChatChannelsUpdated : MessageBase;
public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Message) : MessageBase; public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Message) : MessageBase;
public record GroupCollectionChangedMessage : MessageBase; public record GroupCollectionChangedMessage : MessageBase;
public record OpenUserProfileMessage(UserData User) : MessageBase; public record OpenUserProfileMessage(UserData User) : MessageBase;
public record LocationMessage(string Uid, LocationInfo LocationInfo) : MessageBase; public record LocationSharingMessage(UserData User, LocationInfo LocationInfo, DateTimeOffset ExpireAt) : MessageBase;
public record MapChangedMessage(uint MapId) : MessageBase;
#pragma warning restore S2094 #pragma warning restore S2094
#pragma warning restore MA0048 // File name must match type name #pragma warning restore MA0048 // File name must match type name

View File

@@ -131,7 +131,6 @@ public class DrawFolderGroup : DrawFolderBase
bool disableSounds = perm.IsDisableSounds(); bool disableSounds = perm.IsDisableSounds();
bool disableAnims = perm.IsDisableAnimations(); bool disableAnims = perm.IsDisableAnimations();
bool disableVfx = perm.IsDisableVFX(); bool disableVfx = perm.IsDisableVFX();
bool shareLocation = perm.IsSharingLocation();
if ((_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations() != disableAnims if ((_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations() != disableAnims
|| _groupFullInfoDto.GroupPermissions.IsPreferDisableSounds() != disableSounds || _groupFullInfoDto.GroupPermissions.IsPreferDisableSounds() != disableSounds
@@ -165,13 +164,6 @@ public class DrawFolderGroup : DrawFolderBase
_ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm));
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
if (_uiSharedService.IconTextButton(!shareLocation ? FontAwesomeIcon.Globe : FontAwesomeIcon.StopCircle, !shareLocation ? "Share your location to all users in Syncshell" : "STOP Share your location to all users in Syncshell", menuWidth, true))
{
perm.SetShareLocation(!shareLocation);
_ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm));
ImGui.CloseCurrentPopup();
}
if (IsModerator || IsOwner) if (IsModerator || IsOwner)
{ {

View File

@@ -220,16 +220,47 @@ public class DrawUserPair
} }
UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty)); UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty));
var isShareingLocation = _pair.UserPair!.OwnPermissions.IsSharingLocation(); ImGui.SetCursorPosX(10f);
string isShareingLocationText = isShareingLocation ? "Disable location sharing" : "Enable location sharing"; _uiSharedService.IconText(FontAwesomeIcon.Globe);
var isShareingLocationIcon = isShareingLocation ? FontAwesomeIcon.StopCircle : FontAwesomeIcon.Globe; ImGui.SameLine();
if (_uiSharedService.IconTextButton(isShareingLocationIcon, isShareingLocationText, _menuWidth, true)) if (ImGui.BeginMenu("Toggle Location sharing"))
{ {
var permissions = _pair.UserPair.OwnPermissions; if (ImGui.MenuItem("Share for 30 Mins"))
permissions.SetShareLocation(!isShareingLocation); {
_ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions)); _ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.UtcNow.AddMinutes(30));
}
if (ImGui.MenuItem("Share for 1 Hour"))
{
_ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.UtcNow.AddHours(1));
}
if (ImGui.MenuItem("Share for 3 Hours"))
{
_ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.UtcNow.AddHours(3));
}
if (ImGui.MenuItem("Share until manually stop"))
{
_ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.MaxValue);
}
ImGui.Separator();
if (ImGui.MenuItem("Stop Sharing"))
{
_ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.MinValue);
}
ImGui.EndMenu();
}
}
private async Task ToggleLocationSharing(List<string> users, DateTimeOffset expireAt)
{
var updated = await _apiController.ToggleLocationSharing(new LocationSharingToggleDto(users, expireAt)).ConfigureAwait(false);
if (updated)
{
_locationShareService.UpdateSharingStatus(users, expireAt);
} }
UiSharedService.AttachToolTip("Changes location sharing permissions with this user." + (individual ? individualText : string.Empty));
} }
private void DrawIndividualMenu() private void DrawIndividualMenu()
@@ -581,7 +612,6 @@ public class DrawUserPair
: UiSharedService.TooltipSeparator + "Hold CTRL to enable preferred permissions while pausing." + Environment.NewLine + "This will leave this pair paused even if unpausing syncshells including this pair.")) : UiSharedService.TooltipSeparator + "Hold CTRL to enable preferred permissions while pausing." + Environment.NewLine + "This will leave this pair paused even if unpausing syncshells including this pair."))
: "Resume pairing with " + _pair.UserData.AliasOrUID); : "Resume pairing with " + _pair.UserData.AliasOrUID);
//Location sharing
if (_pair.IsPaired) if (_pair.IsPaired)
{ {
var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false); var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false);
@@ -590,31 +620,32 @@ public class DrawUserPair
var individualIsSticky = _pair.UserPair!.OwnPermissions.IsSticky(); var individualIsSticky = _pair.UserPair!.OwnPermissions.IsSticky();
var individualIcon = individualIsSticky ? FontAwesomeIcon.ArrowCircleUp : FontAwesomeIcon.InfoCircle; var individualIcon = individualIsSticky ? FontAwesomeIcon.ArrowCircleUp : FontAwesomeIcon.InfoCircle;
var shareLocationIcon = FontAwesomeIcon.Globe; var shareLocationIcon = FontAwesomeIcon.Globe;
var shareLocation = _pair.UserPair?.OwnPermissions.IsSharingLocation() ?? false; var location = _locationShareService.GetUserLocation(_pair.UserPair!.User.UID);
var shareLocationOther = _pair.UserPair?.OtherPermissions.IsSharingLocation() ?? false; var shareLocation = !string.IsNullOrEmpty(location);
var expireAt = _locationShareService.GetSharingStatus(_pair.UserPair!.User.UID);
var shareLocationToOther = expireAt > DateTimeOffset.UtcNow;
var shareColor = shareLocation switch var shareColor = shareLocation switch
{ {
true when shareLocationOther => UIColors.Get("LightlessGreen"), true when shareLocationToOther => UIColors.Get("LightlessGreen"),
false when shareLocationOther => UIColors.Get("LightlessBlue"), true when !shareLocationToOther => UIColors.Get("LightlessBlue"),
_ => UIColors.Get("LightlessYellow"), _ => UIColors.Get("LightlessYellow"),
}; };
if (shareLocation || shareLocationOther) if (shareLocation || shareLocationToOther)
{ {
currentRightSide -= (_uiSharedService.GetIconSize(shareLocationIcon).X + spacingX); currentRightSide -= (_uiSharedService.GetIconSize(shareLocationIcon).X + spacingX);
ImGui.SameLine(currentRightSide); ImGui.SameLine(currentRightSide);
using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationOther)) using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther))
_uiSharedService.IconText(shareLocationIcon); _uiSharedService.IconText(shareLocationIcon);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
ImGui.BeginTooltip(); ImGui.BeginTooltip();
if (shareLocationOther)
if (_pair.IsOnline)
{ {
var location = _locationShareService.GetUserLocation(_pair.UserPair!.User.UID); if (shareLocation)
if (_pair.IsOnline)
{ {
if (!string.IsNullOrEmpty(location)) if (!string.IsNullOrEmpty(location))
{ {
@@ -624,27 +655,31 @@ public class DrawUserPair
} }
else else
{ {
ImGui.TextUnformatted("Location info not updated, reconnect or waiting for zone-changing."); ImGui.TextUnformatted("Location info not updated, reconnect or wait for update.");
} }
} }
else else
{ {
ImGui.TextUnformatted("User not onlineㄟ( ▔, ▔ )ㄏ"); ImGui.TextUnformatted("NOT Sharing location with you. o(TヘTo)");
} }
} }
else else
{ {
ImGui.TextUnformatted("NOT Sharing location with you.(⊙x⊙;)"); ImGui.TextUnformatted("User not online. (´・ω・`)?");
} }
ImGui.Separator(); ImGui.Separator();
if (shareLocation) if (shareLocationToOther)
{ {
ImGui.TextUnformatted("Sharing your location.ヾ(•ω•`)o"); ImGui.TextUnformatted("Sharing your location. ヾ(•ω•`)o");
if (expireAt != DateTimeOffset.MaxValue)
{
ImGui.TextUnformatted("Expires at " + expireAt.ToLocalTime().ToString("g"));
}
} }
else else
{ {
ImGui.TextUnformatted("NOT sharing your location.(´。_。`)"); ImGui.TextUnformatted("NOT sharing your location.  ̄へ ̄");
} }
ImGui.EndTooltip(); ImGui.EndTooltip();
} }

View File

@@ -2183,7 +2183,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
bool toggleClicked = false; bool toggleClicked = false;
if (showToggle) if (showToggle)
{ {
var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft; var icon = !isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
Vector2 iconSize; Vector2 iconSize;
using (_uiSharedService.IconFont.Push()) using (_uiSharedService.IconFont.Push())
{ {

View File

@@ -28,8 +28,8 @@ public class DrawEntityFactory
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly LocationShareService _locationShareService;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly LocationShareService _locationShareService;
private readonly CharaDataManager _charaDataManager; private readonly CharaDataManager _charaDataManager;
private readonly SelectTagForPairUi _selectTagForPairUi; private readonly SelectTagForPairUi _selectTagForPairUi;
private readonly RenamePairTagUi _renamePairTagUi; private readonly RenamePairTagUi _renamePairTagUi;
@@ -53,8 +53,8 @@ public class DrawEntityFactory
ServerConfigurationManager serverConfigurationManager, ServerConfigurationManager serverConfigurationManager,
LightlessConfigService configService, LightlessConfigService configService,
UiSharedService uiSharedService, UiSharedService uiSharedService,
LocationShareService locationShareService,
PlayerPerformanceConfigService playerPerformanceConfigService, PlayerPerformanceConfigService playerPerformanceConfigService,
LocationShareService locationShareService,
CharaDataManager charaDataManager, CharaDataManager charaDataManager,
SelectTagForSyncshellUi selectTagForSyncshellUi, SelectTagForSyncshellUi selectTagForSyncshellUi,
RenameSyncshellTagUi renameSyncshellTagUi, RenameSyncshellTagUi renameSyncshellTagUi,
@@ -73,8 +73,8 @@ public class DrawEntityFactory
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_configService = configService; _configService = configService;
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_locationShareService = locationShareService;
_playerPerformanceConfigService = playerPerformanceConfigService; _playerPerformanceConfigService = playerPerformanceConfigService;
_locationShareService = locationShareService;
_charaDataManager = charaDataManager; _charaDataManager = charaDataManager;
_selectTagForSyncshellUi = selectTagForSyncshellUi; _selectTagForSyncshellUi = selectTagForSyncshellUi;
_renameSyncshellTagUi = renameSyncshellTagUi; _renameSyncshellTagUi = renameSyncshellTagUi;

View File

@@ -43,7 +43,6 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
var disableSounds = _ownPermissions.IsDisableSounds(); var disableSounds = _ownPermissions.IsDisableSounds();
var disableAnimations = _ownPermissions.IsDisableAnimations(); var disableAnimations = _ownPermissions.IsDisableAnimations();
var disableVfx = _ownPermissions.IsDisableVFX(); var disableVfx = _ownPermissions.IsDisableVFX();
var shareLocation = _ownPermissions.IsSharingLocation();
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
var indentSize = ImGui.GetFrameHeight() + style.ItemSpacing.X; var indentSize = ImGui.GetFrameHeight() + style.ItemSpacing.X;
@@ -71,7 +70,6 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
var otherDisableSounds = otherPerms.IsDisableSounds(); var otherDisableSounds = otherPerms.IsDisableSounds();
var otherDisableAnimations = otherPerms.IsDisableAnimations(); var otherDisableAnimations = otherPerms.IsDisableAnimations();
var otherDisableVFX = otherPerms.IsDisableVFX(); var otherDisableVFX = otherPerms.IsDisableVFX();
var otherShareLocation = otherPerms.IsSharingLocation();
using (ImRaii.PushIndent(indentSize, false)) using (ImRaii.PushIndent(indentSize, false))
{ {
@@ -126,24 +124,6 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherDisableVFX ? "not " : string.Empty) + "disabled VFX sync with you"); ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherDisableVFX ? "not " : string.Empty) + "disabled VFX sync with you");
} }
if (ImGui.Checkbox("Enable location Sharing", ref shareLocation))
{
_ownPermissions.SetShareLocation(shareLocation);
}
_uiSharedService.DrawHelpText("Enable location sharing will only effect your side." + UiSharedService.TooltipSeparator
+ "Note: this is NOT bidirectional, you can choose to share even others dont share with you.");
using (ImRaii.PushIndent(indentSize, false))
{
_uiSharedService.BooleanToColoredIcon(shareLocation, false);
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.Text((!shareLocation ? "Not" : string.Empty) + "sharing location with " + Pair.UserData.AliasOrUID + " .");
#if DEBUG
_uiSharedService.BooleanToColoredIcon(otherShareLocation, true);
#endif
}
ImGuiHelpers.ScaledDummy(0.5f); ImGuiHelpers.ScaledDummy(0.5f);
ImGui.Separator(); ImGui.Separator();

View File

@@ -28,6 +28,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private readonly TextureMetadataHelper _textureMetadataHelper; private readonly TextureMetadataHelper _textureMetadataHelper;
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams; private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
private readonly SemaphoreSlim _decompressGate =
new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2));
private volatile bool _disableDirectDownloads; private volatile bool _disableDirectDownloads;
private int _consecutiveDirectDownloadFailures; private int _consecutiveDirectDownloadFailures;
@@ -522,32 +524,57 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
try try
{ {
// sanity check length
if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue) if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue)
throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}"); throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}");
// safe cast after check
var len = checked((int)fileLengthBytes);
if (!replacementLookup.TryGetValue(fileHash, out var repl)) if (!replacementLookup.TryGetValue(fileHash, out var repl))
{ {
Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash); Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash);
// still need to skip bytes: fileBlockStream.Seek(len, SeekOrigin.Current);
var skip = checked((int)fileLengthBytes);
fileBlockStream.Position += skip;
continue; continue;
} }
// decompress
var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension); var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension);
Logger.LogTrace("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath);
Logger.LogDebug("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath); // read compressed data
var len = checked((int)fileLengthBytes);
var compressed = new byte[len]; var compressed = new byte[len];
await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false); await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false);
MungeBuffer(compressed); if (len == 0)
var decompressed = LZ4Wrapper.Unwrap(compressed); {
await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
continue;
}
await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false); MungeBuffer(compressed);
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
// limit concurrent decompressions
await _decompressGate.WaitAsync(ct).ConfigureAwait(false);
try
{
var sw = System.Diagnostics.Stopwatch.StartNew();
// decompress
var decompressed = LZ4Wrapper.Unwrap(compressed);
Logger.LogTrace("{dlName}: Unwrap {fileHash} took {ms}ms (compressed {c} bytes, decompressed {d} bytes)",
downloadLabel, fileHash, sw.ElapsedMilliseconds, compressed.Length, decompressed?.Length ?? -1);
// write to file
await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
}
finally
{
_decompressGate.Release();
}
} }
catch (EndOfStreamException) catch (EndOfStreamException)
{ {
@@ -605,20 +632,16 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
.. await FilesGetSizes(hashes, ct).ConfigureAwait(false), .. await FilesGetSizes(hashes, ct).ConfigureAwait(false),
]; ];
Logger.LogDebug("Files with size 0 or less: {files}",
string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
{ {
if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal)))
_orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); _orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
} }
CurrentDownloads = downloadFileInfoFromService CurrentDownloads = [.. downloadFileInfoFromService
.Distinct() .Distinct()
.Select(d => new DownloadFileTransfer(d)) .Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred) .Where(d => d.CanBeTransferred)];
.ToList();
return CurrentDownloads; return CurrentDownloads;
} }

View File

@@ -206,11 +206,15 @@ public partial class ApiController
if (!IsConnected) return; if (!IsConnected) return;
await _lightlessHub!.SendAsync(nameof(UpdateLocation), locationDto, offline).ConfigureAwait(false); await _lightlessHub!.SendAsync(nameof(UpdateLocation), locationDto, offline).ConfigureAwait(false);
} }
public async Task<(List<LocationWithTimeDto>, List<SharingStatusDto>)> RequestAllLocationInfo()
public async Task<List<LocationDto>> RequestAllLocationInfo()
{ {
if (!IsConnected) return []; if (!IsConnected) return ([],[]);
return await _lightlessHub!.InvokeAsync<List<LocationDto>>(nameof(RequestAllLocationInfo)).ConfigureAwait(false); return await _lightlessHub!.InvokeAsync<(List<LocationWithTimeDto>, List<SharingStatusDto>)>(nameof(RequestAllLocationInfo)).ConfigureAwait(false);
}
public async Task<bool> ToggleLocationSharing(LocationSharingToggleDto dto)
{
if (!IsConnected) return false;
return await _lightlessHub!.InvokeAsync<bool>(nameof(ToggleLocationSharing), dto).ConfigureAwait(false);
} }
} }
#pragma warning restore MA0040 #pragma warning restore MA0040

View File

@@ -260,10 +260,10 @@ public partial class ApiController
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task Client_SendLocationToClient(LocationDto locationDto) public Task Client_SendLocationToClient(LocationDto locationDto, DateTimeOffset expireAt)
{ {
Logger.LogDebug($"{nameof(Client_SendLocationToClient)}: {locationDto.user}"); Logger.LogDebug($"{nameof(Client_SendLocationToClient)}: {locationDto.User} {expireAt}");
ExecuteSafely(() => Mediator.Publish(new LocationMessage(locationDto.user.UID, locationDto.location))); ExecuteSafely(() => Mediator.Publish(new LocationSharingMessage(locationDto.User, locationDto.Location, expireAt)));
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -447,8 +447,8 @@ public partial class ApiController
if (_initialized) return; if (_initialized) return;
_lightlessHub!.On(nameof(Client_GposeLobbyPushWorldData), act); _lightlessHub!.On(nameof(Client_GposeLobbyPushWorldData), act);
} }
public void OnReciveLocation(Action<LocationDto> act) public void OnReceiveLocation(Action<LocationDto, DateTimeOffset> act)
{ {
if (_initialized) return; if (_initialized) return;
_lightlessHub!.On(nameof(Client_SendLocationToClient), act); _lightlessHub!.On(nameof(Client_SendLocationToClient), act);

View File

@@ -606,8 +606,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
OnGposeLobbyPushCharacterData((dto) => _ = Client_GposeLobbyPushCharacterData(dto)); OnGposeLobbyPushCharacterData((dto) => _ = Client_GposeLobbyPushCharacterData(dto));
OnGposeLobbyPushPoseData((dto, data) => _ = Client_GposeLobbyPushPoseData(dto, data)); OnGposeLobbyPushPoseData((dto, data) => _ = Client_GposeLobbyPushPoseData(dto, data));
OnGposeLobbyPushWorldData((dto, data) => _ = Client_GposeLobbyPushWorldData(dto, data)); OnGposeLobbyPushWorldData((dto, data) => _ = Client_GposeLobbyPushWorldData(dto, data));
OnReciveLocation(dto => _ = Client_SendLocationToClient(dto)); OnReceiveLocation((dto, time) => _ = Client_SendLocationToClient(dto, time));
_healthCheckTokenSource?.Cancel(); _healthCheckTokenSource?.Cancel();
_healthCheckTokenSource?.Dispose(); _healthCheckTokenSource?.Dispose();
@@ -776,5 +775,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
ServerState = state; ServerState = state;
} }
} }
#pragma warning restore MA0040 #pragma warning restore MA0040