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.
25 Or, for very simple actions, add it to the Action.CUSTOM
26 dictionary. The key should be the type of the action and the
27 value a 3-ary callable that recieves the Action instance, the
28 owner, and a list to append created bullets to.
33 def __init__(self
, owner
, parent
, actions
, params
, rank
, repeat
=1):
34 self
.actions
= actions
41 self
.direction_frames
= 0
46 self
.previous_fire_direction
= 0
47 self
.previous_fire_speed
= 0
52 self
.copy_state(parent
)
55 return "%s(pc=%r, actions=%r)" % (
56 type(self
).__name
__, self
.pc
, self
.actions
)
59 """End this action and its parents."""
65 def copy_state(self
, other
):
66 """Copy fire/movement state from other to self."""
67 self
.direction_frames
= other
.direction_frames
68 self
.direction
= other
.direction
69 self
.aiming
= other
.aiming
70 self
.speed_frames
= other
.speed_frames
71 self
.speed
= other
.speed
72 self
.accel_frames
= other
.accel_frames
75 self
.previous_fire_direction
= other
.previous_fire_direction
76 self
.previous_fire_speed
= other
.previous_fire_speed
78 def step(self
, owner
, created
):
79 """Advance by one frame."""
80 s_params
= self
.params
83 if self
.speed_frames
> 0:
84 self
.speed_frames
-= 1
85 owner
.speed
+= self
.speed
87 if self
.direction_frames
> 0:
88 # I'm still not sure what the aim check is supposed to do.
89 self
.direction_frames
-= 1
90 if self
.aiming
and self
.direction_frames
<= 0:
91 owner
.direction
+= owner
.aim
93 owner
.direction
+= self
.direction
95 if self
.accel_frames
> 0:
96 self
.accel_frames
-= 1
103 if self
.wait_frames
> 0:
104 self
.wait_frames
-= 1
111 action
= self
.actions
[self
.pc
]
117 if self
.parent
is not None:
118 self
.parent
.copy_state(self
)
119 owner
.replace(self
, self
.parent
)
123 action
= self
.actions
[self
.pc
]
125 if isinstance(action
, parser
.Repeat
):
126 repeat
, (actions
, params
) = action(s_params
, rank
)
127 child
= self
.__class
__(
128 owner
, self
, actions
, params
, rank
, repeat
)
129 owner
.replace(self
, child
)
130 child
.step(owner
, created
)
133 elif isinstance(action
, (parser
.ActionDef
, parser
.ActionRef
)):
134 actions
, params
= action(s_params
, rank
)
135 child
= self
.__class
__(owner
, self
, actions
, params
, rank
)
136 owner
.replace(self
, child
)
137 child
.step(owner
, created
)
140 elif isinstance(action
, (parser
.FireDef
, parser
.FireRef
)):
141 direction
, speed
, offset
, tags
, appearance
, actions
= action(
144 direction
, type = direction
145 if type == "aim" or type is None:
146 direction
+= owner
.aim
147 elif type == "sequence":
148 direction
+= self
.previous_fire_direction
149 elif type == "relative":
150 direction
+= owner
.direction
152 direction
= owner
.aim
153 self
.previous_fire_direction
= direction
157 if type == "sequence":
158 speed
+= self
.previous_fire_speed
159 elif type == "relative":
160 # The reference Noiz implementation uses
161 # prvFireSpeed here, but the standard is
162 # pretty clear -- "In case of the type is
163 # "relative", ... the speed is relative to the
164 # speed of this bullet."
168 self
.previous_fire_speed
= speed
170 x
, y
= owner
.x
, owner
.y
172 off_x
, off_y
= offset(s_params
, rank
)
173 if offset
.type == "relative":
176 x
+= c
* off_x
+ s
* off_y
177 y
+= s
* off_x
- c
* off_y
182 if appearance
is None:
183 appearance
= owner
.appearance
184 bullet
= owner
.__class
__(
185 x
=x
, y
=y
, direction
=direction
, speed
=speed
,
186 target
=owner
.target
, actions
=actions
, rank
=rank
,
187 appearance
=appearance
, tags
=tags
, Action
=self
.__class
__)
188 created
.append(bullet
)
190 elif isinstance(action
, parser
.ChangeSpeed
):
191 frames
, (speed
, type) = action(s_params
, rank
)
192 self
.speed_frames
= frames
194 if type == "absolute":
196 elif type == "relative":
198 elif type == "sequence":
200 elif type == "relative":
201 self
.speed
= speed
/ frames
203 self
.speed
= (speed
- owner
.speed
) / frames
205 elif isinstance(action
, parser
.ChangeDirection
):
206 frames
, (direction
, type) = action(s_params
, rank
)
207 self
.direction_frames
= frames
209 if type == "sequence":
210 self
.direction
= direction
212 if type == "absolute":
213 direction
-= owner
.direction
214 elif type != "relative": # aim or default
216 direction
+= owner
.aim
- owner
.direction
218 # Normalize to [-pi, pi).
219 direction
= (direction
+ PI
) % PI_2
- PI
221 owner
.direction
+= direction
223 self
.direction
= direction
/ frames
225 elif isinstance(action
, parser
.Accel
):
226 frames
, horizontal
, vertical
= action(s_params
, rank
)
227 self
.accel_frames
= frames
229 mx
, type = horizontal
231 if type == "absolute":
233 elif type == "relative":
235 elif type == "sequence":
237 elif type == "absolute":
238 self
.mx
= (mx
- owner
.mx
) / frames
239 elif type == "relative":
240 self
.mx
= mx
/ frames
244 if type == "absolute":
246 elif type == "relative":
248 elif type == "sequence":
250 elif type == "absolute":
251 self
.my
= (my
- owner
.my
) / frames
252 elif type == "relative":
253 self
.my
= my
/ frames
255 elif isinstance(action
, parser
.Tag
):
256 owner
.tags
.add(action
.tag
)
258 elif isinstance(action
, parser
.Untag
):
260 owner
.tags
.remove(action
.tag
)
264 elif isinstance(action
, parser
.Wait
):
265 self
.wait_frames
= action(s_params
, rank
)
268 elif isinstance(action
, parser
.Vanish
):
272 elif isinstance(action
, parser
.Appearance
):
273 owner
.appearance
= action
.appearance
276 self
.handle(action
, owner
, created
)
278 def handle(self
, action
, owner
, created
):
279 """Override in subclasses for new action types."""
281 handler
= self
.CUSTOM
[type(action
)]
283 raise NotImplementedError(action
.__class
__.__name
__)
285 handler(self
, owner
, created
)
287 class Bullet(object):
288 """Simple bullet implementation.
291 x, y - current X/Y position
292 px, py - X/Y position prior to the last step
293 mx, my - X/Y axis-oriented speed modifier ("acceleration")
294 direction - direction of movement, in radians
295 speed - speed of movement, in units per frame
296 target - object with .x and .y fields for "aim" directions
297 vanished - set to true by a <vanish> action
298 rank - game difficulty, 0 to 1, default 0.5
299 tags - string tags set by the running actions
300 appearance - string used to set bullet appearance
302 Contructor Arguments:
303 x, y, direction, speed, target, rank, tags, appearance
304 - same as the above attributes
305 actions - internal action list
306 Action - custom Action constructor
311 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
312 actions
=(), rank
=0.5, tags
=(), appearance
=None,
318 self
.direction
= direction
320 self
.vanished
= False
323 self
.tags
= set(tags
)
324 self
.appearance
= appearance
325 # New bullets reset the parent hierarchy.
326 self
._actions
= [Action(self
, None, action
, params
, rank
)
327 for action
, params
in actions
]
330 def FromDocument(cls
, doc
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
331 params
=(), rank
=0.5, Action
=Action
):
332 """Construct a new Bullet from a loaded BulletML document."""
333 actions
= [a(params
, rank
) for a
in doc
.actions
]
334 return cls(x
=x
, y
=y
, direction
=direction
, speed
=speed
,
335 target
=target
, actions
=actions
, rank
=rank
, Action
=Action
)
338 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
339 "actions=%r, target=%r, appearance=vanished=%r)") % (
340 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
341 self
.direction
, self
.speed
, self
._actions
, self
.target
,
342 self
.appearance
, self
.vanished
)
346 """Angle to the target, in radians.
348 If the target does not exist or cannot be found, return 0.
351 target_x
= self
.target
.x
352 target_y
= self
.target
.y
353 except AttributeError:
356 return atan2(target_x
- self
.x
, target_y
- self
.y
)
360 """Check if this bullet is finished running.
362 A bullet is finished when it has vanished, and all its
363 actions have finished.
365 If this is true, the bullet should be removed from the screen.
366 (You will probably want to cull it under other circumstances
369 if not self
.vanished
:
371 for action
in self
._actions
:
372 if not action
.finished
:
377 """Vanish this bullet and stop all actions."""
379 for action
in self
._actions
:
383 def replace(self
, old
, new
):
384 """Replace an active action with another.
386 This is mostly used by actions internally to queue children.
389 idx
= self
._actions
.index(old
)
393 self
._actions
[idx
] = new
396 """Advance by one frame.
398 This updates the position and velocity, and may also set the
401 It returns any new bullets this bullet spawned during this step.
405 for action
in self
._actions
:
406 action
.step(self
, created
)
410 self
.x
+= self
.mx
+ sin(self
.direction
) * self
.speed
411 self
.y
+= -self
.my
+ cos(self
.direction
) * self
.speed