state.py 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. # coding: utf-8
  2. import typing
  3. from _elementtree import Element
  4. from lxml import etree
  5. from synergine2.config import Config
  6. from synergine2.log import get_logger
  7. from opencombat.exception import NotFoundError
  8. from opencombat.simulation.base import TileStrategySimulation
  9. from opencombat.simulation.subject import TileSubject
  10. from opencombat.util import get_class_from_string_path
  11. from opencombat.util import pretty_xml
  12. from opencombat.util import get_text_xml_element
  13. from opencombat.xml import XmlValidator
  14. from opencombat.const import SIDE
  15. from opencombat.const import FLAG
  16. from opencombat.const import SELECTION_COLOR_RGB
  17. class State(object):
  18. def __init__(
  19. self,
  20. config: Config,
  21. state_root: Element,
  22. simulation: TileStrategySimulation,
  23. ) -> None:
  24. self._config = config
  25. self._state_root = state_root
  26. self._subjects = None # type: typing.List[TileSubject]
  27. self._simulation = simulation
  28. @property
  29. def subjects(self) -> typing.List[TileSubject]:
  30. if self._subjects is None:
  31. self._subjects = self._get_subjects()
  32. return self._subjects
  33. def _get_subjects(self) -> typing.List[TileSubject]:
  34. subjects = []
  35. subject_elements = self._state_root.find('subjects').findall('subject')
  36. for subject_element in subject_elements:
  37. subject_class_path = subject_element.find('type').text
  38. subject_class = get_class_from_string_path(
  39. self._config,
  40. subject_class_path,
  41. )
  42. subject = subject_class(self._config, self._simulation)
  43. self._fill_subject(subject, subject_element)
  44. subjects.append(subject)
  45. return subjects
  46. def _fill_subject(
  47. self,
  48. subject: TileSubject,
  49. subject_element: Element,
  50. ) -> None:
  51. subject_properties = {}
  52. subject.position = tuple(
  53. map(
  54. int,
  55. get_text_xml_element(subject_element, 'position').split(','),
  56. ),
  57. )
  58. subject.direction = float(
  59. get_text_xml_element(subject_element, 'direction'),
  60. )
  61. # TODO BS 2018-06-20: Maybe need apply this mode no ?
  62. subject.combat_mode = \
  63. get_text_xml_element(subject_element, 'combat_mode')
  64. properties_element = subject_element.find('properties')
  65. decode_properties_map = self._get_decode_properties_map()
  66. for item_element in properties_element.findall('item'):
  67. key_text = item_element.find('key').text
  68. value_text = item_element.find('value').text
  69. try:
  70. decoded_value = decode_properties_map[key_text](value_text)
  71. except KeyError:
  72. raise NotFoundError(
  73. 'You try to load property "{}" but it is unknown'.format(
  74. key_text,
  75. )
  76. )
  77. subject_properties[key_text] = decoded_value
  78. subject.properties = subject_properties
  79. def _get_decode_properties_map(self) -> typing.Dict[str, typing.Callable[[str], typing.Any]]: # nopep8
  80. return {
  81. SELECTION_COLOR_RGB: lambda v: tuple(map(int, v.split(','))),
  82. FLAG: str,
  83. SIDE: str,
  84. }
  85. class StateDumper(object):
  86. def __init__(
  87. self,
  88. config: Config,
  89. simulation: TileStrategySimulation,
  90. ) -> None:
  91. self._logger = get_logger('StateDumper', config)
  92. self._config = config
  93. self._simulation = simulation
  94. state_template = self._config.resolve(
  95. 'global.state_template',
  96. 'opencombat/state_template.xml',
  97. )
  98. with open(state_template, 'r') as xml_file:
  99. template_str = xml_file.read()
  100. parser = etree.XMLParser(remove_blank_text=True)
  101. self._state_root = etree.fromstring(
  102. template_str.encode('utf-8'),
  103. parser,
  104. )
  105. self._state_root_filled = False
  106. def get_state_dump(self) -> str:
  107. if not self._state_root_filled:
  108. self._fill_state_root()
  109. return pretty_xml(
  110. etree.tostring(
  111. self._state_root,
  112. ).decode('utf-8'),
  113. )
  114. def _fill_state_root(self) -> None:
  115. subjects_element = self._state_root.find('subjects')
  116. map_element = self._state_root.find('map')
  117. map_name_element = etree.SubElement(map_element, 'name')
  118. map_name_element.text = self._config.resolve('_runtime.map_dir_path')
  119. for subject in self._simulation.subjects:
  120. subject_element = etree.SubElement(subjects_element, 'subject')
  121. position_element = etree.SubElement(subject_element, 'type')
  122. position_element.text = '.'.join([
  123. subject.__module__,
  124. subject.__class__.__name__,
  125. ])
  126. position_element = etree.SubElement(subject_element, 'position')
  127. position_element.text = ','.join(map(str, subject.position))
  128. direction_element = etree.SubElement(subject_element, 'direction')
  129. direction_element.text = str(subject.direction)
  130. combat_mode_element = etree.SubElement(
  131. subject_element,
  132. 'combat_mode',
  133. )
  134. combat_mode_element.text = str(subject.combat_mode)
  135. properties_element = etree.SubElement(
  136. subject_element,
  137. 'properties',
  138. )
  139. encode_properties_map = self._get_encode_properties_map()
  140. for key, value in subject.properties.items():
  141. item_element = etree.SubElement(properties_element, 'item')
  142. key_element = etree.SubElement(item_element, 'key')
  143. value_element = etree.SubElement(item_element, 'value')
  144. key_element.text = str(key)
  145. value_element.text = encode_properties_map[key](value)
  146. self._state_root_filled = True
  147. def _get_encode_properties_map(self) -> typing.Dict[str, typing.Callable[[typing.Any], str]]: # nopep8:
  148. return {
  149. SELECTION_COLOR_RGB: lambda v: ','.join(map(str, v)),
  150. FLAG: str,
  151. SIDE: str,
  152. }
  153. class StateLoader(object):
  154. def __init__(
  155. self,
  156. config: Config,
  157. simulation: TileStrategySimulation,
  158. ) -> None:
  159. self._logger = get_logger('StateLoader', config)
  160. self._config = config
  161. self._simulation = simulation
  162. schema_file_path = self._config.get(
  163. 'global.state_schema',
  164. 'opencombat/state.xsd',
  165. )
  166. self._xml_validator = XmlValidator(
  167. config,
  168. schema_file_path,
  169. )
  170. def get_state(
  171. self,
  172. state_file_path: str,
  173. ) -> State:
  174. return State(
  175. self._config,
  176. self._validate_and_return_state_element(state_file_path),
  177. self._simulation,
  178. )
  179. def _validate_and_return_state_element(
  180. self,
  181. state_file_path: str,
  182. ) -> Element:
  183. return self._xml_validator.validate_and_return(state_file_path)
  184. class StateConstructorBuilder(object):
  185. def __init__(
  186. self,
  187. config: Config,
  188. simulation: TileStrategySimulation,
  189. ) -> None:
  190. self._logger = get_logger('StateConstructorBuilder', config)
  191. self._config = config
  192. self._simulation = simulation
  193. def get_state_loader(
  194. self,
  195. ) -> StateLoader:
  196. class_address = self._config.resolve(
  197. 'global.state_loader',
  198. 'opencombat.state.StateLoader',
  199. )
  200. state_loader_class = get_class_from_string_path(
  201. self._config,
  202. class_address,
  203. )
  204. return state_loader_class(
  205. self._config,
  206. self._simulation,
  207. )
  208. def get_state_dumper(
  209. self,
  210. ) -> StateDumper:
  211. class_address = self._config.resolve(
  212. 'global.state_dumper',
  213. 'opencombat.state.StateDumper',
  214. )
  215. state_loader_class = get_class_from_string_path(
  216. self._config,
  217. class_address,
  218. )
  219. return state_loader_class(
  220. self._config,
  221. self._simulation,
  222. )