public override void Update(Task caller) { base.Update(caller); // When an amount is specified if (amount > 0) { // Does player have enough gold? if (GameManager.Instance.PlayerEntity.GetGoldAmount() >= amount) { // Then deduct gold GameManager.Instance.PlayerEntity.DeductGoldAmount(amount); ParentQuest.StartTask(paidTaskSymbol); } else { // Otherwise trigger secondary task and exit ParentQuest.StartTask(notTaskSymbol); } } SetComplete(); }
/// <summary> /// Places NPC to home. This can happen automatically when player enters building or when /// quest script uses "create npc" action to automatically assign Person to home. /// </summary> public bool PlaceAtHome() { // Does not attempt to place a questor as they should be statically place or moved manually Place homePlace = ParentQuest.GetPlace(homePlaceSymbol); if (homePlace == null || isQuestor) { return(false); } // Create SiteLink if not already present if (!QuestMachine.HasSiteLink(ParentQuest, homePlaceSymbol)) { QuestMachine.CreateSiteLink(ParentQuest, homePlaceSymbol); } // Assign to home place homePlace.AssignQuestResource(Symbol); SetAssignedPlaceSymbol(homePlace.Symbol); assignedToHome = true; return(true); }
void CreatePendingFoeSpawn() { // Get the Foe resource Foe foe = ParentQuest.GetFoe(foeSymbol); if (foe == null) { throw new Exception(string.Format("create foe could not find Foe with symbol name {0}", Symbol.Name)); } // Get foe GameObjects pendingFoeGameObjects = foe.CreateFoeGameObjects(Vector3.zero); if (pendingFoeGameObjects == null || pendingFoeGameObjects.Length != foe.SpawnCount) { throw new Exception(string.Format("create foe attempted to create {0}x{1} GameObjects and failed.", foe.SpawnCount, Symbol.Name)); } // Initiate deployment process // Usually the foe will spawn immediately but can take longer depending on available placement space // This process ensures these foes have all been deployed before starting next cycle spawnInProgress = true; pendingFoesSpawned = 0; }
private void MessageBox_OnButtonClick(DaggerfallMessageBox sender, DaggerfallMessageBox.MessageBoxButtons messageBoxButton) { // Start selected task if (messageBoxButton == (DaggerfallMessageBox.MessageBoxButtons)opt1button) { ParentQuest.StartTask(opt1TaskSymbol); } else if (messageBoxButton == (DaggerfallMessageBox.MessageBoxButtons)opt2button) { ParentQuest.StartTask(opt2TaskSymbol); } else if (messageBoxButton == (DaggerfallMessageBox.MessageBoxButtons)opt3button) { ParentQuest.StartTask(opt3TaskSymbol); } else if (messageBoxButton == (DaggerfallMessageBox.MessageBoxButtons)opt4button) { ParentQuest.StartTask(opt4TaskSymbol); } // Close prompt sender.CloseWindow(); }
/// <summary> /// Checks if player in same world cell as Place this Person was assigned to. /// Does not care about specific building/dungeon or interior/exterior, just matching location mapID. /// Does not care if player actually inside bounds, just if inside same world cell. /// </summary> /// <returns>True if player in same world cell as location.</returns> public bool IsPlayerInSameLocationWorldCell() { // Get Place resource Place place = ParentQuest.GetPlace(lastAssignedPlaceSymbol); if (place == null) { return(false); } // Compare mapID of player location and Place DFLocation location = GameManager.Instance.PlayerGPS.CurrentLocation; if (location.Loaded) { if (location.MapTableData.MapId == place.SiteDetails.mapId) { return(true); } } return(false); }
public override void Update(Task caller) { base.Update(caller); // Get related Person resource Person person = ParentQuest.GetPerson(personSymbol); if (person == null) { return; } // Add face to HUD DaggerfallUI.Instance.DaggerfallHUD.EscortingFaces.AddFace(person); // Popup saying message if (sayingID != 0) { ParentQuest.ShowMessagePopup(sayingID); } SetComplete(); }
public override void Tick(Quest caller) { base.Tick(caller); // Auto-assign NPC to home Place if available and player enters // This only happens for very specific NPC types // Equivalent to calling "place anNPC at aPlace" from script // Will not be called again as assignment is permanent for duration of quest if (homePlaceSymbol != null && !assignedToHome) { Place home = ParentQuest.GetPlace(homePlaceSymbol); if (home == null) { return; } // Hot-place NPC at this location when player enters if (home.IsPlayerHere()) { PlaceAtHome(); } } }
void SpawnFoe() { // Roll for spawn chance float chance = spawnChance / 100f; if (UnityEngine.Random.Range(0f, 1f) < chance) { return; } // Get the Foe resource Foe foe = ParentQuest.GetFoe(foeSymbol); if (foe == null) { throw new Exception(string.Format("create foe could not find Foe with symbol name {0}", Symbol.Name)); } // Get game objects GameObject[] gameObjects = foe.CreateFoeGameObjects(Vector3.zero); if (gameObjects == null || gameObjects.Length != foe.SpawnCount) { throw new Exception(string.Format("create foe attempted to spawn {0}x{1} and failed.", foe.SpawnCount, Symbol.Name)); } // For simple initial testing just place enemies 2m behind player // Will eventually need to spawn enemy within a certain radius of player // and ensure not dropped into the void for dungeons and building interiors GameObject player = GameManager.Instance.PlayerObject; Vector3 position = player.transform.position + (-player.transform.forward * 2); for (int i = 0; i < gameObjects.Length; i++) { gameObjects[i].transform.position = position; gameObjects[i].SetActive(true); } }
public override void Update(Task caller) { base.Update(caller); // Drop related Person or Foe resource if (personSymbol != null && !string.IsNullOrEmpty(personSymbol.Name)) { Person person = ParentQuest.GetPerson(personSymbol); if (person != null) { DaggerfallUI.Instance.DaggerfallHUD.EscortingFaces.DropFace(person); } } else if (foeSymbol != null && !string.IsNullOrEmpty(foeSymbol.Name)) { Foe foe = ParentQuest.GetFoe(foeSymbol); if (foe != null) { DaggerfallUI.Instance.DaggerfallHUD.EscortingFaces.DropFace(foe); } } SetComplete(); }
/// <summary> /// Places NPC to home. This can happen automatically when player enters building or when /// quest script uses "create npc" action to automatically assign Person to home. /// </summary> public bool PlaceAtHome() { // Does not attempt to place a questor as they should be statically placed or moved manually // Individual NPCs are also excluded as they are either automatically at home or moved elsewhere by quest Place homePlace = ParentQuest.GetPlace(homePlaceSymbol); if (homePlace == null || isQuestor || isIndividualNPC) { return(false); } // Create SiteLink if not already present if (!QuestMachine.HasSiteLink(ParentQuest, homePlaceSymbol)) { QuestMachine.CreateSiteLink(ParentQuest, homePlaceSymbol); } // Assign to home place homePlace.AssignQuestResource(Symbol); SetAssignedPlaceSymbol(homePlace.Symbol); assignedToHome = true; return(true); }
public override bool CheckTrigger(Task caller) { // Get related Foe resource Foe foe = ParentQuest.GetFoe(foeSymbol); if (foe == null) { return(false); } // Check total kills recorded on this Foe if (foe.KillCount >= killsRequired) { // Popup saying message if (sayingID != 0) { ParentQuest.ShowMessagePopup(sayingID); } return(true); } return(false); }
public override bool CheckTrigger(Task caller) { // Get related Foe resource Foe foe = ParentQuest.GetFoe(foeSymbol); if (foe == null) { return(false); } // Check injured flag if (foe.InjuredTrigger) { // Optionally show message if (textID != 0) { ParentQuest.ShowMessagePopup(textID); } return(true); } return(false); }
void AssignHomeTown(string scopeString) { const string houseString = "house"; Place homePlace; string symbolName = string.Format("_{0}_home_", Symbol.Name); // If this is a Questor or individual NPC then use current location of player using a special helper if (isQuestor || (IsIndividualNPC && isIndividualAtHome)) { homePlace = new Place(ParentQuest); if (GameManager.Instance.PlayerGPS.HasCurrentLocation) { if (!homePlace.ConfigureFromPlayerLocation(symbolName)) { throw new Exception("AssignHomeTown() could not configure questor/individual home from current player location."); } homePlaceSymbol = homePlace.Symbol; ParentQuest.AddResource(homePlace); LogHomePlace(homePlace); return; } } // If this is an individual NPC who is not at home, don't place for now // as this has to be done by the "place _person_ at" command if (IsIndividualNPC) { return; } // For other NPCs use the given scope if any if (string.IsNullOrEmpty(scopeString)) { // Else generate it at random only if there are local buildings if (GameManager.Instance.PlayerGPS.HasCurrentLocation && GameManager.Instance.PlayerGPS.CurrentLocation.Exterior.BuildingCount > 0) { scopeString = UnityEngine.Random.Range(0.0f, 1.0f) < 0.5f ? "local" : "remote"; } else { scopeString = "remote"; } } // Adjust building type based on faction hints string buildingTypeString = houseString; int p1 = 0, p2 = 0, p3 = 0; if (!string.IsNullOrEmpty(factionTableKey)) { // Get faction parameters p1 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p1", factionTableKey)); p2 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p2", factionTableKey)); p3 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p3", factionTableKey)); if (p1 == 0 && p2 >= 0 && p2 <= 20 && p3 == 0) { // Set to a specific building type buildingTypeString = QuestMachine.Instance.PlacesTable.GetKeyForValue("p2", p2.ToString()); } } // Create the home location - this will try to match NPC group (e.g. a Noble will select a Palace) try { // Try preferred location type string source = string.Format("Place {0} {1} {2}", symbolName, scopeString, buildingTypeString); homePlace = new Place(ParentQuest, source); } catch { // Otherwise try to use a generic house // If this doesn't work for some reason then next exception will prevent quest from starting string source = string.Format("Place {0} {1} {2}", symbolName, scopeString, houseString); homePlace = new Place(ParentQuest, source); } // Complete assigning home place homePlaceSymbol = homePlace.Symbol.Clone(); ParentQuest.AddResource(homePlace); LogHomePlace(homePlace); }
public List <TextFile.Token[]> GetMessage(int messageId) { Message message = ParentQuest.GetMessage(messageId); return(message == null ? null : TokenizeMessage(message)); }
void AssignHomeTown() { Place homePlace; string symbolName = string.Format("_{0}_home_", Symbol.Name); // If this is a Questor or individual NPC then use current location of player using a special helper if (isQuestor || (IsIndividualNPC && isIndividualAtHome)) { homePlace = new Place(ParentQuest); if (GameManager.Instance.PlayerGPS.HasCurrentLocation) { if (!homePlace.ConfigureFromPlayerLocation(symbolName)) { throw new Exception("AssignHomeTown() could not configure questor/individual home from current player location."); } homePlaceSymbol = homePlace.Symbol; ParentQuest.AddResource(homePlace); LogHomePlace(homePlace); return; } } // For other NPCs use default scope and building type Place.Scopes scope = Place.Scopes.Remote; string buildingTypeString = "house2"; // Adjust scope and building type based on faction hints int p1 = 0, p2 = 0, p3 = 0; if (!string.IsNullOrEmpty(factionTableKey)) { // Get faction parameters p1 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p1", factionTableKey)); p2 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p2", factionTableKey)); p3 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p3", factionTableKey)); // Set based on parameters if (p1 == 0 && p2 == -3 || p1 == 0 && p2 == -4) { // For local types set to local place // This will support Local_3.0 - Local_4.10k // Referencing quest Sx009 where player must locate and click an NPC with only a home location to go by scope = Place.Scopes.Local; } else if (p1 == 0 && p2 >= 0 && p2 <= 20 && p3 == 0) { // Set to a specific building type buildingTypeString = QuestMachine.Instance.PlacesTable.GetKeyForValue("p2", p2.ToString()); } } // Get scope string - must be "local" or "remote" string scopeString = string.Empty; if (scope == Place.Scopes.Local) { scopeString = "local"; } else if (scope == Place.Scopes.Remote) { scopeString = "remote"; } else { throw new Exception("AssignHomeTown() scope must be either 'local' or 'remote'."); } // Create the home location string source = string.Format("Place {0} {1} {2}", symbolName, scopeString, buildingTypeString); homePlace = new Place(ParentQuest, source); homePlaceSymbol = homePlace.Symbol.Clone(); ParentQuest.AddResource(homePlace); LogHomePlace(homePlace); // // NOTE: Keeping the below for reference only at this time // //const string blank = "BLANK"; //// If this is a Questor or individual NPC then use current location name //// Person is being instantiated where player currently is //if (isQuestor || (IsIndividualNPC && isIndividualAtHome)) //{ // if (GameManager.Instance.PlayerGPS.HasCurrentLocation) // { // homeTownName = GameManager.Instance.PlayerGPS.CurrentLocation.Name; // homeRegionName = GameManager.Instance.PlayerGPS.CurrentLocation.RegionName; // homeBuildingName = blank; // return; // } //} //// Handle specific home Place assigned at create time //if (homePlaceSymbol != null) //{ // Place home = ParentQuest.GetPlace(homePlaceSymbol); // if (home != null) // { // homeTownName = home.SiteDetails.locationName; // homeRegionName = home.SiteDetails.regionName; // homeBuildingName = home.SiteDetails.buildingName; // } //} //else //{ // // Find a random location name from town types for flavour text // // This might take a few attempts but will very quickly find a random town name // int index; // bool found = false; // int regionIndex = GameManager.Instance.PlayerGPS.CurrentRegionIndex; // DFRegion regionData = DaggerfallUnity.Instance.ContentReader.MapFileReader.GetRegion(regionIndex); // while (!found) // { // index = UnityEngine.Random.Range(0, regionData.MapTable.Length); // DFRegion.LocationTypes locationType = regionData.MapTable[index].LocationType; // if (locationType == DFRegion.LocationTypes.TownCity || // locationType == DFRegion.LocationTypes.TownHamlet || // locationType == DFRegion.LocationTypes.TownVillage) // { // homeTownName = regionData.MapNames[index]; // homeRegionName = regionData.Name; // homeBuildingName = blank; // found = true; // } // } //} //// Handle Local_3.x group NPCs (limited) //// These appear to be a special case of assigning a residential person who is automatically instantiated to home Place //// Creating a full target Place for this person automatically and storing in Quest //// NOTE: Understanding is still being developed here, likely will need to rework this later //if (QuestMachine.Instance.FactionsTable.HasValue(careerAllianceName)) //{ // // Get params for this case // int p1 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p1", careerAllianceName)); // int p2 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p2", careerAllianceName)); // //int p3 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p3", careerAllianceName)); // // Only supporting specific cases for now - can expand later based on testing and iteration of support // // This will support Local_3.0 - Local_3.3 // // Referencing quest Sx009 here where player must locate and click an NPC with only a home location to go by // if (p1 == 0 && p2 == -3) // { // // Just using "house2" here as actual meaning of p3 unknown // string homeSymbol = string.Format("_{0}_home_", Symbol.Name); // string source = string.Format("Place {0} remote house2", homeSymbol); // Place home = new Place(ParentQuest, source); // homePlaceSymbol = home.Symbol.Clone(); // ParentQuest.AddResource(home); // } // else if (p1 == 0 && p2 >= 0) // { // // Handle standard building types // string buildingSymbol = string.Format("_{0}_building_", Symbol.Name); // string buildingType = QuestMachine.Instance.PlacesTable.GetKeyForValue("p2", p2.ToString()); // string source = string.Format("Place {0} remote {1}", buildingSymbol, buildingType); // Place building = new Place(ParentQuest, source); // homePlaceSymbol = building.Symbol.Clone(); // ParentQuest.AddResource(building); // } //} }
public void Update() { // Iterate conditions and actions for this task foreach (IQuestAction action in actions) { // Completed actions are never executed again // The action itself should decide if/when to be complete // At a higher level, turning off the task will also disable actions // The exception being triggers which always run even when task disabled if (action.IsComplete) { continue; } // Always check trigger conditions // These can turn task on when any trigger evaluates true // They are no longer checked once task is triggered (unless set to always be on) // But can fire again if owning task is unset/rearmed later if (action.IsTriggerCondition && !IsTriggered || action.IsAlwaysOnTriggerCondition) { if (action.CheckTrigger(this)) { IsTriggered = true; } else { IsTriggered = false; } } // Tick other actions only when active if (IsTriggered && !action.IsTriggerCondition) { // Initialise action if task was previously untriggered if (!prevTriggered) { action.InitialiseOnSet(); } // Update action and handle quest break action.Update(this); if (ParentQuest.QuestBreak) { return; } } } // If this is a PersistUntil task we need to check target condition for unset state. // Experimentation seems to indicate these monitoring tasks get at least one tick. // For example, starting player outside of PH in classic using Z.CFG cheat will // cause main quest to start as normal before _exitstarter_ flag has a chance to // terminate "until _exitstarter_ performed" task. // Performing termination check AFTER executing task at least once to ensure behaviour matches classic. if (type == TaskType.PersistUntil) { Task targetTask = ParentQuest.GetTask(targetSymbol); if (targetTask.IsTriggered) { // Target now set, terminate persistent task Clear(); } else { // Rearm actions so they can repeat next run // This behaviour obvserved in S0000011 in "until _S.12_ performed" to continuously clear Barenziah click RearmActions(); } } // Store trigger state this update prevTriggered = IsTriggered; }
void DirectToPlayerWithNotify(Item item) { // Give player item and show notify message GameManager.Instance.PlayerEntity.Items.AddItem(item.DaggerfallUnityItem, Items.ItemCollection.AddPosition.Front); ParentQuest.ShowMessagePopup(textId); }
public override void Update(Task caller) { ParentQuest.AddLogStep(stepID, messageID); SetComplete(); }
/// <summary> /// Assigns a quest resource to this Place site. /// Supports Persons, Foes, Items from within same quest as Place. /// Quest must have previously created SiteLink for layout builders to discover assigned resources. /// </summary> /// <param name="targetSymbol">Resource symbol of Person, Item, or Foe to assign.</param> public void AssignQuestResource(Symbol targetSymbol) { // Site must have at least one marker of each type if (!ValidateQuestMarkers(siteDetails.questSpawnMarkers, siteDetails.questItemMarkers)) { throw new Exception(string.Format("Tried to assign resource {0} to Place without at least 1x spawn and 1x item marker.", targetSymbol.Name)); } // Attempt to get resource from symbol QuestResource resource = ParentQuest.GetResource(targetSymbol); if (resource == null) { throw new Exception(string.Format("Could not locate quest resource with symbol {0}", targetSymbol.Name)); } // Must be a supported resource type MarkerTypes requiredMarkerType = MarkerTypes.None; if (resource is Person || resource is Foe) { requiredMarkerType = MarkerTypes.QuestSpawn; } else if (resource is Item) { requiredMarkerType = MarkerTypes.QuestItem; } else { throw new Exception(string.Format("Tried to assign incompatible resource symbol {0} to Place", targetSymbol.Name)); } // Assign target resource to marker selected for this quest if (requiredMarkerType == MarkerTypes.QuestSpawn) { AssignResourceToMarker(targetSymbol, ref siteDetails.questSpawnMarkers[siteDetails.selectedQuestSpawnMarker]); } else if (requiredMarkerType == MarkerTypes.QuestItem) { AssignResourceToMarker(targetSymbol, ref siteDetails.questItemMarkers[siteDetails.selectedQuestItemMarker]); } else { throw new Exception(string.Format("Tried to assign resource symbol _{0}_ to Place {1} but it has an unknown MarkerType {2}", targetSymbol.Name, Symbol.Name, requiredMarkerType.ToString())); } // Output debug information if (resource is Person) { if (siteDetails.siteType == SiteTypes.Building) { if (requiredMarkerType == MarkerTypes.QuestSpawn) { Debug.LogFormat("Assigned Person {0} to Building {1}", (resource as Person).DisplayName, SiteDetails.buildingName); } } else if (siteDetails.siteType == SiteTypes.Dungeon) { if (requiredMarkerType == MarkerTypes.QuestSpawn) { Debug.LogFormat("Assigned Person {0} to Dungeon {1}", (resource as Person).DisplayName, SiteDetails.locationName); } } } else if (resource is Foe) { if (siteDetails.siteType == SiteTypes.Building) { if (requiredMarkerType == MarkerTypes.QuestSpawn) { Debug.LogFormat("Assigned Foe _{0}_ to Building {1}", resource.Symbol.Name, SiteDetails.buildingName); } } else if (siteDetails.siteType == SiteTypes.Dungeon) { if (requiredMarkerType == MarkerTypes.QuestSpawn) { Debug.LogFormat("Assigned Foe _{0}_ to Dungeon {1}", resource.Symbol.Name, SiteDetails.locationName); } } } else if (resource is Item) { if (siteDetails.siteType == SiteTypes.Building) { if (requiredMarkerType == MarkerTypes.QuestItem) { Debug.LogFormat("Assigned Item _{0}_ to Building {1}", resource.Symbol.Name, SiteDetails.buildingName); } } else if (siteDetails.siteType == SiteTypes.Dungeon) { if (requiredMarkerType == MarkerTypes.QuestItem) { Debug.LogFormat("Assigned Item _{0}_ to Dungeon {1}", resource.Symbol.Name, SiteDetails.locationName); } } } }
public void Update() { // "Always on" triggers can both start and stop a task // An example is S0000977 _S.03_ task which uses a single "when" trigger to start/stop spawns and play vengeance sound in Daggerfall city // But this becomes an issue if task has multiple "always on" triggers, as trigger A might start the task then trigger B will stop it again // One example of this behaviour is W0C00Y00 _pcgetsgold_ task where either "when" trigger can start reward task but subsequent triggers should not stop it again // This implementation considers the first "always on" trigger as the "primary" - able to both start & stop parent task // Subsequent "always on" triggers within the same task are "secondary" - only able to start a parent task but not stop it again bool ranPrimaryAlwaysOnTrigger = false; // Iterate conditions and actions for this task foreach (IQuestAction action in actions) { // Completed actions are never executed again // The action itself should decide if/when to be complete // At a higher level, turning off the task will also disable actions // The exception being triggers which always run even when task disabled if (action.IsComplete) { continue; } // Always check trigger conditions // These can turn task on when any trigger evaluates true // They are no longer checked once task is triggered (unless set to always be on) // But can fire again if owning task is unset/rearmed later if (action.IsTriggerCondition && !IsTriggered || action.IsAlwaysOnTriggerCondition) { // Handle primary/secondary "always on" triggers if (action.IsAlwaysOnTriggerCondition && !ranPrimaryAlwaysOnTrigger) { // Primary "always on" trigger can start/stop parent task // Flag is raised at first "always on" trigger so that subsequent triggers know the primary has run IsTriggered = action.CheckTrigger(this); ranPrimaryAlwaysOnTrigger = true; } else if (action.IsAlwaysOnTriggerCondition && ranPrimaryAlwaysOnTrigger) { // Secondary "always on" triggers can only start parent task, they cannot stop it again if (action.CheckTrigger(this)) { IsTriggered = true; } } else { // All other triggers IsTriggered = action.CheckTrigger(this); } } // Tick other actions only when active if (IsTriggered && !action.IsTriggerCondition) { // Initialise action if task was previously untriggered if (!prevTriggered) { action.InitialiseOnSet(); } // Update action and handle quest break action.Update(this); if (ParentQuest.QuestBreak) { return; } } } // If this is a PersistUntil task we need to check target condition for unset state. // Experimentation seems to indicate these monitoring tasks get at least one tick. // For example, starting player outside of PH in classic using Z.CFG cheat will // cause main quest to start as normal before _exitstarter_ flag has a chance to // terminate "until _exitstarter_ performed" task. // Performing termination check AFTER executing task at least once to ensure behaviour matches classic. if (type == TaskType.PersistUntil) { Task targetTask = ParentQuest.GetTask(targetSymbol); if (targetTask.IsTriggered) { // Target now set, terminate persistent task Clear(); } else { // Rearm actions so they can repeat next run // This behaviour obvserved in S0000011 in "until _S.12_ performed" to continuously clear Barenziah click RearmActions(); } } // Store trigger state this update prevTriggered = IsTriggered; }
public override void Update(Task caller) { base.Update(caller); // Attempt to get Item resource Item item = ParentQuest.GetItem(itemSymbol); if (item == null) { SetComplete(); throw new Exception(string.Format("Could not find Item resource symbol {0}", itemSymbol)); } // Attempt to get target resource QuestResource target = ParentQuest.GetResource(targetSymbol); if (target == null) { SetComplete(); throw new Exception(string.Format("Could not find target resource symbol {0}", targetSymbol)); } // Add item to Foe resource or generic entity // In practice GiveItem will always be to a Foe resource - NPCs are handled by "get item" // Keeping generic GiveItem behaviour as entity catch-all and reference if (target is Foe) { // Add to Foe item queue (target as Foe).QueueItem(item.DaggerfallUnityItem); // Dequeue items on entity immediately if target already exists in the world // Will also handle placing items to dead enemy loot container if (target.QuestResourceBehaviour) { DaggerfallEntityBehaviour entityBehaviour = target.QuestResourceBehaviour.GetComponent <DaggerfallEntityBehaviour>(); if (entityBehaviour) { target.QuestResourceBehaviour.AddItemQueue(target as Foe, entityBehaviour); } } } else { // Target must exist in the world if (!target.QuestResourceBehaviour) { return; } // Must have an Entity to receive item DaggerfallEntityBehaviour entityBehaviour = target.QuestResourceBehaviour.GetComponent <DaggerfallEntityBehaviour>(); if (!entityBehaviour) { SetComplete(); throw new Exception(string.Format("GiveItem target {0} is not an Entity with DaggerfallEntityBehaviour", targetSymbol)); } // Assign item for player to find // * Some quests assign item to Foe at create time, others on injured event // * It's possible for target enemy to be one-shot or to be killed by other means (such as "killall") // * This assignment will direct quest loot item either to live enemy or corpse loot container if (entityBehaviour.CorpseLootContainer) { // If enemy is already dead then place item in corpse loot container entityBehaviour.CorpseLootContainer.Items.AddItem(item.DaggerfallUnityItem); } else { // Otherwise add quest Item to Entity item collection // It will be transferred to corpse marker loot container when dropped entityBehaviour.Entity.Items.AddItem(item.DaggerfallUnityItem); } } // Remove item from player inventory if they are holding it if (GameManager.Instance.PlayerEntity.Items.Contains(item.DaggerfallUnityItem)) { GameManager.Instance.PlayerEntity.Items.RemoveItem(item.DaggerfallUnityItem); } SetComplete(); }
// Uses raycasts to find next spawn position just outside of player's field of view void PlaceFoeFreely(GameObject[] gameObjects, Transform parent, float minDistance = 5f, float maxDistance = 20f) { const float overlapSphereRadius = 0.65f; const float separationDistance = 1.25f; const float maxFloorDistance = 4f; // Must have received a valid array if (gameObjects == null || gameObjects.Length == 0) { return; } // Set parent - otherwise caller must set a parent if (parent) { gameObjects[pendingFoesSpawned].transform.parent = parent; } // Select a left or right direction outside of camera FOV Quaternion rotation; float directionAngle = GameManager.Instance.MainCamera.fieldOfView; directionAngle += UnityEngine.Random.Range(0f, 4f); if (UnityEngine.Random.Range(0f, 1f) > 0.5f) { rotation = Quaternion.Euler(0, -directionAngle, 0); } else { rotation = Quaternion.Euler(0, directionAngle, 0); } // Get direction vector and create a new ray Vector3 angle = (rotation * Vector3.forward).normalized; Vector3 spawnDirection = GameManager.Instance.PlayerObject.transform.TransformDirection(angle).normalized; Ray ray = new Ray(GameManager.Instance.PlayerObject.transform.position, spawnDirection); // Check for a hit Vector3 currentPoint; RaycastHit initialHit; if (Physics.Raycast(ray, out initialHit, maxDistance)) { // Separate out from hit point float extraDistance = UnityEngine.Random.Range(0f, 2f); currentPoint = initialHit.point + initialHit.normal.normalized * (separationDistance + extraDistance); // Must be greater than minDistance if (initialHit.distance < minDistance) { return; } } else { // Player might be in an open area (e.g. outdoors) pick a random point along spawn direction currentPoint = GameManager.Instance.PlayerObject.transform.position + spawnDirection * UnityEngine.Random.Range(minDistance, maxDistance); } // Must be able to find a surface below RaycastHit floorHit; ray = new Ray(currentPoint, Vector3.down); if (!Physics.Raycast(ray, out floorHit, maxFloorDistance)) { return; } // Ensure this is open space Vector3 testPoint = floorHit.point + Vector3.up * separationDistance; Collider[] colliders = Physics.OverlapSphere(testPoint, overlapSphereRadius); if (colliders.Length > 0) { return; } // This looks like a good spawn position pendingFoeGameObjects[pendingFoesSpawned].transform.position = testPoint; FinalizeFoe(pendingFoeGameObjects[pendingFoesSpawned]); gameObjects[pendingFoesSpawned].transform.LookAt(GameManager.Instance.PlayerObject.transform.position); // Send msg message on first spawn only if (msgMessageID != -1) { ParentQuest.ShowMessagePopup(msgMessageID); msgMessageID = -1; } // Increment count pendingFoesSpawned++; }
public override void Update(Task caller) { ParentQuest.ShowMessagePopup(id); SetComplete(); }
public void Update() { // Iterate conditions and actions for this task foreach (IQuestAction action in actions) { // Completed actions are never executed again // The action itself should decide if/when to be complete // At a higher level, turning off the task will also disable actions // The exception being triggers which always run even when task disabled if (action.IsComplete) { continue; } // Always check trigger conditions // These can turn task on when any trigger evaluates true // They are no longer checked once task is triggered // But can fire again if task is unset/rearmed later if (action.IsTriggerCondition && !triggered) { if (action.CheckTrigger(this)) { triggered = true; } else { triggered = false; } } // Tick other actions only when active if (triggered && !action.IsTriggerCondition) { // Initialise action if task was previously untriggered if (!prevTriggered) { action.InitialiseOnSet(); } // Update action and handle quest break action.Update(this); if (ParentQuest.QuestBreak) { return; } } } // If this is a PersistUntil task we need to check target condition for unset state. // Experimentation seems to indicate these monitoring tasks get at least one tick. // For example, starting player outside of PH in classic using Z.CFG cheat will // cause main quest to start as normal before _exitstarter_ flag has a chance to // terminate "until _exitstarter_ performed" task. // Performing termination check AFTER executing task at least once to ensure behaviour matches classic. if (type == TaskType.PersistUntil) { // Unset this task when target symbol is also unset Task targetTask = ParentQuest.GetTask(targetSymbol); if (targetTask != null) { if (!targetTask.IsSet) { Unset(); } // Should these tasks be able to rearm? // Would need strong evidence before allowing this } } // Store trigger state this update prevTriggered = triggered; }
public override void Update(Task caller) { ulong gameSeconds = DaggerfallUnity.Instance.WorldTime.DaggerfallDateTime.ToSeconds(); // Init spawn timer on first update if (lastSpawnTime == 0) { lastSpawnTime = gameSeconds + (uint)UnityEngine.Random.Range(0, spawnInterval + 1); } // Do nothing if max foes already spawned // This can be cleared on next set/rearm if (spawnCounter >= spawnMaxTimes && spawnMaxTimes != -1) { return; } // Clear pending foes if all have been spawned if (spawnInProgress && pendingFoesSpawned >= pendingFoeGameObjects.Length) { spawnInProgress = false; spawnCounter++; return; } // Check for a new spawn event - only one spawn event can be running at a time if (gameSeconds > lastSpawnTime + spawnInterval && !spawnInProgress) { // Update last spawn time lastSpawnTime = gameSeconds; // Roll for spawn chance float chance = spawnChance / 100f; if (UnityEngine.Random.Range(0f, 1f) > chance) { return; } // Get the Foe resource Foe foe = ParentQuest.GetFoe(foeSymbol); if (foe == null) { SetComplete(); throw new Exception(string.Format("create foe could not find Foe with symbol name {0}", Symbol.Name)); } // Do not spawn if foe is hidden if (foe.IsHidden) { return; } // Start deploying GameObjects CreatePendingFoeSpawn(foe); } // Try to deploy a pending spawns if (spawnInProgress) { TryPlacement(); GameManager.Instance.RaiseOnEncounterEvent(); } }
void AssignHomeTown() { const string houseString = "house"; Place homePlace; string symbolName = string.Format("_{0}_home_", Symbol.Name); // If this is a Questor or individual NPC then use current location of player using a special helper if (isQuestor || (IsIndividualNPC && isIndividualAtHome)) { homePlace = new Place(ParentQuest); if (GameManager.Instance.PlayerGPS.HasCurrentLocation) { if (!homePlace.ConfigureFromPlayerLocation(symbolName)) { throw new Exception("AssignHomeTown() could not configure questor/individual home from current player location."); } homePlaceSymbol = homePlace.Symbol; ParentQuest.AddResource(homePlace); LogHomePlace(homePlace); return; } } // For other NPCs use default scope and building type Place.Scopes scope = Place.Scopes.Remote; string buildingTypeString = houseString; // Adjust scope and building type based on faction hints int p1 = 0, p2 = 0, p3 = 0; if (!string.IsNullOrEmpty(factionTableKey)) { // Get faction parameters p1 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p1", factionTableKey)); p2 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p2", factionTableKey)); p3 = Parser.ParseInt(QuestMachine.Instance.FactionsTable.GetValue("p3", factionTableKey)); // Set based on parameters if (p1 == 0 && p2 < -2 && p2 != -6) { // From usage in the quests it appears -3 and lower are local. // Referencing quest Sx009 where player must locate and click an NPC with only a home location to go by // and K0C00Y04 where two Group_7 npcs are local. // Interkarma Note: -6 is used by Thieves Guild introduction quest O0A0AL00 and should be a remote NPC. Treating -6 as remote. scope = Place.Scopes.Local; } else if (p1 == 0 && p2 >= 0 && p2 <= 20 && p3 == 0) { // Set to a specific building type buildingTypeString = QuestMachine.Instance.PlacesTable.GetKeyForValue("p2", p2.ToString()); } } // Get scope string - must be "local" or "remote" string scopeString = string.Empty; if (scope == Place.Scopes.Local) { scopeString = "local"; } else if (scope == Place.Scopes.Remote) { scopeString = "remote"; } else { throw new Exception("AssignHomeTown() scope must be either 'local' or 'remote'."); } // Create the home location - this will try to match NPC group (e.g. a Noble will select a Palace) try { // Try preferred location type string source = string.Format("Place {0} {1} {2}", symbolName, scopeString, buildingTypeString); homePlace = new Place(ParentQuest, source); } catch { // Otherwise try to use a generic house // If this doesn't work for some reason then next exception will prevent quest from starting string source = string.Format("Place {0} {1} {2}", symbolName, scopeString, houseString); homePlace = new Place(ParentQuest, source); } // Complete assigning home place homePlaceSymbol = homePlace.Symbol.Clone(); ParentQuest.AddResource(homePlace); LogHomePlace(homePlace); }
public override void Update(Task caller) { const int minHour = 7; const int maxHour = 18; const int minDelay = 40; const int maxDelay = 500; base.Update(caller); // "give pc anItem notify 1234" and "give pc anItem silently" require player // to be within a town, outdoors, and during set hours for action to fire if ((textId != 0 || silently) && !offerImmediately) { // Fail if conditions not met, but take note we are waiting DaggerfallDateTime now = DaggerfallUnity.Instance.WorldTime.Now; if (!GameManager.Instance.PlayerGPS.IsPlayerInTown(true, true) || now.Hour < minHour || now.Hour > maxHour) { waitingForTown = true; ticksUntilFire = 0; return; } // If we were waiting then add a small random delay so messages don't all arrive at once if (waitingForTown) { ticksUntilFire = UnityEngine.Random.Range(minDelay, maxDelay + 1); waitingForTown = false; RaiseOnOfferPendingEvent(this); } } // Reduce random delay until nothing is left // This is in questmachine ticks which is currently 10 ticks per second while game is running // Does not tick while game paused or resting - player must be actively in town walking around if (ticksUntilFire > 0) { ticksUntilFire--; return; } // Handle giving player nothing // This also shows QuestComplete message but does not give player loot if (isNothing) { OfferToPlayerWithQuestComplete(null); SetComplete(); return; } // Attempt to get Item resource Item item = ParentQuest.GetItem(itemSymbol); if (item == null) { Debug.LogErrorFormat("Could not find Item resource symbol {0}", itemSymbol); return; } // Give quest item to player based on command format if (textId != 0) { DirectToPlayerWithNotify(item); } else { if (silently) { DirectToPlayerWithoutNotify(item); } else { OfferToPlayerWithQuestComplete(item); } } offerImmediately = false; SetComplete(); }
public override void Update(Task caller) { ParentQuest.SetTask(taskSymbol); SetComplete(); }
public override void Update(Task caller) { base.Update(caller); // Do nothing while player respawning if (GameManager.Instance.PlayerEnterExit.IsRespawning) { return; } // Handle resume on next tick of action after respawn process complete if (resumePending) { GameObject player = GameManager.Instance.PlayerObject; player.transform.position = resumePosition; resumePending = false; SetComplete(); return; } // Create SiteLink if not already present if (!QuestMachine.HasSiteLink(ParentQuest, targetPlace)) { QuestMachine.CreateSiteLink(ParentQuest, targetPlace); } // Attempt to get Place resource Place place = ParentQuest.GetPlace(targetPlace); if (place == null) { return; } // Get selected spawn QuestMarker for this Place bool usingMarker = false; QuestMarker marker = new QuestMarker(); if (targetMarker >= 0 && targetMarker < place.SiteDetails.questSpawnMarkers.Length) { marker = place.SiteDetails.questSpawnMarkers[targetMarker]; usingMarker = true; } // Attempt to get location data - using GetLocation(regionName, locationName) as it can support all locations DFLocation location; if (!DaggerfallUnity.Instance.ContentReader.GetLocation(place.SiteDetails.regionName, place.SiteDetails.locationName, out location)) { return; } // Spawn inside dungeon at this world position DFPosition mapPixel = MapsFile.LongitudeLatitudeToMapPixel((int)location.MapTableData.Longitude, location.MapTableData.Latitude); DFPosition worldPos = MapsFile.MapPixelToWorldCoord(mapPixel.X, mapPixel.Y); GameManager.Instance.PlayerEnterExit.RespawnPlayer( worldPos.X, worldPos.Y, true, true); // Determine start position if (usingMarker) { // Use specified quest marker Vector3 dungeonBlockPosition = new Vector3(marker.dungeonX * RDBLayout.RDBSide, 0, marker.dungeonZ * RDBLayout.RDBSide); resumePosition = dungeonBlockPosition + marker.flatPosition; } else { // Use first quest marker marker = place.SiteDetails.questSpawnMarkers[0]; Vector3 dungeonBlockPosition = new Vector3(marker.dungeonX * RDBLayout.RDBSide, 0, marker.dungeonZ * RDBLayout.RDBSide); resumePosition = dungeonBlockPosition + marker.flatPosition; } resumePending = true; }
public override void Update(Task caller) { ParentQuest.RemoveLogStep(stepID); SetComplete(); }