feat(ui): complete retail item drop branches
Give retained buttons a reusable item-drop seam, wire the toolbar backpack target, and port retail stack-merge legality, capacity clamping, wire dispatch, destination selection, and immediate shortcut rekey notice. Record the live ACE merge gate and keep split quantity under AP-101. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
3281e0acb4
commit
dc1649c493
19 changed files with 479 additions and 28 deletions
|
|
@ -92,8 +92,8 @@ public static class ShortcutDropPlanner
|
|||
|
||||
/// <summary>
|
||||
/// Plan the toolbar side of <c>RecvNotice_FullMergingItem @ 0x004BE9B0</c>.
|
||||
/// The normal retail invariant is one object shortcut; all matching records are
|
||||
/// rekeyed here so a malformed duplicate cannot retain a destroyed stack id.
|
||||
/// <c>CreateShortcutToItem</c> removes an existing destination shortcut before
|
||||
/// placing it into the source shortcut's former slot.
|
||||
/// </summary>
|
||||
public static ShortcutMutation[] PlanFullStackMerge(
|
||||
IReadOnlyList<ShortcutEntry?> slots,
|
||||
|
|
@ -104,15 +104,21 @@ public static class ShortcutDropPlanner
|
|||
if (slots.Count != ShortcutStore.SlotCount || oldObjectId == 0 || newObjectId == 0)
|
||||
return Array.Empty<ShortcutMutation>();
|
||||
|
||||
var mutations = new List<ShortcutMutation>();
|
||||
var state = new ShortcutEntry?[ShortcutStore.SlotCount];
|
||||
for (int slot = 0; slot < slots.Count; slot++)
|
||||
{
|
||||
if (VisibleEntry(slots[slot]) is not { } entry || entry.ObjectId != oldObjectId)
|
||||
continue;
|
||||
|
||||
mutations.Add(ShortcutMutation.Remove(slot));
|
||||
mutations.Add(ShortcutMutation.Add(entry with { ObjectId = newObjectId }));
|
||||
state[slot] = slots[slot];
|
||||
}
|
||||
|
||||
var mutations = new List<ShortcutMutation>(3);
|
||||
int sourceSlot = FindFirstVisibleObject(state, oldObjectId);
|
||||
if (sourceSlot < 0)
|
||||
return Array.Empty<ShortcutMutation>();
|
||||
|
||||
ShortcutEntry sourceEntry = state[sourceSlot]!.Value;
|
||||
RemoveVisibleAt(state, sourceSlot, mutations);
|
||||
RemoveFirstVisibleObject(state, newObjectId, mutations);
|
||||
Add(state, sourceEntry with { ObjectId = newObjectId }, mutations);
|
||||
return mutations.ToArray();
|
||||
}
|
||||
|
||||
|
|
@ -149,6 +155,14 @@ public static class ShortcutDropPlanner
|
|||
}
|
||||
}
|
||||
|
||||
private static int FindFirstVisibleObject(ShortcutEntry?[] state, uint objectId)
|
||||
{
|
||||
for (int slot = 0; slot < state.Length; slot++)
|
||||
if (VisibleEntry(state[slot]) is { } entry && entry.ObjectId == objectId)
|
||||
return slot;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void Add(
|
||||
ShortcutEntry?[] state,
|
||||
ShortcutEntry entry,
|
||||
|
|
|
|||
49
src/AcDream.Core/Items/StackMergePlanner.cs
Normal file
49
src/AcDream.Core/Items/StackMergePlanner.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
|
||||
namespace AcDream.Core.Items;
|
||||
|
||||
public readonly record struct StackMergeItem(
|
||||
uint ObjectId,
|
||||
uint WeenieClassId,
|
||||
int StackSize,
|
||||
int MaxStackSize,
|
||||
int TradeState);
|
||||
|
||||
public readonly record struct StackMergePlan(uint SourceObjectId, uint TargetObjectId, uint Amount);
|
||||
|
||||
/// <summary>
|
||||
/// Pure port of <c>ItemHolder::IsMergeAttemptLegal @ 0x00586F30</c> and the
|
||||
/// transfer-size branch in <c>ItemHolder::AttemptMerge @ 0x005878F0</c>.
|
||||
/// </summary>
|
||||
public static class StackMergePlanner
|
||||
{
|
||||
public static StackMergePlan? Plan(
|
||||
in StackMergeItem source,
|
||||
in StackMergeItem target,
|
||||
bool readyForInventoryRequest,
|
||||
int requestedAmount)
|
||||
{
|
||||
if (!readyForInventoryRequest
|
||||
|| source.ObjectId == 0
|
||||
|| target.ObjectId == 0
|
||||
|| source.ObjectId == target.ObjectId
|
||||
|| source.MaxStackSize <= 1
|
||||
|| target.MaxStackSize <= 1
|
||||
|| source.TradeState == 1
|
||||
|| target.TradeState == 1
|
||||
|| source.WeenieClassId != target.WeenieClassId)
|
||||
return null;
|
||||
|
||||
int targetSize = Math.Max(1, target.StackSize);
|
||||
int available = target.MaxStackSize - targetSize;
|
||||
if (available <= 0)
|
||||
return null;
|
||||
|
||||
int sourceSize = Math.Max(1, source.StackSize);
|
||||
int desired = requestedAmount > 0 ? Math.Min(requestedAmount, sourceSize) : sourceSize;
|
||||
uint transfer = (uint)Math.Min(desired, available);
|
||||
return transfer == 0
|
||||
? null
|
||||
: new StackMergePlan(source.ObjectId, target.ObjectId, transfer);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue