Db: Move Markers into database
This commit is contained in:
parent
f63e70b0c4
commit
94f3fa2ede
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Rendering;
|
||||
|
||||
namespace Pal.Client.Commands
|
||||
@ -32,30 +33,33 @@ namespace Pal.Client.Commands
|
||||
break;
|
||||
|
||||
case "tnear":
|
||||
DebugNearest(m => m.Type == Marker.EType.Trap);
|
||||
DebugNearest(m => m.Type == MemoryLocation.EType.Trap);
|
||||
break;
|
||||
|
||||
case "hnear":
|
||||
DebugNearest(m => m.Type == Marker.EType.Hoard);
|
||||
DebugNearest(m => m.Type == MemoryLocation.EType.Hoard);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DebugNearest(Predicate<Marker> predicate)
|
||||
private void DebugNearest(Predicate<PersistentLocation> predicate)
|
||||
{
|
||||
if (!_territoryState.IsInDeepDungeon())
|
||||
return;
|
||||
|
||||
var state = _floorService.GetFloorMarkers(_clientState.TerritoryType);
|
||||
var state = _floorService.GetTerritoryIfReady(_clientState.TerritoryType);
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var playerPosition = _clientState.LocalPlayer?.Position;
|
||||
if (playerPosition == null)
|
||||
return;
|
||||
_chat.Message($"{playerPosition}");
|
||||
|
||||
var nearbyMarkers = state.Markers
|
||||
var nearbyMarkers = state.Locations
|
||||
.Where(m => predicate(m))
|
||||
.Where(m => m.RenderElement != null && m.RenderElement.Color != RenderData.ColorInvisible)
|
||||
.Select(m => new { m, distance = (playerPosition - m.Position)?.Length() ?? float.MaxValue })
|
||||
.Select(m => new { m, distance = (playerPosition.Value - m.Position).Length() })
|
||||
.OrderBy(m => m.distance)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
@ -114,6 +114,9 @@ namespace Pal.Client.Configuration.Legacy
|
||||
.Cast<ImportHistory>()
|
||||
.Distinct()
|
||||
.ToList(),
|
||||
|
||||
Imported = o.WasImported,
|
||||
SinceVersion = o.SinceVersion ?? "0.0",
|
||||
};
|
||||
|
||||
clientLocation.RemoteEncounters = o.RemoteSeenOn
|
||||
|
@ -30,6 +30,17 @@ namespace Pal.Client.Database
|
||||
/// </summary>
|
||||
public List<ImportHistory> ImportedBy { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this location was originally imported.
|
||||
/// </summary>
|
||||
public bool Imported { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// To make rollbacks of local data easier, keep track of the plugin version which was used to create this location initially.
|
||||
/// </summary>
|
||||
public string SinceVersion { get; set; } = "0.0";
|
||||
|
||||
public enum EType
|
||||
{
|
||||
Trap = 1,
|
||||
|
148
Pal.Client/Database/Migrations/20230218112804_AddImportedAndSinceVersionToClientLocation.Designer.cs
generated
Normal file
148
Pal.Client/Database/Migrations/20230218112804_AddImportedAndSinceVersionToClientLocation.Designer.cs
generated
Normal file
@ -0,0 +1,148 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Pal.Client.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Pal.Client.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(PalClientContext))]
|
||||
[Migration("20230218112804_AddImportedAndSinceVersionToClientLocation")]
|
||||
partial class AddImportedAndSinceVersionToClientLocation
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.3");
|
||||
|
||||
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||
{
|
||||
b.Property<Guid>("ImportedById")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ImportedLocationsLocalId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ImportedById", "ImportedLocationsLocalId");
|
||||
|
||||
b.HasIndex("ImportedLocationsLocalId");
|
||||
|
||||
b.ToTable("LocationImports", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.ClientLocation", b =>
|
||||
{
|
||||
b.Property<int>("LocalId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Imported")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Seen")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SinceVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ushort>("TerritoryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("X")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Y")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Z")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("LocalId");
|
||||
|
||||
b.ToTable("Locations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.ImportHistory", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ImportedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RemoteUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Imports");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AccountId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(13)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ClientLocationId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientLocationId");
|
||||
|
||||
b.ToTable("RemoteEncounters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||
{
|
||||
b.HasOne("Pal.Client.Database.ImportHistory", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ImportedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Pal.Client.Database.ClientLocation", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ImportedLocationsLocalId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||
{
|
||||
b.HasOne("Pal.Client.Database.ClientLocation", "ClientLocation")
|
||||
.WithMany("RemoteEncounters")
|
||||
.HasForeignKey("ClientLocationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ClientLocation");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.ClientLocation", b =>
|
||||
{
|
||||
b.Navigation("RemoteEncounters");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Pal.Client.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddImportedAndSinceVersionToClientLocation : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Imported",
|
||||
table: "Locations",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SinceVersion",
|
||||
table: "Locations",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Imported",
|
||||
table: "Locations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SinceVersion",
|
||||
table: "Locations");
|
||||
}
|
||||
}
|
||||
}
|
@ -38,9 +38,16 @@ namespace Pal.Client.Database.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Imported")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Seen")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SinceVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ushort>("TerritoryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -58,7 +65,7 @@ namespace Pal.Client.Database.Migrations
|
||||
|
||||
b.HasKey("LocalId");
|
||||
|
||||
b.ToTable("Locations", (string)null);
|
||||
b.ToTable("Locations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.ImportHistory", b =>
|
||||
@ -78,7 +85,7 @@ namespace Pal.Client.Database.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Imports", (string)null);
|
||||
b.ToTable("Imports");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||
@ -99,7 +106,7 @@ namespace Pal.Client.Database.Migrations
|
||||
|
||||
b.HasIndex("ClientLocationId");
|
||||
|
||||
b.ToTable("RemoteEncounters", (string)null);
|
||||
b.ToTable("RemoteEncounters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||
@ -120,13 +127,18 @@ namespace Pal.Client.Database.Migrations
|
||||
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||
{
|
||||
b.HasOne("Pal.Client.Database.ClientLocation", "ClientLocation")
|
||||
.WithMany()
|
||||
.WithMany("RemoteEncounters")
|
||||
.HasForeignKey("ClientLocationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ClientLocation");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pal.Client.Database.ClientLocation", b =>
|
||||
{
|
||||
b.Navigation("RemoteEncounters");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Pal.Client.DependencyInjection
|
||||
{
|
||||
internal sealed class FloorService
|
||||
{
|
||||
public ConcurrentDictionary<ushort, LocalState> FloorMarkers { get; } = new();
|
||||
public ConcurrentBag<Marker> EphemeralMarkers { get; set; } = new();
|
||||
|
||||
public LocalState GetFloorMarkers(ushort territoryType)
|
||||
{
|
||||
return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt));
|
||||
}
|
||||
}
|
||||
}
|
@ -14,9 +14,11 @@ using ImGuiNET;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Net;
|
||||
using Pal.Client.Rendering;
|
||||
using Pal.Client.Scheduled;
|
||||
using Pal.Common;
|
||||
|
||||
namespace Pal.Client.DependencyInjection
|
||||
{
|
||||
@ -84,44 +86,45 @@ namespace Pal.Client.DependencyInjection
|
||||
try
|
||||
{
|
||||
bool recreateLayout = false;
|
||||
bool saveMarkers = false;
|
||||
|
||||
while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
|
||||
HandleQueued(queued, ref recreateLayout, ref saveMarkers);
|
||||
HandleQueued(queued, ref recreateLayout);
|
||||
|
||||
if (_territoryState.LastTerritory != _clientState.TerritoryType)
|
||||
{
|
||||
_territoryState.LastTerritory = _clientState.TerritoryType;
|
||||
_territoryState.TerritorySyncState = SyncState.NotAttempted;
|
||||
_territoryState.TerritorySyncState = ESyncState.NotAttempted;
|
||||
NextUpdateObjects.Clear();
|
||||
|
||||
if (_territoryState.IsInDeepDungeon())
|
||||
_floorService.GetFloorMarkers(_territoryState.LastTerritory);
|
||||
_floorService.EphemeralMarkers.Clear();
|
||||
_floorService.ChangeTerritory(_territoryState.LastTerritory);
|
||||
_territoryState.PomanderOfSight = PomanderState.Inactive;
|
||||
_territoryState.PomanderOfIntuition = PomanderState.Inactive;
|
||||
recreateLayout = true;
|
||||
_debugState.Reset();
|
||||
}
|
||||
|
||||
if (!_territoryState.IsInDeepDungeon())
|
||||
if (!_territoryState.IsInDeepDungeon() || !_floorService.IsReady(_territoryState.LastTerritory))
|
||||
return;
|
||||
|
||||
if (_configuration.Mode == EMode.Online && _territoryState.TerritorySyncState == SyncState.NotAttempted)
|
||||
if (_configuration.Mode == EMode.Online &&
|
||||
_territoryState.TerritorySyncState == ESyncState.NotAttempted)
|
||||
{
|
||||
_territoryState.TerritorySyncState = SyncState.Started;
|
||||
_territoryState.TerritorySyncState = ESyncState.Started;
|
||||
Task.Run(async () => await DownloadMarkersForTerritory(_territoryState.LastTerritory));
|
||||
}
|
||||
|
||||
while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
|
||||
HandleQueued(queued, ref recreateLayout, ref saveMarkers);
|
||||
HandleQueued(queued, ref recreateLayout);
|
||||
|
||||
var currentFloor = _floorService.GetFloorMarkers(_territoryState.LastTerritory);
|
||||
(IReadOnlyList<PersistentLocation> visiblePersistentMarkers,
|
||||
IReadOnlyList<EphemeralLocation> visibleEphemeralMarkers) =
|
||||
GetRelevantGameObjects();
|
||||
|
||||
IList<Marker> visibleMarkers = GetRelevantGameObjects();
|
||||
HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers,
|
||||
recreateLayout);
|
||||
HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout);
|
||||
ETerritoryType territoryType = (ETerritoryType)_territoryState.LastTerritory;
|
||||
HandlePersistentLocations(territoryType, visiblePersistentMarkers, recreateLayout);
|
||||
|
||||
if (_floorService.MergeEphemeralLocations(visibleEphemeralMarkers, recreateLayout))
|
||||
RecreateEphemeralLayout();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -131,183 +134,161 @@ namespace Pal.Client.DependencyInjection
|
||||
|
||||
#region Render Markers
|
||||
|
||||
private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers,
|
||||
private void HandlePersistentLocations(ETerritoryType territoryType,
|
||||
IReadOnlyList<PersistentLocation> visiblePersistentMarkers,
|
||||
bool recreateLayout)
|
||||
{
|
||||
var currentFloorMarkers = currentFloor.Markers;
|
||||
|
||||
bool updateSeenMarkers = false;
|
||||
var partialAccountId = _configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
||||
foreach (var visibleMarker in visibleMarkers)
|
||||
bool recreatePersistentLocations = _floorService.MergePersistentLocations(
|
||||
territoryType,
|
||||
visiblePersistentMarkers,
|
||||
recreateLayout,
|
||||
out List<PersistentLocation> locationsToSync);
|
||||
recreatePersistentLocations |= CheckLocationsForPomanders(visiblePersistentMarkers);
|
||||
if (locationsToSync.Count > 0)
|
||||
{
|
||||
Marker? knownMarker = currentFloorMarkers.SingleOrDefault(x => x == visibleMarker);
|
||||
if (knownMarker != null)
|
||||
{
|
||||
if (!knownMarker.Seen)
|
||||
{
|
||||
knownMarker.Seen = true;
|
||||
saveMarkers = true;
|
||||
}
|
||||
|
||||
// This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states,
|
||||
// markers discovered afterwards are automatically marked seen.
|
||||
if (partialAccountId != null && knownMarker is { NetworkId: { }, RemoteSeenRequested: false } &&
|
||||
!knownMarker.RemoteSeenOn.Contains(partialAccountId))
|
||||
updateSeenMarkers = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
currentFloorMarkers.Add(visibleMarker);
|
||||
recreateLayout = true;
|
||||
saveMarkers = true;
|
||||
Task.Run(async () =>
|
||||
await SyncSeenMarkersForTerritory(_territoryState.LastTerritory, locationsToSync));
|
||||
}
|
||||
|
||||
if (!recreateLayout && currentFloorMarkers.Count > 0 &&
|
||||
UploadLocations();
|
||||
|
||||
if (recreatePersistentLocations)
|
||||
RecreatePersistentLayout(visiblePersistentMarkers);
|
||||
}
|
||||
|
||||
private bool CheckLocationsForPomanders(IReadOnlyList<PersistentLocation> visibleLocations)
|
||||
{
|
||||
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
|
||||
if (memoryTerritory is { Locations.Count: > 0 } &&
|
||||
(_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
|
||||
_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander))
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var marker in currentFloorMarkers)
|
||||
foreach (var location in memoryTerritory.Locations)
|
||||
{
|
||||
uint desiredColor = DetermineColor(marker, visibleMarkers);
|
||||
if (marker.RenderElement == null || !marker.RenderElement.IsValid)
|
||||
{
|
||||
recreateLayout = true;
|
||||
break;
|
||||
}
|
||||
uint desiredColor = DetermineColor(location, visibleLocations);
|
||||
if (location.RenderElement == null || !location.RenderElement.IsValid)
|
||||
return true;
|
||||
|
||||
if (marker.RenderElement.Color != desiredColor)
|
||||
marker.RenderElement.Color = desiredColor;
|
||||
if (location.RenderElement.Color != desiredColor)
|
||||
location.RenderElement.Color = desiredColor;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_debugState.SetFromException(e);
|
||||
recreateLayout = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updateSeenMarkers && partialAccountId != null)
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UploadLocations()
|
||||
{
|
||||
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
|
||||
if (memoryTerritory == null)
|
||||
return;
|
||||
|
||||
List<PersistentLocation> locationsToUpload = memoryTerritory.Locations
|
||||
.Where(loc => loc.NetworkId == null && loc.UploadRequested == false)
|
||||
.ToList();
|
||||
if (locationsToUpload.Count > 0)
|
||||
{
|
||||
var markersToUpdate = currentFloorMarkers.Where(x =>
|
||||
x is { Seen: true, NetworkId: { }, RemoteSeenRequested: false } &&
|
||||
!x.RemoteSeenOn.Contains(partialAccountId)).ToList();
|
||||
foreach (var marker in markersToUpdate)
|
||||
marker.RemoteSeenRequested = true;
|
||||
Task.Run(async () => await SyncSeenMarkersForTerritory(_territoryState.LastTerritory, markersToUpdate));
|
||||
}
|
||||
foreach (var location in locationsToUpload)
|
||||
location.UploadRequested = true;
|
||||
|
||||
if (saveMarkers)
|
||||
{
|
||||
currentFloor.Save();
|
||||
|
||||
if (_territoryState.TerritorySyncState == SyncState.Complete)
|
||||
{
|
||||
var markersToUpload = currentFloorMarkers
|
||||
.Where(x => x.IsPermanent() && x.NetworkId == null && !x.UploadRequested).ToList();
|
||||
if (markersToUpload.Count > 0)
|
||||
{
|
||||
foreach (var marker in markersToUpload)
|
||||
marker.UploadRequested = true;
|
||||
Task.Run(async () =>
|
||||
await UploadMarkersForTerritory(_territoryState.LastTerritory, markersToUpload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (recreateLayout)
|
||||
{
|
||||
_renderAdapter.ResetLayer(ELayer.TrapHoard);
|
||||
|
||||
List<IRenderElement> elements = new();
|
||||
foreach (var marker in currentFloorMarkers)
|
||||
{
|
||||
if (marker.Seen || _configuration.Mode == EMode.Online ||
|
||||
marker is { WasImported: true, Imports.Count: > 0 })
|
||||
{
|
||||
if (marker.Type == Marker.EType.Trap)
|
||||
{
|
||||
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers),
|
||||
_configuration.DeepDungeons.Traps);
|
||||
}
|
||||
else if (marker.Type == Marker.EType.Hoard)
|
||||
{
|
||||
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers),
|
||||
_configuration.DeepDungeons.HoardCoffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.Count == 0)
|
||||
return;
|
||||
|
||||
_renderAdapter.SetLayer(ELayer.TrapHoard, elements);
|
||||
Task.Run(async () =>
|
||||
await UploadLocationsForTerritory(_territoryState.LastTerritory, locationsToUpload));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout)
|
||||
private void RecreatePersistentLayout(IReadOnlyList<PersistentLocation> visibleMarkers)
|
||||
{
|
||||
recreateLayout |=
|
||||
_floorService.EphemeralMarkers.Any(existingMarker => visibleMarkers.All(x => x != existingMarker));
|
||||
recreateLayout |=
|
||||
visibleMarkers.Any(visibleMarker => _floorService.EphemeralMarkers.All(x => x != visibleMarker));
|
||||
_renderAdapter.ResetLayer(ELayer.TrapHoard);
|
||||
|
||||
if (recreateLayout)
|
||||
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
|
||||
if (memoryTerritory == null)
|
||||
return;
|
||||
|
||||
List<IRenderElement> elements = new();
|
||||
foreach (var location in memoryTerritory.Locations)
|
||||
{
|
||||
_renderAdapter.ResetLayer(ELayer.RegularCoffers);
|
||||
_floorService.EphemeralMarkers.Clear();
|
||||
|
||||
List<IRenderElement> elements = new();
|
||||
foreach (var marker in visibleMarkers)
|
||||
if (location.Type == MemoryLocation.EType.Trap)
|
||||
{
|
||||
_floorService.EphemeralMarkers.Add(marker);
|
||||
|
||||
if (marker.Type == Marker.EType.SilverCoffer && _configuration.DeepDungeons.SilverCoffers.Show)
|
||||
{
|
||||
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers),
|
||||
_configuration.DeepDungeons.SilverCoffers);
|
||||
}
|
||||
CreateRenderElement(location, elements, DetermineColor(location, visibleMarkers),
|
||||
_configuration.DeepDungeons.Traps);
|
||||
}
|
||||
else if (location.Type == MemoryLocation.EType.Hoard)
|
||||
{
|
||||
CreateRenderElement(location, elements, DetermineColor(location, visibleMarkers),
|
||||
_configuration.DeepDungeons.HoardCoffers);
|
||||
}
|
||||
|
||||
if (elements.Count == 0)
|
||||
return;
|
||||
|
||||
_renderAdapter.SetLayer(ELayer.RegularCoffers, elements);
|
||||
}
|
||||
|
||||
if (elements.Count == 0)
|
||||
return;
|
||||
|
||||
_renderAdapter.SetLayer(ELayer.TrapHoard, elements);
|
||||
}
|
||||
|
||||
private uint DetermineColor(Marker marker, IList<Marker> visibleMarkers)
|
||||
private void RecreateEphemeralLayout()
|
||||
{
|
||||
switch (marker.Type)
|
||||
_renderAdapter.ResetLayer(ELayer.RegularCoffers);
|
||||
|
||||
List<IRenderElement> elements = new();
|
||||
foreach (var location in _floorService.EphemeralLocations)
|
||||
{
|
||||
case Marker.EType.Trap when _territoryState.PomanderOfSight == PomanderState.Inactive ||
|
||||
!_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
|
||||
visibleMarkers.Any(x => x == marker):
|
||||
if (location.Type == MemoryLocation.EType.SilverCoffer &&
|
||||
_configuration.DeepDungeons.SilverCoffers.Show)
|
||||
{
|
||||
CreateRenderElement(location, elements, DetermineColor(location),
|
||||
_configuration.DeepDungeons.SilverCoffers);
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.Count == 0)
|
||||
return;
|
||||
|
||||
_renderAdapter.SetLayer(ELayer.RegularCoffers, elements);
|
||||
}
|
||||
|
||||
private uint DetermineColor(PersistentLocation location, IReadOnlyList<PersistentLocation> visibleLocations)
|
||||
{
|
||||
switch (location.Type)
|
||||
{
|
||||
case MemoryLocation.EType.Trap
|
||||
when _territoryState.PomanderOfSight == PomanderState.Inactive ||
|
||||
!_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
|
||||
visibleLocations.Any(x => x == location):
|
||||
return _configuration.DeepDungeons.Traps.Color;
|
||||
case Marker.EType.Hoard when _territoryState.PomanderOfIntuition == PomanderState.Inactive ||
|
||||
!_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander ||
|
||||
visibleMarkers.Any(x => x == marker):
|
||||
case MemoryLocation.EType.Hoard
|
||||
when _territoryState.PomanderOfIntuition == PomanderState.Inactive ||
|
||||
!_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander ||
|
||||
visibleLocations.Any(x => x == location):
|
||||
return _configuration.DeepDungeons.HoardCoffers.Color;
|
||||
case Marker.EType.SilverCoffer:
|
||||
return _configuration.DeepDungeons.SilverCoffers.Color;
|
||||
case Marker.EType.Trap:
|
||||
case Marker.EType.Hoard:
|
||||
return RenderData.ColorInvisible;
|
||||
default:
|
||||
return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f));
|
||||
return RenderData.ColorInvisible;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color,
|
||||
private uint DetermineColor(EphemeralLocation location)
|
||||
{
|
||||
if (location.Type == MemoryLocation.EType.SilverCoffer)
|
||||
return _configuration.DeepDungeons.SilverCoffers.Color;
|
||||
|
||||
return RenderData.ColorInvisible;
|
||||
}
|
||||
|
||||
private void CreateRenderElement(MemoryLocation location, List<IRenderElement> elements, uint color,
|
||||
MarkerConfiguration config)
|
||||
{
|
||||
if (!config.Show)
|
||||
return;
|
||||
|
||||
var element = _renderAdapter.CreateElement(marker.Type, marker.Position, color, config.Fill);
|
||||
marker.RenderElement = element;
|
||||
var element = _renderAdapter.CreateElement(location.Type, location.Position, color, config.Fill);
|
||||
location.RenderElement = element;
|
||||
elements.Add(element);
|
||||
}
|
||||
|
||||
@ -325,7 +306,7 @@ namespace Pal.Client.DependencyInjection
|
||||
Type = SyncType.Download,
|
||||
TerritoryType = territoryId,
|
||||
Success = success,
|
||||
Markers = downloadedMarkers
|
||||
Locations = downloadedMarkers
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -334,17 +315,17 @@ namespace Pal.Client.DependencyInjection
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UploadMarkersForTerritory(ushort territoryId, List<Marker> markersToUpload)
|
||||
private async Task UploadLocationsForTerritory(ushort territoryId, List<PersistentLocation> locationsToUpload)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (success, uploadedMarkers) = await _remoteApi.UploadMarker(territoryId, markersToUpload);
|
||||
var (success, uploadedLocations) = await _remoteApi.UploadLocations(territoryId, locationsToUpload);
|
||||
LateEventQueue.Enqueue(new QueuedSyncResponse
|
||||
{
|
||||
Type = SyncType.Upload,
|
||||
TerritoryType = territoryId,
|
||||
Success = success,
|
||||
Markers = uploadedMarkers
|
||||
Locations = uploadedLocations
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -353,17 +334,18 @@ namespace Pal.Client.DependencyInjection
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncSeenMarkersForTerritory(ushort territoryId, List<Marker> markersToUpdate)
|
||||
private async Task SyncSeenMarkersForTerritory(ushort territoryId,
|
||||
IReadOnlyList<PersistentLocation> locationsToUpdate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await _remoteApi.MarkAsSeen(territoryId, markersToUpdate);
|
||||
var success = await _remoteApi.MarkAsSeen(territoryId, locationsToUpdate);
|
||||
LateEventQueue.Enqueue(new QueuedSyncResponse
|
||||
{
|
||||
Type = SyncType.MarkSeen,
|
||||
TerritoryType = territoryId,
|
||||
Success = success,
|
||||
Markers = markersToUpdate,
|
||||
Locations = locationsToUpdate,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -374,9 +356,10 @@ namespace Pal.Client.DependencyInjection
|
||||
|
||||
#endregion
|
||||
|
||||
private IList<Marker> GetRelevantGameObjects()
|
||||
private (IReadOnlyList<PersistentLocation>, IReadOnlyList<EphemeralLocation>) GetRelevantGameObjects()
|
||||
{
|
||||
List<Marker> result = new();
|
||||
List<PersistentLocation> persistentLocations = new();
|
||||
List<EphemeralLocation> ephemeralLocations = new();
|
||||
for (int i = 246; i < _objectTable.Length; i++)
|
||||
{
|
||||
GameObject? obj = _objectTable[i];
|
||||
@ -391,16 +374,31 @@ namespace Pal.Client.DependencyInjection
|
||||
case 2007185:
|
||||
case 2007186:
|
||||
case 2009504:
|
||||
result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true });
|
||||
persistentLocations.Add(new PersistentLocation
|
||||
{
|
||||
Type = MemoryLocation.EType.Trap,
|
||||
Position = obj.Position,
|
||||
Seen = true
|
||||
});
|
||||
break;
|
||||
|
||||
case 2007542:
|
||||
case 2007543:
|
||||
result.Add(new Marker(Marker.EType.Hoard, obj.Position) { Seen = true });
|
||||
persistentLocations.Add(new PersistentLocation
|
||||
{
|
||||
Type = MemoryLocation.EType.Hoard,
|
||||
Position = obj.Position,
|
||||
Seen = true
|
||||
});
|
||||
break;
|
||||
|
||||
case 2007357:
|
||||
result.Add(new Marker(Marker.EType.SilverCoffer, obj.Position) { Seen = true });
|
||||
ephemeralLocations.Add(new EphemeralLocation
|
||||
{
|
||||
Type = MemoryLocation.EType.SilverCoffer,
|
||||
Position = obj.Position,
|
||||
Seen = true
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -409,18 +407,25 @@ namespace Pal.Client.DependencyInjection
|
||||
{
|
||||
var obj = _objectTable.FirstOrDefault(x => x.Address == address);
|
||||
if (obj != null && obj.Position.Length() > 0.1)
|
||||
result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true });
|
||||
{
|
||||
persistentLocations.Add(new PersistentLocation
|
||||
{
|
||||
Type = MemoryLocation.EType.Trap,
|
||||
Position = obj.Position,
|
||||
Seen = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return (persistentLocations, ephemeralLocations);
|
||||
}
|
||||
|
||||
private void HandleQueued(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers)
|
||||
private void HandleQueued(IQueueOnFrameworkThread queued, ref bool recreateLayout)
|
||||
{
|
||||
Type handlerType = typeof(IQueueOnFrameworkThread.Handler<>).MakeGenericType(queued.GetType());
|
||||
var handler = (IQueueOnFrameworkThread.IHandler)_serviceProvider.GetRequiredService(handlerType);
|
||||
|
||||
handler.RunIfCompatible(queued, ref recreateLayout, ref saveMarkers);
|
||||
handler.RunIfCompatible(queued, ref recreateLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,28 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Account;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Database;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Floors.Tasks;
|
||||
using Pal.Common;
|
||||
|
||||
namespace Pal.Client.DependencyInjection
|
||||
{
|
||||
internal sealed class ImportService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly FloorService _floorService;
|
||||
|
||||
public ImportService(IServiceProvider serviceProvider)
|
||||
public ImportService(IServiceProvider serviceProvider, FloorService floorService)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_floorService = floorService;
|
||||
}
|
||||
|
||||
/*
|
||||
public void Add(ImportHistory history)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
@ -26,6 +33,7 @@ namespace Pal.Client.DependencyInjection
|
||||
dbContext.Imports.Add(history);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
*/
|
||||
|
||||
public async Task<ImportHistory?> FindLast(CancellationToken token = default)
|
||||
{
|
||||
@ -35,6 +43,7 @@ namespace Pal.Client.DependencyInjection
|
||||
return await dbContext.Imports.OrderByDescending(x => x.ImportedAt).ThenBy(x => x.Id).FirstOrDefaultAsync(cancellationToken: token);
|
||||
}
|
||||
|
||||
/*
|
||||
public List<ImportHistory> FindForServer(string server)
|
||||
{
|
||||
if (string.IsNullOrEmpty(server))
|
||||
@ -44,18 +53,58 @@ namespace Pal.Client.DependencyInjection
|
||||
using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||
|
||||
return dbContext.Imports.Where(x => x.RemoteUrl == server).ToList();
|
||||
}
|
||||
}*/
|
||||
|
||||
public void RemoveAllByIds(List<Guid> ids)
|
||||
public (int traps, int hoard) Import(ExportRoot import)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||
|
||||
dbContext.RemoveRange(dbContext.Imports.Where(x => ids.Contains(x.Id)));
|
||||
dbContext.Imports.RemoveRange(dbContext.Imports.Where(x => x.RemoteUrl == import.ServerUrl).ToList());
|
||||
|
||||
ImportHistory importHistory = new ImportHistory
|
||||
{
|
||||
Id = Guid.Parse(import.ExportId),
|
||||
RemoteUrl = import.ServerUrl,
|
||||
ExportedAt = import.CreatedAt.ToDateTime(),
|
||||
ImportedAt = DateTime.UtcNow,
|
||||
};
|
||||
dbContext.Imports.Add(importHistory);
|
||||
|
||||
int traps = 0;
|
||||
int hoard = 0;
|
||||
foreach (var floor in import.Floors)
|
||||
{
|
||||
ETerritoryType territoryType = (ETerritoryType)floor.TerritoryType;
|
||||
|
||||
List<PersistentLocation> existingLocations = dbContext.Locations
|
||||
.Where(loc => loc.TerritoryType == floor.TerritoryType)
|
||||
.ToList()
|
||||
.Select(LoadTerritory.ToMemoryLocation)
|
||||
.ToList();
|
||||
foreach (var newLocation in floor.Objects)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
// TODO filter here, update territories
|
||||
dbContext.SaveChanges();
|
||||
|
||||
_floorService.ResetAll();
|
||||
return (traps, hoard);
|
||||
}
|
||||
|
||||
public void RemoveById(Guid id)
|
||||
=> RemoveAllByIds(new List<Guid> { id });
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||
|
||||
dbContext.RemoveRange(dbContext.Imports.Where(x => x.Id == id));
|
||||
|
||||
// TODO filter here, update territories
|
||||
dbContext.SaveChanges();
|
||||
|
||||
_floorService.ResetAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace Pal.Client.DependencyInjection
|
||||
}
|
||||
|
||||
public ushort LastTerritory { get; set; }
|
||||
public SyncState TerritorySyncState { get; set; }
|
||||
public ESyncState TerritorySyncState { get; set; }
|
||||
public PomanderState PomanderOfSight { get; set; } = PomanderState.Inactive;
|
||||
public PomanderState PomanderOfIntuition { get; set; } = PomanderState.Inactive;
|
||||
|
||||
|
@ -24,6 +24,7 @@ using Pal.Client.Database;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.DependencyInjection.Logging;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Net;
|
||||
using Pal.Client.Properties;
|
||||
using Pal.Client.Rendering;
|
||||
@ -63,7 +64,8 @@ namespace Pal.Client
|
||||
CommandManager commandManager,
|
||||
DataManager dataManager)
|
||||
{
|
||||
_logger.LogInformation("Building service container");
|
||||
_logger.LogInformation("Building service container for {Assembly}",
|
||||
typeof(DependencyInjectionContext).Assembly.FullName);
|
||||
|
||||
// set up legacy services
|
||||
#pragma warning disable CS0612
|
||||
|
24
Pal.Client/Floors/EphemeralLocation.cs
Normal file
24
Pal.Client/Floors/EphemeralLocation.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Pal.Client.Floors
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a currently-visible marker.
|
||||
/// </summary>
|
||||
internal sealed class EphemeralLocation : MemoryLocation
|
||||
{
|
||||
public override bool Equals(object? obj) => obj is EphemeralLocation && base.Equals(obj);
|
||||
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
|
||||
public static bool operator ==(EphemeralLocation? a, object? b)
|
||||
{
|
||||
return Equals(a, b);
|
||||
}
|
||||
|
||||
public static bool operator !=(EphemeralLocation? a, object? b)
|
||||
{
|
||||
return !Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
147
Pal.Client/Floors/FloorService.cs
Normal file
147
Pal.Client/Floors/FloorService.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors.Tasks;
|
||||
using Pal.Client.Net;
|
||||
using Pal.Common;
|
||||
|
||||
namespace Pal.Client.Floors
|
||||
{
|
||||
internal sealed class FloorService
|
||||
{
|
||||
private readonly IPalacePalConfiguration _configuration;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IReadOnlyDictionary<ETerritoryType, MemoryTerritory> _territories;
|
||||
|
||||
private ConcurrentBag<EphemeralLocation> _ephemeralLocations = new();
|
||||
|
||||
public FloorService(IPalacePalConfiguration configuration, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_territories = Enum.GetValues<ETerritoryType>().ToDictionary(o => o, o => new MemoryTerritory(o));
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<EphemeralLocation> EphemeralLocations => _ephemeralLocations;
|
||||
|
||||
public void ChangeTerritory(ushort territoryType)
|
||||
{
|
||||
_ephemeralLocations = new ConcurrentBag<EphemeralLocation>();
|
||||
|
||||
if (typeof(ETerritoryType).IsEnumDefined(territoryType))
|
||||
ChangeTerritory((ETerritoryType)territoryType);
|
||||
}
|
||||
|
||||
private void ChangeTerritory(ETerritoryType newTerritory)
|
||||
{
|
||||
var territory = _territories[newTerritory];
|
||||
if (!territory.IsReady && !territory.IsLoading)
|
||||
{
|
||||
territory.IsLoading = true;
|
||||
new LoadTerritory(_serviceScopeFactory, territory).Start();
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryTerritory? GetTerritoryIfReady(ushort territoryType)
|
||||
{
|
||||
if (typeof(ETerritoryType).IsEnumDefined(territoryType))
|
||||
return GetTerritoryIfReady((ETerritoryType)territoryType);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public MemoryTerritory? GetTerritoryIfReady(ETerritoryType territoryType)
|
||||
{
|
||||
var territory = _territories[territoryType];
|
||||
if (!territory.IsReady)
|
||||
return null;
|
||||
|
||||
return territory;
|
||||
}
|
||||
|
||||
public bool IsReady(ushort territoryId) => GetTerritoryIfReady(territoryId) != null;
|
||||
|
||||
public bool MergePersistentLocations(
|
||||
ETerritoryType territoryType,
|
||||
IReadOnlyList<PersistentLocation> visibleLocations,
|
||||
bool recreateLayout,
|
||||
out List<PersistentLocation> locationsToSync)
|
||||
{
|
||||
MemoryTerritory? territory = GetTerritoryIfReady(territoryType);
|
||||
locationsToSync = new();
|
||||
if (territory == null)
|
||||
return false;
|
||||
|
||||
var partialAccountId = _configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
||||
var persistentLocations = territory.Locations.ToList();
|
||||
|
||||
List<PersistentLocation> markAsSeen = new();
|
||||
List<PersistentLocation> newLocations = new();
|
||||
foreach (var visibleLocation in visibleLocations)
|
||||
{
|
||||
PersistentLocation? existingLocation = persistentLocations.SingleOrDefault(x => x == visibleLocation);
|
||||
if (existingLocation != null)
|
||||
{
|
||||
if (existingLocation is { Seen: false, LocalId: { } })
|
||||
{
|
||||
existingLocation.Seen = true;
|
||||
markAsSeen.Add(existingLocation);
|
||||
}
|
||||
|
||||
// This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states,
|
||||
// markers discovered afterwards are automatically marked seen.
|
||||
if (partialAccountId != null &&
|
||||
existingLocation is { LocalId: { }, NetworkId: { }, RemoteSeenRequested: false } &&
|
||||
!existingLocation.RemoteSeenOn.Contains(partialAccountId))
|
||||
{
|
||||
existingLocation.RemoteSeenRequested = true;
|
||||
locationsToSync.Add(existingLocation);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
territory.Locations.Add(visibleLocation);
|
||||
newLocations.Add(visibleLocation);
|
||||
recreateLayout = true;
|
||||
}
|
||||
|
||||
if (markAsSeen.Count > 0)
|
||||
new MarkAsSeen(_serviceScopeFactory, territory, markAsSeen).Start();
|
||||
|
||||
if (newLocations.Count > 0)
|
||||
new SaveNewLocations(_serviceScopeFactory, territory, newLocations).Start();
|
||||
|
||||
return recreateLayout;
|
||||
}
|
||||
|
||||
/// <returns>Whether the locations have changed</returns>
|
||||
public bool MergeEphemeralLocations(IReadOnlyList<EphemeralLocation> visibleLocations, bool recreate)
|
||||
{
|
||||
recreate |= _ephemeralLocations.Any(loc => visibleLocations.All(x => x != loc));
|
||||
recreate |= visibleLocations.Any(loc => _ephemeralLocations.All(x => x != loc));
|
||||
|
||||
if (!recreate)
|
||||
return false;
|
||||
|
||||
_ephemeralLocations.Clear();
|
||||
foreach (var visibleLocation in visibleLocations)
|
||||
_ephemeralLocations.Add(visibleLocation);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResetAll()
|
||||
{
|
||||
foreach (var memoryTerritory in _territories.Values)
|
||||
{
|
||||
lock (memoryTerritory.LockObj)
|
||||
memoryTerritory.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
Pal.Client/Floors/MemoryLocation.cs
Normal file
56
Pal.Client/Floors/MemoryLocation.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Pal.Client.Rendering;
|
||||
using Pal.Common;
|
||||
using Palace;
|
||||
|
||||
namespace Pal.Client.Floors
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for <see cref="MemoryLocation"/> and <see cref="EphemeralLocation"/>.
|
||||
/// </summary>
|
||||
internal abstract class MemoryLocation
|
||||
{
|
||||
public required EType Type { get; init; }
|
||||
public required Vector3 Position { get; init; }
|
||||
public bool Seen { get; set; }
|
||||
|
||||
public IRenderElement? RenderElement { get; set; }
|
||||
|
||||
public enum EType
|
||||
{
|
||||
Unknown,
|
||||
|
||||
Hoard,
|
||||
Trap,
|
||||
|
||||
SilverCoffer,
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is MemoryLocation otherLocation &&
|
||||
Type == otherLocation.Type &&
|
||||
PalaceMath.IsNearlySamePosition(Position, otherLocation.Position);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Type, PalaceMath.GetHashCode(Position));
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ETypeExtensions
|
||||
{
|
||||
public static MemoryLocation.EType ToMemoryType(this ObjectType objectType)
|
||||
{
|
||||
return objectType switch
|
||||
{
|
||||
ObjectType.Trap => MemoryLocation.EType.Trap,
|
||||
ObjectType.Hoard => MemoryLocation.EType.Hoard,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(objectType), objectType, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
49
Pal.Client/Floors/MemoryTerritory.cs
Normal file
49
Pal.Client/Floors/MemoryTerritory.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Common;
|
||||
|
||||
namespace Pal.Client.Floors
|
||||
{
|
||||
/// <summary>
|
||||
/// A single set of floors loaded entirely in memory, can be e.g. POTD 51-60.
|
||||
/// </summary>
|
||||
internal sealed class MemoryTerritory
|
||||
{
|
||||
public MemoryTerritory(ETerritoryType territoryType)
|
||||
{
|
||||
TerritoryType = territoryType;
|
||||
}
|
||||
|
||||
public ETerritoryType TerritoryType { get; }
|
||||
public bool IsReady { get; set; }
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
public ConcurrentBag<PersistentLocation> Locations { get; } = new();
|
||||
public object LockObj { get; } = new();
|
||||
|
||||
public void Initialize(IEnumerable<PersistentLocation> locations)
|
||||
{
|
||||
Locations.Clear();
|
||||
foreach (var location in locations)
|
||||
Locations.Add(location);
|
||||
|
||||
IsReady = true;
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
public IEnumerable<PersistentLocation> GetRemovableLocations(EMode mode)
|
||||
{
|
||||
// TODO there was better logic here;
|
||||
return Locations.Where(x => !x.Seen);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Locations.Clear();
|
||||
IsReady = false;
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
48
Pal.Client/Floors/PersistentLocation.cs
Normal file
48
Pal.Client/Floors/PersistentLocation.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Pal.Client.Database;
|
||||
|
||||
namespace Pal.Client.Floors
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ClientLocation"/> loaded in memory, with certain extra attributes as needed.
|
||||
/// </summary>
|
||||
internal sealed class PersistentLocation : MemoryLocation
|
||||
{
|
||||
/// <see cref="ClientLocation.LocalId"/>
|
||||
public int? LocalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Network id for the server you're currently connected to.
|
||||
/// </summary>
|
||||
public Guid? NetworkId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For markers that the server you're connected to doesn't know: Whether this was requested to be uploaded, to avoid duplicate requests.
|
||||
/// </summary>
|
||||
public bool UploadRequested { get; set; }
|
||||
|
||||
/// <see cref="ClientLocation.RemoteEncounters"/>
|
||||
///
|
||||
public List<string> RemoteSeenOn { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this marker was requested to be seen, to avoid duplicate requests.
|
||||
/// </summary>
|
||||
public bool RemoteSeenRequested { get; set; }
|
||||
|
||||
public override bool Equals(object? obj) => obj is PersistentLocation && base.Equals(obj);
|
||||
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
|
||||
public static bool operator ==(PersistentLocation? a, object? b)
|
||||
{
|
||||
return Equals(a, b);
|
||||
}
|
||||
|
||||
public static bool operator !=(PersistentLocation? a, object? b)
|
||||
{
|
||||
return !Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
29
Pal.Client/Floors/Tasks/DbTask.cs
Normal file
29
Pal.Client/Floors/Tasks/DbTask.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Database;
|
||||
|
||||
namespace Pal.Client.Floors.Tasks
|
||||
{
|
||||
internal abstract class DbTask
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
protected DbTask(IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||
|
||||
Run(dbContext);
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void Run(PalClientContext dbContext);
|
||||
}
|
||||
}
|
59
Pal.Client/Floors/Tasks/LoadTerritory.cs
Normal file
59
Pal.Client/Floors/Tasks/LoadTerritory.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Database;
|
||||
|
||||
namespace Pal.Client.Floors.Tasks
|
||||
{
|
||||
internal sealed class LoadTerritory : DbTask
|
||||
{
|
||||
private readonly MemoryTerritory _territory;
|
||||
|
||||
public LoadTerritory(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory)
|
||||
: base(serviceScopeFactory)
|
||||
{
|
||||
_territory = territory;
|
||||
}
|
||||
|
||||
protected override void Run(PalClientContext dbContext)
|
||||
{
|
||||
lock (_territory.LockObj)
|
||||
{
|
||||
if (_territory.IsReady)
|
||||
return;
|
||||
|
||||
List<ClientLocation> locations = dbContext.Locations
|
||||
.Where(o => o.TerritoryType == (ushort)_territory.TerritoryType)
|
||||
.Include(o => o.ImportedBy)
|
||||
.Include(o => o.RemoteEncounters)
|
||||
.ToList();
|
||||
_territory.Initialize(locations.Select(ToMemoryLocation));
|
||||
}
|
||||
}
|
||||
|
||||
public static PersistentLocation ToMemoryLocation(ClientLocation location)
|
||||
{
|
||||
return new PersistentLocation
|
||||
{
|
||||
LocalId = location.LocalId,
|
||||
Type = ToMemoryLocationType(location.Type),
|
||||
Position = new Vector3(location.X, location.Y, location.Z),
|
||||
Seen = location.Seen,
|
||||
RemoteSeenOn = location.RemoteEncounters.Select(o => o.AccountId).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private static MemoryLocation.EType ToMemoryLocationType(ClientLocation.EType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ClientLocation.EType.Trap => MemoryLocation.EType.Trap,
|
||||
ClientLocation.EType.Hoard => MemoryLocation.EType.Hoard,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
33
Pal.Client/Floors/Tasks/MarkAsSeen.cs
Normal file
33
Pal.Client/Floors/Tasks/MarkAsSeen.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Database;
|
||||
|
||||
namespace Pal.Client.Floors.Tasks
|
||||
{
|
||||
internal sealed class MarkAsSeen : DbTask
|
||||
{
|
||||
private readonly MemoryTerritory _territory;
|
||||
private readonly IReadOnlyList<PersistentLocation> _locations;
|
||||
|
||||
public MarkAsSeen(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory,
|
||||
IReadOnlyList<PersistentLocation> locations)
|
||||
: base(serviceScopeFactory)
|
||||
{
|
||||
_territory = territory;
|
||||
_locations = locations;
|
||||
}
|
||||
|
||||
protected override void Run(PalClientContext dbContext)
|
||||
{
|
||||
lock (_territory.LockObj)
|
||||
{
|
||||
dbContext.Locations
|
||||
.Where(loc => _locations.Any(l => l.LocalId == loc.LocalId))
|
||||
.ExecuteUpdate(loc => loc.SetProperty(l => l.Seen, true));
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
Pal.Client/Floors/Tasks/MarkRemoteSeen.cs
Normal file
39
Pal.Client/Floors/Tasks/MarkRemoteSeen.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Database;
|
||||
|
||||
namespace Pal.Client.Floors.Tasks
|
||||
{
|
||||
internal sealed class MarkRemoteSeen : DbTask
|
||||
{
|
||||
private readonly MemoryTerritory _territory;
|
||||
private readonly IReadOnlyList<PersistentLocation> _locations;
|
||||
private readonly string _accountId;
|
||||
|
||||
public MarkRemoteSeen(IServiceScopeFactory serviceScopeFactory,
|
||||
MemoryTerritory territory,
|
||||
IReadOnlyList<PersistentLocation> locations,
|
||||
string accountId)
|
||||
: base(serviceScopeFactory)
|
||||
{
|
||||
_territory = territory;
|
||||
_locations = locations;
|
||||
_accountId = accountId;
|
||||
}
|
||||
|
||||
protected override void Run(PalClientContext dbContext)
|
||||
{
|
||||
lock (_territory.LockObj)
|
||||
{
|
||||
List<ClientLocation> locationsToUpdate = dbContext.Locations
|
||||
.Where(loc => _locations.Any(l =>
|
||||
l.LocalId == loc.LocalId && loc.RemoteEncounters.All(r => r.AccountId != _accountId)))
|
||||
.ToList();
|
||||
foreach (var clientLocation in locationsToUpdate)
|
||||
clientLocation.RemoteEncounters.Add(new RemoteEncounter(clientLocation, _accountId));
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
Pal.Client/Floors/Tasks/SaveNewLocations.cs
Normal file
69
Pal.Client/Floors/Tasks/SaveNewLocations.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pal.Client.Database;
|
||||
using Pal.Common;
|
||||
|
||||
namespace Pal.Client.Floors.Tasks
|
||||
{
|
||||
internal sealed class SaveNewLocations : DbTask
|
||||
{
|
||||
private readonly MemoryTerritory _territory;
|
||||
private readonly List<PersistentLocation> _newLocations;
|
||||
|
||||
public SaveNewLocations(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory,
|
||||
List<PersistentLocation> newLocations)
|
||||
: base(serviceScopeFactory)
|
||||
{
|
||||
_territory = territory;
|
||||
_newLocations = newLocations;
|
||||
}
|
||||
|
||||
protected override void Run(PalClientContext dbContext)
|
||||
{
|
||||
Run(_territory, dbContext, _newLocations);
|
||||
}
|
||||
|
||||
public static void Run(MemoryTerritory territory, PalClientContext dbContext,
|
||||
List<PersistentLocation> locations)
|
||||
{
|
||||
lock (territory.LockObj)
|
||||
{
|
||||
Dictionary<PersistentLocation, ClientLocation> mapping =
|
||||
locations.ToDictionary(x => x, x => ToDatabaseLocation(x, territory.TerritoryType));
|
||||
dbContext.Locations.AddRange(mapping.Values);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
foreach ((PersistentLocation persistentLocation, ClientLocation clientLocation) in mapping)
|
||||
{
|
||||
persistentLocation.LocalId = clientLocation.LocalId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ClientLocation ToDatabaseLocation(PersistentLocation location, ETerritoryType territoryType)
|
||||
{
|
||||
return new ClientLocation
|
||||
{
|
||||
TerritoryType = (ushort)territoryType,
|
||||
Type = ToDatabaseType(location.Type),
|
||||
X = location.Position.X,
|
||||
Y = location.Position.Y,
|
||||
Z = location.Position.Z,
|
||||
Seen = location.Seen,
|
||||
SinceVersion = typeof(Plugin).Assembly.GetName().Version!.ToString(2),
|
||||
};
|
||||
}
|
||||
|
||||
private static ClientLocation.EType ToDatabaseType(MemoryLocation.EType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
MemoryLocation.EType.Trap => ClientLocation.EType.Trap,
|
||||
MemoryLocation.EType.Hoard => ClientLocation.EType.Hoard,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
using Pal.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.Extensions;
|
||||
|
||||
namespace Pal.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON for a single floor set (e.g. 51-60).
|
||||
/// </summary>
|
||||
internal sealed class LocalState
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new() { IncludeFields = true };
|
||||
private const int CurrentVersion = 4;
|
||||
|
||||
internal static string PluginConfigDirectory { get; set; } = null!;
|
||||
internal static EMode Mode { get; set; }
|
||||
|
||||
public uint TerritoryType { get; set; }
|
||||
public ConcurrentBag<Marker> Markers { get; set; } = new();
|
||||
|
||||
public LocalState(uint territoryType)
|
||||
{
|
||||
TerritoryType = territoryType;
|
||||
}
|
||||
|
||||
private void ApplyFilters()
|
||||
{
|
||||
if (Mode == EMode.Offline)
|
||||
Markers = new ConcurrentBag<Marker>(Markers.Where(x => x.Seen || (x.WasImported && x.Imports.Count > 0)));
|
||||
else
|
||||
// ensure old import markers are removed if they are no longer part of a "current" import
|
||||
// this MAY remove markers the server sent you (and that you haven't seen), but this should be fixed the next time you enter the zone
|
||||
Markers = new ConcurrentBag<Marker>(Markers.Where(x => x.Seen || !x.WasImported || x.Imports.Count > 0));
|
||||
}
|
||||
|
||||
public static LocalState? Load(uint territoryType)
|
||||
{
|
||||
string path = GetSaveLocation(territoryType);
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
|
||||
string content = File.ReadAllText(path);
|
||||
if (content.Length == 0)
|
||||
return null;
|
||||
|
||||
LocalState localState;
|
||||
int version = 1;
|
||||
if (content[0] == '[')
|
||||
{
|
||||
// v1 only had a list of markers, not a JSON object as root
|
||||
localState = new LocalState(territoryType)
|
||||
{
|
||||
Markers = new ConcurrentBag<Marker>(JsonSerializer.Deserialize<HashSet<Marker>>(content, JsonSerializerOptions) ?? new()),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var save = JsonSerializer.Deserialize<SaveFile>(content, JsonSerializerOptions);
|
||||
if (save == null)
|
||||
return null;
|
||||
|
||||
localState = new LocalState(territoryType)
|
||||
{
|
||||
Markers = new ConcurrentBag<Marker>(save.Markers.Where(o => o.Type == Marker.EType.Trap || o.Type == Marker.EType.Hoard)),
|
||||
};
|
||||
version = save.Version;
|
||||
}
|
||||
|
||||
localState.ApplyFilters();
|
||||
|
||||
if (version <= 3)
|
||||
{
|
||||
foreach (var marker in localState.Markers)
|
||||
marker.RemoteSeenOn = marker.RemoteSeenOn.Select(x => x.ToPartialId()).ToList();
|
||||
}
|
||||
|
||||
if (version < CurrentVersion)
|
||||
localState.Save();
|
||||
|
||||
return localState;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
string path = GetSaveLocation(TerritoryType);
|
||||
|
||||
ApplyFilters();
|
||||
SaveImpl(path);
|
||||
}
|
||||
|
||||
public void Backup(string suffix)
|
||||
{
|
||||
string path = $"{GetSaveLocation(TerritoryType)}.{suffix}";
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
SaveImpl(path);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveImpl(string path)
|
||||
{
|
||||
foreach (var marker in Markers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(marker.SinceVersion))
|
||||
marker.SinceVersion = typeof(Plugin).Assembly.GetName().Version!.ToString(2);
|
||||
}
|
||||
|
||||
if (Markers.Count == 0)
|
||||
File.Delete(path);
|
||||
else
|
||||
{
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(new SaveFile
|
||||
{
|
||||
Version = CurrentVersion,
|
||||
Markers = new HashSet<Marker>(Markers)
|
||||
}, JsonSerializerOptions));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetSaveLocation() => GetSaveLocation(TerritoryType);
|
||||
|
||||
private static string GetSaveLocation(uint territoryType) => Path.Join(PluginConfigDirectory, $"{territoryType}.json");
|
||||
|
||||
public static void ForEach(Action<LocalState> action)
|
||||
{
|
||||
foreach (ETerritoryType territory in typeof(ETerritoryType).GetEnumValues())
|
||||
{
|
||||
LocalState? localState = Load((ushort)territory);
|
||||
if (localState != null)
|
||||
action(localState);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateAll()
|
||||
{
|
||||
ForEach(s => s.Save());
|
||||
}
|
||||
|
||||
public void UndoImport(List<Guid> importIds)
|
||||
{
|
||||
// When saving a floor state, any markers not seen, not remote seen, and not having an import id are removed;
|
||||
// so it is possible to remove "wrong" markers by not having them be in the current import.
|
||||
foreach (var marker in Markers)
|
||||
marker.Imports.RemoveAll(importIds.Contains);
|
||||
}
|
||||
|
||||
public sealed class SaveFile
|
||||
{
|
||||
public int Version { get; set; }
|
||||
public HashSet<Marker> Markers { get; set; } = new();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
using ECommons.SplatoonAPI;
|
||||
using Pal.Client.Rendering;
|
||||
using Pal.Common;
|
||||
using Palace;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Pal.Client
|
||||
{
|
||||
internal sealed class Marker
|
||||
{
|
||||
public EType Type { get; set; } = EType.Unknown;
|
||||
public Vector3 Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether we have encountered the trap/coffer at this location in-game.
|
||||
/// </summary>
|
||||
public bool Seen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Network id for the server you're currently connected to.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Guid? NetworkId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For markers that the server you're connected to doesn't know: Whether this was requested to be uploaded, to avoid duplicate requests.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool UploadRequested { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which account ids this marker was seen. This is a list merely to support different remote endpoints
|
||||
/// (where each server would assign you a different id).
|
||||
/// </summary>
|
||||
public List<string> RemoteSeenOn { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this marker was requested to be seen, to avoid duplicate requests.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool RemoteSeenRequested { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// To keep track of which markers were imported through a downloaded file, we save the associated import-id.
|
||||
///
|
||||
/// Importing another file for the same remote server will remove the old import-id, and add the new import-id here.
|
||||
/// </summary>
|
||||
public List<Guid> Imports { get; set; } = new();
|
||||
|
||||
public bool WasImported { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// To make rollbacks of local data easier, keep track of the version which was used to write the marker initially.
|
||||
/// </summary>
|
||||
public string? SinceVersion { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IRenderElement? RenderElement { get; set; }
|
||||
|
||||
public Marker(EType type, Vector3 position, Guid? networkId = null)
|
||||
{
|
||||
Type = type;
|
||||
Position = position;
|
||||
NetworkId = networkId;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Type, PalaceMath.GetHashCode(Position));
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Marker otherMarker && Type == otherMarker.Type && PalaceMath.IsNearlySamePosition(Position, otherMarker.Position);
|
||||
}
|
||||
|
||||
public static bool operator ==(Marker? a, object? b)
|
||||
{
|
||||
return Equals(a, b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Marker? a, object? b)
|
||||
{
|
||||
return !Equals(a, b);
|
||||
}
|
||||
|
||||
|
||||
public bool IsPermanent() => Type == EType.Trap || Type == EType.Hoard;
|
||||
|
||||
public enum EType
|
||||
{
|
||||
Unknown = ObjectType.Unknown,
|
||||
|
||||
#region Permanent Markers
|
||||
Trap = ObjectType.Trap,
|
||||
Hoard = ObjectType.Hoard,
|
||||
|
||||
[Obsolete]
|
||||
Debug = 3,
|
||||
#endregion
|
||||
|
||||
# region Markers that only show up if they're currently visible
|
||||
SilverCoffer = 100,
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
@ -5,24 +5,25 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Net
|
||||
{
|
||||
internal partial class RemoteApi
|
||||
{
|
||||
public async Task<(bool, List<Marker>)> DownloadRemoteMarkers(ushort territoryId, CancellationToken cancellationToken = default)
|
||||
public async Task<(bool, List<PersistentLocation>)> DownloadRemoteMarkers(ushort territoryId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!await Connect(cancellationToken))
|
||||
return (false, new());
|
||||
|
||||
var palaceClient = new PalaceService.PalaceServiceClient(_channel);
|
||||
var downloadReply = await palaceClient.DownloadFloorsAsync(new DownloadFloorsRequest { TerritoryType = territoryId }, headers: AuthorizedHeaders(), cancellationToken: cancellationToken);
|
||||
return (downloadReply.Success, downloadReply.Objects.Select(CreateMarkerFromNetworkObject).ToList());
|
||||
return (downloadReply.Success, downloadReply.Objects.Select(CreateLocationFromNetworkObject).ToList());
|
||||
}
|
||||
|
||||
public async Task<(bool, List<Marker>)> UploadMarker(ushort territoryType, IList<Marker> markers, CancellationToken cancellationToken = default)
|
||||
public async Task<(bool, List<PersistentLocation>)> UploadLocations(ushort territoryType, IReadOnlyList<PersistentLocation> locations, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (markers.Count == 0)
|
||||
if (locations.Count == 0)
|
||||
return (true, new());
|
||||
|
||||
if (!await Connect(cancellationToken))
|
||||
@ -33,7 +34,7 @@ namespace Pal.Client.Net
|
||||
{
|
||||
TerritoryType = territoryType,
|
||||
};
|
||||
uploadRequest.Objects.AddRange(markers.Select(m => new PalaceObject
|
||||
uploadRequest.Objects.AddRange(locations.Select(m => new PalaceObject
|
||||
{
|
||||
Type = (ObjectType)m.Type,
|
||||
X = m.Position.X,
|
||||
@ -41,12 +42,12 @@ namespace Pal.Client.Net
|
||||
Z = m.Position.Z
|
||||
}));
|
||||
var uploadReply = await palaceClient.UploadFloorsAsync(uploadRequest, headers: AuthorizedHeaders(), cancellationToken: cancellationToken);
|
||||
return (uploadReply.Success, uploadReply.Objects.Select(CreateMarkerFromNetworkObject).ToList());
|
||||
return (uploadReply.Success, uploadReply.Objects.Select(CreateLocationFromNetworkObject).ToList());
|
||||
}
|
||||
|
||||
public async Task<bool> MarkAsSeen(ushort territoryType, IList<Marker> markers, CancellationToken cancellationToken = default)
|
||||
public async Task<bool> MarkAsSeen(ushort territoryType, IReadOnlyList<PersistentLocation> locations, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (markers.Count == 0)
|
||||
if (locations.Count == 0)
|
||||
return true;
|
||||
|
||||
if (!await Connect(cancellationToken))
|
||||
@ -54,15 +55,22 @@ namespace Pal.Client.Net
|
||||
|
||||
var palaceClient = new PalaceService.PalaceServiceClient(_channel);
|
||||
var seenRequest = new MarkObjectsSeenRequest { TerritoryType = territoryType };
|
||||
foreach (var marker in markers)
|
||||
foreach (var marker in locations)
|
||||
seenRequest.NetworkIds.Add(marker.NetworkId.ToString());
|
||||
|
||||
var seenReply = await palaceClient.MarkObjectsSeenAsync(seenRequest, headers: AuthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken);
|
||||
return seenReply.Success;
|
||||
}
|
||||
|
||||
private Marker CreateMarkerFromNetworkObject(PalaceObject obj) =>
|
||||
new Marker((Marker.EType)obj.Type, new Vector3(obj.X, obj.Y, obj.Z), Guid.Parse(obj.NetworkId));
|
||||
private PersistentLocation CreateLocationFromNetworkObject(PalaceObject obj)
|
||||
{
|
||||
return new PersistentLocation
|
||||
{
|
||||
Type = obj.Type.ToMemoryType(),
|
||||
Position = new Vector3(obj.X, obj.Y, obj.Z),
|
||||
NetworkId = Guid.Parse(obj.NetworkId),
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<(bool, List<FloorStatistics>)> FetchStatistics(CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace Pal.Client.Net
|
||||
#if DEBUG
|
||||
public const string RemoteUrl = "http://localhost:5145";
|
||||
#else
|
||||
public const string RemoteUrl = "https://pal.liza.sh";
|
||||
//public const string RemoteUrl = "https://pal.liza.sh";
|
||||
#endif
|
||||
private readonly string _userAgent =
|
||||
$"{typeof(RemoteApi).Assembly.GetName().Name?.Replace(" ", "")}/{typeof(RemoteApi).Assembly.GetName().Version?.ToString(2)}";
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Rendering
|
||||
{
|
||||
@ -12,7 +13,7 @@ namespace Pal.Client.Rendering
|
||||
|
||||
void ResetLayer(ELayer layer);
|
||||
|
||||
IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false);
|
||||
IRenderElement CreateElement(MemoryLocation.EType type, Vector3 pos, uint color, bool fill = false);
|
||||
|
||||
void DrawDebugItems(uint trapColor, uint hoardColor);
|
||||
}
|
||||
|
@ -1,20 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Rendering
|
||||
{
|
||||
internal sealed class MarkerConfig
|
||||
{
|
||||
private static readonly MarkerConfig EmptyConfig = new();
|
||||
private static readonly Dictionary<Marker.EType, MarkerConfig> MarkerConfigs = new()
|
||||
|
||||
private static readonly Dictionary<MemoryLocation.EType, MarkerConfig> MarkerConfigs = new()
|
||||
{
|
||||
{ Marker.EType.Trap, new MarkerConfig { Radius = 1.7f } },
|
||||
{ Marker.EType.Hoard, new MarkerConfig { Radius = 1.7f, OffsetY = -0.03f } },
|
||||
{ Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } },
|
||||
{ MemoryLocation.EType.Trap, new MarkerConfig { Radius = 1.7f } },
|
||||
{ MemoryLocation.EType.Hoard, new MarkerConfig { Radius = 1.7f, OffsetY = -0.03f } },
|
||||
{ MemoryLocation.EType.SilverCoffer, new MarkerConfig { Radius = 1f } },
|
||||
};
|
||||
|
||||
public float OffsetY { get; private init; }
|
||||
public float Radius { get; private init; } = 0.25f;
|
||||
|
||||
public static MarkerConfig ForType(Marker.EType type) => MarkerConfigs.GetValueOrDefault(type, EmptyConfig);
|
||||
public static MarkerConfig ForType(MemoryLocation.EType type) =>
|
||||
MarkerConfigs.GetValueOrDefault(type, EmptyConfig);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Numerics;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Rendering
|
||||
{
|
||||
@ -56,7 +57,7 @@ namespace Pal.Client.Rendering
|
||||
public void ResetLayer(ELayer layer)
|
||||
=> _implementation.ResetLayer(layer);
|
||||
|
||||
public IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false)
|
||||
public IRenderElement CreateElement(MemoryLocation.EType type, Vector3 pos, uint color, bool fill = false)
|
||||
=> _implementation.CreateElement(type, pos, color, fill);
|
||||
|
||||
public ERenderer GetConfigValue()
|
||||
|
@ -9,6 +9,7 @@ using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Gui;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Rendering
|
||||
{
|
||||
@ -53,7 +54,7 @@ namespace Pal.Client.Rendering
|
||||
l.Dispose();
|
||||
}
|
||||
|
||||
public IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false)
|
||||
public IRenderElement CreateElement(MemoryLocation.EType type, Vector3 pos, uint color, bool fill = false)
|
||||
{
|
||||
var config = MarkerConfig.ForType(type);
|
||||
return new SimpleElement
|
||||
@ -73,9 +74,13 @@ namespace Pal.Client.Rendering
|
||||
TerritoryType = _clientState.TerritoryType,
|
||||
Elements = new List<SimpleElement>
|
||||
{
|
||||
(SimpleElement)CreateElement(Marker.EType.Trap, _clientState.LocalPlayer?.Position ?? default,
|
||||
(SimpleElement)CreateElement(
|
||||
MemoryLocation.EType.Trap,
|
||||
_clientState.LocalPlayer?.Position ?? default,
|
||||
trapColor),
|
||||
(SimpleElement)CreateElement(Marker.EType.Hoard, _clientState.LocalPlayer?.Position ?? default,
|
||||
(SimpleElement)CreateElement(
|
||||
MemoryLocation.EType.Hoard,
|
||||
_clientState.LocalPlayer?.Position ?? default,
|
||||
hoardColor)
|
||||
},
|
||||
ExpiresAt = Environment.TickCount64 + RenderData.TestLayerTimeout
|
||||
@ -120,15 +125,15 @@ namespace Pal.Client.Rendering
|
||||
|
||||
switch (e.Type)
|
||||
{
|
||||
case Marker.EType.Hoard:
|
||||
case MemoryLocation.EType.Hoard:
|
||||
// ignore distance if this is a found hoard coffer
|
||||
if (_territoryState.PomanderOfIntuition == PomanderState.Active &&
|
||||
_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander)
|
||||
break;
|
||||
|
||||
goto case Marker.EType.Trap;
|
||||
goto case MemoryLocation.EType.Trap;
|
||||
|
||||
case Marker.EType.Trap:
|
||||
case MemoryLocation.EType.Trap:
|
||||
var playerPos = _clientState.LocalPlayer?.Position;
|
||||
if (playerPos == null)
|
||||
return;
|
||||
@ -189,7 +194,7 @@ namespace Pal.Client.Rendering
|
||||
public sealed class SimpleElement : IRenderElement
|
||||
{
|
||||
public bool IsValid { get; set; } = true;
|
||||
public required Marker.EType Type { get; init; }
|
||||
public required MemoryLocation.EType Type { get; init; }
|
||||
public required Vector3 Position { get; init; }
|
||||
public required uint Color { get; set; }
|
||||
public required float Radius { get; init; }
|
||||
|
@ -13,6 +13,7 @@ using Dalamud.Game.ClientState;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Rendering
|
||||
{
|
||||
@ -57,7 +58,8 @@ namespace Pal.Client.Rendering
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Could not create splatoon layer {Layer} with {Count} elements", layer, elements.Count);
|
||||
_logger.LogError(e, "Could not create splatoon layer {Layer} with {Count} elements", layer,
|
||||
elements.Count);
|
||||
_debugState.SetFromException(e);
|
||||
}
|
||||
});
|
||||
@ -78,7 +80,7 @@ namespace Pal.Client.Rendering
|
||||
private string ToLayerName(ELayer layer)
|
||||
=> $"PalacePal.{layer}";
|
||||
|
||||
public IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false)
|
||||
public IRenderElement CreateElement(MemoryLocation.EType type, Vector3 pos, uint color, bool fill = false)
|
||||
{
|
||||
MarkerConfig config = MarkerConfig.ForType(type);
|
||||
Element element = new Element(ElementType.CircleAtFixedCoordinates)
|
||||
@ -109,8 +111,8 @@ namespace Pal.Client.Rendering
|
||||
|
||||
var elements = new List<IRenderElement>
|
||||
{
|
||||
CreateElement(Marker.EType.Trap, pos.Value, trapColor),
|
||||
CreateElement(Marker.EType.Hoard, pos.Value, hoardColor),
|
||||
CreateElement(MemoryLocation.EType.Trap, pos.Value, trapColor),
|
||||
CreateElement(MemoryLocation.EType.Hoard, pos.Value, hoardColor),
|
||||
};
|
||||
|
||||
if (!Splatoon.AddDynamicElements(ToLayerName(ELayer.Test),
|
||||
|
@ -8,7 +8,7 @@ namespace Pal.Client.Scheduled
|
||||
{
|
||||
internal interface IHandler
|
||||
{
|
||||
void RunIfCompatible(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers);
|
||||
void RunIfCompatible(IQueueOnFrameworkThread queued, ref bool recreateLayout);
|
||||
}
|
||||
|
||||
internal abstract class Handler<T> : IHandler
|
||||
@ -21,14 +21,14 @@ namespace Pal.Client.Scheduled
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected abstract void Run(T queued, ref bool recreateLayout, ref bool saveMarkers);
|
||||
protected abstract void Run(T queued, ref bool recreateLayout);
|
||||
|
||||
public void RunIfCompatible(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers)
|
||||
public void RunIfCompatible(IQueueOnFrameworkThread queued, ref bool recreateLayout)
|
||||
{
|
||||
if (queued is T t)
|
||||
{
|
||||
_logger.LogInformation("Handling {QueuedType}", queued.GetType());
|
||||
Run(t, ref recreateLayout, ref saveMarkers);
|
||||
Run(t, ref recreateLayout);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Rendering;
|
||||
|
||||
namespace Pal.Client.Scheduled
|
||||
@ -9,38 +10,19 @@ namespace Pal.Client.Scheduled
|
||||
{
|
||||
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedConfigUpdate>
|
||||
{
|
||||
private readonly IPalacePalConfiguration _configuration;
|
||||
private readonly FloorService _floorService;
|
||||
private readonly TerritoryState _territoryState;
|
||||
private readonly RenderAdapter _renderAdapter;
|
||||
|
||||
public Handler(
|
||||
ILogger<Handler> logger,
|
||||
IPalacePalConfiguration configuration,
|
||||
FloorService floorService,
|
||||
TerritoryState territoryState,
|
||||
RenderAdapter renderAdapter)
|
||||
: base(logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_floorService = floorService;
|
||||
_territoryState = territoryState;
|
||||
_renderAdapter = renderAdapter;
|
||||
}
|
||||
|
||||
protected override void Run(QueuedConfigUpdate queued, ref bool recreateLayout, ref bool saveMarkers)
|
||||
protected override void Run(QueuedConfigUpdate queued, ref bool recreateLayout)
|
||||
{
|
||||
if (_configuration.Mode == EMode.Offline)
|
||||
{
|
||||
LocalState.UpdateAll();
|
||||
_floorService.FloorMarkers.Clear();
|
||||
_floorService.EphemeralMarkers.Clear();
|
||||
_territoryState.LastTerritory = 0;
|
||||
|
||||
recreateLayout = true;
|
||||
saveMarkers = true;
|
||||
}
|
||||
|
||||
// TODO filter stuff if offline
|
||||
_renderAdapter.ConfigUpdated();
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
using Account;
|
||||
using Pal.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Pal.Client.Database;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Properties;
|
||||
using Pal.Client.Windows;
|
||||
|
||||
@ -31,63 +27,46 @@ namespace Pal.Client.Scheduled
|
||||
|
||||
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedImport>
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly Chat _chat;
|
||||
private readonly FloorService _floorService;
|
||||
private readonly ImportService _importService;
|
||||
private readonly ConfigWindow _configWindow;
|
||||
|
||||
public Handler(
|
||||
ILogger<Handler> logger,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
Chat chat,
|
||||
FloorService floorService,
|
||||
ImportService importService,
|
||||
ConfigWindow configWindow)
|
||||
: base(logger)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_chat = chat;
|
||||
_floorService = floorService;
|
||||
_importService = importService;
|
||||
_configWindow = configWindow;
|
||||
}
|
||||
|
||||
protected override void Run(QueuedImport import, ref bool recreateLayout, ref bool saveMarkers)
|
||||
protected override void Run(QueuedImport import, ref bool recreateLayout)
|
||||
{
|
||||
recreateLayout = true;
|
||||
saveMarkers = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (!Validate(import))
|
||||
return;
|
||||
|
||||
List<Guid> oldExportIds = _importService.FindForServer(import.Export.ServerUrl)
|
||||
.Select(x => x.Id)
|
||||
.ToList();
|
||||
|
||||
foreach (var remoteFloor in import.Export.Floors)
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
ushort territoryType = (ushort)remoteFloor.TerritoryType;
|
||||
var localState = _floorService.GetFloorMarkers(territoryType);
|
||||
|
||||
localState.UndoImport(oldExportIds);
|
||||
ImportFloor(import, remoteFloor, localState);
|
||||
|
||||
localState.Save();
|
||||
using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||
(import.ImportedTraps, import.ImportedHoardCoffers) = _importService.Import(import.Export);
|
||||
}
|
||||
|
||||
_importService.RemoveAllByIds(oldExportIds);
|
||||
_importService.RemoveById(import.ExportId);
|
||||
_importService.Add(new ImportHistory
|
||||
{
|
||||
Id = import.ExportId,
|
||||
RemoteUrl = import.Export.ServerUrl,
|
||||
ExportedAt = import.Export.CreatedAt.ToDateTime(),
|
||||
ImportedAt = DateTime.UtcNow,
|
||||
});
|
||||
_configWindow.UpdateLastImport();
|
||||
|
||||
_logger.LogInformation(
|
||||
$"Imported {import.ExportId} for {import.ImportedTraps} traps, {import.ImportedHoardCoffers} hoard coffers");
|
||||
"Imported {ExportId} for {Traps} traps, {Hoard} hoard coffers", import.ExportId,
|
||||
import.ImportedTraps, import.ImportedHoardCoffers);
|
||||
_chat.Message(string.Format(Localization.ImportCompleteStatistics, import.ImportedTraps,
|
||||
import.ImportedHoardCoffers));
|
||||
}
|
||||
@ -103,7 +82,8 @@ namespace Pal.Client.Scheduled
|
||||
if (import.Export.ExportVersion != ExportConfig.ExportVersion)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Import: Different version in export file, {ExportVersion} != {ConfiguredVersion}", import.Export.ExportVersion, ExportConfig.ExportVersion);
|
||||
"Import: Different version in export file, {ExportVersion} != {ConfiguredVersion}",
|
||||
import.Export.ExportVersion, ExportConfig.ExportVersion);
|
||||
_chat.Error(Localization.Error_ImportFailed_IncompatibleVersion);
|
||||
return false;
|
||||
}
|
||||
@ -127,28 +107,6 @@ namespace Pal.Client.Scheduled
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ImportFloor(QueuedImport import, ExportFloor remoteFloor, LocalState localState)
|
||||
{
|
||||
var remoteMarkers = remoteFloor.Objects.Select(m =>
|
||||
new Marker((Marker.EType)m.Type, new Vector3(m.X, m.Y, m.Z)) { WasImported = true });
|
||||
foreach (var remoteMarker in remoteMarkers)
|
||||
{
|
||||
Marker? localMarker = localState.Markers.SingleOrDefault(x => x == remoteMarker);
|
||||
if (localMarker == null)
|
||||
{
|
||||
localState.Markers.Add(remoteMarker);
|
||||
localMarker = remoteMarker;
|
||||
|
||||
if (localMarker.Type == Marker.EType.Trap)
|
||||
import.ImportedTraps++;
|
||||
else if (localMarker.Type == Marker.EType.Hoard)
|
||||
import.ImportedHoardCoffers++;
|
||||
}
|
||||
|
||||
remoteMarker.Imports.Add(import.ExportId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Floors.Tasks;
|
||||
using Pal.Client.Net;
|
||||
|
||||
namespace Pal.Client.Scheduled
|
||||
@ -14,10 +17,11 @@ namespace Pal.Client.Scheduled
|
||||
public required SyncType Type { get; init; }
|
||||
public required ushort TerritoryType { get; init; }
|
||||
public required bool Success { get; init; }
|
||||
public required List<Marker> Markers { get; init; }
|
||||
public required IReadOnlyList<PersistentLocation> Locations { get; init; }
|
||||
|
||||
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedSyncResponse>
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IPalacePalConfiguration _configuration;
|
||||
private readonly FloorService _floorService;
|
||||
private readonly TerritoryState _territoryState;
|
||||
@ -25,47 +29,57 @@ namespace Pal.Client.Scheduled
|
||||
|
||||
public Handler(
|
||||
ILogger<Handler> logger,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IPalacePalConfiguration configuration,
|
||||
FloorService floorService,
|
||||
TerritoryState territoryState,
|
||||
DebugState debugState)
|
||||
: base(logger)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_configuration = configuration;
|
||||
_floorService = floorService;
|
||||
_territoryState = territoryState;
|
||||
_debugState = debugState;
|
||||
}
|
||||
|
||||
protected override void Run(QueuedSyncResponse queued, ref bool recreateLayout, ref bool saveMarkers)
|
||||
protected override void Run(QueuedSyncResponse queued, ref bool recreateLayout)
|
||||
{
|
||||
recreateLayout = true;
|
||||
saveMarkers = true;
|
||||
|
||||
try
|
||||
{
|
||||
var remoteMarkers = queued.Markers;
|
||||
var currentFloor = _floorService.GetFloorMarkers(queued.TerritoryType);
|
||||
if (_configuration.Mode == EMode.Online && queued.Success && remoteMarkers.Count > 0)
|
||||
var remoteMarkers = queued.Locations;
|
||||
var memoryTerritory = _floorService.GetTerritoryIfReady(queued.TerritoryType);
|
||||
if (memoryTerritory != null && _configuration.Mode == EMode.Online && queued.Success &&
|
||||
remoteMarkers.Count > 0)
|
||||
{
|
||||
switch (queued.Type)
|
||||
{
|
||||
case SyncType.Download:
|
||||
case SyncType.Upload:
|
||||
List<PersistentLocation> newLocations = new();
|
||||
foreach (var remoteMarker in remoteMarkers)
|
||||
{
|
||||
// Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved.
|
||||
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
|
||||
if (localMarker != null)
|
||||
PersistentLocation? localLocation =
|
||||
memoryTerritory.Locations.SingleOrDefault(x => x == remoteMarker);
|
||||
if (localLocation != null)
|
||||
{
|
||||
localMarker.NetworkId = remoteMarker.NetworkId;
|
||||
localLocation.NetworkId = remoteMarker.NetworkId;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (queued.Type == SyncType.Download)
|
||||
currentFloor.Markers.Add(remoteMarker);
|
||||
{
|
||||
memoryTerritory.Locations.Add(remoteMarker);
|
||||
newLocations.Add(remoteMarker);
|
||||
}
|
||||
}
|
||||
|
||||
if (newLocations.Count > 0)
|
||||
new SaveNewLocations(_serviceScopeFactory, memoryTerritory, newLocations).Start();
|
||||
|
||||
break;
|
||||
|
||||
case SyncType.MarkSeen:
|
||||
@ -73,11 +87,23 @@ namespace Pal.Client.Scheduled
|
||||
_configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
||||
if (partialAccountId == null)
|
||||
break;
|
||||
|
||||
List<PersistentLocation> locationsToUpdate = new();
|
||||
foreach (var remoteMarker in remoteMarkers)
|
||||
{
|
||||
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
|
||||
if (localMarker != null)
|
||||
localMarker.RemoteSeenOn.Add(partialAccountId);
|
||||
PersistentLocation? localLocation =
|
||||
memoryTerritory.Locations.SingleOrDefault(x => x == remoteMarker);
|
||||
if (localLocation != null)
|
||||
{
|
||||
localLocation.RemoteSeenOn.Add(partialAccountId);
|
||||
locationsToUpdate.Add(localLocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (locationsToUpdate.Count > 0)
|
||||
{
|
||||
new MarkRemoteSeen(_serviceScopeFactory, memoryTerritory, locationsToUpdate,
|
||||
partialAccountId).Start();
|
||||
}
|
||||
|
||||
break;
|
||||
@ -91,22 +117,22 @@ namespace Pal.Client.Scheduled
|
||||
if (queued.Type == SyncType.Download)
|
||||
{
|
||||
if (queued.Success)
|
||||
_territoryState.TerritorySyncState = SyncState.Complete;
|
||||
_territoryState.TerritorySyncState = ESyncState.Complete;
|
||||
else
|
||||
_territoryState.TerritorySyncState = SyncState.Failed;
|
||||
_territoryState.TerritorySyncState = ESyncState.Failed;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_debugState.SetFromException(e);
|
||||
if (queued.Type == SyncType.Download)
|
||||
_territoryState.TerritorySyncState = SyncState.Failed;
|
||||
_territoryState.TerritorySyncState = ESyncState.Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum SyncState
|
||||
public enum ESyncState
|
||||
{
|
||||
NotAttempted,
|
||||
NotNeeded,
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Pal.Client.Configuration;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Floors;
|
||||
using Pal.Client.Windows;
|
||||
using Pal.Common;
|
||||
|
||||
@ -20,28 +21,18 @@ namespace Pal.Client.Scheduled
|
||||
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedUndoImport>
|
||||
{
|
||||
private readonly ImportService _importService;
|
||||
private readonly FloorService _floorService;
|
||||
private readonly ConfigWindow _configWindow;
|
||||
|
||||
public Handler(ILogger<Handler> logger, ImportService importService, FloorService floorService, ConfigWindow configWindow)
|
||||
public Handler(ILogger<Handler> logger, ImportService importService, ConfigWindow configWindow)
|
||||
: base(logger)
|
||||
{
|
||||
_importService = importService;
|
||||
_floorService = floorService;
|
||||
_configWindow = configWindow;
|
||||
}
|
||||
|
||||
protected override void Run(QueuedUndoImport queued, ref bool recreateLayout, ref bool saveMarkers)
|
||||
protected override void Run(QueuedUndoImport queued, ref bool recreateLayout)
|
||||
{
|
||||
recreateLayout = true;
|
||||
saveMarkers = true;
|
||||
|
||||
foreach (ETerritoryType territoryType in typeof(ETerritoryType).GetEnumValues())
|
||||
{
|
||||
var localState = _floorService.GetFloorMarkers((ushort)territoryType);
|
||||
localState.UndoImport(new List<Guid> { queued.ExportId });
|
||||
localState.Save();
|
||||
}
|
||||
|
||||
_importService.RemoveById(queued.ExportId);
|
||||
_configWindow.UpdateLastImport();
|
||||
|
@ -25,6 +25,7 @@ using Pal.Client.Configuration;
|
||||
using Pal.Client.Database;
|
||||
using Pal.Client.DependencyInjection;
|
||||
using Pal.Client.Extensions;
|
||||
using Pal.Client.Floors;
|
||||
|
||||
namespace Pal.Client.Windows
|
||||
{
|
||||
@ -382,24 +383,25 @@ namespace Pal.Client.Windows
|
||||
ImGui.Text($"{_debugState.DebugMessage}");
|
||||
|
||||
ImGui.Indent();
|
||||
if (_floorService.FloorMarkers.TryGetValue(_territoryState.LastTerritory, out var currentFloor))
|
||||
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
|
||||
if (memoryTerritory != null)
|
||||
{
|
||||
if (_trapConfig.Show)
|
||||
{
|
||||
int traps = currentFloor.Markers.Count(x => x.Type == Marker.EType.Trap);
|
||||
int traps = memoryTerritory.Locations.Count(x => x.Type == MemoryLocation.EType.Trap);
|
||||
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
|
||||
}
|
||||
|
||||
if (_hoardConfig.Show)
|
||||
{
|
||||
int hoardCoffers = currentFloor.Markers.Count(x => x.Type == Marker.EType.Hoard);
|
||||
int hoardCoffers = memoryTerritory.Locations.Count(x => x.Type == MemoryLocation.EType.Hoard);
|
||||
ImGui.Text($"{hoardCoffers} known hoard coffer{(hoardCoffers == 1 ? "" : "s")}");
|
||||
}
|
||||
|
||||
if (_silverConfig.Show)
|
||||
{
|
||||
int silverCoffers =
|
||||
_floorService.EphemeralMarkers.Count(x => x.Type == Marker.EType.SilverCoffer);
|
||||
_floorService.EphemeralLocations.Count(x => x.Type == MemoryLocation.EType.SilverCoffer);
|
||||
ImGui.Text(
|
||||
$"{silverCoffers} silver coffer{(silverCoffers == 1 ? "" : "s")} visible on current floor");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user