root/trunk/RBPrivate/WheelOfTime/WheelOfTime.py

Revision 747, 13.2 kB (checked in by sholloway, 5 years ago)

*** empty log message ***

Line 
1 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 #~ Imports
3 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 import bisect
6
7 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 #~ Definitions
9 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10
11 class Traits(object):
12     PhysicalTraitsTable = [
13         'agile', 'athletic', 'brawny', 'broadshouldered', 'energetic',
14         'fierce', 'healthy', 'high-pain-threshold', 'lithe', 'nimble', 'quick',
15         'robust', 'targeteer', 'tough', 'wiry',
16     ]
17     MentalTraitsTable = [
18         'alert', 'analytical', 'astute', 'creative', 'discerning',
19         'empathetic', 'experienced', 'insightful', 'inventive', 'knowlegeable',
20         'mechanically-inclined', 'precise', 'recall', 'searcher',
21         'supernumerate',
22     ]
23     SocialTraitsTable = [
24         'adaptive', 'alluring', 'commanding', 'charismatic', 'elegant',
25         'eloquent', 'enigmatic', 'entertaining', 'expressive', 'handsome',
26         'persuasive', 'presence', 'sly', 'strong-willed', 'witty'
27     ]
28
29     Table = {}
30     Table.update(dict([(x, 'physical') for x in PhysicalTraitsTable]))
31     Table.update(dict([(x, 'mental') for x in MentalTraitsTable]))
32     Table.update(dict([(x, 'social') for x in SocialTraitsTable]))
33
34 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 #~ Skills
36 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37
38 class Stat(object):
39     TraitDependencyTable = {
40         'compelling-defense':('strong-willed', 'iron-willed'),
41         'initiative':('agile', 'alert', 'quick'),
42         'soak':('energetic', 'high-pain-threshold', 'tough'),
43         'social-defense':('commanding', 'presence', 'strong-willed'),
44
45         #TODO:
46         # 'mental-defense' stat == 'sense-deception' skill
47         # 'passive-defense' stat == 0.5 * weapon skill in use
48         }
49
50     def __init__(self, statkey):
51         self.statkey = statkey.replace(' ', '-').lower()
52         self.getTraits()
53
54     def getEffectiveLevel(self, traits):
55         return self.getLevel()
56
57     def getLevel(self):
58         return sum(filter(None, map(traits.get, self.getTraits())))
59
60     def getTraits(self):
61         return self.TraitDependencyTable[self.statkey]
62
63 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64
65 class Skill(object):
66     TraitDependencyTable = {
67         # Dreamwalker Skills
68         'control-self':('discerning', 'precise', 'strong-willed'),
69         'dream-travel':('alert', 'insightful', 'searcher'),
70         'enter-dreams':('alert', 'discerning', 'insightful'),
71         'enter-tel\'aran\'rhoid':('healthy', 'tough', 'strong-willed'),
72         'interpret-prophetic-dreams':('discerning', 'insightful', 'analytical'),
73         'manipulate-environment':('alert', 'discerning', 'strong-willed'),
74
75         # Aiel Skills
76         'tactics':('analytical', 'insightful', 'knowlegeable'),
77
78         # Athletic Skills
79         'jump':('athletic', 'energetic', 'fierce'),
80         'run':('athletic', 'energetic', 'quick'),
81         'swim':('athletic', 'energetic', 'quick'),
82         'throw-object':('broadshouldered', 'fierce', 'targeteer'),
83         'acrobatics':('broadshouldered', 'fierce', 'targeteer'),
84
85         # Noble
86         'gaming':('analytical', 'discerning', 'experienced'),
87         'nobel-lore': ('elegant', 'experienced', 'recall'),
88         'hawking':('alert', 'commanding', 'discerning'),
89         'history':('insightful', 'knowlegeable', 'recall'),
90         'daes-dae\'mar':('enigmatic', 'insightful', 'sly'),
91         'heraldry':('discerning', 'knowlegeable', 'recall'),
92
93         # Rural
94         'animal-lore':('experienced', 'knowlegable', 'recall'),
95         'bargain':('astute', 'empathetic', 'stron-willed'),
96         'fishing':('alert', 'agile', 'searcher'),
97         'gambling':('alert', 'experienced', 'supernumerate'),
98         'hunting':('alert', 'discerning', 'insightful'),
99         'lifting':('athletic', 'broadshouldered', 'energetic'),
100         'ride-animal':('agile', 'athletic', 'experienced'),
101         'rural-stealth':('athletic', 'discerning', 'lithe'),
102         'survival':('alert', 'healthy', 'searcher'),
103         'agriculture':('alert', 'energetic', 'experienced'),
104         'animal-handling':('commanding', 'empathetic', 'experienced'),
105         'cooking':('creative', 'insightful', 'precise'),
106         'healing':('discerning', 'experienced', 'recall'),
107         'tracking':('analytical', 'discerning', 'searcher'),
108         'trapping':('experienced', 'mechanically-inclined', 'searcher'),
109         'herbalism':('discerning', 'experienced', 'searcher'),
110
111         # Social
112         'carouse':('energetic', 'entertaining', 'witty'),
113         'con':('energetic', 'persuasive', 'sly'),
114         'consume-alcohol':('healthy', 'robust', 'strong-willed'),
115         'first-impression':('alluring', 'elegant', 'handsome', 'presence'),
116         'gossip':('adaptive', 'enigmatic', 'persuasive', 'sly'),
117         'grovel':('adaptive', 'enigmatic', 'sly'),
118         'interrogate':('commanding', 'empathetic', 'strong-willed'),
119         'intimidate':('commanding', 'fierce', 'strong-willed'),
120         'leadership':('charismatic', 'commanding', 'eloquent'),
121         'oratory':('charismatic', 'eloquent', 'empathetic'),
122         'persuade':('charismatic', 'empathetic', 'persuasive'),
123         'read-emotions':('empathetic', 'enigmatic', 'experienced'),
124         'seduce':('alluring', 'handsome', 'presence'),
125         'taunt':('eloquent', 'empathetic', 'witty'),
126         'etiquette':('adaptive', 'elegant', 'recall'),
127         'languages':('expressive', 'knowlegable', 'recall'),
128
129         # Unarmed Combat
130         'dodge':('alert', 'lithe', 'targeteer'),
131         'kick':('athletic', 'fierce', 'wiry'),
132         'punch':('agile', 'brawny', 'fierce'),
133         'throw':('agile', 'targeteer', 'wiry'),
134         'wrestle':('athletic', 'high-pain-threshold', 'wiry'),
135         'aiel-martial-arts':('athletic', 'lithe', 'wiry'),
136
137         # Vigilance
138         'hear-noise':('alert', 'analytical', 'discerning'),
139         'notice':('alert', 'discerning', 'insightful'),
140         'pierce-disguise':('alert', 'discerning', 'enigmatic'),
141         'read-emotions':('empathetic', 'enigmatic', 'experienced'),
142         'search':('discerning', 'insightful', 'searcher'),
143         'sense-deception':('empathetic', 'enigmatic', 'sly'),
144         'vigilance':('alert', 'energetic', 'precise'),
145
146         # Water
147         'climb':('agile', 'athletic', 'energetic'),
148         'lifting':('athletic', 'broadshouldered', 'energetic'),
149         'swim':('athletic', 'energetic', 'quick'),
150         'boating':('agile', 'alert', 'athletic'),
151         'coast/port-knowledge':('experienced', 'knowlegeable', 'recall'),
152         'river/rivertown-knowledge':('experienced', 'knowlegeable', 'recall'),
153         'boat-making':('astute', 'experienced', 'mechanically-inclined'),
154
155         # Missile Weapon
156         'crossbow':('agile', 'alert', 'targeteer'),
157         'longbow':('agile', 'alert', 'targeteer'),
158         'shortbow':('agile', 'alert', 'targeteer'),
159         'sling':('agile', 'alert', 'targeteer'),
160
161         # Thrown weapons
162         'dagger-thrown':('agile', 'alert', 'targeteer'),
163         'handaxe-thrown':('agile', 'alert', 'targeteer'),
164         'spear-thrown':('agile', 'alert', 'targeteer'),
165
166         # Hand Weapons
167         'battle-axe':('brawny', 'broadshouldered', 'fierce'),
168         'dagger':('agile', 'brawny', 'quick'),
169         'flail':('agile', 'brawny', 'quick'),
170         'garotte':('nimble', 'quick', 'wiry'),
171         'hand-axe':('agile', 'brawny', 'quick'),
172         'hammer':('brawny', 'broadshouldered', 'fierce'),
173         'lance':('brawny', 'broadshouldered', 'tough'),
174         'mace':('brawny', 'broadshouldered', 'fierce'),
175         'pole-arm':('agile', 'brawny', 'broadshouldered'),
176         'spear/staff':('agile', 'brawny', 'quick'),
177         'sword':('agile', 'brawny', 'quick'),
178         }
179
180     SkillLevel = [
181         (0, 'Unskilled'),
182         (1, 'Apprentice'),
183         (4, 'Journeyman'),
184         (10, 'Master'),
185         (20, 'Grand Master'),
186         (35, 'Heroic Skill'),
187         (56, 'Local Legend'),
188         (84, 'Age Legend')]
189
190     naturaltalent = 0
191
192     def __init__(self, skillkey, points):
193         self.skillkey = skillkey.replace(' ', '-').lower()
194         self.points = points
195         self.getTraits()
196
197     def __repr__(self):
198         return "<%s: %r %s (%d | %1.1f%s)>" % (self.__class__.__name__, self.skillkey, self.getTitle(), self.getLevel(), self.points, self.naturaltalent and ' natural tallent' or '')
199
200     def getEffectiveLevel(self, traits):
201         return self.getLevel() + sum(filter(None, map(traits.get, self.getTraits())))
202
203     def getLevel(self):
204         pts = self.points * (1 + self.naturaltalent)
205         return bisect.bisect_right(self.SkillLevel, (pts, ())) - 1
206
207     def getTitle(self):
208         return self.SkillLevel[self.getLevel()][1]
209
210     def getLevelAndTitle(self):
211         level = self.getLevel()
212         return level, self.SkillLevel[level][1]
213
214     def getTraits(self):
215         return self.TraitDependencyTable[self.skillkey]
216
217     def incrementBy(self, skill):
218         assert self.skillkey == skill.skillkey
219         self.points += skill.points
220
221     def skillTraitReferenceCounts(self, counts=None, weighted=False):
222         if counts is None: counts = {}
223         for trait in self.getTraits():
224             if weighted:
225                 r = counts.setdefault(trait, [0])
226                 r[0] += self.points**2 #self.getLevel()**2
227                 r.append(self.skillkey)
228                 counts[trait] = r
229             else:
230                 counts[trait] = counts.get(trait, 0) + 1
231         return counts
232    
233 def HobbySkill(*args, **kw):
234     skill = Skill(*args, **kw)
235     skill.hobby = True
236     return skill
237
238 def DreamSkill(*args, **kw):
239     skill = HobbySkill(*args, **kw)
240     return skill
241
242 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
243 #~ Character
244 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
245
246 class Character:
247     def __init__(self):
248         self.skills = {}
249         self.traits = {}
250         self.attributes = []
251         self.equipment = []
252         self.money = 0 # in silver shillings
253         self.age = 13
254
255     def addEquipment(self, equipment, cost, description=''):
256         assert cost <= self.money
257         self.money -= cost
258         self.equipment.append((cost, equipment, description))
259
260     def addTrait(self, trait, value=1):
261         trait = trait.replace(' ', '-').lower()
262         assert trait in Traits.Table
263         self.traits[trait] = self.traits.get(trait, 0) + value
264         return trait
265     def addTraits(self, traits, value=1):
266         for trait in traits:
267             self.addTrait(trait, value)
268
269     def addAttribute(self, attr):
270         attr = attr.replace(' ', '-').lower()
271         self.attributes.append(attr)
272         return attr
273     def addAttributes(self, attrs):
274         map(self.addAttribute, attrs)
275
276     def getStat(self, statkey):
277         return Stat(statkey)
278
279     def getSkill(self, skillkey):
280         try:
281             return self.skills[skillkey]
282         except KeyError:
283             return self.addSkill(Skill(skillkey, 0))
284
285     def addSkill(self, skill):
286         if skill.skillkey in self.skills:
287             self.skills[skill.skillkey].incrementBy(skill)
288         else:
289             self.skills[skill.skillkey] = skill
290         return skill
291
292     def addSkills(self, skills):
293         map(self.addSkill, skills)
294
295     def addYear(self, occupation, income, skills):
296         self.age += 1
297         self.money += income
298         # TODO: go through skills to make sure that they are "within budget"
299         self.addSkills(skills)
300
301     def skillTraitReferenceCounts(self, counts=None, weighted=False):
302         if counts is None: counts = {}
303         for skill in self.skills.values():
304             skill.skillTraitReferenceCounts(counts, weighted)
305         return counts
306
307     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
308     #~ Debug printing
309     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
310
311     def printSkills(self):
312         skillformat = '%22s %d (base:%d %s)'
313         items = [(s.getEffectiveLevel(self.traits), s.getLevel(), s.points, n, s) for n, s in self.skills.items()]
314         items.sort()
315         items.reverse()
316         print "Skills"
317         for skill in items:
318             skill = skill[-1]
319             print skillformat%(
320                     skill.skillkey,
321                     skill.getEffectiveLevel(self.traits),
322                     skill.getLevel(),
323                     ' '.join(['%s:%d'%(t, self.traits[t]) for t in skill.getTraits() if t in self.traits]),
324                     )
325         print
326
327     def printUsedTraits(self, filter=False):
328         traitformat = '%-8s %d %3d %-16s %s'
329         usedtraits = self.skillTraitReferenceCounts(weighted=True)
330         items = [(Traits.Table[t], t in self.traits, c[0], repr(t), ','.join(c[1:])) for t, c in usedtraits.items() if not filter or t not in char.traits]
331         items.sort()
332         items.reverse()
333         print "Traits weighted by use:"
334         for each in items:
335             print traitformat%each
336         print
337
338     def printUnusedTraits(self):
339         traitformat = '%-8s %d %3d %-16s %s'
340         usedtraits = self.skillTraitReferenceCounts(weighted=True)
341         unusedtraits = []
342         for trait in Traits.Table.keys():
343             c = usedtraits.get(trait, [0])
344             item = (Traits.Table[trait], trait in self.traits, c[0], repr(trait), ','.join(c[1:]))
345             if c[0] < 10:
346                 unusedtraits.append(item)
347         unusedtraits.sort()
348         unusedtraits.reverse()
349         print "Unused traits:"
350         for item in unusedtraits:
351             print traitformat%item
352         print
353
Note: See TracBrowser for help on using the browser.