actor.py 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. # coding: utf-8
  2. import os
  3. import typing
  4. import time
  5. import pyglet
  6. from PIL import Image
  7. from synergine2.config import Config
  8. from synergine2.simulation import Subject
  9. from synergine2_cocos2d.actor import Actor
  10. from synergine2_xyz.exception import UnknownAnimationIndex
  11. from opencombat.exception import UnknownWeapon
  12. from opencombat.exception import WrongMode
  13. from opencombat.exception import UnknownFiringAnimation
  14. from opencombat.game.animation import ANIMATION_CRAWL
  15. from opencombat.game.animation import ANIMATION_WALK
  16. from opencombat.game.const import MODE_MAN_STAND_UP
  17. from opencombat.game.const import MODE_MAN_CRAWLING
  18. from opencombat.game.image import TileImageCacheManager
  19. from opencombat.game.weapon import RIFFLE
  20. from opencombat.game.weapon import WeaponImageApplier
  21. from opencombat.user_action import UserAction
  22. if typing.TYPE_CHECKING:
  23. from opencombat.game.fire import GuiFiringEvent
  24. MODE_DEFAULT = 'MODE_DEFAULT'
  25. class BaseActor(Actor):
  26. position_matching = {
  27. ANIMATION_WALK: MODE_MAN_STAND_UP,
  28. ANIMATION_CRAWL: MODE_MAN_CRAWLING,
  29. }
  30. mode_image_paths = {
  31. MODE_DEFAULT: 'unknown.png',
  32. }
  33. modes = [
  34. MODE_DEFAULT,
  35. ]
  36. weapons_firing_image_scheme = {}
  37. weapon_image_scheme = {}
  38. move_for_gui_actions = {}
  39. def __init__(
  40. self,
  41. image_path: str,
  42. config: Config,
  43. subject: Subject,
  44. ) -> None:
  45. self._mode = MODE_MAN_STAND_UP
  46. self.weapon_image_applier = WeaponImageApplier(config, self)
  47. self.firing_texture_cache = {} # type: typing.Dict[str, typing.Dict[str, typing.List[pyglet.image.AbstractImage]] # nopep8
  48. super().__init__(image_path, subject=subject, config=config)
  49. # Firing
  50. self.last_firing_time = 0
  51. self.firing_change_image_gap = 0.05 # seconds
  52. def get_image_cache_manager(self) -> TileImageCacheManager:
  53. return TileImageCacheManager(self, self.config)
  54. def get_default_mode(self) -> str:
  55. return MODE_DEFAULT
  56. def get_mode_image_path(self, mode: str) -> str:
  57. return self.mode_image_paths[mode]
  58. def get_modes(self) -> typing.List[str]:
  59. return self.modes
  60. @property
  61. def mode(self) -> str:
  62. return self._mode
  63. @mode.setter
  64. def mode(self, value) -> None:
  65. if value not in self.get_modes():
  66. raise WrongMode('Actor "{}" has no mode "{}" ({})'.format(
  67. self.__class__.__name__,
  68. value,
  69. ', '.join(self.get_modes()),
  70. ))
  71. self._mode = value
  72. def get_mode_for_gui_action(self, gui_action: str) -> str:
  73. try:
  74. return self.move_for_gui_actions[gui_action]
  75. except KeyError:
  76. return self.get_default_mode()
  77. @property
  78. def weapons(self) -> typing.List[str]:
  79. return []
  80. def build_textures_cache(self) -> None:
  81. super().build_textures_cache()
  82. self.build_firing_texture_cache()
  83. def get_default_appliable_images(self) -> typing.List[Image.Image]:
  84. if not self.weapons:
  85. return []
  86. return [
  87. self.weapon_image_applier.get_image_for_weapon(
  88. self.mode,
  89. # TODO BS 2018-02-08: Change this when weapon management enhanced
  90. self.weapons[0],
  91. )
  92. ]
  93. def can_rotate_instant(self) -> bool:
  94. return True
  95. def get_animation_appliable_images(
  96. self,
  97. animation_name: str,
  98. animation_position: int,
  99. ) -> typing.List[Image.Image]:
  100. if not self.weapons:
  101. return []
  102. position = self.position_matching[animation_name]
  103. try:
  104. return [
  105. self.weapon_image_applier.get_animation_image_for_weapon(
  106. position,
  107. self.weapons[0],
  108. animation_position,
  109. )
  110. ]
  111. except UnknownWeapon:
  112. return []
  113. def build_firing_texture_cache(self) -> None:
  114. cache_dir = self.config.resolve('global.cache_dir_path')
  115. for mode in self.get_modes():
  116. for weapon in self.weapons:
  117. firing_images = self.image_cache_manager.firing_cache.get_list(
  118. mode,
  119. weapon,
  120. )
  121. for i, firing_image in enumerate(firing_images):
  122. image_name = '{}_firing_{}_{}_{}.png'.format(
  123. str(self.subject.id),
  124. mode,
  125. weapon,
  126. i,
  127. )
  128. cache_image_path = os.path.join(cache_dir, image_name)
  129. firing_image.save(cache_image_path)
  130. self.firing_texture_cache\
  131. .setdefault(mode, {})\
  132. .setdefault(weapon, [])\
  133. .append(pyglet.image.load(cache_image_path))
  134. def firing(self, firing: 'GuiFiringEvent') -> None:
  135. # FIXME: move some code ?
  136. now = time.time()
  137. if now - self.last_firing_time >= self.firing_change_image_gap:
  138. self.last_firing_time = now
  139. firing.increment_animation_index()
  140. try:
  141. texture = self.firing_texture_cache\
  142. [self.mode]\
  143. [firing.weapon]\
  144. [firing.animation_index]
  145. except KeyError:
  146. self.logger.error(
  147. 'No firing animation for actor {}({}) for mode "{}"'
  148. ' and weapon "{}"'.format(
  149. self.__class__.__name__,
  150. str(self.subject.id),
  151. self.mode,
  152. firing.weapon,
  153. )
  154. )
  155. return # There is no firing animation defined
  156. except IndexError:
  157. texture = self.firing_texture_cache\
  158. [self.mode]\
  159. [firing.weapon]\
  160. [0]
  161. firing.reset_animation_index()
  162. self.update_image(texture)
  163. class Man(BaseActor):
  164. animation_image_paths = {
  165. ANIMATION_WALK: [
  166. 'actors/man.png',
  167. 'actors/man_w1.png',
  168. 'actors/man_w2.png',
  169. 'actors/man_w3.png',
  170. 'actors/man_w4.png',
  171. 'actors/man_w5.png',
  172. 'actors/man_w6.png',
  173. 'actors/man_w7.png',
  174. ],
  175. ANIMATION_CRAWL: [
  176. 'actors/man_c1.png',
  177. 'actors/man_c2.png',
  178. 'actors/man_c3.png',
  179. 'actors/man_c4.png',
  180. ]
  181. }
  182. modes = [
  183. MODE_MAN_STAND_UP,
  184. MODE_MAN_CRAWLING,
  185. ]
  186. mode_image_paths = {
  187. MODE_MAN_STAND_UP: 'actors/man.png',
  188. MODE_MAN_CRAWLING: 'actors/man_c1.png',
  189. }
  190. weapon_image_scheme = {
  191. MODE_MAN_STAND_UP: {
  192. RIFFLE: [
  193. 'actors/man_weap1.png'
  194. ],
  195. },
  196. MODE_MAN_CRAWLING: {
  197. RIFFLE: [
  198. 'actors/man_c1_weap1.png',
  199. 'actors/man_c2_weap1.png',
  200. 'actors/man_c3_weap1.png',
  201. 'actors/man_c4_weap1.png',
  202. ],
  203. }
  204. }
  205. weapons_firing_image_scheme = {
  206. MODE_MAN_STAND_UP: {
  207. RIFFLE: [
  208. 'actors/man_weap1_firing1.png',
  209. 'actors/man_weap1_firing2.png',
  210. 'actors/man_weap1_firing3.png',
  211. ],
  212. },
  213. MODE_MAN_CRAWLING: {
  214. RIFFLE: [
  215. 'actors/man_weap1_firing1.png',
  216. 'actors/man_weap1_firing2.png',
  217. 'actors/man_weap1_firing3.png',
  218. ]
  219. }
  220. }
  221. move_for_gui_actions = {
  222. UserAction.ORDER_MOVE: MODE_MAN_STAND_UP,
  223. UserAction.ORDER_MOVE_FAST: MODE_MAN_STAND_UP,
  224. UserAction.ORDER_MOVE_CRAWL: MODE_MAN_CRAWLING,
  225. }
  226. def __init__(
  227. self,
  228. config: Config,
  229. subject: Subject,
  230. ) -> None:
  231. super().__init__('actors/man.png', subject=subject, config=config)
  232. @property
  233. def weapons(self) -> typing.List[str]:
  234. # TODO BS 2018-01-26: Will be managed by complex part of code
  235. return [RIFFLE]
  236. def get_default_mode(self) -> str:
  237. return MODE_MAN_STAND_UP
  238. class HeavyVehicle(BaseActor):
  239. animation_image_paths = {
  240. ANIMATION_WALK: [
  241. 'actors/tank1.png',
  242. ],
  243. ANIMATION_CRAWL: [
  244. 'actors/tank1.png',
  245. ]
  246. }
  247. mode_image_paths = {
  248. MODE_DEFAULT: 'actors/tank1.png',
  249. }
  250. def __init__(
  251. self,
  252. config: Config,
  253. subject: Subject,
  254. ) -> None:
  255. super().__init__('actors/tank1.png', subject=subject, config=config)
  256. @property
  257. def weapons(self) -> typing.List[str]:
  258. # TODO BS 2018-01-26: Will be managed by complex part of code
  259. return [RIFFLE]
  260. def can_rotate_instant(self) -> bool:
  261. return False