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
|