Mercurial > touhou
comparison pytouhou/formats/anm0.pyx @ 525:43ecf0f98f4d
Precalculate the inverse of the texture size at ANM load, to not recalculate at every sprite change.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Wed, 18 Dec 2013 18:15:45 +0100 |
parents | pytouhou/formats/anm0.py@40d5f3083ebc |
children |
comparison
equal
deleted
inserted
replaced
524:7f016dfbdfb1 | 525:43ecf0f98f4d |
---|---|
1 # -*- encoding: utf-8 -*- | |
2 ## | |
3 ## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> | |
4 ## | |
5 ## This program is free software; you can redistribute it and/or modify | |
6 ## it under the terms of the GNU General Public License as published | |
7 ## by the Free Software Foundation; version 3 only. | |
8 ## | |
9 ## This program is distributed in the hope that it will be useful, | |
10 ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 ## GNU General Public License for more details. | |
13 ## | |
14 | |
15 """ANM0 files handling. | |
16 | |
17 This module provides classes for handling the ANM0 file format. | |
18 The ANM0 format is a format used in Touhou 6: EoSD to describe sprites | |
19 and animations. | |
20 Almost everything rendered in the game is described by an ANM0 file. | |
21 """ | |
22 | |
23 from struct import pack, unpack | |
24 from pytouhou.utils.helpers import read_string, get_logger | |
25 | |
26 from pytouhou.formats import WrongFormatError | |
27 from pytouhou.formats.thtx import Texture | |
28 | |
29 | |
30 logger = get_logger(__name__) | |
31 | |
32 #TODO: refactor/clean up | |
33 | |
34 | |
35 class Script(list): | |
36 def __init__(self): | |
37 list.__init__(self) | |
38 self.interrupts = {} | |
39 | |
40 | |
41 | |
42 cdef class ANM: | |
43 def __init__(self): | |
44 self.version = 0 | |
45 self.size_inv[:] = [0, 0] | |
46 self.first_name = None | |
47 self.secondary_name = None | |
48 self.sprites = {} | |
49 self.scripts = {} | |
50 | |
51 property size: | |
52 def __set__(self, tuple value): | |
53 width, height = value | |
54 self.size_inv[:] = [1. / <double>width, 1. / <double>height] | |
55 | |
56 | |
57 | |
58 class ANM0(object): | |
59 _instructions = {0: {0: ('', 'delete'), | |
60 1: ('I', 'set_sprite'), | |
61 2: ('ff', 'set_scale'), | |
62 3: ('I', 'set_alpha'), | |
63 4: ('BBBx', 'set_color'), | |
64 5: ('I', 'jump'), | |
65 7: ('', 'toggle_mirrored'), | |
66 9: ('fff', 'set_3d_rotations'), | |
67 10: ('fff', 'set_3d_rotations_speed'), | |
68 11: ('ff', 'set_scale_speed'), | |
69 12: ('ii', 'fade'), | |
70 13: ('', 'set_blendmode_add'), | |
71 14: ('', 'set_blendmode_alphablend'), | |
72 15: ('', 'keep_still'), | |
73 16: ('ii', 'set_random_sprite'), | |
74 17: ('fff', 'set_3d_translation'), | |
75 18: ('fffi', 'move_to_linear'), | |
76 19: ('fffi', 'move_to_decel'), | |
77 20: ('fffi', 'move_to_accel'), | |
78 21: ('', 'wait'), | |
79 22: ('i', 'interrupt_label'), | |
80 23: ('', 'set_corner_relative_placement'), | |
81 24: ('', 'wait_ex'), | |
82 25: ('i', 'set_allow_offset'), #TODO: better name | |
83 26: ('i', 'set_automatic_orientation'), | |
84 27: ('f', 'shift_texture_x'), | |
85 28: ('f', 'shift_texture_y'), | |
86 29: ('i', 'set_visible'), | |
87 30: ('ffi', 'scale_in'), | |
88 31: ('i', None)}, | |
89 | |
90 2: {0: ('', 'noop'), | |
91 1: ('', 'delete'), | |
92 2: ('', 'keep_still'), | |
93 3: ('I', 'set_sprite'), | |
94 4: ('II', 'jump_bis'), | |
95 5: ('III', 'jump_ex'), | |
96 6: ('fff', 'set_3d_translation'), | |
97 7: ('ff', 'set_scale'), | |
98 8: ('I', 'set_alpha'), | |
99 9: ('BBBx', 'set_color'), | |
100 10: ('', 'toggle_mirrored'), | |
101 12: ('fff', 'set_3d_rotations'), | |
102 13: ('fff', 'set_3d_rotations_speed'), | |
103 14: ('ff', 'set_scale_speed'), | |
104 15: ('ii', 'fade'), | |
105 16: ('I', 'set_blendmode'), | |
106 17: ('fffi', 'move_to_linear'), | |
107 18: ('fffi', 'move_to_decel'), | |
108 19: ('fffi', 'move_to_accel'), | |
109 20: ('', 'wait'), | |
110 21: ('i', 'interrupt_label'), | |
111 22: ('', 'set_corner_relative_placement'), | |
112 23: ('', 'wait_ex'), | |
113 24: ('i', 'set_allow_offset'), #TODO: better name | |
114 25: ('i', 'set_automatic_orientation'), | |
115 26: ('f', 'shift_texture_x'), | |
116 27: ('f', 'shift_texture_y'), | |
117 28: ('i', 'set_visible'), | |
118 29: ('ffi', 'scale_in'), | |
119 30: ('i', None), | |
120 31: ('I', None), | |
121 32: ('IIfff', 'move_in_linear_bis'), | |
122 33: ('IIBBBx', 'change_color_in'), | |
123 34: ('III', 'fade_bis'), | |
124 35: ('IIfff', 'rotate_in_bis'), | |
125 36: ('IIff', 'scale_in_bis'), | |
126 37: ('II', 'set_int'), | |
127 38: ('ff', 'set_float'), | |
128 42: ('ff', 'decrement_float'), | |
129 50: ('fff', 'add_float'), | |
130 52: ('fff', 'substract_float'), | |
131 55: ('III', 'divide_int'), | |
132 59: ('II', 'set_random_int'), | |
133 60: ('ff', 'set_random_float'), | |
134 69: ('IIII', 'branch_if_not_equal'), | |
135 79: ('I', 'wait_duration'), | |
136 80: ('I', None)}} | |
137 | |
138 | |
139 @classmethod | |
140 def read(cls, file): | |
141 anm_list = [] | |
142 start_offset = 0 | |
143 while True: | |
144 file.seek(start_offset) | |
145 nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) | |
146 width, height, fmt, unknown1 = unpack('<IIII', file.read(16)) | |
147 first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) | |
148 version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24)) | |
149 | |
150 if version == 0: | |
151 assert zero1 == 0 | |
152 assert unknown3 == 0 | |
153 assert has_data == 0 | |
154 elif version == 2: | |
155 assert zero1 == 0 | |
156 assert secondary_name_offset == 0 | |
157 assert has_data == 1 # Can be false but we don’t support that yet. | |
158 else: | |
159 raise WrongFormatError(version) | |
160 | |
161 instructions = cls._instructions[version] | |
162 | |
163 sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] | |
164 script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] | |
165 | |
166 self = ANM() | |
167 | |
168 self.size = (width, height) | |
169 self.version = version | |
170 | |
171 # Names | |
172 if first_name_offset: | |
173 file.seek(start_offset + first_name_offset) | |
174 self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? | |
175 if secondary_name_offset: | |
176 file.seek(start_offset + secondary_name_offset) | |
177 self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? | |
178 | |
179 | |
180 # Sprites | |
181 for offset in sprite_offsets: | |
182 file.seek(start_offset + offset) | |
183 idx, x, y, width, height = unpack('<Iffff', file.read(20)) | |
184 self.sprites[idx] = x, y, width, height | |
185 | |
186 | |
187 # Scripts | |
188 for i, offset in script_offsets: | |
189 self.scripts[i] = Script() | |
190 instruction_offsets = [] | |
191 file.seek(start_offset + offset) | |
192 while True: | |
193 instruction_offsets.append(file.tell() - (start_offset + offset)) | |
194 if version == 0: | |
195 time, opcode, size = unpack('<HBB', file.read(4)) | |
196 elif version == 2: | |
197 opcode, size, time, mask = unpack('<HHHH', file.read(8)) | |
198 if opcode == 0xffff: | |
199 break | |
200 size -= 8 | |
201 data = file.read(size) | |
202 if opcode in instructions: | |
203 args = unpack('<%s' % instructions[opcode][0], data) | |
204 else: | |
205 args = (data,) | |
206 logger.warn('unknown opcode %d', opcode) | |
207 | |
208 self.scripts[i].append((time, opcode, args)) | |
209 if version == 0 and opcode == 0: | |
210 break | |
211 | |
212 # Translate offsets to instruction pointers and register interrupts | |
213 for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])): | |
214 time, opcode, args = instr | |
215 if version == 0: | |
216 if opcode == 5: | |
217 args = (instruction_offsets.index(args[0]),) | |
218 elif opcode == 22: | |
219 interrupt = args[0] | |
220 self.scripts[i].interrupts[interrupt] = j + 1 | |
221 elif version == 2: | |
222 if opcode == 4: | |
223 args = (instruction_offsets.index(args[0]), args[1]) | |
224 elif opcode == 5: | |
225 args = (args[0], instruction_offsets.index(args[1]), args[2]) | |
226 elif opcode == 21: | |
227 interrupt = args[0] | |
228 self.scripts[i].interrupts[interrupt] = j + 1 | |
229 elif opcode == 69: | |
230 args = (args[0], args[1], instruction_offsets.index(args[2]), args[3]) | |
231 self.scripts[i][j] = time, opcode, args | |
232 | |
233 # Texture | |
234 if has_data: | |
235 file.seek(start_offset + texture_offset) | |
236 magic = file.read(4) | |
237 assert magic == b'THTX' | |
238 zero, fmt, width, height, size = unpack('<HHHHI', file.read(12)) | |
239 assert zero == 0 | |
240 data = file.read(size) | |
241 self.texture = Texture(width, height, fmt, data) | |
242 | |
243 anm_list.append(self) | |
244 | |
245 if next_offset: | |
246 start_offset += next_offset | |
247 else: | |
248 break | |
249 | |
250 return anm_list |