diff --git a/Angels and Demons/Assets/Editor.meta b/Angels and Demons/Assets/Editor.meta
new file mode 100644
index 0000000..4a7481e
--- /dev/null
+++ b/Angels and Demons/Assets/Editor.meta
@@ -0,0 +1,5 @@
+fileFormatVersion: 2
+guid: 13c149b5679992c4aad3260153c5f7ae
+folderAsset: yes
+DefaultImporter:
+ userData:
diff --git a/Angels and Demons/Assets/Editor/GameObjectExtensions.cs b/Angels and Demons/Assets/Editor/GameObjectExtensions.cs
new file mode 100644
index 0000000..dc2e74a
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/GameObjectExtensions.cs
@@ -0,0 +1,67 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+
+namespace GitMerge
+{
+ public static class GameObjectExtensions
+ {
+ ///
+ /// Adds the copy of a Component to a GameObject.
+ ///
+ /// The GameObject that will get the new Component
+ /// The original component to copy
+ /// The reference to the newly added Component copy
+ public static Component AddComponent(this GameObject go, Component original)
+ {
+ var c = go.AddComponent(original.GetType());
+
+ var originalSerialized = new SerializedObject(original).GetIterator();
+ var nso = new SerializedObject(c);
+ var newSerialized = nso.GetIterator();
+
+ if(originalSerialized.Next(true))
+ {
+ newSerialized.Next(true);
+
+ while(originalSerialized.NextVisible(true))
+ {
+ newSerialized.NextVisible(true);
+ newSerialized.SetValue(originalSerialized.GetValue());
+ }
+ }
+
+ nso.ApplyModifiedProperties();
+
+ return c;
+ }
+
+ ///
+ /// Activates/deactivates the GameObjct, and hides it when it is disabled.
+ /// This is used for "their" objects to hide them while merging.
+ ///
+ /// The object do enable/disable
+ /// Enable or disable the object?
+ public static void SetActiveForMerging(this GameObject go, bool active)
+ {
+ go.SetActive(active);
+ go.hideFlags = active ? HideFlags.None : HideFlags.HideAndDontSave;
+ }
+
+ ///
+ /// Ping the GameObject in the hierarchy, select it, and center it in the scene view.
+ ///
+ /// The GameObject of interest
+ public static void Highlight(this GameObject go)
+ {
+ Selection.activeGameObject = go;
+ EditorGUIUtility.PingObject(go);
+
+ var view = SceneView.lastActiveSceneView;
+ if(view)
+ {
+ view.FrameSelected();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/GameObjectExtensions.cs.meta b/Angels and Demons/Assets/Editor/GameObjectExtensions.cs.meta
new file mode 100644
index 0000000..cdab8fe
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/GameObjectExtensions.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 327c0f7ed6330514791026c2d1ba5a5c
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/GameObjectMergeActions.cs b/Angels and Demons/Assets/Editor/GameObjectMergeActions.cs
new file mode 100644
index 0000000..3c079aa
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/GameObjectMergeActions.cs
@@ -0,0 +1,362 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace GitMerge
+{
+ ///
+ /// One instance of this class represents one GameObject with relevance to the merge process.
+ /// Holds all MergeActions that can be applied to the GameObject or its Components.
+ /// Is considered as "merged" when all its MergeActions are "merged".
+ ///
+ public class GameObjectMergeActions
+ {
+ ///
+ /// Reference to "our" version of the GameObject.
+ ///
+ public GameObject ours { private set; get; }
+ ///
+ /// Reference to "their" versoin of the GameObject.
+ ///
+ public GameObject theirs { private set; get; }
+
+ private string name;
+ public bool merged { private set; get; }
+ public bool hasActions
+ {
+ get { return actions.Count > 0; }
+ }
+ ///
+ /// All actions available for solving specific conflicts on the GameObject.
+ ///
+ private List actions;
+
+
+ public GameObjectMergeActions(GameObject ours, GameObject theirs)
+ {
+ actions = new List();
+
+ this.ours = ours;
+ this.theirs = theirs;
+ GenerateName();
+
+ if(theirs && !ours)
+ {
+ actions.Add(new MergeActionNewGameObject(ours, theirs));
+ }
+ if(ours && !theirs)
+ {
+ actions.Add(new MergeActionDeleteGameObject(ours, theirs));
+ }
+ if(ours && theirs)
+ {
+ FindPropertyDifferences();
+ FindComponentDifferences();
+ }
+
+ //Some Actions have a default and are merged from the beginning.
+ //If all the others did was to add GameObjects, we're done with merging from the start.
+ CheckIfMerged();
+ }
+
+ ///
+ /// Generate a title for this object
+ ///
+ private void GenerateName()
+ {
+ name = "";
+ if(ours)
+ {
+ name = "Your[" + GetPath(ours) + "]";
+ }
+ if(theirs)
+ {
+ if(ours)
+ {
+ name += " vs. ";
+ }
+ name += "Their[" + GetPath(theirs) + "]";
+ }
+ }
+
+ ///
+ /// Finds the differences between properties of the two GameObjects.
+ /// That means the name, layer, tag... everything that's not part of a Component. Also, the parent.
+ ///
+ private void FindPropertyDifferences()
+ {
+ CheckForDifferentParents();
+ FindPropertyDifferences(ours, theirs);
+ }
+
+ ///
+ /// Since parenting is quite special, here's some dedicated handling.
+ ///
+ private void CheckForDifferentParents()
+ {
+ var transform = ours.GetComponent();
+ var ourParent = transform.parent;
+ var theirParent = theirs.GetComponent().parent;
+ if(ourParent != theirParent)
+ {
+ actions.Add(new MergeActionParenting(transform, ourParent, theirParent));
+ }
+ }
+
+ ///
+ /// Check for Components that one of the sides doesn't have, and/or for defferent values
+ /// on Components.
+ ///
+ private void FindComponentDifferences()
+ {
+ var ourComponents = ours.GetComponents();
+ var theirComponents = theirs.GetComponents();
+
+ //Map "their" Components to their respective ids
+ var theirDict = new Dictionary();
+ foreach(var theirComponent in theirComponents)
+ {
+ //Ignore null components
+ if(theirComponent != null)
+ {
+ theirDict.Add(ObjectIDFinder.GetIdentifierFor(theirComponent), theirComponent);
+ }
+ }
+
+ foreach(var ourComponent in ourComponents)
+ {
+ //Ignore null components
+ if(ourComponent == null) continue;
+
+ //Try to find "their" equivalent to our Components
+ var id = ObjectIDFinder.GetIdentifierFor(ourComponent);
+ Component theirComponent;
+ theirDict.TryGetValue(id, out theirComponent);
+
+ if(theirComponent) //Both Components exist
+ {
+ FindPropertyDifferences(ourComponent, theirComponent);
+ //Remove "their" Component from the dict to only keep those new to us
+ theirDict.Remove(id);
+ }
+ else //Component doesn't exist in their version, offer a deletion
+ {
+ actions.Add(new MergeActionDeleteComponent(ours, ourComponent));
+ }
+ }
+
+ //Everything left in the dict is a...
+ foreach(var theirComponent in theirDict.Values)
+ {
+ //...new Component from them
+ actions.Add(new MergeActionNewComponent(ours, theirComponent));
+ }
+ }
+
+ ///
+ /// Find all the values different in "our" and "their" version of a component.
+ ///
+ private void FindPropertyDifferences(Object ourObject, Object theirObject)
+ {
+ var ourSerialized = new SerializedObject(ourObject);
+ var theirSerialized = new SerializedObject(theirObject);
+
+ var ourProperty = ourSerialized.GetIterator();
+ if(ourProperty.NextVisible(true))
+ {
+ var theirProperty = theirSerialized.GetIterator();
+ theirProperty.NextVisible(true);
+ while(ourProperty.NextVisible(false))
+ {
+ theirProperty.NextVisible(false);
+
+ if(ourObject is GameObject)
+ {
+ if(MergeManager.isMergingPrefab)
+ {
+ //If merging a prefab, ignore the gameobject name.
+ if(ourProperty.GetPlainName() == "Name")
+ {
+ continue;
+ }
+ }
+ }
+
+ if(DifferentValues(ourProperty, theirProperty))
+ {
+ //We found a difference, accordingly add a MergeAction
+ actions.Add(new MergeActionChangeValues(ours, ourObject, ourProperty.Copy(), theirProperty.Copy()));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Returns true when the two properties have different values, false otherwise.
+ ///
+ private static bool DifferentValues(SerializedProperty ourProperty, SerializedProperty theirProperty)
+ {
+ if(!ourProperty.IsRealArray())
+ {
+ //Regular single-value property
+ if(DifferentValuesFlat(ourProperty, theirProperty))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ //Array property
+ if(ourProperty.arraySize != theirProperty.arraySize)
+ {
+ return true;
+ }
+
+ var op = ourProperty.Copy();
+ var tp = theirProperty.Copy();
+
+ op.Next(true);
+ op.Next(true);
+ tp.Next(true);
+ tp.Next(true);
+
+ for(int i = 0; i < ourProperty.arraySize; ++i)
+ {
+ op.Next(false);
+ tp.Next(false);
+
+ if(DifferentValuesFlat(op, tp))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static bool DifferentValuesFlat(SerializedProperty ourProperty, SerializedProperty theirProperty)
+ {
+ var our = ourProperty.GetValue(true);
+ var their = theirProperty.GetValue(true);
+
+ return !object.Equals(our, their);
+ }
+
+ ///
+ /// Get the path of a GameObject in the hierarchy.
+ ///
+ private static string GetPath(GameObject g)
+ {
+ var t = g.transform;
+ var sb = new StringBuilder(t.name);
+ while(t.parent != null)
+ {
+ t = t.parent;
+ sb.Insert(0, t.name + "/");
+ }
+ return sb.ToString();
+ }
+
+ private void CheckIfMerged()
+ {
+ merged = actions.TrueForAll(action => action.merged);
+ }
+
+ ///
+ /// Use "our" version for all conflicts.
+ /// This is used on all GameObjectMergeActions objects when the merge is aborted.
+ ///
+ public void UseOurs()
+ {
+ foreach(var action in actions)
+ {
+ action.UseOurs();
+ }
+ merged = true;
+ }
+
+ ///
+ /// Use "their" version for all conflicts.
+ ///
+ public void UseTheirs()
+ {
+ foreach(var action in actions)
+ {
+ action.UseTheirs();
+ }
+ merged = true;
+ }
+
+ //If the foldout is open
+ private bool open;
+ public void OnGUI()
+ {
+ if(open)
+ {
+ GUI.backgroundColor = new Color(0, 0, 0, .8f);
+ }
+ else
+ {
+ GUI.backgroundColor = merged ? new Color(0, .5f, 0, .8f) : new Color(.5f, 0, 0, .8f);
+ }
+ GUILayout.BeginVertical(Resources.styles.mergeActions);
+ GUI.backgroundColor = Color.white;
+
+ GUILayout.BeginHorizontal();
+ open = EditorGUILayout.Foldout(open, new GUIContent(name));
+
+ if(ours && GUILayout.Button("Focus", EditorStyles.miniButton, GUILayout.Width(100)))
+ {
+ //Highlight the instance of the prefab, not the prefab itself
+ //Otherwise, "ours".
+ var objectToHighlight = MergeManager.isMergingPrefab ? MergeManagerPrefab.ourPrefabInstance : ours;
+ objectToHighlight.Highlight();
+ }
+ GUILayout.EndHorizontal();
+
+ if(open)
+ {
+ //Display all merge actions.
+ foreach(var action in actions)
+ {
+ if(action.OnGUIMerge())
+ {
+ CheckIfMerged();
+ }
+ }
+ }
+ else
+ {
+ GUILayout.BeginHorizontal();
+ if(GUILayout.Button("Use ours >>>", EditorStyles.miniButton))
+ {
+ UseOurs();
+ }
+
+ if(GUILayout.Button("<<< Use theirs", EditorStyles.miniButton))
+ {
+ UseTheirs();
+ }
+ GUILayout.EndHorizontal();
+ }
+
+ //If "ours" is null, the GameObject doesn't exist in one of the versions.
+ //Try to get a reference if the object exists in the current merging state.
+ //If it exists, the new/gelete MergeAction will have a reference.
+ if(!ours)
+ {
+ foreach(var action in actions)
+ {
+ ours = action.ours;
+ }
+ }
+
+ GUILayout.EndVertical();
+
+ GUI.backgroundColor = Color.white;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/GameObjectMergeActions.cs.meta b/Angels and Demons/Assets/Editor/GameObjectMergeActions.cs.meta
new file mode 100644
index 0000000..d5271c8
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/GameObjectMergeActions.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cfa76c0e71adc0f439476432ffe37fa3
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/GitMergeWindow.cs b/Angels and Demons/Assets/Editor/GitMergeWindow.cs
new file mode 100644
index 0000000..d200368
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/GitMergeWindow.cs
@@ -0,0 +1,255 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+
+namespace GitMerge
+{
+ ///
+ /// The window that lets you perform merges on scenes and prefabs.
+ ///
+ public class GitMergeWindow : EditorWindow
+ {
+ private VCS vcs = new VCSGit();
+
+ //EditorPrefs keys for settings
+ private const string epAutomerge = "GitMerge_automerge";
+ private const string epAutofocus = "GitMerge_autofocus";
+
+ //Settings
+ public static bool automerge { private set; get; }
+ public static bool autofocus { private set; get; }
+
+ //The MergeManager that has the actual merging logic
+ private MergeManager manager;
+
+ public bool mergeInProgress
+ {
+ get
+ {
+ return manager != null;
+ }
+ }
+
+ private Vector2 scrollPosition = Vector2.zero;
+ private int tab = 0;
+
+
+ [MenuItem("Window/GitMerge")]
+ static void OpenEditor()
+ {
+ var window = EditorWindow.GetWindow(typeof(GitMergeWindow), false, "GitMerge");
+ //In case we're merging and the scene becomes edited,
+ //the shown SerializedProperties should be repainted
+ window.autoRepaintOnSceneChange = true;
+ window.minSize = new Vector2(500, 100);
+ }
+
+ void OnEnable()
+ {
+ LoadSettings();
+ }
+
+ private static void LoadSettings()
+ {
+ if(EditorPrefs.HasKey(epAutomerge))
+ {
+ automerge = EditorPrefs.GetBool(epAutomerge);
+ }
+ else
+ {
+ automerge = true;
+ }
+ if(EditorPrefs.HasKey(epAutofocus))
+ {
+ autofocus = EditorPrefs.GetBool(epAutofocus);
+ }
+ else
+ {
+ autofocus = true;
+ }
+ }
+
+ void OnHierarchyChange()
+ {
+ //Repaint if we changed the scene
+ this.Repaint();
+ }
+
+ //Always check for editor state changes, and abort the active merge process if needed
+ void Update()
+ {
+ if(MergeAction.inMergePhase
+ && (EditorApplication.isCompiling
+ || EditorApplication.isPlayingOrWillChangePlaymode))
+ {
+ ShowNotification(new GUIContent("Aborting merge due to editor state change."));
+ AbortMerge();
+ }
+ }
+
+ private void AbortMerge()
+ {
+ manager.AbortMerge();
+ manager = null;
+ }
+
+ void OnGUI()
+ {
+ Resources.DrawLogo();
+ DrawTabButtons();
+
+ switch(tab)
+ {
+ case 0:
+ OnGUISceneTab();
+ break;
+
+ case 1:
+ OnGUIPrefabTab();
+ break;
+
+ default:
+ OnGUISettingsTab();
+ break;
+ }
+ }
+
+ ///
+ /// Tab that offers scene merging.
+ ///
+ private void OnGUISceneTab()
+ {
+ GUILayout.Label("Open Scene: " + EditorApplication.currentScene);
+ if(EditorApplication.currentScene != ""
+ && !mergeInProgress
+ && GUILayout.Button("Start merging this scene", GUILayout.Height(80)))
+ {
+ var mm = new MergeManagerScene(this, vcs);
+ if(mm.InitializeMerge())
+ {
+ manager = mm;
+ }
+ }
+
+ DisplayMergeProcess();
+ }
+
+ ///
+ /// Tab that offers prefab merging.
+ ///
+ private void OnGUIPrefabTab()
+ {
+ GameObject prefab;
+ if(!mergeInProgress)
+ {
+ GUILayout.Label("Drag your prefab here to start merging:");
+ if(prefab = EditorGUILayout.ObjectField(null, typeof(GameObject), false, GUILayout.Height(60)) as GameObject)
+ {
+ var mm = new MergeManagerPrefab(this, vcs);
+ if(mm.InitializeMerge(prefab))
+ {
+ manager = mm;
+ }
+ }
+ }
+
+ DisplayMergeProcess();
+ }
+
+ ///
+ /// Tab that offers various settings for the tool.
+ ///
+ private void OnGUISettingsTab()
+ {
+ var vcsPath = vcs.exe();
+ var vcsPathNew = EditorGUILayout.TextField("Path to git.exe", vcsPath);
+ if(vcsPath != vcsPathNew)
+ {
+ vcs.SetPath(vcsPathNew);
+ }
+
+ var amNew = EditorGUILayout.Toggle("Automerge", automerge);
+ if(automerge != amNew)
+ {
+ automerge = amNew;
+ EditorPrefs.SetBool(epAutomerge, automerge);
+ }
+ GUILayout.Label("(Automerge new/deleted GameObjects/Components upon merge start)");
+
+ var afNew = EditorGUILayout.Toggle("Auto Highlight", autofocus);
+ if(autofocus != afNew)
+ {
+ autofocus = afNew;
+ EditorPrefs.SetBool(epAutofocus, autofocus);
+ }
+ GUILayout.Label("(Highlight GameObjects when applying a MergeAction to it)");
+ }
+
+ ///
+ /// If no merge is in progress, draws the buttons to switch between tabs.
+ /// Otherwise, draws the "abort merge" button.
+ ///
+ private void DrawTabButtons()
+ {
+ if(!mergeInProgress)
+ {
+ string[] tabs = { "Merge Scene", "Merge Prefab", "Settings" };
+ tab = GUI.SelectionGrid(new Rect(72, 36, 300, 22), tab, tabs, 3);
+ }
+ else
+ {
+ GUI.backgroundColor = new Color(1,0.4f,0.4f,1);
+ if(GUI.Button(new Rect(72, 36, 300, 22), "Abort merge"))
+ {
+ manager.AbortMerge();
+ manager = null;
+ }
+ GUI.backgroundColor = Color.white;
+ }
+ }
+
+ ///
+ /// Displays all MergeActions and the "apply merge" button if a merge is in progress.
+ ///
+ private void DisplayMergeProcess()
+ {
+ if(mergeInProgress)
+ {
+ var done = DisplayMergeActions();
+ GUILayout.BeginHorizontal();
+ if(done && GUILayout.Button("Apply merge"))
+ {
+ manager.CompleteMerge();
+ manager = null;
+ }
+ GUILayout.EndHorizontal();
+ }
+ }
+
+ ///
+ /// Displays all GameObjectMergeActions.
+ ///
+ /// True, if all MergeActions are flagged as "merged".
+ private bool DisplayMergeActions()
+ {
+ scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true);
+ GUILayout.BeginVertical(GUILayout.MinWidth(480));
+
+ var textColor = GUI.skin.label.normal.textColor;
+ GUI.skin.label.normal.textColor = Color.black;
+
+ var done = true;
+ foreach(var actions in manager.allMergeActions)
+ {
+ actions.OnGUI();
+ done = done && actions.merged;
+ }
+
+ GUI.skin.label.normal.textColor = textColor;
+
+ GUILayout.EndVertical();
+ GUILayout.EndScrollView();
+ return done;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/GitMergeWindow.cs.meta b/Angels and Demons/Assets/Editor/GitMergeWindow.cs.meta
new file mode 100644
index 0000000..53ddc40
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/GitMergeWindow.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: aaa504546605b3a479405bee3c11cd04
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeAction.cs b/Angels and Demons/Assets/Editor/MergeAction.cs
new file mode 100644
index 0000000..4df45a4
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeAction.cs
@@ -0,0 +1,154 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections;
+
+namespace GitMerge
+{
+ ///
+ /// Each MergeAction represents a single, specific merge conflict.
+ /// This can be a GameObject added or deleted in one of the versions,
+ /// a Component added or deleted on a GameObject,
+ /// or a single property changed on a Component.
+ ///
+ public abstract class MergeAction
+ {
+ //Don't highlight objects if not in merge phase.
+ //Prevents highlighting while automerging.
+ public static bool inMergePhase;
+
+ //A MergeAction is considere "merged" when, at some point,
+ //"our", "their" or a new version has been applied.
+ public bool merged { protected set; get; }
+
+ public GameObject ours { protected set; get; }
+ public GameObject theirs { protected set; get; }
+
+ //Flags that indicate how this MergeAction has been resolved.
+ protected bool usingOurs;
+ protected bool usingTheirs;
+ protected bool usingNew;
+ //True when this action has been automatically resolved
+ protected bool automatic;
+
+
+ public MergeAction(GameObject ours, GameObject theirs)
+ {
+ this.ours = ours;
+ this.theirs = theirs;
+ }
+
+ public void UseOurs()
+ {
+ try
+ {
+ ApplyOurs();
+ }
+ catch
+ {
+ return;
+ }
+ merged = true;
+ usingOurs = true;
+ usingTheirs = false;
+ usingNew = false;
+
+ automatic = !inMergePhase;
+
+ if(GitMergeWindow.autofocus)
+ {
+ HighlightObject();
+ }
+
+ RefreshPrefabInstance();
+ }
+ public void UseTheirs()
+ {
+ try
+ {
+ ApplyTheirs();
+ }
+ catch
+ {
+ return;
+ }
+ merged = true;
+ usingOurs = false;
+ usingTheirs = true;
+ usingNew = false;
+
+ automatic = !inMergePhase;
+
+ if(GitMergeWindow.autofocus)
+ {
+ HighlightObject();
+ }
+
+ RefreshPrefabInstance();
+ }
+ public void UsedNew()
+ {
+ merged = true;
+ usingOurs = false;
+ usingTheirs = false;
+ usingNew = true;
+
+ automatic = !inMergePhase;
+
+ RefreshPrefabInstance();
+ }
+
+ ///
+ /// Refreshes the prefab instance, if there is any.
+ /// We change the prefab directly, so we have to do this to see the changes in the scene view.
+ ///
+ private static void RefreshPrefabInstance()
+ {
+ if(MergeManager.isMergingPrefab)
+ {
+ PrefabUtility.ResetToPrefabState(MergeManagerPrefab.ourPrefabInstance);
+ }
+ }
+
+ //The implementations of these methods conatain the actual merging steps
+ protected abstract void ApplyOurs();
+ protected abstract void ApplyTheirs();
+
+ ///
+ /// Displays the MergeAction.
+ ///
+ /// True when the represented conflict has now been merged.
+ public bool OnGUIMerge()
+ {
+ var wasMerged = merged;
+ if(merged)
+ {
+ GUI.backgroundColor = automatic ? new Color(.9f, .9f, .3f, 1) : new Color(.2f, .8f, .2f, 1);
+ }
+ else
+ {
+ GUI.backgroundColor = new Color(1f, .25f, .25f, 1);
+ }
+ GUILayout.BeginHorizontal(Resources.styles.mergeAction);
+ GUI.backgroundColor = Color.white;
+ OnGUI();
+ GUI.color = Color.white;
+ GUILayout.EndHorizontal();
+ return merged && !wasMerged;
+ }
+
+ //The actual UI of the MergeAction depends on the actual type
+ public abstract void OnGUI();
+
+ private void HighlightObject()
+ {
+ //Highlight the instance of the prefab, not the prefab itself
+ //Otherwise, "ours".
+ var objectToHighlight = MergeManager.isMergingPrefab ? MergeManagerPrefab.ourPrefabInstance : ours;
+
+ if(objectToHighlight && inMergePhase && objectToHighlight.hideFlags == HideFlags.None)
+ {
+ objectToHighlight.Highlight();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeAction.cs.meta b/Angels and Demons/Assets/Editor/MergeAction.cs.meta
new file mode 100644
index 0000000..8befe59
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeAction.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d9583c639208c1e49b6cc77e128f7ccb
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionChangeValues.cs b/Angels and Demons/Assets/Editor/MergeActionChangeValues.cs
new file mode 100644
index 0000000..84f217a
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionChangeValues.cs
@@ -0,0 +1,268 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections;
+using System.Linq;
+
+namespace GitMerge
+{
+ ///
+ /// The MergeAction allowing to merge the value of a single property of a Component.
+ ///
+ public class MergeActionChangeValues : MergeAction
+ {
+ protected SerializedProperty ourProperty;
+ protected SerializedProperty theirProperty;
+ protected object ourInitialValue;
+ protected object theirInitialValue;
+ protected readonly string ourString;
+ protected readonly string theirString;
+ protected readonly string fieldname;
+ protected Object ourObject;
+
+ public MergeActionChangeValues(GameObject ours, Object ourObject, SerializedProperty ourProperty, SerializedProperty theirProperty)
+ : base(ours, null)
+ {
+ this.ourObject = ourObject;
+
+ this.ourProperty = ourProperty;
+ this.theirProperty = theirProperty;
+
+ fieldname = ourObject.GetPlainType() + "." + ourProperty.GetPlainName();
+
+ ourInitialValue = ourProperty.GetValue();
+ theirInitialValue = theirProperty.GetValue();
+
+ ourString = SerializedValueString(ourProperty);
+ theirString = SerializedValueString(theirProperty);
+ }
+
+ protected override void ApplyOurs()
+ {
+ ourProperty.SetValue(ourInitialValue);
+ ourProperty.serializedObject.ApplyModifiedProperties();
+ }
+
+ protected override void ApplyTheirs()
+ {
+ var value = theirInitialValue;
+
+ //If we're about references here, get "our" version of the object.
+ if(ourProperty.propertyType == SerializedPropertyType.ObjectReference)
+ {
+ var id = ObjectIDFinder.GetIdentifierFor(theirInitialValue as Object);
+ var obj = ObjectDictionaries.GetOurObject(id);
+
+ //If we didn't have our own version of the object before, it must be new
+ if(!obj)
+ {
+ //Get our copy of the new object if it exists
+ obj = ObjectDictionaries.GetOurInstanceOfCopy(value as Object);
+ }
+
+ value = obj;
+ }
+
+ ourProperty.SetValue(value);
+ ourProperty.serializedObject.ApplyModifiedProperties();
+ }
+
+ public override void OnGUI()
+ {
+ GUILayout.BeginVertical();
+ GUILayout.Label(fieldname + ": " + ourProperty.propertyType);
+
+ GUILayout.BeginHorizontal();
+
+ GUILayout.BeginVertical();
+ GUILayout.Label(ourString, GUILayout.Width(100));
+ DisplayArray(ourInitialValue);
+ GUILayout.EndVertical();
+
+ if(MergeButton(">>>", usingOurs))
+ {
+ UseOurs();
+ }
+
+ var c = GUI.backgroundColor;
+ GUI.backgroundColor = Color.white;
+
+ //GUILayout.Label(ourProperty.propertyType + "/" + ourProperty.type + ": " + ourProperty.GetValue());
+ PropertyField(ourProperty);
+
+ GUI.backgroundColor = c;
+
+ if(MergeButton("<<<", usingTheirs))
+ {
+ UseTheirs();
+ }
+
+ GUILayout.BeginVertical();
+ GUILayout.Label(theirString, GUILayout.Width(100));
+ DisplayArray(theirInitialValue);
+ GUILayout.EndVertical();
+
+ GUILayout.EndHorizontal();
+ GUILayout.EndVertical();
+ }
+
+ private void DisplayArray(object value)
+ {
+ if(ourProperty.IsRealArray() && ourProperty.isExpanded)
+ {
+ var values = (object[])value;
+ for(int i = 0; i < values.Length; ++i)
+ {
+ GUILayout.Label(ValueString(values[i]), GUILayout.Width(100));
+ }
+ }
+ }
+
+ ///
+ /// Displays the property field in the center of the window.
+ /// This method distinguishes between certain properties.
+ /// The GameObject tag, for example, shouldn't be displayed with a regular string field.
+ ///
+ /// The SerializedProerty to display
+ /// The width of the whole thing in the ui
+ private void PropertyField(SerializedProperty p, float width = 170)
+ {
+ if(p.IsRealArray())
+ {
+ DisplayArrayProperty(p, width);
+ }
+ else
+ {
+ var oldValue = p.GetValue();
+ if(fieldname == "GameObject.TagString")
+ {
+ var oldTag = oldValue as string;
+ var newTag = EditorGUILayout.TagField("", oldTag, GUILayout.Width(width));
+ if(newTag != oldTag)
+ {
+ p.SetValue(newTag);
+ }
+ }
+ else if(fieldname == "GameObject.StaticEditorFlags")
+ {
+ DisplayStaticFlagChooser(p, width);
+ }
+ else
+ {
+ EditorGUILayout.PropertyField(p, new GUIContent(""), GUILayout.Width(width));
+ }
+ if(!object.Equals(p.GetValue(), oldValue))
+ {
+ p.serializedObject.ApplyModifiedProperties();
+ UsedNew();
+ }
+ }
+ }
+
+ private void DisplayArrayProperty(SerializedProperty p, float width)
+ {
+ GUILayout.BeginVertical();
+ GUILayout.BeginHorizontal(GUILayout.Width(170));
+ EditorGUILayout.PropertyField(p, new GUIContent("Array"), GUILayout.Width(80));
+ if(p.isExpanded)
+ {
+ var copy = p.Copy();
+ var size = copy.arraySize;
+
+ copy.Next(true);
+ copy.Next(true);
+
+ PropertyField(copy, 70);
+ GUILayout.EndHorizontal();
+
+ for(int i = 0; i < size; ++i)
+ {
+ copy.Next(false);
+ PropertyField(copy);
+ }
+ }
+ else
+ {
+ GUILayout.EndHorizontal();
+ }
+ GUILayout.EndVertical();
+ }
+
+ ///
+ /// Displays Toggles that let the user set the static flags of the object.
+ ///
+ /// The StaticEditorFlags SerializedProperty to display
+ /// The width of the whole thing in the ui
+ private void DisplayStaticFlagChooser(SerializedProperty p, float width)
+ {
+ var flags = (StaticEditorFlags)p.intValue;
+ GUILayout.BeginVertical(GUILayout.Width(width));
+
+ p.isExpanded = EditorGUILayout.Foldout(p.isExpanded, SerializedValueString(p));
+ var allOn = true;
+ if(p.isExpanded)
+ {
+ foreach(var flag in System.Enum.GetValues(typeof(StaticEditorFlags)).Cast())
+ {
+ var wasOn = (flags & flag) != 0;
+ var on = EditorGUILayout.Toggle(flag + "", wasOn);
+ if(wasOn != on)
+ {
+ flags = flags ^ flag;
+ }
+ if(!on)
+ {
+ allOn = false;
+ }
+ }
+ }
+ if(allOn)
+ {
+ flags = (StaticEditorFlags)(-1);
+ }
+ p.intValue = (int)flags;
+
+ GUILayout.EndVertical();
+ }
+
+ private string SerializedValueString(SerializedProperty p)
+ {
+ if(fieldname == "GameObject.StaticEditorFlags")
+ {
+ switch(p.intValue)
+ {
+ case 0:
+ return "Not static";
+ case -1:
+ return "Static";
+ default:
+ return "Mixed static";
+ }
+ }
+ else if(p.IsRealArray())
+ {
+ return "Array[" + p.arraySize + "]";
+ }
+ return ValueString(p.GetValue());
+ }
+
+ private static string ValueString(object o)
+ {
+ if(o == null)
+ {
+ return "[none]";
+ }
+ return o.ToString();
+ }
+
+ private static bool MergeButton(string text, bool green)
+ {
+ if(green)
+ {
+ GUI.color = Color.green;
+ }
+ bool result = GUILayout.Button(text, GUILayout.ExpandWidth(false));
+ GUI.color = Color.white;
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionChangeValues.cs.meta b/Angels and Demons/Assets/Editor/MergeActionChangeValues.cs.meta
new file mode 100644
index 0000000..7c65940
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionChangeValues.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9a327ed62a5572a459e54869df134f42
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionDeleteComponent.cs b/Angels and Demons/Assets/Editor/MergeActionDeleteComponent.cs
new file mode 100644
index 0000000..d080ee1
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionDeleteComponent.cs
@@ -0,0 +1,71 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace GitMerge
+{
+ ///
+ /// The MergeAction that handles a Component which exists in "their" version but not "ours".
+ ///
+ public class MergeActionDeleteComponent : MergeActionExistence
+ {
+ protected Component ourComponent;
+ protected Component copy;
+
+ public MergeActionDeleteComponent(GameObject ours, Component ourComponent)
+ : base(ours, null)
+ {
+ this.ourComponent = ourComponent;
+
+ var go = new GameObject("GitMerge Object");
+ go.SetActiveForMerging(false);
+
+ copy = go.AddComponent(ourComponent);
+
+ if(GitMergeWindow.automerge)
+ {
+ UseOurs();
+ }
+ }
+
+ protected override void ApplyOurs()
+ {
+ if(ourComponent == null)
+ {
+ ourComponent = ours.AddComponent(copy);
+ ObjectDictionaries.SetAsOurObject(ourComponent);
+ }
+ }
+
+ protected override void ApplyTheirs()
+ {
+ if(ourComponent != null)
+ {
+ ObjectDictionaries.RemoveOurObject(ourComponent);
+ Object.DestroyImmediate(ourComponent, true);
+ }
+ }
+
+ public override void EnsureExistence()
+ {
+ UseOurs();
+ }
+
+ public override void OnGUI()
+ {
+ GUILayout.Label(copy.GetPlainType());
+
+ var defaultOptionColor = merged ? Color.gray : Color.white;
+
+ GUI.color = usingOurs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Keep Component"))
+ {
+ UseOurs();
+ }
+ GUI.color = usingTheirs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Delete Component"))
+ {
+ UseTheirs();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionDeleteComponent.cs.meta b/Angels and Demons/Assets/Editor/MergeActionDeleteComponent.cs.meta
new file mode 100644
index 0000000..b8a5cb9
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionDeleteComponent.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6414ac2f8bebc8b4bb33597977332630
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionDeleteGameObject.cs b/Angels and Demons/Assets/Editor/MergeActionDeleteGameObject.cs
new file mode 100644
index 0000000..316cc9b
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionDeleteGameObject.cs
@@ -0,0 +1,57 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace GitMerge
+{
+ ///
+ /// The MergeAction that handles a GameObject which exists in "our" version but not "theirs".
+ ///
+ public class MergeActionDeleteGameObject : MergeActionExistence
+ {
+ private bool oursWasActive;
+
+ public MergeActionDeleteGameObject(GameObject ours, GameObject theirs)
+ : base(ours, theirs)
+ {
+ oursWasActive = ours.activeSelf;
+
+ if(GitMergeWindow.automerge)
+ {
+ UseOurs();
+ }
+ }
+
+ protected override void ApplyOurs()
+ {
+ ours.SetActiveForMerging(true);
+ ours.SetActive(oursWasActive);
+ }
+
+ protected override void ApplyTheirs()
+ {
+ ours.SetActiveForMerging(false);
+ SceneView.currentDrawingSceneView.Repaint();
+ }
+
+ public override void EnsureExistence()
+ {
+ UseOurs();
+ }
+
+ public override void OnGUI()
+ {
+ var defaultOptionColor = merged ? Color.gray : Color.white;
+
+ GUI.color = usingOurs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Keep GameObject"))
+ {
+ UseOurs();
+ }
+ GUI.color = usingTheirs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Delete GameObject"))
+ {
+ UseTheirs();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionDeleteGameObject.cs.meta b/Angels and Demons/Assets/Editor/MergeActionDeleteGameObject.cs.meta
new file mode 100644
index 0000000..24955ab
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionDeleteGameObject.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 627176a0f25069f46919f6e3489eb8c3
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionExistence.cs b/Angels and Demons/Assets/Editor/MergeActionExistence.cs
new file mode 100644
index 0000000..7c3a4ba
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionExistence.cs
@@ -0,0 +1,23 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace GitMerge
+{
+ ///
+ /// The abstract base MergeAction for all MergeActions that manage whether or not an object exists
+ ///
+ public abstract class MergeActionExistence : MergeAction
+ {
+ public MergeActionExistence(GameObject ours, GameObject theirs)
+ : base(ours, theirs)
+ {
+ ObjectDictionaries.AddToSchroedingersObjects(ours ?? theirs, this);
+ }
+
+ ///
+ /// Apply whatever version that has the object existing, since it might be needed somewhere.
+ /// When overriding, call either UseOurs or UseTheirs to make sure to trigger the side effects.
+ ///
+ public abstract void EnsureExistence();
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionExistence.cs.meta b/Angels and Demons/Assets/Editor/MergeActionExistence.cs.meta
new file mode 100644
index 0000000..bb9395f
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionExistence.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f94b44d1572d0d14ea949190ef678a3a
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionNewComponent.cs b/Angels and Demons/Assets/Editor/MergeActionNewComponent.cs
new file mode 100644
index 0000000..ca6c760
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionNewComponent.cs
@@ -0,0 +1,66 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace GitMerge
+{
+ ///
+ /// The MergeAction that handles Components that exist in "our" version but not in "theirs".
+ ///
+ public class MergeActionNewComponent : MergeActionExistence
+ {
+ protected Component ourComponent;
+ protected Component theirComponent;
+
+ public MergeActionNewComponent(GameObject ours, Component theirComponent)
+ : base(ours, null)
+ {
+ this.theirComponent = theirComponent;
+
+ if(GitMergeWindow.automerge)
+ {
+ UseOurs();
+ }
+ }
+
+ protected override void ApplyOurs()
+ {
+ if(ourComponent)
+ {
+ ObjectDictionaries.RemoveCopyOf(theirComponent);
+ Object.DestroyImmediate(ourComponent, true);
+ }
+ }
+
+ protected override void ApplyTheirs()
+ {
+ if(!ourComponent)
+ {
+ ourComponent = ours.AddComponent(theirComponent);
+ ObjectDictionaries.SetAsCopy(ourComponent, theirComponent);
+ }
+ }
+
+ public override void EnsureExistence()
+ {
+ UseTheirs();
+ }
+
+ public override void OnGUI()
+ {
+ GUILayout.Label(theirComponent.GetPlainType());
+
+ var defaultOptionColor = merged ? Color.gray : Color.white;
+
+ GUI.color = usingOurs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Don't add Component"))
+ {
+ UseOurs();
+ }
+ GUI.color = usingTheirs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Add new Component"))
+ {
+ UseTheirs();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionNewComponent.cs.meta b/Angels and Demons/Assets/Editor/MergeActionNewComponent.cs.meta
new file mode 100644
index 0000000..69410cd
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionNewComponent.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6c13706c0946b934a95c41957ce759f5
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionNewGameObject.cs b/Angels and Demons/Assets/Editor/MergeActionNewGameObject.cs
new file mode 100644
index 0000000..1459aab
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionNewGameObject.cs
@@ -0,0 +1,59 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace GitMerge
+{
+ ///
+ /// The MergeAction that handles GameObjects that exist in "their" version but not in "ours".
+ ///
+ public class MergeActionNewGameObject : MergeActionExistence
+ {
+ public MergeActionNewGameObject(GameObject ours, GameObject theirs)
+ : base(ours, theirs)
+ {
+ if(GitMergeWindow.automerge)
+ {
+ UseTheirs();
+ }
+ }
+
+ protected override void ApplyOurs()
+ {
+ if(ours)
+ {
+ ObjectDictionaries.RemoveCopyOf(theirs);
+ GameObject.DestroyImmediate(ours, true);
+ }
+ }
+
+ protected override void ApplyTheirs()
+ {
+ if(!ours)
+ {
+ ours = ObjectDictionaries.InstantiateFromMerging(theirs);
+ ObjectDictionaries.SetAsCopy(ours, theirs);
+ }
+ }
+
+ public override void EnsureExistence()
+ {
+ UseTheirs();
+ }
+
+ public override void OnGUI()
+ {
+ var defaultOptionColor = merged ? Color.gray : Color.white;
+
+ GUI.color = usingOurs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Don't add GameObject"))
+ {
+ UseOurs();
+ }
+ GUI.color = usingTheirs ? Color.green : defaultOptionColor;
+ if(GUILayout.Button("Add new GameObject"))
+ {
+ UseTheirs();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionNewGameObject.cs.meta b/Angels and Demons/Assets/Editor/MergeActionNewGameObject.cs.meta
new file mode 100644
index 0000000..012ff36
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionNewGameObject.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fb60feef4712bcc4a9f2d266c289a2d7
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeActionParenting.cs b/Angels and Demons/Assets/Editor/MergeActionParenting.cs
new file mode 100644
index 0000000..eef3bea
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionParenting.cs
@@ -0,0 +1,97 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace GitMerge
+{
+ ///
+ /// The MergeAction that handles a differing parents for a Transform.
+ ///
+ public class MergeActionParenting : MergeAction
+ {
+ private Transform transform;
+ private Transform ourParent;
+ private Transform theirParent;
+
+ public MergeActionParenting(Transform transform, Transform ourParent, Transform theirParent)
+ : base(transform.gameObject, null)
+ {
+ this.transform = transform;
+ this.ourParent = ourParent;
+ this.theirParent = theirParent;
+ }
+
+ protected override void ApplyOurs()
+ {
+ transform.parent = ourParent;
+ }
+
+ protected override void ApplyTheirs()
+ {
+ var ourVersion = ObjectDictionaries.GetOurCounterpartFor(theirParent) as Transform;
+ if(theirParent && !ourVersion)
+ {
+ if(EditorUtility.DisplayDialog("The chosen parent currently does not exist.", "Do you want do add it?", "Yes", "No"))
+ {
+ ObjectDictionaries.EnsureExistence(theirParent.gameObject);
+ ourVersion = ObjectDictionaries.GetOurCounterpartFor(theirParent) as Transform;
+
+ transform.parent = ourVersion;
+ }
+ else
+ {
+ throw new System.Exception("User Abort.");
+ }
+ }
+ else
+ {
+ transform.parent = ourVersion;
+ }
+ }
+
+ public override void OnGUI()
+ {
+ GUILayout.BeginVertical();
+ GUILayout.Label("Parent");
+
+ GUILayout.BeginHorizontal();
+
+ GUILayout.Label(ourParent ? ourParent.ToString() : "None", GUILayout.Width(100));
+
+ if(MergeButton(">>>", usingOurs))
+ {
+ UseOurs();
+ }
+
+ var c = GUI.backgroundColor;
+ GUI.backgroundColor = Color.white;
+ var newParent = EditorGUILayout.ObjectField(transform.parent, typeof(Transform), true, GUILayout.Width(170)) as Transform;
+ if(newParent != transform.parent)
+ {
+ transform.parent = newParent;
+ UsedNew();
+ }
+ GUI.backgroundColor = c;
+
+ if(MergeButton("<<<", usingTheirs))
+ {
+ UseTheirs();
+ }
+
+ GUILayout.Label(theirParent ? theirParent.ToString() : "None", GUILayout.Width(100));
+
+ GUILayout.EndHorizontal();
+ GUILayout.EndVertical();
+ }
+
+ private static bool MergeButton(string text, bool green)
+ {
+ if(green)
+ {
+ GUI.color = Color.green;
+ }
+ bool result = GUILayout.Button(text, GUILayout.ExpandWidth(false));
+ GUI.color = Color.white;
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeActionParenting.cs.meta b/Angels and Demons/Assets/Editor/MergeActionParenting.cs.meta
new file mode 100644
index 0000000..6e45544
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeActionParenting.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 29139dc665a42db45b67df932a04c810
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeManager.cs b/Angels and Demons/Assets/Editor/MergeManager.cs
new file mode 100644
index 0000000..edefe88
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeManager.cs
@@ -0,0 +1,123 @@
+using UnityEngine;
+using System.IO;
+using System.Diagnostics;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace GitMerge
+{
+ public abstract class MergeManager
+ {
+ protected VCS vcs { private set; get; }
+ protected GitMergeWindow window { private set; get; }
+
+ internal List allMergeActions;
+
+ protected static string fileName;
+ protected static string theirFilename;
+
+ public static bool isMergingScene { protected set; get; }
+ public static bool isMergingPrefab { get { return !isMergingScene; } }
+
+
+ public MergeManager(GitMergeWindow window, VCS vcs)
+ {
+ this.window = window;
+ this.vcs = vcs;
+ allMergeActions = new List();
+ }
+
+ ///
+ /// Creates "their" version of the file at the given path,
+ /// named filename--THEIRS.unity.
+ ///
+ /// The path of the file, relative to the project folder.
+ protected void GetTheirVersionOf(string path)
+ {
+ fileName = path;
+
+ string basepath = Path.GetDirectoryName(path);
+ string sname = Path.GetFileNameWithoutExtension(path);
+ string extension = Path.GetExtension(path);
+
+ string ours = Path.Combine(basepath, sname + "--OURS" + extension);
+ theirFilename = Path.Combine(basepath, sname + "--THEIRS" + extension);
+
+ File.Copy(path, ours);
+ try
+ {
+ vcs.GetTheirs(path);
+ }
+ catch(VCSException e)
+ {
+ File.Delete(ours);
+ throw e;
+ }
+ File.Move(path, theirFilename);
+ File.Move(ours, path);
+ }
+
+ ///
+ /// Finds all specific merge conflicts between two sets of GameObjects,
+ /// representing "our" scene and "their" scene.
+ ///
+ /// The GameObjects of "our" version of the scene.
+ /// The GameObjects of "their" version of the scene.
+ protected void BuildAllMergeActions(List ourObjects, List theirObjects)
+ {
+ allMergeActions = new List();
+
+ //Map "their" GameObjects to their respective ids
+ var theirObjectsDict = new Dictionary();
+ foreach(var theirs in theirObjects)
+ {
+ theirObjectsDict.Add(ObjectIDFinder.GetIdentifierFor(theirs), theirs);
+ }
+
+ foreach(var ours in ourObjects)
+ {
+ //Try to find "their" equivalent to "our" GameObjects
+ var id = ObjectIDFinder.GetIdentifierFor(ours);
+ GameObject theirs;
+ theirObjectsDict.TryGetValue(id, out theirs);
+
+ //If theirs is null, mergeActions.hasActions will be false
+ var mergeActions = new GameObjectMergeActions(ours, theirs);
+ if(mergeActions.hasActions)
+ {
+ allMergeActions.Add(mergeActions);
+ }
+ //Remove "their" GameObject from the dict to only keep those new to us
+ theirObjectsDict.Remove(id);
+ }
+
+ //Every GameObject left in the dict is a...
+ foreach(var theirs in theirObjectsDict.Values)
+ {
+ //...new GameObject from them
+ var mergeActions = new GameObjectMergeActions(null, theirs);
+ if(mergeActions.hasActions)
+ {
+ allMergeActions.Add(mergeActions);
+ }
+ }
+ }
+
+ public abstract void CompleteMerge();
+
+ public virtual void AbortMerge()
+ {
+ MergeAction.inMergePhase = false;
+
+ foreach(var actions in allMergeActions)
+ {
+ actions.UseOurs();
+ }
+ ObjectDictionaries.DestroyTheirObjects();
+ ObjectDictionaries.Clear();
+ allMergeActions = null;
+
+ window.ShowNotification(new GUIContent("Merge aborted."));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeManager.cs.meta b/Angels and Demons/Assets/Editor/MergeManager.cs.meta
new file mode 100644
index 0000000..9ccaa2a
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeManager.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b7759967878514e4aace05e726704e8f
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeManagerPrefab.cs b/Angels and Demons/Assets/Editor/MergeManagerPrefab.cs
new file mode 100644
index 0000000..8b614d5
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeManagerPrefab.cs
@@ -0,0 +1,149 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+
+namespace GitMerge
+{
+ public class MergeManagerPrefab : MergeManager
+ {
+ //Stuff needed for prefab merging
+ public static GameObject ourPrefab { private set; get; }
+ private static GameObject theirPrefab;
+ public static GameObject ourPrefabInstance { private set; get; }
+ private static string previouslyOpenedScene;
+
+
+ public MergeManagerPrefab(GitMergeWindow window, VCS vcs)
+ : base(window, vcs)
+ {
+
+ }
+
+ public bool InitializeMerge(GameObject prefab)
+ {
+ if(!EditorApplication.SaveCurrentSceneIfUserWantsTo())
+ {
+ return false;
+ }
+
+ isMergingScene = false;
+ MergeAction.inMergePhase = false;
+
+ ObjectDictionaries.Clear();
+
+ //checkout "their" version
+ GetTheirVersionOf(AssetDatabase.GetAssetOrScenePath(prefab));
+ AssetDatabase.Refresh();
+
+ ourPrefab = prefab;
+
+ //Open a new Scene that will only display the prefab
+ previouslyOpenedScene = EditorApplication.currentScene;
+ EditorApplication.NewScene();
+
+ //make the new scene empty
+ Object.DestroyImmediate(Camera.main.gameObject);
+
+ //instantiate our object in order to view it while merging
+ ourPrefabInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
+
+ //find all of "our" objects in the prefab
+ var ourObjects = GetAllObjects(prefab);
+
+ theirPrefab = AssetDatabase.LoadAssetAtPath(theirFilename, typeof(GameObject)) as GameObject;
+ theirPrefab.hideFlags = HideFlags.HideAndDontSave;
+ var theirObjects = GetAllObjects(theirPrefab);
+
+ //create list of differences that have to be merged
+ BuildAllMergeActions(ourObjects, theirObjects);
+
+ if(allMergeActions.Count == 0)
+ {
+ AssetDatabase.DeleteAsset(theirFilename);
+ OpenPreviousScene();
+ window.ShowNotification(new GUIContent("No conflict found for this prefab."));
+ return false;
+ }
+ MergeAction.inMergePhase = true;
+ ourPrefabInstance.Highlight();
+ return true;
+ }
+
+ ///
+ /// Recursively find all GameObjects that are part of the prefab
+ ///
+ /// The prefab to analyze
+ /// The list with all the objects already found. Pass null in the beginning.
+ /// The list with all the objects
+ private static List GetAllObjects(GameObject prefab, List list = null)
+ {
+ if(list == null)
+ {
+ list = new List();
+ }
+
+ list.Add(prefab);
+ foreach(Transform t in prefab.transform)
+ {
+ GetAllObjects(t.gameObject, list);
+ }
+ return list;
+ }
+
+ ///
+ /// Completes the merge process after solving all conflicts.
+ /// Cleans up the scene by deleting "their" GameObjects, clears merge related data structures,
+ /// executes git add scene_name.
+ ///
+ public override void CompleteMerge()
+ {
+ MergeAction.inMergePhase = false;
+
+ //ObjectDictionaries.Clear();
+
+ allMergeActions = null;
+
+ //TODO: Could we explicitly just save the prefab?
+ AssetDatabase.SaveAssets();
+
+ //Mark as merged for git
+ vcs.MarkAsMerged(fileName);
+
+ //directly committing here might not be that smart, since there might be more conflicts
+
+ ourPrefab = null;
+
+ //delete their prefab file
+ AssetDatabase.DeleteAsset(theirFilename);
+
+ OpenPreviousScene();
+ window.ShowNotification(new GUIContent("Prefab successfully merged."));
+ }
+
+ ///
+ /// Aborts merge by using "our" version in all conflicts.
+ /// Cleans up merge related data.
+ ///
+ public override void AbortMerge()
+ {
+ base.AbortMerge();
+
+ //delete prefab file
+ AssetDatabase.DeleteAsset(theirFilename);
+ OpenPreviousScene();
+ ourPrefab = null;
+ }
+
+ ///
+ /// Opens the previously opened scene, if there was any.
+ ///
+ private static void OpenPreviousScene()
+ {
+ if(!string.IsNullOrEmpty(previouslyOpenedScene))
+ {
+ EditorApplication.OpenScene(previouslyOpenedScene);
+ previouslyOpenedScene = "";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeManagerPrefab.cs.meta b/Angels and Demons/Assets/Editor/MergeManagerPrefab.cs.meta
new file mode 100644
index 0000000..74223b5
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeManagerPrefab.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f8db1a549c01168468790b325c85f3c1
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/MergeManagerScene.cs b/Angels and Demons/Assets/Editor/MergeManagerScene.cs
new file mode 100644
index 0000000..5ebc825
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeManagerScene.cs
@@ -0,0 +1,118 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+
+namespace GitMerge
+{
+ public class MergeManagerScene : MergeManager
+ {
+ public MergeManagerScene(GitMergeWindow window, VCS vcs)
+ : base(window, vcs)
+ {
+
+ }
+
+ public bool InitializeMerge()
+ {
+ isMergingScene = true;
+
+ //Ask if the scene should be saved, because...
+ if(!EditorApplication.SaveCurrentSceneIfUserWantsTo())
+ {
+ return false;
+ }
+ //...we are reloading it to prevent objects from not having a scene id.
+ EditorApplication.OpenScene(EditorApplication.currentScene);
+
+ MergeAction.inMergePhase = false;
+
+ ObjectDictionaries.Clear();
+
+ //checkout "their" version
+ GetTheirVersionOf(EditorApplication.currentScene);
+ AssetDatabase.Refresh();
+
+ //find all of "our" objects
+ var ourObjects = GetAllSceneObjects();
+ ObjectDictionaries.SetAsOurObjects(ourObjects);
+
+ //add "their" objects
+ EditorApplication.OpenSceneAdditive(theirFilename);
+
+ //delete scene file
+ AssetDatabase.DeleteAsset(theirFilename);
+
+ //find all of "their" objects
+ var addedObjects = GetAllNewSceneObjects(ourObjects);
+ ObjectDictionaries.SetAsTheirObjects(addedObjects);
+
+ //create list of differences that have to be merged
+ BuildAllMergeActions(ourObjects, addedObjects);
+
+ if(allMergeActions.Count == 0)
+ {
+ window.ShowNotification(new GUIContent("No conflict found for this scene."));
+ return false;
+ }
+ MergeAction.inMergePhase = true;
+ return true;
+ }
+
+ private static List GetAllSceneObjects()
+ {
+ var objects = (GameObject[])Object.FindObjectsOfType(typeof(GameObject));
+ return new List(objects);
+ }
+
+ ///
+ /// Finds all GameObjects in the scene, minus the ones passed.
+ ///
+ private static List GetAllNewSceneObjects(List oldObjects)
+ {
+ var all = GetAllSceneObjects();
+ var old = oldObjects;
+
+ foreach(var obj in old)
+ {
+ all.Remove(obj);
+ }
+
+ return all;
+ }
+
+ ///
+ /// Completes the merge process after solving all conflicts.
+ /// Cleans up the scene by deleting "their" GameObjects, clears merge related data structures,
+ /// executes git add scene_name.
+ ///
+ public override void CompleteMerge()
+ {
+ MergeAction.inMergePhase = false;
+
+ ObjectDictionaries.DestroyTheirObjects();
+ ObjectDictionaries.Clear();
+ EditorApplication.SaveScene();
+
+ allMergeActions = null;
+
+ //Mark as merged for git
+ vcs.MarkAsMerged(fileName);
+
+ //directly committing here might not be that smart, since there might be more conflicts
+
+ window.ShowNotification(new GUIContent("Scene successfully merged."));
+ }
+
+ ///
+ /// Aborts merge by using "our" version in all conflicts.
+ /// Cleans up merge related data.
+ ///
+ public override void AbortMerge()
+ {
+ base.AbortMerge();
+
+ //Save scene
+ EditorApplication.SaveScene();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Angels and Demons/Assets/Editor/MergeManagerScene.cs.meta b/Angels and Demons/Assets/Editor/MergeManagerScene.cs.meta
new file mode 100644
index 0000000..37afd02
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/MergeManagerScene.cs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2143b0a739488bb4698c51777c4863fc
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
diff --git a/Angels and Demons/Assets/Editor/ObjectDictionaries.cs b/Angels and Demons/Assets/Editor/ObjectDictionaries.cs
new file mode 100644
index 0000000..f76fa08
--- /dev/null
+++ b/Angels and Demons/Assets/Editor/ObjectDictionaries.cs
@@ -0,0 +1,262 @@
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace GitMerge
+{
+ ///
+ /// Dictionaries that categorize the scene's objects into our objects, their objects, and temporary
+ /// copies of their objects that have been instantiated while merging.
+ ///
+ public static class ObjectDictionaries
+ {
+ //This dict holds all of "our" objects
+ //Needed for Reference handling
+ //
+ private static Dictionary ourObjects = new Dictionary();
+
+ //This dict maps our instances of their objects
+ //Whenever we instantiate a copy of "their" new object, they're both added here
+ private static Dictionary