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 ;