root/hodgestar/PythonCode/DmTools/l5rchars.py

Revision 316, 7.8 kB (checked in by simon, 4 years ago)

Support for spells and notes sections.

  • Property svn:mime-type set to text/python-source
  • Property svn:eol-style set to native
Line 
1#!/usr/bin/env python
2
3import re
4import sys
5import logging
6
7class Character(object):
8
9    SECTION_RE = re.compile(r"^(?P<section>\w[\w\s]*):(?P<rest>.*)$")
10    RANK_RE = re.compile(r"^\s*(?P<rank>\d+)\s+\((?P<schools>.*)\)\s*$")
11    RING_RE = re.compile(r"^\s*(?P<ring>\S+)\s+(?P<ring_value>\d+)\s*(\(" \
12                         r"\s*(?P<stat1>\S+)\s+(?P<stat1_value>\d+)\s*;" \
13                         r"\s*(?P<stat2>\S+)\s+(?P<stat2_value>\d+)\s*" \
14                         r"\))?\s*$")
15    SKILL_RE = re.compile(r"^\s*(?P<skill>.+?)\s*(\((?P<emphases>.*)\))?\s*(?P<skill_value>\d+)\s*$")
16    ADVDIS_RE = re.compile(r"^\s*(?P<advdis>.+?)\s*\(\s*(?P<pt>-?\d+)\s*pt\s*;\s*(?P<xp>-?\d+)\s*xp\s*\)\s*$")
17    KATA_RE = re.compile(r"^\s*(?P<kata>.+?)\s*\(\s*(?P<xp>-?\d+)\s*xp\s*\)\s*$")
18
19    def __init__(self):
20        self.name = None
21        self.clan = None
22
23        self.schools = {} # name -> rank
24        self.rings = {} # name -> rank
25        self.stats = {} # name -> rank
26        self.skills = {} # name -> (rank, [emphases])
27        self.advantages = {} # name -> (points, xp)
28        self.kata = {} # name -> xp
29
30    def parse_source(self,sText):
31        sSection = None
32        aSectionsLeft = ["name","clan","rank","rings","skills","adv and dis","kata"]
33        aOptionalSectionsLeft = ["notes","spells"]
34        self.parse_error = None # (line #, line)
35
36        for iLine, sLine in enumerate(sText.split("\n")):
37            self.parse_error = (iLine, sLine)
38
39            oM = self.SECTION_RE.match(sLine)
40            if oM:
41                sSection = oM.group("section").lower().strip()
42                if sSection in aSectionsLeft:
43                    aSectionsLeft.remove(sSection)
44                elif sSection in aOptionalSectionsLeft:
45                    aOptionalSectionsLeft.remove(sSection)
46                else:
47                    raise RuntimeError("Unknown or duplicate section %s." % (sSection,))
48
49                if sSection == "name":
50                    self.name = oM.group("rest").strip()
51                elif sSection == "clan":
52                    self.clan = oM.group("rest").strip()
53                elif sSection == "rank":
54                    oR = self.RANK_RE.match(oM.group("rest"))
55                    for sItem in oR.group("schools").split(";"):
56                        sItem = sItem.strip()
57                        aParts = sItem.split()
58                        sName = " ".join(aParts[:-1])
59                        iRank = int(aParts[-1])
60                        assert sName not in self.schools
61                        self.schools[sName] = iRank
62                    assert int(oR.group("rank")) == sum(self.schools.values())
63
64            elif sSection == "rings" and sLine.strip():
65                oR = self.RING_RE.match(sLine)
66                sRing, iRing = oR.group("ring").lower(), int(oR.group("ring_value"))
67
68                assert sRing not in self.rings
69                self.rings[sRing] = iRing
70
71                if sRing != "void":
72                    sStat1, iStat1 = oR.group("stat1").lower(), int(oR.group("stat1_value"))
73                    sStat2, iStat2 = oR.group("stat2").lower(), int(oR.group("stat2_value"))
74
75                    assert sStat1 not in self.stats
76                    self.stats[sStat1] = iStat1
77
78                    assert sStat2 not in self.stats
79                    self.stats[sStat2] = iStat2
80
81            elif sSection == "skills" and sLine.strip():
82                oR = self.SKILL_RE.match(sLine)
83
84                sSkill, iSkill = oR.group("skill").lower(), int(oR.group("skill_value"))
85                sEmphases = oR.group("emphases")
86                if sEmphases is None:
87                    aEmphases = []
88                else:
89                    aEmphases = [x.strip().lower() for x in sEmphases.split(",")]
90
91                assert sSkill not in self.skills
92                self.skills[sSkill] = (iSkill, aEmphases)
93
94            elif sSection == "adv and dis" and sLine.strip():
95                oR = self.ADVDIS_RE.match(sLine)
96                sAdvDis, iPt, iXp = oR.group("advdis").lower().strip(), int(oR.group("pt")), int(oR.group("xp"))
97
98                assert sAdvDis not in self.advantages
99                self.advantages[sAdvDis] = (iPt, iXp)
100
101            elif sSection == "kata" and sLine.strip():
102                oR = self.KATA_RE.match(sLine)
103                sKata, iXp = oR.group("kata").lower().strip(), int(oR.group("xp"))
104
105                assert sKata not in self.kata
106                self.kata[sKata] = iXp
107
108            elif sSection == "spells" and sLine.strip():
109                pass
110
111            elif sSection == "notes" and sLine.strip():
112                pass
113
114            else:
115                assert not sLine.strip()
116
117        assert not aSectionsLeft
118        del self.parse_error
119
120    def calculate_insight(self):
121        extra_insight = { # name -> [(rank, insight bonus)]
122            'artisan': [(5,2),(10,2)],
123            'games': [(5,2),(10,2)],
124            'instruction': [(5,2),(10,2)],
125            'lore': [(5,2),(10,2)],
126            'meditation': [(7,2)],
127            'storytelling': [(5,2),(10,2)],
128            'theology': [(5,2),(7,2),(10,10)],
129            'craft': [(5,2),(10,2)],
130        }
131
132        def extra_skill_insight(skill,rank):
133            skill = skill.split(':')[0].strip()
134            insight = 0
135            for bonus_rank, bonus in extra_insight.get(skill,[]):
136                if rank >= bonus_rank:
137                    insight += bonus
138            return insight
139
140        # Rings
141        insight = 10 * sum(self.rings.values())
142
143        # Skills
144        insight += sum([rank for rank, emphases in self.skills.values()])
145        insight += 2 * len([rank for rank, emphases in self.skills.values() if rank >= 5])
146        insight += sum([extra_skill_insight(skill,rank) for skill, (rank, emphases) in self.skills.items()])
147
148        return insight
149
150    insight = property(fget=calculate_insight)
151
152    def calculate_xp(self):
153        def stat_xp(skill):
154            return 4 * sum([x for x in range(2+1,skill+1)])
155
156        def skill_xp(skill):
157            return sum([x for x in range(1,skill+1)])
158
159        def emphases_xp(num):
160            return 2 * sum([x for x in range(1,num+1)])
161
162        xp_total = 0
163        xp_breakdown = []
164
165        # Stats
166        xp = sum([stat_xp(rank) for rank in self.stats.values()]) + stat_xp(self.rings['void'])
167        xp_total += xp
168        xp_breakdown.append(("Stats",xp))
169
170        # Skills
171        xp = sum([skill_xp(skill) + emphases_xp(len(emphases))
172                    for skill, emphases in self.skills.values()])
173        xp_total += xp
174        xp_breakdown.append(("Skills",xp))
175
176        # Adv and Dis
177        xp = sum([xp for pt, xp in self.advantages.values()])
178        xp_total += xp
179        xp_breakdown.append(("Adv/Dis",xp))
180
181        # Kata
182        xp = sum([xp for xp in self.kata.values()])
183        xp_total += xp
184        xp_breakdown.append(("Kata",xp))
185
186        return xp_total, xp_breakdown
187
188    xp = property(fget=lambda self: self.calculate_xp()[0])
189    xp_breakdown = property(fget=lambda self: self.calculate_xp()[1])
190
191    def calculate_rank(self):
192        insight = self.insight
193        rank = min((max(0, insight - 125) // 25) + 1, 8)
194        return rank
195
196    rank = property(fget=calculate_rank)
197
198def process_file(sFile):
199    oFile = file(sFile,"rU")
200    try:
201        oC = Character()
202        oC.parse_source(oFile.read())
203    except StandardError, e:
204        iLine, sLine = oC.parse_error
205        logging.error("Exception raised while parsing character file '%s' at line %d." % (sFile, iLine))
206        logging.error("-> %s" % (sLine,))
207        raise
208    finally:
209        oFile.close()
210
211    print "Name:", oC.name
212    print " XP - Total:", oC.xp
213    print "    -", ", ".join(["%s: %d" % (section, xp) for section, xp in oC.xp_breakdown])
214    print " Insight:", oC.insight
215    print " Rank:", oC.rank
216
217def main(aArgs):
218    for sFile in aArgs:
219        process_file(sFile)
220
221if __name__ == "__main__":
222    sys.exit(main(sys.argv[1:]))
Note: See TracBrowser for help on using the browser.