Added the plugins to the GIT
26
Plugins/ProceduralDungeon/ProceduralDungeon.uplugin
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"Version": 1,
|
||||||
|
"VersionName": "2.0.0",
|
||||||
|
"FriendlyName": "ProceduralDungeon",
|
||||||
|
"Description": "Create procedural dungeons like \"The Binding of Isaac\" or \"Rogue Legacy\" but in 3D.\r\nYou can define your own generation rules.",
|
||||||
|
"Category": "Procedural",
|
||||||
|
"CreatedBy": "Ben Pyton",
|
||||||
|
"CreatedByURL": "https://github.com/BenPyton",
|
||||||
|
"DocsURL": "https://github.com/BenPyton/ProceduralDungeon/wiki",
|
||||||
|
"MarketplaceURL": "",
|
||||||
|
"SupportURL": "https://github.com/BenPyton/ProceduralDungeon/issues",
|
||||||
|
"EngineVersion": "4.27.0",
|
||||||
|
"CanContainContent": false,
|
||||||
|
"Installed": true,
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "ProceduralDungeon",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "PreDefault",
|
||||||
|
"WhitelistPlatforms": [
|
||||||
|
"Win64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
Plugins/ProceduralDungeon/Resources/Icon128.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Door.h"
|
||||||
|
#include "Room.h"
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
|
||||||
|
// Sets default values
|
||||||
|
ADoor::ADoor()
|
||||||
|
{
|
||||||
|
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
|
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void ADoor::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
|
||||||
|
bLocked = !bAlwaysUnlocked &&
|
||||||
|
((RoomA == nullptr || (RoomA->GetLevelScript() != nullptr && RoomA->GetLevelScript()->IsLocked))
|
||||||
|
|| (RoomB == nullptr || (RoomB->GetLevelScript() != nullptr && RoomB->GetLevelScript()->IsLocked)));
|
||||||
|
|
||||||
|
SetActorHiddenInGame( !bAlwaysVisible &&
|
||||||
|
(RoomA == nullptr || (RoomA->GetLevelScript() != nullptr && RoomA->GetLevelScript()->IsHidden))
|
||||||
|
&& (RoomB == nullptr || (RoomB->GetLevelScript() != nullptr && RoomB->GetLevelScript()->IsHidden)));
|
||||||
|
|
||||||
|
if (bLocked != bPrevLocked)
|
||||||
|
{
|
||||||
|
if (bLocked)
|
||||||
|
{
|
||||||
|
CloseDoor();
|
||||||
|
OnDoorLock();
|
||||||
|
OnDoorLock_BP();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnDoorUnlock();
|
||||||
|
OnDoorUnlock_BP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bPrevLocked = bLocked;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
DrawDebug(GetWorld());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADoor::OpenDoor()
|
||||||
|
{
|
||||||
|
if (!bIsOpen && !bLocked)
|
||||||
|
{
|
||||||
|
bIsOpen = true;
|
||||||
|
OnDoorOpen();
|
||||||
|
OnDoorOpen_BP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADoor::CloseDoor()
|
||||||
|
{
|
||||||
|
if (bIsOpen)
|
||||||
|
{
|
||||||
|
bIsOpen = false;
|
||||||
|
OnDoorClose();
|
||||||
|
OnDoorClose_BP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADoor::SetConnectingRooms(URoom * _RoomA, URoom * _RoomB)
|
||||||
|
{
|
||||||
|
RoomA = _RoomA;
|
||||||
|
RoomB = _RoomB;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADoor::DrawDebug(UWorld* World, FIntVector DoorCell, EDoorDirection DoorRot, FTransform Transform)
|
||||||
|
{
|
||||||
|
if (URoom::DrawDebug())
|
||||||
|
{
|
||||||
|
FVector DoorSize = URoom::DoorSize();
|
||||||
|
FIntVector rot = URoom::GetDirection(DoorRot == EDoorDirection::NbDirection ? EDoorDirection::North : DoorRot);
|
||||||
|
FVector pos = URoom::GetRealDoorPosition(DoorCell, DoorRot) + FVector(0, 0, DoorSize.Z * 0.5f);
|
||||||
|
pos = Transform.TransformPosition(pos);
|
||||||
|
|
||||||
|
// Arrow
|
||||||
|
DrawDebugDirectionalArrow(World, pos, pos + Transform.GetRotation() * FVector(rot) * 300, 300, FColor::Blue);
|
||||||
|
|
||||||
|
// Door frame
|
||||||
|
FIntVector scale = URoom::Rotate(FIntVector(DoorSize * 0.5f), DoorRot);
|
||||||
|
DrawDebugBox(World, pos, FVector(scale), Transform.GetRotation(), FColor::Blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,546 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DungeonGenerator.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Engine.h"
|
||||||
|
#include "NavigationSystem.h"
|
||||||
|
#include "ProceduralLevelStreaming.h"
|
||||||
|
#include "RoomData.h"
|
||||||
|
#include "Room.h"
|
||||||
|
#include "Door.h"
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
#include "ProceduralDungeon.h"
|
||||||
|
#include "ProceduralDungeonSettings.h"
|
||||||
|
#include "ProceduralDungeonLog.h"
|
||||||
|
#include "QueueOrStack.h"
|
||||||
|
|
||||||
|
// Sets default values
|
||||||
|
ADungeonGenerator::ADungeonGenerator()
|
||||||
|
{
|
||||||
|
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
|
GenerationType = EGenerationType::DFS;
|
||||||
|
SeedType = ESeedType::Random;
|
||||||
|
Seed = 123456789; // default Seed
|
||||||
|
|
||||||
|
bAlwaysRelevant = true;
|
||||||
|
bReplicates = true;
|
||||||
|
NetPriority = 10.0f;
|
||||||
|
NetUpdateFrequency = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the game starts or when spawned
|
||||||
|
void ADungeonGenerator::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::EndPlay(EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
UnloadAllRooms();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void ADungeonGenerator::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
OnStateTick(CurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::Generate()
|
||||||
|
{
|
||||||
|
// Do it only on server, do nothing on clients
|
||||||
|
if (HasAuthority())
|
||||||
|
{
|
||||||
|
if (SeedType == ESeedType::Random)
|
||||||
|
{
|
||||||
|
Random.GenerateNewSeed();
|
||||||
|
Seed = Random.GetCurrentSeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginGeneration(Seed);
|
||||||
|
|
||||||
|
if (SeedType == ESeedType::AutoIncrement)
|
||||||
|
{
|
||||||
|
Seed += 123456;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::BeginGeneration_Implementation(uint32 GenerationSeed)
|
||||||
|
{
|
||||||
|
Seed = GenerationSeed;
|
||||||
|
Random.Initialize(Seed);
|
||||||
|
LogInfo(FString::Printf(TEXT("Seed: %d"), Seed));
|
||||||
|
SetState(EGenerationState::Unload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::CreateDungeon()
|
||||||
|
{
|
||||||
|
IsInit = false;
|
||||||
|
NbInitRoom = 0;
|
||||||
|
int TriesLeft = MaxTry;
|
||||||
|
|
||||||
|
// generate level until there IsValidDungeon return true
|
||||||
|
do {
|
||||||
|
TriesLeft--;
|
||||||
|
|
||||||
|
// Reset generation data
|
||||||
|
UProceduralLevelStreaming::UniqueLevelInstanceId = 0;
|
||||||
|
ARoomLevel::Count = 0;
|
||||||
|
DispatchGenerationInit();
|
||||||
|
|
||||||
|
// Create the first room
|
||||||
|
RoomList.Empty();
|
||||||
|
|
||||||
|
URoomData* def = ChooseFirstRoomData();
|
||||||
|
if(!IsValid(def))
|
||||||
|
{
|
||||||
|
LogError("ChooseFirstRoomData returned null.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
URoom* root = NewObject<URoom>();
|
||||||
|
root->Init(def);
|
||||||
|
RoomList.Add(root);
|
||||||
|
|
||||||
|
// Create the list with the correct mode (depth or breadth)
|
||||||
|
TQueueOrStack<URoom*>::EMode listMode;
|
||||||
|
switch(GenerationType)
|
||||||
|
{
|
||||||
|
case EGenerationType::DFS:
|
||||||
|
listMode = TQueueOrStack<URoom*>::EMode::STACK;
|
||||||
|
break;
|
||||||
|
case EGenerationType::BFS:
|
||||||
|
listMode = TQueueOrStack<URoom*>::EMode::QUEUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the list of rooms
|
||||||
|
TQueueOrStack<URoom*> roomStack(listMode);
|
||||||
|
roomStack.Push(root);
|
||||||
|
URoom* currentRoom = nullptr;
|
||||||
|
URoom* newRoom = nullptr;
|
||||||
|
while(ContinueToAddRoom() && !roomStack.IsEmpty())
|
||||||
|
{
|
||||||
|
currentRoom = roomStack.Pop();
|
||||||
|
check(IsValid(currentRoom)); // currentRoom should always be valid
|
||||||
|
for(URoom* room : AddNewRooms(*currentRoom))
|
||||||
|
{
|
||||||
|
roomStack.Push(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (TriesLeft > 0 && !IsValidDungeon());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ADungeonGenerator::InstantiateRoom(URoom* Room)
|
||||||
|
{
|
||||||
|
// Instantiate room
|
||||||
|
Room->Instantiate(GetWorld());
|
||||||
|
|
||||||
|
for (int i = 0; i < Room->GetConnectionCount(); i++)
|
||||||
|
{
|
||||||
|
// Get next room
|
||||||
|
URoom* r = Room->GetConnection(i).Get();
|
||||||
|
FIntVector DoorCell = Room->GetDoorWorldPosition(i);
|
||||||
|
EDoorDirection DoorRot = Room->GetDoorWorldOrientation(i);
|
||||||
|
int j = Room->GetOtherDoorIndex(i);
|
||||||
|
|
||||||
|
// Don't instantiate door if it's the parent
|
||||||
|
if (!Room->IsDoorInstanced(i))
|
||||||
|
{
|
||||||
|
TSubclassOf<ADoor> DoorClass = ChooseDoor(Room->GetRoomData(), nullptr != r ? r->GetRoomData() : nullptr);
|
||||||
|
|
||||||
|
if (DoorClass != nullptr)
|
||||||
|
{
|
||||||
|
FVector InstanceDoorPos = URoom::GetRealDoorPosition(DoorCell, DoorRot);
|
||||||
|
FRotator InstanceDoorRot = FRotator(0, -90 * (int8)DoorRot, 0);
|
||||||
|
ADoor* Door = GetWorld()->SpawnActor<ADoor>(DoorClass, InstanceDoorPos, InstanceDoorRot);
|
||||||
|
|
||||||
|
if (nullptr != Door)
|
||||||
|
{
|
||||||
|
DoorList.Add(Door);
|
||||||
|
Door->SetConnectingRooms(Room, r);
|
||||||
|
Room->SetDoorInstance(i, Door);
|
||||||
|
if(IsValid(r))
|
||||||
|
{
|
||||||
|
r->SetDoorInstance(j, Door);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogError("Failed to spawn Door, make sure you set door actor to always spawning.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<URoom*> ADungeonGenerator::AddNewRooms(URoom& ParentRoom)
|
||||||
|
{
|
||||||
|
TArray<URoom*> newRooms;
|
||||||
|
int nbDoor = ParentRoom.GetRoomData()->GetNbDoor();
|
||||||
|
URoom* newRoom = nullptr;
|
||||||
|
for(int i = 0; i < nbDoor; ++i)
|
||||||
|
{
|
||||||
|
if(ParentRoom.IsConnected(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int nbTries = MaxRoomTry;
|
||||||
|
// Try to place a new room
|
||||||
|
do
|
||||||
|
{
|
||||||
|
nbTries--;
|
||||||
|
URoomData* def = ChooseNextRoomData(ParentRoom.GetRoomData());
|
||||||
|
if(!IsValid(def))
|
||||||
|
{
|
||||||
|
LogError("ChooseNextRoomData returned null.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create room from roomdef and set connections with current room
|
||||||
|
newRoom = NewObject<URoom>();
|
||||||
|
newRoom->Init(def);
|
||||||
|
int doorIndex = def->RandomDoor ? Random.RandRange(0, newRoom->GetRoomData()->GetNbDoor() - 1) : 0;
|
||||||
|
|
||||||
|
// Place the room at its world position with the correct rotation
|
||||||
|
EDoorDirection parentDoorDir = ParentRoom.GetDoorWorldOrientation(i);
|
||||||
|
FIntVector newRoomPos = ParentRoom.GetDoorWorldPosition(i) + URoom::GetDirection(parentDoorDir);
|
||||||
|
newRoom->SetPositionAndRotationFromDoor(doorIndex, newRoomPos, URoom::Opposite(parentDoorDir));
|
||||||
|
|
||||||
|
// Test if it fit in the place
|
||||||
|
if(!URoom::Overlap(*newRoom, RoomList))
|
||||||
|
{
|
||||||
|
// connect the doors to all possible existing rooms
|
||||||
|
URoom::Connect(*newRoom, doorIndex, ParentRoom, i);
|
||||||
|
if(URoom::CanLoop())
|
||||||
|
{
|
||||||
|
newRoom->TryConnectToExistingDoors(RoomList);
|
||||||
|
}
|
||||||
|
RoomList.Add(newRoom);
|
||||||
|
newRooms.Add(newRoom);
|
||||||
|
DispatchRoomAdded(newRoom->GetRoomData());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newRoom = nullptr;
|
||||||
|
}
|
||||||
|
} while(nbTries > 0 && newRoom == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ADungeonGenerator::LoadAllRooms()
|
||||||
|
{
|
||||||
|
// When a level is correct, load all rooms
|
||||||
|
for (int i = 0; i < RoomList.Num(); i++)
|
||||||
|
{
|
||||||
|
InstantiateRoom(RoomList[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::UnloadAllRooms()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < DoorList.Num(); i++)
|
||||||
|
{
|
||||||
|
DoorList[i]->Destroy();
|
||||||
|
}
|
||||||
|
DoorList.Empty();
|
||||||
|
|
||||||
|
for (int i = 0; i < RoomList.Num(); i++)
|
||||||
|
{
|
||||||
|
RoomList[i]->Destroy(GetWorld());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =======================================
|
||||||
|
* State Machine
|
||||||
|
* =======================================
|
||||||
|
*/
|
||||||
|
void ADungeonGenerator::SetState(EGenerationState NewState)
|
||||||
|
{
|
||||||
|
OnStateEnd(CurrentState);
|
||||||
|
CurrentState = NewState;
|
||||||
|
OnStateBegin(CurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::OnStateBegin(EGenerationState State)
|
||||||
|
{
|
||||||
|
switch (State)
|
||||||
|
{
|
||||||
|
case EGenerationState::Unload:
|
||||||
|
LogInfo("======= Begin Unload All Levels =======");
|
||||||
|
UnloadAllRooms();
|
||||||
|
NbUnloadedRoom = 0;
|
||||||
|
break;
|
||||||
|
case EGenerationState::Generation:
|
||||||
|
DispatchPreGeneration();
|
||||||
|
LogInfo("======= Begin Map Generation =======");
|
||||||
|
CreateDungeon();
|
||||||
|
break;
|
||||||
|
case EGenerationState::Load:
|
||||||
|
LogInfo("======= Begin Load All Levels =======");
|
||||||
|
LoadAllRooms();
|
||||||
|
NbLoadedRoom = 0;
|
||||||
|
break;
|
||||||
|
case EGenerationState::Initialization:
|
||||||
|
LogInfo("======= Begin Init All Levels =======");
|
||||||
|
LogInfo(FString::Printf(TEXT("Nb Room To Initialize: %d"), RoomList.Num()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::OnStateTick(EGenerationState State)
|
||||||
|
{
|
||||||
|
int tmp = 0;
|
||||||
|
switch (State)
|
||||||
|
{
|
||||||
|
case EGenerationState::Unload:
|
||||||
|
|
||||||
|
// Count nb level loaded
|
||||||
|
for (int i = 0; i < RoomList.Num(); i++)
|
||||||
|
{
|
||||||
|
if (RoomList[i]->IsInstanceUnloaded())
|
||||||
|
{
|
||||||
|
tmp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Change state when all levels are loaded
|
||||||
|
if (tmp == RoomList.Num())
|
||||||
|
{
|
||||||
|
SetState(EGenerationState::Generation);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EGenerationState::Generation:
|
||||||
|
SetState(EGenerationState::Load);
|
||||||
|
break;
|
||||||
|
case EGenerationState::Load:
|
||||||
|
|
||||||
|
// Count nb level loaded
|
||||||
|
for (int i = 0; i < RoomList.Num(); i++)
|
||||||
|
{
|
||||||
|
if (RoomList[i]->IsInstanceLoaded())
|
||||||
|
{
|
||||||
|
tmp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Change state when all levels are loaded
|
||||||
|
if (tmp == RoomList.Num())
|
||||||
|
{
|
||||||
|
SetState(EGenerationState::Initialization);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EGenerationState::Initialization:
|
||||||
|
// While initialization isn't done, try to initialize all rooms
|
||||||
|
if (!IsInit)
|
||||||
|
{
|
||||||
|
IsInit = true;
|
||||||
|
for (URoom* room : RoomList)
|
||||||
|
{
|
||||||
|
ARoomLevel* script = room->GetLevelScript();
|
||||||
|
|
||||||
|
if (nullptr != script)
|
||||||
|
{
|
||||||
|
IsInit &= script->IsInit;
|
||||||
|
if (!script->IsInit && !script->PendingInit)
|
||||||
|
{
|
||||||
|
NbInitRoom++;
|
||||||
|
script->Room = nullptr;
|
||||||
|
script->Init(room);
|
||||||
|
|
||||||
|
LogInfo(FString::Printf(TEXT("Room Initialization: %d/%d"), NbInitRoom, RoomList.Num()), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsInit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsInit)
|
||||||
|
{
|
||||||
|
SetState(EGenerationState::None);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::OnStateEnd(EGenerationState State)
|
||||||
|
{
|
||||||
|
FTimerHandle handle;
|
||||||
|
UNavigationSystemV1* nav = nullptr;
|
||||||
|
switch (State)
|
||||||
|
{
|
||||||
|
case EGenerationState::Unload:
|
||||||
|
RoomList.Empty();
|
||||||
|
GetWorld()->FlushLevelStreaming();
|
||||||
|
GEngine->ForceGarbageCollection(true);
|
||||||
|
LogInfo("======= End Unload All Levels =======");
|
||||||
|
break;
|
||||||
|
case EGenerationState::Generation:
|
||||||
|
LogInfo("======= End Map Generation =======");
|
||||||
|
break;
|
||||||
|
case EGenerationState::Load:
|
||||||
|
LogInfo("======= End Load All Levels =======");
|
||||||
|
break;
|
||||||
|
case EGenerationState::Initialization:
|
||||||
|
LogInfo("======= End Init All Levels =======");
|
||||||
|
|
||||||
|
// Try to rebuild the navmesh
|
||||||
|
nav = UNavigationSystemV1::GetCurrent(GetWorld());
|
||||||
|
if (nullptr != nav)
|
||||||
|
{
|
||||||
|
LogInfo("Rebuild navmesh");
|
||||||
|
nav->CancelBuild();
|
||||||
|
nav->Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke Post Generation Event when initialization is done
|
||||||
|
DispatchPostGeneration();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
URoomData* ADungeonGenerator::ChooseFirstRoomData_Implementation()
|
||||||
|
{
|
||||||
|
LogError("Error: ChooseFirstRoomData not implemented");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
URoomData* ADungeonGenerator::ChooseNextRoomData_Implementation(URoomData* CurrentRoom)
|
||||||
|
{
|
||||||
|
LogError("Error: ChooseNextRoomData not implemented");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSubclassOf<ADoor> ADungeonGenerator::ChooseDoor_Implementation(URoomData* CurrentRoom, URoomData* NextRoom)
|
||||||
|
{
|
||||||
|
LogError("Error: ChooseDoor not implemented");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADungeonGenerator::IsValidDungeon_Implementation()
|
||||||
|
{
|
||||||
|
LogError("Error: IsValidDungeon not implemented");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADungeonGenerator::ContinueToAddRoom_Implementation()
|
||||||
|
{
|
||||||
|
LogError("Error: ContinueToAddRoom not implemented");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::DispatchPreGeneration()
|
||||||
|
{
|
||||||
|
OnPreGeneration();
|
||||||
|
OnPreGeneration_BP();
|
||||||
|
OnPreGenerationEvent.Broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::DispatchPostGeneration()
|
||||||
|
{
|
||||||
|
OnPostGeneration();
|
||||||
|
OnPostGeneration_BP();
|
||||||
|
OnPostGenerationEvent.Broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::DispatchGenerationInit()
|
||||||
|
{
|
||||||
|
OnGenerationInit();
|
||||||
|
OnGenerationInit_BP();
|
||||||
|
OnGenerationInitEvent.Broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADungeonGenerator::DispatchRoomAdded(URoomData* NewRoom)
|
||||||
|
{
|
||||||
|
OnRoomAdded(NewRoom);
|
||||||
|
OnRoomAdded_BP(NewRoom);
|
||||||
|
OnRoomAddedEvent.Broadcast(NewRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
URoomData* ADungeonGenerator::GetRandomRoomData(TArray<URoomData*> RoomDataArray)
|
||||||
|
{
|
||||||
|
int n = Random.RandRange(0, RoomDataArray.Num() - 1);
|
||||||
|
return RoomDataArray[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
URoom* ADungeonGenerator::GetRoomAt(FIntVector RoomCell)
|
||||||
|
{
|
||||||
|
return URoom::GetRoomAt(RoomCell, RoomList);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADungeonGenerator::HasAlreadyRoomData(URoomData* RoomData)
|
||||||
|
{
|
||||||
|
return CountRoomData(RoomData) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADungeonGenerator::HasAlreadyOneRoomDataFrom(TArray<URoomData*> RoomDataList)
|
||||||
|
{
|
||||||
|
return CountTotalRoomData(RoomDataList) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ADungeonGenerator::CountRoomData(URoomData* RoomData)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for(int i = 0; i < RoomList.Num(); i++)
|
||||||
|
{
|
||||||
|
if(RoomList[i]->GetRoomData() == RoomData)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ADungeonGenerator::CountTotalRoomData(TArray<URoomData*> RoomDataList)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for(int i = 0; i < RoomList.Num(); i++)
|
||||||
|
{
|
||||||
|
if(RoomDataList.Contains(RoomList[i]->GetRoomData()))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ProceduralDungeon.h"
|
||||||
|
#include "Developer/Settings/Public/ISettingsModule.h"
|
||||||
|
#include "Developer/Settings/Public/ISettingsSection.h"
|
||||||
|
#include "ProceduralDungeonSettings.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FProceduralDungeonModule"
|
||||||
|
|
||||||
|
void FProceduralDungeonModule::StartupModule()
|
||||||
|
{
|
||||||
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||||
|
RegisterSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FProceduralDungeonModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||||
|
// we call this function before unloading the module.
|
||||||
|
if (UObjectInitialized())
|
||||||
|
{
|
||||||
|
UnregisterSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FProceduralDungeonModule::RegisterSettings()
|
||||||
|
{
|
||||||
|
// Registering some settings is just a matter of exposing the default UObject of
|
||||||
|
// your desired class, feel free to add here all those settings you want to expose
|
||||||
|
// to your LDs or artists.
|
||||||
|
|
||||||
|
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||||
|
{
|
||||||
|
// Register the settings
|
||||||
|
ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "Procedural Dungeon",
|
||||||
|
LOCTEXT("RuntimeGeneralSettingsName", "Procedural Dungeon"),
|
||||||
|
LOCTEXT("RuntimeGeneralSettingsDescription", "Configuration for the Procedural Dungeon plugin"),
|
||||||
|
GetMutableDefault<UProceduralDungeonSettings>()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register the save handler to your settings, you might want to use it to
|
||||||
|
// validate those or just act to settings changes.
|
||||||
|
if (SettingsSection.IsValid())
|
||||||
|
{
|
||||||
|
SettingsSection->OnModified().BindRaw(this, &FProceduralDungeonModule::HandleSettingsSaved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FProceduralDungeonModule::UnregisterSettings()
|
||||||
|
{
|
||||||
|
// Ensure to unregister all of your registered settings here, hot-reload would
|
||||||
|
// otherwise yield unexpected results.
|
||||||
|
|
||||||
|
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||||
|
{
|
||||||
|
SettingsModule->UnregisterSettings("Project", "Plugins", "Procedural Dungeon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback for when the settings were saved.
|
||||||
|
bool FProceduralDungeonModule::HandleSettingsSaved()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
bool ResaveSettings = false;
|
||||||
|
|
||||||
|
// You can put any validation code in here and resave the settings in case an invalid
|
||||||
|
// value has been entered
|
||||||
|
|
||||||
|
if (ResaveSettings)
|
||||||
|
{
|
||||||
|
Settings->SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FProceduralDungeonModule, ProceduralDungeon)
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ProceduralDungeonLog.h"
|
||||||
|
#include "ProceduralDungeonSettings.h"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(LogProceduralDungeon);
|
||||||
|
|
||||||
|
bool ShowLogsOnScreen(float& _duration)
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
_duration = Settings->PrintDebugDuration;
|
||||||
|
return Settings->OnScreenPrintDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogInfo(FString message, bool showOnScreen)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Log, TEXT("%s"), *message);
|
||||||
|
float duration;
|
||||||
|
if(showOnScreen && ShowLogsOnScreen(duration))
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, duration, FColor::White, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWarning(FString message, bool showOnScreen)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Warning, TEXT("%s"), *message);
|
||||||
|
float duration;
|
||||||
|
if(showOnScreen && ShowLogsOnScreen(duration))
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, duration, FColor::Yellow, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogError(FString message, bool showOnScreen)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Error, TEXT("%s"), *message);
|
||||||
|
float duration;
|
||||||
|
if(showOnScreen && ShowLogsOnScreen(duration))
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, duration, FColor::Red, message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ProceduralDungeonSettings.h"
|
||||||
|
|
||||||
|
UProceduralDungeonSettings::UProceduralDungeonSettings(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
RoomUnit = FVector(1000, 1000, 400);
|
||||||
|
DoorSize = FVector(40, 640, 400);
|
||||||
|
DoorOffset = 0.0f;
|
||||||
|
OcclusionCulling = true;
|
||||||
|
DrawDebug = true;
|
||||||
|
OnScreenPrintDebug = false;
|
||||||
|
PrintDebugDuration = 60.0f;
|
||||||
|
CanLoop = true;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
|
@ -0,0 +1,172 @@
|
|||||||
|
#include "ProceduralLevelStreaming.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Engine/Level.h"
|
||||||
|
#include "Engine.h"
|
||||||
|
#include "RoomData.h"
|
||||||
|
#include "ProceduralDungeon.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "World"
|
||||||
|
|
||||||
|
int32 UProceduralLevelStreaming::UniqueLevelInstanceId = 0;
|
||||||
|
|
||||||
|
UProceduralLevelStreaming::UProceduralLevelStreaming(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UProceduralLevelStreaming::PostLoad()
|
||||||
|
{
|
||||||
|
Super::PostLoad();
|
||||||
|
|
||||||
|
// Initialize startup state of the streaming level
|
||||||
|
if (GetWorld()->IsGameWorld())
|
||||||
|
{
|
||||||
|
bShouldBeLoaded = bInitiallyLoaded;
|
||||||
|
SetShouldBeVisible(bInitiallyVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UProceduralLevelStreaming::SetShouldBeLoaded(const bool bInShouldBeLoaded)
|
||||||
|
{
|
||||||
|
if (bInShouldBeLoaded != bShouldBeLoaded)
|
||||||
|
{
|
||||||
|
bShouldBeLoaded = bInShouldBeLoaded;
|
||||||
|
if (UWorld* World = GetWorld())
|
||||||
|
{
|
||||||
|
World->UpdateStreamingLevelShouldBeConsidered(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UProceduralLevelStreaming::OnLevelDynamicUnloaded()
|
||||||
|
{
|
||||||
|
//UE_LOG(LogProceduralDungeon, Warning, TEXT("End unload level: %s"), *GetWorldAssetPackageName());
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (nullptr != World)
|
||||||
|
{
|
||||||
|
//UE_LOG(LogProceduralDungeon, Warning, TEXT("Remove instance from world"));
|
||||||
|
World->RemoveStreamingLevel(this);
|
||||||
|
}
|
||||||
|
bIsUnloaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UProceduralLevelStreaming* UProceduralLevelStreaming::LoadLevelInstance(UObject* WorldContextObject, const FString LevelName, const FVector Location, const FRotator Rotation, bool& bOutSuccess)
|
||||||
|
{
|
||||||
|
bOutSuccess = false;
|
||||||
|
UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether requested map exists, this could be very slow if LevelName is a short package name
|
||||||
|
FString LongPackageName;
|
||||||
|
bOutSuccess = FPackageName::SearchForPackageOnDisk(LevelName, &LongPackageName);
|
||||||
|
if (!bOutSuccess)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadLevelInstance_Internal(World, LongPackageName, Location, Rotation, bOutSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
UProceduralLevelStreaming* UProceduralLevelStreaming::LoadLevelInstanceBySoftObjectPtr(UObject* WorldContextObject, const TSoftObjectPtr<UWorld> Level, const FVector Location, const FRotator Rotation, bool& bOutSuccess)
|
||||||
|
{
|
||||||
|
bOutSuccess = false;
|
||||||
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether requested map exists, this could be very slow if LevelName is a short package name
|
||||||
|
if (Level.IsNull())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadLevelInstance_Internal(World, Level.GetLongPackageName(), Location, Rotation, bOutSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
UProceduralLevelStreaming * UProceduralLevelStreaming::Load(UObject * WorldContextObject, URoomData * Data, FVector Location, FRotator Rotation)
|
||||||
|
{
|
||||||
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||||
|
if (nullptr == World)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Error, TEXT("Failed to load LevelStreamingDynamic: World is null"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (nullptr == Data)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Error, TEXT("Failed to load LevelStreamingDynamic: Data is null"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
UProceduralLevelStreaming* Instance = UProceduralLevelStreaming::LoadLevelInstanceBySoftObjectPtr(World, Data->Level, Location, Rotation, success);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Error, TEXT("Failed to load LevelStreamingDynamic: Unknown reason"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UProceduralLevelStreaming::Unload(UObject * WorldContextObject, UProceduralLevelStreaming * Instance)
|
||||||
|
{
|
||||||
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||||
|
if (nullptr == World)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Error, TEXT("Failed to unload LevelStreamingDynamic: World is null"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nullptr == Instance)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Error, TEXT("Failed to unload LevelStreamingDynamic: Instance is null"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix to remove
|
||||||
|
FString LevelName = Instance->GetWorldAssetPackageName();
|
||||||
|
const FString PackagePath = FPackageName::GetLongPackagePath(LevelName);
|
||||||
|
FString LevelPackageName = PackagePath + TEXT("/") + World->StreamingLevelsPrefix;
|
||||||
|
|
||||||
|
LevelName.RemoveFromStart(LevelPackageName, ESearchCase::IgnoreCase);
|
||||||
|
FLatentActionInfo LatentInfo(0, 2222, TEXT("OnLevelDynamicUnloaded"), Instance);
|
||||||
|
UGameplayStatics::UnloadStreamLevel(World, *LevelName, LatentInfo, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
UProceduralLevelStreaming* UProceduralLevelStreaming::LoadLevelInstance_Internal(UWorld* World, const FString& LongPackageName, const FVector Location, const FRotator Rotation, bool& bOutSuccess)
|
||||||
|
{
|
||||||
|
// Create Unique Name for sub-level package
|
||||||
|
const FString ShortPackageName = FPackageName::GetShortName(LongPackageName);
|
||||||
|
const FString PackagePath = FPackageName::GetLongPackagePath(LongPackageName);
|
||||||
|
FString UniqueLevelPackageName = PackagePath + TEXT("/") + World->StreamingLevelsPrefix + ShortPackageName;
|
||||||
|
|
||||||
|
//UE_LOG(LogProceduralDungeon, Warning, TEXT("Unique Id: %d"), UniqueLevelInstanceId);
|
||||||
|
|
||||||
|
UniqueLevelPackageName += TEXT("_LevelInstance_") + FString::FromInt(++UniqueLevelInstanceId);
|
||||||
|
|
||||||
|
// Setup streaming level object that will load specified map
|
||||||
|
UProceduralLevelStreaming* StreamingLevel = NewObject<UProceduralLevelStreaming>(World, UProceduralLevelStreaming::StaticClass(), NAME_None, RF_Transient, NULL);
|
||||||
|
StreamingLevel->SetWorldAssetByPackageName(FName(*UniqueLevelPackageName));
|
||||||
|
StreamingLevel->LevelColor = FColor::MakeRandomColor();
|
||||||
|
StreamingLevel->SetShouldBeLoaded(true);
|
||||||
|
StreamingLevel->SetShouldBeVisible(true);
|
||||||
|
StreamingLevel->bShouldBlockOnLoad = false;
|
||||||
|
StreamingLevel->bInitiallyLoaded = true;
|
||||||
|
StreamingLevel->bInitiallyVisible = true;
|
||||||
|
// Transform
|
||||||
|
StreamingLevel->LevelTransform = FTransform(Rotation, Location);
|
||||||
|
// Map to Load
|
||||||
|
StreamingLevel->PackageNameToLoad = FName(*LongPackageName);
|
||||||
|
|
||||||
|
// Add the new level to world.
|
||||||
|
World->AddStreamingLevel(StreamingLevel);
|
||||||
|
|
||||||
|
bOutSuccess = true;
|
||||||
|
return StreamingLevel;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "QueueOrStack.h"
|
@ -0,0 +1,428 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Room.h"
|
||||||
|
#include "Door.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Engine.h"
|
||||||
|
#include "RoomData.h"
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
#include "ProceduralDungeonSettings.h"
|
||||||
|
#include "ProceduralDungeonLog.h"
|
||||||
|
|
||||||
|
void URoom::Init(URoomData* Data)
|
||||||
|
{
|
||||||
|
RoomData = Data;
|
||||||
|
Instance = nullptr;
|
||||||
|
Position = FIntVector(0,0,0);
|
||||||
|
Direction = EDoorDirection::North;
|
||||||
|
|
||||||
|
if (IsValid(RoomData))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RoomData->GetNbDoor(); i++)
|
||||||
|
{
|
||||||
|
Connections.Add(FRoomConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogError("No RoomData provided.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::IsConnected(int Index)
|
||||||
|
{
|
||||||
|
check(Index >= 0 && Index < Connections.Num());
|
||||||
|
return Connections[Index].OtherRoom != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::SetConnection(int Index, URoom* Room, int OtherIndex)
|
||||||
|
{
|
||||||
|
check(Index >= 0 && Index < Connections.Num());
|
||||||
|
Connections[Index].OtherRoom = Room;
|
||||||
|
Connections[Index].OtherDoorIndex = OtherIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
TWeakObjectPtr<URoom> URoom::GetConnection(int Index)
|
||||||
|
{
|
||||||
|
check(Index >= 0 && Index < Connections.Num());
|
||||||
|
return Connections[Index].OtherRoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
int URoom::GetFirstEmptyConnection()
|
||||||
|
{
|
||||||
|
for(int i = 0; i < Connections.Num(); ++i)
|
||||||
|
{
|
||||||
|
if(Connections[i].OtherRoom == nullptr)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::Instantiate(UWorld* World)
|
||||||
|
{
|
||||||
|
if (Instance == nullptr)
|
||||||
|
{
|
||||||
|
if(!IsValid(RoomData))
|
||||||
|
{
|
||||||
|
LogError("Failed to instantiate the room: it has no RoomData.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = UProceduralLevelStreaming::Load(World, RoomData, URoom::Unit() * FVector(Position), FRotator(0, -90 * (int)Direction, 0));
|
||||||
|
UE_LOG(LogProceduralDungeon, Log, TEXT("Load room Instance: %s"), nullptr != Instance ? *Instance->GetWorldAssetPackageName() : TEXT("Null"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogError("Failed to instantiate the room: it is already instanciated.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::Destroy(UWorld* World)
|
||||||
|
{
|
||||||
|
if (Instance != nullptr)
|
||||||
|
{
|
||||||
|
UE_LOG(LogProceduralDungeon, Log, TEXT("Unload room Instance: %s"), nullptr != Instance ? *Instance->GetWorldAssetPackageName() : TEXT("Null"));
|
||||||
|
|
||||||
|
ARoomLevel* script = GetLevelScript();
|
||||||
|
if (script != nullptr)
|
||||||
|
{
|
||||||
|
script->Room = nullptr;
|
||||||
|
script->Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
UProceduralLevelStreaming::Unload(World, Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARoomLevel* URoom::GetLevelScript()
|
||||||
|
{
|
||||||
|
if (Instance == nullptr || !IsValid(Instance))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return Cast<ARoomLevel>(Instance->GetLevelScriptActor());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::IsInstanceLoaded()
|
||||||
|
{
|
||||||
|
if(Instance == nullptr || !IsValid(Instance))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Instance->IsLevelLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom:: IsInstanceUnloaded()
|
||||||
|
{
|
||||||
|
if(Instance == nullptr || !IsValid(Instance))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Instance->IsLevelUnloaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
EDoorDirection URoom::GetDoorWorldOrientation(int DoorIndex)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
return Add(RoomData->Doors[DoorIndex].Direction, Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector URoom::GetDoorWorldPosition(int DoorIndex)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
return RoomToWorld(RoomData->Doors[DoorIndex].Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
int URoom::GetDoorIndexAt(FIntVector WorldPos, EDoorDirection WorldRot)
|
||||||
|
{
|
||||||
|
FIntVector localPos = WorldToRoom(WorldPos);
|
||||||
|
EDoorDirection localRot = WorldToRoom(WorldRot);
|
||||||
|
|
||||||
|
for(int i = 0; i < RoomData->Doors.Num(); ++i)
|
||||||
|
{
|
||||||
|
const FDoorDef door = RoomData->Doors[i];
|
||||||
|
if(door.Position == localPos && door.Direction == localRot)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::IsDoorInstanced(int DoorIndex)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
return IsValid(Connections[DoorIndex].DoorInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::SetDoorInstance(int DoorIndex, ADoor* Door)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
Connections[DoorIndex].DoorInstance = Door;
|
||||||
|
}
|
||||||
|
|
||||||
|
int URoom::GetOtherDoorIndex(int DoorIndex)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
return Connections[DoorIndex].OtherDoorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector URoom::WorldToRoom(FIntVector WorldPos)
|
||||||
|
{
|
||||||
|
return Rotate(WorldPos - Position, Sub(EDoorDirection::North, Direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector URoom::RoomToWorld(FIntVector RoomPos)
|
||||||
|
{
|
||||||
|
return Rotate(RoomPos, Direction) + Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDoorDirection URoom::WorldToRoom(EDoorDirection WorldRot)
|
||||||
|
{
|
||||||
|
return Sub(WorldRot, Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
EDoorDirection URoom::RoomToWorld(EDoorDirection RoomRot)
|
||||||
|
{
|
||||||
|
return Add(RoomRot, Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::SetRotationFromDoor(int DoorIndex, EDoorDirection WorldRot)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
Direction = Add(Sub(WorldRot, RoomData->Doors[DoorIndex].Direction), EDoorDirection::South);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::SetPositionFromDoor(int DoorIndex, FIntVector WorldPos)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
Position = WorldPos - RoomToWorld(RoomData->Doors[DoorIndex].Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::SetPositionAndRotationFromDoor(int DoorIndex, FIntVector WorldPos, EDoorDirection WorldRot)
|
||||||
|
{
|
||||||
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
||||||
|
Direction = Sub(WorldRot, RoomData->Doors[DoorIndex].Direction);
|
||||||
|
Position = WorldPos - RoomToWorld(RoomData->Doors[DoorIndex].Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool URoom::IsOccupied(FIntVector Cell)
|
||||||
|
{
|
||||||
|
FIntVector local = WorldToRoom(Cell);
|
||||||
|
return local.X >= 0 && local.X < RoomData->Size.X
|
||||||
|
&& local.Y >= 0 && local.Y < RoomData->Size.Y
|
||||||
|
&& local.Z >= 0 && local.Z < RoomData->Size.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::TryConnectToExistingDoors(TArray<URoom*>& RoomList)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < RoomData->GetNbDoor(); ++i)
|
||||||
|
{
|
||||||
|
EDoorDirection dir = GetDoorWorldOrientation(i);
|
||||||
|
FIntVector pos = GetDoorWorldPosition(i) + URoom::GetDirection(dir);
|
||||||
|
URoom* otherRoom = GetRoomAt(pos, RoomList);
|
||||||
|
|
||||||
|
if(IsValid(otherRoom))
|
||||||
|
{
|
||||||
|
int j = otherRoom->GetDoorIndexAt(pos, URoom::Opposite(dir));
|
||||||
|
if(j >= 0) // -1 if no door
|
||||||
|
{
|
||||||
|
Connect(*this, i, *otherRoom, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector Max(const FIntVector& A, const FIntVector& B)
|
||||||
|
{
|
||||||
|
return FIntVector(FMath::Max(A.X, B.X), FMath::Max(A.Y, B.Y), FMath::Max(A.Z, B.Z));
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector Min(const FIntVector& A, const FIntVector& B)
|
||||||
|
{
|
||||||
|
return FIntVector(FMath::Min(A.X, B.X), FMath::Min(A.Y, B.Y), FMath::Min(A.Z, B.Z));
|
||||||
|
}
|
||||||
|
|
||||||
|
// AABB Overlapping
|
||||||
|
bool URoom::Overlap(URoom& A, URoom& B)
|
||||||
|
{
|
||||||
|
FIntVector A_firstPoint = A.Position;
|
||||||
|
FIntVector B_firstPoint = B.Position;
|
||||||
|
FIntVector A_secondPoint = A.RoomToWorld(A.RoomData->Size - FIntVector(1,1,1));
|
||||||
|
FIntVector B_secondPoint = B.RoomToWorld(B.RoomData->Size - FIntVector(1,1,1));
|
||||||
|
|
||||||
|
FIntVector A_min = Min(A_firstPoint, A_secondPoint);
|
||||||
|
FIntVector A_max = Max(A_firstPoint, A_secondPoint);
|
||||||
|
FIntVector B_min = Min(B_firstPoint, B_secondPoint);
|
||||||
|
FIntVector B_max = Max(B_firstPoint, B_secondPoint);
|
||||||
|
|
||||||
|
if (A_min.X > B_max.X) return false;
|
||||||
|
if (A_max.X < B_min.X) return false;
|
||||||
|
if (A_min.Y > B_max.Y) return false;
|
||||||
|
if (A_max.Y < B_min.Y) return false;
|
||||||
|
if (A_min.Z > B_max.Z) return false;
|
||||||
|
if (A_max.Z < B_min.Z) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::Overlap(URoom & Room, TArray<URoom*>& RoomList)
|
||||||
|
{
|
||||||
|
bool overlap = false;
|
||||||
|
for (int i = 0; i < RoomList.Num() && !overlap; i++)
|
||||||
|
{
|
||||||
|
if (Overlap(Room, *RoomList[i]))
|
||||||
|
{
|
||||||
|
overlap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return overlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDoorDirection URoom::Add(EDoorDirection A, EDoorDirection B)
|
||||||
|
{
|
||||||
|
int8 D = (int8)A + (int8)B;
|
||||||
|
while (D > 2) D -= 4;
|
||||||
|
while (D <= -2) D += 4;
|
||||||
|
return (EDoorDirection)D;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDoorDirection URoom::Sub(EDoorDirection A, EDoorDirection B)
|
||||||
|
{
|
||||||
|
int8 D = (int8)A - (int8)B;
|
||||||
|
while (D > 2) D -= 4;
|
||||||
|
while (D <= -2) D += 4;
|
||||||
|
return (EDoorDirection)D;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDoorDirection URoom::Opposite(EDoorDirection O)
|
||||||
|
{
|
||||||
|
return Add(O, EDoorDirection::South);
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector URoom::GetDirection(EDoorDirection O)
|
||||||
|
{
|
||||||
|
FIntVector Dir = FIntVector::ZeroValue;
|
||||||
|
switch (O)
|
||||||
|
{
|
||||||
|
case EDoorDirection::North:
|
||||||
|
Dir.X = 1;
|
||||||
|
break;
|
||||||
|
case EDoorDirection::East:
|
||||||
|
Dir.Y = 1;
|
||||||
|
break;
|
||||||
|
case EDoorDirection::West:
|
||||||
|
Dir.Y = -1;
|
||||||
|
break;
|
||||||
|
case EDoorDirection::South:
|
||||||
|
Dir.X = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntVector URoom::Rotate(FIntVector Pos, EDoorDirection Rot)
|
||||||
|
{
|
||||||
|
FIntVector NewPos = Pos;
|
||||||
|
switch (Rot)
|
||||||
|
{
|
||||||
|
case EDoorDirection::North:
|
||||||
|
NewPos = Pos;
|
||||||
|
break;
|
||||||
|
case EDoorDirection::West:
|
||||||
|
NewPos.Y = -Pos.X;
|
||||||
|
NewPos.X = Pos.Y;
|
||||||
|
break;
|
||||||
|
case EDoorDirection::East:
|
||||||
|
NewPos.Y = Pos.X;
|
||||||
|
NewPos.X = -Pos.Y;
|
||||||
|
break;
|
||||||
|
case EDoorDirection::South:
|
||||||
|
NewPos.Y = -Pos.Y;
|
||||||
|
NewPos.X = -Pos.X;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NewPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector URoom::GetRealDoorPosition(FIntVector DoorCell, EDoorDirection DoorRot)
|
||||||
|
{
|
||||||
|
return URoom::Unit() * (FVector(DoorCell) + 0.5f * FVector(URoom::GetDirection(DoorRot)) + FVector(0, 0, URoom::DoorOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void URoom::Connect(URoom& RoomA, int DoorA, URoom& RoomB, int DoorB)
|
||||||
|
{
|
||||||
|
RoomA.SetConnection(DoorA, &RoomB, DoorB);
|
||||||
|
RoomB.SetConnection(DoorB, &RoomA, DoorA);
|
||||||
|
}
|
||||||
|
|
||||||
|
URoom* URoom::GetRoomAt(FIntVector RoomCell, TArray<URoom*>& RoomList)
|
||||||
|
{
|
||||||
|
for(auto it = RoomList.begin(); it != RoomList.end(); ++it)
|
||||||
|
{
|
||||||
|
if(IsValid(*it) && (*it)->IsOccupied(RoomCell))
|
||||||
|
{
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector URoom::Unit()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
return Settings->RoomUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector URoom::DoorSize()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
return Settings->DoorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
float URoom::DoorOffset()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
return Settings->DoorOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::OcclusionCulling()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
return Settings->OcclusionCulling;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::DrawDebug()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
return Settings->DrawDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URoom::CanLoop()
|
||||||
|
{
|
||||||
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
||||||
|
return Settings->CanLoop;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RoomData.h"
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
|
||||||
|
URoomData::URoomData()
|
||||||
|
: Super()
|
||||||
|
{
|
||||||
|
Doors.Add(FDoorDef());
|
||||||
|
Size = FIntVector(1, 1, 1);
|
||||||
|
RandomDoor = true;
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
#include "CoreUObject.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "EngineUtils.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
#include "GameFramework/GameState.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
#include "Room.h"
|
||||||
|
#include "RoomData.h"
|
||||||
|
#include "Door.h"
|
||||||
|
|
||||||
|
|
||||||
|
uint32 ARoomLevel::Count = 0;
|
||||||
|
|
||||||
|
// Use this for initialization
|
||||||
|
void ARoomLevel::Init(URoom* _Room)
|
||||||
|
{
|
||||||
|
Id = Count;
|
||||||
|
Count++;
|
||||||
|
|
||||||
|
IsInit = false;
|
||||||
|
Room = _Room;
|
||||||
|
PendingInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARoomLevel::ARoomLevel(const FObjectInitializer & ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
IsInit = false;
|
||||||
|
PendingInit = false;
|
||||||
|
Room = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoomLevel::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoomLevel::EndPlay(EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
for (AActor* Actor : ActorsInLevel)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
Actor->Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is called once per frame
|
||||||
|
void ARoomLevel::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
|
||||||
|
if (!IsInit)
|
||||||
|
{
|
||||||
|
if (PendingInit && Room != nullptr)
|
||||||
|
{
|
||||||
|
Transform.SetLocation(FVector(Room->Position) * URoom::Unit());
|
||||||
|
Transform.SetRotation(FRotator(0.0f, -90.0f * (int8)Room->Direction, 0.0f).Quaternion());
|
||||||
|
|
||||||
|
FIntVector forward = URoom::GetDirection(Room->Direction);
|
||||||
|
FIntVector right = URoom::GetDirection(URoom::Add(Room->Direction, EDoorDirection::East));
|
||||||
|
|
||||||
|
// Create triggerBox for occlusion culling
|
||||||
|
Center = 0.5f * (URoom::Unit() * FVector(Room->Position + Room->RoomToWorld(Room->GetRoomData()->Size) - forward - right));
|
||||||
|
HalfExtents = 0.5f * (URoom::Unit() * FVector(Room->RoomToWorld(Room->GetRoomData()->Size) - Room->Position));
|
||||||
|
HalfExtents = FVector(FMath::Abs(HalfExtents.X), FMath::Abs(HalfExtents.Y), FMath::Abs(HalfExtents.Z));
|
||||||
|
|
||||||
|
// Register All Actors in the level
|
||||||
|
for (TActorIterator<AActor> ActorItr(GetWorld()); ActorItr; ++ActorItr)
|
||||||
|
{
|
||||||
|
ULevel *Level = ActorItr->GetLevel();
|
||||||
|
if (Level->GetOuter() == GetLevel()->GetOuter())
|
||||||
|
{
|
||||||
|
ActorsInLevel.Add(*ActorItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingInit = false;
|
||||||
|
IsInit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Display();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsValid(Data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FIntVector forward = URoom::GetDirection(EDoorDirection::North);
|
||||||
|
FIntVector right = URoom::GetDirection(URoom::Add(EDoorDirection::North, EDoorDirection::East));
|
||||||
|
|
||||||
|
Center = 0.5f * (URoom::Unit() * FVector(Data->Size - forward - right));
|
||||||
|
HalfExtents = 0.5f * (URoom::Unit() * FVector(Data->Size));
|
||||||
|
HalfExtents = FVector(FMath::Abs(HalfExtents.X), FMath::Abs(HalfExtents.Y), FMath::Abs(HalfExtents.Z));
|
||||||
|
|
||||||
|
Center = Transform.TransformPosition(Center);
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
if (URoom::DrawDebug())
|
||||||
|
{
|
||||||
|
// Pivot
|
||||||
|
DrawDebugSphere(GetWorld(), Transform.GetLocation(), 100.0f, 4, FColor::Magenta);
|
||||||
|
|
||||||
|
// Room bounds
|
||||||
|
DrawDebugBox(GetWorld(), Center, HalfExtents, Transform.GetRotation(), FColor::Red);
|
||||||
|
|
||||||
|
FVector DoorSize = URoom::DoorSize();
|
||||||
|
|
||||||
|
// Doors
|
||||||
|
for (int i = 0; i < Data->Doors.Num(); i++)
|
||||||
|
{
|
||||||
|
ADoor::DrawDebug(GetWorld(), Data->Doors[i].Position, Data->Doors[i].Direction, Transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ARoomLevel::IsPlayerInside()
|
||||||
|
{
|
||||||
|
bool inside = false;
|
||||||
|
|
||||||
|
FCollisionShape box = FCollisionShape::MakeBox(HalfExtents);
|
||||||
|
|
||||||
|
APawn* player = UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetPawnOrSpectator();
|
||||||
|
TArray<FOverlapResult> overlappedActors;
|
||||||
|
|
||||||
|
if (GetWorld()->OverlapMultiByObjectType(
|
||||||
|
overlappedActors,
|
||||||
|
Center,
|
||||||
|
Transform.GetRotation(),
|
||||||
|
FCollisionObjectQueryParams::AllDynamicObjects,
|
||||||
|
box))
|
||||||
|
{
|
||||||
|
for (FOverlapResult result : overlappedActors)
|
||||||
|
{
|
||||||
|
if (player == result.GetActor())
|
||||||
|
{
|
||||||
|
inside = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARoomLevel::Display()
|
||||||
|
{
|
||||||
|
if (!IsPendingKill() && Room != nullptr && URoom::OcclusionCulling())
|
||||||
|
{
|
||||||
|
PlayerInside = IsPlayerInside();
|
||||||
|
IsHidden = !PlayerInside;
|
||||||
|
for (int i = 0; i < Room->GetConnectionCount(); i++)
|
||||||
|
{
|
||||||
|
if (Room->GetConnection(i) != nullptr
|
||||||
|
&& IsValid(Room->GetConnection(i)->GetLevelScript())
|
||||||
|
&& Room->GetConnection(i)->GetLevelScript()->PlayerInside)
|
||||||
|
{
|
||||||
|
IsHidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// force IsHidden to false if AlwaysVisible is true
|
||||||
|
IsHidden &= !AlwaysVisible;
|
||||||
|
|
||||||
|
for (AActor* Actor : ActorsInLevel)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
Actor->SetActorHiddenInGame(IsHidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RoomLockerBase.h"
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
#include "Room.h"
|
||||||
|
#include "RoomData.h"
|
||||||
|
#include "GameFramework/GameState.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
|
||||||
|
void ARoomLockerBase::SetLocked(bool Locked, bool Self, TSubclassOf<URoomData> RoomType)
|
||||||
|
{
|
||||||
|
ARoomLevel* Script = GetRoomLevel();
|
||||||
|
if (nullptr != Script)
|
||||||
|
{
|
||||||
|
if (Self)
|
||||||
|
{
|
||||||
|
Script->IsLocked = Locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
URoom* Room = Script->Room;
|
||||||
|
if (nullptr != Room && nullptr != RoomType)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Room->GetConnectionCount(); i++)
|
||||||
|
{
|
||||||
|
if (nullptr != Room->GetConnection(i) && nullptr != Room->GetConnection(i)->GetLevelScript())
|
||||||
|
{
|
||||||
|
if (RoomType == Room->GetConnection(i)->GetRoomData()->GetClass())
|
||||||
|
{
|
||||||
|
Room->GetConnection(i)->GetLevelScript()->IsLocked = Locked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARoomLevel * ARoomLockerBase::GetRoomLevel()
|
||||||
|
{
|
||||||
|
return Cast<ARoomLevel>(GetLevel()->GetLevelScriptActor());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TriggerDoor.h"
|
||||||
|
#include "Components/BoxComponent.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "Room.h"
|
||||||
|
#include "RoomLevel.h"
|
||||||
|
|
||||||
|
ATriggerDoor::ATriggerDoor()
|
||||||
|
{
|
||||||
|
BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComponent"));
|
||||||
|
|
||||||
|
if (RootComponent != nullptr)
|
||||||
|
{
|
||||||
|
BoxComponent->SetupAttachment(RootComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATriggerDoor::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
if (nullptr != BoxComponent)
|
||||||
|
{
|
||||||
|
BoxComponent->OnComponentBeginOverlap.AddUniqueDynamic(this, &ATriggerDoor::OnTriggerEnter);
|
||||||
|
BoxComponent->OnComponentEndOverlap.AddUniqueDynamic(this, &ATriggerDoor::OnTriggerExit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATriggerDoor::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
|
||||||
|
if (CharacterList.Num() > 0)
|
||||||
|
{
|
||||||
|
OpenDoor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CloseDoor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATriggerDoor::SetRoomsAlwaysVisible(bool _visible)
|
||||||
|
{
|
||||||
|
if (nullptr != RoomA && nullptr != RoomA->GetLevelScript())
|
||||||
|
{
|
||||||
|
RoomA->GetLevelScript()->AlwaysVisible = _visible;
|
||||||
|
}
|
||||||
|
if (nullptr != RoomB && nullptr != RoomB->GetLevelScript())
|
||||||
|
{
|
||||||
|
RoomB->GetLevelScript()->AlwaysVisible = _visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATriggerDoor::OnTriggerEnter(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
|
||||||
|
{
|
||||||
|
ACharacter* OtherCharacter = Cast<ACharacter>(OtherActor);
|
||||||
|
UCapsuleComponent* OtherCapsule = Cast<UCapsuleComponent>(OtherComp);
|
||||||
|
|
||||||
|
if (OtherCharacter != nullptr && OtherCapsule != nullptr && OtherCapsule == OtherCharacter->GetCapsuleComponent() && !CharacterList.Contains(OtherCharacter))
|
||||||
|
{
|
||||||
|
CharacterList.Add(OtherCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATriggerDoor::OnTriggerExit(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
|
||||||
|
{
|
||||||
|
ACharacter* OtherCharacter = Cast<ACharacter>(OtherActor);
|
||||||
|
UCapsuleComponent* OtherCapsule = Cast<UCapsuleComponent>(OtherComp);
|
||||||
|
|
||||||
|
if (OtherCharacter != nullptr && OtherCapsule != nullptr && OtherCapsule == OtherCharacter->GetCapsuleComponent() && CharacterList.Contains(OtherCharacter))
|
||||||
|
{
|
||||||
|
CharacterList.Remove(OtherCharacter);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TriggerType.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
|
||||||
|
// Sets default values for this component's properties
|
||||||
|
UTriggerType::UTriggerType()
|
||||||
|
{
|
||||||
|
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||||
|
// off to improve performance if you don't need them.
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
|
||||||
|
TickDuration = 0.5f;
|
||||||
|
ActivationDelay = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
|
void UTriggerType::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
if (GetNetMode() != ENetMode::NM_Client)
|
||||||
|
{
|
||||||
|
OnComponentBeginOverlap.AddUniqueDynamic(this, &UTriggerType::OnTriggerEnter);
|
||||||
|
OnComponentEndOverlap.AddUniqueDynamic(this, &UTriggerType::OnTriggerExit);
|
||||||
|
GetWorld()->GetTimerManager().SetTimer(TickTimer, this, &UTriggerType::TriggerTick, TickDuration, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UTriggerType::OnTriggerEnter(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
|
||||||
|
{
|
||||||
|
if (ActorType == nullptr || (OtherActor!=nullptr && OtherActor->IsA(ActorType)))
|
||||||
|
{
|
||||||
|
if (!ActorList.Contains(OtherActor))
|
||||||
|
{
|
||||||
|
ActorList.Add(OtherActor);
|
||||||
|
OnActorEnter.Broadcast(OtherActor);
|
||||||
|
|
||||||
|
if (ActorList.Num() >= requiredActorCountToActivate)
|
||||||
|
{
|
||||||
|
if (ActivationDelay > 0)
|
||||||
|
{
|
||||||
|
GetWorld()->GetTimerManager().SetTimer(ActivationTimer, this, &UTriggerType::TriggerActivate, ActivationDelay, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TriggerActivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UTriggerType::OnTriggerExit(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex)
|
||||||
|
{
|
||||||
|
if (ActorType == nullptr || (OtherActor != nullptr && OtherActor->IsA(ActorType)))
|
||||||
|
{
|
||||||
|
if (ActorList.Contains(OtherActor))
|
||||||
|
{
|
||||||
|
ActorList.Remove(OtherActor);
|
||||||
|
OnActorExit.Broadcast(OtherActor);
|
||||||
|
|
||||||
|
GetWorld()->GetTimerManager().ClearTimer(ActivationTimer);
|
||||||
|
TriggerDeactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UTriggerType::TriggerTick()
|
||||||
|
{
|
||||||
|
OnTriggerTick.Broadcast(ActorList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UTriggerType::TriggerActivate()
|
||||||
|
{
|
||||||
|
if (!bIsActivated)
|
||||||
|
{
|
||||||
|
bIsActivated = true;
|
||||||
|
OnActivation.Broadcast(ActorList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UTriggerType::TriggerDeactivate()
|
||||||
|
{
|
||||||
|
if (bIsActivated)
|
||||||
|
{
|
||||||
|
bIsActivated = false;
|
||||||
|
OnDeactivation.Broadcast(ActorList);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class ProceduralDungeon : ModuleRules
|
||||||
|
{
|
||||||
|
public ProceduralDungeon(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(new string[] { "Core", "NavigationSystem" });
|
||||||
|
PrivateDependencyModuleNames.AddRange(new string[] { "CoreUObject", "Engine" });
|
||||||
|
}
|
||||||
|
}
|
102
Plugins/ProceduralDungeon/Source/ProceduralDungeon/Public/Door.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
#include "Door.generated.h"
|
||||||
|
|
||||||
|
class URoom;
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API ADoor : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
ADoor();
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual bool ShouldTickIfViewportsOnly() const override { return true; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION()
|
||||||
|
void OpenDoor();
|
||||||
|
UFUNCTION()
|
||||||
|
void CloseDoor();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnDoorLock() {}
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Door", meta = (DisplayName = "On Locked"))
|
||||||
|
void OnDoorLock_BP();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnDoorUnlock() {}
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Door", meta = (DisplayName = "On Unlocked"))
|
||||||
|
void OnDoorUnlock_BP();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnDoorOpen() {}
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Door", meta = (DisplayName = "On Open"))
|
||||||
|
void OnDoorOpen_BP();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnDoorClose() {}
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Door", meta = (DisplayName = "On Close"))
|
||||||
|
void OnDoorClose_BP();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool bLocked = false;
|
||||||
|
bool bIsOpen = false;
|
||||||
|
|
||||||
|
// The two connected rooms to this door
|
||||||
|
UPROPERTY()
|
||||||
|
URoom* RoomA;
|
||||||
|
UPROPERTY()
|
||||||
|
URoom* RoomB;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Door", meta = (DisplayName = "Always Visible"))
|
||||||
|
bool bAlwaysVisible = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Door", meta = (DisplayName = "Always Unlocked"))
|
||||||
|
bool bAlwaysUnlocked = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool bPrevLocked = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void SetConnectingRooms(URoom* RoomA, URoom* RoomB);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Door", meta = (DisplayName="Is Locked"))
|
||||||
|
bool IsLocked() { return bLocked; }
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Door", meta = (DisplayName = "Is Open"))
|
||||||
|
bool IsOpen() { return bIsOpen; }
|
||||||
|
|
||||||
|
static void DrawDebug(UWorld* World, FIntVector DoorCell = FIntVector::ZeroValue, EDoorDirection DoorRot = EDoorDirection::NbDirection, FTransform Transform = FTransform::Identity);
|
||||||
|
};
|
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "Math/RandomStream.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
#include "DungeonGenerator.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGenerationEvent);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRoomEvent, URoomData*, NewRoom);
|
||||||
|
|
||||||
|
class ADoor;
|
||||||
|
class URoom;
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API ADungeonGenerator : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Sets default values for this actor's properties
|
||||||
|
ADungeonGenerator();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Called when the game starts or when spawned
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Update the seed and call the generation on all clients
|
||||||
|
// Do nothing when called on clients
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Dungeon Generator")
|
||||||
|
void Generate();
|
||||||
|
|
||||||
|
// ===== Methods that should be overriden in blueprint =====
|
||||||
|
|
||||||
|
// Return the RoomData you want as root of the dungeon generation
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "Dungeon Generator", meta = (DisplayName = "Choose First Room"))
|
||||||
|
URoomData* ChooseFirstRoomData();
|
||||||
|
|
||||||
|
// Return the RoomData that will be connected to the Current Room
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "Dungeon Generator", meta = (DisplayName = "Choose Next Room"))
|
||||||
|
URoomData* ChooseNextRoomData(URoomData* CurrentRoom);
|
||||||
|
|
||||||
|
// Return the door which will be spawned between Current Room and Next Room
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "Dungeon Generator", meta = (DisplayName = "Choose Door"))
|
||||||
|
TSubclassOf<ADoor> ChooseDoor(URoomData* CurrentRoom, URoomData* NextRoom);
|
||||||
|
|
||||||
|
// Condition to validate a dungeon Generation
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "Dungeon Generator", meta = (DisplayName = "Is Valid Dungeon"))
|
||||||
|
bool IsValidDungeon();
|
||||||
|
|
||||||
|
// Condition to continue or stop adding room to the dungeon
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "Dungeon Generator", meta = (DisplayName = "Continue To Add Room"))
|
||||||
|
bool ContinueToAddRoom();
|
||||||
|
|
||||||
|
// ===== Optional events =====
|
||||||
|
|
||||||
|
// Called after unloading previous dungeon but before generating next dungeon.
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Dungeon Generator", meta = (DisplayName = "Pre Generation"))
|
||||||
|
void OnPreGeneration_BP();
|
||||||
|
|
||||||
|
// Called after all rooms are loaded and initialized
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Dungeon Generator", meta = (DisplayName = "Post Generation"))
|
||||||
|
void OnPostGeneration_BP();
|
||||||
|
|
||||||
|
// Called before generating a new dungeon and each time IsValidDungeon return false
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Dungeon Generator", meta = (DisplayName = "Generation Init"))
|
||||||
|
void OnGenerationInit_BP();
|
||||||
|
|
||||||
|
// Called when the room NewRoom is added in the generation (but not spawned yet)
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "Dungeon Generator", meta = (DisplayName = "On Room Added"))
|
||||||
|
void OnRoomAdded_BP(URoomData* NewRoom);
|
||||||
|
|
||||||
|
// ===== Utility functions you can use in blueprint =====
|
||||||
|
|
||||||
|
// Return true if a specific RoomData is already in the dungeon
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Dungeon Generator")
|
||||||
|
bool HasAlreadyRoomData(URoomData* RoomData);
|
||||||
|
|
||||||
|
// Return true if at least one of the RoomData from the list provided is already in the dungeon
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Dungeon Generator")
|
||||||
|
bool HasAlreadyOneRoomDataFrom(TArray<URoomData*> RoomDataList);
|
||||||
|
|
||||||
|
// Return the number of a specific RoomData in the dungeon
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Dungeon Generator")
|
||||||
|
int CountRoomData(URoomData* RoomData);
|
||||||
|
|
||||||
|
// Return the total number of RoomData in the dungeon from the list provided
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Dungeon Generator")
|
||||||
|
int CountTotalRoomData(TArray<URoomData*> RoomDataList);
|
||||||
|
|
||||||
|
// Return a random RoomData from the array provided
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Dungeon Generator")
|
||||||
|
URoomData* GetRandomRoomData(TArray<URoomData*> RoomDataArray);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Dungeon Generator", meta = (CompactNodeTitle="Nb Room"))
|
||||||
|
int GetNbRoom() { return RoomList.Num(); }
|
||||||
|
|
||||||
|
URoom* GetRoomAt(FIntVector RoomCell);
|
||||||
|
|
||||||
|
// ===== Events =====
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Dungeon Generator")
|
||||||
|
FGenerationEvent OnPreGenerationEvent;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Dungeon Generator")
|
||||||
|
FGenerationEvent OnPostGenerationEvent;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Dungeon Generator")
|
||||||
|
FGenerationEvent OnGenerationInitEvent;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Dungeon Generator")
|
||||||
|
FRoomEvent OnRoomAddedEvent;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// ===== Implementation of blueprint native events =====
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual URoomData* ChooseFirstRoomData_Implementation();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual URoomData* ChooseNextRoomData_Implementation(URoomData* CurrentRoom);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual TSubclassOf<ADoor> ChooseDoor_Implementation(URoomData* CurrentRoom, URoomData* NextRoom);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual bool IsValidDungeon_Implementation();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual bool ContinueToAddRoom_Implementation();
|
||||||
|
|
||||||
|
// ===== Overridable events by native inheritance =====
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnPreGeneration() {}
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnPostGeneration() {}
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnGenerationInit() {}
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnRoomAdded(URoomData* NewRoom) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Launch the generation process of the dungeon
|
||||||
|
UFUNCTION(NetMulticast, Reliable, Category = "Dungeon Generator")
|
||||||
|
void BeginGeneration(uint32 GenerationSeed);
|
||||||
|
|
||||||
|
// Create virtually the dungeon (no load nor initialization of rooms)
|
||||||
|
UFUNCTION()
|
||||||
|
void CreateDungeon();
|
||||||
|
|
||||||
|
// That add a room function to generate all rooms
|
||||||
|
TArray<URoom*> AddNewRooms(URoom& ParentRoom);
|
||||||
|
|
||||||
|
// Instantiate a room in the scene
|
||||||
|
void InstantiateRoom(URoom* Room);
|
||||||
|
|
||||||
|
// Load all room levels
|
||||||
|
UFUNCTION()
|
||||||
|
void LoadAllRooms();
|
||||||
|
|
||||||
|
// unload all room levels
|
||||||
|
UFUNCTION()
|
||||||
|
void UnloadAllRooms();
|
||||||
|
|
||||||
|
// ===== FSM =====
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void SetState(EGenerationState NewState);
|
||||||
|
UFUNCTION()
|
||||||
|
void OnStateBegin(EGenerationState State);
|
||||||
|
UFUNCTION()
|
||||||
|
void OnStateTick(EGenerationState State);
|
||||||
|
UFUNCTION()
|
||||||
|
void OnStateEnd(EGenerationState State);
|
||||||
|
|
||||||
|
// ===== Dispatch optional events =====
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void DispatchPreGeneration();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void DispatchPostGeneration();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void DispatchGenerationInit();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void DispatchRoomAdded(URoomData* NewRoom);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Procedural Generation")
|
||||||
|
EGenerationType GenerationType;
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Procedural Generation")
|
||||||
|
ESeedType SeedType;
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Procedural Generation")
|
||||||
|
uint32 Seed;
|
||||||
|
|
||||||
|
static const int MaxTry = 500;
|
||||||
|
static const int MaxRoomTry = 10;
|
||||||
|
FRandomStream Random;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<URoom*> RoomList;
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<class ADoor*> DoorList;
|
||||||
|
|
||||||
|
bool IsInit = false;
|
||||||
|
int NbInitRoom = 0;
|
||||||
|
int NbLoadedRoom = 0;
|
||||||
|
int NbUnloadedRoom = 0;
|
||||||
|
EGenerationState CurrentState = EGenerationState::None;
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
class FProceduralDungeonModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
virtual bool SupportsDynamicReloading() override { return true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RegisterSettings();
|
||||||
|
void UnregisterSettings();
|
||||||
|
bool HandleSettingsSaved();
|
||||||
|
};
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Containers/UnrealString.h"
|
||||||
|
#include "ProceduralDungeonSettings.h"
|
||||||
|
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogProceduralDungeon, Log, All);
|
||||||
|
|
||||||
|
bool ShowLogsOnScreen(float& _duration);
|
||||||
|
void LogInfo(FString message, bool showOnScreen = true);
|
||||||
|
void LogWarning(FString message, bool showOnScreen = true);
|
||||||
|
void LogError(FString message, bool showOnScreen = true);
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/NoExportTypes.h"
|
||||||
|
#include "ProceduralDungeonSettings.generated.h"
|
||||||
|
|
||||||
|
UCLASS(config = Game, defaultconfig)
|
||||||
|
class PROCEDURALDUNGEON_API UProceduralDungeonSettings : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UProceduralDungeonSettings(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
FVector RoomUnit;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
FVector DoorSize;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
float DoorOffset;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
bool OcclusionCulling;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
bool CanLoop;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
bool DrawDebug;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
bool OnScreenPrintDebug;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, config, Category = "Procedural Dungeon")
|
||||||
|
float PrintDebugDuration;
|
||||||
|
};
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "ProceduralDungeonTypes.generated.h"
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGenerationState : uint8
|
||||||
|
{
|
||||||
|
None UMETA(DisplayName = "None"),
|
||||||
|
Generation UMETA(DisplayName = "Generation"),
|
||||||
|
Load UMETA(DisplayName = "Load"),
|
||||||
|
Initialization UMETA(DisplayName = "Initialization"),
|
||||||
|
Unload UMETA(DisplayName = "Unload"),
|
||||||
|
NbState UMETA(Hidden)
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EDoorDirection : uint8
|
||||||
|
{
|
||||||
|
North = 0 UMETA(DisplayName = "North"), // rotation = 0 (world forward)
|
||||||
|
East = 255 UMETA(DisplayName = "East"), // rotation = -90 (world right)
|
||||||
|
West = 1 UMETA(DisplayName = "West"), // rotation = 90 (world left)
|
||||||
|
South = 2 UMETA(DisplayName = "South"), // rotation = 180 (world backward)
|
||||||
|
NbDirection = 4 UMETA(Hidden)
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGenerationType : uint8
|
||||||
|
{
|
||||||
|
DFS = 0 UMETA(DisplayName = "Depth First", Tooltip = "Make the dungeon more linear"),
|
||||||
|
BFS = 1 UMETA(DisplayName = "Breadth First", Tooltip = "Make the dungeon less linear"),
|
||||||
|
NbType = 2 UMETA(Hidden)
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class ESeedType : uint8
|
||||||
|
{
|
||||||
|
Random = 0 UMETA(DisplayName = "Random", Tooltip = "Random seed at each generation"),
|
||||||
|
AutoIncrement = 1 UMETA(DisplayName = "Auto Increment", Tooltip = "Get the initial seed and increment at each generation"),
|
||||||
|
Fixed = 2 UMETA(DisplayName = "Fixed", Tooltip = "Always use initial seed (or you can set it manually via blueprint)"),
|
||||||
|
NbType = 3 UMETA(Hidden)
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FDoorDef
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "DoorDef")
|
||||||
|
FIntVector Position;
|
||||||
|
UPROPERTY(EditAnywhere, Category = "DoorDef")
|
||||||
|
EDoorDirection Direction;
|
||||||
|
};
|
@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/ObjectMacros.h"
|
||||||
|
#include "Engine/LevelStreaming.h"
|
||||||
|
#include "ProceduralLevelStreaming.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was copied from the ULevelStreamingDynamic of the engine
|
||||||
|
* But an Unload methods is added and the UniqueLevelInstanceID is switch to public (accessed by the generator)
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class PROCEDURALDUNGEON_API UProceduralLevelStreaming : public ULevelStreaming
|
||||||
|
{
|
||||||
|
GENERATED_UCLASS_BODY()
|
||||||
|
|
||||||
|
/** Whether the level should be loaded at startup */
|
||||||
|
UPROPERTY(Category = LevelStreaming, EditAnywhere)
|
||||||
|
uint32 bInitiallyLoaded : 1;
|
||||||
|
|
||||||
|
/** Whether the level should be visible at startup if it is loaded */
|
||||||
|
UPROPERTY(Category = LevelStreaming, EditAnywhere)
|
||||||
|
uint32 bInitiallyVisible : 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream in a level with a specific location and rotation. You can create multiple instances of the same level!
|
||||||
|
*
|
||||||
|
* The level to be loaded does not have to be in the persistent map's Levels list, however to ensure that the .umap does get
|
||||||
|
* packaged, please be sure to include the .umap in your Packaging Settings:
|
||||||
|
*
|
||||||
|
* Project Settings -> Packaging -> List of Maps to Include in a Packaged Build (you may have to show advanced or type in filter)
|
||||||
|
*
|
||||||
|
* @param LevelName - Level package name, ex: /Game/Maps/MyMapName, specifying short name like MyMapName will force very slow search on disk
|
||||||
|
* @param Location - World space location where the level should be spawned
|
||||||
|
* @param Rotation - World space rotation for rotating the entire level
|
||||||
|
* @param bOutSuccess - Whether operation was successful (map was found and added to the sub-levels list)
|
||||||
|
* @return Streaming level object for a level instance
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = LevelStreaming, meta = (DisplayName = "Load Level Instance (by Name)", WorldContext = "WorldContextObject"))
|
||||||
|
static UProceduralLevelStreaming* LoadLevelInstance(UObject* WorldContextObject, FString LevelName, FVector Location, FRotator Rotation, bool& bOutSuccess);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = LevelStreaming, meta = (DisplayName = "Load Level Instance (by Object Reference)", WorldContext = "WorldContextObject"))
|
||||||
|
static UProceduralLevelStreaming* LoadLevelInstanceBySoftObjectPtr(UObject* WorldContextObject, TSoftObjectPtr<UWorld> Level, FVector Location, FRotator Rotation, bool& bOutSuccess);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = LevelStreaming, meta = (DisplayName = "Load Level Instance (by Room Data)", WorldContext = "WorldContextObject"))
|
||||||
|
static UProceduralLevelStreaming* Load(UObject* WorldContextObject, class URoomData* Data, FVector Location, FRotator Rotation);
|
||||||
|
UFUNCTION(BlueprintCallable, Category = LevelStreaming, meta = (DisplayName = "Unload Level Instance", WorldContext = "WorldContextObject"))
|
||||||
|
static void Unload(UObject* WorldContextObject, UProceduralLevelStreaming* Instance);
|
||||||
|
|
||||||
|
|
||||||
|
//~ Begin UObject Interface
|
||||||
|
virtual void PostLoad() override;
|
||||||
|
//~ End UObject Interface
|
||||||
|
|
||||||
|
//~ Begin ULevelStreaming Interface
|
||||||
|
virtual bool ShouldBeLoaded() const override { return bShouldBeLoaded; }
|
||||||
|
//~ End ULevelStreaming Interface
|
||||||
|
|
||||||
|
virtual void SetShouldBeLoaded(bool bShouldBeLoaded) override;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Procedural Level Streaming")
|
||||||
|
void OnLevelDynamicUnloaded();
|
||||||
|
|
||||||
|
bool IsLevelUnloaded() { return bIsUnloaded; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Counter used by LoadLevelInstance to create unique level names
|
||||||
|
static int32 UniqueLevelInstanceId;
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint32 bIsUnloaded : 1;
|
||||||
|
|
||||||
|
static UProceduralLevelStreaming* LoadLevelInstance_Internal(UWorld* World, const FString& LongPackageName, FVector Location, FRotator Rotation, bool& bOutSuccess);
|
||||||
|
};
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Containers/Queue.h"
|
||||||
|
#include "Containers/Array.h"
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class TQueueOrStack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class EMode { QUEUE, STACK };
|
||||||
|
|
||||||
|
TQueueOrStack(EMode _Mode)
|
||||||
|
: Mode(_Mode), Queue(), Stack()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Push(T& Element)
|
||||||
|
{
|
||||||
|
switch(Mode)
|
||||||
|
{
|
||||||
|
case EMode::QUEUE:
|
||||||
|
Queue.Enqueue(Element);
|
||||||
|
break;
|
||||||
|
case EMode::STACK:
|
||||||
|
Stack.Push(Element);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T Pop()
|
||||||
|
{
|
||||||
|
check(!IsEmpty());
|
||||||
|
T item = T();
|
||||||
|
switch(Mode)
|
||||||
|
{
|
||||||
|
case EMode::QUEUE:
|
||||||
|
Queue.Dequeue(item);
|
||||||
|
break;
|
||||||
|
case EMode::STACK:
|
||||||
|
item = Stack.Pop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Num()
|
||||||
|
{
|
||||||
|
switch(Mode)
|
||||||
|
{
|
||||||
|
case EMode::QUEUE:
|
||||||
|
return Queue.Num();
|
||||||
|
case EMode::STACK:
|
||||||
|
return Stack.Num();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEmpty()
|
||||||
|
{
|
||||||
|
switch(Mode)
|
||||||
|
{
|
||||||
|
case EMode::QUEUE:
|
||||||
|
return Queue.IsEmpty();
|
||||||
|
case EMode::STACK:
|
||||||
|
return Stack.Num() <= 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
EMode Mode;
|
||||||
|
TQueue<T> Queue;
|
||||||
|
TArray<T> Stack;
|
||||||
|
};
|
122
Plugins/ProceduralDungeon/Source/ProceduralDungeon/Public/Room.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ProceduralLevelStreaming.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
#include "Room.generated.h"
|
||||||
|
|
||||||
|
class ARoomLevel;
|
||||||
|
class URoomData;
|
||||||
|
class ADoor;
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FRoomConnection
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TWeakObjectPtr<URoom> OtherRoom = nullptr;
|
||||||
|
int OtherDoorIndex = -1;
|
||||||
|
ADoor* DoorInstance = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API URoom : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
private:
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FRoomConnection> Connections;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY()
|
||||||
|
UProceduralLevelStreaming* Instance;
|
||||||
|
UPROPERTY()
|
||||||
|
FIntVector Position;
|
||||||
|
EDoorDirection Direction;
|
||||||
|
|
||||||
|
URoomData* GetRoomData() { return RoomData; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY()
|
||||||
|
URoomData* RoomData;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Init(URoomData* RoomData);
|
||||||
|
|
||||||
|
bool IsConnected(int Index);
|
||||||
|
void SetConnection(int Index, URoom* Room, int OtherDoorIndex);
|
||||||
|
TWeakObjectPtr<URoom> GetConnection(int Index);
|
||||||
|
int GetFirstEmptyConnection();
|
||||||
|
|
||||||
|
void Instantiate(UWorld* World);
|
||||||
|
void Destroy(UWorld* World);
|
||||||
|
ARoomLevel* GetLevelScript();
|
||||||
|
bool IsInstanceLoaded();
|
||||||
|
bool IsInstanceUnloaded();
|
||||||
|
|
||||||
|
EDoorDirection GetDoorWorldOrientation(int DoorIndex);
|
||||||
|
FIntVector GetDoorWorldPosition(int DoorIndex);
|
||||||
|
int GetConnectionCount() { return Connections.Num(); }
|
||||||
|
int GetDoorIndexAt(FIntVector WorldPos, EDoorDirection WorldRot);
|
||||||
|
bool IsDoorInstanced(int DoorIndex);
|
||||||
|
void SetDoorInstance(int DoorIndex, ADoor* Door);
|
||||||
|
int GetOtherDoorIndex(int DoorIndex);
|
||||||
|
void TryConnectToExistingDoors(TArray<URoom*>& RoomList);
|
||||||
|
|
||||||
|
FIntVector WorldToRoom(FIntVector WorldPos);
|
||||||
|
FIntVector RoomToWorld(FIntVector RoomPos);
|
||||||
|
EDoorDirection WorldToRoom(EDoorDirection WorldRot);
|
||||||
|
EDoorDirection RoomToWorld(EDoorDirection RoomRot);
|
||||||
|
void SetRotationFromDoor(int DoorIndex, EDoorDirection WorldRot);
|
||||||
|
void SetPositionFromDoor(int DoorIndex, FIntVector WorldPos);
|
||||||
|
void SetPositionAndRotationFromDoor(int DoorIndex, FIntVector WorldPos, EDoorDirection WorldRot);
|
||||||
|
bool IsOccupied(FIntVector Cell);
|
||||||
|
|
||||||
|
// AABB Overlapping
|
||||||
|
static bool Overlap(URoom& A, URoom& B);
|
||||||
|
static bool Overlap(URoom& Room, TArray<URoom*>& RoomList);
|
||||||
|
static EDoorDirection Add(EDoorDirection A, EDoorDirection B);
|
||||||
|
static EDoorDirection Sub(EDoorDirection A, EDoorDirection B);
|
||||||
|
static EDoorDirection Opposite(EDoorDirection O);
|
||||||
|
static FIntVector GetDirection(EDoorDirection O);
|
||||||
|
static FIntVector Rotate(FIntVector Pos, EDoorDirection Rot);
|
||||||
|
|
||||||
|
static FVector GetRealDoorPosition(FIntVector DoorCell, EDoorDirection DoorRot);
|
||||||
|
|
||||||
|
static void Connect(URoom& RoomA, int DoorA, URoom& RoomB, int DoorB);
|
||||||
|
static URoom* GetRoomAt(FIntVector RoomCell, TArray<URoom*>& RoomList);
|
||||||
|
|
||||||
|
// Plugin Settings
|
||||||
|
static FVector Unit();
|
||||||
|
static FVector DoorSize();
|
||||||
|
static float DoorOffset();
|
||||||
|
static bool OcclusionCulling();
|
||||||
|
static bool DrawDebug();
|
||||||
|
static bool CanLoop();
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
#include "RoomData.generated.h"
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API URoomData : public UPrimaryDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
friend class UProceduralLevelStreaming;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Level")
|
||||||
|
TSoftObjectPtr<UWorld> Level;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Door")
|
||||||
|
bool RandomDoor;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Doors")
|
||||||
|
TArray<FDoorDef> Doors;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Room")
|
||||||
|
FIntVector Size;
|
||||||
|
|
||||||
|
public:
|
||||||
|
URoomData();
|
||||||
|
|
||||||
|
int GetNbDoor() const { return Doors.Num(); }
|
||||||
|
};
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/LevelScriptActor.h"
|
||||||
|
#include "RoomLevel.generated.h"
|
||||||
|
|
||||||
|
class URoom;
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API ARoomLevel : public ALevelScriptActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Data", meta = ( BlueprintBaseOnly /* Doesn't work... */ ) )
|
||||||
|
class URoomData* Data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static uint32 Count;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
URoom* Room = nullptr;
|
||||||
|
bool PlayerInside = false;
|
||||||
|
bool IsHidden = false;
|
||||||
|
bool IsInit = false;
|
||||||
|
bool PendingInit = false;
|
||||||
|
bool IsLocked = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Room Level")
|
||||||
|
bool AlwaysVisible = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ARoomLevel(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual bool ShouldTickIfViewportsOnly() const override { return true; }
|
||||||
|
|
||||||
|
void Init(URoom* Room);
|
||||||
|
uint32 GetId() { return Id; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY(VisibleAnywhere, Category = "Room Level")
|
||||||
|
uint32 Id;
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<AActor*> ActorsInLevel;
|
||||||
|
FTransform Transform;
|
||||||
|
FVector Center;
|
||||||
|
FVector HalfExtents;
|
||||||
|
|
||||||
|
bool IsPlayerInside();
|
||||||
|
void Display();
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "ProceduralDungeonTypes.h"
|
||||||
|
#include "RoomLockerBase.generated.h"
|
||||||
|
|
||||||
|
class URoomData;
|
||||||
|
class ARoomLevel;
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API ARoomLockerBase : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Set the room where this actor is locked or not (with self parameter) and the neighbor rooms of RoomType.
|
||||||
|
void SetLocked(bool Locked, bool Self = true, TSubclassOf<URoomData> RoomType = nullptr);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ARoomLevel* GetRoomLevel();
|
||||||
|
};
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Door.h"
|
||||||
|
#include "TriggerDoor.generated.h"
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class PROCEDURALDUNGEON_API ATriggerDoor : public ADoor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY(EditAnywhere, Category="Door Trigger")
|
||||||
|
class UBoxComponent* BoxComponent;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<class ACharacter*> CharacterList;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ATriggerDoor();
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnTriggerEnter(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnTriggerExit(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Door")
|
||||||
|
void SetRoomsAlwaysVisible(bool Visible);
|
||||||
|
};
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/BoxComponent.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "TriggerType.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTriggerEvent, AActor*, Actor);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTriggerArrayEvent, TArray<AActor*>, Actor);
|
||||||
|
|
||||||
|
UCLASS(BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
|
||||||
|
class PROCEDURALDUNGEON_API UTriggerType : public UBoxComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UTriggerType();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger Type")
|
||||||
|
float TickDuration;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger Type")
|
||||||
|
float ActivationDelay;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger Type")
|
||||||
|
uint8 requiredActorCountToActivate;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger Type")
|
||||||
|
TSubclassOf<AActor> ActorType;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "On Actor Enter"))
|
||||||
|
FTriggerEvent OnActorEnter;
|
||||||
|
UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "On Actor Exit"))
|
||||||
|
FTriggerEvent OnActorExit;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "On Trigger Tick"))
|
||||||
|
FTriggerArrayEvent OnTriggerTick;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "On Trigger Activation"))
|
||||||
|
FTriggerArrayEvent OnActivation;
|
||||||
|
UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "On Trigger Deactivation"))
|
||||||
|
FTriggerArrayEvent OnDeactivation;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
bool IsActivated() { return bIsActivated; }
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
TArray<AActor*> GetActorList() { return ActorList; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Trigger Type")
|
||||||
|
bool bIsActivated;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Trigger Type")
|
||||||
|
TArray<AActor*> ActorList;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY()
|
||||||
|
FTimerHandle TickTimer;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FTimerHandle ActivationTimer;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnTriggerEnter(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
|
||||||
|
UFUNCTION()
|
||||||
|
void OnTriggerExit(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void TriggerTick();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void TriggerActivate();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void TriggerDeactivate();
|
||||||
|
};
|
7
Plugins/UE4GitPlugin-2.17-beta/.gitbugtraq
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# .gitbugtraq for Git GUIs (SmartGit/TortoiseGit) to show links to the Github issue tracker.
|
||||||
|
# Instead of the repository root directory, it could be added as an additional section to $GIT_DIR/config.
|
||||||
|
# (note that '\' need to be escaped).
|
||||||
|
[bugtraq]
|
||||||
|
url = https://github.com/SRombauts/UE4GitPlugin/issues/%BUGID%
|
||||||
|
loglinkregex = "#\\d+"
|
||||||
|
logregex = \\d+
|
5
Plugins/UE4GitPlugin-2.17-beta/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/Binaries/*/*.pdb
|
||||||
|
/Binaries/*/*Debug*
|
||||||
|
/Binaries/*/*.dylib
|
||||||
|
/Binaries/*/*.modules
|
||||||
|
/Intermediate
|
25
Plugins/UE4GitPlugin-2.17-beta/GitSourceControl.uplugin
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"FileVersion" : 3,
|
||||||
|
"Version" : 37,
|
||||||
|
"VersionName" : "2.17",
|
||||||
|
"FriendlyName" : "Git LFS 2",
|
||||||
|
"Description" : "Git source control management (dev)",
|
||||||
|
"Category" : "Source Control",
|
||||||
|
"CreatedBy" : "SRombauts",
|
||||||
|
"CreatedByURL" : "http://srombauts.github.com",
|
||||||
|
"DocsURL" : "",
|
||||||
|
"MarketplaceURL" : "",
|
||||||
|
"SupportURL" : "",
|
||||||
|
"EnabledByDefault" : true,
|
||||||
|
"CanContainContent" : false,
|
||||||
|
"IsBetaVersion" : true,
|
||||||
|
"Installed" : false,
|
||||||
|
"Modules" :
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name" : "GitSourceControl",
|
||||||
|
"Type" : "Editor",
|
||||||
|
"LoadingPhase" : "Default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
20
Plugins/UE4GitPlugin-2.17-beta/LICENSE.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
201
Plugins/UE4GitPlugin-2.17-beta/README.md
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Unreal Engine 4 Git Source Control Plugin
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
[![release](https://img.shields.io/github/release/SRombauts/UE4GitPlugin.svg)](https://github.com/SRombauts/UE4GitPlugin/releases)
|
||||||
|
[![Git Plugin issues](https://img.shields.io/github/issues/SRombauts/UE4GitPlugin.svg)](https://github.com/SRombauts/UE4GitPlugin/issues)
|
||||||
|
[![Join the chat at https://gitter.im/SRombauts/UE4GitPlugin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SRombauts/UE4GitPlugin)
|
||||||
|
UE4GitPlugin is a simple Git Source Control Plugin for **Unreal Engine 4.26**.
|
||||||
|
|
||||||
|
Developed and contributed by Sébastien Rombauts 2014-2020 (sebastien.rombauts@gmail.com)
|
||||||
|
<a href="https://www.paypal.me/SRombauts" title="Pay Me a Beer! Donate with PayPal :)"><img src="https://www.paypalobjects.com/webstatic/paypalme/images/pp_logo_small.png" width="118"></a>
|
||||||
|
|
||||||
|
- First version of the plugin has been **integrated by default in UE4.7 in "beta version"**.
|
||||||
|
- This is a developement fork named "**Git LFS 2**" adding File Locks supported by Github.
|
||||||
|
|
||||||
|
You need to install it into your Project **Plugins/** folder, and it will overwrite (replace) the default "Git (beta version)" Source Control Provider with the "Git LFS 2" plugin.
|
||||||
|
|
||||||
|
Have a look at the [Git Plugin Tutorial on the Wiki](https://wiki.unrealengine.com/Git_source_control_%28Tutorial%29). ([alternate link](https://michaeljcole.github.io/wiki.unrealengine.com/Git_source_control_%28Tutorial%29/))
|
||||||
|
|
||||||
|
Written and contributed by Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
|
||||||
|
Source Control Login window to create a new workspace/a new repository:
|
||||||
|
![Source Control Login window - create a new repository](Screenshots/SourceControlLogin_Init.png)
|
||||||
|
|
||||||
|
Source Control status tooltip, when hovering the Source Control icon in toolbar:
|
||||||
|
![Source Control Status Tooltip](Screenshots/SourceControlStatusTooltip.png)
|
||||||
|
|
||||||
|
Source Control top Menu, extended with a few commands specific to Git:
|
||||||
|
![Source Control Status Tooltip](Screenshots/SourceControlMenu.png)
|
||||||
|
|
||||||
|
Submit Files to Source Control window, to commit assets:
|
||||||
|
![Submit Files to Source Control](Screenshots/SubmitFiles.png)
|
||||||
|
|
||||||
|
File History window, to see the changelog of an asset:
|
||||||
|
![History of a file](Screenshots/FileHistory.png)
|
||||||
|
|
||||||
|
Visual Diffing of two revisions of a Blueprint:
|
||||||
|
<img src="https://cdn2.unrealengine.com/blog/DiffTool-1009x542-719850393.png" width="720">
|
||||||
|
|
||||||
|
Merge conflict of a Blueprint:
|
||||||
|
<img src="https://docs.unrealengine.com/latest/images/Support/Builds/ReleaseNotes/2015/4_7/BPmergeTool.jpg" width="720">
|
||||||
|
|
||||||
|
Status Icons:
|
||||||
|
|
||||||
|
![New/Unsaved/Untracked](Screenshots/Icons/New.png)
|
||||||
|
![Added](Screenshots/Icons/Added.png)
|
||||||
|
![Unchanged](Screenshots/Icons/Unchanged.png)
|
||||||
|
![Modified](Screenshots/Icons/Modified.png)
|
||||||
|
![Moved/Renamed](Screenshots/Icons/Renamed.png)
|
||||||
|
|
||||||
|
### Supported features
|
||||||
|
- initialize a new Git local repository ('git init') to manage your UE4 Game Project
|
||||||
|
- can also create an appropriate .gitignore file as part of initialization
|
||||||
|
- can also create a .gitattributes file to enable Git LFS (Large File System) as part of initialization
|
||||||
|
- can also enable Git LFS 2.x File Locks as part of initialization
|
||||||
|
- can also make the initial commit, with custom multi-line message
|
||||||
|
- display status icons to show modified/added/deleted/untracked files, not at head and conflicted
|
||||||
|
- show history of a file
|
||||||
|
- visual diff of a blueprint against depot or between previous versions of a file
|
||||||
|
- revert modifications of a file (works best with "Content Hot-Reload" experimental option of UE4.15, by default since 4.16)
|
||||||
|
- add, delete, rename a file
|
||||||
|
- checkin/commit a file (cannot handle atomically more than 50 files)
|
||||||
|
- migrate an asset between two projects if both are using Git
|
||||||
|
- solve a merge conflict on a blueprint
|
||||||
|
- show current branch name in status text
|
||||||
|
- Configure remote origin URL ('git remote add origin url')
|
||||||
|
- Sync to Pull (rebase) the current branch if there is no local modified files
|
||||||
|
- Push the current branch
|
||||||
|
- Git LFS (Github, Gitlab, Bitbucket), git-annex, git-fat and git-media are working with Git 2.10+
|
||||||
|
- Git LFS 2 File Locks
|
||||||
|
- Windows, Mac and Linux
|
||||||
|
|
||||||
|
### What *cannot* be done presently
|
||||||
|
- Branch/Merge are not in the current Editor workflow
|
||||||
|
- Amend a commit is not in the current Editor workflow
|
||||||
|
- Revert All (using either "Stash" or "reset --hard")
|
||||||
|
- Configure user name & email ('git config user.name' & git config user.email')
|
||||||
|
- Authentication is not managed if needed for Sync (Pull)
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
- #34 "outside repository" fatal error
|
||||||
|
- #37 Rebase workflow: conflicts not detected!
|
||||||
|
- #41 UE-44637: Deleting an asset is unsuccessful if the asset is marked for add (since UE4.13)
|
||||||
|
- #46 Merge Conflicts - Accept Target - causes engine to crash bug
|
||||||
|
- #47 Git LFS conflict resolution not working
|
||||||
|
- #49 Git LFS 2: False error in logs after a successful push
|
||||||
|
- #51 Git LFS 2: cannot revert a modified/unchecked-out asset
|
||||||
|
- #53 Git LFS 2: document the configuration and workflow
|
||||||
|
- #54 Poor performances of 'lfs locks' on Windows command line
|
||||||
|
- #55 Git LFS 2: Unlocking a renamed asset
|
||||||
|
|
||||||
|
- missing localisation for git specific messages
|
||||||
|
- displaying states of 'Engine' assets (also needs management of 'out of tree' files)
|
||||||
|
- renaming a Blueprint in Editor leaves a redirector file, AND modify too much the asset to enable git to track its history through renaming
|
||||||
|
|
||||||
|
### Getting started
|
||||||
|
|
||||||
|
Quick demo of the Git Plugin on Unreal Engine 4.12 (preview)
|
||||||
|
[![Git Plugin on Unreal Engine 4.12 (preview)](https://img.youtube.com/vi/rRhPl9vL58Q/0.jpg)](https://youtu.be/rRhPl9vL58Q)
|
||||||
|
|
||||||
|
#### Install Git
|
||||||
|
|
||||||
|
Under Windows 64bits, you should install the standard standalone Git for Windows
|
||||||
|
(now comming with Git LFS 2 with File Locking) with default parameters,
|
||||||
|
usually in "C:\Program Files\Git\bin\git.exe".
|
||||||
|
|
||||||
|
Then you have to configure your name and e-mail that will appear in each of your commits:
|
||||||
|
|
||||||
|
```
|
||||||
|
git config --global user.name "Sébastien Rombauts"
|
||||||
|
git config --global user.email sebastien.rombauts@gmail.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Install this Git Plugin (dev) into your Game Project
|
||||||
|
|
||||||
|
Unreal Engine comes with a stable version of this plugin, so no need to install it.
|
||||||
|
|
||||||
|
This alternate "Git development plugin" needs to be installed into a subfolder or your Game Project "Plugins" directory
|
||||||
|
(that is, you cannot install it into the Engine Plugins directory):
|
||||||
|
|
||||||
|
```
|
||||||
|
<YourGameProject>/Plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
You will obviously only be able to use the plugin within this project.
|
||||||
|
|
||||||
|
See also the [Plugins official Documentation](https://docs.unrealengine.com/latest/INT/Programming/Plugins/index.html)
|
||||||
|
|
||||||
|
#### Activate Git Source Control for your Game Project
|
||||||
|
|
||||||
|
Load your Game Project in Unreal Engine, then open:
|
||||||
|
|
||||||
|
```
|
||||||
|
File->Connect To Source Control... -> Git
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Project already managed by Git
|
||||||
|
|
||||||
|
If your project is already under Git (it contains a ".git" subfolder), just click on "Accept Settings". This connect the Editor to your local Git repository ("Depot").
|
||||||
|
|
||||||
|
##### Project not already under Git
|
||||||
|
|
||||||
|
Otherwise, the Git Plugin is able to create (initialize) a new local Git Repository with your project Assets and Sources files:
|
||||||
|
|
||||||
|
<img src="Screenshots/SourceControlLogin_Init.png" width="720">
|
||||||
|
|
||||||
|
Click "Initialize project with Git" that will add all relevant files to source control and make the initial commit with the customizable message.
|
||||||
|
When everything is done, click on "Accept Settings".
|
||||||
|
|
||||||
|
#### Using the Git Source Control Provider in the Unreal Engine Editor
|
||||||
|
|
||||||
|
The plugin mostly interacts with you local Git repository ("Depot"), not much with the remote server (usually "origin").
|
||||||
|
|
||||||
|
It displays Git status icons on top of assets in the Asset Browser:
|
||||||
|
- No icon means that the file is under source control and unchanged since last commit, or ignored.
|
||||||
|
- A red mark is for "modified" assets, that is the one that needs to be committed (so not the same as "Check-out" in Perforce/SVN/Plastic SCM).
|
||||||
|
- A red cross is for "added" assets, that also needs to be committed
|
||||||
|
- A blue lightning means "renamed".
|
||||||
|
- A yellow exclamation point is for files in conflict after a merge, or is not at head (latest revision on the current remote branch).
|
||||||
|
- A yellow question mark is for files not in source control.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- specifics of rename and redirectors, and "Fix Up Redirector in Folder" command
|
||||||
|
- history / visual diff
|
||||||
|
- CheckIn = Commit
|
||||||
|
- CheckOut = Commit+Push+unlock (when using LFS 2)
|
||||||
|
|
||||||
|
See also the [Source Control official Documentation](https://docs.unrealengine.com/latest/INT/Engine/UI/SourceControl/index.html)
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
Copyright (c) 2014-2020 Sébastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
|
||||||
|
Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
## How to contribute
|
||||||
|
### GitHub website
|
||||||
|
The most efficient way to help and contribute to this wrapper project is to
|
||||||
|
use the tools provided by GitHub:
|
||||||
|
- please fill bug reports and feature requests here: https://github.com/SRombauts/UE4GitPlugin/issues
|
||||||
|
- fork the repository, make some small changes and submit them with independent pull-requests
|
||||||
|
|
||||||
|
### Contact
|
||||||
|
- You can use the Unreal Engine forums.
|
||||||
|
- You can also email me directly, I will answer any questions and requests.
|
||||||
|
|
||||||
|
### Coding Style Guidelines
|
||||||
|
The source code follow the UnreaEngine official [Coding Standard](https://docs.unrealengine.com/latest/INT/Programming/Development/CodingStandard/index.html):
|
||||||
|
- CamelCase naming convention, with a prefix letter to differentiate classes ('F'), interfaces ('I'), templates ('T')
|
||||||
|
- files (.cpp/.h) are named like the class they contains
|
||||||
|
- Doxygen comments, documentation is located with declaration, on headers
|
||||||
|
- Use portable common features of C++11 like nullptr, auto, range based for, override keyword
|
||||||
|
- Braces on their own line
|
||||||
|
- Tabs to indent code, with a width of 4 characters
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Git Source Control Tutorial on the Wikis](https://wiki.unrealengine.com/Git_source_control_(Tutorial))
|
||||||
|
- [UE4 Git Plugin website](http://srombauts.github.com/UE4GitPlugin)
|
||||||
|
|
||||||
|
- [ue4-hg-plugin for Mercurial (and bigfiles)](https://github.com/enlight/ue4-hg-plugin)
|
BIN
Plugins/UE4GitPlugin-2.17-beta/Resources/Icon128.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/FileHistory.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/Icons/Added.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/Icons/Modified.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/Icons/New.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/Icons/Renamed.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/Icons/Unchanged.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 62 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/SourceControlMenu.png
Normal file
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 28 KiB |
BIN
Plugins/UE4GitPlugin-2.17-beta/Screenshots/SubmitFiles.png
Normal file
After Width: | Height: | Size: 42 KiB |
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class GitSourceControl : ModuleRules
|
||||||
|
{
|
||||||
|
public GitSourceControl(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
// Enable the Include-What-You-Use (IWYU) UE4.15 policy (see https://docs.unrealengine.com/en-us/Programming/UnrealBuildSystem/IWYUReferenceGuide)
|
||||||
|
// "Shared PCHs may be used if an explicit private PCH is not set through PrivatePCHHeaderFile. In either case, none of the source files manually include a module PCH, and should include a matching header instead."
|
||||||
|
bEnforceIWYU = true;
|
||||||
|
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
PrivatePCHHeaderFile = "Private/GitSourceControlPrivatePCH.h";
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[] {
|
||||||
|
"Core",
|
||||||
|
"CoreUObject",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"InputCore",
|
||||||
|
"DesktopWidgets",
|
||||||
|
"EditorStyle",
|
||||||
|
"UnrealEd",
|
||||||
|
"SourceControl",
|
||||||
|
"Projects",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlCommand.h"
|
||||||
|
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
|
||||||
|
FGitSourceControlCommand::FGitSourceControlCommand(const TSharedRef<class ISourceControlOperation, ESPMode::ThreadSafe>& InOperation, const TSharedRef<class IGitSourceControlWorker, ESPMode::ThreadSafe>& InWorker, const FSourceControlOperationComplete& InOperationCompleteDelegate)
|
||||||
|
: Operation(InOperation)
|
||||||
|
, Worker(InWorker)
|
||||||
|
, OperationCompleteDelegate(InOperationCompleteDelegate)
|
||||||
|
, bExecuteProcessed(0)
|
||||||
|
, bCommandSuccessful(false)
|
||||||
|
, bConnectionDropped(false)
|
||||||
|
, bAutoDelete(true)
|
||||||
|
, Concurrency(EConcurrency::Synchronous)
|
||||||
|
{
|
||||||
|
// grab the providers settings here, so we don't access them once the worker thread is launched
|
||||||
|
check(IsInGameThread());
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>( "GitSourceControl" );
|
||||||
|
PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
bUsingGitLfsLocking = GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
|
||||||
|
PathToRepositoryRoot = GitSourceControl.GetProvider().GetPathToRepositoryRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlCommand::DoWork()
|
||||||
|
{
|
||||||
|
bCommandSuccessful = Worker->Execute(*this);
|
||||||
|
FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1);
|
||||||
|
|
||||||
|
return bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlCommand::Abandon()
|
||||||
|
{
|
||||||
|
FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlCommand::DoThreadedWork()
|
||||||
|
{
|
||||||
|
Concurrency = EConcurrency::Asynchronous;
|
||||||
|
DoWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
ECommandResult::Type FGitSourceControlCommand::ReturnResults()
|
||||||
|
{
|
||||||
|
// Save any messages that have accumulated
|
||||||
|
for (FString& String : InfoMessages)
|
||||||
|
{
|
||||||
|
Operation->AddInfoMessge(FText::FromString(String));
|
||||||
|
}
|
||||||
|
for (FString& String : ErrorMessages)
|
||||||
|
{
|
||||||
|
Operation->AddErrorMessge(FText::FromString(String));
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the completion delegate if we have one bound
|
||||||
|
ECommandResult::Type Result = bCommandSuccessful ? ECommandResult::Succeeded : ECommandResult::Failed;
|
||||||
|
OperationCompleteDelegate.ExecuteIfBound(Operation, Result);
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ISourceControlProvider.h"
|
||||||
|
#include "Misc/IQueuedWork.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to execute Git commands multi-threaded.
|
||||||
|
*/
|
||||||
|
class FGitSourceControlCommand : public IQueuedWork
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
FGitSourceControlCommand(const TSharedRef<class ISourceControlOperation, ESPMode::ThreadSafe>& InOperation, const TSharedRef<class IGitSourceControlWorker, ESPMode::ThreadSafe>& InWorker, const FSourceControlOperationComplete& InOperationCompleteDelegate = FSourceControlOperationComplete() );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where the real thread work is done. All work that is done for
|
||||||
|
* this queued object should be done from within the call to this function.
|
||||||
|
*/
|
||||||
|
bool DoWork();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the queued work that it is being abandoned so that it can do
|
||||||
|
* per object clean up as needed. This will only be called if it is being
|
||||||
|
* abandoned before completion. NOTE: This requires the object to delete
|
||||||
|
* itself using whatever heap it was allocated in.
|
||||||
|
*/
|
||||||
|
virtual void Abandon() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is also used to tell the object to cleanup but not before
|
||||||
|
* the object has finished it's work.
|
||||||
|
*/
|
||||||
|
virtual void DoThreadedWork() override;
|
||||||
|
|
||||||
|
/** Save any results and call any registered callbacks. */
|
||||||
|
ECommandResult::Type ReturnResults();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Path to the Git binary */
|
||||||
|
FString PathToGitBinary;
|
||||||
|
|
||||||
|
/** Path to the root of the Git repository: can be the ProjectDir itself, or any parent directory (found by the "Connect" operation) */
|
||||||
|
FString PathToRepositoryRoot;
|
||||||
|
|
||||||
|
/** Tell if using the Git LFS file Locking workflow */
|
||||||
|
bool bUsingGitLfsLocking;
|
||||||
|
|
||||||
|
/** Operation we want to perform - contains outward-facing parameters & results */
|
||||||
|
TSharedRef<class ISourceControlOperation, ESPMode::ThreadSafe> Operation;
|
||||||
|
|
||||||
|
/** The object that will actually do the work */
|
||||||
|
TSharedRef<class IGitSourceControlWorker, ESPMode::ThreadSafe> Worker;
|
||||||
|
|
||||||
|
/** Delegate to notify when this operation completes */
|
||||||
|
FSourceControlOperationComplete OperationCompleteDelegate;
|
||||||
|
|
||||||
|
/**If true, this command has been processed by the source control thread*/
|
||||||
|
volatile int32 bExecuteProcessed;
|
||||||
|
|
||||||
|
/**If true, the source control command succeeded*/
|
||||||
|
bool bCommandSuccessful;
|
||||||
|
|
||||||
|
/** TODO LFS If true, the source control connection was dropped while this command was being executed*/
|
||||||
|
bool bConnectionDropped;
|
||||||
|
|
||||||
|
/** Current Commit full SHA1 */
|
||||||
|
FString CommitId;
|
||||||
|
|
||||||
|
/** Current Commit description's Summary */
|
||||||
|
FString CommitSummary;
|
||||||
|
|
||||||
|
/** If true, this command will be automatically cleaned up in Tick() */
|
||||||
|
bool bAutoDelete;
|
||||||
|
|
||||||
|
/** Whether we are running multi-treaded or not*/
|
||||||
|
EConcurrency::Type Concurrency;
|
||||||
|
|
||||||
|
/** Files to perform this operation on */
|
||||||
|
TArray<FString> Files;
|
||||||
|
|
||||||
|
/**Info and/or warning message storage*/
|
||||||
|
TArray<FString> InfoMessages;
|
||||||
|
|
||||||
|
/**Potential error message storage*/
|
||||||
|
TArray<FString> ErrorMessages;
|
||||||
|
};
|
@ -0,0 +1,515 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlMenu.h"
|
||||||
|
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
#include "GitSourceControlProvider.h"
|
||||||
|
#include "GitSourceControlOperations.h"
|
||||||
|
#include "GitSourceControlUtils.h"
|
||||||
|
|
||||||
|
#include "ISourceControlModule.h"
|
||||||
|
#include "ISourceControlOperation.h"
|
||||||
|
#include "SourceControlOperations.h"
|
||||||
|
|
||||||
|
#include "LevelEditor.h"
|
||||||
|
#include "Widgets/Notifications/SNotificationList.h"
|
||||||
|
#include "Framework/Notifications/NotificationManager.h"
|
||||||
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||||
|
#include "Misc/MessageDialog.h"
|
||||||
|
#include "EditorStyleSet.h"
|
||||||
|
|
||||||
|
#include "PackageTools.h"
|
||||||
|
#include "FileHelpers.h"
|
||||||
|
|
||||||
|
#include "Logging/MessageLog.h"
|
||||||
|
|
||||||
|
static const FName GitSourceControlMenuTabName("GitSourceControlMenu");
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::Register()
|
||||||
|
{
|
||||||
|
// Register the extension with the level editor
|
||||||
|
FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor");
|
||||||
|
if (LevelEditorModule)
|
||||||
|
{
|
||||||
|
FLevelEditorModule::FLevelEditorMenuExtender ViewMenuExtender = FLevelEditorModule::FLevelEditorMenuExtender::CreateRaw(this, &FGitSourceControlMenu::OnExtendLevelEditorViewMenu);
|
||||||
|
auto& MenuExtenders = LevelEditorModule->GetAllLevelEditorToolbarSourceControlMenuExtenders();
|
||||||
|
MenuExtenders.Add(ViewMenuExtender);
|
||||||
|
ViewMenuExtenderHandle = MenuExtenders.Last().GetHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::Unregister()
|
||||||
|
{
|
||||||
|
// Unregister the level editor extensions
|
||||||
|
FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor");
|
||||||
|
if (LevelEditorModule)
|
||||||
|
{
|
||||||
|
LevelEditorModule->GetAllLevelEditorToolbarSourceControlMenuExtenders().RemoveAll([=](const FLevelEditorModule::FLevelEditorMenuExtender& Extender) { return Extender.GetHandle() == ViewMenuExtenderHandle; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlMenu::HaveRemoteUrl() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
return !Provider.GetRemoteUrl().IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompt to save or discard all packages
|
||||||
|
bool FGitSourceControlMenu::SaveDirtyPackages()
|
||||||
|
{
|
||||||
|
const bool bPromptUserToSave = true;
|
||||||
|
const bool bSaveMapPackages = true;
|
||||||
|
const bool bSaveContentPackages = true;
|
||||||
|
const bool bFastSave = false;
|
||||||
|
const bool bNotifyNoPackagesSaved = false;
|
||||||
|
const bool bCanBeDeclined = true; // If the user clicks "don't save" this will continue and lose their changes
|
||||||
|
bool bHadPackagesToSave = false;
|
||||||
|
|
||||||
|
bool bSaved = FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages, bFastSave, bNotifyNoPackagesSaved, bCanBeDeclined, &bHadPackagesToSave);
|
||||||
|
|
||||||
|
// bSaved can be true if the user selects to not save an asset by unchecking it and clicking "save"
|
||||||
|
if (bSaved)
|
||||||
|
{
|
||||||
|
TArray<UPackage*> DirtyPackages;
|
||||||
|
FEditorFileUtils::GetDirtyWorldPackages(DirtyPackages);
|
||||||
|
FEditorFileUtils::GetDirtyContentPackages(DirtyPackages);
|
||||||
|
bSaved = DirtyPackages.Num() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bSaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find all packages in Content directory
|
||||||
|
TArray<FString> FGitSourceControlMenu::ListAllPackages()
|
||||||
|
{
|
||||||
|
TArray<FString> PackageRelativePaths;
|
||||||
|
FPackageName::FindPackagesInDirectory(PackageRelativePaths, *FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()));
|
||||||
|
|
||||||
|
TArray<FString> PackageNames;
|
||||||
|
PackageNames.Reserve(PackageRelativePaths.Num());
|
||||||
|
for (const FString& Path : PackageRelativePaths)
|
||||||
|
{
|
||||||
|
FString PackageName;
|
||||||
|
FString FailureReason;
|
||||||
|
if (FPackageName::TryConvertFilenameToLongPackageName(Path, PackageName, &FailureReason))
|
||||||
|
{
|
||||||
|
PackageNames.Add(PackageName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog("SourceControl").Error(FText::FromString(FailureReason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PackageNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unkink all loaded packages to allow to update them
|
||||||
|
TArray<UPackage*> FGitSourceControlMenu::UnlinkPackages(const TArray<FString>& InPackageNames)
|
||||||
|
{
|
||||||
|
TArray<UPackage*> LoadedPackages;
|
||||||
|
|
||||||
|
// Inspired from ContentBrowserUtils::SyncPathsFromSourceControl()
|
||||||
|
if (InPackageNames.Num() > 0)
|
||||||
|
{
|
||||||
|
// Form a list of loaded packages to reload...
|
||||||
|
LoadedPackages.Reserve(InPackageNames.Num());
|
||||||
|
for (const FString& PackageName : InPackageNames)
|
||||||
|
{
|
||||||
|
UPackage* Package = FindPackage(nullptr, *PackageName);
|
||||||
|
if (Package)
|
||||||
|
{
|
||||||
|
LoadedPackages.Emplace(Package);
|
||||||
|
|
||||||
|
// Detach the linkers of any loaded packages so that SCC can overwrite the files...
|
||||||
|
if (!Package->IsFullyLoaded())
|
||||||
|
{
|
||||||
|
FlushAsyncLoading();
|
||||||
|
Package->FullyLoad();
|
||||||
|
}
|
||||||
|
ResetLoaders(Package);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Reseted Loader for %d Packages"), LoadedPackages.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadedPackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::ReloadPackages(TArray<UPackage*>& InPackagesToReload)
|
||||||
|
{
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Reloading %d Packages..."), InPackagesToReload.Num());
|
||||||
|
|
||||||
|
// Syncing may have deleted some packages, so we need to unload those rather than re-load them...
|
||||||
|
TArray<UPackage*> PackagesToUnload;
|
||||||
|
InPackagesToReload.RemoveAll([&](UPackage* InPackage) -> bool
|
||||||
|
{
|
||||||
|
const FString PackageExtension = InPackage->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension();
|
||||||
|
const FString PackageFilename = FPackageName::LongPackageNameToFilename(InPackage->GetName(), PackageExtension);
|
||||||
|
if (!FPaths::FileExists(PackageFilename))
|
||||||
|
{
|
||||||
|
PackagesToUnload.Emplace(InPackage);
|
||||||
|
return true; // remove package
|
||||||
|
}
|
||||||
|
return false; // keep package
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hot-reload the new packages...
|
||||||
|
UPackageTools::ReloadPackages(InPackagesToReload);
|
||||||
|
|
||||||
|
// Unload any deleted packages...
|
||||||
|
UPackageTools::UnloadPackages(PackagesToUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the user if he wants to stash any modification and try to unstash them afterward, which could lead to conflicts
|
||||||
|
bool FGitSourceControlMenu::StashAwayAnyModifications()
|
||||||
|
{
|
||||||
|
bool bStashOk = true;
|
||||||
|
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
const FString& PathToRespositoryRoot = Provider.GetPathToRepositoryRoot();
|
||||||
|
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
const TArray<FString> ParametersStatus{"--porcelain --untracked-files=no"};
|
||||||
|
TArray<FString> InfoMessages;
|
||||||
|
TArray<FString> ErrorMessages;
|
||||||
|
// Check if there is any modification to the working tree
|
||||||
|
const bool bStatusOk = GitSourceControlUtils::RunCommand(TEXT("status"), PathToGitBinary, PathToRespositoryRoot, ParametersStatus, TArray<FString>(), InfoMessages, ErrorMessages);
|
||||||
|
if ((bStatusOk) && (InfoMessages.Num() > 0))
|
||||||
|
{
|
||||||
|
// Ask the user before stashing
|
||||||
|
const FText DialogText(LOCTEXT("SourceControlMenu_Stash_Ask", "Stash (save) all modifications of the working tree? Required to Sync/Pull!"));
|
||||||
|
const EAppReturnType::Type Choice = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText);
|
||||||
|
if (Choice == EAppReturnType::Ok)
|
||||||
|
{
|
||||||
|
const TArray<FString> ParametersStash{ "save \"Stashed by Unreal Engine Git Plugin\"" };
|
||||||
|
bStashMadeBeforeSync = GitSourceControlUtils::RunCommand(TEXT("stash"), PathToGitBinary, PathToRespositoryRoot, ParametersStash, TArray<FString>(), InfoMessages, ErrorMessages);
|
||||||
|
if (!bStashMadeBeforeSync)
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_StashFailed", "Stashing away modifications failed!"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bStashOk = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bStashOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unstash any modifications if a stash was made at the beginning of the Sync operation
|
||||||
|
void FGitSourceControlMenu::ReApplyStashedModifications()
|
||||||
|
{
|
||||||
|
if (bStashMadeBeforeSync)
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
const FString& PathToRespositoryRoot = Provider.GetPathToRepositoryRoot();
|
||||||
|
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
const TArray<FString> ParametersStash{ "pop" };
|
||||||
|
TArray<FString> InfoMessages;
|
||||||
|
TArray<FString> ErrorMessages;
|
||||||
|
const bool bUnstashOk = GitSourceControlUtils::RunCommand(TEXT("stash"), PathToGitBinary, PathToRespositoryRoot, ParametersStash, TArray<FString>(), InfoMessages, ErrorMessages);
|
||||||
|
if (!bUnstashOk)
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_UnstashFailed", "Unstashing previously saved modifications failed!"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::SyncClicked()
|
||||||
|
{
|
||||||
|
if (!OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
// Ask the user to save any dirty assets opened in Editor
|
||||||
|
const bool bSaved = SaveDirtyPackages();
|
||||||
|
if (bSaved)
|
||||||
|
{
|
||||||
|
// Find and Unlink all packages in Content directory to allow to update them
|
||||||
|
PackagesToReload = UnlinkPackages(ListAllPackages());
|
||||||
|
|
||||||
|
// Ask the user if he wants to stash any modification and try to unstash them afterward, which could lead to conflicts
|
||||||
|
const bool bStashed = StashAwayAnyModifications();
|
||||||
|
if (bStashed)
|
||||||
|
{
|
||||||
|
// Launch a "Sync" operation
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
TSharedRef<FSync, ESPMode::ThreadSafe> SyncOperation = ISourceControlOperation::Create<FSync>();
|
||||||
|
const ECommandResult::Type Result = Provider.Execute(SyncOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||||
|
if (Result == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
// Display an ongoing notification during the whole operation (packages will be reloaded at the completion of the operation)
|
||||||
|
DisplayInProgressNotification(SyncOperation->GetInProgressString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Report failure with a notification and Reload all packages
|
||||||
|
DisplayFailureNotification(SyncOperation->GetName());
|
||||||
|
ReloadPackages(PackagesToReload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_Sync_Unsaved", "Stash away all modifications before attempting to Sync!"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_Sync_Unsaved", "Save All Assets before attempting to Sync!"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Source control operation already in progress"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::PushClicked()
|
||||||
|
{
|
||||||
|
if (!OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
// Launch a "Push" Operation
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
TSharedRef<FGitPush, ESPMode::ThreadSafe> PushOperation = ISourceControlOperation::Create<FGitPush>();
|
||||||
|
const ECommandResult::Type Result = Provider.Execute(PushOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||||
|
if (Result == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
// Display an ongoing notification during the whole operation
|
||||||
|
DisplayInProgressNotification(PushOperation->GetInProgressString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Report failure with a notification
|
||||||
|
DisplayFailureNotification(PushOperation->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Source control operation already in progress"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::RevertClicked()
|
||||||
|
{
|
||||||
|
if (!OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
// Ask the user before reverting all!
|
||||||
|
const FText DialogText(LOCTEXT("SourceControlMenu_Revert_Ask", "Revert all modifications of the working tree?"));
|
||||||
|
const EAppReturnType::Type Choice = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText);
|
||||||
|
if (Choice == EAppReturnType::Ok)
|
||||||
|
{
|
||||||
|
// NOTE No need to force the user to SaveDirtyPackages(); since he will be presented with a choice by the Editor
|
||||||
|
|
||||||
|
// Find and Unlink all packages in Content directory to allow to update them
|
||||||
|
PackagesToReload = UnlinkPackages(ListAllPackages());
|
||||||
|
|
||||||
|
// Launch a "Revert" Operation
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
TSharedRef<FRevert, ESPMode::ThreadSafe> RevertOperation = ISourceControlOperation::Create<FRevert>();
|
||||||
|
const ECommandResult::Type Result = Provider.Execute(RevertOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||||
|
if (Result == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
// Display an ongoing notification during the whole operation
|
||||||
|
DisplayInProgressNotification(RevertOperation->GetInProgressString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Report failure with a notification and Reload all packages
|
||||||
|
DisplayFailureNotification(RevertOperation->GetName());
|
||||||
|
ReloadPackages(PackagesToReload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Source control operation already in progress"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::RefreshClicked()
|
||||||
|
{
|
||||||
|
if (!OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
// Launch an "UpdateStatus" Operation
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> RefreshOperation = ISourceControlOperation::Create<FUpdateStatus>();
|
||||||
|
RefreshOperation->SetCheckingAllFiles(true);
|
||||||
|
const ECommandResult::Type Result = Provider.Execute(RefreshOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||||
|
if (Result == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
// Display an ongoing notification during the whole operation
|
||||||
|
DisplayInProgressNotification(RefreshOperation->GetInProgressString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Report failure with a notification
|
||||||
|
DisplayFailureNotification(RefreshOperation->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Source control operation already in progress"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display an ongoing notification during the whole operation
|
||||||
|
void FGitSourceControlMenu::DisplayInProgressNotification(const FText& InOperationInProgressString)
|
||||||
|
{
|
||||||
|
if (!OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
FNotificationInfo Info(InOperationInProgressString);
|
||||||
|
Info.bFireAndForget = false;
|
||||||
|
Info.ExpireDuration = 0.0f;
|
||||||
|
Info.FadeOutDuration = 1.0f;
|
||||||
|
OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
if (OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the ongoing notification at the end of the operation
|
||||||
|
void FGitSourceControlMenu::RemoveInProgressNotification()
|
||||||
|
{
|
||||||
|
if (OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
OperationInProgressNotification.Pin()->ExpireAndFadeout();
|
||||||
|
OperationInProgressNotification.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display a temporary success notification at the end of the operation
|
||||||
|
void FGitSourceControlMenu::DisplaySucessNotification(const FName& InOperationName)
|
||||||
|
{
|
||||||
|
const FText NotificationText = FText::Format(
|
||||||
|
LOCTEXT("SourceControlMenu_Success", "{0} operation was successful!"),
|
||||||
|
FText::FromName(InOperationName)
|
||||||
|
);
|
||||||
|
FNotificationInfo Info(NotificationText);
|
||||||
|
Info.bUseSuccessFailIcons = true;
|
||||||
|
Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
||||||
|
FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("%s"), *NotificationText.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display a temporary failure notification at the end of the operation
|
||||||
|
void FGitSourceControlMenu::DisplayFailureNotification(const FName& InOperationName)
|
||||||
|
{
|
||||||
|
const FText NotificationText = FText::Format(
|
||||||
|
LOCTEXT("SourceControlMenu_Failure", "Error: {0} operation failed!"),
|
||||||
|
FText::FromName(InOperationName)
|
||||||
|
);
|
||||||
|
FNotificationInfo Info(NotificationText);
|
||||||
|
Info.ExpireDuration = 8.0f;
|
||||||
|
FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
UE_LOG(LogSourceControl, Error, TEXT("%s"), *NotificationText.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
|
||||||
|
{
|
||||||
|
RemoveInProgressNotification();
|
||||||
|
|
||||||
|
if ((InOperation->GetName() == "Sync") || (InOperation->GetName() == "Revert"))
|
||||||
|
{
|
||||||
|
// Unstash any modifications if a stash was made at the beginning of the Sync operation
|
||||||
|
ReApplyStashedModifications();
|
||||||
|
// Reload packages that where unlinked at the beginning of the Sync/Revert operation
|
||||||
|
ReloadPackages(PackagesToReload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report result with a notification
|
||||||
|
if (InResult == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
DisplaySucessNotification(InOperation->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayFailureNotification(InOperation->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlMenu::AddMenuExtension(FMenuBuilder& Builder)
|
||||||
|
{
|
||||||
|
Builder.AddMenuEntry(
|
||||||
|
LOCTEXT("GitPush", "Push"),
|
||||||
|
LOCTEXT("GitPushTooltip", "Push all local commits to the remote server."),
|
||||||
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Submit"),
|
||||||
|
FUIAction(
|
||||||
|
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::PushClicked),
|
||||||
|
FCanExecuteAction::CreateRaw(this, &FGitSourceControlMenu::HaveRemoteUrl)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Builder.AddMenuEntry(
|
||||||
|
LOCTEXT("GitSync", "Sync/Pull"),
|
||||||
|
LOCTEXT("GitSyncTooltip", "Update all files in the local repository to the latest version of the remote server."),
|
||||||
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Sync"),
|
||||||
|
FUIAction(
|
||||||
|
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::SyncClicked),
|
||||||
|
FCanExecuteAction::CreateRaw(this, &FGitSourceControlMenu::HaveRemoteUrl)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Builder.AddMenuEntry(
|
||||||
|
LOCTEXT("GitRevert", "Revert"),
|
||||||
|
LOCTEXT("GitRevertTooltip", "Revert all files in the repository to their unchanged state."),
|
||||||
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Revert"),
|
||||||
|
FUIAction(
|
||||||
|
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::RevertClicked),
|
||||||
|
FCanExecuteAction()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Builder.AddMenuEntry(
|
||||||
|
LOCTEXT("GitRefresh", "Refresh"),
|
||||||
|
LOCTEXT("GitRefreshTooltip", "Update the source control status of all files in the local repository."),
|
||||||
|
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Refresh"),
|
||||||
|
FUIAction(
|
||||||
|
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::RefreshClicked),
|
||||||
|
FCanExecuteAction()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<FExtender> FGitSourceControlMenu::OnExtendLevelEditorViewMenu(const TSharedRef<FUICommandList> CommandList)
|
||||||
|
{
|
||||||
|
TSharedRef<FExtender> Extender(new FExtender());
|
||||||
|
|
||||||
|
Extender->AddMenuExtension(
|
||||||
|
"SourceControlActions",
|
||||||
|
EExtensionHook::After,
|
||||||
|
nullptr,
|
||||||
|
FMenuExtensionDelegate::CreateRaw(this, &FGitSourceControlMenu::AddMenuExtension));
|
||||||
|
|
||||||
|
return Extender;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ISourceControlProvider.h"
|
||||||
|
|
||||||
|
class FToolBarBuilder;
|
||||||
|
class FMenuBuilder;
|
||||||
|
|
||||||
|
/** Git extension of the Source Control toolbar menu */
|
||||||
|
class FGitSourceControlMenu
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Register();
|
||||||
|
void Unregister();
|
||||||
|
|
||||||
|
/** This functions will be bound to appropriate Command. */
|
||||||
|
void PushClicked();
|
||||||
|
void SyncClicked();
|
||||||
|
void RevertClicked();
|
||||||
|
void RefreshClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool HaveRemoteUrl() const;
|
||||||
|
|
||||||
|
bool SaveDirtyPackages();
|
||||||
|
TArray<FString> ListAllPackages();
|
||||||
|
TArray<UPackage*> UnlinkPackages(const TArray<FString>& InPackageNames);
|
||||||
|
void ReloadPackages(TArray<UPackage*>& InPackagesToReload);
|
||||||
|
|
||||||
|
bool StashAwayAnyModifications();
|
||||||
|
void ReApplyStashedModifications();
|
||||||
|
|
||||||
|
void AddMenuExtension(FMenuBuilder& Builder);
|
||||||
|
|
||||||
|
TSharedRef<class FExtender> OnExtendLevelEditorViewMenu(const TSharedRef<class FUICommandList> CommandList);
|
||||||
|
|
||||||
|
void DisplayInProgressNotification(const FText& InOperationInProgressString);
|
||||||
|
void RemoveInProgressNotification();
|
||||||
|
void DisplaySucessNotification(const FName& InOperationName);
|
||||||
|
void DisplayFailureNotification(const FName& InOperationName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
FDelegateHandle ViewMenuExtenderHandle;
|
||||||
|
|
||||||
|
/** Was there a need to stash away modifications before Sync? */
|
||||||
|
bool bStashMadeBeforeSync;
|
||||||
|
|
||||||
|
/** Loaded packages to reload after a Sync or Revert operation */
|
||||||
|
TArray<UPackage*> PackagesToReload;
|
||||||
|
|
||||||
|
/** Current source control operation from extended menu if any */
|
||||||
|
TWeakPtr<class SNotificationItem> OperationInProgressNotification;
|
||||||
|
|
||||||
|
/** Delegate called when a source control operation has completed */
|
||||||
|
void OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
|
||||||
|
};
|
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
|
||||||
|
#include "Misc/App.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "GitSourceControlOperations.h"
|
||||||
|
#include "Features/IModularFeatures.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static TSharedRef<IGitSourceControlWorker, ESPMode::ThreadSafe> CreateWorker()
|
||||||
|
{
|
||||||
|
return MakeShareable( new Type() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlModule::StartupModule()
|
||||||
|
{
|
||||||
|
// Register our operations (implemented in GitSourceControlOperations.cpp by subclassing from Engine\Source\Developer\SourceControl\Public\SourceControlOperations.h)
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Connect", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitConnectWorker> ) );
|
||||||
|
// Note: this provider uses the "CheckOut" command only with Git LFS 2 "lock" command, since Git itself has no lock command (all tracked files in the working copy are always already checked-out).
|
||||||
|
GitSourceControlProvider.RegisterWorker( "CheckOut", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitCheckOutWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "UpdateStatus", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitUpdateStatusWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "MarkForAdd", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitMarkForAddWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Delete", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitDeleteWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Revert", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitRevertWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Sync", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitSyncWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Push", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitPushWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "CheckIn", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitCheckInWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Copy", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitCopyWorker> ) );
|
||||||
|
GitSourceControlProvider.RegisterWorker( "Resolve", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitResolveWorker> ) );
|
||||||
|
|
||||||
|
// load our settings
|
||||||
|
GitSourceControlSettings.LoadSettings();
|
||||||
|
|
||||||
|
// Bind our source control provider to the editor
|
||||||
|
IModularFeatures::Get().RegisterModularFeature( "SourceControl", &GitSourceControlProvider );
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
// shut down the provider, as this module is going away
|
||||||
|
GitSourceControlProvider.Close();
|
||||||
|
|
||||||
|
// unbind provider from editor
|
||||||
|
IModularFeatures::Get().UnregisterModularFeature("SourceControl", &GitSourceControlProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlModule::SaveSettings()
|
||||||
|
{
|
||||||
|
if (FApp::IsUnattended() || IsRunningCommandlet())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GitSourceControlSettings.SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FGitSourceControlModule, GitSourceControl);
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleInterface.h"
|
||||||
|
#include "GitSourceControlSettings.h"
|
||||||
|
#include "GitSourceControlProvider.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
UE4GitPlugin is a simple Git Source Control Plugin for Unreal Engine
|
||||||
|
|
||||||
|
Written and contributed by Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
|
||||||
|
### Supported features
|
||||||
|
- initialize a new Git local repository ('git init') to manage your UE4 Game Project
|
||||||
|
- can also create an appropriate .gitignore file as part of initialization
|
||||||
|
- can also create a .gitattributes file to enable Git LFS (Large File System) as part of initialization
|
||||||
|
- can also make the initial commit, with custom multi-line message
|
||||||
|
- can also configure the default remote origin URL
|
||||||
|
- display status icons to show modified/added/deleted/untracked files
|
||||||
|
- show history of a file
|
||||||
|
- visual diff of a blueprint against depot or between previous versions of a file
|
||||||
|
- revert modifications of a file
|
||||||
|
- add, delete, rename a file
|
||||||
|
- checkin/commit a file (cannot handle atomically more than 50 files)
|
||||||
|
- migrate an asset between two projects if both are using Git
|
||||||
|
- solve a merge conflict on a blueprint
|
||||||
|
- show current branch name in status text
|
||||||
|
- Sync to Pull (rebase) the current branch
|
||||||
|
- Git LFS (Github, Gitlab, Bitbucket) is working with Git 2.10+ under Windows
|
||||||
|
- Git LFS 2 File Locking is working with Git 2.10+ and Git LFS 2.0.0
|
||||||
|
- Windows, Mac and Linux
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
1. configure the name of the remote instead of default "origin"
|
||||||
|
|
||||||
|
### TODO LFS 2.x File Locking
|
||||||
|
|
||||||
|
Known issues:
|
||||||
|
0. False error logs after a successful push:
|
||||||
|
To https://github.com/SRombauts/UE4GitLfs2FileLocks.git
|
||||||
|
ee44ff5..59da15e HEAD -> master
|
||||||
|
|
||||||
|
Use "TODO LFS" in the code to track things left to do/improve/refactor:
|
||||||
|
1. IsUsingGitLfsLocking() should be cached in the Provider to avoid calling AccessSettings() too frequently
|
||||||
|
it can not change without re-initializing (at least re-connect) the Provider!
|
||||||
|
2. Implement FGitSourceControlProvider::bWorkingOffline like the SubversionSourceControl plugin
|
||||||
|
3. Trying to deactivate Git LFS 2 file locking afterward on the "Login to Source Control" (Connect/Configure) screen
|
||||||
|
is not working after Git LFS 2 has switched "read-only" flag on files (which needs the Checkout operation to be editable)!
|
||||||
|
- temporarily deactivating locks may be required if we want to be able to work while not connected (do we really need this ???)
|
||||||
|
- does Git LFS have a command to do this deactivation ?
|
||||||
|
- perhaps should we rely on detection of such flags to detect LFS 2 usage (ie. the need to do a checkout)
|
||||||
|
- see SubversionSourceControl plugin that deals with such flags
|
||||||
|
- this would need a rework of the way the "bIsUsingFileLocking" si propagated, since this would no more be a configuration (or not only) but a file state
|
||||||
|
- else we should at least revert those read-only flags when going out of "Lock mode"
|
||||||
|
4. Optimize usage of "git lfs locks", ie reduce the use of UdpateStatus() in Operations
|
||||||
|
|
||||||
|
### What *cannot* be done presently
|
||||||
|
- Branch/Merge are not in the current Editor workflow
|
||||||
|
- Fetch is not in the current Editor workflow
|
||||||
|
- Amend a commit is not in the current Editor workflow
|
||||||
|
- Configure user name & email ('git config user.name' & git config user.email')
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
- the Editor does not show deleted files (only when deleted externally?)
|
||||||
|
- the Editor does not show missing files
|
||||||
|
- missing localization for git specific messages
|
||||||
|
- displaying states of 'Engine' assets (also needs management of 'out of tree' files)
|
||||||
|
- renaming a Blueprint in Editor leaves a redirector file, AND modify too much the asset to enable git to track its history through renaming
|
||||||
|
- standard Editor commit dialog asks if user wants to "Keep Files Checked Out" => no use for Git or Mercurial CanCheckOut()==false
|
||||||
|
|
||||||
|
*/
|
||||||
|
class FGitSourceControlModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
/** Access the Git source control settings */
|
||||||
|
FGitSourceControlSettings& AccessSettings()
|
||||||
|
{
|
||||||
|
return GitSourceControlSettings;
|
||||||
|
}
|
||||||
|
const FGitSourceControlSettings& AccessSettings() const
|
||||||
|
{
|
||||||
|
return GitSourceControlSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save the Git source control settings */
|
||||||
|
void SaveSettings();
|
||||||
|
|
||||||
|
/** Access the Git source control provider */
|
||||||
|
FGitSourceControlProvider& GetProvider()
|
||||||
|
{
|
||||||
|
return GitSourceControlProvider;
|
||||||
|
}
|
||||||
|
const FGitSourceControlProvider& GetProvider() const
|
||||||
|
{
|
||||||
|
return GitSourceControlProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** The Git source control provider */
|
||||||
|
FGitSourceControlProvider GitSourceControlProvider;
|
||||||
|
|
||||||
|
/** The settings for Git source control */
|
||||||
|
FGitSourceControlSettings GitSourceControlSettings;
|
||||||
|
};
|
@ -0,0 +1,684 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlOperations.h"
|
||||||
|
|
||||||
|
#include "Misc/Paths.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "SourceControlOperations.h"
|
||||||
|
#include "ISourceControlModule.h"
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
#include "GitSourceControlCommand.h"
|
||||||
|
#include "GitSourceControlUtils.h"
|
||||||
|
#include "Logging/MessageLog.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||||
|
|
||||||
|
FName FGitPush::GetName() const
|
||||||
|
{
|
||||||
|
return "Push";
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FGitPush::GetInProgressString() const
|
||||||
|
{
|
||||||
|
// TODO Configure origin
|
||||||
|
return LOCTEXT("SourceControl_Push", "Pushing local commits to remote origin...");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FName FGitConnectWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Connect";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitConnectWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
TSharedRef<FConnect, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FConnect>(InCommand.Operation);
|
||||||
|
|
||||||
|
// Check Git Availability
|
||||||
|
if((InCommand.PathToGitBinary.Len() > 0) && GitSourceControlUtils::CheckGitAvailability(InCommand.PathToGitBinary))
|
||||||
|
{
|
||||||
|
// Now update the status of assets in Content/ directory and also Config files
|
||||||
|
TArray<FString> ProjectDirs;
|
||||||
|
ProjectDirs.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()));
|
||||||
|
ProjectDirs.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()));
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, ProjectDirs, InCommand.ErrorMessages, States);
|
||||||
|
if(!InCommand.bCommandSuccessful || InCommand.ErrorMessages.Num() > 0)
|
||||||
|
{
|
||||||
|
Operation->SetErrorText(LOCTEXT("NotAGitRepository", "Failed to enable Git source control. You need to initialize the project as a Git repository first."));
|
||||||
|
InCommand.bCommandSuccessful = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||||
|
|
||||||
|
if(InCommand.bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
// Check server connection by checking lock status (when using Git LFS file Locking worflow)
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("lfs locks"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Operation->SetErrorText(LOCTEXT("GitNotFound", "Failed to enable Git source control. You need to install Git and specify a valid path to git executable."));
|
||||||
|
InCommand.bCommandSuccessful = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitConnectWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitCheckOutWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "CheckOut";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitCheckOutWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
if(InCommand.bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
// lock files: execute the LFS command on relative filenames
|
||||||
|
InCommand.bCommandSuccessful = true;
|
||||||
|
const TArray<FString> RelativeFiles = GitSourceControlUtils::RelativeFilenames(InCommand.Files, InCommand.PathToRepositoryRoot);
|
||||||
|
for(const auto& RelativeFile : RelativeFiles)
|
||||||
|
{
|
||||||
|
TArray<FString> OneFile;
|
||||||
|
OneFile.Add(RelativeFile);
|
||||||
|
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("lfs lock"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), OneFile, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InCommand.bCommandSuccessful = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitCheckOutWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FText ParseCommitResults(const TArray<FString>& InResults)
|
||||||
|
{
|
||||||
|
if(InResults.Num() >= 1)
|
||||||
|
{
|
||||||
|
const FString& FirstLine = InResults[0];
|
||||||
|
return FText::Format(LOCTEXT("CommitMessage", "Commited {0}."), FText::FromString(FirstLine));
|
||||||
|
}
|
||||||
|
return LOCTEXT("CommitMessageUnknown", "Submitted revision.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Locked Files (that is, CheckedOut files, not Added ones)
|
||||||
|
const TArray<FString> GetLockedFiles(const TArray<FString>& InFiles)
|
||||||
|
{
|
||||||
|
TArray<FString> LockedFiles;
|
||||||
|
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
|
||||||
|
TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> LocalStates;
|
||||||
|
Provider.GetState(InFiles, LocalStates, EStateCacheUsage::Use);
|
||||||
|
for(const auto& State : LocalStates)
|
||||||
|
{
|
||||||
|
if(State->IsCheckedOut())
|
||||||
|
{
|
||||||
|
LockedFiles.Add(State->GetFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LockedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitCheckInWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "CheckIn";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitCheckInWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
TSharedRef<FCheckIn, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FCheckIn>(InCommand.Operation);
|
||||||
|
|
||||||
|
// make a temp file to place our commit message in
|
||||||
|
FGitScopedTempFile CommitMsgFile(Operation->GetDescription());
|
||||||
|
if(CommitMsgFile.GetFilename().Len() > 0)
|
||||||
|
{
|
||||||
|
TArray<FString> Parameters;
|
||||||
|
FString ParamCommitMsgFilename = TEXT("--file=\"");
|
||||||
|
ParamCommitMsgFilename += FPaths::ConvertRelativePathToFull(CommitMsgFile.GetFilename());
|
||||||
|
ParamCommitMsgFilename += TEXT("\"");
|
||||||
|
Parameters.Add(ParamCommitMsgFilename);
|
||||||
|
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommit(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters, InCommand.Files, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
if(InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
// Remove any deleted files from status cache
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
|
||||||
|
TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> LocalStates;
|
||||||
|
Provider.GetState(InCommand.Files, LocalStates, EStateCacheUsage::Use);
|
||||||
|
for(const auto& State : LocalStates)
|
||||||
|
{
|
||||||
|
if(State->IsDeleted())
|
||||||
|
{
|
||||||
|
Provider.RemoveFileFromCache(State->GetFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Operation->SetSuccessMessage(ParseCommitResults(InCommand.InfoMessages));
|
||||||
|
const FString Message = (InCommand.InfoMessages.Num() > 0) ? InCommand.InfoMessages[0] : TEXT("");
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("commit successful: %s"), *Message);
|
||||||
|
|
||||||
|
// git-lfs: push and unlock files
|
||||||
|
if(InCommand.bUsingGitLfsLocking && InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
TArray<FString> Parameters2;
|
||||||
|
// TODO Configure origin
|
||||||
|
Parameters2.Add(TEXT("origin"));
|
||||||
|
Parameters2.Add(TEXT("HEAD"));
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("push"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters2, TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
if(!InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
// if out of date, pull first, then try again
|
||||||
|
bool bWasOutOfDate = false;
|
||||||
|
for (const auto& PushError : InCommand.ErrorMessages)
|
||||||
|
{
|
||||||
|
if (PushError.Contains(TEXT("[rejected]")) && PushError.Contains(TEXT("non-fast-forward")))
|
||||||
|
{
|
||||||
|
// Don't do it during iteration, want to append pull results to InCommand.ErrorMessages
|
||||||
|
bWasOutOfDate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bWasOutOfDate)
|
||||||
|
{
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Push failed because we're out of date, pulling automatically to try to resolve"));
|
||||||
|
// Use pull --rebase since that's what the pull command does by default
|
||||||
|
// This requires that we stash if dirty working copy though
|
||||||
|
bool bStashed = false;
|
||||||
|
bool bStashNeeded = false;
|
||||||
|
const TArray<FString> ParametersStatus{"--porcelain --untracked-files=no"};
|
||||||
|
TArray<FString> StatusInfoMessages;
|
||||||
|
TArray<FString> StatusErrorMessages;
|
||||||
|
// Check if there is any modification to the working tree
|
||||||
|
const bool bStatusOk = GitSourceControlUtils::RunCommand(TEXT("status"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, ParametersStatus, TArray<FString>(), StatusInfoMessages, StatusErrorMessages);
|
||||||
|
if ((bStatusOk) && (StatusInfoMessages.Num() > 0))
|
||||||
|
{
|
||||||
|
bStashNeeded = true;
|
||||||
|
const TArray<FString> ParametersStash{ "save \"Stashed by Unreal Engine Git Plugin\"" };
|
||||||
|
bStashed = GitSourceControlUtils::RunCommand(TEXT("stash"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, ParametersStash, TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
if (!bStashed)
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_StashFailed", "Stashing away modifications failed!"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!bStashNeeded || bStashed)
|
||||||
|
{
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("pull --rebase"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
if (InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
// Repeat the push
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("push origin HEAD"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Succeed or fail, restore the stash
|
||||||
|
if (bStashed)
|
||||||
|
{
|
||||||
|
const TArray<FString> ParametersStashPop{ "pop" };
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("stash"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, ParametersStashPop, TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
if (!InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_UnstashFailed", "Unstashing previously saved modifications failed!"));
|
||||||
|
SourceControlLog.Notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
// unlock files: execute the LFS command on relative filenames
|
||||||
|
// (unlock only locked files, that is, not Added files)
|
||||||
|
const TArray<FString> LockedFiles = GetLockedFiles(InCommand.Files);
|
||||||
|
if(LockedFiles.Num() > 0)
|
||||||
|
{
|
||||||
|
const TArray<FString> RelativeFiles = GitSourceControlUtils::RelativeFilenames(LockedFiles, InCommand.PathToRepositoryRoot);
|
||||||
|
for(const auto& RelativeFile : RelativeFiles)
|
||||||
|
{
|
||||||
|
TArray<FString> OneFile;
|
||||||
|
OneFile.Add(RelativeFile);
|
||||||
|
GitSourceControlUtils::RunCommand(TEXT("lfs unlock"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), OneFile, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitCheckInWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitMarkForAddWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "MarkForAdd";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitMarkForAddWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), InCommand.Files, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitMarkForAddWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitDeleteWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Delete";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitDeleteWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("rm"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), InCommand.Files, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitDeleteWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get lists of Missing files (ie "deleted"), Modified files, and "other than Added" Existing files
|
||||||
|
void GetMissingVsExistingFiles(const TArray<FString>& InFiles, TArray<FString>& OutMissingFiles, TArray<FString>& OutAllExistingFiles, TArray<FString>& OutOtherThanAddedExistingFiles)
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
|
||||||
|
const TArray<FString> Files = (InFiles.Num() > 0) ? (InFiles) : (Provider.GetFilesInCache());
|
||||||
|
|
||||||
|
TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> LocalStates;
|
||||||
|
Provider.GetState(Files, LocalStates, EStateCacheUsage::Use);
|
||||||
|
for(const auto& State : LocalStates)
|
||||||
|
{
|
||||||
|
if(FPaths::FileExists(State->GetFilename()))
|
||||||
|
{
|
||||||
|
if(State->IsAdded())
|
||||||
|
{
|
||||||
|
OutAllExistingFiles.Add(State->GetFilename());
|
||||||
|
}
|
||||||
|
else if(State->IsModified())
|
||||||
|
{
|
||||||
|
OutOtherThanAddedExistingFiles.Add(State->GetFilename());
|
||||||
|
OutAllExistingFiles.Add(State->GetFilename());
|
||||||
|
}
|
||||||
|
else if(State->CanRevert()) // for locked but unmodified files
|
||||||
|
{
|
||||||
|
OutOtherThanAddedExistingFiles.Add(State->GetFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (State->IsSourceControlled())
|
||||||
|
{
|
||||||
|
OutMissingFiles.Add(State->GetFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitRevertWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitRevertWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
// Filter files by status to use the right "revert" commands on them
|
||||||
|
TArray<FString> MissingFiles;
|
||||||
|
TArray<FString> AllExistingFiles;
|
||||||
|
TArray<FString> OtherThanAddedExistingFiles;
|
||||||
|
GetMissingVsExistingFiles(InCommand.Files, MissingFiles, AllExistingFiles, OtherThanAddedExistingFiles);
|
||||||
|
|
||||||
|
InCommand.bCommandSuccessful = true;
|
||||||
|
if(MissingFiles.Num() > 0)
|
||||||
|
{
|
||||||
|
// "Added" files that have been deleted needs to be removed from source control
|
||||||
|
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("rm"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), MissingFiles, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
if(AllExistingFiles.Num() > 0)
|
||||||
|
{
|
||||||
|
// reset any changes already added to the index
|
||||||
|
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), AllExistingFiles, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
if(OtherThanAddedExistingFiles.Num() > 0)
|
||||||
|
{
|
||||||
|
// revert any changes in working copy (this would fails if the asset was in "Added" state, since after "reset" it is now "untracked")
|
||||||
|
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("checkout"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), OtherThanAddedExistingFiles, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(InCommand.bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
// unlock files: execute the LFS command on relative filenames
|
||||||
|
// (unlock only locked files, that is, not Added files)
|
||||||
|
const TArray<FString> LockedFiles = GetLockedFiles(OtherThanAddedExistingFiles);
|
||||||
|
if(LockedFiles.Num() > 0)
|
||||||
|
{
|
||||||
|
const TArray<FString> RelativeFiles = GitSourceControlUtils::RelativeFilenames(LockedFiles, InCommand.PathToRepositoryRoot);
|
||||||
|
for(const auto& RelativeFile : RelativeFiles)
|
||||||
|
{
|
||||||
|
TArray<FString> OneFile;
|
||||||
|
OneFile.Add(RelativeFile);
|
||||||
|
GitSourceControlUtils::RunCommand(TEXT("lfs unlock"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), OneFile, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no files were specified (full revert), refresh all relevant files instead of the specified files (which is an empty list in full revert)
|
||||||
|
// This is required so that files that were "Marked for add" have their status updated after a full revert.
|
||||||
|
TArray<FString> FilesToUpdate = InCommand.Files;
|
||||||
|
if (InCommand.Files.Num() <= 0)
|
||||||
|
{
|
||||||
|
for (const auto& File : MissingFiles) FilesToUpdate.Add(File);
|
||||||
|
for (const auto& File : AllExistingFiles) FilesToUpdate.Add(File);
|
||||||
|
for (const auto& File : OtherThanAddedExistingFiles) FilesToUpdate.Add(File);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, FilesToUpdate, InCommand.ErrorMessages, States);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitRevertWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitSyncWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Sync";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSyncWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
// pull the branch to get remote changes by rebasing any local commits (not merging them to avoid complex graphs)
|
||||||
|
TArray<FString> Parameters;
|
||||||
|
Parameters.Add(TEXT("--rebase"));
|
||||||
|
Parameters.Add(TEXT("--autostash"));
|
||||||
|
// TODO Configure origin
|
||||||
|
Parameters.Add(TEXT("origin"));
|
||||||
|
Parameters.Add(TEXT("HEAD"));
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("pull"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters, TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSyncWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FName FGitPushWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Push";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitPushWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
|
||||||
|
// If we have any locked files, check if we should unlock them
|
||||||
|
TArray<FString> FilesToUnlock;
|
||||||
|
if (InCommand.bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
TMap<FString, FString> Locks;
|
||||||
|
// Get locks as relative paths
|
||||||
|
GitSourceControlUtils::GetAllLocks(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, false, InCommand.ErrorMessages, Locks);
|
||||||
|
if(Locks.Num() > 0)
|
||||||
|
{
|
||||||
|
// test to see what lfs files we would push, and compare to locked files, unlock after if push OK
|
||||||
|
FString BranchName;
|
||||||
|
GitSourceControlUtils::GetBranchName(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, BranchName);
|
||||||
|
|
||||||
|
TArray<FString> LfsPushParameters;
|
||||||
|
LfsPushParameters.Add(TEXT("push"));
|
||||||
|
LfsPushParameters.Add(TEXT("--dry-run"));
|
||||||
|
LfsPushParameters.Add(TEXT("origin"));
|
||||||
|
LfsPushParameters.Add(BranchName);
|
||||||
|
TArray<FString> LfsPushInfoMessages;
|
||||||
|
TArray<FString> LfsPushErrMessages;
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("lfs"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, LfsPushParameters, TArray<FString>(), LfsPushInfoMessages, LfsPushErrMessages);
|
||||||
|
|
||||||
|
if(InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
// Result format is of the form
|
||||||
|
// push f4ee401c063058a78842bb3ed98088e983c32aa447f346db54fa76f844a7e85e => Path/To/Asset.uasset
|
||||||
|
// With some potential informationals we can ignore
|
||||||
|
for (auto& Line : LfsPushInfoMessages)
|
||||||
|
{
|
||||||
|
if (Line.StartsWith(TEXT("push")))
|
||||||
|
{
|
||||||
|
FString Prefix, Filename;
|
||||||
|
if (Line.Split(TEXT("=>"), &Prefix, &Filename))
|
||||||
|
{
|
||||||
|
Filename = Filename.TrimStartAndEnd();
|
||||||
|
if (Locks.Contains(Filename))
|
||||||
|
{
|
||||||
|
// We do not need to check user or if the file has local modifications before attempting unlocking, git-lfs will reject the unlock if so
|
||||||
|
// No point duplicating effort here
|
||||||
|
FilesToUnlock.Add(Filename);
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Post-push will try to unlock: %s"), *Filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// push the branch to its default remote
|
||||||
|
// (works only if the default remote "origin" is set and does not require authentication)
|
||||||
|
TArray<FString> Parameters;
|
||||||
|
Parameters.Add(TEXT("--set-upstream"));
|
||||||
|
// TODO Configure origin
|
||||||
|
Parameters.Add(TEXT("origin"));
|
||||||
|
Parameters.Add(TEXT("HEAD"));
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("push"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters, TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
|
||||||
|
if(InCommand.bCommandSuccessful && InCommand.bUsingGitLfsLocking && FilesToUnlock.Num() > 0)
|
||||||
|
{
|
||||||
|
// unlock files: execute the LFS command on relative filenames
|
||||||
|
for(const auto& FileToUnlock : FilesToUnlock)
|
||||||
|
{
|
||||||
|
TArray<FString> OneFile;
|
||||||
|
OneFile.Add(FileToUnlock);
|
||||||
|
bool bUnlocked = GitSourceControlUtils::RunCommand(TEXT("lfs unlock"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), OneFile, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
if (!bUnlocked)
|
||||||
|
{
|
||||||
|
// Report but don't fail, it's not essential
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Unlock failed for %s"), *FileToUnlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to update status if we unlock
|
||||||
|
// This command needs absolute filenames
|
||||||
|
TArray<FString> AbsFilesToUnlock = GitSourceControlUtils::AbsoluteFilenames(FilesToUnlock, InCommand.PathToRepositoryRoot);
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, AbsFilesToUnlock, InCommand.ErrorMessages, States);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitPushWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitUpdateStatusWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "UpdateStatus";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitUpdateStatusWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FUpdateStatus>(InCommand.Operation);
|
||||||
|
|
||||||
|
if(InCommand.Files.Num() > 0)
|
||||||
|
{
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||||
|
|
||||||
|
if(Operation->ShouldUpdateHistory())
|
||||||
|
{
|
||||||
|
for(int32 Index = 0; Index < States.Num(); Index++)
|
||||||
|
{
|
||||||
|
FString& File = InCommand.Files[Index];
|
||||||
|
TGitSourceControlHistory History;
|
||||||
|
|
||||||
|
if(States[Index].IsConflicted())
|
||||||
|
{
|
||||||
|
// In case of a merge conflict, we first need to get the tip of the "remote branch" (MERGE_HEAD)
|
||||||
|
GitSourceControlUtils::RunGetHistory(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, File, true, InCommand.ErrorMessages, History);
|
||||||
|
}
|
||||||
|
// Get the history of the file in the current branch
|
||||||
|
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunGetHistory(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, File, false, InCommand.ErrorMessages, History);
|
||||||
|
Histories.Add(*File, History);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no path provided: only update the status of assets in Content/ directory and also Config files
|
||||||
|
TArray<FString> ProjectDirs;
|
||||||
|
ProjectDirs.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()));
|
||||||
|
ProjectDirs.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()));
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, ProjectDirs, InCommand.ErrorMessages, States);
|
||||||
|
}
|
||||||
|
|
||||||
|
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||||
|
|
||||||
|
// don't use the ShouldUpdateModifiedState() hint here as it is specific to Perforce: the above normal Git status has already told us this information (like Git and Mercurial)
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitUpdateStatusWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
bool bUpdated = GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>( "GitSourceControl" );
|
||||||
|
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||||
|
|
||||||
|
const FDateTime Now = FDateTime::Now();
|
||||||
|
|
||||||
|
// add history, if any
|
||||||
|
for(const auto& History : Histories)
|
||||||
|
{
|
||||||
|
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> State = Provider.GetStateInternal(History.Key);
|
||||||
|
State->History = History.Value;
|
||||||
|
State->TimeStamp = Now;
|
||||||
|
bUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitCopyWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitCopyWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
// Copy or Move operation on a single file : Git does not need an explicit copy nor move,
|
||||||
|
// but after a Move the Editor create a redirector file with the old asset name that points to the new asset.
|
||||||
|
// The redirector needs to be commited with the new asset to perform a real rename.
|
||||||
|
// => the following is to "MarkForAdd" the redirector, but it still need to be committed by selecting the whole directory and "check-in"
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), InCommand.Files, InCommand.InfoMessages, InCommand.ErrorMessages);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitCopyWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitResolveWorker::GetName() const
|
||||||
|
{
|
||||||
|
return "Resolve";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitResolveWorker::Execute( class FGitSourceControlCommand& InCommand )
|
||||||
|
{
|
||||||
|
check(InCommand.Operation->GetName() == GetName());
|
||||||
|
|
||||||
|
// mark the conflicting files as resolved:
|
||||||
|
TArray<FString> Results;
|
||||||
|
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), InCommand.Files, Results, InCommand.ErrorMessages);
|
||||||
|
|
||||||
|
// now update the status of our files
|
||||||
|
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ErrorMessages, States);
|
||||||
|
|
||||||
|
return InCommand.bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitResolveWorker::UpdateStates() const
|
||||||
|
{
|
||||||
|
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "IGitSourceControlWorker.h"
|
||||||
|
#include "GitSourceControlState.h"
|
||||||
|
|
||||||
|
#include "ISourceControlOperation.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal operation used to push local commits to configured remote origin
|
||||||
|
*/
|
||||||
|
class FGitPush : public ISourceControlOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// ISourceControlOperation interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
|
||||||
|
virtual FText GetInProgressString() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Called when first activated on a project, and then at project load time.
|
||||||
|
* Look for the root directory of the git repository (where the ".git/" subdirectory is located). */
|
||||||
|
class FGitConnectWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitConnectWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Lock (check-out) a set of files using Git LFS 2. */
|
||||||
|
class FGitCheckOutWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitCheckOutWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Commit (check-in) a set of files to the local depot. */
|
||||||
|
class FGitCheckInWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitCheckInWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Add an untraked file to source control (so only a subset of the git add command). */
|
||||||
|
class FGitMarkForAddWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitMarkForAddWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Delete a file and remove it from source control. */
|
||||||
|
class FGitDeleteWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitDeleteWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Revert any change to a file to its state on the local depot. */
|
||||||
|
class FGitRevertWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitRevertWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Git pull --rebase to update branch from its configured remote */
|
||||||
|
class FGitSyncWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitSyncWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Git push to publish branch for its configured remote */
|
||||||
|
class FGitPushWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitPushWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Get source control status of files on local working copy. */
|
||||||
|
class FGitUpdateStatusWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitUpdateStatusWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
|
||||||
|
/** Map of filenames to history */
|
||||||
|
TMap<FString, TGitSourceControlHistory> Histories;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Copy or Move operation on a single file */
|
||||||
|
class FGitCopyWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitCopyWorker() {}
|
||||||
|
// IGitSourceControlWorker interface
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** git add to mark a conflict as resolved */
|
||||||
|
class FGitResolveWorker : public IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FGitResolveWorker() {}
|
||||||
|
virtual FName GetName() const override;
|
||||||
|
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||||
|
virtual bool UpdateStates() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Temporary states for results */
|
||||||
|
TArray<FGitSourceControlState> States;
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ISourceControlModule.h"
|
||||||
|
#include "ISourceControlOperation.h"
|
||||||
|
#include "ISourceControlProvider.h"
|
||||||
|
#include "ISourceControlRevision.h"
|
||||||
|
#include "ISourceControlState.h"
|
||||||
|
#include "Misc/Paths.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
@ -0,0 +1,467 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlProvider.h"
|
||||||
|
|
||||||
|
#include "HAL/PlatformProcess.h"
|
||||||
|
#include "Misc/Paths.h"
|
||||||
|
#include "Misc/QueuedThreadPool.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
||||||
|
#include "GitSourceControlCommand.h"
|
||||||
|
#include "ISourceControlModule.h"
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
#include "GitSourceControlUtils.h"
|
||||||
|
#include "SGitSourceControlSettings.h"
|
||||||
|
#include "Logging/MessageLog.h"
|
||||||
|
#include "ScopedSourceControlProgress.h"
|
||||||
|
#include "SourceControlHelpers.h"
|
||||||
|
#include "SourceControlOperations.h"
|
||||||
|
#include "Interfaces/IPluginManager.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||||
|
|
||||||
|
static FName ProviderName("Git LFS 2");
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::Init(bool bForceConnection)
|
||||||
|
{
|
||||||
|
// Init() is called multiple times at startup: do not check git each time
|
||||||
|
if(!bGitAvailable)
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("GitSourceControl"));
|
||||||
|
if(Plugin.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Git plugin '%s'"), *(Plugin->GetDescriptor().VersionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckGitAvailability();
|
||||||
|
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
bUsingGitLfsLocking = GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
|
||||||
|
}
|
||||||
|
|
||||||
|
// bForceConnection: not used anymore
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::CheckGitAvailability()
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FString PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
if(PathToGitBinary.IsEmpty())
|
||||||
|
{
|
||||||
|
// Try to find Git binary, and update settings accordingly
|
||||||
|
PathToGitBinary = GitSourceControlUtils::FindGitBinaryPath();
|
||||||
|
if(!PathToGitBinary.IsEmpty())
|
||||||
|
{
|
||||||
|
GitSourceControl.AccessSettings().SetBinaryPath(PathToGitBinary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!PathToGitBinary.IsEmpty())
|
||||||
|
{
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("Using '%s'"), *PathToGitBinary);
|
||||||
|
bGitAvailable = GitSourceControlUtils::CheckGitAvailability(PathToGitBinary, &GitVersion);
|
||||||
|
if(bGitAvailable)
|
||||||
|
{
|
||||||
|
CheckRepositoryStatus(PathToGitBinary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bGitAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::CheckRepositoryStatus(const FString& InPathToGitBinary)
|
||||||
|
{
|
||||||
|
// Find the path to the root Git directory (if any, else uses the ProjectDir)
|
||||||
|
const FString PathToProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
|
||||||
|
bGitRepositoryFound = GitSourceControlUtils::FindRootDirectory(PathToProjectDir, PathToRepositoryRoot);
|
||||||
|
if(bGitRepositoryFound)
|
||||||
|
{
|
||||||
|
GitSourceControlMenu.Register();
|
||||||
|
|
||||||
|
// Get branch name
|
||||||
|
bGitRepositoryFound = GitSourceControlUtils::GetBranchName(InPathToGitBinary, PathToRepositoryRoot, BranchName);
|
||||||
|
if(bGitRepositoryFound)
|
||||||
|
{
|
||||||
|
GitSourceControlUtils::GetRemoteUrl(InPathToGitBinary, PathToRepositoryRoot, RemoteUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogSourceControl, Error, TEXT("'%s' is not a valid Git repository"), *PathToRepositoryRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogSourceControl, Warning, TEXT("'%s' is not part of a Git repository"), *FPaths::ProjectDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user name & email (of the repository, else from the global Git config)
|
||||||
|
GitSourceControlUtils::GetUserConfig(InPathToGitBinary, PathToRepositoryRoot, UserName, UserEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::Close()
|
||||||
|
{
|
||||||
|
// clear the cache
|
||||||
|
StateCache.Empty();
|
||||||
|
// Remove all extensions to the "Source Control" menu in the Editor Toolbar
|
||||||
|
GitSourceControlMenu.Unregister();
|
||||||
|
|
||||||
|
bGitAvailable = false;
|
||||||
|
bGitRepositoryFound = false;
|
||||||
|
UserName.Empty();
|
||||||
|
UserEmail.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> FGitSourceControlProvider::GetStateInternal(const FString& Filename)
|
||||||
|
{
|
||||||
|
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe>* State = StateCache.Find(Filename);
|
||||||
|
if(State != NULL)
|
||||||
|
{
|
||||||
|
// found cached item
|
||||||
|
return (*State);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// cache an unknown state for this item
|
||||||
|
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> NewState = MakeShareable( new FGitSourceControlState(Filename, bUsingGitLfsLocking) );
|
||||||
|
StateCache.Add(Filename, NewState);
|
||||||
|
return NewState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FGitSourceControlProvider::GetStatusText() const
|
||||||
|
{
|
||||||
|
FFormatNamedArguments Args;
|
||||||
|
Args.Add( TEXT("RepositoryName"), FText::FromString(PathToRepositoryRoot) );
|
||||||
|
Args.Add( TEXT("RemoteUrl"), FText::FromString(RemoteUrl) );
|
||||||
|
Args.Add( TEXT("UserName"), FText::FromString(UserName) );
|
||||||
|
Args.Add( TEXT("UserEmail"), FText::FromString(UserEmail) );
|
||||||
|
Args.Add( TEXT("BranchName"), FText::FromString(BranchName) );
|
||||||
|
Args.Add( TEXT("CommitId"), FText::FromString(CommitId.Left(8)) );
|
||||||
|
Args.Add( TEXT("CommitSummary"), FText::FromString(CommitSummary) );
|
||||||
|
|
||||||
|
return FText::Format( NSLOCTEXT("Status", "Provider: Git\nEnabledLabel", "Local repository: {RepositoryName}\nRemote origin: {RemoteUrl}\nUser: {UserName}\nE-mail: {UserEmail}\n[{BranchName} {CommitId}] {CommitSummary}"), Args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Quick check if source control is enabled */
|
||||||
|
bool FGitSourceControlProvider::IsEnabled() const
|
||||||
|
{
|
||||||
|
return bGitRepositoryFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Quick check if source control is available for use (useful for server-based providers) */
|
||||||
|
bool FGitSourceControlProvider::IsAvailable() const
|
||||||
|
{
|
||||||
|
return bGitRepositoryFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FName& FGitSourceControlProvider::GetName(void) const
|
||||||
|
{
|
||||||
|
return ProviderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECommandResult::Type FGitSourceControlProvider::GetState( const TArray<FString>& InFiles, TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> >& OutState, EStateCacheUsage::Type InStateCacheUsage )
|
||||||
|
{
|
||||||
|
if(!IsEnabled())
|
||||||
|
{
|
||||||
|
return ECommandResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FString> AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
|
||||||
|
|
||||||
|
if(InStateCacheUsage == EStateCacheUsage::ForceUpdate)
|
||||||
|
{
|
||||||
|
Execute(ISourceControlOperation::Create<FUpdateStatus>(), AbsoluteFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& AbsoluteFile : AbsoluteFiles)
|
||||||
|
{
|
||||||
|
OutState.Add(GetStateInternal(*AbsoluteFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ECommandResult::Succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FSourceControlStateRef> FGitSourceControlProvider::GetCachedStateByPredicate(TFunctionRef<bool(const FSourceControlStateRef&)> Predicate) const
|
||||||
|
{
|
||||||
|
TArray<FSourceControlStateRef> Result;
|
||||||
|
for(const auto& CacheItem : StateCache)
|
||||||
|
{
|
||||||
|
FSourceControlStateRef State = CacheItem.Value;
|
||||||
|
if(Predicate(State))
|
||||||
|
{
|
||||||
|
Result.Add(State);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlProvider::RemoveFileFromCache(const FString& Filename)
|
||||||
|
{
|
||||||
|
return StateCache.Remove(Filename) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get files in cache */
|
||||||
|
TArray<FString> FGitSourceControlProvider::GetFilesInCache()
|
||||||
|
{
|
||||||
|
TArray<FString> Files;
|
||||||
|
for (const auto& State : StateCache)
|
||||||
|
{
|
||||||
|
Files.Add(State.Key);
|
||||||
|
}
|
||||||
|
return Files;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDelegateHandle FGitSourceControlProvider::RegisterSourceControlStateChanged_Handle( const FSourceControlStateChanged::FDelegate& SourceControlStateChanged )
|
||||||
|
{
|
||||||
|
return OnSourceControlStateChanged.Add( SourceControlStateChanged );
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::UnregisterSourceControlStateChanged_Handle( FDelegateHandle Handle )
|
||||||
|
{
|
||||||
|
OnSourceControlStateChanged.Remove( Handle );
|
||||||
|
}
|
||||||
|
|
||||||
|
ECommandResult::Type FGitSourceControlProvider::Execute( const TSharedRef<ISourceControlOperation, ESPMode::ThreadSafe>& InOperation, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency, const FSourceControlOperationComplete& InOperationCompleteDelegate )
|
||||||
|
{
|
||||||
|
if(!IsEnabled() && !(InOperation->GetName() == "Connect")) // Only Connect operation allowed while not Enabled (Repository found)
|
||||||
|
{
|
||||||
|
InOperationCompleteDelegate.ExecuteIfBound(InOperation, ECommandResult::Failed);
|
||||||
|
return ECommandResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FString> AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
|
||||||
|
|
||||||
|
// Query to see if we allow this operation
|
||||||
|
TSharedPtr<IGitSourceControlWorker, ESPMode::ThreadSafe> Worker = CreateWorker(InOperation->GetName());
|
||||||
|
if(!Worker.IsValid())
|
||||||
|
{
|
||||||
|
// this operation is unsupported by this source control provider
|
||||||
|
FFormatNamedArguments Arguments;
|
||||||
|
Arguments.Add( TEXT("OperationName"), FText::FromName(InOperation->GetName()) );
|
||||||
|
Arguments.Add( TEXT("ProviderName"), FText::FromName(GetName()) );
|
||||||
|
FText Message(FText::Format(LOCTEXT("UnsupportedOperation", "Operation '{OperationName}' not supported by source control provider '{ProviderName}'"), Arguments));
|
||||||
|
FMessageLog("SourceControl").Error(Message);
|
||||||
|
InOperation->AddErrorMessge(Message);
|
||||||
|
|
||||||
|
InOperationCompleteDelegate.ExecuteIfBound(InOperation, ECommandResult::Failed);
|
||||||
|
return ECommandResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGitSourceControlCommand* Command = new FGitSourceControlCommand(InOperation, Worker.ToSharedRef());
|
||||||
|
Command->Files = AbsoluteFiles;
|
||||||
|
Command->OperationCompleteDelegate = InOperationCompleteDelegate;
|
||||||
|
|
||||||
|
// fire off operation
|
||||||
|
if(InConcurrency == EConcurrency::Synchronous)
|
||||||
|
{
|
||||||
|
Command->bAutoDelete = false;
|
||||||
|
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("ExecuteSynchronousCommand(%s)"), *InOperation->GetName().ToString());
|
||||||
|
return ExecuteSynchronousCommand(*Command, InOperation->GetInProgressString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Command->bAutoDelete = true;
|
||||||
|
|
||||||
|
UE_LOG(LogSourceControl, Log, TEXT("IssueAsynchronousCommand(%s)"), *InOperation->GetName().ToString());
|
||||||
|
return IssueCommand(*Command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlProvider::CanCancelOperation( const TSharedRef<ISourceControlOperation, ESPMode::ThreadSafe>& InOperation ) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::CancelOperation( const TSharedRef<ISourceControlOperation, ESPMode::ThreadSafe>& InOperation )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlProvider::UsesLocalReadOnlyState() const
|
||||||
|
{
|
||||||
|
return bUsingGitLfsLocking; // Git LFS Lock uses read-only state
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlProvider::UsesChangelists() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlProvider::UsesCheckout() const
|
||||||
|
{
|
||||||
|
return bUsingGitLfsLocking; // Git LFS Lock uses read-only state
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<IGitSourceControlWorker, ESPMode::ThreadSafe> FGitSourceControlProvider::CreateWorker(const FName& InOperationName) const
|
||||||
|
{
|
||||||
|
const FGetGitSourceControlWorker* Operation = WorkersMap.Find(InOperationName);
|
||||||
|
if(Operation != nullptr)
|
||||||
|
{
|
||||||
|
return Operation->Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::RegisterWorker( const FName& InName, const FGetGitSourceControlWorker& InDelegate )
|
||||||
|
{
|
||||||
|
WorkersMap.Add( InName, InDelegate );
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::OutputCommandMessages(const FGitSourceControlCommand& InCommand) const
|
||||||
|
{
|
||||||
|
FMessageLog SourceControlLog("SourceControl");
|
||||||
|
|
||||||
|
for(int32 ErrorIndex = 0; ErrorIndex < InCommand.ErrorMessages.Num(); ++ErrorIndex)
|
||||||
|
{
|
||||||
|
SourceControlLog.Error(FText::FromString(InCommand.ErrorMessages[ErrorIndex]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int32 InfoIndex = 0; InfoIndex < InCommand.InfoMessages.Num(); ++InfoIndex)
|
||||||
|
{
|
||||||
|
SourceControlLog.Info(FText::FromString(InCommand.InfoMessages[InfoIndex]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::UpdateRepositoryStatus(const class FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
// For all operations running UpdateStatus, get Commit informations:
|
||||||
|
if (!InCommand.CommitId.IsEmpty())
|
||||||
|
{
|
||||||
|
CommitId = InCommand.CommitId;
|
||||||
|
CommitSummary = InCommand.CommitSummary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlProvider::Tick()
|
||||||
|
{
|
||||||
|
bool bStatesUpdated = false;
|
||||||
|
|
||||||
|
for(int32 CommandIndex = 0; CommandIndex < CommandQueue.Num(); ++CommandIndex)
|
||||||
|
{
|
||||||
|
FGitSourceControlCommand& Command = *CommandQueue[CommandIndex];
|
||||||
|
if(Command.bExecuteProcessed)
|
||||||
|
{
|
||||||
|
// Remove command from the queue
|
||||||
|
CommandQueue.RemoveAt(CommandIndex);
|
||||||
|
|
||||||
|
// Update respository status on UpdateStatus operations
|
||||||
|
UpdateRepositoryStatus(Command);
|
||||||
|
|
||||||
|
// let command update the states of any files
|
||||||
|
bStatesUpdated |= Command.Worker->UpdateStates();
|
||||||
|
|
||||||
|
// dump any messages to output log
|
||||||
|
OutputCommandMessages(Command);
|
||||||
|
|
||||||
|
// run the completion delegate callback if we have one bound
|
||||||
|
Command.ReturnResults();
|
||||||
|
|
||||||
|
// commands that are left in the array during a tick need to be deleted
|
||||||
|
if(Command.bAutoDelete)
|
||||||
|
{
|
||||||
|
// Only delete commands that are not running 'synchronously'
|
||||||
|
delete &Command;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only do one command per tick loop, as we dont want concurrent modification
|
||||||
|
// of the command queue (which can happen in the completion delegate)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bStatesUpdated)
|
||||||
|
{
|
||||||
|
OnSourceControlStateChanged.Broadcast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray< TSharedRef<ISourceControlLabel> > FGitSourceControlProvider::GetLabels( const FString& InMatchingSpec ) const
|
||||||
|
{
|
||||||
|
TArray< TSharedRef<ISourceControlLabel> > Tags;
|
||||||
|
|
||||||
|
// NOTE list labels. Called by CrashDebugHelper() (to remote debug Engine crash)
|
||||||
|
// and by SourceControlHelpers::AnnotateFile() (to add source file to report)
|
||||||
|
// Reserved for internal use by Epic Games with Perforce only
|
||||||
|
return Tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SOURCE_CONTROL_WITH_SLATE
|
||||||
|
TSharedRef<class SWidget> FGitSourceControlProvider::MakeSettingsWidget() const
|
||||||
|
{
|
||||||
|
return SNew(SGitSourceControlSettings);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ECommandResult::Type FGitSourceControlProvider::ExecuteSynchronousCommand(FGitSourceControlCommand& InCommand, const FText& Task)
|
||||||
|
{
|
||||||
|
ECommandResult::Type Result = ECommandResult::Failed;
|
||||||
|
|
||||||
|
// Display the progress dialog if a string was provided
|
||||||
|
{
|
||||||
|
FScopedSourceControlProgress Progress(Task);
|
||||||
|
|
||||||
|
// Issue the command asynchronously...
|
||||||
|
IssueCommand( InCommand );
|
||||||
|
|
||||||
|
// ... then wait for its completion (thus making it synchronous)
|
||||||
|
while(!InCommand.bExecuteProcessed)
|
||||||
|
{
|
||||||
|
// Tick the command queue and update progress.
|
||||||
|
Tick();
|
||||||
|
|
||||||
|
Progress.Tick();
|
||||||
|
|
||||||
|
// Sleep so we don't busy-wait so much.
|
||||||
|
FPlatformProcess::Sleep(0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// always do one more Tick() to make sure the command queue is cleaned up.
|
||||||
|
Tick();
|
||||||
|
|
||||||
|
if(InCommand.bCommandSuccessful)
|
||||||
|
{
|
||||||
|
Result = ECommandResult::Succeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the command now (asynchronous commands are deleted in the Tick() method)
|
||||||
|
check(!InCommand.bAutoDelete);
|
||||||
|
|
||||||
|
// ensure commands that are not auto deleted do not end up in the command queue
|
||||||
|
if ( CommandQueue.Contains( &InCommand ) )
|
||||||
|
{
|
||||||
|
CommandQueue.Remove( &InCommand );
|
||||||
|
}
|
||||||
|
delete &InCommand;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECommandResult::Type FGitSourceControlProvider::IssueCommand(FGitSourceControlCommand& InCommand)
|
||||||
|
{
|
||||||
|
if(GThreadPool != nullptr)
|
||||||
|
{
|
||||||
|
// Queue this to our worker thread(s) for resolving
|
||||||
|
GThreadPool->AddQueuedWork(&InCommand);
|
||||||
|
CommandQueue.Add(&InCommand);
|
||||||
|
return ECommandResult::Succeeded;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FText Message(LOCTEXT("NoSCCThreads", "There are no threads available to process the source control command."));
|
||||||
|
|
||||||
|
FMessageLog("SourceControl").Error(Message);
|
||||||
|
InCommand.bCommandSuccessful = false;
|
||||||
|
InCommand.Operation->AddErrorMessge(Message);
|
||||||
|
|
||||||
|
return InCommand.ReturnResults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,210 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ISourceControlOperation.h"
|
||||||
|
#include "ISourceControlState.h"
|
||||||
|
#include "ISourceControlProvider.h"
|
||||||
|
#include "IGitSourceControlWorker.h"
|
||||||
|
#include "GitSourceControlState.h"
|
||||||
|
#include "GitSourceControlMenu.h"
|
||||||
|
|
||||||
|
class FGitSourceControlCommand;
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_RetVal(FGitSourceControlWorkerRef, FGetGitSourceControlWorker)
|
||||||
|
|
||||||
|
/// Git version and capabilites extracted from the string "git version 2.11.0.windows.3"
|
||||||
|
struct FGitVersion
|
||||||
|
{
|
||||||
|
// Git version extracted from the string "git version 2.11.0.windows.3" (Windows) or "git version 2.11.0" (Linux/Mac/Cygwin/WSL)
|
||||||
|
int Major; // 2 Major version number
|
||||||
|
int Minor; // 11 Minor version number
|
||||||
|
int Patch; // 0 Patch/bugfix number
|
||||||
|
int Windows; // 3 Windows specific revision number (under Windows only)
|
||||||
|
|
||||||
|
uint32 bHasCatFileWithFilters : 1;
|
||||||
|
uint32 bHasGitLfs : 1;
|
||||||
|
uint32 bHasGitLfsLocking : 1;
|
||||||
|
|
||||||
|
FGitVersion()
|
||||||
|
: Major(0)
|
||||||
|
, Minor(0)
|
||||||
|
, Patch(0)
|
||||||
|
, Windows(0)
|
||||||
|
, bHasCatFileWithFilters(false)
|
||||||
|
, bHasGitLfs(false)
|
||||||
|
, bHasGitLfsLocking(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsGreaterOrEqualThan(int InMajor, int InMinor) const
|
||||||
|
{
|
||||||
|
return (Major > InMajor) || (Major == InMajor && (Minor >= InMinor));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FGitSourceControlProvider : public ISourceControlProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Constructor */
|
||||||
|
FGitSourceControlProvider()
|
||||||
|
: bGitAvailable(false)
|
||||||
|
, bGitRepositoryFound(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ISourceControlProvider implementation */
|
||||||
|
virtual void Init(bool bForceConnection = true) override;
|
||||||
|
virtual void Close() override;
|
||||||
|
virtual FText GetStatusText() const override;
|
||||||
|
virtual bool IsEnabled() const override;
|
||||||
|
virtual bool IsAvailable() const override;
|
||||||
|
virtual const FName& GetName(void) const override;
|
||||||
|
virtual bool QueryStateBranchConfig(const FString& ConfigSrc, const FString& ConfigDest) /* override UE4.20 */ { return false; }
|
||||||
|
virtual void RegisterStateBranches(const TArray<FString>& BranchNames, const FString& ContentRoot) /* override UE4.20 */ {}
|
||||||
|
virtual int32 GetStateBranchIndex(const FString& InBranchName) const /* override UE4.20 */ { return INDEX_NONE; }
|
||||||
|
virtual ECommandResult::Type GetState( const TArray<FString>& InFiles, TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> >& OutState, EStateCacheUsage::Type InStateCacheUsage ) override;
|
||||||
|
virtual TArray<FSourceControlStateRef> GetCachedStateByPredicate(TFunctionRef<bool(const FSourceControlStateRef&)> Predicate) const override;
|
||||||
|
virtual FDelegateHandle RegisterSourceControlStateChanged_Handle(const FSourceControlStateChanged::FDelegate& SourceControlStateChanged) override;
|
||||||
|
virtual void UnregisterSourceControlStateChanged_Handle(FDelegateHandle Handle) override;
|
||||||
|
virtual ECommandResult::Type Execute(const TSharedRef<ISourceControlOperation, ESPMode::ThreadSafe>& InOperation, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency = EConcurrency::Synchronous, const FSourceControlOperationComplete& InOperationCompleteDelegate = FSourceControlOperationComplete()) override;
|
||||||
|
virtual bool CanCancelOperation( const TSharedRef<ISourceControlOperation, ESPMode::ThreadSafe>& InOperation ) const override;
|
||||||
|
virtual void CancelOperation( const TSharedRef<ISourceControlOperation, ESPMode::ThreadSafe>& InOperation ) override;
|
||||||
|
virtual bool UsesLocalReadOnlyState() const override;
|
||||||
|
virtual bool UsesChangelists() const override;
|
||||||
|
virtual bool UsesCheckout() const override;
|
||||||
|
virtual void Tick() override;
|
||||||
|
virtual TArray< TSharedRef<class ISourceControlLabel> > GetLabels( const FString& InMatchingSpec ) const override;
|
||||||
|
#if SOURCE_CONTROL_WITH_SLATE
|
||||||
|
virtual TSharedRef<class SWidget> MakeSettingsWidget() const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check configuration, else standard paths, and run a Git "version" command to check the availability of the binary.
|
||||||
|
*/
|
||||||
|
void CheckGitAvailability();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the .git/ repository and check it's status.
|
||||||
|
*/
|
||||||
|
void CheckRepositoryStatus(const FString& InPathToGitBinary);
|
||||||
|
|
||||||
|
/** Is git binary found and working. */
|
||||||
|
inline bool IsGitAvailable() const
|
||||||
|
{
|
||||||
|
return bGitAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Git version for feature checking */
|
||||||
|
inline const FGitVersion& GetGitVersion() const
|
||||||
|
{
|
||||||
|
return GitVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the path to the root of the Git repository: can be the ProjectDir itself, or any parent directory */
|
||||||
|
inline const FString& GetPathToRepositoryRoot() const
|
||||||
|
{
|
||||||
|
return PathToRepositoryRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Git config user.name */
|
||||||
|
inline const FString& GetUserName() const
|
||||||
|
{
|
||||||
|
return UserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Git config user.email */
|
||||||
|
inline const FString& GetUserEmail() const
|
||||||
|
{
|
||||||
|
return UserEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Git remote origin url */
|
||||||
|
inline const FString& GetRemoteUrl() const
|
||||||
|
{
|
||||||
|
return RemoteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper function used to update state cache */
|
||||||
|
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> GetStateInternal(const FString& Filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a worker with the provider.
|
||||||
|
* This is used internally so the provider can maintain a map of all available operations.
|
||||||
|
*/
|
||||||
|
void RegisterWorker( const FName& InName, const FGetGitSourceControlWorker& InDelegate );
|
||||||
|
|
||||||
|
/** Remove a named file from the state cache */
|
||||||
|
bool RemoveFileFromCache(const FString& Filename);
|
||||||
|
|
||||||
|
/** Get files in cache */
|
||||||
|
TArray<FString> GetFilesInCache();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Is git binary found and working. */
|
||||||
|
bool bGitAvailable;
|
||||||
|
|
||||||
|
/** Is git repository found. */
|
||||||
|
bool bGitRepositoryFound;
|
||||||
|
|
||||||
|
/** Is LFS File Locking enabled? */
|
||||||
|
bool bUsingGitLfsLocking = false;
|
||||||
|
|
||||||
|
/** Helper function for Execute() */
|
||||||
|
TSharedPtr<class IGitSourceControlWorker, ESPMode::ThreadSafe> CreateWorker(const FName& InOperationName) const;
|
||||||
|
|
||||||
|
/** Helper function for running command synchronously. */
|
||||||
|
ECommandResult::Type ExecuteSynchronousCommand(class FGitSourceControlCommand& InCommand, const FText& Task);
|
||||||
|
/** Issue a command asynchronously if possible. */
|
||||||
|
ECommandResult::Type IssueCommand(class FGitSourceControlCommand& InCommand);
|
||||||
|
|
||||||
|
/** Output any messages this command holds */
|
||||||
|
void OutputCommandMessages(const class FGitSourceControlCommand& InCommand) const;
|
||||||
|
|
||||||
|
/** Update repository status on Connect and UpdateStatus operations */
|
||||||
|
void UpdateRepositoryStatus(const class FGitSourceControlCommand& InCommand);
|
||||||
|
|
||||||
|
/** Path to the root of the Git repository: can be the ProjectDir itself, or any parent directory (found by the "Connect" operation) */
|
||||||
|
FString PathToRepositoryRoot;
|
||||||
|
|
||||||
|
/** Git config user.name (from local repository, else globally) */
|
||||||
|
FString UserName;
|
||||||
|
|
||||||
|
/** Git config user.email (from local repository, else globally) */
|
||||||
|
FString UserEmail;
|
||||||
|
|
||||||
|
/** Name of the current branch */
|
||||||
|
FString BranchName;
|
||||||
|
|
||||||
|
/** URL of the "origin" defaut remote server */
|
||||||
|
FString RemoteUrl;
|
||||||
|
|
||||||
|
/** Current Commit full SHA1 */
|
||||||
|
FString CommitId;
|
||||||
|
|
||||||
|
/** Current Commit description's Summary */
|
||||||
|
FString CommitSummary;
|
||||||
|
|
||||||
|
/** State cache */
|
||||||
|
TMap<FString, TSharedRef<class FGitSourceControlState, ESPMode::ThreadSafe> > StateCache;
|
||||||
|
|
||||||
|
/** The currently registered source control operations */
|
||||||
|
TMap<FName, FGetGitSourceControlWorker> WorkersMap;
|
||||||
|
|
||||||
|
/** Queue for commands given by the main thread */
|
||||||
|
TArray < FGitSourceControlCommand* > CommandQueue;
|
||||||
|
|
||||||
|
/** For notifying when the source control states in the cache have changed */
|
||||||
|
FSourceControlStateChanged OnSourceControlStateChanged;
|
||||||
|
|
||||||
|
/** Git version for feature checking */
|
||||||
|
FGitVersion GitVersion;
|
||||||
|
|
||||||
|
/** Source Control Menu Extension */
|
||||||
|
FGitSourceControlMenu GitSourceControlMenu;
|
||||||
|
};
|
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlRevision.h"
|
||||||
|
|
||||||
|
#include "HAL/FileManager.h"
|
||||||
|
#include "Misc/Paths.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
#include "GitSourceControlUtils.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||||
|
|
||||||
|
bool FGitSourceControlRevision::Get( FString& InOutFilename ) const
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const FString PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
const FString PathToRepositoryRoot = GitSourceControl.GetProvider().GetPathToRepositoryRoot();
|
||||||
|
|
||||||
|
// if a filename for the temp file wasn't supplied generate a unique-ish one
|
||||||
|
if(InOutFilename.Len() == 0)
|
||||||
|
{
|
||||||
|
// create the diff dir if we don't already have it (Git wont)
|
||||||
|
IFileManager::Get().MakeDirectory(*FPaths::DiffDir(), true);
|
||||||
|
// create a unique temp file name based on the unique commit Id
|
||||||
|
const FString TempFileName = FString::Printf(TEXT("%stemp-%s-%s"), *FPaths::DiffDir(), *CommitId, *FPaths::GetCleanFilename(Filename));
|
||||||
|
InOutFilename = FPaths::ConvertRelativePathToFull(TempFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff against the revision
|
||||||
|
const FString Parameter = FString::Printf(TEXT("%s:%s"), *CommitId, *Filename);
|
||||||
|
|
||||||
|
bool bCommandSuccessful;
|
||||||
|
if(FPaths::FileExists(InOutFilename))
|
||||||
|
{
|
||||||
|
bCommandSuccessful = true; // if the temp file already exists, reuse it directly
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bCommandSuccessful = GitSourceControlUtils::RunDumpToFile(PathToGitBinary, PathToRepositoryRoot, Parameter, InOutFilename);
|
||||||
|
}
|
||||||
|
return bCommandSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlRevision::GetAnnotated( TArray<FAnnotationLine>& OutLines ) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlRevision::GetAnnotated( FString& InOutFilename ) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlRevision::GetFilename() const
|
||||||
|
{
|
||||||
|
return Filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FGitSourceControlRevision::GetRevisionNumber() const
|
||||||
|
{
|
||||||
|
return RevisionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlRevision::GetRevision() const
|
||||||
|
{
|
||||||
|
return ShortCommitId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlRevision::GetDescription() const
|
||||||
|
{
|
||||||
|
return Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlRevision::GetUserName() const
|
||||||
|
{
|
||||||
|
return UserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlRevision::GetClientSpec() const
|
||||||
|
{
|
||||||
|
static FString EmptyString(TEXT(""));
|
||||||
|
return EmptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlRevision::GetAction() const
|
||||||
|
{
|
||||||
|
return Action;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlRevision::GetBranchSource() const
|
||||||
|
{
|
||||||
|
// if this revision was copied/moved from some other revision
|
||||||
|
return BranchSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDateTime& FGitSourceControlRevision::GetDate() const
|
||||||
|
{
|
||||||
|
return Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FGitSourceControlRevision::GetCheckInIdentifier() const
|
||||||
|
{
|
||||||
|
return CommitIdNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FGitSourceControlRevision::GetFileSize() const
|
||||||
|
{
|
||||||
|
return FileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ISourceControlRevision.h"
|
||||||
|
|
||||||
|
/** Revision of a file, linked to a specific commit */
|
||||||
|
class FGitSourceControlRevision : public ISourceControlRevision, public TSharedFromThis<FGitSourceControlRevision, ESPMode::ThreadSafe>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FGitSourceControlRevision()
|
||||||
|
: RevisionNumber(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ISourceControlRevision interface */
|
||||||
|
virtual bool Get( FString& InOutFilename ) const override;
|
||||||
|
virtual bool GetAnnotated( TArray<FAnnotationLine>& OutLines ) const override;
|
||||||
|
virtual bool GetAnnotated( FString& InOutFilename ) const override;
|
||||||
|
virtual const FString& GetFilename() const override;
|
||||||
|
virtual int32 GetRevisionNumber() const override;
|
||||||
|
virtual const FString& GetRevision() const override;
|
||||||
|
virtual const FString& GetDescription() const override;
|
||||||
|
virtual const FString& GetUserName() const override;
|
||||||
|
virtual const FString& GetClientSpec() const override;
|
||||||
|
virtual const FString& GetAction() const override;
|
||||||
|
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetBranchSource() const override;
|
||||||
|
virtual const FDateTime& GetDate() const override;
|
||||||
|
virtual int32 GetCheckInIdentifier() const override;
|
||||||
|
virtual int32 GetFileSize() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** The filename this revision refers to */
|
||||||
|
FString Filename;
|
||||||
|
|
||||||
|
/** The full hexadecimal SHA1 id of the commit this revision refers to */
|
||||||
|
FString CommitId;
|
||||||
|
|
||||||
|
/** The short hexadecimal SHA1 id (8 first hex char out of 40) of the commit: the string to display */
|
||||||
|
FString ShortCommitId;
|
||||||
|
|
||||||
|
/** The numeric value of the short SHA1 (8 first hex char out of 40) */
|
||||||
|
int32 CommitIdNumber;
|
||||||
|
|
||||||
|
/** The index of the revision in the history (SBlueprintRevisionMenu assumes order for the "Depot" label) */
|
||||||
|
int32 RevisionNumber;
|
||||||
|
|
||||||
|
/** The SHA1 identifier of the file at this revision */
|
||||||
|
FString FileHash;
|
||||||
|
|
||||||
|
/** The description of this revision */
|
||||||
|
FString Description;
|
||||||
|
|
||||||
|
/** The user that made the change */
|
||||||
|
FString UserName;
|
||||||
|
|
||||||
|
/** The action (add, edit, branch etc.) performed at this revision */
|
||||||
|
FString Action;
|
||||||
|
|
||||||
|
/** Source of move ("branch" in Perforce term) if any */
|
||||||
|
TSharedPtr<FGitSourceControlRevision, ESPMode::ThreadSafe> BranchSource;
|
||||||
|
|
||||||
|
/** The date this revision was made */
|
||||||
|
FDateTime Date;
|
||||||
|
|
||||||
|
/** The size of the file at this revision */
|
||||||
|
int32 FileSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** History composed of the last 100 revisions of the file */
|
||||||
|
typedef TArray< TSharedRef<FGitSourceControlRevision, ESPMode::ThreadSafe> > TGitSourceControlHistory;
|
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlSettings.h"
|
||||||
|
|
||||||
|
#include "Misc/ScopeLock.h"
|
||||||
|
#include "Misc/ConfigCacheIni.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "SourceControlHelpers.h"
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
#include "GitSourceControlUtils.h"
|
||||||
|
|
||||||
|
namespace GitSettingsConstants
|
||||||
|
{
|
||||||
|
|
||||||
|
/** The section of the ini file we load our settings from */
|
||||||
|
static const FString SettingsSection = TEXT("GitSourceControl.GitSourceControlSettings");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString FGitSourceControlSettings::GetBinaryPath() const
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
return BinaryPath; // Return a copy to be thread-safe
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlSettings::SetBinaryPath(const FString& InString)
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
const bool bChanged = (BinaryPath != InString);
|
||||||
|
if(bChanged)
|
||||||
|
{
|
||||||
|
BinaryPath = InString;
|
||||||
|
}
|
||||||
|
return bChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tell if using the Git LFS file Locking workflow */
|
||||||
|
bool FGitSourceControlSettings::IsUsingGitLfsLocking() const
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
return bUsingGitLfsLocking;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configure the usage of Git LFS file Locking workflow */
|
||||||
|
bool FGitSourceControlSettings::SetUsingGitLfsLocking(const bool InUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
const bool bChanged = (bUsingGitLfsLocking != InUsingGitLfsLocking);
|
||||||
|
bUsingGitLfsLocking = InUsingGitLfsLocking;
|
||||||
|
return bChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString FGitSourceControlSettings::GetLfsUserName() const
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
return LfsUserName; // Return a copy to be thread-safe
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlSettings::SetLfsUserName(const FString& InString)
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
const bool bChanged = (LfsUserName != InString);
|
||||||
|
if (bChanged)
|
||||||
|
{
|
||||||
|
LfsUserName = InString;
|
||||||
|
}
|
||||||
|
return bChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called at startup nearly before anything else in our module: BinaryPath will then be used by the provider
|
||||||
|
void FGitSourceControlSettings::LoadSettings()
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
const FString& IniFile = SourceControlHelpers::GetSettingsIni();
|
||||||
|
GConfig->GetString(*GitSettingsConstants::SettingsSection, TEXT("BinaryPath"), BinaryPath, IniFile);
|
||||||
|
GConfig->GetBool(*GitSettingsConstants::SettingsSection, TEXT("UsingGitLfsLocking"), bUsingGitLfsLocking, IniFile);
|
||||||
|
GConfig->GetString(*GitSettingsConstants::SettingsSection, TEXT("LfsUserName"), LfsUserName, IniFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGitSourceControlSettings::SaveSettings() const
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
const FString& IniFile = SourceControlHelpers::GetSettingsIni();
|
||||||
|
GConfig->SetString(*GitSettingsConstants::SettingsSection, TEXT("BinaryPath"), *BinaryPath, IniFile);
|
||||||
|
GConfig->SetBool(*GitSettingsConstants::SettingsSection, TEXT("UsingGitLfsLocking"), bUsingGitLfsLocking, IniFile);
|
||||||
|
GConfig->SetString(*GitSettingsConstants::SettingsSection, TEXT("LfsUserName"), *LfsUserName, IniFile);
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
class FGitSourceControlSettings
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Get the Git Binary Path */
|
||||||
|
const FString GetBinaryPath() const;
|
||||||
|
|
||||||
|
/** Set the Git Binary Path */
|
||||||
|
bool SetBinaryPath(const FString& InString);
|
||||||
|
|
||||||
|
/** Tell if using the Git LFS file Locking workflow */
|
||||||
|
bool IsUsingGitLfsLocking() const;
|
||||||
|
|
||||||
|
/** Configure the usage of Git LFS file Locking workflow */
|
||||||
|
bool SetUsingGitLfsLocking(const bool InUsingGitLfsLocking);
|
||||||
|
|
||||||
|
/** Get the username used by the Git LFS 2 File Locks server */
|
||||||
|
const FString GetLfsUserName() const;
|
||||||
|
|
||||||
|
/** Set the username used by the Git LFS 2 File Locks server */
|
||||||
|
bool SetLfsUserName(const FString& InString);
|
||||||
|
|
||||||
|
/** Load settings from ini file */
|
||||||
|
void LoadSettings();
|
||||||
|
|
||||||
|
/** Save settings to ini file */
|
||||||
|
void SaveSettings() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** A critical section for settings access */
|
||||||
|
mutable FCriticalSection CriticalSection;
|
||||||
|
|
||||||
|
/** Git binary path */
|
||||||
|
FString BinaryPath;
|
||||||
|
|
||||||
|
/** Tells if using the Git LFS file Locking workflow */
|
||||||
|
bool bUsingGitLfsLocking;
|
||||||
|
|
||||||
|
/** Username used by the Git LFS 2 File Locks server */
|
||||||
|
FString LfsUserName;
|
||||||
|
};
|
@ -0,0 +1,386 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "GitSourceControlState.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "GitSourceControl.State"
|
||||||
|
|
||||||
|
int32 FGitSourceControlState::GetHistorySize() const
|
||||||
|
{
|
||||||
|
return History.Num();
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::GetHistoryItem( int32 HistoryIndex ) const
|
||||||
|
{
|
||||||
|
check(History.IsValidIndex(HistoryIndex));
|
||||||
|
return History[HistoryIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::FindHistoryRevision( int32 RevisionNumber ) const
|
||||||
|
{
|
||||||
|
for(const auto& Revision : History)
|
||||||
|
{
|
||||||
|
if(Revision->GetRevisionNumber() == RevisionNumber)
|
||||||
|
{
|
||||||
|
return Revision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::FindHistoryRevision(const FString& InRevision) const
|
||||||
|
{
|
||||||
|
for(const auto& Revision : History)
|
||||||
|
{
|
||||||
|
if(Revision->GetRevision() == InRevision)
|
||||||
|
{
|
||||||
|
return Revision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::GetBaseRevForMerge() const
|
||||||
|
{
|
||||||
|
for(const auto& Revision : History)
|
||||||
|
{
|
||||||
|
// look for the the SHA1 id of the file, not the commit id (revision)
|
||||||
|
if(Revision->FileHash == PendingMergeBaseFileHash)
|
||||||
|
{
|
||||||
|
return Revision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo add Slate icons for git specific states (NotAtHead vs Conflicted...)
|
||||||
|
FName FGitSourceControlState::GetIconName() const
|
||||||
|
{
|
||||||
|
if(LockState == ELockState::Locked)
|
||||||
|
{
|
||||||
|
return FName("Subversion.CheckedOut");
|
||||||
|
}
|
||||||
|
else if(LockState == ELockState::LockedOther)
|
||||||
|
{
|
||||||
|
return FName("Subversion.CheckedOutByOtherUser");
|
||||||
|
}
|
||||||
|
else if (!IsCurrent())
|
||||||
|
{
|
||||||
|
return FName("Subversion.NotAtHeadRevision");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(WorkingCopyState)
|
||||||
|
{
|
||||||
|
case EWorkingCopyState::Modified:
|
||||||
|
if(bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
return FName("Subversion.NotInDepot");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return FName("Subversion.CheckedOut");
|
||||||
|
}
|
||||||
|
case EWorkingCopyState::Added:
|
||||||
|
return FName("Subversion.OpenForAdd");
|
||||||
|
case EWorkingCopyState::Renamed:
|
||||||
|
case EWorkingCopyState::Copied:
|
||||||
|
return FName("Subversion.Branched");
|
||||||
|
case EWorkingCopyState::Deleted: // Deleted & Missing files does not show in Content Browser
|
||||||
|
case EWorkingCopyState::Missing:
|
||||||
|
return FName("Subversion.MarkedForDelete");
|
||||||
|
case EWorkingCopyState::Conflicted:
|
||||||
|
return FName("Subversion.ModifiedOtherBranch");
|
||||||
|
case EWorkingCopyState::NotControlled:
|
||||||
|
return FName("Subversion.NotInDepot");
|
||||||
|
case EWorkingCopyState::Unknown:
|
||||||
|
case EWorkingCopyState::Unchanged: // Unchanged is the same as "Pristine" (not checked out) for Perforce, ie no icon
|
||||||
|
case EWorkingCopyState::Ignored:
|
||||||
|
default:
|
||||||
|
return NAME_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NAME_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FGitSourceControlState::GetSmallIconName() const
|
||||||
|
{
|
||||||
|
if(LockState == ELockState::Locked)
|
||||||
|
{
|
||||||
|
return FName("Subversion.CheckedOut_Small");
|
||||||
|
}
|
||||||
|
else if(LockState == ELockState::LockedOther)
|
||||||
|
{
|
||||||
|
return FName("Subversion.CheckedOutByOtherUser_Small");
|
||||||
|
}
|
||||||
|
else if (!IsCurrent())
|
||||||
|
{
|
||||||
|
return FName("Subversion.NotAtHeadRevision_Small");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(WorkingCopyState)
|
||||||
|
{
|
||||||
|
case EWorkingCopyState::Modified:
|
||||||
|
if(bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
return FName("Subversion.NotInDepot_Small");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return FName("Subversion.CheckedOut_Small");
|
||||||
|
}
|
||||||
|
case EWorkingCopyState::Added:
|
||||||
|
return FName("Subversion.OpenForAdd_Small");
|
||||||
|
case EWorkingCopyState::Renamed:
|
||||||
|
case EWorkingCopyState::Copied:
|
||||||
|
return FName("Subversion.Branched_Small");
|
||||||
|
case EWorkingCopyState::Deleted: // Deleted & Missing files can appear in the Submit to Source Control window
|
||||||
|
case EWorkingCopyState::Missing:
|
||||||
|
return FName("Subversion.MarkedForDelete_Small");
|
||||||
|
case EWorkingCopyState::Conflicted:
|
||||||
|
return FName("Subversion.ModifiedOtherBranch_Small");
|
||||||
|
case EWorkingCopyState::NotControlled:
|
||||||
|
return FName("Subversion.NotInDepot_Small");
|
||||||
|
case EWorkingCopyState::Unknown:
|
||||||
|
case EWorkingCopyState::Unchanged: // Unchanged is the same as "Pristine" (not checked out) for Perforce, ie no icon
|
||||||
|
case EWorkingCopyState::Ignored:
|
||||||
|
default:
|
||||||
|
return NAME_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NAME_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FGitSourceControlState::GetDisplayName() const
|
||||||
|
{
|
||||||
|
if(LockState == ELockState::Locked)
|
||||||
|
{
|
||||||
|
return LOCTEXT("Locked", "Locked For Editing");
|
||||||
|
}
|
||||||
|
else if(LockState == ELockState::LockedOther)
|
||||||
|
{
|
||||||
|
return FText::Format( LOCTEXT("LockedOther", "Locked by "), FText::FromString(LockUser) );
|
||||||
|
}
|
||||||
|
else if (!IsCurrent())
|
||||||
|
{
|
||||||
|
return LOCTEXT("NotCurrent", "Not current");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(WorkingCopyState)
|
||||||
|
{
|
||||||
|
case EWorkingCopyState::Unknown:
|
||||||
|
return LOCTEXT("Unknown", "Unknown");
|
||||||
|
case EWorkingCopyState::Unchanged:
|
||||||
|
return LOCTEXT("Unchanged", "Unchanged");
|
||||||
|
case EWorkingCopyState::Added:
|
||||||
|
return LOCTEXT("Added", "Added");
|
||||||
|
case EWorkingCopyState::Deleted:
|
||||||
|
return LOCTEXT("Deleted", "Deleted");
|
||||||
|
case EWorkingCopyState::Modified:
|
||||||
|
return LOCTEXT("Modified", "Modified");
|
||||||
|
case EWorkingCopyState::Renamed:
|
||||||
|
return LOCTEXT("Renamed", "Renamed");
|
||||||
|
case EWorkingCopyState::Copied:
|
||||||
|
return LOCTEXT("Copied", "Copied");
|
||||||
|
case EWorkingCopyState::Conflicted:
|
||||||
|
return LOCTEXT("ContentsConflict", "Contents Conflict");
|
||||||
|
case EWorkingCopyState::Ignored:
|
||||||
|
return LOCTEXT("Ignored", "Ignored");
|
||||||
|
case EWorkingCopyState::NotControlled:
|
||||||
|
return LOCTEXT("NotControlled", "Not Under Source Control");
|
||||||
|
case EWorkingCopyState::Missing:
|
||||||
|
return LOCTEXT("Missing", "Missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
return FText();
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FGitSourceControlState::GetDisplayTooltip() const
|
||||||
|
{
|
||||||
|
if(LockState == ELockState::Locked)
|
||||||
|
{
|
||||||
|
return LOCTEXT("Locked_Tooltip", "Locked for editing by current user");
|
||||||
|
}
|
||||||
|
else if(LockState == ELockState::LockedOther)
|
||||||
|
{
|
||||||
|
return FText::Format( LOCTEXT("LockedOther_Tooltip", "Locked for editing by: {0}"), FText::FromString(LockUser) );
|
||||||
|
}
|
||||||
|
else if (!IsCurrent())
|
||||||
|
{
|
||||||
|
return LOCTEXT("NotCurrent_Tooltip", "The file(s) are not at the head revision");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(WorkingCopyState)
|
||||||
|
{
|
||||||
|
case EWorkingCopyState::Unknown:
|
||||||
|
return LOCTEXT("Unknown_Tooltip", "Unknown source control state");
|
||||||
|
case EWorkingCopyState::Unchanged:
|
||||||
|
return LOCTEXT("Pristine_Tooltip", "There are no modifications");
|
||||||
|
case EWorkingCopyState::Added:
|
||||||
|
return LOCTEXT("Added_Tooltip", "Item is scheduled for addition");
|
||||||
|
case EWorkingCopyState::Deleted:
|
||||||
|
return LOCTEXT("Deleted_Tooltip", "Item is scheduled for deletion");
|
||||||
|
case EWorkingCopyState::Modified:
|
||||||
|
return LOCTEXT("Modified_Tooltip", "Item has been modified");
|
||||||
|
case EWorkingCopyState::Renamed:
|
||||||
|
return LOCTEXT("Renamed_Tooltip", "Item has been renamed");
|
||||||
|
case EWorkingCopyState::Copied:
|
||||||
|
return LOCTEXT("Copied_Tooltip", "Item has been copied");
|
||||||
|
case EWorkingCopyState::Conflicted:
|
||||||
|
return LOCTEXT("ContentsConflict_Tooltip", "The contents of the item conflict with updates received from the repository.");
|
||||||
|
case EWorkingCopyState::Ignored:
|
||||||
|
return LOCTEXT("Ignored_Tooltip", "Item is being ignored.");
|
||||||
|
case EWorkingCopyState::NotControlled:
|
||||||
|
return LOCTEXT("NotControlled_Tooltip", "Item is not under version control.");
|
||||||
|
case EWorkingCopyState::Missing:
|
||||||
|
return LOCTEXT("Missing_Tooltip", "Item is missing (e.g., you moved or deleted it without using Git). This also indicates that a directory is incomplete (a checkout or update was interrupted).");
|
||||||
|
}
|
||||||
|
|
||||||
|
return FText();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FString& FGitSourceControlState::GetFilename() const
|
||||||
|
{
|
||||||
|
return LocalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDateTime& FGitSourceControlState::GetTimeStamp() const
|
||||||
|
{
|
||||||
|
return TimeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleted and Missing assets cannot appear in the Content Browser, but the do in the Submit files to Source Control window!
|
||||||
|
bool FGitSourceControlState::CanCheckIn() const
|
||||||
|
{
|
||||||
|
if(bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
return ( ( (LockState == ELockState::Locked) && !IsConflicted() ) || (WorkingCopyState == EWorkingCopyState::Added) ) && IsCurrent();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (WorkingCopyState == EWorkingCopyState::Added
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Deleted
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Missing
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Modified
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Renamed) && IsCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::CanCheckout() const
|
||||||
|
{
|
||||||
|
if(bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
// We don't want to allow checkout if the file is out-of-date, as modifying an out-of-date binary file will most likely result in a merge conflict
|
||||||
|
return (WorkingCopyState == EWorkingCopyState::Unchanged || WorkingCopyState == EWorkingCopyState::Modified) && LockState == ELockState::NotLocked && IsCurrent();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false; // With Git all tracked files in the working copy are always already checked-out (as opposed to Perforce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsCheckedOut() const
|
||||||
|
{
|
||||||
|
if (bUsingGitLfsLocking)
|
||||||
|
{
|
||||||
|
return LockState == ELockState::Locked;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return IsSourceControlled(); // With Git all tracked files in the working copy are always checked-out (as opposed to Perforce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsCheckedOutOther(FString* Who) const
|
||||||
|
{
|
||||||
|
if (Who != NULL)
|
||||||
|
{
|
||||||
|
*Who = LockUser;
|
||||||
|
}
|
||||||
|
return LockState == ELockState::LockedOther;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsCurrent() const
|
||||||
|
{
|
||||||
|
return !bNewerVersionOnServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsSourceControlled() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState != EWorkingCopyState::NotControlled && WorkingCopyState != EWorkingCopyState::Ignored && WorkingCopyState != EWorkingCopyState::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsAdded() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState == EWorkingCopyState::Added;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsDeleted() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState == EWorkingCopyState::Deleted || WorkingCopyState == EWorkingCopyState::Missing;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsIgnored() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState == EWorkingCopyState::Ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::CanEdit() const
|
||||||
|
{
|
||||||
|
return IsCurrent(); // With Git all files in the working copy are always editable (as opposed to Perforce)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::CanDelete() const
|
||||||
|
{
|
||||||
|
return !IsCheckedOutOther() && IsSourceControlled() && IsCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsUnknown() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState == EWorkingCopyState::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsModified() const
|
||||||
|
{
|
||||||
|
// Warning: for Perforce, a checked-out file is locked for modification (whereas with Git all tracked files are checked-out),
|
||||||
|
// so for a clean "check-in" (commit) checked-out files unmodified should be removed from the changeset (the index)
|
||||||
|
// http://stackoverflow.com/questions/12357971/what-does-revert-unchanged-files-mean-in-perforce
|
||||||
|
//
|
||||||
|
// Thus, before check-in UE4 Editor call RevertUnchangedFiles() in PromptForCheckin() and CheckinFiles().
|
||||||
|
//
|
||||||
|
// So here we must take care to enumerate all states that need to be commited,
|
||||||
|
// all other will be discarded :
|
||||||
|
// - Unknown
|
||||||
|
// - Unchanged
|
||||||
|
// - NotControlled
|
||||||
|
// - Ignored
|
||||||
|
return WorkingCopyState == EWorkingCopyState::Added
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Deleted
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Modified
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Renamed
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Copied
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Missing
|
||||||
|
|| WorkingCopyState == EWorkingCopyState::Conflicted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FGitSourceControlState::CanAdd() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState == EWorkingCopyState::NotControlled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::IsConflicted() const
|
||||||
|
{
|
||||||
|
return WorkingCopyState == EWorkingCopyState::Conflicted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGitSourceControlState::CanRevert() const
|
||||||
|
{
|
||||||
|
return CanCheckIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ISourceControlState.h"
|
||||||
|
#include "ISourceControlRevision.h"
|
||||||
|
#include "GitSourceControlRevision.h"
|
||||||
|
|
||||||
|
namespace EWorkingCopyState
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Unchanged, // called "clean" in SVN, "Pristine" in Perforce
|
||||||
|
Added,
|
||||||
|
Deleted,
|
||||||
|
Modified,
|
||||||
|
Renamed,
|
||||||
|
Copied,
|
||||||
|
Missing,
|
||||||
|
Conflicted,
|
||||||
|
NotControlled,
|
||||||
|
Ignored,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ELockState
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
NotLocked,
|
||||||
|
Locked,
|
||||||
|
LockedOther,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class FGitSourceControlState : public ISourceControlState, public TSharedFromThis<FGitSourceControlState, ESPMode::ThreadSafe>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FGitSourceControlState( const FString& InLocalFilename, const bool InUsingLfsLocking)
|
||||||
|
: LocalFilename(InLocalFilename)
|
||||||
|
, WorkingCopyState(EWorkingCopyState::Unknown)
|
||||||
|
, LockState(ELockState::Unknown)
|
||||||
|
, bUsingGitLfsLocking(InUsingLfsLocking)
|
||||||
|
, bNewerVersionOnServer(false)
|
||||||
|
, TimeStamp(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ISourceControlState interface */
|
||||||
|
virtual int32 GetHistorySize() const override;
|
||||||
|
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetHistoryItem(int32 HistoryIndex) const override;
|
||||||
|
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FindHistoryRevision(int32 RevisionNumber) const override;
|
||||||
|
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FindHistoryRevision(const FString& InRevision) const override;
|
||||||
|
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetBaseRevForMerge() const override;
|
||||||
|
virtual FName GetIconName() const override;
|
||||||
|
virtual FName GetSmallIconName() const override;
|
||||||
|
virtual FText GetDisplayName() const override;
|
||||||
|
virtual FText GetDisplayTooltip() const override;
|
||||||
|
virtual const FString& GetFilename() const override;
|
||||||
|
virtual const FDateTime& GetTimeStamp() const override;
|
||||||
|
virtual bool CanCheckIn() const override;
|
||||||
|
virtual bool CanCheckout() const override;
|
||||||
|
virtual bool IsCheckedOut() const override;
|
||||||
|
virtual bool IsCheckedOutOther(FString* Who = nullptr) const override;
|
||||||
|
virtual bool IsCheckedOutInOtherBranch(const FString& CurrentBranch = FString()) const /* UE4.20 override */ { return false; }
|
||||||
|
virtual bool IsModifiedInOtherBranch(const FString& CurrentBranch = FString()) const /* UE4.20 override */ { return false; }
|
||||||
|
virtual bool IsCheckedOutOrModifiedInOtherBranch(const FString& CurrentBranch = FString()) const /* UE4.20 override */ { return IsCheckedOutInOtherBranch(CurrentBranch) || IsModifiedInOtherBranch(CurrentBranch); }
|
||||||
|
virtual TArray<FString> GetCheckedOutBranches() const /* UE4.20 override */ { return TArray<FString>(); }
|
||||||
|
virtual FString GetOtherUserBranchCheckedOuts() const /* UE4.20 override */ { return FString(); }
|
||||||
|
virtual bool GetOtherBranchHeadModification(FString& HeadBranchOut, FString& ActionOut, int32& HeadChangeListOut) const /* UE4.20 override */ { return false; }
|
||||||
|
virtual bool IsCurrent() const override;
|
||||||
|
virtual bool IsSourceControlled() const override;
|
||||||
|
virtual bool IsAdded() const override;
|
||||||
|
virtual bool IsDeleted() const override;
|
||||||
|
virtual bool IsIgnored() const override;
|
||||||
|
virtual bool CanEdit() const override;
|
||||||
|
virtual bool CanDelete() const override;
|
||||||
|
virtual bool IsUnknown() const override;
|
||||||
|
virtual bool IsModified() const override;
|
||||||
|
virtual bool CanAdd() const override;
|
||||||
|
virtual bool IsConflicted() const override;
|
||||||
|
virtual bool CanRevert() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** History of the item, if any */
|
||||||
|
TGitSourceControlHistory History;
|
||||||
|
|
||||||
|
/** Filename on disk */
|
||||||
|
FString LocalFilename;
|
||||||
|
|
||||||
|
/** File Id with which our local revision diverged from the remote revision */
|
||||||
|
FString PendingMergeBaseFileHash;
|
||||||
|
|
||||||
|
/** State of the working copy */
|
||||||
|
EWorkingCopyState::Type WorkingCopyState;
|
||||||
|
|
||||||
|
/** Lock state */
|
||||||
|
ELockState::Type LockState;
|
||||||
|
|
||||||
|
/** Name of user who has locked the file */
|
||||||
|
FString LockUser;
|
||||||
|
|
||||||
|
/** Tells if using the Git LFS file Locking workflow */
|
||||||
|
bool bUsingGitLfsLocking;
|
||||||
|
|
||||||
|
/** Whether a newer version exists on the server */
|
||||||
|
bool bNewerVersionOnServer;
|
||||||
|
|
||||||
|
/** The timestamp of the last update */
|
||||||
|
FDateTime TimeStamp;
|
||||||
|
};
|
@ -0,0 +1,220 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GitSourceControlState.h"
|
||||||
|
|
||||||
|
class FGitSourceControlCommand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper struct for maintaining temporary files for passing to commands
|
||||||
|
*/
|
||||||
|
class FGitScopedTempFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor - open & write string to temp file */
|
||||||
|
FGitScopedTempFile(const FText& InText);
|
||||||
|
|
||||||
|
/** Destructor - delete temp file */
|
||||||
|
~FGitScopedTempFile();
|
||||||
|
|
||||||
|
/** Get the filename of this temp file - empty if it failed to be created */
|
||||||
|
const FString& GetFilename() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** The filename we are writing to */
|
||||||
|
FString Filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FGitVersion;
|
||||||
|
|
||||||
|
namespace GitSourceControlUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the path to the Git binary, looking into a few places (standalone Git install, and other common tools embedding Git)
|
||||||
|
* @returns the path to the Git binary if found, or an empty string.
|
||||||
|
*/
|
||||||
|
FString FindGitBinaryPath();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git "version" command to check the availability of the binary.
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param OutGitVersion If provided, populate with the git version parsed from "version" command
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool CheckGitAvailability(const FString& InPathToGitBinary, FGitVersion* OutVersion = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the output from the "version" command into GitMajorVersion and GitMinorVersion.
|
||||||
|
* @param InVersionString The version string returned by `git --version`
|
||||||
|
* @param OutVersion The FGitVersion to populate
|
||||||
|
*/
|
||||||
|
void ParseGitVersion(const FString& InVersionString, FGitVersion* OutVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check git for various optional capabilities by various means.
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param OutGitVersion If provided, populate with the git version parsed from "version" command
|
||||||
|
*/
|
||||||
|
void FindGitCapabilities(const FString& InPathToGitBinary, FGitVersion *OutVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git "lfs" command to check the availability of the "Large File System" extension.
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param OutGitVersion If provided, populate with the git version parsed from "version" command
|
||||||
|
*/
|
||||||
|
void FindGitLfsCapabilities(const FString& InPathToGitBinary, FGitVersion *OutVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the root of the Git repository, looking from the provided path and upward in its parent directories
|
||||||
|
* @param InPath The path to the Game Directory (or any path or file in any git repository)
|
||||||
|
* @param OutRepositoryRoot The path to the root directory of the Git repository if found, else the path to the ProjectDir
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool FindRootDirectory(const FString& InPath, FString& OutRepositoryRoot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Git config user.name & user.email
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
|
||||||
|
* @param OutUserName Name of the Git user configured for this repository (or globaly)
|
||||||
|
* @param OutEmailName E-mail of the Git user configured for this repository (or globaly)
|
||||||
|
*/
|
||||||
|
void GetUserConfig(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutUserName, FString& OutUserEmail);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Git current checked-out branch
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param OutBranchName Name of the current checked-out branch (if any, ie. not in detached HEAD)
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool GetBranchName(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutBranchName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Git current commit details
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param OutCommitId Current Commit full SHA1
|
||||||
|
* @param OutCommitSummary Current Commit description's Summary
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool GetCommitInfo(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutCommitId, FString& OutCommitSummary);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL of the "origin" defaut remote server
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param OutRemoteUrl URL of "origin" defaut remote server
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool GetRemoteUrl(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutRemoteUrl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git command - output is a string TArray.
|
||||||
|
*
|
||||||
|
* @param InCommand The Git command - e.g. commit
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
|
||||||
|
* @param InParameters The parameters to the Git command
|
||||||
|
* @param InFiles The files to be operated on
|
||||||
|
* @param OutResults The results (from StdOut) as an array per-line
|
||||||
|
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool RunCommand(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray<FString>& InParameters, const TArray<FString>& InFiles, TArray<FString>& OutResults, TArray<FString>& OutErrorMessages);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git "commit" command by batches.
|
||||||
|
*
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param InParameter The parameters to the Git commit command
|
||||||
|
* @param InFiles The files to be operated on
|
||||||
|
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool RunCommit(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray<FString>& InParameters, const TArray<FString>& InFiles, TArray<FString>& OutResults, TArray<FString>& OutErrorMessages);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git "status" command and parse it.
|
||||||
|
*
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
|
||||||
|
* @param InUsingLfsLocking Tells if using the Git LFS file Locking workflow
|
||||||
|
* @param InFiles The files to be operated on
|
||||||
|
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool RunUpdateStatus(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool InUsingLfsLocking, const TArray<FString>& InFiles, TArray<FString>& OutErrorMessages, TArray<FGitSourceControlState>& OutStates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git "cat-file" command to dump the binary content of a revision into a file.
|
||||||
|
*
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param InParameter The parameters to the Git show command (rev:path)
|
||||||
|
* @param InDumpFileName The temporary file to dump the revision
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool RunDumpToFile(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InParameter, const FString& InDumpFileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a Git "log" command and parse it.
|
||||||
|
*
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param InFile The file to be operated on
|
||||||
|
* @param bMergeConflict In case of a merge conflict, we also need to get the tip of the "remote branch" (MERGE_HEAD) before the log of the "current branch" (HEAD)
|
||||||
|
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
|
||||||
|
* @param OutHistory The history of the file
|
||||||
|
*/
|
||||||
|
bool RunGetHistory(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InFile, bool bMergeConflict, TArray<FString>& OutErrorMessages, TGitSourceControlHistory& OutHistory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to convert a filename array to relative paths.
|
||||||
|
* @param InFileNames The filename array
|
||||||
|
* @param InRelativeTo Path to the WorkspaceRoot
|
||||||
|
* @return an array of filenames, transformed into relative paths
|
||||||
|
*/
|
||||||
|
TArray<FString> RelativeFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to convert a filename array to absolute paths.
|
||||||
|
* @param InFileNames The filename array (relative paths)
|
||||||
|
* @param InRelativeTo Path to the WorkspaceRoot
|
||||||
|
* @return an array of filenames, transformed into absolute paths
|
||||||
|
*/
|
||||||
|
TArray<FString> AbsoluteFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for various commands to update cached states.
|
||||||
|
* @returns true if any states were updated
|
||||||
|
*/
|
||||||
|
bool UpdateCachedStates(const TArray<FGitSourceControlState>& InStates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove redundant errors (that contain a particular string) and also
|
||||||
|
* update the commands success status if all errors were removed.
|
||||||
|
*/
|
||||||
|
void RemoveRedundantErrors(FGitSourceControlCommand& InCommand, const FString& InFilter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run 'git lfs locks" to extract all lock information for all files in the repository
|
||||||
|
*
|
||||||
|
* @param InPathToGitBinary The path to the Git binary
|
||||||
|
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
|
||||||
|
* @param bAbsolutePaths Whether to report absolute filenames, false for repo-relative
|
||||||
|
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
|
||||||
|
* @param OutLocks The lock results (file, username)
|
||||||
|
* @returns true if the command succeeded and returned no errors
|
||||||
|
*/
|
||||||
|
bool GetAllLocks(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool bAbsolutePaths, TArray<FString>& OutErrorMessages, TMap<FString, FString>& OutLocks);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
class IGitSourceControlWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Name describing the work that this worker does. Used for factory method hookup.
|
||||||
|
*/
|
||||||
|
virtual FName GetName() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that actually does the work. Can be executed on another thread.
|
||||||
|
*/
|
||||||
|
virtual bool Execute( class FGitSourceControlCommand& InCommand ) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state of any items after completion (if necessary). This is always executed on the main thread.
|
||||||
|
* @returns true if states were updated
|
||||||
|
*/
|
||||||
|
virtual bool UpdateStates() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef TSharedRef<IGitSourceControlWorker, ESPMode::ThreadSafe> FGitSourceControlWorkerRef;
|
@ -0,0 +1,750 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#include "SGitSourceControlSettings.h"
|
||||||
|
|
||||||
|
#include "Fonts/SlateFontInfo.h"
|
||||||
|
#include "Misc/App.h"
|
||||||
|
#include "Misc/FileHelper.h"
|
||||||
|
#include "Misc/Paths.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "Styling/SlateTypes.h"
|
||||||
|
#include "Widgets/SBoxPanel.h"
|
||||||
|
#include "Widgets/Text/STextBlock.h"
|
||||||
|
#include "Widgets/Input/SButton.h"
|
||||||
|
#include "Widgets/Input/SCheckBox.h"
|
||||||
|
#include "Widgets/Input/SEditableTextBox.h"
|
||||||
|
#include "Widgets/Input/SFilePathPicker.h"
|
||||||
|
#include "Widgets/Input/SMultiLineEditableTextBox.h"
|
||||||
|
#include "Widgets/Layout/SBorder.h"
|
||||||
|
#include "Widgets/Layout/SSeparator.h"
|
||||||
|
#include "Widgets/Notifications/SNotificationList.h"
|
||||||
|
#include "Framework/Notifications/NotificationManager.h"
|
||||||
|
#include "EditorDirectories.h"
|
||||||
|
#include "EditorStyleSet.h"
|
||||||
|
#include "SourceControlOperations.h"
|
||||||
|
#include "GitSourceControlModule.h"
|
||||||
|
#include "GitSourceControlUtils.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "SGitSourceControlSettings"
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::Construct(const FArguments& InArgs)
|
||||||
|
{
|
||||||
|
const FSlateFontInfo Font = FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"));
|
||||||
|
|
||||||
|
bAutoCreateGitIgnore = true;
|
||||||
|
bAutoCreateReadme = true;
|
||||||
|
bAutoCreateGitAttributes = false;
|
||||||
|
bAutoInitialCommit = true;
|
||||||
|
|
||||||
|
InitialCommitMessage = LOCTEXT("InitialCommitMessage", "Initial commit");
|
||||||
|
|
||||||
|
const FText FileFilterType = NSLOCTEXT("GitSourceControl", "Executables", "Executables");
|
||||||
|
#if PLATFORM_WINDOWS
|
||||||
|
const FString FileFilterText = FString::Printf(TEXT("%s (*.exe)|*.exe"), *FileFilterType.ToString());
|
||||||
|
#else
|
||||||
|
const FString FileFilterText = FString::Printf(TEXT("%s"), *FileFilterType.ToString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ReadmeContent = FText::FromString(FString(TEXT("# ")) + FApp::GetProjectName() + "\n\nDeveloped with Unreal Engine 4\n");
|
||||||
|
|
||||||
|
ChildSlot
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage( FEditorStyle::GetBrush("DetailsView.CategoryBottom"))
|
||||||
|
.Padding(FMargin(0.0f, 3.0f, 0.0f, 0.0f))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
// Path to the Git command line executable
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.ToolTipText(LOCTEXT("BinaryPathLabel_Tooltip", "Path to Git binary"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("BinaryPathLabel", "Git Path"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
[
|
||||||
|
SNew(SFilePathPicker)
|
||||||
|
.BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
|
||||||
|
.BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
|
||||||
|
.BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN))
|
||||||
|
.BrowseTitle(LOCTEXT("BinaryPathBrowseTitle", "File picker..."))
|
||||||
|
.FilePath(this, &SGitSourceControlSettings::GetBinaryPathString)
|
||||||
|
.FileTypeFilter(FileFilterText)
|
||||||
|
.OnPathPicked(this, &SGitSourceControlSettings::OnBinaryPathPicked)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Root of the local repository
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.ToolTipText(LOCTEXT("RepositoryRootLabel_Tooltip", "Path to the root of the Git repository"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("RepositoryRootLabel", "Root of the repository"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetPathToRepositoryRoot)
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// User Name
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.ToolTipText(LOCTEXT("GitUserName_Tooltip", "User name configured for the Git repository"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("GitUserName", "User Name"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetUserName)
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// User e-mail
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.FillHeight(1.0f)
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.ToolTipText(LOCTEXT("GitUserEmail_Tooltip", "User e-mail configured for the Git repository"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("GitUserEmail", "E-Mail"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetUserEmail)
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Separator
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SSeparator)
|
||||||
|
]
|
||||||
|
// Explanation text
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.FillHeight(1.0f)
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("RepositoryNotFound", "Current Project is not contained in a Git Repository. Fill the form below to initialize a new Repository."))
|
||||||
|
.ToolTipText(LOCTEXT("RepositoryNotFound_Tooltip", "No Repository found at the level or above the current Project"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Option to configure the URL of the default remote 'origin'
|
||||||
|
// TODO: option to configure the name of the remote instead of the default origin
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
.ToolTipText(LOCTEXT("ConfigureOrigin_Tooltip", "Configure the URL of the default remote 'origin'"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("ConfigureOrigin", "URL of the remote server 'origin'"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SEditableTextBox)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetRemoteUrl)
|
||||||
|
.OnTextCommitted(this, &SGitSourceControlSettings::OnRemoteUrlCommited)
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Option to add a proper .gitignore file (true by default)
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
.ToolTipText(LOCTEXT("CreateGitIgnore_Tooltip", "Create and add a standard '.gitignore' file"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.1f)
|
||||||
|
[
|
||||||
|
SNew(SCheckBox)
|
||||||
|
.IsChecked(ECheckBoxState::Checked)
|
||||||
|
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateGitIgnore)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.9f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("CreateGitIgnore", "Add a .gitignore file"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Option to add a README.md file with custom content
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
.ToolTipText(LOCTEXT("CreateReadme_Tooltip", "Add a README.md file"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.1f)
|
||||||
|
[
|
||||||
|
SNew(SCheckBox)
|
||||||
|
.IsChecked(ECheckBoxState::Checked)
|
||||||
|
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateReadme)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.9f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("CreateReadme", "Add a basic README.md file"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
.Padding(2.0f)
|
||||||
|
[
|
||||||
|
SNew(SMultiLineEditableTextBox)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetReadmeContent)
|
||||||
|
.OnTextCommitted(this, &SGitSourceControlSettings::OnReadmeContentCommited)
|
||||||
|
.IsEnabled(this, &SGitSourceControlSettings::GetAutoCreateReadme)
|
||||||
|
.SelectAllTextWhenFocused(true)
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Option to add a proper .gitattributes file for Git LFS (false by default)
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
.ToolTipText(LOCTEXT("CreateGitAttributes_Tooltip", "Create and add a '.gitattributes' file to enable Git LFS for the whole 'Content/' directory (needs Git LFS extensions to be installed)."))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.1f)
|
||||||
|
[
|
||||||
|
SNew(SCheckBox)
|
||||||
|
.IsChecked(ECheckBoxState::Unchecked)
|
||||||
|
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateGitAttributes)
|
||||||
|
.IsEnabled(this, &SGitSourceControlSettings::CanInitializeGitLfs)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.9f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("CreateGitAttributes", "Add a .gitattributes file to enable Git LFS"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Option to use the Git LFS File Locking workflow (false by default)
|
||||||
|
// Enabled even after init to switch it off in case of no network
|
||||||
|
// TODO LFS turning it off afterwards does not work because all files are readonly !
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.ToolTipText(LOCTEXT("UseGitLfsLocking_Tooltip", "Uses Git LFS 2 File Locking workflow (CheckOut and Commit/Push)."))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.1f)
|
||||||
|
[
|
||||||
|
SNew(SCheckBox)
|
||||||
|
.IsChecked(SGitSourceControlSettings::IsUsingGitLfsLocking())
|
||||||
|
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedUseGitLfsLocking)
|
||||||
|
.IsEnabled(this, &SGitSourceControlSettings::CanUseGitLfsLocking)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.9f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("UseGitLfsLocking", "Uses Git LFS 2 File Locking workflow"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
// Username credential used to access the Git LFS 2 File Locks server
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SEditableTextBox)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetLfsUserName)
|
||||||
|
.OnTextCommitted(this, &SGitSourceControlSettings::OnLfsUserNameCommited)
|
||||||
|
.IsEnabled(this, &SGitSourceControlSettings::GetIsUsingGitLfsLocking)
|
||||||
|
.HintText(LOCTEXT("LfsUserName_Hint", "Username to lock files on the LFS server"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Option to Make the initial Git commit with custom message
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
.ToolTipText(LOCTEXT("InitialGitCommit_Tooltip", "Make the initial Git commit"))
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.1f)
|
||||||
|
[
|
||||||
|
SNew(SCheckBox)
|
||||||
|
.IsChecked(ECheckBoxState::Checked)
|
||||||
|
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedInitialCommit)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(0.9f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(LOCTEXT("InitialGitCommit", "Make the initial Git commit"))
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0f)
|
||||||
|
.Padding(2.0f)
|
||||||
|
[
|
||||||
|
SNew(SMultiLineEditableTextBox)
|
||||||
|
.Text(this, &SGitSourceControlSettings::GetInitialCommitMessage)
|
||||||
|
.OnTextCommitted(this, &SGitSourceControlSettings::OnInitialCommitMessageCommited)
|
||||||
|
.IsEnabled(this, &SGitSourceControlSettings::GetAutoInitialCommit)
|
||||||
|
.SelectAllTextWhenFocused(true)
|
||||||
|
.Font(Font)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
// Button to initialize the project with Git, create .gitignore/.gitattributes files, and make the first commit)
|
||||||
|
+SVerticalBox::Slot()
|
||||||
|
.FillHeight(2.5f)
|
||||||
|
.Padding(4.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||||
|
+SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.Text(LOCTEXT("GitInitRepository", "Initialize project with Git"))
|
||||||
|
.ToolTipText(LOCTEXT("GitInitRepository_Tooltip", "Initialize current project as a new Git repository"))
|
||||||
|
.OnClicked(this, &SGitSourceControlSettings::OnClickedInitializeGitRepository)
|
||||||
|
.IsEnabled(this, &SGitSourceControlSettings::CanInitializeGitRepository)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
.ContentPadding(6)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
SGitSourceControlSettings::~SGitSourceControlSettings()
|
||||||
|
{
|
||||||
|
RemoveInProgressNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString SGitSourceControlSettings::GetBinaryPathString() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
return GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnBinaryPathPicked( const FString& PickedPath ) const
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
FString PickedFullPath = FPaths::ConvertRelativePathToFull(PickedPath);
|
||||||
|
const bool bChanged = GitSourceControl.AccessSettings().SetBinaryPath(PickedFullPath);
|
||||||
|
if(bChanged)
|
||||||
|
{
|
||||||
|
// Re-Check provided git binary path for each change
|
||||||
|
GitSourceControl.GetProvider().CheckGitAvailability();
|
||||||
|
if(GitSourceControl.GetProvider().IsGitAvailable())
|
||||||
|
{
|
||||||
|
GitSourceControl.SaveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetPathToRepositoryRoot() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
return FText::FromString(GitSourceControl.GetProvider().GetPathToRepositoryRoot());
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetUserName() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
return FText::FromString(GitSourceControl.GetProvider().GetUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetUserEmail() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
return FText::FromString(GitSourceControl.GetProvider().GetUserEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
EVisibility SGitSourceControlSettings::MustInitializeGitRepository() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const bool bGitAvailable = GitSourceControl.GetProvider().IsGitAvailable();
|
||||||
|
const bool bGitRepositoryFound = GitSourceControl.GetProvider().IsEnabled();
|
||||||
|
return (bGitAvailable && !bGitRepositoryFound) ? EVisibility::Visible : EVisibility::Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SGitSourceControlSettings::CanInitializeGitRepository() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const bool bGitAvailable = GitSourceControl.GetProvider().IsGitAvailable();
|
||||||
|
const bool bGitRepositoryFound = GitSourceControl.GetProvider().IsEnabled();
|
||||||
|
const FString LfsUserName = GitSourceControl.AccessSettings().GetLfsUserName();
|
||||||
|
const bool bIsUsingGitLfsLocking = GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
|
||||||
|
const bool bGitLfsConfigOk = !bIsUsingGitLfsLocking || !LfsUserName.IsEmpty();
|
||||||
|
const bool bInitialCommitConfigOk = !bAutoInitialCommit || !InitialCommitMessage.IsEmpty();
|
||||||
|
return (bGitAvailable && !bGitRepositoryFound && bGitLfsConfigOk && bInitialCommitConfigOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SGitSourceControlSettings::CanInitializeGitLfs() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
const bool bGitLfsAvailable = GitSourceControl.GetProvider().GetGitVersion().bHasGitLfs;
|
||||||
|
return bGitLfsAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SGitSourceControlSettings::CanUseGitLfsLocking() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const bool bGitLfsLockingAvailable = GitSourceControl.GetProvider().GetGitVersion().bHasGitLfsLocking;
|
||||||
|
// TODO LFS SRombauts : check if .gitattributes file is present and if Content/ is already tracked!
|
||||||
|
const bool bGitAttributesCreated = true;
|
||||||
|
return (bGitLfsLockingAvailable && (bAutoCreateGitAttributes || bGitAttributesCreated));
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SGitSourceControlSettings::OnClickedInitializeGitRepository()
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||||
|
const FString PathToProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
|
||||||
|
TArray<FString> InfoMessages;
|
||||||
|
TArray<FString> ErrorMessages;
|
||||||
|
|
||||||
|
// 1.a. Synchronous (very quick) "git init" operation: initialize a Git local repository with a .git/ subdirectory
|
||||||
|
GitSourceControlUtils::RunCommand(TEXT("init"), PathToGitBinary, PathToProjectDir, TArray<FString>(), TArray<FString>(), InfoMessages, ErrorMessages);
|
||||||
|
// 1.b. Synchronous (very quick) "git remote add" operation: configure the URL of the default remote server 'origin' if specified
|
||||||
|
if(!RemoteUrl.IsEmpty())
|
||||||
|
{
|
||||||
|
TArray<FString> Parameters;
|
||||||
|
Parameters.Add(TEXT("add origin"));
|
||||||
|
Parameters.Add(RemoteUrl.ToString());
|
||||||
|
GitSourceControlUtils::RunCommand(TEXT("remote"), PathToGitBinary, PathToProjectDir, Parameters, TArray<FString>(), InfoMessages, ErrorMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the new repository status to enable connection (branch, user e-mail)
|
||||||
|
GitSourceControl.GetProvider().CheckRepositoryStatus(PathToGitBinary);
|
||||||
|
if(GitSourceControl.GetProvider().IsAvailable())
|
||||||
|
{
|
||||||
|
// List of files to add to Source Control (.uproject, Config/, Content/, Source/ files and .gitignore/.gitattributes if any)
|
||||||
|
TArray<FString> ProjectFiles;
|
||||||
|
ProjectFiles.Add(FPaths::GetProjectFilePath());
|
||||||
|
ProjectFiles.Add(FPaths::ProjectConfigDir());
|
||||||
|
ProjectFiles.Add(FPaths::ProjectContentDir());
|
||||||
|
if (FPaths::DirectoryExists(FPaths::GameSourceDir()))
|
||||||
|
{
|
||||||
|
ProjectFiles.Add(FPaths::GameSourceDir());
|
||||||
|
}
|
||||||
|
if(bAutoCreateGitIgnore)
|
||||||
|
{
|
||||||
|
// 2.a. Create a standard ".gitignore" file with common patterns for a typical Blueprint & C++ project
|
||||||
|
const FString GitIgnoreFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT(".gitignore"));
|
||||||
|
const FString GitIgnoreContent = TEXT("Binaries\nDerivedDataCache\nIntermediate\nSaved\n.vscode\n.vs\n*.VC.db\n*.opensdf\n*.opendb\n*.sdf\n*.sln\n*.suo\n*.xcodeproj\n*.xcworkspace\n*.log");
|
||||||
|
if(FFileHelper::SaveStringToFile(GitIgnoreContent, *GitIgnoreFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||||
|
{
|
||||||
|
ProjectFiles.Add(GitIgnoreFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(bAutoCreateReadme)
|
||||||
|
{
|
||||||
|
// 2.b. Create a "README.md" file with a custom description
|
||||||
|
const FString ReadmeFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT("README.md"));
|
||||||
|
if (FFileHelper::SaveStringToFile(ReadmeContent.ToString(), *ReadmeFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||||
|
{
|
||||||
|
ProjectFiles.Add(ReadmeFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(bAutoCreateGitAttributes)
|
||||||
|
{
|
||||||
|
// 2.c. Synchronous (very quick) "lfs install" operation: needs only to be run once by user
|
||||||
|
GitSourceControlUtils::RunCommand(TEXT("lfs install"), PathToGitBinary, PathToProjectDir, TArray<FString>(), TArray<FString>(), InfoMessages, ErrorMessages);
|
||||||
|
|
||||||
|
// 2.d. Create a ".gitattributes" file to enable Git LFS (Large File System) for the whole "Content/" subdir
|
||||||
|
const FString GitAttributesFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT(".gitattributes"));
|
||||||
|
FString GitAttributesContent;
|
||||||
|
if(GitSourceControl.AccessSettings().IsUsingGitLfsLocking())
|
||||||
|
{
|
||||||
|
// Git LFS 2.x File Locking mechanism
|
||||||
|
GitAttributesContent = TEXT("Content/** filter=lfs diff=lfs merge=lfs -text lockable\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GitAttributesContent = TEXT("Content/** filter=lfs diff=lfs merge=lfs -text\n");
|
||||||
|
}
|
||||||
|
if(FFileHelper::SaveStringToFile(GitAttributesContent, *GitAttributesFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||||
|
{
|
||||||
|
ProjectFiles.Add(GitAttributesFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Add files to Source Control: launch an asynchronous MarkForAdd operation
|
||||||
|
LaunchMarkForAddOperation(ProjectFiles);
|
||||||
|
|
||||||
|
// 4. The CheckIn will follow, at completion of the MarkForAdd operation
|
||||||
|
}
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch an asynchronous "MarkForAdd" operation and start an ongoing notification
|
||||||
|
void SGitSourceControlSettings::LaunchMarkForAddOperation(const TArray<FString>& InFiles)
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
TSharedRef<FMarkForAdd, ESPMode::ThreadSafe> MarkForAddOperation = ISourceControlOperation::Create<FMarkForAdd>();
|
||||||
|
ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(MarkForAddOperation, InFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
|
||||||
|
if (Result == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
DisplayInProgressNotification(MarkForAddOperation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayFailureNotification(MarkForAddOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch an asynchronous "CheckIn" operation and start another ongoing notification
|
||||||
|
void SGitSourceControlSettings::LaunchCheckInOperation()
|
||||||
|
{
|
||||||
|
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
|
||||||
|
CheckInOperation->SetDescription(InitialCommitMessage);
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(CheckInOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
|
||||||
|
if (Result == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
DisplayInProgressNotification(CheckInOperation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayFailureNotification(CheckInOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delegate called when a source control operation has completed: launch the next one and manage notifications
|
||||||
|
void SGitSourceControlSettings::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
|
||||||
|
{
|
||||||
|
RemoveInProgressNotification();
|
||||||
|
|
||||||
|
// Report result with a notification
|
||||||
|
if (InResult == ECommandResult::Succeeded)
|
||||||
|
{
|
||||||
|
DisplaySuccessNotification(InOperation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayFailureNotification(InOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((InOperation->GetName() == "MarkForAdd") && (InResult == ECommandResult::Succeeded) && bAutoInitialCommit)
|
||||||
|
{
|
||||||
|
// 4. optional initial Asynchronous commit with custom message: launch a "CheckIn" Operation
|
||||||
|
LaunchCheckInOperation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Display an ongoing notification during the whole operation
|
||||||
|
void SGitSourceControlSettings::DisplayInProgressNotification(const FSourceControlOperationRef& InOperation)
|
||||||
|
{
|
||||||
|
FNotificationInfo Info(InOperation->GetInProgressString());
|
||||||
|
Info.bFireAndForget = false;
|
||||||
|
Info.ExpireDuration = 0.0f;
|
||||||
|
Info.FadeOutDuration = 1.0f;
|
||||||
|
OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
if (OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the ongoing notification at the end of the operation
|
||||||
|
void SGitSourceControlSettings::RemoveInProgressNotification()
|
||||||
|
{
|
||||||
|
if (OperationInProgressNotification.IsValid())
|
||||||
|
{
|
||||||
|
OperationInProgressNotification.Pin()->ExpireAndFadeout();
|
||||||
|
OperationInProgressNotification.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display a temporary success notification at the end of the operation
|
||||||
|
void SGitSourceControlSettings::DisplaySuccessNotification(const FSourceControlOperationRef& InOperation)
|
||||||
|
{
|
||||||
|
const FText NotificationText = FText::Format(LOCTEXT("InitialCommit_Success", "{0} operation was successfull!"), FText::FromName(InOperation->GetName()));
|
||||||
|
FNotificationInfo Info(NotificationText);
|
||||||
|
Info.bUseSuccessFailIcons = true;
|
||||||
|
Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
||||||
|
FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display a temporary failure notification at the end of the operation
|
||||||
|
void SGitSourceControlSettings::DisplayFailureNotification(const FSourceControlOperationRef& InOperation)
|
||||||
|
{
|
||||||
|
const FText NotificationText = FText::Format(LOCTEXT("InitialCommit_Failure", "Error: {0} operation failed!"), FText::FromName(InOperation->GetName()));
|
||||||
|
FNotificationInfo Info(NotificationText);
|
||||||
|
Info.ExpireDuration = 8.0f;
|
||||||
|
FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnCheckedCreateGitIgnore(ECheckBoxState NewCheckedState)
|
||||||
|
{
|
||||||
|
bAutoCreateGitIgnore = (NewCheckedState == ECheckBoxState::Checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnCheckedCreateReadme(ECheckBoxState NewCheckedState)
|
||||||
|
{
|
||||||
|
bAutoCreateReadme = (NewCheckedState == ECheckBoxState::Checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SGitSourceControlSettings::GetAutoCreateReadme() const
|
||||||
|
{
|
||||||
|
return bAutoCreateReadme;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnReadmeContentCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||||
|
{
|
||||||
|
ReadmeContent = InText;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetReadmeContent() const
|
||||||
|
{
|
||||||
|
return ReadmeContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnCheckedCreateGitAttributes(ECheckBoxState NewCheckedState)
|
||||||
|
{
|
||||||
|
bAutoCreateGitAttributes = (NewCheckedState == ECheckBoxState::Checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnCheckedUseGitLfsLocking(ECheckBoxState NewCheckedState)
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
GitSourceControl.AccessSettings().SetUsingGitLfsLocking(NewCheckedState == ECheckBoxState::Checked);
|
||||||
|
GitSourceControl.AccessSettings().SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SGitSourceControlSettings::GetIsUsingGitLfsLocking() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
return GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
|
||||||
|
}
|
||||||
|
|
||||||
|
ECheckBoxState SGitSourceControlSettings::IsUsingGitLfsLocking() const
|
||||||
|
{
|
||||||
|
return (GetIsUsingGitLfsLocking() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnLfsUserNameCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||||
|
{
|
||||||
|
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
GitSourceControl.AccessSettings().SetLfsUserName(InText.ToString());
|
||||||
|
GitSourceControl.AccessSettings().SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetLfsUserName() const
|
||||||
|
{
|
||||||
|
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||||
|
return FText::FromString(GitSourceControl.AccessSettings().GetLfsUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnCheckedInitialCommit(ECheckBoxState NewCheckedState)
|
||||||
|
{
|
||||||
|
bAutoInitialCommit = (NewCheckedState == ECheckBoxState::Checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SGitSourceControlSettings::GetAutoInitialCommit() const
|
||||||
|
{
|
||||||
|
return bAutoInitialCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnInitialCommitMessageCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||||
|
{
|
||||||
|
InitialCommitMessage = InText;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetInitialCommitMessage() const
|
||||||
|
{
|
||||||
|
return InitialCommitMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SGitSourceControlSettings::OnRemoteUrlCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||||
|
{
|
||||||
|
RemoteUrl = InText;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SGitSourceControlSettings::GetRemoteUrl() const
|
||||||
|
{
|
||||||
|
return RemoteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||||
|
// or copy at http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Layout/Visibility.h"
|
||||||
|
#include "Input/Reply.h"
|
||||||
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
||||||
|
#include "Widgets/SCompoundWidget.h"
|
||||||
|
#include "SlateFwd.h"
|
||||||
|
#include "ISourceControlOperation.h"
|
||||||
|
#include "ISourceControlProvider.h"
|
||||||
|
|
||||||
|
enum class ECheckBoxState : uint8;
|
||||||
|
|
||||||
|
class SGitSourceControlSettings : public SCompoundWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SLATE_BEGIN_ARGS(SGitSourceControlSettings) {}
|
||||||
|
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
void Construct(const FArguments& InArgs);
|
||||||
|
|
||||||
|
~SGitSourceControlSettings();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Delegates to get Git binary path from/to settings */
|
||||||
|
FString GetBinaryPathString() const;
|
||||||
|
void OnBinaryPathPicked(const FString & PickedPath) const;
|
||||||
|
|
||||||
|
/** Delegate to get repository root, user name and email from provider */
|
||||||
|
FText GetPathToRepositoryRoot() const;
|
||||||
|
FText GetUserName() const;
|
||||||
|
FText GetUserEmail() const;
|
||||||
|
|
||||||
|
EVisibility MustInitializeGitRepository() const;
|
||||||
|
bool CanInitializeGitRepository() const;
|
||||||
|
bool CanInitializeGitLfs() const;
|
||||||
|
bool CanUseGitLfsLocking() const;
|
||||||
|
|
||||||
|
/** Delegate to initialize a new Git repository */
|
||||||
|
FReply OnClickedInitializeGitRepository();
|
||||||
|
|
||||||
|
void OnCheckedCreateGitIgnore(ECheckBoxState NewCheckedState);
|
||||||
|
bool bAutoCreateGitIgnore;
|
||||||
|
|
||||||
|
/** Delegates to create a README.md file */
|
||||||
|
void OnCheckedCreateReadme(ECheckBoxState NewCheckedState);
|
||||||
|
bool GetAutoCreateReadme() const;
|
||||||
|
bool bAutoCreateReadme;
|
||||||
|
void OnReadmeContentCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||||
|
FText GetReadmeContent() const;
|
||||||
|
FText ReadmeContent;
|
||||||
|
|
||||||
|
void OnCheckedCreateGitAttributes(ECheckBoxState NewCheckedState);
|
||||||
|
bool bAutoCreateGitAttributes;
|
||||||
|
|
||||||
|
void OnCheckedUseGitLfsLocking(ECheckBoxState NewCheckedState);
|
||||||
|
ECheckBoxState IsUsingGitLfsLocking() const;
|
||||||
|
bool GetIsUsingGitLfsLocking() const;
|
||||||
|
|
||||||
|
void OnLfsUserNameCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||||
|
FText GetLfsUserName() const;
|
||||||
|
|
||||||
|
void OnCheckedInitialCommit(ECheckBoxState NewCheckedState);
|
||||||
|
bool GetAutoInitialCommit() const;
|
||||||
|
bool bAutoInitialCommit;
|
||||||
|
void OnInitialCommitMessageCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||||
|
FText GetInitialCommitMessage() const;
|
||||||
|
FText InitialCommitMessage;
|
||||||
|
|
||||||
|
void OnRemoteUrlCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||||
|
FText GetRemoteUrl() const;
|
||||||
|
FText RemoteUrl;
|
||||||
|
|
||||||
|
/** Launch initial asynchronous add and commit operations */
|
||||||
|
void LaunchMarkForAddOperation(const TArray<FString>& InFiles);
|
||||||
|
void LaunchCheckInOperation();
|
||||||
|
|
||||||
|
/** Delegate called when a source control operation has completed */
|
||||||
|
void OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
|
||||||
|
|
||||||
|
/** Asynchronous operation progress notifications */
|
||||||
|
TWeakPtr<SNotificationItem> OperationInProgressNotification;
|
||||||
|
|
||||||
|
void DisplayInProgressNotification(const FSourceControlOperationRef& InOperation);
|
||||||
|
void RemoveInProgressNotification();
|
||||||
|
void DisplaySuccessNotification(const FSourceControlOperationRef& InOperation);
|
||||||
|
void DisplayFailureNotification(const FSourceControlOperationRef& InOperation);
|
||||||
|
};
|
2
Plugins/UE4GitPlugin-2.17-beta/_config.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
show_downloads: true
|
||||||
|
theme: jekyll-theme-slate
|