TinTin++ Configs for DiscworldMUD
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

334 lines
13 KiB

  1. #!/usr/bin/env python
  2. import os
  3. import re
  4. import sys
  5. import math
  6. import json
  7. class MapDoorText:
  8. number_map = {
  9. "a ": 1,
  10. "an ": 1,
  11. "the ": 1,
  12. "one ": 1,
  13. "two ": 2,
  14. "three ": 3,
  15. "four ": 4,
  16. "five ": 5,
  17. "six ": 6,
  18. "seven ": 7,
  19. "eight ": 8,
  20. "nine ": 9,
  21. "ten ": 10,
  22. "eleven ": 11,
  23. "twelve ": 12,
  24. "thirteen ": 13,
  25. "fourteen ": 14,
  26. "fifteen ": 15,
  27. "sixteen ": 16,
  28. "seventeen ": 17,
  29. "eighteen ": 18,
  30. "nineteen ": 19,
  31. "twenty ": 20,
  32. "twenty-one ": 21,
  33. "twenty-two ": 22,
  34. "twenty-three ": 23,
  35. "twenty-four ": 24,
  36. "twenty-five ": 25,
  37. "twenty-six ": 26,
  38. "twenty-seven ": 27,
  39. "twenty-eight ": 28,
  40. "twenty-nine ": 29,
  41. "thirty ": 30,
  42. }
  43. direction_map = {
  44. "north": "n",
  45. "northeast": "ne",
  46. "east": "e",
  47. "southeast": "se",
  48. "south": "s",
  49. "southwest": "sw",
  50. "west": "w",
  51. "northwest": "nw",
  52. "n": "n",
  53. "ne": "ne",
  54. "e": "e",
  55. "se": "se",
  56. "s": "s",
  57. "sw": "sw",
  58. "w": "w",
  59. "nw": "nw",
  60. }
  61. colour_map = {
  62. # http://terminal-color-builder.mudasobwa.ru/
  63. "orange": "\033[01;38;05;214m",
  64. "red": "\033[01;38;05;196m",
  65. "reset": "\033[00;39;49m"
  66. }
  67. def __init__(self):
  68. self.return_value = []
  69. self.custom_matches = []
  70. # Load the JSON configuration file
  71. with open('mdtconfig.json', 'r') as config_file:
  72. config = json.load(config_file)
  73. # Strip comments from custom matches
  74. for match in config['custom_matches']:
  75. if len(match) > 1:
  76. self.custom_matches.append(match)
  77. self.default_npc_value = config['default_npc_value']
  78. self.bonus_player_value = config['bonus_player_value']
  79. self.minimum_room_value = config['minimum_room_value']
  80. self.show_hidden_room_count = config['show_hidden_room_count']
  81. @staticmethod
  82. def explode(div, mdt):
  83. if div == '':
  84. return False
  85. pos, fragments = 0, []
  86. for m in re.finditer(div, mdt):
  87. fragments.append(mdt[pos:m.start()])
  88. pos = m.end()
  89. fragments.append(mdt[pos:])
  90. return fragments
  91. def parse_mdt(self, mdt_line):
  92. # Make lower case, do initial replacements
  93. mdt_line = mdt_line.lower()
  94. mdt_line = mdt_line.replace(" are ", " is ")
  95. mdt_line = mdt_line.replace(" black and white ", " black white ")
  96. mdt_line = mdt_line.replace(" brown and white ", " brown white ")
  97. mdt_line = mdt_line.replace("the limit of your vision is ", "the limit of your vision:")
  98. mdt_line = mdt_line.replace(" and ", ", ")
  99. mdt_line = mdt_line.replace(" is ", ", ")
  100. mdt_table = self.explode(', ', mdt_line)
  101. data = {
  102. 'last_direction': '',
  103. 'last_enemy_line': '',
  104. 'last_count': 0,
  105. 'last_was_dir': 0,
  106. 'enemies_by_square': [],
  107. 'ignoring_exits': False,
  108. 'entity_table': [],
  109. 'room_id': 1,
  110. 'room_value': 0,
  111. 'longest_direction': 0,
  112. 'nothing': True,
  113. 'next_color': ''
  114. }
  115. exit_strings = ['doors ', 'a door ', 'exits ', 'an exit ', 'a hard to see through exit ']
  116. for entry in mdt_table:
  117. if entry != "" and ' of a ' not in entry:
  118. if entry.startswith(tuple(exit_strings)):
  119. # print('Special exit, ignore this line? next line is processed...')
  120. data['ignoring_exits'] = True
  121. elif entry.startswith('the limit of your vision:'):
  122. if data['last_count'] > 0:
  123. this_square = [data['last_count'], data['last_direction'], data['room_id'], int(math.floor(data['room_value']))]
  124. data['enemies_by_square'].append(this_square)
  125. data['nothing'] = False
  126. data['next_color'] = ''
  127. data['room_id'] = data['room_id'] + 1
  128. data['room_value'] = 0
  129. data['last_direction'] = ''
  130. data['last_enemy_line'] = ''
  131. data['last_count'] = 0
  132. data['last_was_dir'] = 0
  133. else:
  134. # find the quantity first
  135. quantity = 1
  136. for nm_key in self.number_map:
  137. if entry.startswith(nm_key):
  138. quantity = self.number_map[nm_key]
  139. entry = entry[len(nm_key):]
  140. break
  141. is_direction = 0
  142. this_direction = ''
  143. if entry.startswith("northeast"):
  144. is_direction = 1
  145. this_direction = "northeast"
  146. elif entry.startswith("northwest"):
  147. is_direction = 1
  148. this_direction = "northwest"
  149. elif entry.startswith("southeast"):
  150. is_direction = 1
  151. this_direction = "southeast"
  152. elif entry.startswith("southwest"):
  153. is_direction = 1
  154. this_direction = "southwest"
  155. elif entry.startswith("north"):
  156. is_direction = 1
  157. this_direction = "north"
  158. elif entry.startswith("east"):
  159. is_direction = 1
  160. this_direction = "east"
  161. elif entry.startswith("south"):
  162. is_direction = 1
  163. this_direction = "south"
  164. elif entry.startswith("west"):
  165. is_direction = 1
  166. this_direction = "west"
  167. if is_direction == 1:
  168. if not data['ignoring_exits']:
  169. # print('[handling direction, not exits]')
  170. data['last_was_dir'] = 1
  171. if data['last_direction'] != '':
  172. data['last_direction'] = '{}, '.format(data['last_direction'])
  173. data['last_direction'] = '{}{} {}'.format(
  174. data['last_direction'], quantity, self.direction_map[this_direction]
  175. )
  176. else:
  177. # print('[ignoring exits direction line]')
  178. pass
  179. else:
  180. data['ignoring_exits'] = False
  181. if data['last_was_dir'] == 1:
  182. # reset count
  183. if data['last_count'] > 0:
  184. this_square = [data['last_count'], data['last_direction'], data['room_id'], int(math.floor(data['room_value']))]
  185. data['enemies_by_square'].append(this_square)
  186. data['nothing'] = False
  187. data['next_color'] = ''
  188. data['room_id'] = data['room_id'] + 1
  189. data['room_value'] = 0
  190. data['last_direction'] = ''
  191. data['last_enemy_line'] = ''
  192. data['last_count'] = 0
  193. data['last_was_dir'] = 0
  194. data['next_color'] = ''
  195. add_player_value = False
  196. # Special GMCP MDT colour codes
  197. if entry[0:6] == 'u001b[':
  198. # u001b[38;5;37mRuhsbaaru001b[39;49mu001b[0m
  199. here = entry.index('m')
  200. data['next_color'] = entry[7:here]
  201. # entry = entry[here + 1:-20]
  202. # entry = entry.replace('u001b', '')
  203. entry = entry.replace('u001b', '\033')
  204. # Might be a second colour code for PK
  205. if entry[0:6] == 'u001b[':
  206. here = entry.index('m')
  207. data['next_color'] = entry[7:here]
  208. entry = entry[here + 1:-20]
  209. add_player_value = True
  210. this_value = self.default_npc_value
  211. for custom_match in self.custom_matches:
  212. if custom_match[3]:
  213. # This is a regex match
  214. rexp = re.compile(custom_match[0])
  215. if rexp.match(entry):
  216. if custom_match[1] and custom_match[1] in self.colour_map:
  217. entry = '{}{}{}'.format(
  218. self.colour_map[custom_match[1]],
  219. entry,
  220. self.colour_map['reset']
  221. )
  222. this_value = custom_match[2]
  223. else:
  224. # This is a regular string match
  225. if custom_match[0] in entry:
  226. if custom_match[1] and custom_match[1] in self.colour_map:
  227. entry = '{}{}{}'.format(
  228. self.colour_map[custom_match[1]],
  229. entry,
  230. self.colour_map['reset']
  231. )
  232. this_value = custom_match[2]
  233. if add_player_value == True:
  234. this_value = this_value + self.bonus_player_value
  235. data['room_value'] = data['room_value'] + (this_value * quantity)
  236. if quantity > 1:
  237. entry = '{} {}'.format(quantity, entry)
  238. data['entity_table'].append([data['room_id'], entry, data['next_color']])
  239. data['last_count'] = data['last_count'] + quantity
  240. if data['last_enemy_line'] != '':
  241. data['last_enemy_line'] = '{}, '.format(data['last_enemy_line'])
  242. data['last_enemy_line'] = '{}{}'.format(data['last_enemy_line'], entry)
  243. if data['nothing']:
  244. self.return_value.append('Nothing seen, try elsewhere!')
  245. else:
  246. done_here = False
  247. rooms_ignored = 0
  248. data['enemies_by_square'].sort(key=lambda square: square[3])
  249. # Grab shortest
  250. for square in data['enemies_by_square']:
  251. # Only show if this room meets the minimum value
  252. if square[3] >= self.minimum_room_value and len(square[1]) > data['longest_direction']:
  253. data['longest_direction'] = len(square[1])
  254. for square in data['enemies_by_square']:
  255. # Only show if this room meets the minimum value
  256. if square[3] >= self.minimum_room_value:
  257. done_here = False
  258. fstring = '{{:<{}}} [{{}}] '.format(data['longest_direction'])
  259. output = fstring.format(square[1], square[3])
  260. for entity in data['entity_table']:
  261. if entity[0] == square[2]:
  262. if done_here:
  263. output = '{}, '.format(output)
  264. output = '{}{}'.format(output, entity[1])
  265. done_here = True
  266. if square[0] < 2:
  267. output = '{} [{} thing]'.format(output, square[0])
  268. else:
  269. output = '{} [{} things]'.format(output, square[0])
  270. self.return_value.append(output)
  271. else:
  272. rooms_ignored = rooms_ignored + 1
  273. square = None
  274. for entity in data['entity_table']:
  275. entity = None
  276. if rooms_ignored > 0 and self.show_hidden_room_count:
  277. output = '({} rooms below your value limit of {})'.format(rooms_ignored, self.minimum_room_value)
  278. self.return_value.append(output)
  279. if __name__ == '__main__':
  280. if len(sys.argv) < 2:
  281. print('[error] No input provided.')
  282. sys.exit()
  283. argument, mdt_line = sys.argv.pop(1), None
  284. # Is this a file passed to us?
  285. if os.path.exists(argument):
  286. with open(argument, 'r') as f:
  287. mdt_line = f.readline()
  288. else:
  289. mdt_line = argument
  290. mdt = MapDoorText()
  291. mdt.parse_mdt(mdt_line)
  292. for line in mdt.return_value:
  293. print(line)