Bastien Sevajol 6 years ago
parent
commit
2471b7cad7

BIN
medias/images/actors/man.png View File


BIN
medias/images/actors/man_weap1.png View File


BIN
medias/images/actors/man_weap1_firing1.png View File


BIN
medias/images/actors/man_weap1_firing2.png View File


BIN
medias/images/actors/man_weap1_firing3.png View File


BIN
medias/images/actors/src/man_weap1_firing.xcf View File


+ 4 - 0
opencombat/exception.py View File

7
 
7
 
8
 class UnknownWeapon(OpenCombatException):
8
 class UnknownWeapon(OpenCombatException):
9
     pass
9
     pass
10
+
11
+
12
+class UnknownAnimationIndex(OpenCombatException):
13
+    pass

+ 102 - 6
opencombat/gui/actor.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import typing
2
 import typing
3
 
3
 
4
+import time
5
+
6
+import pyglet
4
 from PIL import Image
7
 from PIL import Image
5
 from synergine2.config import Config
8
 from synergine2.config import Config
6
 from synergine2.simulation import Subject
9
 from synergine2.simulation import Subject
7
 from synergine2_cocos2d.actor import Actor
10
 from synergine2_cocos2d.actor import Actor
8
 
11
 
9
-from opencombat.exception import UnknownWeapon
12
+from opencombat.exception import UnknownWeapon, UnknownAnimationIndex
10
 from opencombat.gui.animation import ANIMATION_CRAWL
13
 from opencombat.gui.animation import ANIMATION_CRAWL
11
 from opencombat.gui.animation import ANIMATION_WALK
14
 from opencombat.gui.animation import ANIMATION_WALK
12
 from opencombat.gui.const import POSITION_MAN_STAND_UP
15
 from opencombat.gui.const import POSITION_MAN_STAND_UP
13
 from opencombat.gui.const import POSITION_MAN_CRAWLING
16
 from opencombat.gui.const import POSITION_MAN_CRAWLING
17
+from opencombat.gui.image import TileImageCache
14
 from opencombat.gui.weapon import RIFFLE
18
 from opencombat.gui.weapon import RIFFLE
15
 from opencombat.gui.weapon import WeaponImageApplier
19
 from opencombat.gui.weapon import WeaponImageApplier
16
 
20
 
21
+if typing.TYPE_CHECKING:
22
+    from opencombat.gui.fire import GuiFiringEvent
23
+
17
 
24
 
18
 class BaseActor(Actor):
25
 class BaseActor(Actor):
19
     position_matching = {
26
     position_matching = {
31
         self.weapon_image_applier = WeaponImageApplier(config, self)
38
         self.weapon_image_applier = WeaponImageApplier(config, self)
32
         super().__init__(image_path, subject=subject, config=config)
39
         super().__init__(image_path, subject=subject, config=config)
33
 
40
 
41
+        # Firing
42
+        self.last_firing_time = 0
43
+        self.firing_change_image_gap = 0.05  # seconds
44
+        self.firing_images_cache = {}  # type: TODO
45
+
46
+        self.default_image_path = image_path
47
+
48
+        self.image_cache = TileImageCache(self, self.config)
49
+        self.image_cache.build()
50
+        pass
51
+
34
     @property
52
     @property
35
     def mode(self) -> str:
53
     def mode(self) -> str:
36
         return self._mode
54
         return self._mode
44
             return []
62
             return []
45
 
63
 
46
         return [
64
         return [
47
-            self.weapon_image_applier.get_default_image_for_weapon(
65
+            self.weapon_image_applier.get_image_for_weapon(
48
                 self.mode,
66
                 self.mode,
49
-                self.weapons[0],
67
+                self.weapons[0],  # FIXME
50
             )
68
             )
51
         ]
69
         ]
52
 
70
 
71
         except UnknownWeapon:
89
         except UnknownWeapon:
72
             return []
90
             return []
73
 
91
 
74
-    def firing(self) -> None:
75
-        pass
92
+    def get_modes(self) -> typing.List[str]:
93
+        return [POSITION_MAN_STAND_UP]
94
+
95
+    # def build_firing_images(self) -> None:
96
+    #     cache_dir = self.config.resolve('global.cache_dir_path')
97
+    #     for mode in self.get_modes():
98
+    #         for weapon in self.weapons:
99
+    #             images = self.weapon_image_applier.get_firing_image(
100
+    #                 mode=mode,
101
+    #                 weapon_type=weapon,
102
+    #             )
103
+    #
104
+    #             for position in range(len(images)):
105
+    #                 pass
106
+    #
107
+    #
108
+    #     for animation_name, animation_image_paths in self.animation_image_paths.items():
109
+    #         self.animation_images[animation_name] = []
110
+    #         for i, animation_image_path in enumerate(animation_image_paths):
111
+    #             final_image_path = self.path_manager.path(animation_image_path)
112
+    #             final_image = Image.open(final_image_path)
113
+    #
114
+    #             for appliable_image in self.get_animation_appliable_images(
115
+    #                 animation_name,
116
+    #                 i,
117
+    #             ):
118
+    #                 final_image.paste(
119
+    #                     appliable_image,
120
+    #                     (0, 0),
121
+    #                     appliable_image,
122
+    #                 )
123
+    #
124
+    #             # FIXME NOW: nom des image utilise au dessus
125
+    #             final_name = '_'.join([
126
+    #                 str(subject_id),
127
+    #                 ntpath.basename(final_image_path),
128
+    #             ])
129
+    #             final_path = os.path.join(cache_dir, final_name)
130
+    #
131
+    #             final_image.save(final_path)
132
+    #
133
+    #             self.animation_images[animation_name].append(
134
+    #                 pyglet.image.load(
135
+    #                     final_path,
136
+    #                 )
137
+    #             )
138
+
139
+    def firing(self, firing: 'GuiFiringEvent') -> None:
140
+        # FIXME: move some code ?
141
+        now = time.time()
142
+        if now - self.last_firing_time >= self.firing_change_image_gap:
143
+            self.last_firing_time = now
144
+            firing.increment_animation_index()
145
+
146
+            try:
147
+                image = self.image_cache.firing_cache.get(
148
+                    mode=self.mode,
149
+                    weapon=firing.weapon,
150
+                    position=firing.animation_index,
151
+                )
152
+            except UnknownAnimationIndex:
153
+                image = self.image_cache.firing_cache.get(
154
+                    mode=self.mode,
155
+                    weapon=firing.weapon,
156
+                    position=0,
157
+                )
158
+                firing.reset_animation_index()
159
+
160
+            # FIXME cache: prepare before firing
161
+            import uuid
162
+            tmp_path = '/tmp/{}.png'.format(str(uuid.uuid4()))
163
+            image.save(tmp_path)
164
+            pyglet_image = pyglet.image.load(tmp_path)
165
+
166
+            self.update_image(pyglet_image.get_texture())
76
 
167
 
77
 
168
 
78
 class Man(BaseActor):
169
 class Man(BaseActor):
108
         return [RIFFLE]
199
         return [RIFFLE]
109
 
200
 
110
 
201
 
111
-class HeavyVehicle(Actor):
202
+class HeavyVehicle(BaseActor):
112
     animation_image_paths = {
203
     animation_image_paths = {
113
         ANIMATION_WALK: [
204
         ANIMATION_WALK: [
114
             'actors/tank1.png',
205
             'actors/tank1.png',
124
         subject: Subject,
215
         subject: Subject,
125
     ) -> None:
216
     ) -> None:
126
         super().__init__('actors/tank1.png', subject=subject, config=config)
217
         super().__init__('actors/tank1.png', subject=subject, config=config)
218
+
219
+    @property
220
+    def weapons(self) -> typing.List[str]:
221
+        # TODO BS 2018-01-26: Will be managed by complex part of code
222
+        return [RIFFLE]

+ 9 - 1
opencombat/gui/base.py View File

17
 from synergine2_cocos2d.middleware import MapMiddleware
17
 from synergine2_cocos2d.middleware import MapMiddleware
18
 from synergine2_cocos2d.util import PathManager
18
 from synergine2_cocos2d.util import PathManager
19
 
19
 
20
+from opencombat.gui.fire import GuiFiringEvent
20
 from opencombat.simulation.interior import InteriorManager
21
 from opencombat.simulation.interior import InteriorManager
21
 from opencombat.simulation.tmx import TileMap
22
 from opencombat.simulation.tmx import TileMap
22
 from opencombat.user_action import UserAction
23
 from opencombat.user_action import UserAction
346
         def gunshot_sound():
347
         def gunshot_sound():
347
             self.sound_lib.get_sound('gunshot_default').play()
348
             self.sound_lib.get_sound('gunshot_default').play()
348
 
349
 
350
+        firing_event = GuiFiringEvent(shooter_actor, event.weapon_type)
351
+        original_actor_image = shooter_actor.image
352
+
349
         def actor_firing():
353
         def actor_firing():
350
-            shooter_actor.firing(event.weapon_type)
354
+            shooter_actor.firing(firing_event)
355
+
356
+        def actor_end_firing():
357
+            shooter_actor.update_image(original_actor_image)
351
 
358
 
352
         # To avoid all in same time
359
         # To avoid all in same time
353
         # TODO BS 2018-01-24: This should be unecessary when core events sending will be
360
         # TODO BS 2018-01-24: This should be unecessary when core events sending will be
368
             actor_firing,
375
             actor_firing,
369
             duration=0.2,  # TODO BS 2018-01-25: Wil depend of weapon type
376
             duration=0.2,  # TODO BS 2018-01-25: Wil depend of weapon type
370
             delay=delay,
377
             delay=delay,
378
+            end_callback=actor_end_firing,
371
         )
379
         )
372
 
380
 
373
     def subject_die(self, event: DieEvent) -> None:
381
     def subject_die(self, event: DieEvent) -> None:

+ 26 - 0
opencombat/gui/fire.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import typing
2
 import typing
3
 
3
 
4
+from opencombat.gui.actor import BaseActor
5
+from opencombat.simulation.event import DEFAULT_WEAPON_TYPE
4
 from opencombat.simulation.fire import RequestFireBehaviour
6
 from opencombat.simulation.fire import RequestFireBehaviour
5
 from synergine2_cocos2d.interaction import BaseActorInteraction
7
 from synergine2_cocos2d.interaction import BaseActorInteraction
6
 from opencombat.user_action import UserAction
8
 from opencombat.user_action import UserAction
87
 class FireActorInteraction(BaseFireActorInteraction):
89
 class FireActorInteraction(BaseFireActorInteraction):
88
     gui_action = UserAction.ORDER_FIRE
90
     gui_action = UserAction.ORDER_FIRE
89
     color = (255, 0, 0)
91
     color = (255, 0, 0)
92
+
93
+
94
+class GuiFiringEvent(object):
95
+    def __init__(
96
+        self,
97
+        actor: BaseActor,
98
+        weapon: str,
99
+    ) -> None:
100
+        self.actor = actor
101
+        self.weapon = weapon
102
+        self._animation_index = -1
103
+
104
+        if weapon == DEFAULT_WEAPON_TYPE:
105
+            self.weapon = self.actor.weapons[0]
106
+
107
+    @property
108
+    def animation_index(self) -> int:
109
+        return self._animation_index
110
+
111
+    def increment_animation_index(self) -> None:
112
+        self._animation_index += 1
113
+
114
+    def reset_animation_index(self) -> None:
115
+        self._animation_index = -1

+ 100 - 0
opencombat/gui/image.py View File

1
+# coding: utf-8
2
+import io
3
+import typing
4
+
5
+from PIL import Image
6
+from synergine2.config import Config
7
+from synergine2_cocos2d.actor import Actor
8
+from synergine2_cocos2d.util import PathManager
9
+
10
+from opencombat.exception import UnknownAnimationIndex
11
+
12
+
13
+class ImageCache(object):
14
+    def __init__(self) -> None:
15
+        self.cache = {}
16
+
17
+
18
+class FiringCache(ImageCache):
19
+    def add(
20
+        self,
21
+        mode: str,
22
+        weapon: str,
23
+        image: Image.Image,
24
+    ) -> None:
25
+        self.cache.setdefault(mode, {}).setdefault(weapon, []).append(image)
26
+
27
+    def get(
28
+        self,
29
+        mode: str,
30
+        weapon: str,
31
+        position: int,
32
+    ) -> Image.Image:
33
+        try:
34
+            return self.cache[mode][weapon][position]
35
+        except KeyError:
36
+            raise Exception('TODO')
37
+        except IndexError:
38
+            raise UnknownAnimationIndex(
39
+                'Unknown animation index "{}" for mode "{}" and weapon "{}"'.format(
40
+                    position,
41
+                    mode,
42
+                    weapon,
43
+                ),
44
+            )
45
+
46
+
47
+class ImageCache(object):
48
+    # FIXME: Move into synergine
49
+    def __init__(
50
+        self,
51
+        actor: Actor,
52
+        config: Config,
53
+    ) -> None:
54
+        self.config = config
55
+        self.actor = actor
56
+
57
+    def build(self) -> None:
58
+        pass
59
+
60
+
61
+class TileImageCache(ImageCache):
62
+    def __init__(
63
+        self,
64
+        actor: Actor,
65
+        config: Config,
66
+    ) -> None:
67
+        super().__init__(actor, config)
68
+        self.firing_cache = FiringCache()
69
+        from opencombat.gui.actor import BaseActor
70
+        self.actor = typing.cast(BaseActor, self.actor)
71
+        self.path_manager = PathManager(
72
+            self.config.resolve('global.include_path.graphics'),
73
+        )
74
+
75
+    def build(self) -> None:
76
+        super().build()
77
+        self.build_firing()
78
+
79
+    def build_firing(self) -> None:
80
+        for mode in self.actor.get_modes():
81
+            mode_image_path = self.actor.default_image_path  # FIXME !
82
+            mode_image = Image.open(self.path_manager.path(mode_image_path))
83
+
84
+            for weapon in self.actor.weapons:
85
+                images = self.actor.weapon_image_applier.get_firing_image(
86
+                    mode=mode,
87
+                    weapon_type=weapon,
88
+                )
89
+
90
+                for position in range(len(images)):
91
+                    position_image = images[position]
92
+
93
+                    final_image = mode_image.copy()
94
+                    final_image.paste(
95
+                        position_image,
96
+                        (0, 0),
97
+                        position_image,
98
+                    )
99
+
100
+                    self.firing_cache.add(mode, weapon, final_image)

+ 38 - 3
opencombat/gui/weapon.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import typing
2
 import typing
3
 
3
 
4
+import pyglet
4
 from PIL import Image
5
 from PIL import Image
6
+from pyglet.image import ImageData
5
 from synergine2.config import Config
7
 from synergine2.config import Config
6
 from synergine2_cocos2d.util import PathManager
8
 from synergine2_cocos2d.util import PathManager
7
 
9
 
26
         actor: 'BaseActor',
28
         actor: 'BaseActor',
27
     ) -> None:
29
     ) -> None:
28
         self.actor = actor
30
         self.actor = actor
29
-        self._images_scheme = self.get_images_scheme()
31
+        self._images_scheme = self.get_rest_images_scheme()
32
+        self._firing_images_scheme = self.get_firing_images_scheme()
30
         self.path_manager = PathManager(config.resolve('global.include_path.graphics'))
33
         self.path_manager = PathManager(config.resolve('global.include_path.graphics'))
31
         self._cache = {}  # type: typing.Dict[str, Image.Image]
34
         self._cache = {}  # type: typing.Dict[str, Image.Image]
35
+        self._firing_cache = {}  # type: typing.Dict[str, Image.Image]
32
 
36
 
33
-    def get_images_scheme(self) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]:
37
+    def get_rest_images_scheme(self) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]:
34
         return {
38
         return {
35
             POSITION_MAN_STAND_UP: {
39
             POSITION_MAN_STAND_UP: {
36
                 RIFFLE: [
40
                 RIFFLE: [
48
             }
52
             }
49
         }
53
         }
50
 
54
 
51
-    def get_default_image_for_weapon(self, mode: str, weapon_type: str) -> Image.Image:
55
+    def get_firing_images_scheme(self) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]:
56
+        return {
57
+            POSITION_MAN_STAND_UP: {
58
+                RIFFLE: [
59
+                    'actors/man_weap1_firing1.png',
60
+                    'actors/man_weap1_firing2.png',
61
+                    'actors/man_weap1_firing3.png',
62
+                ],
63
+            },
64
+        }
65
+
66
+    def get_image_for_weapon(self, mode: str, weapon_type: str) -> Image.Image:
52
         try:
67
         try:
53
             image_file_path = self.path_manager.path(
68
             image_file_path = self.path_manager.path(
54
                 self._images_scheme[mode][weapon_type][0],
69
                 self._images_scheme[mode][weapon_type][0],
63
                 'Unknown weapon "{}" for mode "{}"'.format(weapon_type, mode),
78
                 'Unknown weapon "{}" for mode "{}"'.format(weapon_type, mode),
64
             )
79
             )
65
 
80
 
81
+    def get_firing_image(
82
+        self, mode: str,
83
+        weapon_type: str,
84
+    ) -> typing.List[Image.Image]:
85
+        images = []
86
+        try:
87
+            image_file_paths = self._firing_images_scheme[mode][weapon_type]
88
+            for image_file_path in image_file_paths:
89
+                final_path = self.path_manager.path(image_file_path)
90
+                try:
91
+                    images.append(self._firing_cache[final_path])
92
+                except KeyError:
93
+                    self._firing_cache[image_file_path] = Image.open(final_path)
94
+                    images.append(self._firing_cache[image_file_path])
95
+            return images
96
+        except KeyError:
97
+            raise UnknownWeapon(
98
+                'Unknown weapon "{}" for mode "{}"'.format(weapon_type, mode),
99
+            )
100
+
66
     def get_animation_image_for_weapon(
101
     def get_animation_image_for_weapon(
67
         self,
102
         self,
68
         mode: str,
103
         mode: str,