base.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. # coding: utf-8
  2. import io
  3. import os
  4. import random
  5. import typing
  6. import cocos
  7. import pyglet
  8. import time
  9. from PIL import Image
  10. from pyglet.window import key
  11. from cocos.actions import MoveTo as BaseMoveTo
  12. from cocos.audio.pygame.mixer import Sound
  13. from synergine2_cocos2d.interaction import InteractionManager
  14. from synergine2_cocos2d.middleware import MapMiddleware
  15. from synergine2_cocos2d.util import PathManager
  16. from opencombat.simulation.interior import InteriorManager
  17. from opencombat.simulation.tmx import TileMap
  18. from opencombat.user_action import UserAction
  19. from synergine2.config import Config
  20. from synergine2.terminals import Terminal
  21. from synergine2_cocos2d.actions import MoveTo
  22. from opencombat.gui.animation import ANIMATION_CRAWL
  23. from opencombat.gui.animation import ANIMATION_WALK
  24. from synergine2_cocos2d.animation import Animate
  25. from synergine2_cocos2d.gl import draw_line
  26. from synergine2_cocos2d.gui import EditLayer as BaseEditLayer
  27. from synergine2_cocos2d.gui import Gui
  28. from synergine2_cocos2d.gui import TMXGui
  29. from synergine2_cocos2d.layer import LayerManager
  30. from synergine2_xyz.move.simulation import FinishMoveEvent
  31. from synergine2_xyz.move.simulation import StartMoveEvent
  32. from synergine2_xyz.physics import Physics
  33. from synergine2_xyz.utils import get_angle
  34. from opencombat.simulation.event import NewVisibleOpponent
  35. from opencombat.simulation.event import NoLongerVisibleOpponent
  36. from opencombat.simulation.event import FireEvent
  37. from opencombat.simulation.event import DieEvent
  38. class EditLayer(BaseEditLayer):
  39. def _on_key_press(self, k, m):
  40. if self.selection:
  41. if k == key.M:
  42. self.user_action_pending = UserAction.ORDER_MOVE
  43. if k == key.R:
  44. self.user_action_pending = UserAction.ORDER_MOVE_FAST
  45. if k == key.C:
  46. self.user_action_pending = UserAction.ORDER_MOVE_CRAWL
  47. if k == key.F:
  48. self.user_action_pending = UserAction.ORDER_FIRE
  49. def draw(self) -> None:
  50. super().draw()
  51. class BackgroundLayer(cocos.layer.Layer):
  52. def __init__(
  53. self,
  54. config: Config,
  55. layer_manager: LayerManager,
  56. background_sprite: cocos.sprite.Sprite,
  57. ) -> None:
  58. super().__init__()
  59. self.config = config
  60. self.layer_manager = layer_manager
  61. self.background_sprite = background_sprite
  62. self.last_interior_draw_timestamp = 0
  63. self.draw_interiors_gap = self.config.resolve(
  64. 'game.building.draw_interior_gap',
  65. 2,
  66. )
  67. self.interior_manager = InteriorManager(TileMap(
  68. layer_manager.middleware.get_map_file_path(),
  69. ))
  70. self.map_tile_width = self.layer_manager.middleware.get_cell_width()
  71. self.map_tile_height = self.layer_manager.middleware.get_cell_height()
  72. def draw(self, *args, **kwargs):
  73. super().draw(*args, **kwargs)
  74. self.draw_interiors()
  75. def draw_interiors(self):
  76. now = time.time()
  77. if now - self.last_interior_draw_timestamp > self.draw_interiors_gap:
  78. self.last_interior_draw_timestamp = now
  79. subject_grid_positions = [
  80. a.subject.position for a
  81. in self.layer_manager.subject_layer.subjects_index.values()
  82. ]
  83. interiors = self.interior_manager.get_interiors(
  84. where_positions=subject_grid_positions)
  85. if interiors:
  86. image = Image.open(os.path.join(
  87. self.layer_manager.middleware.map_dir_path,
  88. 'background.png',
  89. ))
  90. image_fake_file = io.BytesIO()
  91. self.interior_manager.update_image_for_interiors(
  92. image,
  93. interiors,
  94. self.map_tile_width,
  95. self.map_tile_height,
  96. )
  97. image.save(image_fake_file, format='PNG')
  98. self.background_sprite.image = pyglet.image.load(
  99. 'new_background.png',
  100. file=image_fake_file,
  101. )
  102. class TileLayerManager(LayerManager):
  103. edit_layer_class = EditLayer
  104. def __init__(
  105. self,
  106. config: Config,
  107. middleware: MapMiddleware,
  108. interaction_manager: 'InteractionManager',
  109. gui: 'Gui',
  110. ) -> None:
  111. super().__init__(
  112. config,
  113. middleware,
  114. interaction_manager,
  115. gui,
  116. )
  117. self.background_layer = None # type: BackgroundLayer
  118. self.interior_sprite = None # type: cocos.sprite.Sprite
  119. self.ground_layer = None # type: cocos.tiles.RectMapLayer
  120. self.top_layer = None # type: cocos.tiles.RectMapLayer
  121. def init(self) -> None:
  122. super().init()
  123. self.interior_sprite = self.middleware.get_interior_sprite()
  124. background_sprite = self.middleware.get_background_sprite()
  125. self.background_layer = BackgroundLayer(self.config, self, background_sprite)
  126. self.background_layer.add(background_sprite)
  127. self.ground_layer = self.middleware.get_ground_layer()
  128. self.top_layer = self.middleware.get_top_layer()
  129. def connect_layers(self) -> None:
  130. self.main_layer.add(self.interior_sprite)
  131. self.main_layer.add(self.background_layer)
  132. self.main_layer.add(self.ground_layer)
  133. super().connect_layers()
  134. self.main_layer.add(self.top_layer)
  135. def center(self) -> None:
  136. super().center()
  137. self.interior_sprite.position = \
  138. 0 + (self.interior_sprite.width / 2), 0 + (self.interior_sprite.height / 2)
  139. self.background_layer.background_sprite.position = \
  140. 0 + (self.background_layer.background_sprite.width / 2), 0 +\
  141. (self.background_layer.background_sprite.height/2)
  142. self.ground_layer.set_view(
  143. 0, 0, self.ground_layer.px_width, self.ground_layer.px_height,
  144. )
  145. self.top_layer.set_view(
  146. 0, 0, self.top_layer.px_width, self.top_layer.px_height,
  147. )
  148. # TODO: Move into synergine2cocos2d
  149. class AudioLibrary(object):
  150. sound_file_paths = {
  151. 'gunshot_default': '204010__duckduckpony__homemade-gunshot-2.ogg',
  152. }
  153. def __init__(self, config: Config) -> None:
  154. self.config = config
  155. self._path_manager = PathManager(config.resolve('global.include_path.sounds'))
  156. self._sounds = {}
  157. def get_sound(self, name: str) -> Sound:
  158. if name not in self._sounds:
  159. sound_file_name = self.sound_file_paths[name]
  160. self._sounds[name] = Sound(os.path.join(self._sound_dir_path, sound_file_name))
  161. return self._sounds[name]
  162. class Game(TMXGui):
  163. layer_manager_class = TileLayerManager
  164. def __init__(
  165. self,
  166. config: Config,
  167. terminal: Terminal,
  168. physics: Physics,
  169. read_queue_interval: float = 1 / 60.0,
  170. map_dir_path: str=None,
  171. ):
  172. super().__init__(
  173. config,
  174. terminal,
  175. physics=physics,
  176. read_queue_interval=read_queue_interval,
  177. map_dir_path=map_dir_path,
  178. )
  179. self.sound_lib = AudioLibrary(self.config)
  180. self.terminal.register_event_handler(
  181. FinishMoveEvent,
  182. self.set_subject_position,
  183. )
  184. self.terminal.register_event_handler(
  185. StartMoveEvent,
  186. self.start_move_subject,
  187. )
  188. self.terminal.register_event_handler(
  189. NewVisibleOpponent,
  190. self.new_visible_opponent,
  191. )
  192. self.terminal.register_event_handler(
  193. NoLongerVisibleOpponent,
  194. self.no_longer_visible_opponent,
  195. )
  196. self.terminal.register_event_handler(
  197. FireEvent,
  198. self.fire_happen,
  199. )
  200. self.terminal.register_event_handler(
  201. DieEvent,
  202. self.subject_die,
  203. )
  204. # configs
  205. self.move_duration_ref = float(self.config.resolve('game.move.walk_ref_time'))
  206. self.move_fast_duration_ref = float(self.config.resolve('game.move.run_ref_time'))
  207. self.move_crawl_duration_ref = float(self.config.resolve('game.move.crawl_ref_time'))
  208. def before_run(self) -> None:
  209. from opencombat.gui.move import MoveActorInteraction
  210. from opencombat.gui.move import MoveFastActorInteraction
  211. from opencombat.gui.move import MoveCrawlActorInteraction
  212. from opencombat.gui.fire import FireActorInteraction
  213. self.layer_manager.interaction_manager.register(MoveActorInteraction, self.layer_manager)
  214. self.layer_manager.interaction_manager.register(MoveFastActorInteraction, self.layer_manager)
  215. self.layer_manager.interaction_manager.register(MoveCrawlActorInteraction, self.layer_manager)
  216. self.layer_manager.interaction_manager.register(FireActorInteraction, self.layer_manager)
  217. def set_subject_position(self, event: FinishMoveEvent):
  218. actor = self.layer_manager.subject_layer.subjects_index[event.subject_id]
  219. new_world_position = self.layer_manager.grid_manager.get_world_position_of_grid_position(event.to_position)
  220. actor.stop_actions((BaseMoveTo,))
  221. actor.set_position(*new_world_position)
  222. def start_move_subject(self, event: StartMoveEvent):
  223. actor = self.layer_manager.subject_layer.subjects_index[event.subject_id]
  224. new_world_position = self.layer_manager.grid_manager.get_world_position_of_grid_position(event.to_position)
  225. if event.gui_action == UserAction.ORDER_MOVE:
  226. animation = ANIMATION_WALK
  227. cycle_duration = 2
  228. move_duration = self.move_duration_ref
  229. elif event.gui_action == UserAction.ORDER_MOVE_FAST:
  230. animation = ANIMATION_WALK
  231. cycle_duration = 0.5
  232. move_duration = self.move_fast_duration_ref
  233. elif event.gui_action == UserAction.ORDER_MOVE_CRAWL:
  234. animation = ANIMATION_CRAWL
  235. cycle_duration = 2
  236. move_duration = self.move_crawl_duration_ref
  237. else:
  238. raise NotImplementedError()
  239. move_action = MoveTo(new_world_position, move_duration)
  240. actor.do(move_action)
  241. actor.do(Animate(animation, duration=move_duration, cycle_duration=cycle_duration))
  242. actor.rotation = get_angle(event.from_position, event.to_position)
  243. def new_visible_opponent(self, event: NewVisibleOpponent):
  244. self.visible_or_no_longer_visible_opponent(event, (153, 0, 153))
  245. def no_longer_visible_opponent(self, event: NoLongerVisibleOpponent):
  246. self.visible_or_no_longer_visible_opponent(event, (255, 102, 0))
  247. def visible_or_no_longer_visible_opponent(
  248. self,
  249. event: typing.Union[NoLongerVisibleOpponent, NewVisibleOpponent],
  250. line_color,
  251. ) -> None:
  252. if not self.layer_manager.debug:
  253. return
  254. observer_actor = self.layer_manager.subject_layer.subjects_index[event.observer_subject_id]
  255. observed_actor = self.layer_manager.subject_layer.subjects_index[event.observed_subject_id]
  256. observer_pixel_position = self.layer_manager.scrolling_manager.world_to_screen(
  257. *self.layer_manager.grid_manager.get_world_position_of_grid_position(
  258. observer_actor.subject.position,
  259. )
  260. )
  261. observed_pixel_position = self.layer_manager.scrolling_manager.world_to_screen(
  262. *self.layer_manager.grid_manager.get_world_position_of_grid_position(
  263. observed_actor.subject.position,
  264. )
  265. )
  266. def draw_visible_opponent():
  267. draw_line(
  268. observer_pixel_position,
  269. observed_pixel_position,
  270. line_color,
  271. )
  272. self.layer_manager.edit_layer.append_callback(draw_visible_opponent, 1.0)
  273. def fire_happen(self, event: FireEvent) -> None:
  274. shooter_actor = self.layer_manager.subject_layer.subjects_index[event.shooter_subject_id]
  275. shooter_pixel_position = self.layer_manager.scrolling_manager.world_to_screen(
  276. *self.layer_manager.grid_manager.get_world_position_of_grid_position(
  277. shooter_actor.subject.position,
  278. )
  279. )
  280. fire_to_pixel_position = self.layer_manager.scrolling_manager.world_to_screen(
  281. *self.layer_manager.grid_manager.get_world_position_of_grid_position(
  282. event.target_position,
  283. )
  284. )
  285. def gunshot_trace():
  286. draw_line(
  287. shooter_pixel_position,
  288. fire_to_pixel_position,
  289. color=(255, 0, 0),
  290. )
  291. def gunshot_sound():
  292. self.sound_lib.get_sound('gunshot_default').play()
  293. # To avoid all in same time
  294. # TODO BS 2018-01-24: This should be unecessary when core events sending will be
  295. # base on time base instead cycle base. Remove it to ensure.
  296. delay = random.uniform(0.0, 0.6)
  297. self.layer_manager.edit_layer.append_callback(gunshot_trace, duration=0.1, delay=delay)
  298. self.layer_manager.edit_layer.append_callback(gunshot_sound, duration=0.0, delay=delay)
  299. def subject_die(self, event: DieEvent) -> None:
  300. killed_actor = self.layer_manager.subject_layer.subjects_index[event.shoot_subject_id]
  301. dead_image = pyglet.resource.image('opencombat/maps/003/actors/man_d1.png')
  302. killed_actor.update_image(dead_image)
  303. killed_actor.freeze()