Deliveroo/Deliveroo/DeliverooPlugin.cs
2024-11-16 14:37:45 +01:00

481 lines
18 KiB
C#

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;
using Dalamud.Game.Command;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Deliveroo.External;
using Deliveroo.GameData;
using Deliveroo.Handlers;
using Deliveroo.Windows;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib;
using LLib.GameUI;
namespace Deliveroo;
public sealed partial class DeliverooPlugin : IDalamudPlugin
{
private readonly WindowSystem _windowSystem = new(typeof(DeliverooPlugin).AssemblyQualifiedName);
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IChatGui _chatGui;
private readonly IGameGui _gameGui;
private readonly IFramework _framework;
private readonly IClientState _clientState;
private readonly ICondition _condition;
private readonly ICommandManager _commandManager;
private readonly IPluginLog _pluginLog;
private readonly IAddonLifecycle _addonLifecycle;
private readonly IKeyState _keyState;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly Configuration _configuration;
private readonly GameStrings _gameStrings;
private readonly GameFunctions _gameFunctions;
private readonly ExternalPluginHandler _externalPluginHandler;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly GcRewardsCache _gcRewardsCache;
private readonly IconCache _iconCache;
private readonly ItemCache _itemCache;
private readonly ExchangeHandler _exchangeHandler;
private readonly SupplyHandler _supplyHandler;
private readonly ConfigWindow _configWindow;
private readonly TurnInWindow _turnInWindow;
private Stage _currentStageInternal = Stage.Stopped;
public DeliverooPlugin(IDalamudPluginInterface pluginInterface, IChatGui chatGui, IGameGui gameGui,
IFramework framework, IClientState clientState, IObjectTable objectTable, ITargetManager targetManager,
IDataManager dataManager, ICondition condition, ICommandManager commandManager, IPluginLog pluginLog,
IAddonLifecycle addonLifecycle, ITextureProvider textureProvider, IGameConfig gameConfig, IKeyState keyState)
{
ArgumentNullException.ThrowIfNull(dataManager);
_pluginInterface = pluginInterface;
_chatGui = chatGui;
_gameGui = gameGui;
_framework = framework;
_clientState = clientState;
_condition = condition;
_commandManager = commandManager;
_pluginLog = pluginLog;
_addonLifecycle = addonLifecycle;
_keyState = keyState;
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
MigrateConfiguration();
_gameStrings = new GameStrings(dataManager, _pluginLog);
_externalPluginHandler = new ExternalPluginHandler(_pluginInterface, gameConfig, _configuration, _pluginLog);
_gameFunctions = new GameFunctions(objectTable, _clientState, targetManager, dataManager,
_externalPluginHandler, _pluginLog);
_gcRewardsCache = new GcRewardsCache(dataManager);
_iconCache = new IconCache(textureProvider);
_itemCache = new ItemCache(dataManager);
_exchangeHandler = new ExchangeHandler(this, _gameFunctions, targetManager, _gameGui, _chatGui, _pluginLog);
_supplyHandler = new SupplyHandler(this, _gameFunctions, targetManager, _gameGui, _pluginLog);
_configWindow = new ConfigWindow(_pluginInterface, this, _configuration, _gcRewardsCache, _clientState,
_pluginLog, _iconCache, _gameFunctions);
_windowSystem.AddWindow(_configWindow);
_turnInWindow = new TurnInWindow(this, _pluginInterface, _configuration, _condition, _clientState,
_gcRewardsCache, _configWindow, _iconCache, _keyState, _gameFunctions);
_windowSystem.AddWindow(_turnInWindow);
_framework.Update += FrameworkUpdate;
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;
_clientState.Login += Login;
_clientState.Logout += Logout;
_chatGui.ChatMessage += ChatMessage;
_commandManager.AddHandler("/deliveroo", new CommandInfo(ProcessCommand)
{
HelpMessage = "Open the configuration"
});
if (_clientState.IsLoggedIn)
Login();
if (_configuration.AddVentureIfNoItemToPurchaseSelected())
_pluginInterface.SavePluginConfig(_configuration);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GrandCompanySupplyReward", GrandCompanySupplyRewardPostSetup);
}
private void MigrateConfiguration()
{
#pragma warning disable CS0612 // Type or member is obsolete
if (_configuration.Version == 1)
{
_configuration.ItemsAvailableToPurchase = _configuration.ItemsAvailableForPurchase.Select(x =>
new Configuration.PurchaseOption
{
ItemId = x,
SameQuantityForAllLists = false,
}).ToList();
_configuration.Version = 2;
_pluginInterface.SavePluginConfig(_configuration);
}
#pragma warning restore CS0612 // Type or member is obsolete
}
private void ChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message,
ref bool isHandled)
{
if (_configuration.PauseAtRank <= 0)
return;
if (type != _gameStrings.RankUpFcType)
return;
var match = _gameStrings.RankUpFc.Match(message.ToString());
if (!match.Success)
return;
foreach (var group in match.Groups.Values)
{
if (int.TryParse(group.Value, out int rank) && rank == _configuration.PauseAtRank)
{
_turnInWindow.State = false;
_pluginLog.Information($"Pausing GC delivery, FC reached rank {rank}");
DeliveryResult = new MessageDeliveryResult
{
Message = new SeStringBuilder()
.Append($"Pausing Deliveroo, your FC reached rank {rank}.")
.Build(),
};
return;
}
}
}
internal CharacterConfiguration? CharacterConfiguration { get; set; }
internal Stage CurrentStage
{
get => _currentStageInternal;
set
{
if (_currentStageInternal != value)
{
_pluginLog.Verbose($"Changing stage from {_currentStageInternal} to {value}");
_currentStageInternal = value;
}
}
}
internal DateTime ContinueAt { private get; set; } = DateTime.MinValue;
internal List<PurchaseItemRequest> ItemsToPurchaseNow { get; private set; } = new();
internal int LastTurnInListSize { get; set; } = int.MaxValue;
internal IDeliveryResult? DeliveryResult { get; set; }
internal bool TurnInState
{
set => _turnInWindow.State = value;
}
internal string TurnInError
{
set => _turnInWindow.Error = value;
}
public int EffectiveReservedSealCount
{
get
{
if (CharacterConfiguration is { IgnoreMinimumSealsToKeep: true })
return 0;
return _configuration.ReserveDifferentSealCountAtMaxRank &&
_gameFunctions.GetSealCap() == _gameFunctions.MaxSealCap
? _configuration.ReservedSealCountAtMaxRank
: _configuration.ReservedSealCount;
}
}
private void Login()
{
try
{
CharacterConfiguration = CharacterConfiguration.Load(_pluginInterface, _clientState.LocalContentId);
if (CharacterConfiguration != null)
{
if (CharacterConfiguration.CachedPlayerName != _clientState.LocalPlayer!.Name.ToString() ||
CharacterConfiguration.CachedWorldName !=
_clientState.LocalPlayer.HomeWorld.Value.Name.ToString())
{
CharacterConfiguration.CachedPlayerName = _clientState.LocalPlayer!.Name.ToString();
CharacterConfiguration.CachedWorldName =
_clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
CharacterConfiguration.Save(_pluginInterface);
}
_pluginLog.Information($"Loaded character-specific information for {_clientState.LocalContentId}");
}
else
{
_pluginLog.Verbose(
$"No character-specific information for {_clientState.LocalContentId}");
}
}
catch (Exception ex)
{
_pluginLog.Error(ex, "Unable to load character configuration");
CharacterConfiguration = null;
}
}
private void Logout(int type, int code)
{
CharacterConfiguration = null;
}
private unsafe void FrameworkUpdate(IFramework f)
{
_turnInWindow.Error = string.Empty;
if (!_clientState.IsLoggedIn ||
_clientState.TerritoryType is not 128 and not 130 and not 132 ||
_condition[ConditionFlag.OccupiedInCutSceneEvent] ||
_gameFunctions.GetDistanceToNpc(_gameFunctions.GetQuartermasterId(), out IGameObject? quartermaster) >= 7f ||
_gameFunctions.GetDistanceToNpc(_gameFunctions.GetPersonnelOfficerId(), out IGameObject? personnelOfficer) >=
7f ||
CharacterConfiguration is { DisableForCharacter: true } ||
_configWindow.IsOpen)
{
_turnInWindow.IsOpen = false;
_turnInWindow.State = false;
StopTurnIn();
}
else if (DateTime.Now > ContinueAt)
{
_turnInWindow.IsOpen = true;
_turnInWindow.Multiplier = _gameFunctions.GetSealMultiplier();
if (!_turnInWindow.State)
{
StopTurnIn();
return;
}
else if (_turnInWindow.State && CurrentStage == Stage.Stopped)
{
CurrentStage = Stage.TargetPersonnelOfficer;
ItemsToPurchaseNow = _turnInWindow.SelectedItems;
DeliveryResult = new MessageDeliveryResult();
_supplyHandler.ResetTurnInErrorHandling();
if (ItemsToPurchaseNow.Count > 0)
{
_pluginLog.Information("Items to purchase:");
foreach (var item in ItemsToPurchaseNow)
_pluginLog.Information($" {item.Name} (limit = {item.EffectiveLimit})");
}
else
_pluginLog.Information("No items to purchase configured or available");
var nextItem = _exchangeHandler.GetNextItemToPurchase();
if (nextItem != null && _gameFunctions.GetCurrentSealCount() >=
EffectiveReservedSealCount + nextItem.SealCost)
CurrentStage = Stage.TargetQuartermaster;
if (_gameGui.TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList",
out var gcSupplyList) &&
LAddon.IsAddonReady(&gcSupplyList->AtkUnitBase))
CurrentStage = Stage.SelectExpertDeliveryTab;
if (_gameGui.TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var gcExchange) &&
LAddon.IsAddonReady(gcExchange))
CurrentStage = Stage.SelectRewardTier;
if (_gameGui.TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
LAddon.IsAddonReady(&addonSelectString->AtkUnitBase))
{
if (SelectStringPostSetup(addonSelectString, Stage.OpenGcSupply))
return;
}
}
if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_externalPluginHandler.Saved)
_externalPluginHandler.Save();
switch (CurrentStage)
{
case Stage.TargetPersonnelOfficer:
_supplyHandler.InteractWithPersonnelOfficer(personnelOfficer!, quartermaster!);
break;
case Stage.OpenGcSupply:
// see SelectStringPostSetup
break;
case Stage.SelectExpertDeliveryTab:
_supplyHandler.SelectExpertDeliveryTab();
break;
case Stage.SelectItemToTurnIn:
_supplyHandler.SelectItemToTurnIn();
break;
case Stage.TurnInSelected:
// see GrandCompanySupplyReward
break;
case Stage.FinalizeTurnIn:
case Stage.SingleFinalizeTurnIn:
_supplyHandler.FinalizeTurnInItem();
break;
case Stage.CloseGcSupplySelectString:
// see SelectStringPostSetup
break;
case Stage.CloseGcSupplySelectStringThenStop:
// see SelectStringPostSetup
break;
case Stage.CloseGcSupplyWindowThenStop:
_supplyHandler.CloseGcSupplyWindow();
break;
case Stage.TargetQuartermaster:
_exchangeHandler.InteractWithQuartermaster(personnelOfficer!, quartermaster!);
break;
case Stage.SelectRewardTier:
_exchangeHandler.SelectRewardTier();
break;
case Stage.SelectRewardSubCategory:
_exchangeHandler.SelectRewardSubCategory();
break;
case Stage.SelectReward:
_exchangeHandler.SelectReward();
break;
case Stage.ConfirmReward:
// see SelectYesNoPostSetup
break;
case Stage.CloseGcExchange:
_exchangeHandler.CloseGcExchange();
break;
case Stage.RequestStop:
StopTurnIn();
break;
case Stage.Stopped:
break;
default:
_pluginLog.Warning($"Unknown stage {CurrentStage}");
break;
}
}
}
private void StopTurnIn()
{
if (CurrentStage != Stage.Stopped)
{
_externalPluginHandler.Restore();
CurrentStage = Stage.Stopped;
if (DeliveryResult is null or MessageDeliveryResult)
{
var text = (DeliveryResult as MessageDeliveryResult)?.Message ?? "Delivery completed.";
var message = _configuration.ChatType switch
{
XivChatType.Say
or XivChatType.Shout
or XivChatType.TellOutgoing
or XivChatType.TellIncoming
or XivChatType.Party
or XivChatType.Alliance
or (>= XivChatType.Ls1 and <= XivChatType.Ls8)
or XivChatType.FreeCompany
or XivChatType.NoviceNetwork
or XivChatType.Yell
or XivChatType.CrossParty
or XivChatType.PvPTeam
or XivChatType.CrossLinkShell1
or XivChatType.NPCDialogue
or XivChatType.NPCDialogueAnnouncements
or (>= XivChatType.CrossLinkShell2 and <= XivChatType.CrossLinkShell8)
=> new XivChatEntry
{
Message = text,
Type = _configuration.ChatType,
Name = new SeStringBuilder().AddUiForeground("Deliveroo", 52).Build(),
},
_ => new XivChatEntry
{
Message = new SeStringBuilder().AddUiForeground("[Deliveroo] ", 52)
.Append(text)
.Build(),
Type = _configuration.ChatType,
}
};
_chatGui.Print(message);
}
DeliveryResult = null;
}
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GrandCompanySupplyReward", GrandCompanySupplyRewardPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_commandManager.RemoveHandler("/deliveroo");
_chatGui.ChatMessage -= ChatMessage;
_clientState.Logout -= Logout;
_clientState.Login -= Login;
_pluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_framework.Update -= FrameworkUpdate;
_gameFunctions.Dispose();
_externalPluginHandler.Dispose();
_iconCache.Dispose();
}
private void ProcessCommand(string command, string arguments)
{
switch (arguments)
{
case "e" or "enable":
if (_turnInWindow.IsOpen)
_turnInWindow.State = true;
break;
case "d" or "disable":
_turnInWindow.State = false;
break;
default:
_configWindow.Toggle();
break;
}
}
}