/// <summary> /// Generates a trait set for a Character, based on parent traits /// </summary> /// <returns>Array containing trait set</returns> /// <param name="mummyTraits">The mother's traits</param> /// <param name="daddyTraits">The father's traits</param> /// <param name="isMale">Whether character is a male</param> public static Tuple <Trait, int>[] GenerateTraitSetFromParents(Tuple <Trait, int>[] mummyTraits, Tuple <Trait, int>[] daddyTraits, bool isMale) { // store all unique traitKeys from both parents List <string> uniqueTraitKeys = new List <string>(); // mummy's traits for (int i = 0; i < mummyTraits.Length; i++) { uniqueTraitKeys.Add(mummyTraits[i].Item1.id); } // daddy's traits for (int i = 0; i < daddyTraits.Length; i++) { if (!uniqueTraitKeys.Contains(daddyTraits[i].Item1.id)) { uniqueTraitKeys.Add(daddyTraits[i].Item1.id); } } // create new traits using uniqueTraitKeys Tuple <Trait, int>[] newTraits = Utility_Methods.GenerateTraitSet(uniqueTraitKeys); return(newTraits); }
/// <summary> /// Checks that an OwnershipChallenge id is in the correct format /// </summary> /// <returns>bool indicating whether the id is valid</returns> /// <param name="id">The id to be validated</param> public static bool ValidateChallengeID(string id) { bool isValid = true; // split and ensure has correct format string[] idSplit = id.Split('_'); if (idSplit.Length != 2) { isValid = false; } // must start with 'Challenge' else if (!idSplit[0].Equals("Challenge")) { isValid = false; } // must end with numbers else if (!Utility_Methods.CheckStringValid("numbers", idSplit[1])) { isValid = false; } return(isValid); }
/// <summary> /// Checks that a JournalEntry personae entry is in the correct format /// </summary> /// <returns>bool indicating whether the personae entry is valid</returns> /// <param name="personae">The personae entry to be validated</param> public static bool ValidateJentryPersonae(string personae) { bool isValid = true; // split using'|' string[] persSplit = personae.Split('|'); if (persSplit.Length != 2) { isValid = false; } // 1st section must be valid character ID or 'all' else if ((!persSplit[0].Equals("all")) && (!Utility_Methods.ValidateCharacterID(persSplit[0]))) { isValid = false; } // 2nd section must be all letters else if (!Utility_Methods.CheckStringValid("letters", persSplit[1])) { isValid = false; } return(isValid); }
/// <summary> /// Constructor for Terrain /// </summary> /// <param name="id">String holding terrain code</param> /// <param name="desc">String holding terrain description</param> /// <param name="tc">double holding terrain travel cost</param> public Terrain(String id, string desc, double tc) { // VALIDATION // ID // trim id = id.Trim(); if (!Utility_Methods.ValidateTerrainID(id)) { throw new InvalidDataException("Terrain ID must have the format 'terr_' followed by some letters"); } // DESC // trim and ensure 1st is uppercase desc = Utility_Methods.FirstCharToUpper(desc.Trim()); if (!Utility_Methods.ValidateName(desc)) { throw new InvalidDataException("Terrain description must be 1-40 characters long and contain only valid characters (a-z and ') or spaces"); } // TC if (tc < 1) { throw new InvalidDataException("Terrain travelCost must be a double >= 1"); } this.id = id; this.description = desc; this.travelCost = tc; }
/// <summary> /// Constructor for BaseLanguage /// </summary> /// <param name="id">String holding language ID</param> /// <param name="nam">String holding language name</param> public BaseLanguage(String id, String nam) { // VALIDATION // ID // trim id = id.Trim(); if (!Utility_Methods.ValidateLanguageID(id, "baseLang")) { throw new InvalidDataException("BaseLanguage ID must have the format 'lang_' followed by 1-2 letters"); } // NAM // trim and ensure 1st is uppercase nam = Utility_Methods.FirstCharToUpper(nam.Trim()); if (!Utility_Methods.ValidateName(nam)) { throw new InvalidDataException("BaseLanguage name must be 1-40 characters long and contain only valid characters (a-z and ') or spaces"); } this.id = id; this.name = nam; }
/// <summary> /// Constructor for TitleName /// </summary> /// <param name="lang">string holding Language ID</param> /// <param name="nam">string holding title name associated with specific language</param> public TitleName(string lang, string nam) { // VALIDATION // LANG // trim lang = lang.Trim(); if ((!Utility_Methods.ValidateLanguageID(lang)) && (!lang.Equals("generic"))) { throw new InvalidDataException("TitleName langID must either be 'generic' or have the format 'lang_' followed by 1-2 letters, ending in 1-2 numbers"); } // NAM // trim and ensure 1st is uppercase nam = Utility_Methods.FirstCharToUpper(nam.Trim()); if (!Utility_Methods.ValidateName(nam)) { throw new InvalidDataException("TitleName name must be 1-40 characters long and contain only valid characters (a-z and ') or spaces"); } this.langID = lang; this.name = nam; }
/// <summary> /// Constructor for Language_Serialised taking seperate values. /// For creating Language_Serialised from CSV file. /// </summary> /// <param name="id">string holding Language ID</param> /// <param name="bLang">string holding BaseLanguage (ID)</param> /// <param name="dial">int holding language dialect code</param> public Language_Serialised(string id, string bLang, int dial) { // VALIDATION // ID // trim id and bLang id = id.Trim(); bLang = bLang.Trim(); if (!id.Contains(bLang)) { throw new InvalidDataException("Language_Serialised ID be based on its BaseLanguage ID"); } else if (!Utility_Methods.ValidateLanguageID(id)) { throw new InvalidDataException("Language_Serialised ID must have the format 'lang_' followed by 1-2 letters, ending in 1-2 numbers"); } // BLANG if (!Utility_Methods.ValidateLanguageID(bLang, "baseLang")) { throw new InvalidDataException("Language_Serialised BaseLanguage ID must have the format 'lang_' followed by 1-2 letters"); } // DIALECT if (dial < 1) { throw new InvalidDataException("Language dialect code must be an integer >= 0"); } this.id = id; this.baseLanguage = bLang; this.dialect = dial; }
/// <summary> /// Constructor for Place_Serialised taking seperate values. /// For creating Place_Serialised from CSV file. /// </summary> /// <param name="id">String holding place ID</param> /// <param name="nam">String holding place name</param> /// <param name="own">String holding Place owner (ID)</param> /// <param name="tiHo">String holding place title holder (charID)</param> /// <param name="rnk">String holding Place rank (ID)</param> public Place_Serialised(String id, String nam, byte r, String tiHo = null, string own = null) { // VALIDATION // ID // trim and ensure is uppercase id = id.Trim().ToUpper(); if (!Utility_Methods.ValidatePlaceID(id)) { throw new InvalidDataException("Place_Serialised id must be 5 characters long, start with a letter, and end in at least 2 numbers"); } // NAM // trim and ensure 1st is uppercase nam = Utility_Methods.FirstCharToUpper(nam.Trim()); if (!Utility_Methods.ValidateName(nam)) { throw new InvalidDataException("Place_Serialised name must be 1-40 characters long and contain only valid characters (a-z and ') or spaces"); } // TIHO if (!String.IsNullOrWhiteSpace(tiHo)) { // trim and ensure 1st is uppercase tiHo = Utility_Methods.FirstCharToUpper(tiHo.Trim()); if (!Utility_Methods.ValidateCharacterID(tiHo)) { throw new InvalidDataException("Place_Serialised titleHolder must have the format 'Char_' followed by some numbers"); } } // OWNER if (!String.IsNullOrWhiteSpace(owner)) { // trim and ensure 1st is uppercase owner = Utility_Methods.FirstCharToUpper(owner.Trim()); if (!Utility_Methods.ValidateCharacterID(owner)) { throw new InvalidDataException("Place_Serialised owner must have the format 'Char_' followed by some numbers"); } } this.id = id; this.name = nam; this.owner = own; this.titleHolder = tiHo; this.rank = r; }
/// <summary> /// Generates a characteristic stat for a Character, based on parent stats /// </summary> /// <returns>Double containing characteristic stat</returns> /// <param name="mummyStat">The mother's characteristic stat</param> /// <param name="daddyStat">The father's characteristic stat</param> public static Double GenerateKeyCharacteristics(Double mummyStat, Double daddyStat) { Double newStat = 0; // get average of parents' stats Double parentalAverage = (mummyStat + daddyStat) / 2; // generate random (0 - 100) to determine relationship of new stat to parentalAverage double randPercentage = Utility_Methods.GetRandomDouble(100); // calculate new stat if (randPercentage <= 35) { newStat = parentalAverage; } else if (randPercentage <= 52.5) { newStat = parentalAverage - 1; } else if (randPercentage <= 70) { newStat = parentalAverage + 1; } else if (randPercentage <= 80) { newStat = parentalAverage - 2; } else if (randPercentage <= 90) { newStat = parentalAverage + 2; } else if (randPercentage <= 95) { newStat = parentalAverage - 3; } else { newStat = parentalAverage + 3; } // make sure new stat falls within acceptable range if (newStat < 1) { newStat = 1; } else if (newStat > 9) { newStat = 9; } return(newStat); }
/// <summary> /// Constructor for Province /// </summary> /// <param name="otax">Double holding province tax rate</param> /// <param name="king">Province's Kingdom object</param> public Province(String id, String nam, Double otax, String tiHo = null, PlayerCharacter own = null, Kingdom king = null, Rank r = null) : base(id, nam, tiHo, own, r) { // VALIDATION // OTAX if (!Utility_Methods.ValidatePercentage(otax)) { throw new InvalidDataException("Province taxrate must be a double between 0 and 100"); } this.taxRate = otax; this.kingdom = king; }
/// <summary> /// Constructor for Kingdom_Serialised taking seperate values. /// For creating Kingdom_Serialised from CSV file. /// </summary> /// <param name="nat">Kingdom's Nationality object</param> public Kingdom_Serialised(String id, String nam, byte r, string nat, String tiHo = null, string own = null) : base(id, nam, r, own: own, tiHo: tiHo) { // VALIDATION // NAT // trim and ensure 1st is uppercase nat = Utility_Methods.FirstCharToUpper(nat.Trim()); if (!Utility_Methods.ValidateNationalityID(nat)) { throw new InvalidDataException("Kingdom_Serialised nationality ID must be 1-3 characters long, and consist entirely of letters"); } this.nationality = nat; }
/// <summary> /// Checks that a nationality id is in the correct format /// </summary> /// <returns>bool indicating whether the id is valid</returns> /// <param name="nat">The id to be validated</param> public static bool ValidateNationalityID(string nat) { bool isValid = true; // 1-3 in length if ((nat.Length < 1) || (nat.Length > 3)) { isValid = false; } // letters only if (!Utility_Methods.CheckStringValid("letters", nat)) { isValid = false; } return(isValid); }
/// <summary> /// Checks that a Language id is in the correct format /// </summary> /// <returns>bool indicating whether the id is valid</returns> /// <param name="id">The id to be validated</param> /// <param name="langType">The type of id to be validated (lang, baseLang)</param> public static bool ValidateLanguageID(string id, string langType = "lang") { bool isValid = true; // split and ensure has correct format string[] idSplit = id.Split('_'); if (idSplit.Length != 2) { isValid = false; } // must start with 'lang' else if (!idSplit[0].ToLower().Equals("lang")) { isValid = false; } else if (langType.Equals("baseLang")) { // 2nd section must be letters if (!Utility_Methods.CheckStringValid("letters", idSplit[1])) { isValid = false; } } else { // 1st character of 2nd section must be letter if (!Utility_Methods.CheckStringValid("letters", idSplit[1].Substring(0, 1))) { isValid = false; } // last character of 2nd section must be number else if (!Utility_Methods.CheckStringValid("numbers", idSplit[1].Substring(idSplit[1].Length - 1, 1))) { isValid = false; } } return(isValid); }
/// <summary> /// Constructor for Position /// </summary> /// <param name="holder">string holding ID of the office holder</param> /// <param name="nat">Nationality associated with the position</param> public Position(byte id, TitleName[] ti, byte stat, string holder, Nationality nat) : base(id, ti, stat) { // VALIDATION // HOLDER if (!String.IsNullOrWhiteSpace(holder)) { // trim and ensure 1st is uppercase holder = Utility_Methods.FirstCharToUpper(holder.Trim()); if (!Utility_Methods.ValidateCharacterID(holder)) { throw new InvalidDataException("Position officeHolder id must have the format 'Char_' followed by some numbers"); } } this.officeHolder = holder; this.nationality = nat; }
/// <summary> /// Constructor for Position_Serialised taking seperate values. /// For creating Position_Serialised from CSV file. /// </summary> /// <param name="id">byte holding Position ID</param> /// <param name="ti">title name in various languages</param> /// <param name="stat">byte holding stature for this position</param> /// <param name="holder">string ID of the office holder</param> /// <param name="nat">string holding ID of Nationality associated with the position</param> public Position_Serialised(byte id, TitleName[] ti, byte stat, string holder, string nat) { // VALIDATION // STAT if (stat < 1) { stat = 1; } else if (stat > 3) { stat = 3; } // HOLDER if (!String.IsNullOrWhiteSpace(holder)) { // trim and ensure 1st is uppercase holder = Utility_Methods.FirstCharToUpper(holder.Trim()); if (!Utility_Methods.ValidateCharacterID(holder)) { throw new InvalidDataException("Position_Serialised officeHolder id must have the format 'Char_' followed by some numbers"); } } // NAT // trim and ensure 1st is uppercase nat = Utility_Methods.FirstCharToUpper(nat.Trim()); if (!Utility_Methods.ValidateNationalityID(nat)) { throw new InvalidDataException("Position_Serialised nationality ID must be 1-3 characters long, and consist entirely of letters"); } this.id = id; this.title = ti; this.stature = stat; this.officeHolder = holder; this.nationality = nat; }
/// <summary> /// Constructor for Trait /// </summary> /// <param name="id">String holding trait ID</param> /// <param name="nam">String holding trait name</param> /// <param name="effs">Dictionary(string, double) holding trait effects</param> public Trait(String id, String nam, Dictionary <Globals_Game.Stats, double> effs) { // VALIDATION // ID // trim id = id.Trim(); if (!Utility_Methods.ValidateTraitID(id)) { throw new InvalidDataException("Trait ID must have the format 'trait_' followed by some numbers"); } // NAM // trim and ensure 1st is uppercase nam = Utility_Methods.FirstCharToUpper(nam.Trim()); if (!Utility_Methods.ValidateName(nam)) { throw new InvalidDataException("Trait name must be 1-40 characters long and contain only valid characters (a-z and ') or spaces"); } // effect values double[] effVals = new double[effs.Count]; effs.Values.CopyTo(effVals, 0); for (int i = 0; i < effVals.Length; i++) { if ((effVals[i] < -0.99) || (effVals[i] > 0.99)) { throw new InvalidDataException("All Trait effect values must be doubles between -0.99 and 0.99"); } } this.id = id; this.name = nam; this.effects = effs; }
/// <summary> /// Constructor for Province_Serialised taking seperate values. /// For creating Province_Serialised from CSV file. /// </summary> /// <param name="otax">Double holding province tax rate</param> /// <param name="king">string holding Province's Kingdom (id)</param> public Province_Serialised(String id, String nam, byte r, Double otax, String tiHo = null, string own = null, string king = null) : base(id, nam, r, own: own, tiHo: tiHo) { // VALIDATION // OTAX if (!Utility_Methods.ValidatePercentage(otax)) { throw new InvalidDataException("Province_Serialised taxrate must be a double between 0 and 100"); } // KING // trim and ensure is uppercase king = king.Trim().ToUpper(); if (!Utility_Methods.ValidatePlaceID(king)) { throw new InvalidDataException("Province_Serialised kingdom ID must be 5 characters long, start with a letter, and end in at least 2 numbers"); } this.taxRate = otax; this.kingdom = king; }
/// <summary> /// Checks that a Place id is in the correct format /// </summary> /// <returns>bool indicating whether the id is valid</returns> /// <param name="id">The id to be validated</param> public static bool ValidatePlaceID(string id) { bool isValid = true; // ensure is 5 in length if (id.Length != 5) { isValid = false; } // ensure 1st is letter else if (!Utility_Methods.CheckStringValid("letters", id.Substring(0, 1))) { isValid = false; } // ensure ends in 2 numbers else if (!Utility_Methods.CheckStringValid("numbers", id.Substring(3))) { isValid = false; } return(isValid); }
/// <summary> /// Create a new JournalEntry- used for more complex messages that would be more appropriate to be reconstructed on the client side /// </summary> /// <param name="m"></param> /// <param name="id"></param> /// <param name="yr"></param> /// <param name="seas"></param> /// <param name="pers"></param> /// <param name="typ"></param> /// <param name="loc"></param> /// <param name="desc"></param> public JournalEntry(ProtoMessage m, uint id, uint yr, byte seas, String[] pers, String typ, String loc = null, string desc = null) { this.entryDetails = m; // VALIDATION // SEAS // check between 0-3 if (!Utility_Methods.ValidateSeason(seas)) { throw new InvalidDataException("JournalEntry season must be a byte between 0-3"); } // PERS if (pers.Length > 0) { for (int i = 0; i < pers.Length; i++) { // split using'|' string[] persSplit = pers[i].Split('|'); if (persSplit.Length > 1) { // character ID: trim and ensure 1st is uppercase if (!persSplit[0].Contains("all")) { persSplit[0] = Utility_Methods.FirstCharToUpper(persSplit[0].Trim()); } // trim role persSplit[1] = persSplit[1].Trim(); // put back together pers[i] = persSplit[0] + "|" + persSplit[1]; } if (!Utility_Methods.ValidateJentryPersonae(pers[i])) { throw new InvalidDataException("Each JournalEntry personae must consist of 2 sections of letters, divided by '|', the 1st of which must be a valid character ID"); } } } // TYPE if (String.IsNullOrWhiteSpace(typ)) { throw new InvalidDataException("JournalEntry type must be a string > 0 characters in length"); } // LOC if (!String.IsNullOrWhiteSpace(loc)) { // trim and ensure is uppercase loc = loc.Trim().ToUpper(); if (!Utility_Methods.ValidatePlaceID(loc)) { throw new InvalidDataException("JournalEntry location id must be 5 characters long, start with a letter, and end in at least 2 numbers"); } } this.jEntryID = id; this.year = yr; this.season = seas; this.personae = pers; this.type = typ; if (!String.IsNullOrWhiteSpace(loc)) { this.location = loc; } this.viewed = false; }
/// <summary> /// Calculates casualties from a battle for both sides /// </summary> /// <returns>double[] containing percentage loss modifier for each side</returns> /// <param name="attackerTroops">uint containing attacking army troop numbers</param> /// <param name="defenderTroops">uint containing defending army troop numbers</param> /// <param name="attackerValue">uint containing attacking army battle value</param> /// <param name="defenderValue">uint containing defending army battle value</param> /// <param name="attackerVictorious">bool indicating whether attacking army was victorious</param> public static double[] CalculateBattleCasualties(uint attackerTroops, uint defenderTroops, uint attackerValue, uint defenderValue, bool attackerVictorious) { double[] battleCasualties = new double[2]; double largeArmyModifier = 0; bool largestWon = true; // determine highest/lowest battle value double maxBV = Math.Max(attackerValue, defenderValue); double minBV = Math.Min(attackerValue, defenderValue); // use BVs to determine high mark for base casualty rate of army with smallest battle value (see below) double highCasualtyRate = maxBV / (maxBV + minBV); // determine base casualty rate for army with smallest battle value double smallestModifier = Utility_Methods.GetRandomDouble(highCasualtyRate, min: 0.1); // determine if army with largest battle value won if (maxBV == attackerValue) { if (!attackerVictorious) { largestWon = false; } } else { if (attackerVictorious) { largestWon = false; } } // if army with largest battle value won if (largestWon) { // calculate casualty modifier for army with largest battle value // (based on adapted version of Lanchester's Square Law - i.e. largest army loses less troops than smallest) largeArmyModifier = (1 + ((minBV * minBV) / (maxBV * maxBV))) / 2; // attacker is large army if (attackerVictorious) { battleCasualties[1] = smallestModifier; // determine actual troop losses for largest army based on smallest army losses, // modified by largeArmyModifier uint largeArmyLosses = Convert.ToUInt32((defenderTroops * battleCasualties[1]) * largeArmyModifier); // derive final casualty modifier for largest army battleCasualties[0] = largeArmyLosses / (double)attackerTroops; } // defender is large army else { battleCasualties[0] = smallestModifier; uint largeArmyLosses = Convert.ToUInt32((attackerTroops * battleCasualties[0]) * largeArmyModifier); battleCasualties[1] = largeArmyLosses / (double)defenderTroops; } } // if army with smallest battle value won else { // calculate casualty modifier for army with largest battle value // this ensures its losses will be roughly the same as the smallest army (because it lost) largeArmyModifier = Utility_Methods.GetRandomDouble(1.20, min: 0.8); // defender is large army if (attackerVictorious) { // smallest army losses reduced because they won battleCasualties[0] = smallestModifier / 2; // determine actual troop losses for largest army based on smallest army losses, // modified by largeArmyModifier uint largeArmyLosses = Convert.ToUInt32((attackerTroops * battleCasualties[0]) * largeArmyModifier); // derive final casualty modifier for largest army battleCasualties[1] = largeArmyLosses / (double)defenderTroops; } // attacker is large army else { battleCasualties[1] = smallestModifier / 2; uint largeArmyLosses = Convert.ToUInt32((defenderTroops * battleCasualties[1]) * largeArmyModifier); battleCasualties[0] = largeArmyLosses / (double)attackerTroops; } } return(battleCasualties); }
/// <summary> /// Constructor for Ailment /// </summary> /// <param name="id">String holding ailment ID</param> /// <param name="descr">string holding ailment description</param> /// <param name="wh">string holding ailment date</param> /// <param name="eff">uint holding current ailment effect</param> /// <param name="minEff">uint holding minimum ailment effect</param> public Ailment(String id, string descr, string wh, uint eff, uint minEff) { // VALIDATION // ID // trim and ensure 1st is uppercase id = Utility_Methods.FirstCharToUpper(id.Trim()); if (!Utility_Methods.ValidateAilmentID(id)) { throw new InvalidDataException("Ailment ID must have the format 'Ail_' followed by some numbers"); } // DESCR // trim and ensure 1st is uppercase descr = Utility_Methods.FirstCharToUpper(descr.Trim()); if (!Utility_Methods.ValidateName(descr)) { throw new InvalidDataException("Ailment description must be 1-40 characters long and contain only valid characters (a-z and ') or spaces"); } // WHEN // trim and ensure 1st is uppercase wh = Utility_Methods.FirstCharToUpper(wh.Trim()); // check contains season if (!wh.Contains("Spring")) { if (!wh.Contains("Summer")) { if (!wh.Contains("Autumn")) { if (!wh.Contains("Winter")) { throw new InvalidDataException("Ailment 'when' must specify the season and year in which the ailment occurred"); } } } } // EFF // check must be 1-5 if ((eff < 1) || (eff > 5)) { throw new InvalidDataException("Ailment effect must be a uint between 1-5"); } // MINEFF // check not > 1 if (minEff > 1) { throw new InvalidDataException("Ailment minimumEffect must be a uint less than 2"); } this.ailmentID = id; this.description = descr; this.when = wh; this.effect = eff; this.minimumEffect = minEff; }