212
|
1 #!/usr/bin/env python
|
|
2 # Copyright (C) 2000-2010 The OpenRPG Project
|
|
3 #
|
|
4 # openrpg-dev@lists.sourceforge.net
|
|
5 #
|
|
6 # This program is free software; you can redistribute it and/or modify
|
|
7 # it under the terms of the GNU General Public License as published by
|
|
8 # the Free Software Foundation; either version 2 of the License, or
|
|
9 # (at your option) any later version.
|
|
10 #
|
|
11 # This program is distributed in the hope that it will be useful,
|
|
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14 # GNU General Public License for more details.
|
|
15 #
|
|
16 # You should have received a copy of the GNU General Public License
|
|
17 # along with this program; if not, write to the Free Software
|
|
18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
19 # --
|
|
20 #
|
|
21 # File: InterParse.py
|
|
22 # Author:
|
|
23 # Maintainer: Tyler Starke (Traipse)
|
|
24 # Version:
|
|
25 # $Id: InterParse.py,v Traipse 'Ornery-Orc' prof.ebral Exp $
|
|
26 #
|
|
27 # Description: InterParse = Interpretor Parser. This class parses all of the node referencing.
|
|
28 #
|
|
29
|
191
|
30 from orpg.orpgCore import component
|
|
31 import re
|
|
32 from orpg.tools.orpg_log import logger
|
194
|
33 from wx import TextEntryDialog, ID_OK
|
212
|
34 from xml.etree.ElementTree import iselement
|
191
|
35
|
|
36 class InterParse():
|
|
37
|
|
38 def __init__(self):
|
|
39 pass
|
|
40
|
212
|
41 def Post(self, s, tab=False, send=False, myself=False):
|
|
42 if not tab: tab = component.get('chat')
|
|
43 s = self.Normalize(s, tab)
|
|
44 tab.set_colors()
|
|
45 tab.Post(s, send, myself)
|
191
|
46
|
245
|
47 def ParseLogic(self, s, node=None):
|
212
|
48 'Nodes now parse through ParsLogic. Easily add new parse rules right here!!'
|
245
|
49 if not node: s = self.NameSpaceE(s)
|
212
|
50 s = self.NameSpaceI(s, node)
|
238
|
51 #s = self.NodeMap(s, node)
|
|
52 #s = self.NodeParent(s, node)
|
212
|
53 return s
|
|
54
|
242
|
55 def Normalize(self, s, tab=False):
|
|
56 if not tab: tab = component.get('chat')
|
212
|
57 for plugin_fname in tab.activeplugins.keys():
|
|
58 plugin = tab.activeplugins[plugin_fname]
|
191
|
59 try: s = plugin.pre_parse(s)
|
|
60 except Exception, e:
|
|
61 if str(e) != "'module' object has no attribute 'post_msg'":
|
212
|
62 #logger.general(traceback.format_exc())
|
191
|
63 logger.general("EXCEPTION: " + str(e))
|
212
|
64 if tab.parsed == 0:
|
|
65 s = self.NameSpaceE(s)
|
191
|
66 s = self.Node(s)
|
|
67 s = self.Dice(s)
|
212
|
68 s = self.Filter(s, tab)
|
|
69 tab.parsed = 1
|
191
|
70 return s
|
|
71
|
212
|
72 def Filter(self, s, tab):
|
|
73 s = tab.GetFilteredText(s)
|
191
|
74 return s
|
|
75
|
|
76 def Node(self, s):
|
|
77 """Parses player input for embedded nodes rolls"""
|
|
78 cur_loc = 0
|
|
79 #[a-zA-Z0-9 _\-\.]
|
|
80 reg = re.compile("(!@(.*?)@!)")
|
|
81 matches = reg.findall(s)
|
|
82 for i in xrange(0,len(matches)):
|
|
83 newstr = self.Node(self.resolve_nodes(matches[i][1]))
|
|
84 s = s.replace(matches[i][0], newstr, 1)
|
|
85 return s
|
|
86
|
|
87 def Dice(self, s):
|
|
88 """Parses player input for embedded dice rolls"""
|
|
89 reg = re.compile("\[([^]]*?)\]")
|
|
90 matches = reg.findall(s)
|
|
91 for i in xrange(0,len(matches)):
|
|
92 newstr = self.Unknown(matches[i])
|
|
93 qmode = 0
|
|
94 newstr1 = newstr
|
|
95 if newstr[0].lower() == 'q':
|
|
96 newstr = newstr[1:]
|
|
97 qmode = 1
|
|
98 if newstr[0].lower() == '#':
|
|
99 newstr = newstr[1:]
|
|
100 qmode = 2
|
|
101 try: newstr = component.get('DiceManager').proccessRoll(newstr)
|
|
102 except: pass
|
|
103 if qmode == 1:
|
|
104 s = s.replace("[" + matches[i] + "]",
|
|
105 "<!-- Official Roll [" + newstr1 + "] => " + newstr + "-->" + newstr, 1)
|
|
106 elif qmode == 2:
|
|
107 s = s.replace("[" + matches[i] + "]", newstr[len(newstr)-2:-1], 1)
|
|
108 else: s = s.replace("[" + matches[i] + "]",
|
|
109 "[" + newstr1 + "<!-- Official Roll -->] => " + newstr, 1)
|
|
110 return s
|
|
111
|
|
112 def Unknown(self, s):
|
|
113 # Uses a tuple. Usage: ?Label}dY. If no Label is assigned then use ?}DY
|
|
114 newstr = "0"
|
|
115 reg = re.compile("(\?\{*)([a-zA-Z ]*)(\}*)")
|
|
116 matches = reg.findall(s)
|
|
117 for i in xrange(0,len(matches)):
|
|
118 lb = "Replace '?' with: "
|
|
119 if len(matches[i][0]):
|
|
120 lb = matches[i][1] + "?: "
|
212
|
121 dlg = TextEntryDialog(component.get('chat'), lb, "Missing Value?")
|
191
|
122 dlg.SetValue('')
|
|
123 if matches[i][0] != '':
|
|
124 dlg.SetTitle("Enter Value for " + matches[i][1])
|
194
|
125 if dlg.ShowModal() == ID_OK: newstr = dlg.GetValue()
|
191
|
126 if newstr == '': newstr = '0'
|
|
127 s = s.replace(matches[i][0], newstr, 1).replace(matches[i][1], '', 1).replace(matches[i][2], '', 1)
|
|
128 dlg.Destroy()
|
|
129 return s
|
|
130
|
212
|
131 def LocationCheck(self, node, tree_map, new_map, find):
|
|
132 if node == 'Invalid Reference!': return node
|
|
133 namespace = node.getiterator('nodehandler'); tr = tree_map.split('::')
|
|
134 newstr = ''
|
|
135 for name in namespace:
|
|
136 try: t = new_map.index(name.get('name'))-1
|
|
137 except: t = 0
|
|
138 if find[0] == name.get('name'):
|
|
139 s = '::'.join(new_map[:len(tr)-t])+'::'+'::'.join(find)
|
|
140 newstr = self.NameSpaceE('!&' +s+ '&!')
|
|
141 break
|
|
142 if newstr != '': return newstr
|
|
143 else:
|
|
144 del new_map[len(new_map)-1]
|
|
145 node = self.get_node(new_map)
|
|
146 newstr = self.LocationCheck(node, tree_map, new_map, find)
|
|
147 return newstr
|
|
148
|
|
149 def FutureCheck(self, node, next):
|
|
150 future = node.getiterator('nodehandler')
|
|
151 for advance in future:
|
|
152 if next == advance.get('name'): return True
|
|
153 return False
|
|
154
|
|
155 def NameSpaceI(self, s, node):
|
222
|
156 reg1 = re.compile('(!"(.*?)"!)') ## Easter Egg!
|
|
157 """If you found this you found my first easter egg. I was tired of people telling me multiple
|
|
158 references syntax for the game tree is confusing, so I dropped this in there without telling
|
|
159 anyone. Using !" :: "! will allow you to use an internal namespace from within another internal
|
|
160 namespace -- TaS, Prof. Ebral"""
|
|
161 reg2 = re.compile("(!=(.*?)=!)")
|
238
|
162 """Adding the Parent and Child references to Namespace Internal. Namespace 2.0 is powerful enough it
|
|
163 should be able to handle them with no problem. For future reference, if you are paying attention, Namespace
|
243
|
164 will include two methods for Internal and External. !@ :: @! and !& :: &! for External and !" :: "! and != :: =!
|
238
|
165 for Internal. See above Easter Egg for reasoning."""
|
|
166 reg3 = re.compile("(!!(.*?)!!)")
|
|
167 reg4 = re.compile("(!#(.*?)#!)")
|
|
168 matches = reg1.findall(s) + reg2.findall(s) + reg3.findall(s) + reg4.findall(s)
|
236
|
169 try: tree_map = node.get('map')
|
|
170 except: return node
|
212
|
171 for i in xrange(0,len(matches)):
|
|
172 ## Build the new tree_map
|
|
173 new_map = tree_map.split('::')
|
245
|
174 if new_map == ['']: new_map = [node.get('name')]
|
212
|
175 find = matches[i][1].split('::')
|
|
176 ## Backwards Reference the Parent Children
|
|
177 node = self.get_node(new_map)
|
|
178 newstr = self.LocationCheck(node, tree_map, new_map, find)
|
|
179 s = s.replace(matches[i][0], newstr, 1)
|
238
|
180 s = s.replace(u'\xa0', ' ')
|
245
|
181 s = self.NameSpaceI(s, node)
|
212
|
182 return s
|
|
183
|
243
|
184 def NameSpaceXE(self, s):
|
|
185 reg = re.compile("(!&(.*?)&!)")
|
|
186 matches = reg.findall(s)
|
|
187 nodeable = ['rpg_grid_handler', 'container_handler',
|
|
188 'group_handler', 'tabber_handler',
|
|
189 'splitter_handler', 'form_handler', 'textctrl_handler']
|
|
190
|
|
191 for i in xrange(0,len(matches)):
|
|
192 find = matches[i][1].split('::')
|
|
193 node = component.get('tree').xml_root
|
|
194 for x in xrange(0, len(find)):
|
|
195 namespace = node.getiterator('nodehandler')
|
|
196 for node in namespace:
|
|
197 if find[x] == node.get('name'):
|
|
198 if node.get('class') not in nodeable: continue
|
|
199 try:
|
|
200 if self.FutureCheck(node, find[x+1]): break
|
|
201 else: continue
|
|
202 except:
|
|
203 if x == len(find)-1:
|
|
204 return node
|
|
205 break
|
|
206 else: break
|
|
207 return None
|
|
208
|
212
|
209 def NameSpaceE(self, s):
|
|
210 reg = re.compile("(!&(.*?)&!)")
|
|
211 matches = reg.findall(s)
|
|
212 newstr = False
|
|
213 nodeable = ['rpg_grid_handler', 'container_handler',
|
|
214 'group_handler', 'tabber_handler',
|
|
215 'splitter_handler', 'form_handler', 'textctrl_handler']
|
|
216 for i in xrange(0,len(matches)):
|
|
217 find = matches[i][1].split('::')
|
|
218 node = component.get('tree').xml_root
|
|
219 if not iselement(node):
|
|
220 s = s.replace(matches[i][0], 'Invalid Reference!', 1);
|
|
221 s = self.NameSpaceE(s)
|
|
222 return s
|
|
223 for x in xrange(0, len(find)):
|
|
224 namespace = node.getiterator('nodehandler')
|
|
225 for node in namespace:
|
|
226 if find[x] == node.get('name'):
|
|
227 if node.get('class') not in nodeable: continue
|
|
228 if node.get('class') == 'rpg_grid_handler':
|
|
229 try: newstr = self.NameSpaceGrid(find[x+1], node); break
|
|
230 except: newstr = 'Invalid Grid Reference!'
|
|
231 try:
|
|
232 if self.FutureCheck(node, find[x+1]): break
|
|
233 else: continue
|
|
234 except:
|
|
235 if x == len(find)-1:
|
|
236 if node.find('text') != None: newstr = str(node.find('text').text)
|
|
237 else: newstr = 'Invalid Reference!'
|
|
238 break
|
|
239 else: break
|
|
240 if not newstr: newstr = 'Invalid Reference!'
|
|
241 s = s.replace(matches[i][0], newstr, 1)
|
238
|
242 s = s.replace(u'\xa0', ' ') #Required for XSLT sheets
|
212
|
243 s = self.ParseLogic(s, node)
|
|
244 return s
|
|
245
|
|
246 def NameSpaceGrid(self, s, node):
|
|
247 cell = tuple(s.strip('(').strip(')').split(','))
|
|
248 grid = node.find('grid')
|
|
249 rows = grid.findall('row')
|
|
250 try:
|
|
251 col = rows[int(self.Dice(cell[0]))-1].findall('cell')
|
|
252 s = self.ParseLogic(col[int(self.Dice(cell[1]))-1].text, node) or 'No Cell Data'
|
|
253 except: s = 'Invalid Grid Reference!'
|
|
254 return s
|
|
255
|
191
|
256 def NodeMap(self, s, node):
|
|
257 """Parses player input for embedded nodes rolls"""
|
|
258 cur_loc = 0
|
|
259 reg = re.compile("(!!(.*?)!!)")
|
|
260 matches = reg.findall(s)
|
|
261 for i in xrange(0,len(matches)):
|
|
262 tree_map = node.get('map')
|
238
|
263 tree_map = str(tree_map + '::' + matches[i][1])
|
|
264 if tree_map[:2] == '::': tree_map = tree_map[2:]
|
|
265 newstr = '!@'+ str(tree_map) +'@!'
|
191
|
266 s = s.replace(matches[i][0], newstr, 1)
|
|
267 s = self.Node(s)
|
238
|
268 s = self.NodeParent(s, node)
|
191
|
269 return s
|
|
270
|
236
|
271 def NodeParent(self, s, node):
|
191
|
272 """Parses player input for embedded nodes rolls"""
|
236
|
273 if node == 'Invalid Reference!': return node
|
|
274 tree_map = node.get('map')
|
191
|
275 cur_loc = 0
|
|
276 reg = re.compile("(!#(.*?)#!)")
|
|
277 matches = reg.findall(s)
|
|
278 for i in xrange(0,len(matches)):
|
|
279 ## Build the new tree_map
|
|
280 new_map = tree_map.split('::')
|
|
281 del new_map[len(new_map)-1]
|
|
282 parent_map = matches[i][1].split('::')
|
|
283 ## Backwards Reference the Parent Children
|
212
|
284 child_node = self.get_node(new_map)
|
191
|
285 newstr = self.get_root(child_node, tree_map, new_map, parent_map)
|
|
286 s = s.replace(matches[i][0], newstr, 1)
|
|
287 s = self.Node(s)
|
|
288 return s
|
|
289
|
|
290 def get_root(self, child_node, tree_map, new_map, parent_map):
|
|
291 if child_node == 'Invalid Reference!': return child_node
|
|
292 roots = child_node.getchildren(); tr = tree_map.split('::')
|
|
293 newstr = ''
|
|
294 for root in roots:
|
|
295 try: t = new_map.index(root.get('name'))
|
|
296 except: t = 1
|
|
297 if parent_map[0] == root.get('name'):
|
|
298 newstr = '!@' + '::'.join(new_map[:len(tr)-t]) + '::' + '::'.join(parent_map) + '@!'
|
|
299 if newstr != '': return newstr
|
|
300 else:
|
|
301 del new_map[len(new_map)-1]
|
212
|
302 child_node = self.get_node(new_map)
|
191
|
303 newstr = self.get_root(child_node, tree_map, new_map, parent_map)
|
|
304 return newstr
|
|
305
|
212
|
306 def get_node(self, path):
|
191
|
307 return_node = 'Invalid Reference!'
|
|
308 value = ""
|
|
309 depth = len(path)
|
|
310 try: node = component.get('tree').tree_map[path[0]]['node']
|
|
311 except Exception, e: return return_node
|
|
312 return_node = self.resolve_get_loop(node, path, 1, depth)
|
|
313 return return_node
|
|
314
|
|
315 def resolve_get_loop(self, node, path, step, depth):
|
|
316 if step == depth: return node
|
|
317 else:
|
212
|
318 child_list = node.getchildren()
|
191
|
319 for child in child_list:
|
|
320 if step == depth: break
|
|
321 if child.get('name') == path[step]:
|
|
322 node = self.resolve_get_loop(child, path, step+1, depth)
|
|
323 return node
|
|
324
|
|
325 def resolve_nodes(self, s):
|
|
326 self.passed = False
|
|
327 string = 'Invalid Reference!'
|
|
328 value = ""
|
|
329 path = s.split('::')
|
|
330 depth = len(path)
|
|
331 try: node = component.get('tree').tree_map[path[0]]['node']
|
|
332 except Exception, e: return string
|
|
333 if node.get('class') in ('dnd35char_handler',
|
|
334 "SWd20char_handler",
|
|
335 "d20char_handler",
|
|
336 "dnd3echar_handler"): string = self.resolve_cust_loop(node, path, 1, depth)
|
|
337 elif node.get('class') == 'rpg_grid_handler': self.resolve_grid(node, path, 1, depth)
|
|
338 else: string = self.resolve_loop(node, path, 1, depth)
|
|
339 return string
|
|
340
|
|
341 def resolve_loop(self, node, path, step, depth):
|
|
342 if step == depth: return self.resolution(node)
|
|
343 else:
|
|
344 child_list = node.findall('nodehandler')
|
|
345 for child in child_list:
|
|
346 if step == depth: break
|
|
347 if child.get('name') == path[step]:
|
|
348 node = child
|
|
349 step += 1
|
|
350 if node.get('class') in ('dnd35char_handler',
|
|
351 "SWd20char_handler",
|
|
352 "d20char_handler",
|
|
353 "dnd3echar_handler"):
|
|
354 string = self.resolve_cust_loop(node, path, step, depth)
|
|
355 elif node.get('class') == 'rpg_grid_handler':
|
|
356 string = self.resolve_grid(node, path, step, depth)
|
|
357 else: string = self.resolve_loop(node, path, step, depth)
|
|
358 return string
|
|
359
|
|
360 def resolution(self, node):
|
|
361 if self.passed == False:
|
|
362 self.passed = True
|
|
363 if node.get('class') == 'textctrl_handler':
|
|
364 s = str(node.find('text').text)
|
|
365 else: s = 'Nodehandler for '+ node.get('class') + ' not done!' or 'Invalid Reference!'
|
212
|
366 else: s = ''
|
|
367 s = self.ParseLogic(s, node)
|
191
|
368 return s
|
|
369
|
|
370 def resolve_grid(self, node, path, step, depth):
|
|
371 if step == depth:
|
|
372 return 'Invalid Grid Reference!'
|
|
373 cell = tuple(path[step].strip('(').strip(')').split(','))
|
|
374 grid = node.find('grid')
|
|
375 rows = grid.findall('row')
|
|
376 col = rows[int(self.Dice(cell[0]))-1].findall('cell')
|
212
|
377 try: s = self.ParseLogic(col[int(self.Dice(cell[1]))-1].text, node) or 'No Cell Data'
|
191
|
378 except: s = 'Invalid Grid Reference!'
|
|
379 return s
|
|
380
|
|
381 def resolve_cust_loop(self, node, path, step, depth):
|
|
382 s = 'Invalid Reference!'
|
|
383 node_class = node.get('class')
|
|
384 ## Code needs clean up. Either choose .lower() or .title(), then reset the path list's content ##
|
|
385 if step == depth: self.resolution(node)
|
|
386 ##Build Abilities dictionary##
|
|
387 if node_class not in ('d20char_handler', "SWd20char_handler"): ab = node.find('character').find('abilities')
|
|
388 else: ab = node.find('abilities')
|
|
389 ab_list = ab.findall('stat'); pc_stats = {}
|
|
390
|
|
391 for ability in ab_list:
|
|
392 pc_stats[ability.get('name')] = (
|
|
393 str(ability.get('base')),
|
|
394 str((int(ability.get('base'))-10)/2) )
|
|
395 pc_stats[ability.get('abbr')] = (
|
|
396 str(ability.get('base')),
|
|
397 str((int(ability.get('base'))-10)/2) )
|
|
398
|
|
399 if node_class not in ('d20char_handler', "SWd20char_handler"): ab = node.find('character').find('saves')
|
|
400 else: ab = node.find('saves')
|
|
401 ab_list = ab.findall('save')
|
|
402 for save in ab_list:
|
|
403 pc_stats[save.get('name')] = (str(save.get('base')), str(int(save.get('magmod')) + int(save.get('miscmod')) + int(pc_stats[save.get('stat')][1]) ) )
|
|
404 if save.get('name') == 'Fortitude': abbr = 'Fort'
|
|
405 if save.get('name') == 'Reflex': abbr = 'Ref'
|
|
406 if save.get('name') == 'Will': abbr = 'Will'
|
|
407 pc_stats[abbr] = ( str(save.get('base')), str(int(save.get('magmod')) + int(save.get('miscmod')) + int(pc_stats[save.get('stat')][1]) ) )
|
|
408
|
|
409 if path[step].lower() == 'skill':
|
|
410 if node_class not in ('d20char_handler', "SWd20char_handler"): node = node.find('snf')
|
|
411 node = node.find('skills')
|
|
412 child_list = node.findall('skill')
|
|
413 for child in child_list:
|
|
414 if path[step+1].lower() == child.get('name').lower():
|
|
415 if step+2 == depth: s = child.get('rank')
|
|
416 elif path[step+2].lower() == 'check':
|
|
417 s = '<b>Skill Check:</b> ' + child.get('name') + ' [1d20+'+str( int(child.get('rank')) + int(pc_stats[child.get('stat')][1]) )+']'
|
|
418 return s
|
|
419
|
|
420 if path[step].lower() == 'feat':
|
|
421 if node_class not in ('d20char_handler', "SWd20char_handler"): node = node.find('snf')
|
|
422 node = node.find('feats')
|
|
423 child_list = node.findall('feat')
|
|
424 for child in child_list:
|
|
425 if path[step+1].lower() == child.get('name').lower():
|
|
426 if step+2 == depth: s = '<b>'+child.get('name')+'</b>'+': '+child.get('desc')
|
|
427 return s
|
|
428 if path[step].lower() == 'cast':
|
|
429 if node_class not in ('d20char_handler', "SWd20char_handler"): node = node.find('snp')
|
|
430 node = node.find('spells')
|
|
431 child_list = node.findall('spell')
|
|
432 for child in child_list:
|
|
433 if path[step+1].lower() == child.get('name').lower():
|
|
434 if step+2 == depth: s = '<b>'+child.get('name')+'</b>'+': '+child.get('desc')
|
|
435 return s
|
|
436 if path[step].lower() == 'attack':
|
|
437 if node_class not in ('d20char_handler', "SWd20char_handler"): node = node.find('combat')
|
|
438 if path[step+1].lower() == 'melee' or path[step+1].lower() == 'm':
|
|
439 bonus_text = '(Melee)'
|
|
440 bonus = node.find('attacks')
|
|
441 bonus = bonus.find('melee')
|
|
442 bonus = bonus.attrib; d = int(pc_stats['Str'][1])
|
|
443 elif path[step+1].lower() == 'ranged' or path[step+1].lower() == 'r':
|
|
444 bonus_text = '(Ranged)'
|
|
445 bonus = node.find('attacks')
|
|
446 bonus = bonus.find('ranged')
|
|
447 bonus = bonus.attrib; d = int(pc_stats['Dex'][1])
|
|
448 for b in bonus:
|
|
449 d += int(bonus[b])
|
|
450 bonus = str(d)
|
|
451 if path[step+2] == None: s= bonus
|
|
452 else:
|
|
453 weapons = node.find('attacks')
|
|
454 weapons = weapons.findall('weapon')
|
|
455 for child in weapons:
|
|
456 if path[step+2].lower() == child.get('name').lower():
|
|
457 s = '<b>Attack: '+bonus_text+'</b> '+child.get('name')+' [1d20+'+bonus+'] ' + 'Damage: ['+child.get('damage')+']'
|
|
458 return s
|
|
459 elif pc_stats.has_key(path[step].title()):
|
|
460 if step+1 == depth: s = pc_stats[path[step].title()][0] + ' +('+pc_stats[path[step].title()][1]+')'
|
|
461 elif path[step+1].title() == 'Mod': s = pc_stats[path[step].title()][1]
|
|
462 elif path[step+1].title() == 'Check': s = '<b>'+path[step].title()+' Check:</b> [1d20+'+str(pc_stats[path[step].title()][1])+']'
|
|
463 return s
|
|
464 return s
|
|
465
|
|
466 Parse = InterParse()
|