move.py 17KB

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