6e8138a042bec6374d2a21de47a3c5efb9456e88
1 """BulletML implementation."""
3 from __future__
import division
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
, sin
=math
.sin
, cos
=math
.cos
):
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
, sin
, cos
)
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
, sin
, cos
)
136 elif isinstance(action
, (parser
.FireDef
, parser
.FireRef
)):
137 direction
, speed
, offset
, tags
, actions
= action(s_params
, rank
)
139 direction
, type = direction
140 if type == "aim" or type is None:
141 direction
+= owner
.aim
142 elif type == "sequence":
143 direction
+= self
.previous_fire_direction
144 elif type == "relative":
145 direction
+= owner
.direction
147 direction
= owner
.aim
148 self
.previous_fire_direction
= direction
152 if type == "sequence":
153 speed
+= self
.previous_fire_speed
154 elif type == "relative":
155 # The reference Noiz implementation uses
156 # prvFireSpeed here, but the standard is
157 # pretty clear -- "In case of the type is
158 # "relative", ... the speed is relative to the
159 # speed of this bullet."
163 self
.previous_fire_speed
= speed
165 x
, y
= owner
.x
, owner
.y
167 off_x
, off_y
= offset(s_params
, rank
)
168 if offset
.type == "relative":
171 x
+= c
* off_x
+ s
* off_y
172 y
+= s
* off_x
- c
* off_y
177 bullet
= owner
.__class
__(
178 x
=x
, y
=y
, direction
=direction
, speed
=speed
,
179 target
=owner
.target
, actions
=actions
, rank
=rank
,
180 tags
=tags
, Action
=self
.__class
__)
181 created
.append(bullet
)
183 elif isinstance(action
, parser
.ChangeSpeed
):
184 frames
, (speed
, type) = action(s_params
, rank
)
185 self
.speed_frames
= frames
187 if type == "absolute":
189 elif type == "relative":
191 elif type == "sequence":
193 elif type == "relative":
194 self
.speed
= speed
/ frames
196 self
.speed
= (speed
- owner
.speed
) / frames
198 elif isinstance(action
, parser
.ChangeDirection
):
199 frames
, (direction
, type) = action(s_params
, rank
)
200 self
.direction_frames
= frames
202 if type == "sequence":
203 self
.direction
= direction
205 if type == "absolute":
206 direction
-= owner
.direction
207 elif type != "relative": # aim or default
209 direction
+= owner
.aim
- owner
.direction
211 # Normalize to [-pi, pi).
212 direction
= (direction
+ math
.pi
) % PI_2
- math
.pi
214 owner
.direction
+= direction
216 self
.direction
= direction
/ frames
218 elif isinstance(action
, parser
.Accel
):
219 frames
, horizontal
, vertical
= action(s_params
, rank
)
220 self
.accel_frames
= frames
222 mx
, type = horizontal
224 if type == "absolute":
226 elif type == "relative":
228 elif type == "sequence":
230 elif type == "absolute":
231 self
.mx
= (mx
- owner
.mx
) / frames
232 elif type == "relative":
233 self
.mx
= mx
/ frames
237 if type == "absolute":
239 elif type == "relative":
241 elif type == "sequence":
243 elif type == "absolute":
244 self
.my
= (my
- owner
.my
) / frames
245 elif type == "relative":
246 self
.my
= my
/ frames
248 elif isinstance(action
, parser
.Tag
):
249 owner
.tags
.add(action
.tag
)
251 elif isinstance(action
, parser
.Untag
):
253 owner
.tags
.remove(action
.tag
)
257 elif isinstance(action
, parser
.Wait
):
258 self
.wait_frames
= action(s_params
, rank
)
261 elif isinstance(action
, parser
.Vanish
):
266 self
.handle(action
, owner
, created
, sin
, cos
)
268 def handle(self
, action
, owner
, created
, sin
, cos
):
269 """Override in subclasses for new action types."""
270 raise NotImplementedError(action
.__class
__.__name
__)
272 class Bullet(object):
273 """Simple bullet implementation.
276 x, y - current X/Y position
277 px, py - X/Y position prior to the last step
278 mx, my - X/Y axis-oriented speed modifier ("acceleration")
279 direction - direction of movement, in radians
280 speed - speed of movement, in units per frame
281 target - object with .x and .y fields for "aim" directions
282 vanished - set to true by a <vanish> action
283 rank - game difficulty, 0 to 1, default 0.5
284 tags - string tags set by the running actions
286 Contructor Arguments:
287 x, y, direction, speed, target, rank - same as the attributes
288 actions - internal action list
289 Action - custom Action constructor
294 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
295 actions
=(), rank
=0.5, tags
=(), Action
=Action
):
300 self
.direction
= direction
302 self
.vanished
= False
305 self
.tags
= set(tags
)
306 # New bullets reset the parent hierarchy.
307 self
._actions
= [Action(self
, None, action
, params
, rank
)
308 for action
, params
in actions
]
311 def FromDocument(cls
, doc
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
312 params
=(), rank
=0.5, Action
=Action
):
313 """Construct a new Bullet from a loaded BulletML document."""
314 actions
= [a(params
, rank
) for a
in doc
.actions
]
315 return cls(x
=x
, y
=y
, direction
=direction
, speed
=speed
,
316 target
=target
, actions
=actions
, rank
=rank
, Action
=Action
)
319 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
320 "actions=%r, target=%r, vanished=%r)") % (
321 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
322 self
.direction
, self
.speed
, self
._actions
, self
.target
,
327 """Angle to the target, in radians.
329 If the target does not exist or cannot be found, return 0.
332 target_x
= self
.target
.x
333 target_y
= self
.target
.y
334 except AttributeError:
337 return math
.atan2(target_x
- self
.x
, self
.y
- target_y
)
341 """Check if this bullet is finished running.
343 A bullet is finished when it has vanished, and all its
344 actions have finished.
346 If this is true, the bullet should be removed from the screen.
347 (You will probably want to cull it under other circumstances
350 if not self
.vanished
:
352 for action
in self
._actions
:
353 if not action
.finished
:
358 """Vanish this bullet and stop all actions."""
360 for action
in self
._actions
:
364 def replace(self
, old
, new
):
365 """Replace an active action with another.
367 This is mostly used by actions internally to queue children.
370 idx
= self
._actions
.index(old
)
374 self
._actions
[idx
] = new
376 def step(self
, sin
=math
.sin
, cos
=math
.cos
):
377 """Advance by one frame.
379 This updates the position and velocity, and may also set the
382 It returns any new bullets this bullet spawned during this step.
386 for action
in self
._actions
:
387 action
.step(self
, created
, sin
, cos
)
391 self
.x
+= self
.mx
+ sin(self
.direction
) * self
.speed
392 self
.y
+= self
.my
- cos(self
.direction
) * self
.speed