XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
UTF-8
using XFEEliteDangerous.Client;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;

namespace XFEEliteDangerous.Core;

public sealed partial class OfflineWorkspace
{
    public Task<JsonNode> OpenLocalOwnerSessionAsync()=>OpenOwner();
    private async Task<JsonNode> OpenOwner()
    {
        var owner=await store.Update(state=>
        {
            const string email="local-owner@xfe.local";
            var account=state.Accounts.Values.SingleOrDefault(value=>value.Email==email);
            if(account is null)account=Register(state,email,AppText.Get("OfflineWorkspace_Fleet.Text403"),Wire.Secret(),AccountRoles.Administrator);
            if(account.Disabled||account.Role!=AccountRoles.Administrator)throw new UnauthorizedAccessException(AppText.Get("OfflineWorkspace_Fleet.Text404"));
            return (account.Id,Profile:Json(account.Public()));
        });
        var token=Wire.Secret();sessions[token]=(owner.Id,clock.GetUtcNow().AddHours(12));
        return new JsonObject{["token"]=token,["account"]=owner.Profile};
    }
    public Task<JsonNode> FleetAsync(string? session,string? targetId)=>store.Read(state=>
    {
        var account=Target(state,session,targetId);var save=account.NativeSave?.ExportCurrent(account);
        if(save is null)return Json(new{commander=account.Commander.Public(),native=false,ships=account.Commander.Ships,stored=Array.Empty<object>()});
        var ships=save.Snapshot.Vessels.Select(ship=>new
        {
            ship.Id,prototype=ship.Name,label=save.ShipLabels.GetValueOrDefault(ship.Id,new(ship.Name,"")),active=ship.Id==save.Snapshot.CurrentVesselId,
            ship.Fuel,ship.Health,
            slots=ship.Modules.Select(slot=>new{slot.Name,slot.Module,removable=Removable(slot.Name),
                compatible=save.Catalogue.Modules.Where(module=>Fits(slot,module)).ToArray()}).ToArray()
        }).ToArray();
        return Json(new{commander=account.Commander.Public(),native=true,ships,stored=save.StoredModules});
    });
    public Task<JsonNode> EditFleetAsync(string? session,string? targetId,JsonObject command,string version)
    {
        var body=(JsonObject)command.DeepClone();
        return store.Update(state=>
        {
            var actor=Authenticate(state,session);var account=Target(state,actor,targetId);
            if(actor.Role!=AccountRoles.Administrator&&!state.Permissions.AllowPlayerShipEditing)throw new UnauthorizedAccessException(AppText.Get("FleetPage.Text153"));
            CheckVersion(account.Commander,version);
            var save=account.NativeSave?.ExportCurrent(account)??throw new InvalidOperationException(AppText.Get("OfflineWorkspace_Fleet.Text405"));
            var ships=save.Snapshot.Vessels.ToList();var labels=new Dictionary<uint,NativeShipLabel>(save.ShipLabels);
            uint Id(string key)=>(uint)Integer(body,key,1,uint.MaxValue-1L);
            var action=Text(body,"action",40);var selected=action=="clone"?Id("templateShipId"):Id("shipId");
            var ship=ships.SingleOrDefault(value=>value.Id==selected)??throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text406"));
            if(action=="clone")
            {
                var id=checked(ships.Max(value=>value.Id)+1);if(id==uint.MaxValue)throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text407"));
                NativeSlotSnapshot Copy(NativeSlotSnapshot slot)=>slot with{Module=slot.Module is {} module?module with{Uid=null}:null};
                ships.Add(ship with{Id=id,Cargo=[],ParentShipId=null,Modules=ship.Modules.Select(Copy).ToArray(),FixedModules=ship.FixedModules.Select(Copy).ToArray()});
                labels[id]=new(Text(body,"name",100),Text(body,"ident",24));
            }
            else if(action=="rename")labels[ship.Id]=new(Text(body,"name",100),Text(body,"ident",24));
            else if(action=="remove")
            {
                if(ship.Id==save.Snapshot.CurrentVesselId||ship.Id==save.Snapshot.ParentVesselId||ships.Any(value=>value.ParentShipId==ship.Id))
                    throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text408"));
                ships.Remove(ship);labels.Remove(ship.Id);
                if(save.UploadState is {} upload)save=save with{UploadState=upload with{VesselExtras=upload.VesselExtras.Where(pair=>pair.Key!=ship.Id).ToDictionary()}};
            }
            else if(action=="select")
            {
                if((ship.Location.Address&0x007F_FFFF_FFFF_FFFFUL)!=save.Profile.CurrentSystem)throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text409"));
                save=save with{Snapshot=save.Snapshot with{CurrentVesselId=ship.Id,ParentVesselId=ship.Id,Location=ship.Location}};
            }
            else if(action is "module" or "store" or "retrieve")
            {
                var slotName=Text(body,"slot",100);var slot=ship.Modules.SingleOrDefault(value=>value.Name==slotName)??throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text410"));
                var storage=save.StoredModules.ToList();NativeModuleSnapshot? replacement;
                if(action=="store")
                {
                    if(!Removable(slot.Name))throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text411"));
                    replacement=null;
                }
                else if(action=="retrieve")
                {
                    var entry=storage.SingleOrDefault(value=>value.SlotId==Id("storageId"))??throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text412"));
                    if(entry.InTransit)throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text413"));
                    if(!Fits(slot,new(entry.Module.Id,entry.Module.Name)))throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text414"));
                    replacement=entry.Module;storage.Remove(entry);
                }
                else
                {
                    var name=Text(body,"module",160);var definition=save.Catalogue.Modules.SingleOrDefault(value=>value.Name==name)
                        ??throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text415"));
                    if(!Fits(slot,definition))throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text416"));
                    var source=save.Snapshot.Vessels.SelectMany(value=>value.Modules.Concat(value.FixedModules)).Select(value=>value.Module)
                        .FirstOrDefault(value=>value?.Name==name)??save.StoredModules.Select(value=>value.Module).FirstOrDefault(value=>value.Name==name);
                    replacement=source is {} template?template with{Uid=null}:new NativeModuleSnapshot{Id=definition.Id,Name=definition.Name,Value=0,Excess=0,On=true,Priority=0,HealthMillion=1_000_000,Free=true};
                }
                if(slot.Module is {} old)
                {
                    if(old.Id==0)throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text417"));
                    var storageId=storage.Count==0?1:checked(storage.Max(value=>value.SlotId)+1);
                    storage.Add(new(){SlotId=storageId,SystemName=save.SystemMetadata?.Name??"Local",MarketId=0,InTransit=false,TransferTime=0,Value=checked((long)old.Value),Module=old});
                }
                ship=ship with{Modules=ship.Modules.Select(value=>value.Name==slotName?value with{Module=replacement}:value).ToArray()};
                ships[ships.FindIndex(value=>value.Id==ship.Id)]=ship;
                save=save with{StoredModules=storage,Catalogue=save.Catalogue with{Ships=save.Catalogue.Ships.Select(definition=>definition.Name==ship.Name
                    ?definition with{Modules=definition.Modules.Select(value=>Removable(value.Name)?value with{RequiredModule=false}:value).ToArray()}:definition).ToArray()}};
            }
            else throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text418"));
            save=save with{Snapshot=save.Snapshot with{Vessels=ships},ShipLabels=labels};
            return CommitManagedNative(account,save);
        });
    }
    private JsonNode CommitManagedNative(AccountState account,NativeCommanderSave save)
    {
        var inventory=save.CreateInventory(account,checked(account.Commander.Revision+1));
        save.ValidateCurrent(new(){NativeUserId=account.NativeUserId,NativeSave=save,Commander=inventory});
        var backup=store.BackupNative(account);account.NativeSave=save;account.Commander=inventory;
        return Json(new{saved=true,backup,commander=inventory.Public(),requiresReload=true});
    }
    public Task<JsonNode> PrepareMaterialsAsync(string? session,string? targetId,string version)=>store.Update(state=>
    {
        var actor=Authenticate(state,session);RequireAdministrator(actor);var account=Target(state,actor,targetId);CheckVersion(account.Commander,version);
        var save=account.NativeSave?.ExportCurrent(account)??throw new InvalidOperationException(AppText.Get("OfflineWorkspace_Fleet.Text419"));
        var rows=save.Materials.Concat(BundledData.NativeMaterials.Where(def=>!save.Materials.Any(value=>value.Id==def.Id))).ToArray();
        foreach(var row in rows)
            if(!state.Catalog.Any(value=>value.Id==row.Id.ToString(CultureInfo.InvariantCulture)&&value.Category==row.Type))state.Catalog.Add(new(row.Id.ToString(CultureInfo.InvariantCulture),row.Name,row.Type));
        return CommitManagedNative(account,save with{Materials=rows});
    });
    private static bool Removable(string slot)=>slot.Contains("Hardpoint",StringComparison.Ordinal)||slot.StartsWith("Slot",StringComparison.Ordinal);
    public Task<JsonNode> InitializeNativeAsync(string? session,string? targetId,string version)=>store.Update(state=>
    {
        var actor=Authenticate(state,session);RequireAdministrator(actor);var account=Target(state,actor,targetId);CheckVersion(account.Commander,version);
        if(account.NativeSave is not null)throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text420"));
        if(account.Commander.Ships.Count>0||account.Commander.Cargo.Count>0||account.Commander.Materials.Count>0)
            throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text421"));
        var seed=state.Accounts.Values.Select(value=>value.NativeSave).FirstOrDefault(value=>value?.SystemMetadata is not null)
            ??throw new InvalidDataException(AppText.Get("OfflineWorkspace_Fleet.Text422"));
        var profile=seed.Profile with{Name=account.DisplayName,LoanCeiling=0,Debt=0,StartupState=0,ProvingGround=new(0,0,0,0),BattleRating=0,
            Avatar="",AvatarXml="",VehicleDistanceTravelled=0,AtrLevel=0,Notoriety=0,MarkedWanted=false};
        var save=NativeSidewinderSaveFactory.Create(account.NativeUserId,1,account.Commander.Credits,profile,seed.Snapshot.Location)
            with{SystemMetadata=seed.SystemMetadata,Materials=BundledData.NativeMaterials,ShipLabels=new Dictionary<uint,NativeShipLabel>{{1,new("Sidewinder","XFE-01")}}};
        foreach(var row in save.Materials)
            if(!state.Catalog.Any(value=>value.Id==row.Id.ToString(CultureInfo.InvariantCulture)&&value.Category==row.Type))state.Catalog.Add(new(row.Id.ToString(CultureInfo.InvariantCulture),row.Name,row.Type));
        return CommitManagedNative(account,save);
    });
    private static bool Fits(NativeSlotSnapshot slot,NativeModuleDefinition module)
    {
        if(slot.Module?.Name==module.Name)return module.Id>0;
        if(module.Id==0)return false;
        var name=module.Name.ToLowerInvariant();var current=slot.Module?.Name.ToLowerInvariant();
        int Size(string value){var match=Regex.Match(value,"_size([0-9]+)",RegexOptions.IgnoreCase);return match.Success?int.Parse(match.Groups[1].Value,CultureInfo.InvariantCulture):0;}
        if(slot.Name.Contains("Hardpoint",StringComparison.Ordinal))
        {
            if(!name.StartsWith("hpt_",StringComparison.Ordinal))return false;
            var sizes=new[]{"Tiny","Small","Medium","Large","Huge"};var capacity=Array.FindIndex(sizes,value=>slot.Name.StartsWith(value,StringComparison.OrdinalIgnoreCase));
            var size=Array.FindIndex(sizes,value=>name.EndsWith("_"+value,StringComparison.OrdinalIgnoreCase));return size>=0&&capacity>=size;
        }
        if(slot.Name.StartsWith("Slot",StringComparison.Ordinal))return name.StartsWith("int_",StringComparison.Ordinal)&&Size(name)<=Size(slot.Name)
            &&!new[]{"int_powerplant_","int_engine_","int_hyperdrive_","int_lifesupport_","int_powerdistributor_","int_sensors_","int_fueltank_"}.Any(name.StartsWith);
        if(current is null)return false;
        var prefix=current.Split("_size",StringSplitOptions.None)[0];return name.StartsWith(prefix+"_size",StringComparison.Ordinal)&&Size(name)<=Size(current);
    }
}