b2a0aaecfe5f86c712c6bf775d5dcb62dd45d6d5
1 """BulletML implementation."""
3 from __future__
import division
5 from math
import atan2
, sin
, cos
, pi
as PI
7 from bulletml
import parser
11 __all__
= ["Action", "Bullet"]
14 """Running action implementation.
16 To implement new actions,
18 - Add a new element/class pair to parser.ActionDef.CONSTRUCTORS.
19 It should support FromXML, __getstate__, and __setstate__.
20 - Subclass impl.Action and override the 'handle' method to handle
21 your custom action type.
22 - Pass the impl.Bullet constructor your Action subclass when
23 creating your root Bullet.
26 def __init__(self
, owner
, parent
, actions
, params
, rank
, repeat
=1):
27 self
.actions
= actions
34 self
.direction_frames
= 0
39 self
.previous_fire_direction
= 0
40 self
.previous_fire_speed
= 0
45 self
.copy_state(parent
)
48 return "%s(pc=%r, actions=%r)" % (
49 type(self
).__name
__, self
.pc
, self
.actions
)
52 """End this action and its parents."""
58 def copy_state(self
, other
):
59 """Copy fire/movement state from other to self."""
60 self
.direction_frames
= other
.direction_frames
61 self
.direction
= other
.direction
62 self
.aiming
= other
.aiming
63 self
.speed_frames
= other
.speed_frames
64 self
.speed
= other
.speed
65 self
.accel_frames
= other
.accel_frames
68 self
.previous_fire_direction
= other
.previous_fire_direction
69 self
.previous_fire_speed
= other
.previous_fire_speed
71 def step(self
, owner
, created
):
72 """Advance by one frame."""
73 s_params
= self
.params
76 if self
.speed_frames
> 0:
77 self
.speed_frames
-= 1
78 owner
.speed
+= self
.speed
80 if self
.direction_frames
> 0:
81 # The Noiz implementation was a little weird here, I think
82 # there was a bug in it that prevented it from working if
83 # the frame count was 1. I'm still not sure what the aim
84 # check is supposed to do, exactly.
85 self
.direction_frames
-= 1
86 if self
.aiming
and self
.direction_frames
<= 0:
87 owner
.direction
+= owner
.aim
89 owner
.direction
+= self
.direction
91 if self
.accel_frames
> 0:
92 self
.accel_frames
-= 1
99 if self
.wait_frames
> 0:
100 self
.wait_frames
-= 1
107 action
= self
.actions
[self
.pc
]
113 if self
.parent
is not None:
114 self
.parent
.copy_state(self
)
115 owner
.replace(self
, self
.parent
)
119 action
= self
.actions
[self
.pc
]
121 if isinstance(action
, parser
.Repeat
):
122 repeat
, (actions
, params
) = action(s_params
, rank
)
123 child
= self
.__class
__(
124 owner
, self
, actions
, params
, rank
, repeat
)
125 owner
.replace(self
, child
)
126 child
.step(owner
, created
)
129 elif isinstance(action
, (parser
.ActionDef
, parser
.ActionRef
)):
130 actions
, params
= action(s_params
, rank
)
131 child
= self
.__class
__(owner
, self
, actions
, params
, rank
)
132 owner
.replace(self
, child
)
133 child
.step(owner
, created
)
136 elif isinstance(action
, (parser
.FireDef
, parser
.FireRef
)):
137 direction
, speed
, offset
, tags
, appearance
, actions
= action(
140 direction
, type = direction
141 if type == "aim" or type is None:
142 direction
+= owner
.aim
143 elif type == "sequence":
144 direction
+= self
.previous_fire_direction
145 elif type == "relative":
146 direction
+= owner
.direction
148 direction
= owner
.aim
149 self
.previous_fire_direction
= direction
153 if type == "sequence":
154 speed
+= self
.previous_fire_speed
155 elif type == "relative":
156 # The reference Noiz implementation uses
157 # prvFireSpeed here, but the standard is
158 # pretty clear -- "In case of the type is
159 # "relative", ... the speed is relative to the
160 # speed of this bullet."
164 self
.previous_fire_speed
= speed
166 x
, y
= owner
.x
, owner
.y
168 off_x
, off_y
= offset(s_params
, rank
)
169 if offset
.type == "relative":
172 x
+= c
* off_x
+ s
* off_y
173 y
+= s
* off_x
- c
* off_y
178 if appearance
is None:
179 appearance
= owner
.appearance
180 bullet
= owner
.__class
__(
181 x
=x
, y
=y
, direction
=direction
, speed
=speed
,
182 target
=owner
.target
, actions
=actions
, rank
=rank
,
183 appearance
=appearance
, tags
=tags
, Action
=self
.__class
__)
184 created
.append(bullet
)
186 elif isinstance(action
, parser
.ChangeSpeed
):
187 frames
, (speed
, type) = action(s_params
, rank
)
188 self
.speed_frames
= frames
190 if type == "absolute":
192 elif type == "relative":
194 elif type == "sequence":
196 elif type == "relative":
197 self
.speed
= speed
/ frames
199 self
.speed
= (speed
- owner
.speed
) / frames
201 elif isinstance(action
, parser
.ChangeDirection
):
202 frames
, (direction
, type) = action(s_params
, rank
)
203 self
.direction_frames
= frames
205 if type == "sequence":
206 self
.direction
= direction
208 if type == "absolute":
209 direction
-= owner
.direction
210 elif type != "relative": # aim or default
212 direction
+= owner
.aim
- owner
.direction
214 # Normalize to [-pi, pi).
215 direction
= (direction
+ PI
) % PI_2
- PI
217 owner
.direction
+= direction
219 self
.direction
= direction
/ frames
221 elif isinstance(action
, parser
.Accel
):
222 frames
, horizontal
, vertical
= action(s_params
, rank
)
223 self
.accel_frames
= frames
225 mx
, type = horizontal
227 if type == "absolute":
229 elif type == "relative":
231 elif type == "sequence":
233 elif type == "absolute":
234 self
.mx
= (mx
- owner
.mx
) / frames
235 elif type == "relative":
236 self
.mx
= mx
/ frames
240 if type == "absolute":
242 elif type == "relative":
244 elif type == "sequence":
246 elif type == "absolute":
247 self
.my
= (my
- owner
.my
) / frames
248 elif type == "relative":
249 self
.my
= my
/ frames
251 elif isinstance(action
, parser
.Tag
):
252 owner
.tags
.add(action
.tag
)
254 elif isinstance(action
, parser
.Untag
):
256 owner
.tags
.remove(action
.tag
)
260 elif isinstance(action
, parser
.Wait
):
261 self
.wait_frames
= action(s_params
, rank
)
264 elif isinstance(action
, parser
.Vanish
):
268 elif isinstance(action
, parser
.Appearance
):
269 owner
.appearance
= action
.appearance
272 self
.handle(action
, owner
, created
)
274 def handle(self
, action
, owner
, created
):
275 """Override in subclasses for new action types."""
276 raise NotImplementedError(action
.__class
__.__name
__)
278 class Bullet(object):
279 """Simple bullet implementation.
282 x, y - current X/Y position
283 px, py - X/Y position prior to the last step
284 mx, my - X/Y axis-oriented speed modifier ("acceleration")
285 direction - direction of movement, in radians
286 speed - speed of movement, in units per frame
287 target - object with .x and .y fields for "aim" directions
288 vanished - set to true by a <vanish> action
289 rank - game difficulty, 0 to 1, default 0.5
290 tags - string tags set by the running actions
291 appearance - string used to set bullet appearance
293 Contructor Arguments:
294 x, y, direction, speed, target, rank, tags, appearance
295 - same as the above attributes
296 actions - internal action list
297 Action - custom Action constructor
302 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
303 actions
=(), rank
=0.5, tags
=(), appearance
=None,
309 self
.direction
= direction
311 self
.vanished
= False
314 self
.tags
= set(tags
)
315 self
.appearance
= appearance
316 # New bullets reset the parent hierarchy.
317 self
._actions
= [Action(self
, None, action
, params
, rank
)
318 for action
, params
in actions
]
321 def FromDocument(cls
, doc
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
322 params
=(), rank
=0.5, Action
=Action
):
323 """Construct a new Bullet from a loaded BulletML document."""
324 actions
= [a(params
, rank
) for a
in doc
.actions
]
325 return cls(x
=x
, y
=y
, direction
=direction
, speed
=speed
,
326 target
=target
, actions
=actions
, rank
=rank
, Action
=Action
)
329 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
330 "actions=%r, target=%r, appearance=vanished=%r)") % (
331 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
332 self
.direction
, self
.speed
, self
._actions
, self
.target
,
333 self
.appearance
, self
.vanished
)
337 """Angle to the target, in radians.
339 If the target does not exist or cannot be found, return 0.
342 target_x
= self
.target
.x
343 target_y
= self
.target
.y
344 except AttributeError:
347 return atan2(target_x
- self
.x
, target_y
- self
.y
)
351 """Check if this bullet is finished running.
353 A bullet is finished when it has vanished, and all its
354 actions have finished.
356 If this is true, the bullet should be removed from the screen.
357 (You will probably want to cull it under other circumstances
360 if not self
.vanished
:
362 for action
in self
._actions
:
363 if not action
.finished
:
368 """Vanish this bullet and stop all actions."""
370 for action
in self
._actions
:
374 def replace(self
, old
, new
):
375 """Replace an active action with another.
377 This is mostly used by actions internally to queue children.
380 idx
= self
._actions
.index(old
)
384 self
._actions
[idx
] = new
387 """Advance by one frame.
389 This updates the position and velocity, and may also set the
392 It returns any new bullets this bullet spawned during this step.
396 for action
in self
._actions
:
397 action
.step(self
, created
)
401 self
.x
+= self
.mx
+ sin(self
.direction
) * self
.speed
402 self
.y
+= -self
.my
+ cos(self
.direction
) * self
.speed