move.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. # coding: utf-8
  2. import time
  3. import typing
  4. from synergine2.simulation import SubjectBehaviour, SubjectMechanism
  5. from synergine2.simulation import Event
  6. from synergine2_xyz.move.intention import MoveToIntention
  7. from synergine2_xyz.simulation import XYZSimulation
  8. from synergine2_xyz.utils import get_angle
  9. from opencombat.const import COLLECTION_ALIVE
  10. from opencombat.user_action import UserAction
  11. class SubjectStartRotationEvent(Event):
  12. def __init__(
  13. self,
  14. subject_id: int,
  15. rotate_relative: float,
  16. rotate_absolute: float,
  17. duration: float,
  18. gui_action: UserAction,
  19. ) -> None:
  20. self.subject_id = subject_id
  21. self.rotate_relative = rotate_relative
  22. self.rotate_absolute = rotate_absolute
  23. self.duration = duration
  24. self.gui_action = gui_action
  25. class SubjectContinueRotationEvent(Event):
  26. def __init__(
  27. self,
  28. subject_id: int,
  29. rotate_relative: float,
  30. duration: float,
  31. gui_action: UserAction,
  32. ) -> None:
  33. self.subject_id = subject_id
  34. self.rotate_relative = rotate_relative
  35. self.duration = duration
  36. self.gui_action = gui_action
  37. class SubjectFinishRotationEvent(Event):
  38. def __init__(
  39. self,
  40. subject_id: int,
  41. rotation_absolute: float,
  42. gui_action: UserAction,
  43. ) -> None:
  44. self.subject_id = subject_id
  45. self.rotation_absolute = rotation_absolute
  46. self.gui_action = gui_action
  47. class SubjectStartTileMoveEvent(Event):
  48. def __init__(
  49. self,
  50. subject_id: int,
  51. move_to: typing.Tuple[int, int],
  52. duration: float,
  53. gui_action: UserAction,
  54. ) -> None:
  55. self.subject_id = subject_id
  56. self.move_to = move_to
  57. self.duration = duration
  58. self.gui_action = gui_action
  59. class SubjectContinueTileMoveEvent(Event):
  60. def __init__(
  61. self,
  62. subject_id: int,
  63. move_to: typing.Tuple[int, int],
  64. duration: float,
  65. gui_action: UserAction,
  66. ) -> None:
  67. self.subject_id = subject_id
  68. self.move_to = move_to
  69. self.duration = duration
  70. self.gui_action = gui_action
  71. class SubjectFinishTileMoveEvent(Event):
  72. def __init__(
  73. self,
  74. subject_id: int,
  75. move_to: typing.Tuple[int, int],
  76. gui_action: UserAction,
  77. ) -> None:
  78. self.subject_id = subject_id
  79. self.move_to = move_to
  80. self.gui_action = gui_action
  81. class SubjectFinishMoveEvent(Event):
  82. def __init__(
  83. self,
  84. subject_id: int,
  85. move_to: typing.Tuple[int, int],
  86. gui_action: UserAction,
  87. ) -> None:
  88. self.subject_id = subject_id
  89. self.move_to = move_to
  90. self.gui_action = gui_action
  91. class MoveToMechanism(SubjectMechanism):
  92. def run(self) -> dict:
  93. try:
  94. # TODO: MoveToIntention doit être configurable
  95. move = self.subject.intentions.get(MoveToIntention) # type: MoveToIntention
  96. except KeyError:
  97. return {}
  98. if COLLECTION_ALIVE not in self.subject.collections:
  99. return {}
  100. return move.get_data()
  101. class MoveWithRotationBehaviour(SubjectBehaviour):
  102. use = [MoveToMechanism]
  103. def __init__(self, *args, **kwargs):
  104. super().__init__(*args, **kwargs)
  105. self.simulation = typing.cast(XYZSimulation, self.simulation)
  106. def run(self, data) -> object:
  107. """
  108. Compute data relative to move
  109. """
  110. data = data[MoveToMechanism]
  111. if not data:
  112. return False
  113. # Prepare data
  114. to = data['to'] # type: typing.Tuple(int, int)
  115. return_data = {}
  116. now = time.time()
  117. # Test if it's first time
  118. if not data.get('path'):
  119. return_data['path'] = self.simulation.physics.found_path(
  120. start=self.subject.position,
  121. end=to,
  122. subject=self.subject,
  123. )
  124. # find path algorithm can skip start position, add it if not in
  125. if return_data['path'][0] != self.subject.position:
  126. return_data['path'] = [self.subject.position] + return_data['path']
  127. data['path'] = return_data['path']
  128. # Prepare data
  129. path = data['path'] # type: typing.List[typing.Tuple(int, int)]
  130. path_index = path.index(self.subject.position)
  131. next_position = path[path_index + 1]
  132. next_position_direction = get_angle(self.subject.position, next_position)
  133. rotate_relative = next_position_direction - self.subject.direction
  134. # Test if finish move
  135. if path_index == len(path) - 1:
  136. return {
  137. 'move_to_finished': to,
  138. 'gui_action': data['gui_action'],
  139. }
  140. # Check if moving
  141. if self.subject.moving_to == next_position:
  142. if self.subject.start_move + self.subject.move_duration > now:
  143. # Let moving
  144. return {
  145. 'tile_move_to': next_position,
  146. 'gui_action': data['gui_action'],
  147. }
  148. return_data['tile_move_to_finished'] = self.subject.moving_to
  149. # Must consider new position of subject
  150. path_index = path.index(return_data['tile_move_to_finished'])
  151. if path_index == len(path) - 1:
  152. return {
  153. 'move_to_finished': to,
  154. 'gui_action': data['gui_action'],
  155. }
  156. next_position = path[path_index + 1]
  157. next_position_direction = get_angle(
  158. return_data['tile_move_to_finished'],
  159. next_position,
  160. )
  161. rotate_relative = next_position_direction - self.subject.direction
  162. # Check if rotating
  163. if self.subject.rotate_to != -1:
  164. # If it is not finished
  165. if self.subject.start_rotation + self.subject.rotate_duration > now:
  166. # Let rotation do it's job
  167. return {
  168. 'rotate_relative': rotate_relative,
  169. 'rotate_absolute': next_position_direction,
  170. 'gui_action': data['gui_action'],
  171. }
  172. # rotation finish
  173. return_data['rotate_to_finished'] = self.subject.rotate_to
  174. # Check if need to rotate
  175. if not return_data.get('rotate_to_finished') \
  176. and self.subject.direction != next_position_direction:
  177. return_data.update({
  178. 'rotate_relative': rotate_relative,
  179. 'rotate_absolute': next_position_direction,
  180. 'gui_action': data['gui_action'],
  181. })
  182. return return_data
  183. # Need to move to next tile
  184. return_data['tile_move_to'] = next_position
  185. return_data['gui_action'] = data['gui_action']
  186. return return_data
  187. def action(self, data) -> [Event]:
  188. events = []
  189. now = time.time()
  190. if data.get('path'):
  191. move = self.subject.intentions.get(MoveToIntention)
  192. move.path = data['path']
  193. self.subject.intentions.set(move)
  194. if data.get('tile_move_to_finished'):
  195. self.subject.position = data['tile_move_to_finished']
  196. self.subject.moving_to = (-1, -1)
  197. self.subject.start_move = -1
  198. self.subject.move_duration = -1
  199. events.append(SubjectFinishTileMoveEvent(
  200. subject_id=self.subject.id,
  201. move_to=data['tile_move_to_finished'],
  202. gui_action=data['gui_action'],
  203. ))
  204. if data.get('move_to_finished'):
  205. self.subject.position = data['move_to_finished']
  206. self.subject.moving_to = (-1, -1)
  207. self.subject.start_move = -1
  208. self.subject.move_duration = -1
  209. self.subject.intentions.remove(MoveToIntention)
  210. events.append(SubjectFinishMoveEvent(
  211. subject_id=self.subject.id,
  212. move_to=data['move_to_finished'],
  213. gui_action=data['gui_action'],
  214. ))
  215. if data.get('rotate_to_finished'):
  216. self.subject.rotate_to = -1
  217. self.subject.rotate_duration = -1
  218. self.subject.start_rotation = -1
  219. self.subject.direction = data['rotate_to_finished']
  220. events.append(SubjectFinishRotationEvent(
  221. subject_id=self.subject.id,
  222. rotation_absolute=data['rotate_to_finished'],
  223. gui_action=data['gui_action'],
  224. ))
  225. if data.get('rotate_relative'):
  226. # Test if rotation is already started
  227. if self.subject.rotate_to == data['rotate_absolute']:
  228. # look at progression
  229. rotate_since = now - self.subject.start_rotation
  230. rotate_progress = rotate_since / self.subject.rotate_duration
  231. rotation_to_do = self.subject.rotate_to - self.subject.direction
  232. rotation_done = rotation_to_do * rotate_progress
  233. self.subject.direction = self.subject.direction + rotation_done
  234. rotation_left = self.subject.rotate_to - self.subject.direction
  235. duration = self.subject.get_rotate_duration(angle=rotation_left)
  236. self.subject.rotate_duration = duration
  237. self.subject.start_rotation = now
  238. return [SubjectContinueRotationEvent(
  239. subject_id=self.subject.id,
  240. rotate_relative=rotation_left,
  241. duration=duration,
  242. gui_action=data['gui_action'],
  243. )]
  244. else:
  245. duration = self.subject.get_rotate_duration(angle=data['rotate_relative'])
  246. self.subject.rotate_to = data['rotate_absolute']
  247. self.subject.rotate_duration = duration
  248. self.subject.start_rotation = time.time()
  249. events.append(SubjectStartRotationEvent(
  250. subject_id=self.subject.id,
  251. rotate_relative=data['rotate_relative'],
  252. rotate_absolute=data['rotate_absolute'],
  253. duration=duration,
  254. gui_action=data['gui_action'],
  255. ))
  256. if data.get('tile_move_to'):
  257. # It is already moving ?
  258. if self.subject.moving_to == data.get('tile_move_to'):
  259. # look at progression
  260. move_since = now - self.subject.start_move
  261. move_progress = move_since / self.subject.move_duration
  262. move_done = self.subject.move_duration * move_progress
  263. duration = self.subject.move_duration - move_done
  264. self.subject.move_duration = duration
  265. return [SubjectContinueTileMoveEvent(
  266. subject_id=self.subject.id,
  267. move_to=data['tile_move_to'],
  268. duration=duration,
  269. gui_action=data['gui_action'],
  270. )]
  271. else:
  272. move = self.subject.intentions.get(MoveToIntention)
  273. move_type_duration = self.subject.get_move_duration(move)
  274. # FIXME: duration depend next tile type, etc
  275. # see opencombat.gui.base.Game#start_move_subject
  276. duration = move_type_duration * 1
  277. self.subject.moving_to = data['tile_move_to']
  278. self.subject.move_duration = duration
  279. self.subject.start_move = time.time()
  280. events.append(SubjectStartTileMoveEvent(
  281. subject_id=self.subject.id,
  282. move_to=data['tile_move_to'],
  283. duration=duration,
  284. gui_action=data['gui_action'],
  285. ))
  286. return events
  287. class MoveBehaviour(SubjectBehaviour):
  288. use = [MoveToMechanism]
  289. def __init__(self, *args, **kwargs):
  290. super().__init__(*args, **kwargs)
  291. self.simulation = typing.cast(XYZSimulation, self.simulation)
  292. def run(self, data) -> object:
  293. """
  294. Compute data relative to move
  295. """
  296. data = data[MoveToMechanism]
  297. if not data:
  298. return False
  299. # Prepare data
  300. to = data['to'] # type: typing.Tuple(int, int)
  301. return_data = {}
  302. now = time.time()
  303. # Test if it's first time
  304. if not data.get('path'):
  305. return_data['path'] = self.simulation.physics.found_path(
  306. start=self.subject.position,
  307. end=to,
  308. subject=self.subject,
  309. )
  310. # find path algorithm can skip start position, add it if not in
  311. if return_data['path'][0] != self.subject.position:
  312. return_data['path'] = [self.subject.position] + return_data['path']
  313. data['path'] = return_data['path']
  314. # Prepare data
  315. path = data['path'] # type: typing.List[typing.Tuple(int, int)]
  316. path_index = path.index(self.subject.position)
  317. next_position = path[path_index + 1]
  318. # Test if finish move
  319. if path_index == len(path) - 1:
  320. return {
  321. 'move_to_finished': to,
  322. 'gui_action': data['gui_action'],
  323. }
  324. # Check if moving
  325. if self.subject.moving_to == next_position:
  326. if self.subject.start_move + self.subject.move_duration > now:
  327. # Let moving
  328. return {
  329. 'tile_move_to': next_position,
  330. 'gui_action': data['gui_action'],
  331. }
  332. return_data['tile_move_to_finished'] = self.subject.moving_to
  333. # Must consider new position of subject
  334. path_index = path.index(return_data['tile_move_to_finished'])
  335. if path_index == len(path) - 1:
  336. return {
  337. 'move_to_finished': to,
  338. 'gui_action': data['gui_action'],
  339. }
  340. next_position = path[path_index + 1]
  341. # Need to move to next tile
  342. return_data['tile_move_to'] = next_position
  343. return_data['gui_action'] = data['gui_action']
  344. return return_data
  345. def action(self, data) -> [Event]:
  346. events = []
  347. now = time.time()
  348. if data.get('path'):
  349. move = self.subject.intentions.get(MoveToIntention)
  350. move.path = data['path']
  351. self.subject.intentions.set(move)
  352. if data.get('tile_move_to_finished'):
  353. self.subject.position = data['tile_move_to_finished']
  354. self.subject.moving_to = (-1, -1)
  355. self.subject.start_move = -1
  356. self.subject.move_duration = -1
  357. events.append(SubjectFinishTileMoveEvent(
  358. subject_id=self.subject.id,
  359. move_to=data['tile_move_to_finished'],
  360. gui_action=data['gui_action'],
  361. ))
  362. if data.get('move_to_finished'):
  363. self.subject.position = data['move_to_finished']
  364. self.subject.moving_to = (-1, -1)
  365. self.subject.start_move = -1
  366. self.subject.move_duration = -1
  367. self.subject.intentions.remove(MoveToIntention)
  368. events.append(SubjectFinishMoveEvent(
  369. subject_id=self.subject.id,
  370. move_to=data['move_to_finished'],
  371. gui_action=data['gui_action'],
  372. ))
  373. if data.get('tile_move_to'):
  374. # It is already moving ?
  375. if self.subject.moving_to == data.get('tile_move_to'):
  376. # look at progression
  377. move_since = now - self.subject.start_move
  378. move_progress = move_since / self.subject.move_duration
  379. move_done = self.subject.move_duration * move_progress
  380. duration = self.subject.move_duration - move_done
  381. self.subject.move_duration = duration
  382. self.subject.start_move = time.time()
  383. return [SubjectContinueTileMoveEvent(
  384. subject_id=self.subject.id,
  385. move_to=data['tile_move_to'],
  386. duration=duration,
  387. gui_action=data['gui_action'],
  388. )]
  389. else:
  390. move = self.subject.intentions.get(MoveToIntention)
  391. move_type_duration = self.subject.get_move_duration(move)
  392. # FIXME: duration depend next tile type, etc
  393. # see opencombat.gui.base.Game#start_move_subject
  394. duration = move_type_duration * 1
  395. self.subject.moving_to = data['tile_move_to']
  396. self.subject.move_duration = duration
  397. self.subject.start_move = time.time()
  398. events.append(SubjectStartTileMoveEvent(
  399. subject_id=self.subject.id,
  400. move_to=data['tile_move_to'],
  401. duration=duration,
  402. gui_action=data['gui_action'],
  403. ))
  404. return events