Some housekeeping and some editor tools for schedules anchors
This commit is contained in:
@@ -22,6 +22,20 @@ class SWidget;
|
||||
* an id that no actor in this level claims is left as it is, because that is a real state (furniture
|
||||
* that was deleted, or furniture sitting in a sublevel that is not loaded) and not something to hide.
|
||||
*
|
||||
* Picking an anchor also records where that anchor stands, into the action's own AnchorTransform, so
|
||||
* that a schedule still means something in a running world while the furniture it points at sits in a
|
||||
* cell nobody has loaded. The panel is the only moment at which both halves can be had from the actor
|
||||
* itself, which is why the two fields that snapshot is stored in are written here and, being written
|
||||
* rather than authored, keep no row anything could be typed into.
|
||||
*
|
||||
* What that record needs is not another field to fill in but a way to see that it has gone stale, so
|
||||
* the panel reports it in a read-only row of its own, Anchor Location, directly under the anchor it
|
||||
* belongs to. The row stands the recorded place against where the anchor stands now and says so when
|
||||
* the two have come apart. It reads the same whole-transform rule as Schedule Asset Recheck, so the
|
||||
* row and the sweep can never disagree about which schedules have drifted, and it is the sweep - or
|
||||
* picking the anchor again - that clears the marker. The two fields themselves are still reported
|
||||
* through the anchor row's tooltip.
|
||||
*
|
||||
* Every other member of the struct keeps the row the details panel would have given it.
|
||||
*/
|
||||
class FNpcScheduleActionCustomization : public IPropertyTypeCustomization
|
||||
@@ -39,6 +53,13 @@ protected:
|
||||
* unassign button. Stands in for the raw Guid field, which is never shown. */
|
||||
void AddAnchorRow(IDetailChildrenBuilder& ChildBuilder, TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** The anchor location row: the place this action kept for its anchor, read back out of the
|
||||
* snapshot the pick wrote and stood against where that anchor stands now, directly under the anchor
|
||||
* row it belongs to. Read only on purpose rather than a disabled copy of the rows above it: the
|
||||
* record is written by picking an anchor and by Recheck Schedules, and a row that let it be typed
|
||||
* in would be a third writer able to send an NPC anywhere at all. */
|
||||
void AddAnchorLocationRow(IDetailChildrenBuilder& ChildBuilder, TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** What the id points at: the actor's label, "None" when nothing is assigned, or the raw id when no
|
||||
* actor in this level claims it. */
|
||||
FText GetAnchorText(TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
@@ -51,6 +72,48 @@ protected:
|
||||
* glance from one whose anchor the panel cannot name. */
|
||||
FSlateColor GetAnchorColor(TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** The recorded place, in whichever state the row is actually in: "None" for an action with no
|
||||
* anchor, "Not recorded" for an anchor picked before there was anywhere to record one, and the
|
||||
* recorded point itself, marked "(differs)" once the anchor has moved away from it. */
|
||||
FText GetAnchorLocationText(TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** What the row is reporting and what to do when it reports a difference. Written per state, the
|
||||
* way the anchor row's tooltip is, so that an action agreeing with its anchor is not handed
|
||||
* instructions it has no use for. */
|
||||
FText GetAnchorLocationTooltip(TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** Warning while the record and the anchor disagree, subdued while there is no record at all, and
|
||||
* ordinary foreground otherwise - a record this level cannot check included, since a schedule for
|
||||
* a cell nobody has loaded is exactly what the record is for. */
|
||||
FSlateColor GetAnchorLocationColor(TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** How the recorded place stands against the anchor itself. One answer worked out in one place,
|
||||
* because three of the members above are each asked for it: a row whose text said "(differs)"
|
||||
* while its colour said otherwise would be two readings of one rule, and the rule itself is
|
||||
* already shared with Schedule Asset Recheck by hand. */
|
||||
enum class EAnchorLocationState : uint8
|
||||
{
|
||||
/** No anchor at all, so there is no place to record and nothing to compare. */
|
||||
NoAnchor,
|
||||
|
||||
/** An anchor, but no place recorded for it: a pick made before there was anywhere to store one,
|
||||
* which is the state every action authored before snapshots existed is in. */
|
||||
NotRecorded,
|
||||
|
||||
/** A place recorded, and no actor in this level claims the anchor's id to check it against. */
|
||||
Unresolved,
|
||||
|
||||
/** A place recorded that still matches where the anchor stands. */
|
||||
UpToDate,
|
||||
|
||||
/** A place recorded that the anchor has since moved away from, whole transform and all. */
|
||||
Differs
|
||||
};
|
||||
|
||||
/** Which of those states this action is in, with the two transforms it was decided from handed back
|
||||
* as well, since every caller has something to say about one or both of them. */
|
||||
EAnchorLocationState GetAnchorLocationState(TSharedRef<IPropertyHandle> AnchorHandle, TOptional<FTransform>& OutSnapshot, FTransform& OutLiveTransform) const;
|
||||
|
||||
bool CanClearAnchor(TSharedRef<IPropertyHandle> AnchorHandle) const;
|
||||
|
||||
/** Writes one id into the action. An invalid id is what unassign does, so the button that clears
|
||||
@@ -66,6 +129,32 @@ protected:
|
||||
* value and so comes back as unassigned. */
|
||||
static FGuid ReadAnchorGuid(const TSharedRef<IPropertyHandle>& AnchorHandle);
|
||||
|
||||
/** A field of the action the anchor handle belongs to, reached from the anchor handle rather than
|
||||
* handed around: the widgets that hold the anchor handle outlive the layout call that built them,
|
||||
* and a sibling looked up at the moment it is written is exactly as fresh as the anchor itself.
|
||||
* Null when the handle is not a child of a struct node, or when the struct has no such field, and
|
||||
* that is not a failure: the anchor is then assigned without a snapshot, which is the state every
|
||||
* schedule authored before snapshots existed is in. */
|
||||
static TSharedPtr<IPropertyHandle> GetAnchorSibling(const TSharedRef<IPropertyHandle>& AnchorHandle, FName ChildName);
|
||||
|
||||
/** Where the actor an id belongs to is standing, answered through the rule the game reads
|
||||
* (EleriStandPoint::ResolveAnchorTransform) rather than through the actor's own transform, so that
|
||||
* a schedule saved in the editor and one resolved in a running world cannot put the same NPC in
|
||||
* two different places. False when no actor in this level claims the id, which is what clearing
|
||||
* the anchor leaves behind and what assigning an anchor whose sublevel is not loaded leaves
|
||||
* behind. */
|
||||
static bool ResolveAnchorSnapshot(const FGuid& AnchorGuid, FTransform& OutTransform);
|
||||
|
||||
/** The place this action has kept for its anchor, if it kept one. Nothing when no snapshot was ever
|
||||
* stored, when those fields are not part of the struct, or when the row stands for several actions
|
||||
* at once, since several actions have no one place between them to report. */
|
||||
TOptional<FTransform> GetCachedAnchorTransform(const TSharedRef<IPropertyHandle>& AnchorHandle) const;
|
||||
|
||||
/** Writes the snapshot that goes with an id. Raw data rather than a formatted string, because a
|
||||
* transform has no text import to round trip through and this is the only way one reaches the
|
||||
* asset. */
|
||||
static void WriteCachedAnchorTransform(const TSharedRef<IPropertyHandle>& AnchorTransformHandle, const FTransform& Snapshot);
|
||||
|
||||
/** The first actor in the current selection that can be an anchor, or null when there is none. */
|
||||
static AActor* GetSelectedAnchorActor();
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ public:
|
||||
/** Opens (or focuses) the Item Editor window. */
|
||||
void OpenItemEditorTab();
|
||||
|
||||
/** Compares every NPC schedule asset's recorded anchor transforms against the anchors standing in
|
||||
* the open level, writes the ones that have moved and saves the assets they belong to, and says
|
||||
* what it found. */
|
||||
void RecheckScheduleAssets();
|
||||
|
||||
private:
|
||||
void RegisterMenus();
|
||||
|
||||
|
||||
59
Source/ProjectEleriEditor/Public/ScheduleAssetRecheck.h
Normal file
59
Source/ProjectEleriEditor/Public/ScheduleAssetRecheck.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
* Re-reads the place every NPC schedule sends its NPCs to.
|
||||
*
|
||||
* A schedule names its anchor by PersistentId and keeps a copy of where that anchor stood when it was
|
||||
* picked beside it, see FScheduledAction::AnchorTransform. That copy is what an NPC is sent at while
|
||||
* its anchor sits in a cell nobody has loaded, and it is written once, when the anchor is picked in
|
||||
* the details panel. The furniture it is a copy of can be moved afterwards without anybody picking
|
||||
* anything again, and then the two disagree: the schedule says the bed is where it used to be, and
|
||||
* the level says otherwise.
|
||||
*
|
||||
* This is the sweep that closes that gap. It stands every anchor the open level can answer for
|
||||
* against the copy its schedule holds, writes the ones that have moved, and saves the assets that
|
||||
* changed. It is an editor tool rather than something the game runs, because the drift is a fault in
|
||||
* the authored data and not in what a running game does with it: nothing reads the copy while it can
|
||||
* see the anchor itself, so a game never notices, and the schedule only starts lying when an NPC
|
||||
* meets a level that has the anchor streamed out.
|
||||
*
|
||||
* The one thing it cannot answer is an anchor that is not in the open level, which is either
|
||||
* furniture in a cell that is not loaded or a schedule naming something that is gone. Nothing here
|
||||
* can tell those two apart, so both are counted and reported rather than guessed at, and the copies
|
||||
* they belong to are left exactly as they were.
|
||||
*/
|
||||
namespace ScheduleAssetRecheck
|
||||
{
|
||||
/** What one sweep found, so that a caller can say it out loud rather than only log it. */
|
||||
struct FResult
|
||||
{
|
||||
/** Assets the registry handed over, whether or not anything in them had moved. */
|
||||
int32 NumAssetsScanned = 0;
|
||||
|
||||
/** Assets that no longer agreed with the level, and so were written and saved. */
|
||||
int32 NumAssetsUpdated = 0;
|
||||
|
||||
/** Actions carrying an anchor, which is every action there is a place to check for. An action
|
||||
* with no anchor happens wherever the NPC already is and has nothing to go stale. */
|
||||
int32 NumAnchorsChecked = 0;
|
||||
|
||||
/** Copies that had moved, or that had never been recorded at all. */
|
||||
int32 NumAnchorsUpdated = 0;
|
||||
|
||||
/** Anchors no actor in the open level claims, whose copies were left as they were. */
|
||||
int32 NumAnchorsUnresolved = 0;
|
||||
};
|
||||
|
||||
/** Walks every NpcScheduleAsset in the project, refreshes the anchor copy of every action whose
|
||||
* anchor is standing in the open level, and saves the assets that changed.
|
||||
*
|
||||
* The editor world is the only one read, so what this answers for is exactly what the level in
|
||||
* front of the author can see, and a schedule whose anchors live in some other level is left
|
||||
* alone by the same rule that leaves an unloaded cell alone. What it did is reported through a
|
||||
* notification and the log, and returned here as well, for a caller that wants the numbers. */
|
||||
FResult RecheckAllScheduleAssets();
|
||||
}
|
||||
Reference in New Issue
Block a user