move.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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 # nopep8
  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 = abs(self.subject.get_rotate_duration(
  236. angle=rotation_left,
  237. ))
  238. self.subject.rotate_duration = duration
  239. self.subject.start_rotation = now
  240. return [SubjectContinueRotationEvent(
  241. subject_id=self.subject.id,
  242. rotate_relative=rotation_left,
  243. duration=duration,
  244. gui_action=data['gui_action'],
  245. )]
  246. else:
  247. duration = abs(self.subject.get_rotate_duration(
  248. angle=data['rotate_relative'],
  249. ))
  250. self.subject.rotate_to = data['rotate_absolute']
  251. self.subject.rotate_duration = duration
  252. self.subject.start_rotation = time.time()
  253. events.append(SubjectStartRotationEvent(
  254. subject_id=self.subject.id,
  255. rotate_relative=data['rotate_relative'],
  256. rotate_absolute=data['rotate_absolute'],
  257. duration=duration,
  258. gui_action=data['gui_action'],
  259. ))
  260. if data.get('tile_move_to'):
  261. # It is already moving ?
  262. if self.subject.moving_to == data.get('tile_move_to'):
  263. # look at progression
  264. move_since = now - self.subject.start_move
  265. move_progress = move_since / self.subject.move_duration
  266. move_done = self.subject.move_duration * move_progress
  267. duration = self.subject.move_duration - move_done
  268. self.subject.move_duration = duration
  269. return [SubjectContinueTileMoveEvent(
  270. subject_id=self.subject.id,
  271. move_to=data['tile_move_to'],
  272. duration=duration,
  273. gui_action=data['gui_action'],
  274. )]
  275. else:
  276. move = self.subject.intentions.get(MoveToIntention)
  277. move_type_duration = self.subject.get_move_duration(move)
  278. # FIXME: duration depend next tile type, etc
  279. # see opencombat.gui.base.Game#start_move_subject
  280. duration = move_type_duration * 1
  281. self.subject.moving_to = data['tile_move_to']
  282. self.subject.move_duration = duration
  283. self.subject.start_move = time.time()
  284. events.append(SubjectStartTileMoveEvent(
  285. subject_id=self.subject.id,
  286. move_to=data['tile_move_to'],
  287. duration=duration,
  288. gui_action=data['gui_action'],
  289. ))
  290. return events
  291. class MoveBehaviour(SubjectBehaviour):
  292. use = [MoveToMechanism]
  293. def __init__(self, *args, **kwargs):
  294. super().__init__(*args, **kwargs)
  295. self.simulation = typing.cast(XYZSimulation, self.simulation)
  296. def run(self, data) -> object:
  297. """
  298. Compute data relative to move
  299. """
  300. data = data[MoveToMechanism]
  301. if not data:
  302. return False
  303. # Prepare data
  304. to = data['to'] # type: typing.Tuple(int, int)
  305. return_data = {}
  306. now = time.time()
  307. # Test if it's first time
  308. if not data.get('path'):
  309. return_data['path'] = self.simulation.physics.found_path(
  310. start=self.subject.position,
  311. end=to,
  312. subject=self.subject,
  313. )
  314. # find path algorithm can skip start position, add it if not in
  315. if return_data['path'][0] != self.subject.position:
  316. return_data['path'] = [self.subject.position] + return_data['path']
  317. data['path'] = return_data['path']
  318. # Prepare data
  319. path = data['path'] # type: typing.List[typing.Tuple(int, int)]
  320. path_index = path.index(self.subject.position)
  321. next_position = path[path_index + 1]
  322. # Test if finish move
  323. if path_index == len(path) - 1:
  324. return {
  325. 'move_to_finished': to,
  326. 'gui_action': data['gui_action'],
  327. }
  328. # Check if moving
  329. if self.subject.moving_to == next_position:
  330. if self.subject.start_move + self.subject.move_duration > now:
  331. # Let moving
  332. return {
  333. 'tile_move_to': next_position,
  334. 'gui_action': data['gui_action'],
  335. }
  336. return_data['tile_move_to_finished'] = self.subject.moving_to
  337. # Must consider new position of subject
  338. path_index = path.index(return_data['tile_move_to_finished'])
  339. if path_index == len(path) - 1:
  340. return {
  341. 'move_to_finished': to,
  342. 'gui_action': data['gui_action'],
  343. }
  344. next_position = path[path_index + 1]
  345. # Need to move to next tile
  346. return_data['tile_move_to'] = next_position
  347. return_data['gui_action'] = data['gui_action']
  348. return return_data
  349. def action(self, data) -> [Event]:
  350. events = []
  351. now = time.time()
  352. if data.get('path'):
  353. move = self.subject.intentions.get(MoveToIntention)
  354. move.path = data['path']
  355. self.subject.intentions.set(move)
  356. if data.get('tile_move_to_finished'):
  357. self.subject.position = data['tile_move_to_finished']
  358. self.subject.moving_to = (-1, -1)
  359. self.subject.start_move = -1
  360. self.subject.move_duration = -1
  361. events.append(SubjectFinishTileMoveEvent(
  362. subject_id=self.subject.id,
  363. move_to=data['tile_move_to_finished'],
  364. gui_action=data['gui_action'],
  365. ))
  366. if data.get('move_to_finished'):
  367. self.subject.position = data['move_to_finished']
  368. self.subject.moving_to = (-1, -1)
  369. self.subject.start_move = -1
  370. self.subject.move_duration = -1
  371. self.subject.intentions.remove(MoveToIntention)
  372. events.append(SubjectFinishMoveEvent(
  373. subject_id=self.subject.id,
  374. move_to=data['move_to_finished'],
  375. gui_action=data['gui_action'],
  376. ))
  377. if data.get('tile_move_to'):
  378. # It is already moving ?
  379. if self.subject.moving_to == data.get('tile_move_to'):
  380. # look at progression
  381. move_since = now - self.subject.start_move
  382. move_progress = move_since / self.subject.move_duration
  383. move_done = self.subject.move_duration * move_progress
  384. duration = self.subject.move_duration - move_done
  385. self.subject.move_duration = duration
  386. self.subject.start_move = time.time()
  387. return [SubjectContinueTileMoveEvent(
  388. subject_id=self.subject.id,
  389. move_to=data['tile_move_to'],
  390. duration=duration,
  391. gui_action=data['gui_action'],
  392. )]
  393. else:
  394. move = self.subject.intentions.get(MoveToIntention)
  395. move_type_duration = self.subject.get_move_duration(move)
  396. # FIXME: duration depend next tile type, etc
  397. # see opencombat.gui.base.Game#start_move_subject
  398. duration = move_type_duration * 1
  399. self.subject.moving_to = data['tile_move_to']
  400. self.subject.move_duration = duration
  401. self.subject.start_move = time.time()
  402. events.append(SubjectStartTileMoveEvent(
  403. subject_id=self.subject.id,
  404. move_to=data['tile_move_to'],
  405. duration=duration,
  406. gui_action=data['gui_action'],
  407. ))
  408. return events