From 091bfe1ad2d845bf0471ea4c0916c15c17be3208 Mon Sep 17 00:00:00 2001 From: jyelon Date: Sun, 29 Mar 2026 02:37:47 -0400 Subject: [PATCH] Lots more progress on the name overhaul --- .../Handlers/BlueprintInterface_Remove.h | 26 +++- .../UEWingman/Handlers/GraphNode_Duplicate.h | 104 --------------- .../Handlers/GraphNode_SetDefaults.h | 4 +- .../Source/UEWingman/Handlers/Property_Get.h | 4 +- .../Source/UEWingman/Handlers/Property_Set.h | 6 +- .../Source/UEWingman/Private/WingProperty.cpp | 21 --- .../UEWingman/Private/WingTokenizer.cpp | 11 ++ .../Source/UEWingman/Private/WingTypes.cpp | 9 +- .../Source/UEWingman/Private/WingUtils.cpp | 23 ++-- .../Source/UEWingman/Public/WingProperty.h | 1 - .../Source/UEWingman/Public/WingTokenizer.h | 7 + .../Source/UEWingman/Public/WingUtils.h | 123 +++++++----------- 12 files changed, 106 insertions(+), 233 deletions(-) delete mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Duplicate.h diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h index 53f72698..849b7262 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintInterface_Remove.h @@ -4,6 +4,7 @@ #include "WingHandler.h" #include "WingFetcher.h" #include "WingUtils.h" +#include "WingTypes.h" #include "WingServer.h" #include "Engine/Blueprint.h" #include "Kismet2/BlueprintEditorUtils.h" @@ -41,11 +42,26 @@ public: UBlueprint* BP = F.Asset(Blueprint).Cast(); if (!BP) return; - // Find the interface by name - FBPInterfaceDescription *IFaceDesc = - WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces, TEXT("Interface")); - if (!IFaceDesc) return; - UClass* FoundInterface = IFaceDesc->Interface; + // Resolve the interface name to a UClass* + UWingTypes::Requirements Req; + Req.BlueprintType = false; + Req.Blueprintable = false; + Req.AllowContainer = false; + UClass* FoundInterface = UWingTypes::TextToOneInterfaceType(Interface, Req); + if (!FoundInterface) return; + + // Verify this blueprint actually implements it + bool Found = false; + for (const FBPInterfaceDescription& Desc : BP->ImplementedInterfaces) + { + if (Desc.Interface == FoundInterface) { Found = true; break; } + } + if (!Found) + { + UWingServer::Printf(TEXT("ERROR: Blueprint %s does not implement interface %s\n"), + *WingUtils::FormatName(BP), *WingUtils::FormatName(FoundInterface)); + return; + } FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Duplicate.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Duplicate.h deleted file mode 100644 index ead3ea33..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Duplicate.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "WingServer.h" -#include "WingHandler.h" -#include "WingFetcher.h" -#include "WingUtils.h" -#include "EdGraph/EdGraph.h" -#include "EdGraph/EdGraphNode.h" -#include "EdGraph/EdGraphPin.h" -#include "GraphNode_Duplicate.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -UCLASS() -class UWing_GraphNode_Duplicate : public UObject, public IWingHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Target graph")) - FString Graph; - - UPROPERTY(meta=(Description="Array of node names to duplicate (as returned by FormatName)")) - FWingJsonArray Nodes; - - UPROPERTY(meta=(Optional, Description="X offset for duplicated nodes")) - int32 OffsetX = 50; - - UPROPERTY(meta=(Optional, Description="Y offset for duplicated nodes")) - int32 OffsetY = 50; - - virtual FString GetDescription() const override - { - return TEXT("Duplicate one or more nodes in a Blueprint graph. " - "Creates copies offset from the originals with new GUIDs. " - "Connections are not preserved on the duplicates."); - } - - virtual void Handle() override - { - WingFetcher F; - UEdGraph* TargetGraph = F.Walk(Graph).Cast(); - if (!TargetGraph) return; - - if (Nodes.Array.Num() == 0) - { - UWingServer::Print(TEXT("ERROR: Nodes array is empty\n")); - return; - } - - // Find all source nodes by name - TArray SourceNodes; - for (const TSharedPtr& IdVal : Nodes.Array) - { - FString Name = IdVal->AsString(); - UEdGraphNode* Found = nullptr; - for (UEdGraphNode* Node : TargetGraph->Nodes) - { - if (WingUtils::Identifies(Name, Node)) - { - Found = Node; - break; - } - } - if (!Found) - { - UWingServer::Printf(TEXT("ERROR: Node '%s' not found in graph\n"), *Name); - continue; - } - SourceNodes.Add(Found); - } - - if (SourceNodes.Num() == 0) return; - - // Duplicate each node - for (UEdGraphNode* SourceNode : SourceNodes) - { - UEdGraphNode* NewNode = DuplicateObject(SourceNode, TargetGraph); - if (!NewNode) - { - UWingServer::Printf(TEXT("ERROR: Failed to duplicate %s\n"), *WingUtils::FormatName(SourceNode)); - continue; - } - - NewNode->CreateNewGuid(); - NewNode->NodePosX += OffsetX; - NewNode->NodePosY += OffsetY; - - for (UEdGraphPin* Pin : NewNode->Pins) - { - if (Pin) - Pin->LinkedTo.Empty(); - } - - TargetGraph->AddNode(NewNode, false, false); - UWingServer::Printf(TEXT("Duplicated: %s -> %s\n"), *WingUtils::FormatName(SourceNode), *WingUtils::FormatName(NewNode)); - } - - } -}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h index 8c411dae..557a4c1c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h @@ -92,12 +92,12 @@ public: if (!Node) return; TArray All = FWingProperty::GetDetailsMutable(Node, CPF_Edit); - FWingProperty P = FWingProperty::FindOneExactMatch(All, Entry.Name); + FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property")); if (!P) return; UWingServer::AddTouchedObject(Node); - if (!P.SetText(Entry.Value)) + if (!P->SetText(Entry.Value)) return; } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h index 0ac6773b..7b06fd98 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h @@ -37,10 +37,10 @@ public: if (!Obj) return; TArray All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit); - FWingProperty P = FWingProperty::FindOneExactMatch(All, Property); + FWingProperty *P = WingUtils::FindOneWithExternalID(Property, All, TEXT("Property")); if (!P) return; - UWingServer::Print(P.GetText()); + UWingServer::Print(P->GetText()); UWingServer::Print(TEXT("\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h index edb5fd47..294976bf 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h @@ -48,15 +48,15 @@ public: // Validation pass — resolve all properties and values before modifying anything. for (const auto& Pair : Properties.Json->Values) { - FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key); + FWingProperty *P = WingUtils::FindOneWithExternalID(Pair.Key, All, TEXT("Property")); if (!P) return; } // Assignment Pass - store the values. for (const auto& Pair : Properties.Json->Values) { - FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key); - if (P.SetJson(Pair.Value)) SuccessCount++; + FWingProperty *P = WingUtils::FindOneWithExternalID(Pair.Key, All, TEXT("Property")); + if (P && P->SetJson(Pair.Value)) SuccessCount++; } UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num()); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index 63b78e67..eb5c34ee 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -336,27 +336,6 @@ TArray FWingProperty::FindAllSubstring(const TArray& Props, const FString& Name) -{ - TArray Matches; - for (const FWingProperty& P : Props) - { - if (WingUtils::Identifies(Name, P.Prop)) - Matches.Add(P); - } - if (Matches.Num() == 0) - { - UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name); - return FWingProperty(); - } - if (Matches.Num() > 1) - { - UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name); - return FWingProperty(); - } - return Matches[0]; -} - bool FWingProperty::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional) { FString JsonKey = WingUtils::FormatName(P.Prop); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp index 384de0c0..299e7efa 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp @@ -311,3 +311,14 @@ FString WingTokenizer::CheckInternalizeID(const FString &ExternalID) } return InternalID; } + +FString WingTokenizer::SimplifyID(const FString &ID) +{ + TStringBuilder<512> Result; + for (TCHAR Ch : ID) + { + if (WingCharacterClasses::GetCat(Ch) == Cat::Identifier) + Result.AppendChar(Ch); + } + return Result.ToString(); +} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp index 773564be..dcc2ab5b 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp @@ -2,6 +2,7 @@ #include "WingUtils.h" #include "WingServer.h" #include "WingManual.h" +#include "WingTokenizer.h" #include "Editor.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" @@ -161,15 +162,17 @@ FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const U // Verify that the path is not already associated. check(!PathToShort.Find(Path)); - // Derive the name proposal from the path. + // Derive the name proposal from the path. Names used by + // the type system are always simple ids that require no + // escaping when emitted. int32 DotIndex; check(Path.FindLastChar('.', DotIndex)); FString Proposal = Path.Mid(DotIndex + 1); if ((PinCategory == UEdGraphSchema_K2::PC_Object || PinCategory == UEdGraphSchema_K2::PC_Interface) && IsUserDefined && Proposal.EndsWith(TEXT("_C"))) Proposal.LeftChopInline(2); - Proposal = WingUtils::ExternalizeID(Proposal); - check(!Proposal.IsEmpty()); + Proposal = WingTokenizer::SimplifyID(Proposal); + if (Proposal.IsEmpty()) Proposal = "Unknown"; // Construct the Info struct. Info TypeInfo; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 5db1a7fb..062cb5ef 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -55,10 +55,7 @@ // Name sanitization // ============================================================ -FString WingUtils::ExternalizeID(const FString &InName) -{ - return WingTokenizer::ExternalizeID(InName); -} +FName WingUtils::GetFName(const FWingProperty &Prop) { return Prop.Prop->GetFName(); } FString WingUtils::ExternalizeID(FName Name) { @@ -126,7 +123,7 @@ FString WingUtils::FormatName(const UBlueprint *BP) FString WingUtils::FormatName(const UActorComponent *C) { - return ExternalizeID(C->GetName()); + return ExternalizeID(C->GetFName()); } FString WingUtils::FormatName(const USCS_Node *Node) @@ -136,22 +133,22 @@ FString WingUtils::FormatName(const USCS_Node *Node) FString WingUtils::FormatName(const UEdGraph *Graph) { - return ExternalizeID(Graph->GetName()); + return ExternalizeID(Graph->GetFName()); } FString WingUtils::FormatName(const TObjectPtr &Graph) { - return ExternalizeID(Graph->GetName()); + return ExternalizeID(Graph->GetFName()); } FString WingUtils::FormatName(const UEdGraphNode* Node) { - return ExternalizeID(Node->GetName()); + return ExternalizeID(Node->GetFName()); } FString WingUtils::FormatName(const UEdGraphPin *Pin) { - return ExternalizeID(Pin->GetName()); + return ExternalizeID(Pin->GetFName()); } FString WingUtils::FormatName(const FMemberReference &Ref) @@ -168,7 +165,7 @@ FString WingUtils::FormatName(const UStruct *Struct) { if (Cast(Struct) || Cast(Struct)) return UWingTypes::TypeToTextOrDie(Struct); - return ExternalizeID(Struct->GetName()); + return ExternalizeID(Struct->GetFName()); } FString WingUtils::FormatName(const UClass *Class) @@ -193,7 +190,7 @@ FString WingUtils::FormatName(const UMaterialFunction *MaterialFunction) FString WingUtils::FormatName(const UMaterialExpression *Expression) { - return ExternalizeID(Expression->GetName()); + return ExternalizeID(Expression->GetFName()); } FString WingUtils::FormatName(const UStaticMesh *Mesh) @@ -233,7 +230,7 @@ FString WingUtils::FormatName(const UEnum *Enum) FString WingUtils::FormatName(const FProperty *Prop) { - return ExternalizeID(Prop->GetName()); + return ExternalizeID(Prop->GetFName()); } FString WingUtils::FormatName(const FUserPinInfo &Pin) @@ -253,7 +250,7 @@ FString WingUtils::FormatName(const UWingComponentReference *Ref) FString WingUtils::FormatName(const UWidget *Widget) { - return ExternalizeID(Widget->GetName()); + return ExternalizeID(Widget->GetFName()); } // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h index d661dee9..a98ab12e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h @@ -62,7 +62,6 @@ struct FWingProperty // Functions to find items by name in an array of properties. // static TArray FindAllSubstring(const TArray& Props, const FString& Substring); - static FWingProperty FindOneExactMatch(const TArray& Props, const FString& Name); static void Remove(TArray& Props, const FString& Name); static void Move(TArray &Out, TArray &In, const FString &Name); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h index 9479dab1..b3f367fa 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h @@ -150,6 +150,13 @@ struct WingTokenizer // string. static FString CheckInternalizeID(const FString &ExternalID); + // Simplify an ID. This removes any non-identifier + // characters from the ID. Be careful! This could + // remove the whole identifier! So obviously this + // should only be used in certain rare contexts where + // that's OK. + static FString SimplifyID(const FString &ID); + // Print all tokens to the log for debugging. void PrintEverything() const; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index 096ad1da..f5eff48f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -22,6 +22,7 @@ class UAnimStateTransitionNode; class UScriptStruct; class UEnum; struct FBPInterfaceDescription; +struct FWingProperty; #include "Engine/World.h" #include "Materials/Material.h" @@ -37,21 +38,14 @@ struct FBPInterfaceDescription; class WingUtils { public: - // Given an array and a reference to an element of the array, return - // a pointer to the element, or if the element is already a pointer, the element. - template static const T* EltAsPtr(const TArray &A, const T &Elt) { return &Elt; } - template static T* EltAsPtr(TArray &A, T &Elt) { return &Elt; } - template static T* EltAsPtr(const TArray &A, T* &Elt) { return Elt; } - template static T* EltAsPtr(TArray &A, T* &Elt) { return Elt; } - template static const T* EltAsPtr(const TArray &A, const T* &Elt) { return Elt; } - template static const T* EltAsPtr(TArray &A, const T* &Elt) { return Elt; } - //////////////////////////////////////////////////////// // // GetFName // - // For objects whose FormatName uses ExternalizeID, - // GetFName returns the raw internal FName. + // Get the FName of anything that has an FName. This + // is here because having a consistent way to get the FName + // of something makes it possible to write search templates + // that work on any of these types. // //////////////////////////////////////////////////////// @@ -64,10 +58,45 @@ public: static FName GetFName(const FBPVariableDescription &Var) { return Var.VarName; } static FName GetFName(const UMaterialExpression *Expression) { return Expression->GetFName(); } static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); } + static FName GetFName(const FWingProperty &Prop); static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; } static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; } static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); } + //////////////////////////////////////////////////////// + // + // When searching an array of structs, you want to + // return a pointer to one of the structs. When + // searching an array of pointers, you want to return + // one of the pointers. These helpers make it easy for + // the search templates below to do that. + // + //////////////////////////////////////////////////////// + + template static const T* EltAsPtr(const TArray &A, const T &Elt) { return &Elt; } + template static T* EltAsPtr(TArray &A, T &Elt) { return &Elt; } + template static T* EltAsPtr(const TArray &A, T* &Elt) { return Elt; } + template static T* EltAsPtr(TArray &A, T* &Elt) { return Elt; } + template static const T* EltAsPtr(const TArray &A, const T* &Elt) { return Elt; } + template static const T* EltAsPtr(TArray &A, const T* &Elt) { return Elt; } + + //////////////////////////////////////////////////////// + // + // Search Templates. Search an array for an item with a + // given name. Works correctly no matter how a name is + // expressed: eg, "A+B" finds the same results as + // "A+B". Handles failure cases correctly: two + // items with the same name, no such item, and malformed + // names. In case of failure, sends readable and + // informative error messages to the AI Agent. + // + // Try to direct all searching through these templates, + // given the amount of care that was put into ensuring + // their correctness and the quality of their error + // reporting. + // + //////////////////////////////////////////////////////// + template static auto FindOneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind) { @@ -103,32 +132,13 @@ public: return FindNoneWithInternalID(InternalID, Array, Kind); } - //////////////////////////////////////////////////////// - // - // GetPathName - // - // For objects whose FormatName returns a path name, - // GetPathName returns the raw path. - // - //////////////////////////////////////////////////////// - - static FString GetPathName(const UWorld *World) { return World->GetPathName(); } - static FString GetPathName(const UMaterial *Material) { return Material->GetPathName(); } - static FString GetPathName(const UMaterialInstance *MI) { return MI->GetPathName(); } - static FString GetPathName(const UMaterialFunction *MF) { return MF->GetPathName(); } - static FString GetPathName(const UStaticMesh *Mesh) { return Mesh->GetPathName(); } - static FString GetPathName(const USkeletalMesh *Mesh) { return Mesh->GetPathName(); } - static FString GetPathName(const UAnimSequence *Anim) { return Anim->GetPathName(); } - static FString GetPathName(const UTexture *Texture) { return Texture->GetPathName(); } - //////////////////////////////////////////////////////// // // Name Formatting // - // The goal here is to centralize the code that outputs - // names, and have everybody use it, so that names are - // used consistently. When an identifier is output by - // FormatName, it's always an ExternalID. + // Get the name of something, and format it for transmission + // to the AI Agent. These correctly handle all escaping and + // quoting. // //////////////////////////////////////////////////////// @@ -161,54 +171,9 @@ public: static FString FormatName(const UWingComponentReference *Ref); static FString FormatName(const UWidget *Widget); - //////////////////////////////////////////////////////// - // Identifies - // - // Return true if the name matches the formatted name - // of the object, using the formatname routines above. - //////////////////////////////////////////////////////// - - template - static bool Identifies(const FString &ExternalID, T&& Obj) - { - return FormatName(std::forward(Obj)).Equals(ExternalID, ESearchCase::IgnoreCase); - } //////////////////////////////////////////////////////// - - - template - static T* FindExactlyOneNamed(const FString &Name, const TArray &Array, const TCHAR *Kind) - { - int Count = 0; - T* Result = nullptr; - for (T* Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = Elt; } - if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr; - return Result; - } - - template>> - static T* FindExactlyOneNamed(const FString &Name, TArray &Array, const TCHAR *Kind) - { - int Count = 0; - T* Result = nullptr; - for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; } - if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr; - return Result; - } - - template - static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array, const TCHAR *Kind) - { - for (const T& Elt: Array) if (Identifies(Name, Elt)) - { - return CheckExactlyNoneNamed(1, Kind, Name); - } - return true; - } - - //////////////////////////////////////////////////////// - static FString ExternalizeID(const FString& InternalID); + // static FString ExternalizeID(const FString& InternalID); static FString ExternalizeID(FName Name); static FName CheckInternalizeID(const FString &ExternalID);