public ArtPair(Ability technique, Ability form) { if (!MagicArts.IsTechnique(technique) || !MagicArts.IsForm(form)) { throw new ArgumentException("one of the arts used to initialize this pair is invalid!"); } Technique = technique; Form = form; }
/// <summary> /// Looks at the giver and receiver vis desires, and sees what combination of vis can meet the desires /// </summary> /// <param name="price">the agreed upon price, in Vim vis</param> /// <param name="giverVisDesires"></param> /// <param name="receiverVisDesires"></param> /// <returns></returns> private IEnumerable <VisOffer> GenerateVisOffer(double price, VisDesire[] giverVisDesires, VisDesire[] receiverVisDesires) { double remainingPrice = price; List <VisOffer> offerSegments = new List <VisOffer>(); // order the receiver's desires from largest to smallest foreach (VisDesire desire in receiverVisDesires.Where(d => d.Quantity > 0).OrderByDescending(d => d.Quantity)) { var giverArt = giverVisDesires.First(d => d.Art == desire.Art); // see if the giver can supply this type of vis if (giverArt.Quantity < 0) { // they have some to trade double maxNeed = remainingPrice; if (MagicArts.IsTechnique(desire.Art)) { maxNeed /= 4.0; if (giverArt.Quantity * -1 >= maxNeed) { offerSegments.Add(new VisOffer(desire.Art, maxNeed)); remainingPrice = 0; break; } else { // we're going to need more than this, so use all of this vis and move on offerSegments.Add(new VisOffer(desire.Art, giverArt.Quantity * -1)); remainingPrice -= giverArt.Quantity * -4; } } else if (desire.Art != MagicArts.Vim) { maxNeed /= 2.0; if (giverArt.Quantity * -1 >= maxNeed) { offerSegments.Add(new VisOffer(desire.Art, maxNeed)); remainingPrice = 0; break; } else { // we're going to need more than this, so use all of this vis and move on offerSegments.Add(new VisOffer(desire.Art, giverArt.Quantity * -1)); remainingPrice -= giverArt.Quantity * -2; } } } } if (remainingPrice > 0) { return(null); } return(offerSegments); }
private double CalculateVisValue() { double total = 0; foreach (VisOffer offer in VisOffers) { if (MagicArts.IsTechnique(offer.Art)) { total += offer.Quantity * 4.0; } else if (offer.Art != MagicArts.Vim) { total += offer.Quantity * 2.0; } else { total += offer.Quantity; } } return(total); }
/// <summary> /// Determines the value of an experience gain in terms of the value of vis, /// and the amount of time it would take the character to produce and learn from /// that amount of vis /// </summary> /// <param name="ability"></param> /// <param name="gain"></param> /// <returns>the vis equivalence (vis savings) of this gain relative to vis study</returns> public override double RateSeasonalExperienceGain(Ability ability, double gain) { if (!MagicArts.IsArt(ability)) { return(base.RateSeasonalExperienceGain(ability, gain)); } double baseDistillVisRate = GetVisDistillationRate(); double distillVisRate = baseDistillVisRate; if (MagicArts.IsTechnique(ability)) { distillVisRate /= 4.0; } else if (ability != MagicArts.Vim) { distillVisRate /= 2.0; } CharacterAbilityBase charAbility = GetAbility(ability); double visUsedPerStudySeason = 0.5 + ((charAbility.Value + (charAbility.GetValueGain(gain) / 2)) / 10.0); // the gain per season depends on how the character views vis double studySeasons = gain / VisStudyRate; double visNeeded = studySeasons * visUsedPerStudySeason; // compare to the number of seasons we would need to extract the vis // plus the number of seasons we would need to study the extracted vis double extractTime = visNeeded / distillVisRate; double totalVisEquivalent = (extractTime + studySeasons) * baseDistillVisRate; // credit back the value of the exposure gained in the process of distilling double exposureGained = 2.0 * extractTime; double exposureSeasonsOfVis = exposureGained / VisStudyRate; CharacterAbilityBase vim = GetAbility(MagicArts.Vim); CharacterAbilityBase creo = GetAbility(MagicArts.Creo); CharacterAbilityBase exposureAbility = creo.Value < vim.Value ? creo : vim; double visValueOfExposure = 0.5 + ((exposureAbility.Value + (exposureAbility.GetValueGain(exposureGained) / 2)) / 10.0) * exposureSeasonsOfVis; return(totalVisEquivalent - visValueOfExposure); }
public override IBook GetBestBookToWrite() { double currentBestBookValue = 0; IBook bestBook = null; HashSet <int> consideredTopics = new HashSet <int>(); // since the value of a tractatus is independent of topic, // calculate the value of writing a tractatus now, so that we don't have to keep doing it double tractatusValue = (6 + GetAttributeValue(AttributeType.Communication)) * GlobalEconomy.GlobalTractatusValue / 6; double writingRate = GetAttributeValue(AttributeType.Communication) + GetAbility(_writingLanguage).Value; var unneededBookTopics = GetUnneededBooksFromCollection().Select(b => b.Topic).Distinct(); foreach (BookDesire bookDesire in GlobalEconomy.DesiredBooksList) { // if we already have a suitable book for this topic, let's not try to write another if (unneededBookTopics.Contains(bookDesire.Ability)) { continue; } // check to see if we could even write a summa of a level that would meet this desire if (GetAbility(bookDesire.Ability).Value > bookDesire.CurrentLevel * 2) { CharacterAbility buyerAbility = new CharacterAbility(bookDesire.Ability); buyerAbility.Experience = buyerAbility.GetExperienceUntilLevel(bookDesire.CurrentLevel); // see if we have started a summa on this topic var relatedIncompleteBooks = _incompleteBooks.Where(b => b.Topic == bookDesire.Ability && b.Level > bookDesire.CurrentLevel); if (relatedIncompleteBooks.Any()) { foreach (Summa incompleteBook in relatedIncompleteBooks) { // the effective value is based on time to finish, not time already invested double experienceValue = buyerAbility.GetExperienceUntilLevel(incompleteBook.Level); double seasonsOfStudy = Math.Ceiling(experienceValue / incompleteBook.Quality); double effectiveQuality = experienceValue / seasonsOfStudy; // at a minimum, the book is worth the vis it would take, on average, to gain that experience double visUsedPerStudySeason = 0.5 + ((buyerAbility.Value + (buyerAbility.GetValueGain(experienceValue) / 2)) / 10.0); double studySeasons = experienceValue / VisStudyRate; double visNeeded = studySeasons * visUsedPerStudySeason; // scale visNeeded according to vis type if (MagicArts.IsTechnique(bookDesire.Ability)) { visNeeded *= 4; } else if (MagicArts.IsArt(bookDesire.Ability) && bookDesire.Ability != MagicArts.Vim) { visNeeded *= 2; } // for now, scale vis according to quality of book vs. quality of vis study visNeeded *= incompleteBook.Quality / VisStudyRate; // divide this visNeed valuation by how many seasons are left for writing double writingNeeded = incompleteBook.GetWritingPointsNeeded() - incompleteBook.PointsComplete; double seasonsLeft = Math.Ceiling(writingNeeded / writingRate); double writingValue = visNeeded / seasonsLeft; if (writingValue > currentBestBookValue) { // continue writing this summa bestBook = incompleteBook; } } } else { // NOTE: this could lead us down a strange rabbit hole of starting a bunch of // summae on a subject of varying levels, but I think that's unlikely enough // to not try and protect from for now double maxLevel = GetAbility(bookDesire.Ability).Value / 2.0; for (double l = maxLevel; l > bookDesire.CurrentLevel; l--) { double q = 6 + maxLevel - l; // the effective value is based on time to finish, not time already invested double experienceValue = buyerAbility.GetExperienceUntilLevel(l); double seasonsOfStudy = Math.Ceiling(experienceValue / q); double effectiveQuality = experienceValue / seasonsOfStudy; // at a minimum, the book is worth the vis it would take, on average, to gain that experience double visUsedPerStudySeason = 0.5 + ((buyerAbility.Value + (buyerAbility.GetValueGain(experienceValue) / 2)) / 10.0); double studySeasons = experienceValue / VisStudyRate; double visNeeded = studySeasons * visUsedPerStudySeason; // scale visNeeded according to vis type if (MagicArts.IsTechnique(bookDesire.Ability)) { visNeeded *= 4; } else if (MagicArts.IsArt(bookDesire.Ability) && bookDesire.Ability != MagicArts.Vim) { visNeeded *= 2; } // for now, scale vis according to quality of book vs. quality of vis study visNeeded *= q / VisStudyRate; // divide this visNeed valuation by how many seasons are left for writing double seasonsLeft = Math.Ceiling(l / writingRate); double writingValue = visNeeded / seasonsLeft; if (writingValue > currentBestBookValue) { // write this summa bestBook = new Summa { Quality = q, Level = l, Topic = bookDesire.Ability, Title = bookDesire.Ability.AbilityName + " Summa for Dummies by " + Name, Value = writingValue }; currentBestBookValue = writingValue; } } } } // consider the value of writing a tractatus var charAbility = GetAbility(bookDesire.Ability); if (!consideredTopics.Contains(bookDesire.Ability.AbilityId) && tractatusValue > currentBestBookValue && CanWriteTractatus(charAbility)) { ushort previouslyWrittenCount = GetTractatiiWrittenOnTopic(bookDesire.Ability); string name = Name + " " + bookDesire.Ability.AbilityName + " T" + previouslyWrittenCount.ToString(); bestBook = new Tractatus { Topic = bookDesire.Ability, Title = name, Value = tractatusValue }; currentBestBookValue = tractatusValue; } consideredTopics.Add(bookDesire.Ability.AbilityId); } return(bestBook); }