using System.Collections; using System.Collections.Generic; using UnityEngine; using RPGCreationKit; using RPGCreationKit.BehaviourTree; using UnityEngine.AI; using RPGCreationKit.SaveSystem; using RPGCreationKit.CellsSystem; using RPGCreationKit.Player; using System; namespace RPGCreationKit.AI { [DisallowMultipleComponent] public class RckAI : AIBehaviourTreed { public Race race; public bool hasBeenInstantiated; private void Reset() { iGUIDReferences = GetComponent(); m_Anim = GetComponent(); attributes = GetComponent(); ragdoll = GetComponentInChildren(); aiLookAt = GetComponent(); headPos = GetComponentInChildren().transform; entityFocusPart = headPos; physicalCollider = GetComponent(); interactableCollider = RCKFunctions.GetChildWithName(this.gameObject, "InteractZone").GetComponent(); agent = GetComponent(); m_Rigidbody = GetComponent(); movementType = MovementType.UnityNavmesh; arriveSteeringBehaviourDeceleration = Deceleration.Normal; inventory = GetComponent(); equipment = GetComponent(); bodyData = GetComponent(); defaultWeaponOnHand = RCKFunctions.GetChildWithName(this.gameObject, "DefaultWeapon").GetComponent(); defaultWeapon = defaultWeaponOnHand.weaponItem; lootingPoint = GetComponentInChildren(); lootingPoint.inventory = inventory; lootingPoint.equipment = equipment; onlineComponents.ai = GetComponent(); onlineComponents.GFX = RCKFunctions.GetChildWithName(this.gameObject, "GFX"); onlineComponents.rigidbody = m_Rigidbody; onlineComponents.animator = m_Anim; onlineComponents.iGUIDReferences = iGUIDReferences; onlineComponents.agent = agent; onlineComponents.audioSource = GetComponent(); } public void DelayedStart() { base.Start(); // Ignore children colliders CapsuleCollider coll = GetComponent(); var colliders = GetComponentsInChildren(); for (int i = 0; i < colliders.Length; i++) Physics.IgnoreCollision(coll, colliders[i]); } public IEnumerator QueueSet(bool _combat, string _treeID) { if (anyWaitForSwitchRunning) yield return null; SetNewBehaviourTree(_combat, _treeID); } [AIInvokable] public void SetNewBehaviourTree(bool _combat, string _treeID) { if (anyWaitForSetRunning) { StartCoroutine(QueueSet(_combat, _treeID)); return; } // Execute OnExit node if present if (currentBehaviour != null && currentBehaviour.nodes.Count > 0 && ((currentBehaviour.nodes[0] as BTNode).GetOutputPort("onExitNode").Connection != null && ((currentBehaviour.nodes[0] as BTNode).GetOutputPort("onExitNode").Connection.node != null))) { anyWaitForSetRunning = true; // Execute OnExit before changing the tree StartCoroutine(SetNewBehaviourTree_WaitForExitNodeTask(_combat, _treeID)); } else { RPGCK_BT newBT = BehaviourDatabase.GetItem(_treeID).RPGCK_BTCopy(gameObject, this); if (_combat) combatBehaviourTree = newBT; else purposeBehaviourTree = newBT; } } IEnumerator SetNewBehaviourTree_WaitForExitNodeTask(bool _combat, string _treeID) { pauseBT = true; yield return new WaitForEndOfFrame(); var node = (((currentBehaviour.nodes[0] as BTNode).GetOutputPort("onExitNode").Connection.node) as BTNode); node.ReEvaluate(); while (node.m_NodeState == NodeState.Null || node.m_NodeState == NodeState.Running) { node.Execute(); yield return new WaitForEndOfFrame(); } RPGCK_BT newBT = BehaviourDatabase.GetItem(_treeID).RPGCK_BTCopy(gameObject, this); if (_combat) combatBehaviourTree = newBT; else purposeBehaviourTree = newBT; anyWaitForSetRunning = false; pauseBT = false; yield break; } [AIInvokable] public void SetNewBehaviourTreeToAI(string _toAI, bool _combat, string _treeID) { // Get the RckAI if possible RckAI sTo = null; CellsSystem.CellInformation.TryToGetAI(_toAI, out sTo); if (sTo != null) { sTo.SetNewBehaviourTree(_combat, _treeID); } } public bool TryLoadFromSavefile() { GetMyCellInfoImmediate(); var allAI = SaveSystemManager.instance.saveFile.AIData.aiDictionary; if (allAI.ContainsKey(entityID)) { // Look if it's in the same original cell if (startingCellID == allAI[entityID].saveCellID || hasBeenInstantiated) { // Load it StartCoroutine(LoadAI(allAI[entityID])); } else { CellInformation.activeCells[myCellInfo.cell.ID].aiInWorld.Remove(entityID); // Destroy this AI, it will be instantiated by CellInfo Destroy(this.gameObject); } } else { pauseBT = true; StartCoroutine(NewAIWaitsForLoad()); return true; } return true; } public IEnumerator NewAIWaitsForLoad() { isLoaded = true; // Wait for game to start before letting the ai do its job while (!CellInformation.AllActiveCellsLoaded() || WorldManager.instance.isLoading) yield return null; GetMyCellInfoImmediate(); pauseBT = false; } public IEnumerator LoadAI(AISaveData data) { yield return new WaitForEndOfFrame(); pauseBT = true; yield return new WaitForEndOfFrame(); agent.enabled = false; startingCellID = data.startingCellID; transform.position = data.position; transform.rotation = data.rotation; // Load Entity Attributes attributes.attributes = data.aiAttributes.attributes; attributes.derivedAttributes = data.aiAttributes.derivedAttributes; attributes.ExecuteEffects(data.aiAttributes.activeEffects.ToArray()); ResetRecover(); inventory.ClearInventoryAndEquipment(); equipment.OnEquipmentChanges(); OnEquipmentChangesHands(); OnEquipmentChangesAmmo(); // Those items gets equipped after everyone else, used for the shield List toEquipAfter = new List(); for(int i = 0; i < data.aiInventory.items.Count; i++) { var item = data.aiInventory.items[i]; var aItem = inventory.AddItem(item.externalItemID, item.Amount); aItem.metadata.IsOwnedByNPC = true; if (item.isEquipped) { if (aItem.item.itemType == ItemTypes.ArmorItem && RCKFunctions.ContainsBiped(((ArmorItem)(aItem.item)).Bipeds, BipedObject.Shield)) { // Delay the equipment of shields toEquipAfter.Add(aItem); } else equipment.Equip(aItem); } } equipment.OnEquipmentChanges(); OnEquipmentChangesHands(); OnEquipmentChangesAmmo(); // Equip delayed for(int i = 0; i < toEquipAfter.Count; i++) { equipment.Equip(toEquipAfter[i]); } inventory.InitializeInventory(); GetMyCellInfoImmediate(); // Load important states isEssential = data.isEssential; if (!data.isAlive) { Die(); if (data.usesRagdoll) { ragdoll.ForceRagdoll(); // Load hips pos/rot ragdoll.LoadFromSaveData(data.ragdollSaveData); } } else // Entity is alive { followTargetOutsideOfCell = data.followTargetOutsideOfCell; // Load Behaviour Trees if (!string.IsNullOrEmpty(data.purposeBehaviourTreeID)) purposeBehaviourTree = BehaviourDatabase.GetItem(data.purposeBehaviourTreeID).RPGCK_BTCopy(this.gameObject, this); if (!string.IsNullOrEmpty(data.combatBehaviourTreeID)) combatBehaviourTree = BehaviourDatabase.GetItem(data.combatBehaviourTreeID).RPGCK_BTCopy(this.gameObject, this); SwitchBehaviourTree(!data.isUsingPurposeBehaviour); // Movements lookAtVector3IfStopped = data.lookAtVector3IfStopped; vector3ToLookAtIfStopped = data.vector3ToLookAtIfStopped; // Load perception radius = data.radius; sphereYOffset = data.sphereYoffset; sphereForwardOffset = data.sphereZoffset; // Load Combat state nad entities fighting if (data.isInCombat || data.enterInCombatCalled) { for(int i = 0; i < data.entityHesFighting.Count; i++) { if(data.entityHesFighting[i] == "PLAYER_ID") { // Add the player enemyTargets.Add(new VisibleEnemy(RckPlayer.instance, RckPlayer.instance.playerAttributes, new AggroInfo(50))); } else { RckAI ai = null; CellInformation.activeCells[myCellInfo.cell.ID].aiInWorld.TryGetValue(data.entityHesFighting[i], out ai); if (ai != null) enemyTargets.Add(new VisibleEnemy(ai, ai.GetComponent(), new AggroInfo(50))); } } if (enemyTargets.Count > 0) EnterInCombatAgainst(enemyTargets[0].m_entity); } // Load Dialogues if(!string.IsNullOrEmpty(data.currentDialogueID)) currentDialogueGraph = DialoguesDatabase.GetItem(data.currentDialogueID); if (!string.IsNullOrEmpty(data.previousDialogueID)) previousDialogueGraph = DialoguesDatabase.GetItem(data.previousDialogueID); if (!string.IsNullOrEmpty(data.defaultDialogueID)) defaultDialogueGraph = DialoguesDatabase.GetItem(data.defaultDialogueID); dialogueSystemEnabled = data.dialogueSystemEnabled; // Load MainTarget if(!string.IsNullOrEmpty(data.mainTargetID)) { ITargetableType targetableType = (ITargetableType)data.mainTargetType; switch (targetableType) { case ITargetableType.Entity: if (data.mainTargetID == "PLAYER_ID") SetTarget(RckPlayer.instance.gameObject); else { RckAI entity = null; CellInformation.TryToGetAI(data.mainTargetID, out entity); if (entity != null) SetTarget(entity.gameObject); } break; } } if(!string.IsNullOrEmpty(data.cellIDMovingTo)) { MoveToCell(data.cellIDMovingTo); } // Load PurposeState purposeState = data.purposeState; // Try to solve purpose ref if (!string.IsNullOrEmpty(purposeState.ITargetableID)) { ITargetableType targetableType = (ITargetableType)data.purposeState.ITargetableType; switch (targetableType) { case ITargetableType.Entity: if (data.mainTargetID == "PLAYER_ID") purposeState.AssignPurpose(this, RckPlayer.instance.gameObject, data.purposeState.clearsOn, data.purposeState.clearsOnData, data.purposeState.nextPurposeBehaviourID); else { RckAI entity = null; CellInformation.TryToGetAI(data.mainTargetID, out entity); if (entity != null) purposeState.AssignPurpose(this, entity.gameObject, data.purposeState.clearsOn, data.purposeState.clearsOnData, data.purposeState.nextPurposeBehaviourID); } break; case ITargetableType.Door: purposeState.AssignPurpose(this, GetTransformToDoor(purposeState.ITargetableExtraData).gameObject, data.purposeState.clearsOn, data.purposeState.clearsOnData, data.purposeState.nextPurposeBehaviourID); break; case ITargetableType.PRef: purposeState.AssignPurpose(this, PersistentReferences.PersistentReferenceManager.instance.refs[purposeState.ITargetableID].gameObject, data.purposeState.clearsOn, data.purposeState.clearsOnData, data.purposeState.nextPurposeBehaviourID); break; case ITargetableType.Transform: if(myCellInfo.allTargetables.ContainsKey(purposeState.ITargetableID)) purposeState.AssignPurpose(this, myCellInfo.allTargetables[purposeState.ITargetableID].gameObject, data.purposeState.clearsOn, data.purposeState.clearsOnData, data.purposeState.nextPurposeBehaviourID); break; case ITargetableType.AIPath: if (myCellInfo.allAIPaths.ContainsKey(purposeState.ITargetableID)) purposeState.AssignPurpose(this, myCellInfo.allAIPaths[purposeState.ITargetableID].gameObject, data.purposeState.clearsOn, data.purposeState.clearsOnData, data.purposeState.nextPurposeBehaviourID); break; } if (data.isUsingPurposeBehaviour) purposeState.ResumePurpose(); } else purposeState.ClearPurpose(); // Move to door position if he entered a door if (data.enteredInADoorLastTime) { if (!data.enteredInADoorLastTimeOverridePos) { if (!string.IsNullOrEmpty(data.doorEnteredID)) { // TODO If he entered < 5 minutes ago bring him to the teleport mark Door door = CellInformation.activeCells[myCellInfo.cell.ID].allDoors[data.doorEnteredID]; transform.position = door.teleportMarker.position; // Otherwise if he entered > 5 minutes ago randomize his position } else { // just telport him to the cell entry transform.position = myCellInfo.cellEntry.transform.position; transform.rotation = myCellInfo.cellEntry.transform.rotation; } } else { transform.position = data.enteredDoorLastTimePos; transform.rotation = data.enteredDoorLastTimeRot; // Reset boolean enteredInADoorLastTime = false; } } if(data.enteredInADoorLastTimeOverridePos) { enteredInADoorLastTimeOverridePos = data.enteredInADoorLastTimeOverridePos; enteredDoorLastTimePos = data.enteredDoorLastTimePos; enteredDoorLastTimeRot = data.enteredDoorLastTimeRot; } if(data.isFollowingAIPath) { shouldFollowAIPath = true; aiPathCurrentIndex = data.aiPathCurrentIndex; aiPathResumeFromCurrentIndex = true; invertAIPath = data.invertAIPath; FollowCurrentPath(); } SetOverrideShouldFollowMainTarget(data.overrideShouldFollowMainTargetActive, data.overrideShouldFollowMainTarget); // Load factions for (int i = 0; i < data.allFactions.Count; i++) AddToFaction(data.allFactions[i]); } // Assign runtime cell runtimeStartingCell = myCellInfo.cell.ID; agent.enabled = true; isLoaded = true; // Wait for game to start before letting the ai do its job while (!CellInformation.AllActiveCellsLoaded() || WorldManager.instance.isLoading) yield return null; pauseBT = false; yield return null; } /// /// THis method is called when the AI is being unloaded because the cell is too far away from the Player /// public void OnBeingUnloaded(CellInformation calledBy) { if (calledBy == myCellInfo) // if the ai is in the cell that is being unloaded { if (teleportIfUnloadedWhileGoingToDoor) { if (isReachingDoor) { isReachingDoor = false; enteredInADoorLastTime = true; TeleportToCell(IsReachingCellID); } } } } public void SaveOnFile() { if (isInOfflineMode) // AI shouldn't be saved in offline mode return; GetMyCellInfoImmediate(); string saveCellID = myCellInfo.cell.ID; if (!string.IsNullOrEmpty(cellIDOfLastSaved) && cellIDOfLastSaved != saveCellID) { // remove cellidoflastsaved var dict = SaveSystemManager.instance.saveFile.AIData.aiCellDictionary; if (dict.ContainsKey(cellIDOfLastSaved)) dict[cellIDOfLastSaved].allIDs.Remove(entityID); } cellIDOfLastSaved = saveCellID; var allAI = SaveSystemManager.instance.saveFile.AIData.aiDictionary; // Save this AI to the allAI dictionary if (allAI.ContainsKey(entityID)) { // Update entry allAI[entityID] = ToSaveData(); } else // create allAI.Add(entityID, ToSaveData()); // Save/Update/DoNothing on the aiCellDictionary var allCellAI = SaveSystemManager.instance.saveFile.AIData.aiCellDictionary; // If the AI was loaded in a different cell from the starting point if(runtimeStartingCell != saveCellID) { if (allCellAI.ContainsKey(runtimeStartingCell)) { if (allCellAI[runtimeStartingCell].allIDs.Contains(entityID)) allCellAI[runtimeStartingCell].allIDs.Remove(entityID); } // Add to the new one if (!allCellAI.ContainsKey(saveCellID)) { allCellAI.Add(saveCellID, new AIIDList()); allCellAI[saveCellID].allIDs.Add(entityID); } else { if (!allCellAI[saveCellID].allIDs.Contains(entityID)) allCellAI[saveCellID].allIDs.Add(entityID); } transform.SetParent(CellInformation.activeCells[saveCellID].aiContainer); if(!CellInformation.activeCells[saveCellID].aiInWorld.ContainsKey(entityID)) CellInformation.activeCells[saveCellID].aiInWorld.Add(entityID, this); } else { if (!allCellAI.ContainsKey(runtimeStartingCell)) { allCellAI.Add(runtimeStartingCell, new AIIDList()); allCellAI[runtimeStartingCell].allIDs.Add(entityID); } } } public void SaveOnFile_JustInstantaited() { var allAI = SaveSystemManager.instance.saveFile.AIData.aiDictionary; // Save this AI to the allAI dictionary if (allAI.ContainsKey(entityID)) { // Update entry allAI[entityID] = ToSaveData(); } else // create allAI.Add(entityID, ToSaveData()); // Update CellAI dictionary var aiCell = SaveSystem.SaveSystemManager.instance.saveFile.AIData.aiCellDictionary; if (aiCell.ContainsKey(cellIDOfLastSaved)) { // Update it var res = aiCell[cellIDOfLastSaved]; if (!res.allIDs.Contains(entityID)) { res.allIDs.Add(entityID); aiCell[cellIDOfLastSaved] = res; } } else { AIIDList list = new AIIDList(); list.allIDs.Add(entityID); // Create new entry aiCell.Add(cellIDOfLastSaved, list); } } public AISaveData ToSaveData() { AISaveData newData = new AISaveData() { position = transform.position, rotation = transform.rotation, aiAttributes = attributes.ToSaveData(), aiInventory = inventory.ToSaveData(), isAlive = isAlive, isHostile = isHostile, isHostileAgainstPC = isHostileAgainstPC, isInCombat = isInCombat, isFleeing = isFleeing, isUnconscious = isUnconscious, enterInCombatCalled = enterInCombatCalled, isEssential = isEssential, usesRagdoll = usesRagdoll, dialogueSystemEnabled = dialogueSystemEnabled, currentDialogueID = (currentDialogueGraph) ? currentDialogueGraph.ID : string.Empty, previousDialogueID = (previousDialogueGraph) ? previousDialogueGraph.ID : string.Empty, defaultDialogueID = (defaultDialogueGraph) ? defaultDialogueGraph.ID : string.Empty, isInConversation = isInConversation, isTalking = isTalking, isListening = isListening, mustSpeakToPlayer = mustSpeakToPlayer, mustSpeakToEntity = mustSpeakToEntity, speakerIndex = speakerIndex, usesMovements = usesMovements, usesOfflineMode = usesOfflineMode, usesTlinkUnstuck = usesTlinkUnstuck, isWalking = isWalking, canSeeTarget = canSeeTarget, hasCompletlyLostTarget = hasCompletlyLostTarget, shouldFollowMainTarget = shouldFollowMainTarget, shouldFollowOnlyIfCanSee = shouldFollowOnlyIfCanSee, lookAtTarget = lookAtTarget, dialogueLookAt = dialogueLookAt, shouldFollowAIPath = shouldFollowAIPath, shouldWander = shouldWander, shouldUseNPCActionPoint = shouldUseNPCActionPoint, hasMainTarget = hasMainTarget, shouldFollowTargetVector = shouldFollowTargetVector, //aiCustomPathID = aiPath.ID, //actionPointID = actionPoint.ID, fleeAtDistanceValue = fleeAtDistanceValue, selectedSteeringBehaviour = (int)selectedSteeringBehaviour, arriveSteeringBehaviourDeceleration = (int)arriveSteeringBehaviourDeceleration, invertAIPath = invertAIPath, aiPathCurrentIndex = aiPathCurrentIndex, isFollowingAIPath = isFollowingAIPath, stoppingHalt = stoppingHalt, stoppingDistance = stoppingDistance, generalRotationSpeed = generalRotationSpeed, cachedMainTarget = cachedMainTarget, cachedMainTargetPos = cachedMainTargetPos, hasToRandomlySearchTarget = hasToRandomlySearchTarget, isReachingActionPoint = isReachingActionPoint, fixingInPlaceForActionPoint = fixingInPlaceForActionPoint, playingEnterAnimationActionPoint = playingEnterAnimationActionPoint, isUsingActionPoint = isUsingActionPoint, allowsLootWhenDead = allowsLootWhenDead, allowsLootOfEquipment = allowsLootOfEquipment, perceptionEnabled = perceptionEnabled, checkTick = checkTick, isDrawingWeapon = isDrawingWeapon, weaponDrawn = weaponDrawn, treeTickRate = (int)tickRate, xFrames = xFrames, xSeconds = xSeconds, useBT = useBT, pauseBT = pauseBT, curBehaviourTreeID = (currentBehaviour) ? currentBehaviour.ID : string.Empty, purposeBehaviourTreeID = (purposeBehaviourTree) ? purposeBehaviourTree.ID : string.Empty, combatBehaviourTreeID = (combatBehaviourTree) ? combatBehaviourTree.ID : string.Empty, keepTicking = keepTicking, startingCellID = startingCellID, saveCellID = (myCellInfo != null) ? myCellInfo.cell.ID : cellIDOfLastSaved, runtimeSaveCellID = runtimeStartingCell, hipsPosition = bodyData.hips.transform.position, hipsRotation = bodyData.hips.transform.rotation, isUsingPurposeBehaviour = isUsingPurposeBehaviour, cellIDMovingTo = IsReachingCellID, enteredInADoorLastTime = enteredInADoorLastTime, doorEnteredID = doorEnteredID, overrideShouldFollowMainTarget = overrideShouldFollowMainTarget, overrideShouldFollowMainTargetActive = overrideShouldFollowMainTargetActive, enteredInADoorLastTimeOverridePos = enteredInADoorLastTimeOverridePos, enteredDoorLastTimePos = enteredDoorLastTimePos, enteredDoorLastTimeRot = enteredDoorLastTimeRot, radius = radius, sphereYoffset = sphereYOffset, sphereZoffset = sphereForwardOffset, lookAtVector3IfStopped = lookAtVector3IfStopped, vector3ToLookAtIfStopped = vector3ToLookAtIfStopped, followTargetOutsideOfCell = followTargetOutsideOfCell }; // Save other data if (usesRagdoll) newData.ragdollSaveData = ragdoll.ToSaveData(); // Save factions for (int i = 0; i < belongsToFactions.Count; i++) newData.allFactions.Add(belongsToFactions[i].ID); newData.entityHesFighting = new List(); for (int i = 0; i < enemyTargets.Count; i++) newData.entityHesFighting.Add(enemyTargets[i].m_entity.entityID); // See if we can save Target if(mainTarget != null) { ITargetable targetable = mainTarget.GetComponent(); if(targetable != null) { newData.mainTargetID = targetable.GetID(); newData.mainTargetType = (int)targetable.GetTargetableType(); } else newData.mainTargetID = string.Empty; } else newData.mainTargetID = string.Empty; // Save PurposeState newData.purposeState = purposeState; return newData; } public void DestroyThis() { SaveOnFile(); myCellInfo.aiInWorld.Remove(entityID); Destroy(this.gameObject); } // Travel doors public bool teleportIfUnloadedWhileGoingToDoor = true; public bool teleportIfWalkableSurfaceEnds = true; public bool isReachingDoor; public Door doorIsReaching; public string IsReachingCellID; public bool enteredInADoorLastTime; public bool enteredInADoorLastTimeOverridePos; public Vector3 enteredDoorLastTimePos; public Quaternion enteredDoorLastTimeRot; public string doorEnteredID; public string aiwuhje; [ContextMenu("GO TO CELL")] public void ae() { // Assign Purpose MoveToCell(aiwuhje); SetNewBehaviourTree(false, "BTP_FollowMainTargetOnly"); SwitchBehaviourTree(false); purposeState.AssignPurpose(this, mainTarget.gameObject, PurposeClearTypes.ClearOnTeleportToCell, new PurposeStateClearsOnData("FarmHouseT"), "BTP_RoamRelax001"); } public override void Update() { base.Update(); if (followTargetOutsideOfCell) FollowTargetOutsideOfCell(); } public static Transform GetTransformToDoor(string _cellID) { bool doorFound = false; // Lookup the dictionary that contains "Cell -> DoorsToThatCell" var pref = PersistentReferences.PersistentReferenceManager.instance; if (pref.cellsDoorsDictionary.ContainsKey(_cellID)) { string doorID = pref.cellsDoorsDictionary[_cellID]; if (pref.cellsDoorsDictionary.ContainsKey(_cellID) && pref.refs.ContainsKey(doorID)) { return pref.refs[doorID].transform; } } else { // The AI has to move to a door (or a PRef) that will bring him there Door fDoor = null; // Foreach active cell foreach (KeyValuePair cellinfo in CellInformation.activeCells) { // For each Door in the current cell foreach (KeyValuePair door in cellinfo.Value.allDoors) { if (door.Value.teleports && door.Value.toCell != null && door.Value.toCell.ID == _cellID) { // We found the door in the active cells doorFound = true; fDoor = door.Value; return fDoor.transform; } } if (doorFound) break; } } if (!doorFound) // Door is not in active cells, so look for pref otherwise teleport { Debug.Log("Tried to GetTransformToDoor: " + _cellID + " | But couldn't be found because it's unloaded and there is no Persistent Reference to that door."); } return null; } public void MoveToCell(string _cellID, Vector3 onceEnteredPos, Quaternion onceEnteredRot) { enteredInADoorLastTimeOverridePos = true; enteredDoorLastTimePos = onceEnteredPos; enteredDoorLastTimeRot = onceEnteredRot; MoveToCell(_cellID); } /// /// Says to the AI to find a door and move to the desired cell after /// public void MoveToCell(string _cellID) { bool doorFound = false; // Lookup the dictionary that contains "Cell -> DoorsToThatCell" var pref = PersistentReferences.PersistentReferenceManager.instance; if (pref.cellsDoorsDictionary.ContainsKey(_cellID)) { string doorID = pref.cellsDoorsDictionary[_cellID]; if (pref.cellsDoorsDictionary.ContainsKey(_cellID) && pref.refs.ContainsKey(doorID)) { doorIsReaching = null; // Door is reaching is null because we're following a persistent reference transform // it will be assigned later, when the AI reaches the door PersistentReferences.PersistentReference TToDoor = pref.refs[doorID]; IsReachingCellID = _cellID; doorFound = true; toDoorTransform = TToDoor.transform; isReachingDoor = true; SetTarget(TToDoor.gameObject); StartCoroutine(MovingToCell(_cellID, doorID)); } } else { // The AI has to move to a door (or a PRef) that will bring him there Door fDoor = null; // Foreach active cell foreach (KeyValuePair cellinfo in CellInformation.activeCells) { // For each Door in the current cell foreach (KeyValuePair door in cellinfo.Value.allDoors) { if (door.Value.teleports && door.Value.toCell != null && door.Value.toCell.ID == _cellID) { // We found the door in the active cells doorFound = true; fDoor = door.Value; IsReachingCellID = fDoor.toCell.ID; doorEnteredID = fDoor.linkedDoorObjRef; break; } } if (doorFound) break; } // Door was in active cells if (doorFound && fDoor != null) { isReachingDoor = true; doorIsReaching = fDoor; toDoorTransform = doorIsReaching.groundPivot; SetTarget(doorIsReaching.groundPivot.gameObject); StartCoroutine(MovingToCell(_cellID)); } } if(!doorFound) // Door is not in active cells, so look for pref otherwise teleport { Debug.Log("The AI can't get to Cell: " + _cellID + " | Because it's unloaded and there is no Persistent Reference to that door."); } } public Transform toDoorTransform; IEnumerator MovingToCell(string _cellID, string doorID = null) { // Wait until the AI reaches the door, the Teleport to the other cell isReachingDoor = true; while (Vector3.Distance(transform.position, toDoorTransform.position) > stoppingDistance || m_isInCombat) { yield return null; } m_Anim.CrossFade("OpenDoor", 0.1f); yield return new WaitForSeconds(m_Anim.GetCurrentAnimatorClipInfo(0).Length); if (doorIsReaching == null && doorID != null) { if (myCellInfo.allDoors.ContainsKey(doorID)) { // Assign the door and the linkedDoorObjRef doorIsReaching = myCellInfo.allDoors[doorID]; doorEnteredID = doorIsReaching.linkedDoorObjRef; } } isReachingDoor = false; IsReachingCellID = string.Empty; enteredInADoorLastTime = true; SaveOnFile(); // We reached the door TeleportToCell(_cellID); yield return null; } public void TeleportToCell(string _cellID) { //// PURPOSE_CALLBACK: ClearOnTeleportToCell \\\\\ if (purposeState != null && purposeState.IsAssigned && purposeState.clearsOn == PurposeClearTypes.ClearOnTeleportToCell) { if (purposeState.clearsOnData.stringData == _cellID) { // We did it purposeState.CompletePurpose(); } } var aiData = SaveSystemManager.instance.saveFile.AIData; // Check if cellDictionary contains the cell we're putting this npc into if (aiData.aiCellDictionary.ContainsKey(_cellID)) { if (!aiData.aiCellDictionary[_cellID].allIDs.Contains(entityID)) aiData.aiCellDictionary[_cellID].allIDs.Add(entityID); } else { aiData.aiCellDictionary.Add(_cellID, new AIIDList()); aiData.aiCellDictionary[_cellID].allIDs.Add(entityID); } // Manually set the Cell and coor and manually save string oldSaveCell = myCellInfo.cell.ID; myCellInfo.aiInWorld.Remove(entityID); AISaveData thisAIData = ToSaveData(); thisAIData.isReachingDoor = false; thisAIData.cellIDMovingTo = string.Empty; thisAIData.saveCellID = _cellID; thisAIData.position = new Vector3(0, 0, 0); // If the AI has never been saved we can safely move it to the new cell if (!aiData.aiDictionary.ContainsKey(entityID)) aiData.aiDictionary.Add(entityID, thisAIData); else { aiData.aiDictionary[entityID] = thisAIData; // Remove it from his old aiCellDicitonary if (aiData.aiCellDictionary.ContainsKey(oldSaveCell)) aiData.aiCellDictionary[oldSaveCell].allIDs.Remove(entityID); } myCellInfo.aiInWorld.Remove(entityID); // If the cell is loaded (or cached, move this gameobject, otherwise destroy it, it will be instantiated next time the cell will be loaded) if (CellInformation.activeCells.ContainsKey(_cellID)) CellInformation.activeCells[_cellID].AddToAIToInstantiateIfCached(entityID); Destroy(this.gameObject); } public bool followTargetOutsideOfCell = false; /// /// This function detects if the AI was following its target and that target changed cell, if its enabled, this AI will be teleported where the /// public void FollowTargetOutsideOfCell() { // If its the player if(isAlive && shouldFollowMainTarget && mainTarget.CompareTag("Player") && myCellInfo != null && WorldManager.instance != null) { // Perform the check if(myCellInfo.cell != null && myCellInfo.cell.ID != WorldManager.instance.currentCenterCell.ID && this.isInOfflineMode) { myCellInfo.aiInWorld.Remove(this.entityID); // Teleport out transform.SetParent(CellInformation.activeCells[WorldManager.instance.currentCenterCell.ID].aiContainer); CellInformation.activeCells[WorldManager.instance.currentCenterCell.ID].aiInWorld.Add(entityID, this); myCellInfo = CellInformation.activeCells[WorldManager.instance.currentCenterCell.ID]; transform.position = (mainTarget.position + mainTarget.forward) + (transform.right * UnityEngine.Random.Range(-1,1)); onlineComponents.OnGoingOnline(); SaveOnFile(); } } } } }