public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) { return(null); } // Factory new action ReputeExceedsDo action = new ReputeExceedsDo(parentQuest); action.npcSymbol = new Symbol(match.Groups["npcSymbol"].Value); action.taskSymbol = new Symbol(match.Groups["taskSymbol"].Value); action.minReputation = Parser.ParseInt(match.Groups["minReputation"].Value); return(action); }
public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) { return(null); } // Factory new action ItemUsedDo action = new ItemUsedDo(parentQuest); action.itemSymbol = new Symbol(match.Groups["anItem"].Value); action.taskSymbol = new Symbol(match.Groups["aTask"].Value); action.textID = Parser.ParseInt(match.Groups["textID"].Value); return(action); }
public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) { return(null); } // Factory new action TeleportPc action = new TeleportPc(parentQuest); action.targetPlace = new Symbol(match.Groups["aPlace"].Value); if (match.Groups["marker"].Success) { action.targetMarker = Parser.ParseInt(match.Groups["marker"].Value); } return(action); }
public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) { return(null); } // Factory new action CreateFoe action = new CreateFoe(parentQuest); action.foeSymbol = new Symbol(match.Groups["symbol"].Value); action.spawnInterval = (uint)Parser.ParseInt(match.Groups["minutes"].Value) * 60; action.spawnMaxTimes = Parser.ParseInt(match.Groups["count"].Value); action.spawnChance = Parser.ParseInt(match.Groups["percent"].Value); action.lastSpawnTime = DaggerfallUnity.Instance.WorldTime.DaggerfallDateTime.ToSeconds(); // Handle infinite if (!string.IsNullOrEmpty(match.Groups["infinite"].Value)) { action.spawnMaxTimes = -1; } // Handle "send" variant if (!string.IsNullOrEmpty(match.Groups["send"].Value)) { action.isSendAction = true; // "send" without "count" implies infinite if (action.spawnMaxTimes == 0) { action.spawnMaxTimes = -1; } } return(action); }
public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) { return(null); } // Factory new action DailyFrom action = new DailyFrom(parentQuest); int hours1 = Parser.ParseInt(match.Groups["hours1"].Value); int minutes1 = Parser.ParseInt(match.Groups["minutes1"].Value); int hours2 = Parser.ParseInt(match.Groups["hours2"].Value); int minutes2 = Parser.ParseInt(match.Groups["minutes2"].Value); action.minDailySeconds = ToDailySeconds(hours1, minutes1); action.maxDailySeconds = ToDailySeconds(hours2, minutes2); return(action); }
/// <summary> /// Parse optional message tags from this resource. /// </summary> /// <param name="line"></param> protected void ParseMessageTags(string line) { string matchStr = @"anyInfo (?<info>\d+)|used (?<used>\d+)|rumors (?<rumors>\d+)"; // Get message tag matches MatchCollection matches = Regex.Matches(line, matchStr); if (matches.Count == 0) { return; } // Grab tag values foreach (Match match in matches) { // Match info message id Group info = match.Groups["info"]; if (info.Success) { infoMessageID = Parser.ParseInt(info.Value); } // Match used message id Group used = match.Groups["used"]; if (used.Success) { usedMessageID = Parser.ParseInt(used.Value); } // Match rumors message id Group rumors = match.Groups["rumors"]; if (rumors.Success) { rumorsMessageID = Parser.ParseInt(rumors.Value); } } }
public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) { return(null); } // Factory new action WhenAttributeLevel action = new WhenAttributeLevel(parentQuest); string attributeName = match.Groups["attributeName"].Value; if (!Enum.IsDefined(typeof(DFCareer.Stats), attributeName)) { SetComplete(); throw new Exception(string.Format("WhenAttributeLevel: Attribute name {0} is not a known Daggerfall attribute", attributeName)); } action.attribute = (DFCareer.Stats)Enum.Parse(typeof(DFCareer.Stats), attributeName); action.minAttributeValue = Parser.ParseInt(match.Groups["minAttributeValue"].Value); return(action); }
public override IQuestAction CreateNew(string source, Quest parentQuest) { // Source must match pattern Match match = Test(source); if (!match.Success) return null; // Get signed value int value; string sign = match.Groups["sign"].Value; if (sign == "+") value = Parser.ParseInt(match.Groups["amount"].Value); else if (sign == "-") value = -Parser.ParseInt(match.Groups["amount"].Value); else throw new System.Exception("Invalid sign encountered by ChangeReputeWith action"); // Factory new action ChangeReputeWith action = new ChangeReputeWith(parentQuest); action.target = new Symbol(match.Groups["target"].Value); action.amount = value; return action; }
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); }
// Gets factionID of a career NPC int GetCareerFactionID(string careerAllianceName) { const int magesGuild = 40; const int nobles = 242; const int genericTemple = 450; const int merchants = 510; // P2 is careerID int careerID; Table factionsTable = QuestMachine.Instance.FactionsTable; if (factionsTable.HasValue(careerAllianceName)) { careerID = Parser.ParseInt(factionsTable.GetValue("p2", careerAllianceName)); } else { Debug.LogErrorFormat("Could not find careerAllianceName {0}", careerAllianceName); return(-1); } // Initial handling for Local_X.X career groups pending further review and information // These appear to use P3 for career association and will all resolve to Merchants for now // P3=0 (Apothecary), P3=1 (Town1), P3=2 (Armory), P3=3 (Bank), P3=10000 (unused in any quests) if (careerID < 0) { careerID = Parser.ParseInt(factionsTable.GetValue("p3", careerAllianceName)); } // Handle Local_4.10k - unused in any quests and will default to Merchants for now if (careerID == 10000) { careerID = 0; } // Assign factionID based on careerID // How Daggerfall links these is not 100% confirmed, some guesses below // Most of these NPC careers seem to be aligned with faction #510 Merchants switch (careerID) { case 0: case 1: case 2: case 3: case 5: case 6: case 7: case 8: case 9: case 10: case 12: case 13: case 15: return(merchants); // Merchants case 11: return(magesGuild); // Mages Guild case 14: return(genericTemple); // Generic Temple seems to link all the temples together case 16: return(nobles); // Random Noble case 17: case 18: case 19: case 20: // Default for everything else will just be fairly generic "people of" faction // This at least ensures the object will compile to something valid default: // Not sure if "Resident1-4" career really maps to regional "people of" in classic return(GameManager.Instance.PlayerGPS.GetPeopleOfCurrentRegion()); } }
/// <summary> /// Parse optional message tags from this resource. /// </summary> /// <param name="line"></param> protected void ParseMessageTags(string line) { string matchStr = @"anyInfo (?<info>\d+)|used (?<used>\d+)|rumors (?<rumors>\d+)|" + @"anyInfo (?<infoName>\w+)|used (?<usedName>\w+)|rumors (?<rumorsName>\w+)"; // Get message tag matches MatchCollection matches = Regex.Matches(line, matchStr); if (matches.Count == 0) { return; } // Grab tag values foreach (Match match in matches) { // // info // // Match info message ID Group info = match.Groups["info"]; if (info.Success) { infoMessageID = Parser.ParseInt(info.Value); } // Resolve info message name back to ID string infoName = match.Groups["infoName"].Value; if (infoMessageID == -1 && !string.IsNullOrEmpty(infoName)) { Table table = QuestMachine.Instance.StaticMessagesTable; infoMessageID = Parser.ParseInt(table.GetValue("id", infoName)); } // // used // // Match used message ID Group used = match.Groups["used"]; if (used.Success) { usedMessageID = Parser.ParseInt(used.Value); } // Resolve used message name back to ID string usedName = match.Groups["usedName"].Value; if (usedMessageID == -1 && !string.IsNullOrEmpty(usedName)) { Table table = QuestMachine.Instance.StaticMessagesTable; usedMessageID = Parser.ParseInt(table.GetValue("id", usedName)); } // // rumors // // Match rumors message ID Group rumors = match.Groups["rumors"]; if (rumors.Success) { rumorsMessageID = Parser.ParseInt(rumors.Value); } // Resolve rumors message name back to ID string rumorsName = match.Groups["rumorsName"].Value; if (rumorsMessageID == -1 && !string.IsNullOrEmpty(rumorsName)) { Table table = QuestMachine.Instance.StaticMessagesTable; rumorsMessageID = Parser.ParseInt(table.GetValue("id", rumorsName)); } } }
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 SetResource(string line) { base.SetResource(line); string declMatchStr = @"(Item|item) (?<symbol>[a-zA-Z0-9_.-]+) (?<artifact>artifact) (?<itemName>[a-zA-Z0-9_.-]+)|(Item|item) (?<symbol>[a-zA-Z0-9_.-]+) (?<itemName>[a-zA-Z0-9_.-]+)"; string optionsMatchStr = @"range (?<rangeLow>\d+) to (?<rangeHigh>\d+)|" + @"item class (?<itemClass>\d+) subclass (?<itemSubClass>\d+)"; // Try to match source line with pattern string itemName = string.Empty; int itemClass = -1; int itemSubClass = -1; bool isGold = false; int rangeLow = -1; int rangeHigh = -1; Match match = Regex.Match(line, declMatchStr); if (match.Success) { // Store symbol for quest system Symbol = new Symbol(match.Groups["symbol"].Value); // Item or artifact name itemName = match.Groups["itemName"].Value; // Artifact status if (!string.IsNullOrEmpty(match.Groups["artifact"].Value)) { artifact = true; } // Set gold - this is not in the lookup table if (itemName == "gold") { isGold = true; } // Split options from declaration string optionsLine = line.Substring(match.Length); // Match all options MatchCollection options = Regex.Matches(optionsLine, optionsMatchStr); foreach (Match option in options) { // Range low value Group rangeLowGroup = option.Groups["rangeLow"]; if (rangeLowGroup.Success) { rangeLow = Parser.ParseInt(rangeLowGroup.Value); } // Range high value Group rangeHighGroup = option.Groups["rangeHigh"]; if (rangeHighGroup.Success) { rangeHigh = Parser.ParseInt(rangeHighGroup.Value); } // Item class value Group itemClassGroup = option.Groups["itemClass"]; if (itemClassGroup.Success) { itemClass = Parser.ParseInt(itemClassGroup.Value); } // Item subclass value Group itemSubClassGroup = option.Groups["itemSubClass"]; if (itemClassGroup.Success) { itemSubClass = Parser.ParseInt(itemSubClassGroup.Value); } } // Create item if (!string.IsNullOrEmpty(itemName) && !isGold) { item = CreateItem(itemName); // Create by name of item in lookup table } else if (itemClass != -1 && !isGold) { item = CreateItem(itemClass, itemSubClass); // Create item by class and subclass (a.k.a ItemGroup and GroupIndex) } else if (isGold) { item = CreateGold(rangeLow, rangeHigh); // Create gold pieces of amount by level or range values } else { throw new Exception(string.Format("Could not create Item from line {0}", line)); } // add conversation topics from anyInfo command tag AddConversationTopics(); } }
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 override void SetResource(string line) { string individualNPCName = string.Empty; string factionTypeName = string.Empty; string factionAllianceName = string.Empty; string careerAllianceName = string.Empty; string genderName = string.Empty; int faceIndex = -1; bool atHome = false; base.SetResource(line); // Match strings string declMatchStr = @"(Person|person) (?<symbol>[a-zA-Z0-9'_.-]+)"; string optionsMatchStr = @"named (?<individualNPCName>[a-zA-Z0-9'_.-]+)|" + @"face (?<faceIndex>\d+)|" + @"(factionType|factiontype) (?<factionType>[a-zA-Z0-9'_.-]+)|" + @"faction (?<factionAlliance>[a-zA-Z0-9'_.-]+)|" + @"group (?<careerAlliance>[a-zA-Z0-9'_.-]+)|" + @"(?<gender>female|male)|" + @"(?<atHome>(atHome|athome))"; // Try to match source line with pattern Match match = Regex.Match(line, declMatchStr); if (match.Success) { // Store symbol for quest system Symbol = new Symbol(match.Groups["symbol"].Value); // Match all options MatchCollection options = Regex.Matches(line, optionsMatchStr); foreach (Match option in options) { // Individual NPC Group individualNPCNameGroup = option.Groups["individualNPCName"]; if (individualNPCNameGroup.Success) { individualNPCName = individualNPCNameGroup.Value; } // Face Group faceGroup = option.Groups["faceIndex"]; if (faceGroup.Success) { faceIndex = Parser.ParseInt(faceGroup.Value); } // Faction type Group factionTypeGroup = option.Groups["factionType"]; if (factionTypeGroup.Success) { factionTypeName = factionTypeGroup.Value; } // Faction alliance Group factionAllianceGroup = option.Groups["factionAlliance"]; if (factionAllianceGroup.Success) { factionAllianceName = factionAllianceGroup.Value; } // Group Group careerAllianceGroup = option.Groups["careerAlliance"]; if (careerAllianceGroup.Success) { careerAllianceName = careerAllianceGroup.Value; } // Gender Group genderGroup = option.Groups["gender"]; if (genderGroup.Success) { genderName = genderGroup.Value; } // At home Group atHomeGroup = option.Groups["atHome"]; atHome = atHomeGroup.Success; } // Setup NPC based on input parameters if (!string.IsNullOrEmpty(individualNPCName)) { SetupIndividualNPC(individualNPCName); } else if (!string.IsNullOrEmpty(careerAllianceName)) { SetupCareerAllianceNPC(careerAllianceName); } else if (!string.IsNullOrEmpty(factionTypeName)) { SetupFactionTypeNPC(factionTypeName); } else if (!string.IsNullOrEmpty(factionAllianceName)) { SetupFactionAllianceNPC(factionAllianceName); } else { throw new Exception(string.Format("Person resource could not identify NPC from line {0}", line)); } // Assign NPC details AssignRace(); AssignGender(genderName); AssignHUDFace(faceIndex); AssignDisplayName(); AssignHomeTown(); AssignGod(); // Is NPC at home? isIndividualAtHome = atHome; // Done Debug.LogFormat("Created NPC {0} with FactionID #{1}.", displayName, factionData.id); } }
// Gets factionID of a faction type NPC int GetFactionTypeFactionID(string factionTypeName) { // Only allowing a small range of random faction types for now FactionFile.FactionTypes[] randomFactionTypes = new FactionFile.FactionTypes[] { FactionFile.FactionTypes.Courts, FactionFile.FactionTypes.Province, FactionFile.FactionTypes.People, FactionFile.FactionTypes.Temple, }; // P3 is faction type int factionType; Table factionsTable = QuestMachine.Instance.FactionsTable; if (factionsTable.HasValue(factionTypeName)) { factionType = Parser.ParseInt(factionsTable.GetValue("p3", factionTypeName)); } else { Debug.LogErrorFormat("Could not find factionTypeName {0}", factionTypeName); return(-1); } // Handle random faction type // This selects from a restricted pool to ensure vital NPCs don't get randomly selected if (factionType == -1) { factionType = (int)UnityEngine.Random.Range(0, randomFactionTypes.Length); } // Assign factionID based on factionType // This value is 0-15 and maps to "type:" in faction.txt // Daggerfall seems to largely select from selected pool of factionType objects at random // But some factionTypes do not exist in file and suspect special handling for others // Treating on a case-by-case basis for now switch ((FactionFile.FactionTypes)factionType) { // These faction types do not generally have a specific region associated with them // Select from pool of all objects this faction type case FactionFile.FactionTypes.Daedra: case FactionFile.FactionTypes.Group: case FactionFile.FactionTypes.Subgroup: case FactionFile.FactionTypes.Official: case FactionFile.FactionTypes.Temple: return(GetRandomFactionOfType(factionType)); // Faction type of God is never used in quests // Many of these factions do not have an NPC flat // Recommend never using - not sure how to redirect to working state yet case FactionFile.FactionTypes.God: return(GetRandomFactionOfType(factionType)); // The individual type is not used by any canonical quests // It is more or less equivalent to reserving an individual NPC // Not recommended to use this in quests // Just returning a random person to ensure NPC is created case FactionFile.FactionTypes.Individual: return(GetRandomFactionOfType(factionType)); // Not sure how to use vampire clans yet // These are *mostly* used by vampire quests where its assumed the player's vampire faction will be used // It wouldn't make sense for player to gain reputation with another vampire clan after all // As vampire factions not in game yet, just select one at random to ensure NPC is created case FactionFile.FactionTypes.VampireClan: return(GetRandomFactionOfType(factionType)); // Assign an NPC from current player region case FactionFile.FactionTypes.Province: return(GetCurrentRegionFaction()); // Not all regions have a witches coven associated // Just select a random coven for now case FactionFile.FactionTypes.WitchesCoven: return(GetRandomFactionOfType(factionType)); // Type 10 Knightly_Guard does not exist in FACTION.TXT but IS used in some quests // Redirecting this to "Generic Knightly Order" #844 to ensure NPC is created case FactionFile.FactionTypes.KnightlyGuard: return(844); // Type 11 Magic_User does not exist in FACTION.TXT and is not used in any quests // Redirecting this to "Mages Guild" #40 to ensure NPC is created case FactionFile.FactionTypes.MagicUser: return(40); // Type 12 Generic_Group does not exist in FACTION.TXT and is not used in any quests // Redirecting this to a random choice between "Generic Temple" #450 and "Generic Knightly Order" #844 to ensure NPC is created case FactionFile.FactionTypes.Generic: return((UnityEngine.Random.Range(0f, 1f) < 0.5f) ? 450 : 844); // Type 13 Thieves_Den does not exist in FACTION.TXT and is not used in any quests // Redirecting this to "Thieves Guild" #42 to ensure NPC is created case FactionFile.FactionTypes.Thieves: return(42); // Get "court of" current region case FactionFile.FactionTypes.Courts: return(GetCourtOfCurrentRegion()); // Get "people of" current region case FactionFile.FactionTypes.People: return(GameManager.Instance.PlayerGPS.GetPeopleOfCurrentRegion()); // Give up default: return(-1); } }
public override void SetResource(string line) { base.SetResource(line); string declMatchStr = @"(Clock|clock) (?<symbol>[a-zA-Z0-9_.-]+)"; string optionsMatchStr = @"(?<ddhhmm>)\d+.\d+:\d+|" + @"(?<hhmm>)\d+:\d+|" + @"(?<mm>)\d+|" + @"flag (?<flag>\d+)|" + @"range (?<minRange>\d+) (?<maxRange>\d+)"; // Try to match source line with pattern Match match = Regex.Match(line, declMatchStr); if (match.Success) { // Seed random //UnityEngine.Random.InitState(Time.renderedFrameCount); // Store symbol for quest system Symbol = new Symbol(match.Groups["symbol"].Value); // Split options from declaration string optionsLine = line.Substring(match.Length); // Match all options // TODO: Work out meaning of "flag" and "range" values int timeValue0 = -1; int timeValue1 = -1; int currentTimeValue = 0; MatchCollection options = Regex.Matches(optionsLine, optionsMatchStr); foreach (Match option in options) { // Match any possible time value syntax Group ddhhmmGroup = option.Groups["ddhhmm"]; Group hhmmGroup = option.Groups["hhmm"]; Group mmGroup = option.Groups["mm"]; if (ddhhmmGroup.Success || hhmmGroup.Success || mmGroup.Success) { // Get time value int timeValue = MatchTimeValue(option.Value); // Assign time value if (currentTimeValue == 0) { timeValue0 = timeValue; currentTimeValue++; } else if (currentTimeValue == 1) { timeValue1 = timeValue; currentTimeValue++; } else { throw new Exception("Clock cannot specify more than 2 time values."); } } // Unknown flag value Group flagGroup = option.Groups["flag"]; if (flagGroup.Success) { flag = Parser.ParseInt(flagGroup.Value); } // Unknown minRange value Group minRangeGroup = option.Groups["minRange"]; if (minRangeGroup.Success) { minRange = Parser.ParseInt(minRangeGroup.Value); } // Unknown maxRange value Group maxRangeGroup = option.Groups["maxRange"]; if (maxRangeGroup.Success) { maxRange = Parser.ParseInt(maxRangeGroup.Value); } } // Set total clock time based on values int clockTimeInSeconds = 0; if (currentTimeValue == 0) { // No time value specifed: "clock _symbol_" // Clock timer starts at a random value between 1 week and 1 minute // TODO: Work out the actual range Daggerfall uses here int minSeconds = GetTimeInSeconds(0, 0, 1); int maxSeconds = GetTimeInSeconds(7, 0, 0); clockTimeInSeconds = FromRange(minSeconds, maxSeconds); } else if (currentTimeValue == 1) { // One time value specified: "clock _symbol_ dd.hh:mm" // Clock timer starts at this value clockTimeInSeconds = timeValue0; } else if (currentTimeValue == 2) { // Two time values specified: "clock _symbol_ dd.hh:mm dd.hh:mm" // Clock timer starts at a random point between timeValue0 (min) and timeValue1 (max) // But second value must be greater than first to be a valid range if (timeValue1 > timeValue0) { clockTimeInSeconds = FromRange(timeValue0, timeValue1); } else { clockTimeInSeconds = timeValue0; } } // Flag & 16 seems to indicate clock should be // set to 2.5x cautious travel time of first Place resource specificed in quest script // Quests using this configuration will usually end once timer elapsed if ((flag & 16) == 16) { clockTimeInSeconds = GetTravelTimeInSeconds(); } // HACK: Force another travel time check when flag & 1, clock type involves some target, and clockTimeInSeconds is still 0 // This ensures player has travel time from automatic NPC quests such as A0C00Y17 and quest does not end instantly if ((flag & 1) == 1 && maxRange > 0 && clockTimeInSeconds == 0) { clockTimeInSeconds = GetTravelTimeInSeconds(); } // TODO: Improve clock types using available information // Note that TEMPLATE misinterprets upper byte of flag value as "range min" and clock type as "range max" // And unfortunately some information about targets is lost by TEMPLATE, but this does not seem critical in most cases // Decompiled quest scripts can be fixed where necessary without re-decompiling all quests to a new timer format // Thanks and credit to ELENWEL and PANGO for discovering more information about timers // Refer to following links for more detail: // https://en.uesp.net/wiki/Daggerfall:Quest_hacking_guide#Timers_Section // https://forums.dfworkshop.net/viewtopic.php?f=23&t=1655&start=10#p19163 // https://forums.dfworkshop.net/viewtopic.php?f=23&t=1655&start=10#p19262 // Timer "type" values that are currently stored in "range max": // 0 Random duration (Random time from min to max) // 1 Fixed duration (timer duration = min) // 2 One location or NPC (one location duration will be travelTime( here(), link1) * 1.5) // 3 Two locations/ NPCs, only one dungeon(gives extra week in some circumstances) (two locations (from=>To quest) duration will be travelTime(link1, link2)*1.5) // 4 Same as 2 ? // 5 Two locations / NPCs, both are dungeons(gives up to 2 extra weeks in some circumstances) (two locations duration will be travelTime(here(), link1)*1,5 + travelTime(link1, link2)*1.5) // Notes on flags: // for destination based timer, flags & 0x100 : link1 is a NPC, if not link1 is a location // for destination based timer, flags & 0x200 : link2 is a NPC, if not link1 is a location // flags & 0x10 double timer’s duration(there and back) // flags & 8 : timer can fire multiple time(unsure ? ) // flags & 4 : timer will set state to 0 upon expiration(unsure?) // flags & 2 : timer will set state to 1 upon expiration +something(todo) // flags & 1 : timer will set state to 1 upon expiration // other flags are reserved for internal uses (timer state) // Set timer value in seconds InitialiseTimer(clockTimeInSeconds); } }
public override void SetResource(string line) { base.SetResource(line); string declMatchStr = @"(Clock|clock) (?<symbol>[a-zA-Z0-9_.-]+)"; string optionsMatchStr = @"(?<ddhhmm>)\d+.\d+:\d+|" + @"(?<hhmm>)\d+:\d+|" + @"(?<mm>)\d+|" + @"flag (?<flag>\d+)|" + @"range (?<minRange>\d+) (?<maxRange>\d+)"; // Try to match source line with pattern Match match = Regex.Match(line, declMatchStr); if (match.Success) { // Seed random //UnityEngine.Random.InitState(Time.renderedFrameCount); // Store symbol for quest system Symbol = new Symbol(match.Groups["symbol"].Value); // Split options from declaration string optionsLine = line.Substring(match.Length); // Match all options // TODO: Work out meaning of "flag" and "range" values int timeValue0 = -1; int timeValue1 = -1; int currentTimeValue = 0; MatchCollection options = Regex.Matches(optionsLine, optionsMatchStr); foreach (Match option in options) { // Match any possible time value syntax Group ddhhmmGroup = option.Groups["ddhhmm"]; Group hhmmGroup = option.Groups["hhmm"]; Group mmGroup = option.Groups["mm"]; if (ddhhmmGroup.Success || hhmmGroup.Success | mmGroup.Success) { // Get time value int timeValue = MatchTimeValue(option.Value); // Assign time value if (currentTimeValue == 0) { timeValue0 = timeValue; currentTimeValue++; } else if (currentTimeValue == 1) { timeValue1 = timeValue; currentTimeValue++; } else { throw new Exception("Clock cannot specify more than 2 time values."); } } // Unknown flag value Group flagGroup = option.Groups["flag"]; if (flagGroup.Success) { flag = Parser.ParseInt(flagGroup.Value); } // Unknown minRange value Group minRangeGroup = option.Groups["minRange"]; if (minRangeGroup.Success) { minRange = Parser.ParseInt(minRangeGroup.Value); } // Unknown maxRange value Group maxRangeGroup = option.Groups["maxRange"]; if (maxRangeGroup.Success) { maxRange = Parser.ParseInt(maxRangeGroup.Value); } } // Set total clock time based on values int clockTimeInSeconds = 0; if (currentTimeValue == 0) { // No time value specifed: "clock _symbol_" // Clock timer starts at a random value between 1 week and 1 minute // TODO: Work out the actual range Daggerfall uses here int minSeconds = GetTimeInSeconds(0, 0, 1); int maxSeconds = GetTimeInSeconds(7, 0, 0); clockTimeInSeconds = FromRange(minSeconds, maxSeconds); } else if (currentTimeValue == 1) { // One time value specified: "clock _symbol_ dd.hh:mm" // Clock timer starts at this value clockTimeInSeconds = timeValue0; } else if (currentTimeValue == 2) { // Two time values specified: "clock _symbol_ dd.hh:mm dd.hh:mm" // Clock timer starts at a random point between timeValue0 (min) and timeValue1 (max) // But second value must be greater than first to be a valid range if (timeValue1 > timeValue0) { clockTimeInSeconds = FromRange(timeValue0, timeValue1); } else { clockTimeInSeconds = timeValue0; } } // Flag & 16 seems to indicate clock should be // set to 2.5x cautious travel time of first Place resource specificed in quest script // Quests using this configuration will usually end once timer elapsed if ((flag & 16) == 16) { clockTimeInSeconds = GetTravelTimeInSeconds(); } // HACK: Add range of time in days when flag & 1 and maxRange > 0 // Still not positive this is the correct usage of minRange - maxRange if ((flag & 1) == 1 && maxRange > 0) { // Perform another check for travel time if total time 0 // This ensures player has travel time from automatic NPCs if (clockTimeInSeconds == 0) { clockTimeInSeconds = GetTravelTimeInSeconds(); } // Add range int randomDays = UnityEngine.Random.Range(minRange, maxRange); clockTimeInSeconds += randomDays * DaggerfallDateTime.SecondsPerDay; } // Set timer value in seconds InitialiseTimer(clockTimeInSeconds); } }
// Creates a career-based NPC like a Shopkeeper or Banker void SetupCareerAllianceNPC(string careerAllianceName) { // Special handling for Questor class // Will revisit this later when guilds are more integrated if (careerAllianceName.Equals("Questor", StringComparison.InvariantCultureIgnoreCase)) { if (SetupQuestorNPC()) { return; } } // 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); } } // Get faction data int factionID = GetCareerFactionID(careerAllianceName); if (factionID != -1) { FactionFile.FactionData factionData = GetFactionData(factionID); // Setup Person resource this.factionData = factionData; } else { Debug.LogErrorFormat("SetupCareerAllianceNPC() failed to setup {0}", careerAllianceName); } }