This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
@@ -0,0 +1,129 @@
using GeometryTD.Definition;
using GeometryTD.CustomEvent;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class EventFormController : UIFormControllerCommonBase<EventFormContext, EventForm>
{
private EventFormUseCase m_UseCase;
protected override UIFormType UIFormTypeId => UIFormType.EventForm;
protected override void RefreshUI(EventForm form, EventFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(EventOptionItemSelectedEventArgs.EventId, OnEventOptionItemSelected);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(EventOptionItemSelectedEventArgs.EventId, OnEventOptionItemSelected);
}
public override int? OpenUI(object userData = null)
{
if (userData is EventFormContext eventFormContext)
{
return OpenUIInternal(eventFormContext);
}
if (userData is EventFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("EventFormController.OpenUI() userData type is invalid.");
return null;
}
if (m_UseCase == null)
{
Log.Error("EventFormController.OpenUI() useCase is null.");
return null;
}
EventFormRawData rawData = m_UseCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(EventFormRawData rawData)
{
EventFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is EventFormUseCase eventFormUseCase))
{
Log.Error("EventFormController.BindUseCase() useCase is invalid.");
return;
}
m_UseCase = eventFormUseCase;
}
private static EventFormContext BuildContext(EventFormRawData rawData)
{
if (rawData == null)
{
return null;
}
EventOptionItemContext[] options = new EventOptionItemContext[4];
for (int i = 0; i < options.Length; i++)
{
string optionText = string.Empty;
bool isSelectable = false;
string blockedReason = string.Empty;
if (rawData.OptionItems != null && i < rawData.OptionItems.Length &&
rawData.OptionItems[i] != null)
{
optionText = rawData.OptionItems[i].OptionText;
isSelectable = rawData.OptionItems[i].IsSelectable;
blockedReason = rawData.OptionItems[i].BlockedReason;
}
options[i] = new EventOptionItemContext
{
OptionIndex = i,
OptionText = optionText,
IsSelectable = isSelectable,
BlockedReason = blockedReason
};
}
return new EventFormContext
{
EventId = rawData.EventId,
Title = rawData.Title,
Description = rawData.Description,
OptionItems = options
};
}
private void OnEventOptionItemSelected(object sender, GameEventArgs e)
{
if (!(e is EventOptionItemSelectedEventArgs args))
{
return;
}
if (m_UseCase == null)
{
Log.Warning("EventFormController.OnOptionSelected() useCase is null.");
return;
}
m_UseCase.TrySelectOption(args.SelectedItemId);
}
}
}
@@ -0,0 +1,79 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class MainFormController : UIFormControllerCommonBase<MainFormContext, MainForm>
{
protected override UIFormType UIFormTypeId => UIFormType.MainForm;
protected override void RefreshUI(MainForm form, MainFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(ReturnButtonClickedEventArgs.EventId, OnReturnButtonClicked);
GameEntry.Event.Subscribe(RepoButtonClickedEventArgs.EventId, OnRepoButtonClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(ReturnButtonClickedEventArgs.EventId, OnReturnButtonClicked);
GameEntry.Event.Unsubscribe(RepoButtonClickedEventArgs.EventId, OnRepoButtonClicked);
}
public override int? OpenUI(object userData = null)
{
if (userData is MainFormContext context)
{
return OpenUIInternal(context);
}
if (userData != null)
{
Log.Warning("MainFormController.OpenUI() userData type is invalid.");
return null;
}
return OpenUIInternal(BuildDefaultContext());
}
public override void BindUseCase(IUIUseCase useCase)
{
if (useCase != null)
{
Log.Warning("MainFormController does not use a use case.");
}
}
private static MainFormContext BuildDefaultContext()
{
return new MainFormContext();
}
private void OnReturnButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is MainForm) || !(e is ReturnButtonClickedEventArgs))
{
return;
}
//TODO:游戏 UI 返回菜单 UI
//GameEntry.UIRouter.CloseUI(UIFormType.MainForm);
}
private void OnRepoButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is MainForm) || !(e is RepoButtonClickedEventArgs))
{
return;
}
GameEntry.UIRouter.OpenUI(UIFormType.RepoForm);
}
}
}
@@ -0,0 +1,130 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GeometryTD.Procedure;
using GameFramework.Event;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public sealed partial class NodeMapFormController : UIFormControllerCommonBase<NodeMapFormContext, NodeMapForm>
{
private NodeMapFormUseCase _useCase;
protected override UIFormType UIFormTypeId => UIFormType.NodeMapForm;
protected override void RefreshUI(NodeMapForm form, NodeMapFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(NodeMapNodeClickEventArgs.EventId, OnNodeClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(NodeMapNodeClickEventArgs.EventId, OnNodeClicked);
}
public override int? OpenUI(object userData = null)
{
if (userData is NodeMapFormContext context)
{
return OpenUIInternal(context);
}
if (userData is NodeMapFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("NodeMapFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("NodeMapFormController.OpenUI() useCase is null.");
return null;
}
return OpenUI(_useCase.CreateInitialModel());
}
public int? OpenUI(NodeMapFormRawData rawData)
{
return OpenUIInternal(BuildContext(rawData));
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is NodeMapFormUseCase nodeMapFormUseCase))
{
Log.Error("NodeMapFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = nodeMapFormUseCase;
}
private void OnNodeClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is NodeMapNodeClickEventArgs args) || _useCase == null)
{
return;
}
if (!_useCase.TryRequestEnterNode(args.SequenceIndex, out NodeMapNodeRawData selectedNode, out NodeMapFormRawData latestRawData))
{
if (latestRawData != null)
{
SetContext(BuildContext(latestRawData));
RefreshCurrentUI();
}
return;
}
if (latestRawData != null)
{
SetContext(BuildContext(latestRawData));
RefreshCurrentUI();
}
if (Form != null && selectedNode != null)
{
GameEntry.Event.Fire(Form, NodeMapNodeEnterRequestedEventArgs.Create(
selectedNode.RunId,
selectedNode.NodeId,
selectedNode.NodeType,
selectedNode.SequenceIndex));
}
}
private bool IsEventFromCurrentForm(object sender)
{
if (Form == null)
{
return false;
}
if (ReferenceEquals(sender, Form))
{
return true;
}
if (sender is Component component)
{
NodeMapForm ownerForm = component.GetComponentInParent<NodeMapForm>();
return ownerForm == Form;
}
return false;
}
}
}
@@ -0,0 +1,425 @@
using System.Collections.Generic;
using GeometryTD.CustomEvent;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using GameFramework.Event;
using GeometryTD.CustomComponent;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public partial class RepoFormController : UIFormControllerCommonBase<RepoFormContext, RepoForm>
{
private const int MaxParticipantCount = 4;
private RepoFormUseCase _useCase;
private readonly Dictionary<long, RepoItemClickActionType> _itemClickActionMap =
new Dictionary<long, RepoItemClickActionType>();
private readonly Dictionary<long, ItemDescSeed> _itemDescSeedMap = new Dictionary<long, ItemDescSeed>();
private readonly HashSet<long> _participantTowerIds = new HashSet<long>();
private readonly HashSet<long> _compAreaTowerIds = new HashSet<long>();
private sealed class ItemDescSeed
{
public string Title;
public string TypeText;
public string Description;
public TagType[] Tags;
public TagRuntimeData[] TagRuntimes;
}
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
protected override void RefreshUI(RepoForm form, RepoFormContext context)
{
form.RefreshUI(context);
ApplyParticipantSelection();
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(RepoItemClickedEventArgs.EventId, OnRepoItemClicked);
GameEntry.Event.Subscribe(RepoItemDragEndedEventArgs.EventId, OnRepoItemDragEnded);
GameEntry.Event.Subscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked);
GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
GameEntry.Event.Subscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
GameEntry.Event.Subscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
GameEntry.Event.Subscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
GameEntry.Event.Subscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(RepoItemClickedEventArgs.EventId, OnRepoItemClicked);
GameEntry.Event.Unsubscribe(RepoItemDragEndedEventArgs.EventId, OnRepoItemDragEnded);
GameEntry.Event.Unsubscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked);
GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
GameEntry.Event.Unsubscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
GameEntry.Event.Unsubscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
GameEntry.Event.Unsubscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
GameEntry.Event.Unsubscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
}
public override int? OpenUI(object userData = null)
{
if (userData is RepoFormContext repoFormContext)
{
return OpenUIInternal(repoFormContext);
}
if (userData is RepoFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("RepoFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("RepoFormController.OpenUI() useCase is null.");
return null;
}
RepoFormRawData rawData = _useCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(RepoFormRawData rawData)
{
RepoFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is RepoFormUseCase repoFormUseCase))
{
Log.Error("RepoFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = repoFormUseCase;
}
#region Event Handlers
private void OnRepoItemClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (!(e is RepoItemClickedEventArgs args))
{
return;
}
RepoItemClickActionType clickActionType = RepoItemClickActionType.OpenDetail;
if (_itemClickActionMap.TryGetValue(args.ItemId, out RepoItemClickActionType mappedAction))
{
clickActionType = mappedAction;
}
if (args.ItemId <= 0)
{
return;
}
if (_useCase != null && _useCase.State == RepoFormState.Sell)
{
if (_useCase.TryToggleSellSelection(
args.ItemId,
out RepoFormRawData sellModeRawData,
out PlayerInventorySaleFailureReason failureReason))
{
SetContext(BuildContext(sellModeRawData));
RefreshCurrentUI();
}
else if (failureReason == PlayerInventorySaleFailureReason.ParticipantTower)
{
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
}
return;
}
if (clickActionType == RepoItemClickActionType.RemoveParticipant)
{
if (_useCase == null || Form == null)
{
return;
}
if (!_useCase.TryRemoveParticipantTower(args.ItemId))
{
return;
}
RefreshParticipantAreaOnly();
return;
}
if (!_itemDescSeedMap.TryGetValue(args.ItemId, out ItemDescSeed seed))
{
return;
}
GameEntry.UIRouter.OpenUI(UIFormType.ItemDescForm, new ItemDescFormRawData
{
Title = seed.Title,
TypeText = seed.TypeText,
Description = seed.Description ?? string.Empty,
Price = 0,
ScreenPosition = args.ScreenPosition,
Tags = seed.Tags,
TagRuntimes = seed.TagRuntimes
});
}
private void OnRepoItemDragEnded(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
if (!(e is RepoItemDragEndedEventArgs args))
{
return;
}
if (Form == null || !_itemClickActionMap.ContainsKey(args.ItemId))
{
return;
}
bool isSelected = _compAreaTowerIds.Contains(args.ItemId)
? _participantTowerIds.Contains(args.ItemId)
: args.Assigned;
Form.SetRepoItemSelected(args.ItemId, isSelected);
}
private void OnCombineSlotClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
if (!(e is CombineSlotClickedEventArgs args))
{
return;
}
if (Form == null)
{
return;
}
if (Form.TryClearCombineSlot(args.SlotIndex, out long removedItemId) && removedItemId > 0)
{
Form.SetRepoItemSelected(removedItemId, false);
}
}
private void OnRepoFormReturn(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoFormReturnEventArgs))
{
return;
}
if (Form == null)
{
return;
}
CloseUI();
}
private void OnRepoCombineRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (!(e is RepoCombineRequestedEventArgs args))
{
return;
}
if (_useCase == null || Form == null)
{
return;
}
if (!_useCase.TryAssembleTower(args.MuzzleItemId, args.BearingItemId, args.BaseItemId))
{
return;
}
RepoFormRawData latestRawData = _useCase.CreateInitialModel();
RepoFormContext latestContext = BuildContext(latestRawData);
if (latestContext != null)
{
SetContext(latestContext);
RefreshCurrentUI();
}
}
private void OnRepoParticipantAssignRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
if (!(e is RepoParticipantAssignRequestedEventArgs args))
{
return;
}
if (_useCase == null || Form == null || args.TowerItemId <= 0)
{
return;
}
ParticipantTowerAssignResult result = _useCase.TryAddParticipantTower(args.TowerItemId);
if (result == null || !result.IsSuccess)
{
if (result != null)
{
Log.Warning(
"RepoFormController denied participant assignment. TowerId={0}, FailureReason={1}, ValidationFailureReason={2}.",
result.TowerInstanceId,
result.FailureReason,
result.ValidationFailureReason);
GameEntry.UIRouter.OpenUI(
UIFormType.DialogForm,
RepoParticipantAssignDialogUtility.BuildDialog(result, MaxParticipantCount));
}
return;
}
RefreshParticipantAreaOnly();
}
private void OnRepoSellModeToggleRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellModeToggleRequestedEventArgs) || _useCase == null)
{
return;
}
if (_useCase.State != RepoFormState.Assemble)
{
return;
}
RepoFormRawData rawData = _useCase.EnterSellState();
SetContext(BuildContext(rawData));
RefreshCurrentUI();
}
private void OnRepoSellCancelRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellCancelRequestedEventArgs) || _useCase == null)
{
return;
}
if (_useCase.State != RepoFormState.Sell)
{
return;
}
SetContext(BuildContext(_useCase.ExitSellState()));
RefreshCurrentUI();
}
private void OnRepoSellConfirmRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellConfirmRequestedEventArgs) || _useCase == null)
{
return;
}
if (!_useCase.TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result))
{
if (result != null && result.FailureReason == PlayerInventorySaleFailureReason.ParticipantTower)
{
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
}
return;
}
SetContext(BuildContext(rawData));
RefreshCurrentUI();
if (GameEntry.UI.GetUIForm(UIFormType.ShopForm) != null)
{
GameEntry.UIRouter.OpenUI(UIFormType.ShopForm);
}
}
private bool IsEventFromCurrentForm(object sender)
{
if (Form == null)
{
return false;
}
if (ReferenceEquals(sender, Form))
{
return true;
}
if (sender is Component component)
{
RepoForm ownerForm = component.GetComponentInParent<RepoForm>();
return ownerForm == Form;
}
return false;
}
private static void OpenSellBlockedDialog(string message)
{
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "无法出售",
Message = message,
PauseGame = false,
ConfirmText = "知道了"
});
}
#endregion
}
}