Support non-english clients
This commit is contained in:
parent
3e68dd6e79
commit
500e25b508
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<Version>2.3</Version>
|
||||
<Version>2.4</Version>
|
||||
<LangVersion>11.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -165,26 +165,6 @@ partial class DeliverooPlugin
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ConfirmReward()
|
||||
{
|
||||
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||
if (item == null)
|
||||
{
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectSelectYesno(0, s => s.StartsWith("Exchange ")))
|
||||
{
|
||||
var nextItem = GetNextItemToPurchase(item);
|
||||
if (nextItem != null && GetCurrentSealCount() >= _configuration.ReservedSealCount + nextItem.SealCost)
|
||||
CurrentStage = Stage.SelectRewardTier;
|
||||
else
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void CloseGcExchange()
|
||||
{
|
||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
||||
|
@ -9,7 +9,6 @@ using Deliveroo.GameData;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
@ -52,17 +51,25 @@ partial class DeliverooPlugin
|
||||
|
||||
private float GetDistanceToNpc(int npcId, out GameObject? o)
|
||||
{
|
||||
foreach (var obj in _objectTable)
|
||||
try
|
||||
{
|
||||
if (obj.ObjectKind == ObjectKind.EventNpc && obj is Character c)
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (GetNpcId(obj) == npcId)
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (obj != null && obj.ObjectKind == ObjectKind.EventNpc && obj is Character c)
|
||||
{
|
||||
o = obj;
|
||||
return Vector3.Distance(_clientState.LocalPlayer!.Position, c.Position);
|
||||
if (GetNpcId(obj) == npcId)
|
||||
{
|
||||
o = obj;
|
||||
return Vector3.Distance(_clientState.LocalPlayer!.Position, c.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
o = null;
|
||||
return float.MaxValue;
|
||||
@ -196,32 +203,4 @@ partial class DeliverooPlugin
|
||||
{
|
||||
return addon->IsVisible && addon->UldManager.LoadedState == AtkLoadState.Loaded;
|
||||
}
|
||||
|
||||
private unsafe bool SelectSelectString(int choice)
|
||||
{
|
||||
if (TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
|
||||
IsAddonReady(&addonSelectString->AtkUnitBase))
|
||||
{
|
||||
addonSelectString->AtkUnitBase.FireCallbackInt(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool SelectSelectYesno(int choice, Predicate<string> predicate)
|
||||
{
|
||||
if (TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
|
||||
IsAddonReady(&addonSelectYesno->AtkUnitBase) &&
|
||||
predicate(MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString()))
|
||||
{
|
||||
_pluginLog.Information(
|
||||
$"Selecting choice={choice} for '{MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText)}'");
|
||||
|
||||
addonSelectYesno->AtkUnitBase.FireCallbackInt(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
100
Deliveroo/DeliverooPlugin.SelectString.cs
Normal file
100
Deliveroo/DeliverooPlugin.SelectString.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace Deliveroo;
|
||||
|
||||
partial class DeliverooPlugin
|
||||
{
|
||||
private unsafe void SelectStringPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
_pluginLog.Verbose("SelectString post-setup");
|
||||
|
||||
string desiredText;
|
||||
Action followUp;
|
||||
if (CurrentStage == Stage.OpenGcSupply)
|
||||
{
|
||||
desiredText = _gameStrings.UndertakeSupplyAndProvisioningMission;
|
||||
followUp = OpenGcSupplyFollowUp;
|
||||
}
|
||||
else if (CurrentStage == Stage.CloseGcSupply)
|
||||
{
|
||||
desiredText = _gameStrings.ClosePersonnelOfficerTalk;
|
||||
followUp = CloseGcSupplyFollowUp;
|
||||
}
|
||||
else if (CurrentStage == Stage.CloseGcSupplyThenStop)
|
||||
{
|
||||
desiredText = _gameStrings.ClosePersonnelOfficerTalk;
|
||||
followUp = CloseGcSupplyThenCloseFollowUp;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
_pluginLog.Verbose($"Looking for '{desiredText}' in prompt");
|
||||
AddonSelectString* addonSelectString = (AddonSelectString*)args.Addon;
|
||||
int entries = addonSelectString->PopupMenu.PopupMenu.EntryCount;
|
||||
|
||||
for (int i = 0; i < entries; ++i)
|
||||
{
|
||||
var textPointer = addonSelectString->PopupMenu.PopupMenu.EntryNames[i];
|
||||
if (textPointer == null)
|
||||
continue;
|
||||
|
||||
var text = MemoryHelper.ReadSeStringNullTerminated((nint)textPointer).ToString();
|
||||
_pluginLog.Verbose($" Choice {i} → {text}");
|
||||
if (text == desiredText)
|
||||
{
|
||||
|
||||
_pluginLog.Information($"Selecting choice {i} ({text})");
|
||||
addonSelectString->AtkUnitBase.FireCallbackInt(i);
|
||||
|
||||
followUp();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_pluginLog.Verbose($"Text '{desiredText}' was not found in prompt.");
|
||||
}
|
||||
|
||||
private void OpenGcSupplyFollowUp()
|
||||
{
|
||||
CurrentStage = Stage.SelectExpertDeliveryTab;
|
||||
}
|
||||
|
||||
private void CloseGcSupplyFollowUp()
|
||||
{
|
||||
if (GetNextItemToPurchase() == null)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else
|
||||
{
|
||||
// you can occasionally get a 'not enough seals' warning lol
|
||||
_continueAt = DateTime.Now.AddSeconds(1);
|
||||
CurrentStage = Stage.TargetQuartermaster;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseGcSupplyThenCloseFollowUp()
|
||||
{
|
||||
if (GetNextItemToPurchase() == null)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else if (GetCurrentSealCount() <=
|
||||
_configuration.ReservedSealCount + GetNextItemToPurchase()!.SealCost)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else
|
||||
{
|
||||
_continueAt = DateTime.Now.AddSeconds(1);
|
||||
CurrentStage = Stage.TargetQuartermaster;
|
||||
}
|
||||
}
|
||||
}
|
47
Deliveroo/DeliverooPlugin.SelectYesNo.cs
Normal file
47
Deliveroo/DeliverooPlugin.SelectYesNo.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace Deliveroo;
|
||||
|
||||
partial class DeliverooPlugin
|
||||
{
|
||||
private unsafe void SelectYesNoPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
_pluginLog.Verbose("SelectYesNo post-setup");
|
||||
|
||||
AddonSelectYesno* addonSelectYesNo = (AddonSelectYesno*)args.Addon;
|
||||
string text = MemoryHelper.ReadSeString(&addonSelectYesNo->PromptText->NodeText).ToString().Replace("\n", "").Replace("\r", "");
|
||||
_pluginLog.Verbose($"YesNo prompt: '{text}'");
|
||||
|
||||
if (CurrentStage == Stage.ConfirmReward &&
|
||||
_gameStrings.ExchangeItems.IsMatch(text))
|
||||
{
|
||||
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||
if (item == null)
|
||||
{
|
||||
addonSelectYesNo->AtkUnitBase.FireCallbackInt(1);
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
return;
|
||||
}
|
||||
|
||||
_pluginLog.Information($"Selecting 'yes' ({text})");
|
||||
addonSelectYesNo->AtkUnitBase.FireCallbackInt(0);
|
||||
|
||||
var nextItem = GetNextItemToPurchase(item);
|
||||
if (nextItem != null && GetCurrentSealCount() >= _configuration.ReservedSealCount + nextItem.SealCost)
|
||||
CurrentStage = Stage.SelectRewardTier;
|
||||
else
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
}
|
||||
else if (CurrentStage == Stage.TurnInSelected &&
|
||||
_gameStrings.TradeHighQualityItem == text)
|
||||
{
|
||||
_pluginLog.Information($"Selecting 'yes' ({text})");
|
||||
addonSelectYesNo->AtkUnitBase.FireCallbackInt(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,12 +20,6 @@ partial class DeliverooPlugin
|
||||
CurrentStage = Stage.OpenGcSupply;
|
||||
}
|
||||
|
||||
private void OpenGcSupply()
|
||||
{
|
||||
if (SelectSelectString(0))
|
||||
CurrentStage = Stage.SelectExpertDeliveryTab;
|
||||
}
|
||||
|
||||
private unsafe void SelectExpertDeliveryTab()
|
||||
{
|
||||
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
|
||||
@ -147,9 +141,6 @@ partial class DeliverooPlugin
|
||||
|
||||
private unsafe void TurnInSelectedItem()
|
||||
{
|
||||
if (SelectSelectYesno(0, s => s == "Do you really want to trade a high-quality item?"))
|
||||
return;
|
||||
|
||||
if (TryGetAddonByName<AddonGrandCompanySupplyReward>("GrandCompanySupplyReward",
|
||||
out var addonSupplyReward) && IsAddonReady(&addonSupplyReward->AtkUnitBase))
|
||||
{
|
||||
@ -201,47 +192,6 @@ partial class DeliverooPlugin
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseGcSupply()
|
||||
{
|
||||
if (SelectSelectString(3))
|
||||
{
|
||||
if (GetNextItemToPurchase() == null)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else
|
||||
{
|
||||
// you can occasionally get a 'not enough seals' warning lol
|
||||
_continueAt = DateTime.Now.AddSeconds(1);
|
||||
CurrentStage = Stage.TargetQuartermaster;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseGcSupplyThenStop()
|
||||
{
|
||||
if (SelectSelectString(3))
|
||||
{
|
||||
if (GetNextItemToPurchase() == null)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else if (GetCurrentSealCount() <=
|
||||
_configuration.ReservedSealCount + GetNextItemToPurchase()!.SealCost)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else
|
||||
{
|
||||
_continueAt = DateTime.Now.AddSeconds(1);
|
||||
CurrentStage = Stage.TargetQuartermaster;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ItemFilterType ResolveSelectedSupplyFilter()
|
||||
{
|
||||
if (CharacterConfiguration is { UseHideArmouryChestItemsFilter: true })
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
@ -31,10 +32,12 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
private readonly ICondition _condition;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IPluginLog _pluginLog;
|
||||
private readonly IAddonLifecycle _addonLifecycle;
|
||||
|
||||
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
|
||||
private readonly Configuration _configuration;
|
||||
|
||||
private readonly GameStrings _gameStrings;
|
||||
private readonly ExternalPluginHandler _externalPluginHandler;
|
||||
|
||||
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
|
||||
@ -50,7 +53,8 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
|
||||
public DeliverooPlugin(DalamudPluginInterface pluginInterface, IChatGui chatGui, IGameGui gameGui,
|
||||
IFramework framework, IClientState clientState, IObjectTable objectTable, ITargetManager targetManager,
|
||||
IDataManager dataManager, ICondition condition, ICommandManager commandManager, IPluginLog pluginLog)
|
||||
IDataManager dataManager, ICondition condition, ICommandManager commandManager, IPluginLog pluginLog,
|
||||
IAddonLifecycle addonLifecycle)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_chatGui = chatGui;
|
||||
@ -62,7 +66,9 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
_condition = condition;
|
||||
_commandManager = commandManager;
|
||||
_pluginLog = pluginLog;
|
||||
_addonLifecycle = addonLifecycle;
|
||||
|
||||
_gameStrings = new GameStrings(dataManager, _pluginLog);
|
||||
_externalPluginHandler = new ExternalPluginHandler(_pluginInterface, _framework, _pluginLog);
|
||||
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
|
||||
_gcRewardsCache = new GcRewardsCache(dataManager);
|
||||
@ -88,6 +94,9 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
|
||||
if (_configuration.AddVentureIfNoItemToPurchaseSelected())
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup);
|
||||
}
|
||||
|
||||
internal CharacterConfiguration? CharacterConfiguration { get; set; }
|
||||
@ -215,7 +224,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
break;
|
||||
|
||||
case Stage.OpenGcSupply:
|
||||
OpenGcSupply();
|
||||
// see SelectStringPostSetup
|
||||
break;
|
||||
|
||||
case Stage.SelectExpertDeliveryTab:
|
||||
@ -243,11 +252,11 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
break;
|
||||
|
||||
case Stage.CloseGcSupply:
|
||||
CloseGcSupply();
|
||||
// see SelectStringPostSetup
|
||||
break;
|
||||
|
||||
case Stage.CloseGcSupplyThenStop:
|
||||
CloseGcSupplyThenStop();
|
||||
// see SelectStringPostSetup
|
||||
break;
|
||||
|
||||
case Stage.TargetQuartermaster:
|
||||
@ -267,7 +276,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
break;
|
||||
|
||||
case Stage.ConfirmReward:
|
||||
ConfirmReward();
|
||||
// see SelectYesNoPostSetup
|
||||
break;
|
||||
|
||||
case Stage.CloseGcExchange:
|
||||
@ -292,6 +301,9 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup);
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
|
||||
|
||||
_commandManager.RemoveHandler("/deliveroo");
|
||||
_clientState.Logout -= Logout;
|
||||
_clientState.Login -= Login;
|
||||
|
88
Deliveroo/GameData/GameStrings.cs
Normal file
88
Deliveroo/GameData/GameStrings.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.CustomSheets;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.Payloads;
|
||||
|
||||
namespace Deliveroo.GameData;
|
||||
|
||||
internal sealed class GameStrings
|
||||
{
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly IPluginLog _pluginLog;
|
||||
|
||||
public GameStrings(IDataManager dataManager, IPluginLog pluginLog)
|
||||
{
|
||||
_dataManager = dataManager;
|
||||
_pluginLog = pluginLog;
|
||||
|
||||
UndertakeSupplyAndProvisioningMission =
|
||||
GetDialogue<ComDefGrandCompanyOfficer>("TEXT_COMDEFGRANDCOMPANYOFFICER_00073_A4_002");
|
||||
ClosePersonnelOfficerTalk =
|
||||
GetDialogue<ComDefGrandCompanyOfficer>("TEXT_COMDEFGRANDCOMPANYOFFICER_00073_A4_004");
|
||||
ExchangeItems = GetRegex<Addon>(4928, addon => addon.Text)
|
||||
?? throw new Exception($"Unable to resolve {nameof(ExchangeItems)}");
|
||||
TradeHighQualityItem = GetString<Addon>(102434, addon => addon.Text)
|
||||
?? throw new Exception($"Unable to resolve {nameof(TradeHighQualityItem)}");
|
||||
}
|
||||
|
||||
|
||||
public string UndertakeSupplyAndProvisioningMission { get; }
|
||||
public string ClosePersonnelOfficerTalk { get; }
|
||||
public Regex ExchangeItems { get; }
|
||||
public string TradeHighQualityItem { get; }
|
||||
|
||||
private string GetDialogue<T>(string key)
|
||||
where T : QuestDialogueText
|
||||
{
|
||||
string result = _dataManager.GetExcelSheet<T>()!
|
||||
.Single(x => x.Key == key)
|
||||
.Value
|
||||
.ToString();
|
||||
_pluginLog.Verbose($"{typeof(T).Name}.{key} => {result}");
|
||||
return result;
|
||||
}
|
||||
|
||||
private SeString? GetSeString<T>(uint rowId, Func<T, SeString?> mapper)
|
||||
where T : ExcelRow
|
||||
{
|
||||
var row = _dataManager.GetExcelSheet<T>()?.GetRow(rowId);
|
||||
if (row == null)
|
||||
return null;
|
||||
|
||||
return mapper(row);
|
||||
}
|
||||
|
||||
private string? GetString<T>(uint rowId, Func<T, SeString?> mapper)
|
||||
where T : ExcelRow
|
||||
{
|
||||
string? text = GetSeString(rowId, mapper)?.ToString();
|
||||
|
||||
_pluginLog.Verbose($"{typeof(T).Name}.{rowId} => {text}");
|
||||
return text;
|
||||
}
|
||||
|
||||
private Regex? GetRegex<T>(uint rowId, Func<T, SeString?> mapper)
|
||||
where T : ExcelRow
|
||||
{
|
||||
SeString? text = GetSeString(rowId, mapper);
|
||||
string regex = string.Join("", text.Payloads.Select(payload =>
|
||||
{
|
||||
if (payload is TextPayload)
|
||||
return Regex.Escape(payload.RawString);
|
||||
else
|
||||
return ".*";
|
||||
}));
|
||||
_pluginLog.Verbose($"{typeof(T).Name}.{rowId} => /{regex}/");
|
||||
return new Regex(regex);
|
||||
}
|
||||
|
||||
[Sheet("custom/000/ComDefGrandCompanyOfficer_00073")]
|
||||
private class ComDefGrandCompanyOfficer : QuestDialogueText
|
||||
{
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user