Add cutscene talk, update EW parts
This commit is contained in:
parent
a5bb4f15cb
commit
ab2c4f505c
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "LLib"]
|
||||
path = LLib
|
||||
url = https://git.carvel.li/liza/LLib.git
|
1
LLib
Submodule
1
LLib
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b5125d4b3f7cdc0c7514a01764e5b5d4d85f80a7
|
@ -2,6 +2,8 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Questionable", "Questionable\Questionable.csproj", "{C91EEF13-A1AC-4A40-B695-DD4E378E5989}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LLib", "LLib\LLib.csproj", "{EEDE3BBE-E260-445E-8FB3-1264E0CBBE91}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -12,5 +14,9 @@ Global
|
||||
{C91EEF13-A1AC-4A40-B695-DD4E378E5989}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C91EEF13-A1AC-4A40-B695-DD4E378E5989}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C91EEF13-A1AC-4A40-B695-DD4E378E5989}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EEDE3BBE-E260-445E-8FB3-1264E0CBBE91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EEDE3BBE-E260-445E-8FB3-1264E0CBBE91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EEDE3BBE-E260-445E-8FB3-1264E0CBBE91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EEDE3BBE-E260-445E-8FB3-1264E0CBBE91}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -66,7 +66,7 @@ internal sealed class MovementController : IDisposable
|
||||
}
|
||||
}
|
||||
else if (!Destination.IsFlying && !_condition[ConditionFlag.Mounted] && navPoints.Count > 0 &&
|
||||
!_gameFunctions.HasStatusPreventingSprintOrMount())
|
||||
!_gameFunctions.HasStatusPreventingSprintOrMount() && Destination.CanSprint)
|
||||
{
|
||||
float actualDistance = 0;
|
||||
foreach (Vector3 end in navPoints)
|
||||
@ -118,10 +118,19 @@ internal sealed class MovementController : IDisposable
|
||||
{
|
||||
if (AetheryteConverter.IsLargeAetheryte((EAetheryteLocation)Destination.DataId))
|
||||
{
|
||||
/*
|
||||
if ((EAetheryteLocation) Destination.DataId is EAetheryteLocation.OldSharlayan
|
||||
or EAetheryteLocation.UltimaThuleAbodeOfTheEa)
|
||||
Stop();
|
||||
|
||||
// TODO verify the first part of this, is there any aetheryte like that?
|
||||
// TODO Unsure if this is per-aetheryte or what; because e.g. old sharlayan is at -1.53;
|
||||
// but Elpis aetherytes fail at around -0.95
|
||||
if (localPlayerPosition.Y - gameObject.Position.Y < 2.95f &&
|
||||
localPlayerPosition.Y - gameObject.Position.Y > -0.9f)
|
||||
Stop();
|
||||
*/
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -145,27 +154,27 @@ internal sealed class MovementController : IDisposable
|
||||
return pointOnFloor != null && Math.Abs(pointOnFloor.Value.Y - p.Y) > 0.5f;
|
||||
}
|
||||
|
||||
private void PrepareNavigation(EMovementType type, uint? dataId, Vector3 to, bool fly, float? stopDistance)
|
||||
private void PrepareNavigation(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance)
|
||||
{
|
||||
ResetPathfinding();
|
||||
|
||||
_gameFunctions.ExecuteCommand("/automove off");
|
||||
|
||||
Destination = new DestinationData(dataId, to, stopDistance ?? (DefaultStopDistance - 0.2f), fly);
|
||||
Destination = new DestinationData(dataId, to, stopDistance ?? (DefaultStopDistance - 0.2f), fly, sprint);
|
||||
}
|
||||
|
||||
public void NavigateTo(EMovementType type, uint? dataId, Vector3 to, bool fly, float? stopDistance = null)
|
||||
public void NavigateTo(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance = null)
|
||||
{
|
||||
PrepareNavigation(type, dataId, to, fly, stopDistance);
|
||||
PrepareNavigation(type, dataId, to, fly, sprint, stopDistance);
|
||||
_cancellationTokenSource = new();
|
||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
_pathfindTask =
|
||||
_navmeshIpc.Pathfind(_clientState.LocalPlayer!.Position, to, fly, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, float? stopDistance)
|
||||
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, bool sprint, float? stopDistance)
|
||||
{
|
||||
PrepareNavigation(type, dataId, to.Last(), fly, stopDistance);
|
||||
PrepareNavigation(type, dataId, to.Last(), fly, sprint, stopDistance);
|
||||
_navmeshIpc.MoveTo(to);
|
||||
}
|
||||
|
||||
@ -198,5 +207,5 @@ internal sealed class MovementController : IDisposable
|
||||
Stop();
|
||||
}
|
||||
|
||||
public sealed record DestinationData(uint? DataId, Vector3 Position, float StopDistance, bool IsFlying);
|
||||
public sealed record DestinationData(uint? DataId, Vector3 Position, float StopDistance, bool IsFlying, bool CanSprint);
|
||||
}
|
||||
|
@ -3,17 +3,23 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using LLib.GameUI;
|
||||
using Lumina.Excel.CustomSheets;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Model.V1;
|
||||
using Questionable.Model.V1.Converter;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
|
||||
@ -28,6 +34,7 @@ internal sealed class QuestController
|
||||
private readonly ICondition _condition;
|
||||
private readonly IChatGui _chatGui;
|
||||
private readonly IFramework _framework;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly AetheryteData _aetheryteData;
|
||||
private readonly LifestreamIpc _lifestreamIpc;
|
||||
private readonly TerritoryData _territoryData;
|
||||
@ -35,7 +42,8 @@ internal sealed class QuestController
|
||||
|
||||
public QuestController(DalamudPluginInterface pluginInterface, IDataManager dataManager, IClientState clientState,
|
||||
GameFunctions gameFunctions, MovementController movementController, IPluginLog pluginLog, ICondition condition,
|
||||
IChatGui chatGui, IFramework framework, AetheryteData aetheryteData, LifestreamIpc lifestreamIpc)
|
||||
IChatGui chatGui, IFramework framework, IGameGui gameGui, AetheryteData aetheryteData,
|
||||
LifestreamIpc lifestreamIpc)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_dataManager = dataManager;
|
||||
@ -46,6 +54,7 @@ internal sealed class QuestController
|
||||
_condition = condition;
|
||||
_chatGui = chatGui;
|
||||
_framework = framework;
|
||||
_gameGui = gameGui;
|
||||
_aetheryteData = aetheryteData;
|
||||
_lifestreamIpc = lifestreamIpc;
|
||||
_territoryData = new TerritoryData(dataManager);
|
||||
@ -318,13 +327,47 @@ internal sealed class QuestController
|
||||
}
|
||||
}
|
||||
|
||||
if (step.SkipIf.Contains(ESkipCondition.FlyingUnlocked) && _gameFunctions.IsFlyingUnlocked(step.TerritoryId))
|
||||
if (!step.SkipIf.Contains(ESkipCondition.Never))
|
||||
{
|
||||
_pluginLog.Information("Checking skip conditions");
|
||||
|
||||
if (step.SkipIf.Contains(ESkipCondition.FlyingUnlocked) &&
|
||||
_gameFunctions.IsFlyingUnlocked(step.TerritoryId))
|
||||
{
|
||||
_pluginLog.Information("Skipping step, as flying is unlocked");
|
||||
IncreaseStepCount();
|
||||
return;
|
||||
}
|
||||
|
||||
if (step is
|
||||
{
|
||||
DataId: not null,
|
||||
InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
|
||||
} &&
|
||||
_gameFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value))
|
||||
{
|
||||
_pluginLog.Information("Skipping step, as aetheryte/aethernet shard is unlocked");
|
||||
IncreaseStepCount();
|
||||
return;
|
||||
}
|
||||
|
||||
if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
|
||||
_gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
|
||||
{
|
||||
_pluginLog.Information("Skipping step, as current is unlocked");
|
||||
IncreaseStepCount();
|
||||
return;
|
||||
}
|
||||
|
||||
QuestWork? questWork = _gameFunctions.GetQuestEx(CurrentQuest.Quest.QuestId);
|
||||
if (questWork != null && step.MatchesQuestVariables(questWork.Value))
|
||||
{
|
||||
_pluginLog.Information("Skipping step, as quest variables match");
|
||||
IncreaseStepCount();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CurrentQuest.StepProgress.AethernetShortcutUsed)
|
||||
{
|
||||
if (step.AethernetShortcut != null &&
|
||||
@ -350,7 +393,7 @@ internal sealed class QuestController
|
||||
}
|
||||
else
|
||||
_movementController.NavigateTo(EMovementType.Quest, (uint)from, _aetheryteData.Locations[from],
|
||||
false,
|
||||
false, true,
|
||||
AetheryteConverter.IsLargeAetheryte(from) ? 10.9f : 6.9f);
|
||||
|
||||
return;
|
||||
@ -368,6 +411,11 @@ internal sealed class QuestController
|
||||
{
|
||||
_pluginLog.Information("We're at the jump destination, skipping movement");
|
||||
}
|
||||
else if (step.InteractionType == EInteractionType.CutsceneSelectString &&
|
||||
_condition[ConditionFlag.OccupiedInCutSceneEvent])
|
||||
{
|
||||
_pluginLog.Information("In cutscene selection, skipping movement");
|
||||
}
|
||||
else if (step.Position != null)
|
||||
{
|
||||
float distance;
|
||||
@ -413,7 +461,9 @@ internal sealed class QuestController
|
||||
if (actualDistance > distance)
|
||||
{
|
||||
_movementController.NavigateTo(EMovementType.Quest, step.DataId, step.Position.Value,
|
||||
step.Fly && _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), distance);
|
||||
fly: step.Fly == true && _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType),
|
||||
sprint: step.Sprint != false,
|
||||
stopDistance: distance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -428,7 +478,9 @@ internal sealed class QuestController
|
||||
distance /= 2;
|
||||
|
||||
_movementController.NavigateTo(EMovementType.Quest, step.DataId, [step.Position.Value],
|
||||
step.Fly && _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), distance);
|
||||
fly: step.Fly == true && _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType),
|
||||
sprint: step.Sprint != false,
|
||||
stopDistance: distance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -609,8 +661,8 @@ internal sealed class QuestController
|
||||
else
|
||||
{
|
||||
_movementController.NavigateTo(EMovementType.Quest, step.DataId,
|
||||
[step.JumpDestination.Position],
|
||||
false, step.JumpDestination.StopDistance ?? stopDistance);
|
||||
[step.JumpDestination.Position], false, false,
|
||||
step.JumpDestination.StopDistance ?? stopDistance);
|
||||
_framework.RunOnTick(() => ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2),
|
||||
TimeSpan.FromSeconds(step.JumpDestination.DelaySeconds ?? 0.5f));
|
||||
}
|
||||
@ -623,6 +675,54 @@ internal sealed class QuestController
|
||||
// Need to manually forward
|
||||
break;
|
||||
|
||||
case EInteractionType.CutsceneSelectString:
|
||||
// to do this automatically, should likely be in Addon's post setup
|
||||
if (_gameGui.TryGetAddonByName<AddonCutSceneSelectString>("CutSceneSelectString", out var addon) &&
|
||||
LAddon.IsAddonReady(&addon->AtkUnitBase))
|
||||
{
|
||||
foreach (DialogueChoice dialogueChoice in step.DialogueChoices)
|
||||
{
|
||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(dialogueChoice.ExcelSheet);
|
||||
if (excelSheet == null)
|
||||
{
|
||||
_pluginLog.Error($"Unknown excel sheet '{dialogueChoice.ExcelSheet}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
string? excelString = excelSheet
|
||||
.FirstOrDefault(x => x.Key == dialogueChoice.Answer)
|
||||
?.Value
|
||||
?.ToString();
|
||||
if (excelString == null)
|
||||
{
|
||||
_pluginLog.Error(
|
||||
$"Could not extract '{dialogueChoice.Answer}' from sheet '{dialogueChoice.ExcelSheet}'");
|
||||
return;
|
||||
}
|
||||
|
||||
_pluginLog.Verbose($"Looking for option '{excelString}'");
|
||||
for (int i = 5; i < addon->AtkUnitBase.AtkValuesCount; ++i)
|
||||
{
|
||||
var atkValue = addon->AtkUnitBase.AtkValues[i];
|
||||
if (atkValue.Type != ValueType.String)
|
||||
continue;
|
||||
|
||||
string? atkString = atkValue.ReadAtkString();
|
||||
_pluginLog.Verbose($"Option {i}: {atkString}");
|
||||
if (excelString == atkString)
|
||||
{
|
||||
_pluginLog.Information($"Selecting option {i - 5}: {atkString}");
|
||||
addon->AtkUnitBase.FireCallbackInt(i - 5);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (step.DataId != null && !_condition[ConditionFlag.OccupiedInCutSceneEvent])
|
||||
_gameFunctions.InteractWith(step.DataId.Value);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
_pluginLog.Warning($"Action '{step.InteractionType}' is not implemented");
|
||||
break;
|
||||
|
@ -20,6 +20,7 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
|
||||
{ EInteractionType.Duty, "Duty" },
|
||||
{ EInteractionType.SinglePlayerDuty, "SinglePlayerDuty" },
|
||||
{ EInteractionType.Jump, "Jump" },
|
||||
{ EInteractionType.CutsceneSelectString, "CutsceneSelectString" },
|
||||
{ EInteractionType.ShouldBeAJump, "ShouldBeAJump" },
|
||||
{ EInteractionType.Instruction, "Instruction" },
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ public sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Val
|
||||
{
|
||||
private static readonly Dictionary<ESkipCondition, string> Values = new()
|
||||
{
|
||||
{ ESkipCondition.Never, "Never" },
|
||||
{ ESkipCondition.FlyingUnlocked, "FlyingUnlocked" },
|
||||
};
|
||||
}
|
||||
|
7
Questionable/Model/V1/DialogueChoice.cs
Normal file
7
Questionable/Model/V1/DialogueChoice.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Questionable.Model.V1;
|
||||
|
||||
public sealed class DialogueChoice
|
||||
{
|
||||
public string ExcelSheet { get; set; } = null!;
|
||||
public string Answer { get; set; } = null!;
|
||||
}
|
@ -20,6 +20,7 @@ public enum EInteractionType
|
||||
Duty,
|
||||
SinglePlayerDuty,
|
||||
Jump,
|
||||
CutsceneSelectString,
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be adjusted for coords etc. in the quest data.
|
||||
|
@ -7,5 +7,6 @@ namespace Questionable.Model.V1;
|
||||
public enum ESkipCondition
|
||||
{
|
||||
None,
|
||||
Never,
|
||||
FlyingUnlocked,
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text.Json.Serialization;
|
||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
||||
using Questionable.Model.V1.Converter;
|
||||
|
||||
namespace Questionable.Model.V1;
|
||||
@ -21,7 +22,8 @@ public class QuestStep
|
||||
public bool Disabled { get; set; }
|
||||
public bool DisableNavmesh { get; set; }
|
||||
public bool? Mount { get; set; }
|
||||
public bool Fly { get; set; }
|
||||
public bool? Fly { get; set; }
|
||||
public bool? Sprint { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
|
||||
public EAetheryteLocation? AetheryteShortcut { get; set; }
|
||||
@ -42,4 +44,28 @@ public class QuestStep
|
||||
public uint? ContentFinderConditionId { get; set; }
|
||||
|
||||
public IList<ESkipCondition> SkipIf { get; set; } = new List<ESkipCondition>();
|
||||
public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();
|
||||
public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>();
|
||||
|
||||
public unsafe bool MatchesQuestVariables(QuestWork questWork)
|
||||
{
|
||||
if (CompletionQuestVariablesFlags.Count != 6)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
short? check = CompletionQuestVariablesFlags[i];
|
||||
if (check == null)
|
||||
continue;
|
||||
|
||||
byte actualValue = questWork.Variables[i];
|
||||
byte expectedValue = check > 0 ? (byte)check : (byte)0;
|
||||
byte checkByte = check > 0 ? (byte)check : (byte)-check;
|
||||
|
||||
if ((actualValue & checkByte) != expectedValue)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,9 @@
|
||||
"Z": -239.7956
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"Mount": false,
|
||||
"Sprint": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -44,6 +44,14 @@
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
64
|
||||
],
|
||||
"$": "QuestVariables after: 17 0 0 0 0 64"
|
||||
},
|
||||
{
|
||||
@ -60,6 +68,14 @@
|
||||
"KillEnemyDataIds": [
|
||||
14111
|
||||
],
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"$": "QuestVariables after killing enemy: 17 1 0 0 0 64"
|
||||
},
|
||||
{
|
||||
@ -71,6 +87,14 @@
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
],
|
||||
"$": "QuestVariables after: 33 2 0 0 0 96"
|
||||
},
|
||||
{
|
||||
@ -81,7 +105,16 @@
|
||||
"Z": 372.54907
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
128
|
||||
],
|
||||
"$": "QuestVariables if done first: 16 16 16 0 0 128"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -43,8 +43,21 @@
|
||||
"Z": 799.2217
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "WaitForManualProgress",
|
||||
"Comment": "Talk (2, 2, 1)"
|
||||
"InteractionType": "CutsceneSelectString",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A2_000_088"
|
||||
},
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A3_000_098"
|
||||
},
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A5_000_107"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -59,8 +72,21 @@
|
||||
"Z": 681.7273
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "WaitForManualProgress",
|
||||
"Comment": "Talk (2, 1, 2)"
|
||||
"InteractionType": "CutsceneSelectString",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A6_000_149"
|
||||
},
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A7_000_158"
|
||||
},
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A8_000_164"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -75,8 +101,21 @@
|
||||
"Z": 517.72327
|
||||
},
|
||||
"TerritoryId": 957,
|
||||
"InteractionType": "WaitForManualProgress",
|
||||
"Comment": "Talk (2, 2, 2)"
|
||||
"InteractionType": "CutsceneSelectString",
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A9_000_200"
|
||||
},
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A10_000_209"
|
||||
},
|
||||
{
|
||||
"ExcelSheet": "quest/043/AktKma114_04370",
|
||||
"Answer": "TEXT_AKTKMA114_04370_A11_000_218"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -77,9 +77,9 @@
|
||||
"Y": 4.357494,
|
||||
"Z": 0.7476196
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact",
|
||||
"StopDistance": 5,
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] The Rostra",
|
||||
"[Old Sharlayan] The Baldesion Annex"
|
||||
|
@ -86,7 +86,8 @@
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] Scholar's Harbor",
|
||||
"[Old Sharlayan] The Studium"
|
||||
]
|
||||
],
|
||||
"$.1": "QuestVariables if done first: 16 16 128 0 0 128"
|
||||
},
|
||||
{
|
||||
"DataId": 1039825,
|
||||
@ -97,7 +98,6 @@
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact",
|
||||
"Comment": "Unsure why this is on the same Sequence No",
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] The Studium",
|
||||
"[Old Sharlayan] The Leveilleur Estate"
|
||||
|
@ -58,7 +58,7 @@
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"$.0": "[2]",
|
||||
"$.2": "QuestVariables if done after [1]: 33 1 0 0 0 192"
|
||||
"$.1": "QuestVariables if done after [1]: 33 1 0 0 0 192"
|
||||
},
|
||||
{
|
||||
"DataId": 1040293,
|
||||
@ -68,7 +68,9 @@
|
||||
"Z": 665.5221
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"$.0": "[3]",
|
||||
"$.2": "QuestVariables if done first: 16 16 0 0 0 32"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -31,6 +31,14 @@
|
||||
"StopDistance": 8,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
64
|
||||
],
|
||||
"$.0": "[1]",
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 64"
|
||||
},
|
||||
@ -45,6 +53,14 @@
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"$.0": "[2]",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
],
|
||||
"$.1": "QuestVariables if done after [1]: 2 0 0 0 0 96"
|
||||
},
|
||||
{
|
||||
@ -67,7 +83,16 @@
|
||||
},
|
||||
"StopDistance": 8,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
128
|
||||
],
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 128"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -98,8 +123,16 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
],
|
||||
"$.0": "[1]",
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 ??"
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 32"
|
||||
},
|
||||
{
|
||||
"DataId": 2012282,
|
||||
@ -110,6 +143,14 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
64
|
||||
],
|
||||
"$.0": "[1]",
|
||||
"$.1": "QuestVariables if done after [1]: 2 0 0 0 0 ??"
|
||||
},
|
||||
@ -121,7 +162,16 @@
|
||||
"Z": -20.676025
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
128
|
||||
],
|
||||
"Comment": "TODO Verify quest variables flags"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -170,6 +220,7 @@
|
||||
"Y": 64.78333,
|
||||
"Z": -200.3357
|
||||
},
|
||||
"StopDistance": 8,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
|
@ -82,6 +82,7 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"Mount": true,
|
||||
"DisableNavmesh": true
|
||||
}
|
||||
]
|
||||
@ -114,6 +115,14 @@
|
||||
"StopDistance": 4,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
128
|
||||
],
|
||||
"$.0": "[1]",
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 128"
|
||||
},
|
||||
@ -127,6 +136,14 @@
|
||||
"StopDistance": 4,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
64
|
||||
],
|
||||
"$.0": "[2]",
|
||||
"$.1": "QuestVariables if done after [1]: 2 0 0 0 0 192"
|
||||
},
|
||||
@ -152,7 +169,15 @@
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"$.0": "[3]",
|
||||
"$.1": "QuestVariables if done after [2]: 3 0 0 0 0 224"
|
||||
"$.1": "QuestVariables if done after [2]: 3 0 0 0 0 224",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
]
|
||||
},
|
||||
{
|
||||
"DataId": 2012357,
|
||||
@ -164,8 +189,16 @@
|
||||
"StopDistance": 4,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
16
|
||||
],
|
||||
"$.0": "[4]",
|
||||
"$.1": "QuestVariables if done first: TODO"
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 16"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -101,8 +101,7 @@
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "AttuneAetherCurrent",
|
||||
"AetheryteShortcut": "Ultima Thule - Abode of the Ea",
|
||||
"AetherCurrentId": 2818390,
|
||||
"Comment": "TODO Verify"
|
||||
"AetherCurrentId": 2818390
|
||||
},
|
||||
{
|
||||
"DataId": 1039778,
|
||||
|
@ -12,7 +12,7 @@
|
||||
"Y": 269.0203,
|
||||
"Z": -633.5393
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"StopDistance": 6,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
|
@ -116,13 +116,24 @@
|
||||
"AetherCurrentId": 2818396
|
||||
},
|
||||
{
|
||||
"DataId": 2012038,
|
||||
"Position": {
|
||||
"X": 639.9123,
|
||||
"Y": 438.7276,
|
||||
"Z": 293.33954
|
||||
"X": 645.6607,
|
||||
"Y": 438.6276,
|
||||
"Z": 291.0269
|
||||
},
|
||||
"StopDistance": 1,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "WalkTo"
|
||||
"InteractionType": "Jump",
|
||||
"JumpDestination": {
|
||||
"DataId": 2012038,
|
||||
"Position": {
|
||||
"X": 637.1709,
|
||||
"Y": 439.23096,
|
||||
"Z": 289.66187
|
||||
},
|
||||
"StopDistance": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"DataId": 2012038,
|
||||
|
@ -68,6 +68,7 @@
|
||||
"Y": 417.0675,
|
||||
"Z": 414.66382
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
|
@ -29,6 +29,14 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
128
|
||||
],
|
||||
"$.0": "[1]",
|
||||
"$.1": "QuestVariables if done first: 16 0 0 16 0 128"
|
||||
},
|
||||
@ -41,6 +49,14 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
16
|
||||
],
|
||||
"$.0": "[2]",
|
||||
"$.1": "QuestVariables if done after [1]: 32 16 0 16 0 144"
|
||||
},
|
||||
@ -53,6 +69,14 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
8
|
||||
],
|
||||
"$.0": "[3]",
|
||||
"$.1": "QuestVariables if done after [1, 2]: 48 17 0 16 0 152"
|
||||
},
|
||||
@ -65,6 +89,14 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
],
|
||||
"$.0": "[4]",
|
||||
"$.1": "QuestVariables if done after [1, 2, 3]: 65 17 0 16 0 184"
|
||||
},
|
||||
@ -77,6 +109,14 @@
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
4
|
||||
],
|
||||
"$.0": "[5]",
|
||||
"$.1": "QuestVariables if done after [1, 2, 3, 4]: 81 17 16 16 0 188"
|
||||
},
|
||||
@ -88,7 +128,17 @@
|
||||
"Z": 241.9928
|
||||
},
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
64
|
||||
],
|
||||
"$.0": "[6]",
|
||||
"$.1": "QuestVariables if done first: 16 0 1 0 0 64"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -146,6 +196,7 @@
|
||||
"Y": 637.10297,
|
||||
"Z": 5.2338257
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 960,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
|
@ -69,6 +69,14 @@
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
1
|
||||
],
|
||||
"$.0": "[1]",
|
||||
"$.1": "QuestVariables if done first: 1 0 0 0 0 1"
|
||||
},
|
||||
@ -82,6 +90,14 @@
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
128
|
||||
],
|
||||
"$.0": "[2]",
|
||||
"$.1": "QuestVariables if done after [1]: 2 0 0 0 0 129"
|
||||
},
|
||||
@ -94,6 +110,14 @@
|
||||
},
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
64
|
||||
],
|
||||
"$.0": "[3]",
|
||||
"$.1": "QuestVariables if done after [1, 2]: 3 0 0 0 0 193"
|
||||
},
|
||||
@ -107,6 +131,14 @@
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
16
|
||||
],
|
||||
"$.0": "[4]",
|
||||
"$.1": "QuestVariables if done after [1, 2, 3]: 4 0 0 0 0 209"
|
||||
},
|
||||
@ -119,6 +151,14 @@
|
||||
},
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
],
|
||||
"$.0": "[5]",
|
||||
"$.1": "QuestVariables if done after [1, 2, 3, 4]: 5 0 0 0 0 241"
|
||||
},
|
||||
@ -131,6 +171,14 @@
|
||||
},
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
2
|
||||
],
|
||||
"$.0": "[6]",
|
||||
"$.1": "QuestVariables if done after [1, 2, 3, 4, 5]: 6 0 0 0 0 243"
|
||||
},
|
||||
@ -143,6 +191,14 @@
|
||||
},
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
8
|
||||
],
|
||||
"$.0": "[7]",
|
||||
"$.1": "QuestVariables if done after [1, 2, 3, 4, 5, 6]: 7 0 0 0 0 251"
|
||||
},
|
||||
@ -154,7 +210,16 @@
|
||||
"Z": 0.1373291
|
||||
},
|
||||
"TerritoryId": 351,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
4
|
||||
],
|
||||
"$.2": "QuestVariables if done first: 1 0 0 0 0 0 4"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -13,7 +13,8 @@
|
||||
"Z": -631.281
|
||||
},
|
||||
"TerritoryId": 156,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"DisableNavmesh": true
|
||||
},
|
||||
{
|
||||
"DataId": 1041232,
|
||||
|
@ -106,6 +106,7 @@
|
||||
"Duty",
|
||||
"SinglePlayerDuty",
|
||||
"Jump",
|
||||
"CutsceneSelectString",
|
||||
"ShouldBeAJump",
|
||||
"Instruction"
|
||||
]
|
||||
@ -129,6 +130,12 @@
|
||||
"type": "boolean",
|
||||
"description": "If true and flying is unlocked in a zone, will use a flight path"
|
||||
},
|
||||
"Sprint": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"AetheryteShortcut": {
|
||||
"type": "string",
|
||||
"description": "The Aetheryte to teleport to (before moving)",
|
||||
@ -419,7 +426,7 @@
|
||||
]
|
||||
},
|
||||
"ContentFinderConditionId": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"SkipIf": {
|
||||
@ -428,10 +435,60 @@
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Never",
|
||||
"FlyingUnlocked"
|
||||
]
|
||||
}
|
||||
},
|
||||
"CompletionQuestVariablesFlags": {
|
||||
"type": "array",
|
||||
"description": "Quest Variables that dictate whether or not this step is skipped: null is don't check, positive values need to be set, negative values need to be unset",
|
||||
"items": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"enum": [
|
||||
null,
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
16,
|
||||
32,
|
||||
64,
|
||||
128,
|
||||
-1,
|
||||
-2,
|
||||
-4,
|
||||
-8,
|
||||
-16,
|
||||
-32,
|
||||
-64,
|
||||
-128
|
||||
]
|
||||
},
|
||||
"minItems": 6,
|
||||
"maxItems": 6
|
||||
},
|
||||
"DialogueChoices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ExcelSheet": {
|
||||
"type": "string"
|
||||
},
|
||||
"Answer": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ExcelSheet",
|
||||
"Answer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Comment": {
|
||||
"type": "string"
|
||||
}
|
||||
|
@ -54,6 +54,10 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LLib\LLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="QuestPaths/**/*.json"/>
|
||||
|
@ -53,7 +53,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
_movementController =
|
||||
new MovementController(navmeshIpc, clientState, _gameFunctions, condition, pluginLog);
|
||||
_questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions,
|
||||
_movementController, pluginLog, condition, chatGui, framework, aetheryteData, lifestreamIpc);
|
||||
_movementController, pluginLog, condition, chatGui, framework, gameGui, aetheryteData, lifestreamIpc);
|
||||
_windowSystem.AddWindow(new DebugWindow(_movementController, _questController, _gameFunctions, clientState,
|
||||
targetManager));
|
||||
|
||||
@ -88,7 +88,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
out Vector3 worldPos))
|
||||
{
|
||||
_movementController.NavigateTo(EMovementType.Shortcut, null, worldPos,
|
||||
_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType));
|
||||
_gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,8 @@ internal sealed class DebugWindow : Window
|
||||
if (ImGui.Button("Move to Target"))
|
||||
{
|
||||
_movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId,
|
||||
_targetManager.Target.Position, _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType));
|
||||
_targetManager.Target.Position, _gameFunctions.IsFlyingUnlocked(_clientState.TerritoryType),
|
||||
true);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -21,6 +21,9 @@
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
|
||||
},
|
||||
"llib": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user