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.

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