Mercurial > touhou
comparison pytouhou/formats/anm0.py @ 608:725bd24235a2
Make ANM0 pure-python again, by moving the Cython-dependent ANM class into its own module.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Wed, 26 Nov 2014 14:00:17 +0100 |
parents | pytouhou/formats/anm0.pyx@43ecf0f98f4d |
children | d18c0bf11138 |
comparison
equal
deleted
inserted
replaced
607:9dbc234ea087 | 608:725bd24235a2 |
---|---|
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.animation import Animation | |
28 from pytouhou.formats.thtx import Texture | |
29 | |
30 | |
31 logger = get_logger(__name__) | |
32 | |
33 #TODO: refactor/clean up | |
34 | |
35 | |
36 class Script(list): | |
37 def __init__(self): | |
38 list.__init__(self) | |
39 self.interrupts = {} | |
40 | |
41 | |
42 | |
43 class ANM0(Animation): | |
44 _instructions = {0: {0: ('', 'delete'), | |
45 1: ('I', 'set_sprite'), | |
46 2: ('ff', 'set_scale'), | |
47 3: ('I', 'set_alpha'), | |
48 4: ('BBBx', 'set_color'), | |
49 5: ('I', 'jump'), | |
50 7: ('', 'toggle_mirrored'), | |
51 9: ('fff', 'set_3d_rotations'), | |
52 10: ('fff', 'set_3d_rotations_speed'), | |
53 11: ('ff', 'set_scale_speed'), | |
54 12: ('ii', 'fade'), | |
55 13: ('', 'set_blendmode_add'), | |
56 14: ('', 'set_blendmode_alphablend'), | |
57 15: ('', 'keep_still'), | |
58 16: ('ii', 'set_random_sprite'), | |
59 17: ('fff', 'set_3d_translation'), | |
60 18: ('fffi', 'move_to_linear'), | |
61 19: ('fffi', 'move_to_decel'), | |
62 20: ('fffi', 'move_to_accel'), | |
63 21: ('', 'wait'), | |
64 22: ('i', 'interrupt_label'), | |
65 23: ('', 'set_corner_relative_placement'), | |
66 24: ('', 'wait_ex'), | |
67 25: ('i', 'set_allow_offset'), #TODO: better name | |
68 26: ('i', 'set_automatic_orientation'), | |
69 27: ('f', 'shift_texture_x'), | |
70 28: ('f', 'shift_texture_y'), | |
71 29: ('i', 'set_visible'), | |
72 30: ('ffi', 'scale_in'), | |
73 31: ('i', None)}, | |
74 | |
75 2: {0: ('', 'noop'), | |
76 1: ('', 'delete'), | |
77 2: ('', 'keep_still'), | |
78 3: ('I', 'set_sprite'), | |
79 4: ('II', 'jump_bis'), | |
80 5: ('III', 'jump_ex'), | |
81 6: ('fff', 'set_3d_translation'), | |
82 7: ('ff', 'set_scale'), | |
83 8: ('I', 'set_alpha'), | |
84 9: ('BBBx', 'set_color'), | |
85 10: ('', 'toggle_mirrored'), | |
86 12: ('fff', 'set_3d_rotations'), | |
87 13: ('fff', 'set_3d_rotations_speed'), | |
88 14: ('ff', 'set_scale_speed'), | |
89 15: ('ii', 'fade'), | |
90 16: ('I', 'set_blendmode'), | |
91 17: ('fffi', 'move_to_linear'), | |
92 18: ('fffi', 'move_to_decel'), | |
93 19: ('fffi', 'move_to_accel'), | |
94 20: ('', 'wait'), | |
95 21: ('i', 'interrupt_label'), | |
96 22: ('', 'set_corner_relative_placement'), | |
97 23: ('', 'wait_ex'), | |
98 24: ('i', 'set_allow_offset'), #TODO: better name | |
99 25: ('i', 'set_automatic_orientation'), | |
100 26: ('f', 'shift_texture_x'), | |
101 27: ('f', 'shift_texture_y'), | |
102 28: ('i', 'set_visible'), | |
103 29: ('ffi', 'scale_in'), | |
104 30: ('i', None), | |
105 31: ('I', None), | |
106 32: ('IIfff', 'move_in_linear_bis'), | |
107 33: ('IIBBBx', 'change_color_in'), | |
108 34: ('III', 'fade_bis'), | |
109 35: ('IIfff', 'rotate_in_bis'), | |
110 36: ('IIff', 'scale_in_bis'), | |
111 37: ('II', 'set_int'), | |
112 38: ('ff', 'set_float'), | |
113 42: ('ff', 'decrement_float'), | |
114 50: ('fff', 'add_float'), | |
115 52: ('fff', 'substract_float'), | |
116 55: ('III', 'divide_int'), | |
117 59: ('II', 'set_random_int'), | |
118 60: ('ff', 'set_random_float'), | |
119 69: ('IIII', 'branch_if_not_equal'), | |
120 79: ('I', 'wait_duration'), | |
121 80: ('I', None)}} | |
122 | |
123 | |
124 @classmethod | |
125 def read(cls, file): | |
126 anm_list = [] | |
127 start_offset = 0 | |
128 while True: | |
129 file.seek(start_offset) | |
130 nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) | |
131 width, height, fmt, unknown1 = unpack('<IIII', file.read(16)) | |
132 first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) | |
133 version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24)) | |
134 | |
135 if version == 0: | |
136 assert zero1 == 0 | |
137 assert unknown3 == 0 | |
138 assert has_data == 0 | |
139 elif version == 2: | |
140 assert zero1 == 0 | |
141 assert secondary_name_offset == 0 | |
142 assert has_data == 1 # Can be false but we don’t support that yet. | |
143 else: | |
144 raise WrongFormatError(version) | |
145 | |
146 instructions = cls._instructions[version] | |
147 | |
148 sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] | |
149 script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] | |
150 | |
151 self = cls() | |
152 | |
153 self.size = (width, height) | |
154 self.version = version | |
155 | |
156 # Names | |
157 if first_name_offset: | |
158 file.seek(start_offset + first_name_offset) | |
159 self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? | |
160 if secondary_name_offset: | |
161 file.seek(start_offset + secondary_name_offset) | |
162 self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? | |
163 | |
164 | |
165 # Sprites | |
166 for offset in sprite_offsets: | |
167 file.seek(start_offset + offset) | |
168 idx, x, y, width, height = unpack('<Iffff', file.read(20)) | |
169 self.sprites[idx] = x, y, width, height | |
170 | |
171 | |
172 # Scripts | |
173 for i, offset in script_offsets: | |
174 self.scripts[i] = Script() | |
175 instruction_offsets = [] | |
176 file.seek(start_offset + offset) | |
177 while True: | |
178 instruction_offsets.append(file.tell() - (start_offset + offset)) | |
179 if version == 0: | |
180 time, opcode, size = unpack('<HBB', file.read(4)) | |
181 elif version == 2: | |
182 opcode, size, time, mask = unpack('<HHHH', file.read(8)) | |
183 if opcode == 0xffff: | |
184 break | |
185 size -= 8 | |
186 data = file.read(size) | |
187 if opcode in instructions: | |
188 args = unpack('<%s' % instructions[opcode][0], data) | |
189 else: | |
190 args = (data,) | |
191 logger.warn('unknown opcode %d', opcode) | |
192 | |
193 self.scripts[i].append((time, opcode, args)) | |
194 if version == 0 and opcode == 0: | |
195 break | |
196 | |
197 # Translate offsets to instruction pointers and register interrupts | |
198 for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])): | |
199 time, opcode, args = instr | |
200 if version == 0: | |
201 if opcode == 5: | |
202 args = (instruction_offsets.index(args[0]),) | |
203 elif opcode == 22: | |
204 interrupt = args[0] | |
205 self.scripts[i].interrupts[interrupt] = j + 1 | |
206 elif version == 2: | |
207 if opcode == 4: | |
208 args = (instruction_offsets.index(args[0]), args[1]) | |
209 elif opcode == 5: | |
210 args = (args[0], instruction_offsets.index(args[1]), args[2]) | |
211 elif opcode == 21: | |
212 interrupt = args[0] | |
213 self.scripts[i].interrupts[interrupt] = j + 1 | |
214 elif opcode == 69: | |
215 args = (args[0], args[1], instruction_offsets.index(args[2]), args[3]) | |
216 self.scripts[i][j] = time, opcode, args | |
217 | |
218 # Texture | |
219 if has_data: | |
220 file.seek(start_offset + texture_offset) | |
221 magic = file.read(4) | |
222 assert magic == b'THTX' | |
223 zero, fmt, width, height, size = unpack('<HHHHI', file.read(12)) | |
224 assert zero == 0 | |
225 data = file.read(size) | |
226 self.texture = Texture(width, height, fmt, data) | |
227 | |
228 anm_list.append(self) | |
229 | |
230 if next_offset: | |
231 start_offset += next_offset | |
232 else: | |
233 break | |
234 | |
235 return anm_list |