lib/vhyl-rpg-expanded-templates.t

Reference index ยท View on GitHub

   1  /* Include after expanded characters and skills. Source: full-2008, B258-B259.
   2   * Only character packages are implemented; ancestry cost exceptions are pending. */
   3  
   4  /** Character package factory. Every call creates fresh owned training and traits.
   5   * Skill rows are [definition, points]; trait rows are [Lite ID, level, scope]. */
   6  class RpgExpandedTemplate: object
   7      st = 10
   8      dx = 10
   9      iq = 10
  10      ht = 10
  11      hpAdjustment = 0
  12      fpAdjustment = 0
  13      willAdjustment = 0
  14      perceptionAdjustment = 0
  15      speedQuarterAdjustment = 0
  16      moveAdjustment = 0
  17      skillRows = []
  18      traitRows = []
  19      choiceGroups = []
  20      campaignTL = 3
  21      techLevel = nil
  22      pointBudget = 100
  23      disadvantageLimit = 50
  24      create(rules)
  25      {
  26          if (self.choiceGroups.length() != 0) rpgExpandedReject('template-unresolved-choices');
  27          local p = new RpgExpandedProfile(); p.rules = rules;
  28          p.campaignTL = self.campaignTL;
  29          p.techLevel = self.techLevel == nil ? self.campaignTL : self.techLevel;
  30          p.st = self.st; p.dx = self.dx; p.iq = self.iq; p.ht = self.ht;
  31          p.hpAdjustment = self.hpAdjustment; p.fpAdjustment = self.fpAdjustment;
  32          p.willAdjustment = self.willAdjustment; p.perceptionAdjustment = self.perceptionAdjustment;
  33          p.speedQuarterAdjustment = self.speedQuarterAdjustment; p.moveAdjustment = self.moveAdjustment;
  34          p.pointBudget = self.pointBudget; p.disadvantageLimit = self.disadvantageLimit;
  35          foreach (local row in self.skillRows)
  36          {
  37              if (row.length() != 2 || row[1] == nil || !row[1].ofKind(RpgSkill))
  38                  rpgExpandedReject('template-skill');
  39              rpgInteger(row[2], 1, 10000);
  40              local training = new RpgExpandedTraining(); training.skill = row[1]; training.points = row[2];
  41              p.skills = p.skills + [training];
  42          }
  43          foreach (local row in self.traitRows)
  44          {
  45              if (row.length() != 3) rpgExpandedReject('template-trait');
  46              p.traits = p.traits + [rpgLiteMakeTrait(row[1],row[2],row[3])];
  47          }
  48          try { rpgLiteValidateTraits(p); }
  49          catch (RpgLiteCharacterError error) { rpgExpandedReject(error.reason); }
  50          rpgExpandedValidateCharacter(p); return p;
  51      }
  52  ;
  53  
  54  /** Internal temporary requirement for composing purchased skill levels. */
  55  class RpgExpandedTemplateSkillRequirement: object
  56      skill = nil
  57      score = 0
  58  ;
  59  
  60  /** Combine occupational packages, B259. All outputs are freshly constructed;
  61   * actual secondary scores are maximized before their final costs are derived. */
  62  rpgExpandedCombineTemplates(templates, rules, pointBudget, disadvantageLimit)
  63  {
  64      if (templates.length() == 0) rpgExpandedReject('empty-template-composition');
  65      local result = new RpgExpandedProfile(); result.rules = rules;
  66      result.pointBudget = pointBudget; result.disadvantageLimit = disadvantageLimit;
  67      local first = true; local hp = 0; local fp = 0; local will = 0; local perception = 0;
  68      local speed = 0; local move = 0; local requirements = [];
  69      foreach (local template in templates)
  70      {
  71          if (!template.ofKind(RpgExpandedTemplate)) rpgExpandedReject('template-type');
  72          local source = template.create(rules);
  73          if (source.contributions.length() != 0 || source.languages.length() != 0
  74              || source.equipment.length() != 0)
  75              rpgExpandedReject('template-composition-components');
  76          if (first)
  77          { result.campaignTL = source.campaignTL; result.techLevel = source.techLevel; }
  78          else if (source.campaignTL != result.campaignTL || source.techLevel != result.techLevel)
  79              rpgExpandedReject('template-technology-conflict');
  80          foreach (local training in source.skills)
  81          {
  82              local skill = training.skill;
  83              local wildcard = skill.ofKind(RpgExpandedSkill) && skill.wildcard;
  84              local score = rpgExpandedSkillLevel(source.attributeScore(skill.attribute),
  85                  skill.difficulty, training.points, wildcard);
  86              if (score == nil) rpgExpandedReject('template-skill');
  87              local found = nil;
  88              foreach (local requirement in requirements)
  89                  if (requirement.skill == skill) found = requirement;
  90              if (found == nil)
  91              {
  92                  found = new RpgExpandedTemplateSkillRequirement(); found.skill = skill;
  93                  found.score = score; requirements = requirements + [found];
  94              }
  95              else if (score > found.score) found.score = score;
  96          }
  97          if (first || source.st > result.st) result.st = source.st;
  98          if (first || source.dx > result.dx) result.dx = source.dx;
  99          if (first || source.iq > result.iq) result.iq = source.iq;
 100          if (first || source.ht > result.ht) result.ht = source.ht;
 101          if (source.maxHP > hp) hp = source.maxHP;
 102          if (source.maxEnergy > fp) fp = source.maxEnergy;
 103          if (source.will > will) will = source.will;
 104          if (source.perception > perception) perception = source.perception;
 105          if (source.basicSpeedQuarters() > speed) speed = source.basicSpeedQuarters();
 106          if (source.basicMove() > move) move = source.basicMove();
 107          foreach (local trait in source.traits)
 108          {
 109              local found = nil;
 110              foreach (local current in result.traits)
 111                  if (current.id == trait.id && current.scope == trait.scope) found = current;
 112              if (found == nil) result.traits = result.traits + [trait];
 113              else if (trait.level > found.level) found.level = trait.level;
 114          }
 115          first = nil;
 116      }
 117      result.hpAdjustment = hp - result.st; result.fpAdjustment = fp - result.ht;
 118      result.willAdjustment = will - result.iq; result.perceptionAdjustment = perception - result.iq;
 119      result.speedQuarterAdjustment = speed - result.dx - result.ht;
 120      result.moveAdjustment = move - speed / 4;
 121      foreach (local requirement in requirements)
 122      {
 123          local skill = requirement.skill;
 124          local wildcard = skill.ofKind(RpgExpandedSkill) && skill.wildcard;
 125          local points = rpgExpandedSkillCost(skill.difficulty,
 126              requirement.score - result.attributeScore(skill.attribute), wildcard);
 127          local minimum = wildcard ? 3 : 1;
 128          if (points < minimum) points = minimum;
 129          local training = new RpgExpandedTraining(); training.skill = skill; training.points = points;
 130          result.skills = result.skills + [training];
 131      }
 132      try { rpgLiteValidateTraits(result); }
 133      catch (RpgLiteCharacterError error) { rpgExpandedReject(error.reason); }
 134      rpgExpandedValidateCharacter(result); return result;
 135  }
 136  
 137  /** A concrete selectable component bundle; rows follow the template factory. */
 138  class RpgExpandedTemplateOption: object
 139      id = nil
 140      eligible = true
 141      skillRows = []
 142      traitRows = []
 143      stChange = 0
 144      dxChange = 0
 145      iqChange = 0
 146      htChange = 0
 147      hpChange = 0
 148      fpChange = 0
 149      willChange = 0
 150      perceptionChange = 0
 151      speedQuarterChange = 0
 152      moveChange = 0
 153      changes()
 154      {
 155          local values = [self.stChange,self.dxChange,self.iqChange,self.htChange,
 156              self.hpChange,self.fpChange,self.willChange,self.perceptionChange,
 157              self.speedQuarterChange,self.moveChange];
 158          foreach (local value in values) rpgInteger(value, -1000, 1000);
 159          return values;
 160      }
 161      cost()
 162      {
 163          local total = 0; local values = self.changes();
 164          local prices = [10,20,20,10,2,3,5,5,5,5];
 165          for (local index = 1; index <= 10; index += 1) total += values[index] * prices[index];
 166          foreach (local row in self.skillRows)
 167          {
 168              if (row.length() != 2 || row[1] == nil || !row[1].ofKind(RpgSkill))
 169                  rpgExpandedReject('template-skill');
 170              rpgInteger(row[2], 1, 10000); total += row[2];
 171          }
 172          foreach (local row in self.traitRows)
 173          {
 174              if (row.length() != 3) rpgExpandedReject('template-trait');
 175              total += rpgLiteMakeTrait(row[1],row[2],row[3]).creationCost();
 176          }
 177          rpgInteger(total, -1000000, 1000000); return total;
 178      }
 179  ;
 180  
 181  /** Count and signed point bounds for one authored choice group. */
 182  class RpgExpandedTemplateChoice: object
 183      id = nil
 184      options = []
 185      minimumChoices = 1
 186      maximumChoices = 1
 187      minimumPoints = nil
 188      maximumPoints = nil
 189  ;
 190  
 191  modify RpgExpandedProfile
 192      templateChoices = []
 193  ;
 194  
 195  modify RpgExpandedTemplate
 196      /** selections: one [group ID, [option IDs]] row for every group. */
 197      createSelected(rules, selections)
 198      {
 199          if (selections.length() != self.choiceGroups.length())
 200              rpgExpandedReject('template-choice-groups');
 201          local selectedGroups = [];
 202          foreach (local row in selections)
 203          {
 204              if (row.length() != 2 || holds(selectedGroups,row[1]))
 205                  rpgExpandedReject('template-choice-groups');
 206              selectedGroups = selectedGroups + [row[1]];
 207          }
 208          local skillRows = self.skillRows; local traitRows = self.traitRows;
 209          local ids = []; local receipt = []; local changes = [0,0,0,0,0,0,0,0,0,0];
 210          foreach (local group in self.choiceGroups)
 211          {
 212              if (!group.ofKind(RpgExpandedTemplateChoice) || group.id == nil || holds(ids,group.id))
 213                  rpgExpandedReject('template-choice-definition');
 214              ids = ids + [group.id];
 215              rpgInteger(group.minimumChoices, 0, 1000);
 216              rpgInteger(group.maximumChoices, group.minimumChoices, 1000);
 217              if (group.minimumPoints != nil) rpgInteger(group.minimumPoints, -1000000, 1000000);
 218              if (group.maximumPoints != nil) rpgInteger(group.maximumPoints, -1000000, 1000000);
 219              if (group.minimumPoints != nil && group.maximumPoints != nil
 220                  && group.minimumPoints > group.maximumPoints)
 221                  rpgExpandedReject('template-choice-definition');
 222              local chosen = nil;
 223              foreach (local row in selections) if (row[1] == group.id) chosen = row[2];
 224              if (chosen == nil) rpgExpandedReject('template-choice-groups');
 225              if (chosen.length() < group.minimumChoices || chosen.length() > group.maximumChoices)
 226                  rpgExpandedReject('template-choice-count');
 227              local optionIds = [];
 228              foreach (local option in group.options)
 229              {
 230                  if (!option.ofKind(RpgExpandedTemplateOption) || option.id == nil || holds(optionIds,option.id))
 231                      rpgExpandedReject('template-choice-definition');
 232                  optionIds = optionIds + [option.id]; option.cost();
 233              }
 234              local used = []; local total = 0;
 235              foreach (local id in chosen)
 236              {
 237                  if (holds(used,id) || !holds(optionIds,id)) rpgExpandedReject('template-choice-option');
 238                  used = used + [id];
 239                  foreach (local option in group.options)
 240                      if (option.id == id)
 241                      {
 242                          if (!option.eligible) rpgExpandedReject('template-choice-ineligible');
 243                          total += option.cost(); skillRows = skillRows + option.skillRows;
 244                          traitRows = traitRows + option.traitRows;
 245                          local values = option.changes(); local combined = [];
 246                          for (local index = 1; index <= 10; index += 1)
 247                              combined = combined + [changes[index] + values[index]];
 248                          changes = combined;
 249                          receipt = receipt + [[group.id,option.id]];
 250                      }
 251              }
 252              if (group.minimumPoints != nil && total < group.minimumPoints
 253                  || group.maximumPoints != nil && total > group.maximumPoints)
 254                  rpgExpandedReject('template-choice-points');
 255          }
 256          local resolved = new RpgExpandedTemplate();
 257          resolved.st = self.st + changes[1]; resolved.dx = self.dx + changes[2];
 258          resolved.iq = self.iq + changes[3]; resolved.ht = self.ht + changes[4];
 259          resolved.hpAdjustment = self.hpAdjustment + changes[5];
 260          resolved.fpAdjustment = self.fpAdjustment + changes[6];
 261          resolved.willAdjustment = self.willAdjustment + changes[7];
 262          resolved.perceptionAdjustment = self.perceptionAdjustment + changes[8];
 263          resolved.speedQuarterAdjustment = self.speedQuarterAdjustment + changes[9];
 264          resolved.moveAdjustment = self.moveAdjustment + changes[10];
 265          resolved.campaignTL = self.campaignTL; resolved.techLevel = self.techLevel;
 266          resolved.pointBudget = self.pointBudget; resolved.disadvantageLimit = self.disadvantageLimit;
 267          resolved.skillRows = skillRows; resolved.traitRows = traitRows;
 268          local profile = resolved.create(rules); profile.templateChoices = receipt; return profile;
 269      }
 270  ;