/// <summary> /// Checks evaluation chain built at factory time. /// Assuming simple left-to-right short-circuit evaluation. /// </summary> /// <returns>Result of chained evaluations.</returns> bool CheckEvals() { // Must have at least one evaluation by this point if (evaluations.Count == 0) { throw new Exception("WhenTask condition has no evaluations."); } // Evaluate conditions left to right bool left = false, right = false; for (int i = 0; i < evaluations.Count; i++) { // Get task state based on operator Operator op = evaluations[i].op; Symbol taskSymbol = new Symbol(evaluations[i].task); switch (op) { case Operator.When: case Operator.And: case Operator.Or: right = IsTaskSet(taskSymbol); break; case Operator.WhenNot: case Operator.AndNot: case Operator.OrNot: right = !IsTaskSet(taskSymbol); break; default: return(false); } // Evaluate short-circuit conditions switch (op) { // Handle single-task condition case Operator.When: case Operator.WhenNot: if (evaluations.Count == 1 && !right) { return(false); } break; // Both sides must be true case Operator.And: case Operator.AndNot: if (!left || !right) { // Allow avaluation to continue unless at the end of chain if (i < evaluations.Count - 1) { right = false; } else { return(false); } } else { right = true; } // Exit true when right is true and next condition is an Or/OrNot if (right == true && i < evaluations.Count - 1) { if (evaluations[i + 1].op == Operator.Or || evaluations[i + 1].op == Operator.OrNot) { return(true); } } break; // Either side can be true to result as true case Operator.Or: case Operator.OrNot: if (!left && !right) { // Allow avaluation to continue unless at the end of chain if (i < evaluations.Count - 1) { right = false; } else { return(false); } } else { right = true; } break; default: return(false); } // Move to the right left = right; } // All conditions have evaluated return(true); }
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 Foe GetFoe(Symbol symbol) { return(GetResource(symbol) as Foe); }
public Item GetItem(Symbol symbol) { return(GetResource(symbol) as Item); }
public Person GetPerson(Symbol symbol) { return(GetResource(symbol) as Person); }
public Place GetPlace(Symbol symbol) { return(GetResource(symbol) as Place); }
public Clock GetClock(Symbol symbol) { return(GetResource(symbol) as Clock); }