1 """BulletML implementation."""
3 from __future__
import division
7 from bulletml
import parser
9 # TODO(jfw): This is very non-Pythonic, it's pretty much just the
10 # BulletML reference ActionImpl translated to Python.
14 __all__
= ["Action", "Bullet"]
17 """Running action implementation."""
19 def __init__(self
, owner
, parent
, actions
, params
, rank
, repeat
=1):
20 self
.actions
= actions
27 self
.direction_frames
= 0
32 self
.previous_fire_direction
= 0
33 self
.previous_fire_speed
= 0
38 self
.copy_state(parent
)
41 return "%s(pc=%r, actions=%r)" % (
42 type(self
).__name
__, self
.pc
, self
.actions
)
45 """End this action and its parents."""
51 def copy_state(self
, other
):
52 """Copy fire/movement state from other to self."""
53 self
.direction_frames
= other
.direction_frames
54 self
.direction
= other
.direction
55 self
.aiming
= other
.aiming
56 self
.speed_frames
= other
.speed_frames
57 self
.speed
= other
.speed
58 self
.accel_frames
= other
.accel_frames
61 self
.previous_fire_direction
= other
.previous_fire_direction
62 self
.previous_fire_speed
= other
.previous_fire_speed
64 def step(self
, owner
, rank
, created
, sin
=math
.sin
, cos
=math
.cos
):
65 """Advance by one frame."""
66 s_params
= self
.params
68 if self
.speed_frames
> 0:
69 self
.speed_frames
-= 1
70 owner
.speed
+= self
.speed
72 if self
.direction_frames
> 0:
73 # The Noiz implementation was a little weird here, I think
74 # there was a bug in it that prevented it from working if
75 # the frame count was 1. I'm still not sure what the aim
76 # check is supposed to do, exactly.
77 self
.direction_frames
-= 1
78 if self
.aiming
and self
.direction_frames
<= 0:
79 owner
.direction
+= owner
.aim
81 owner
.direction
+= self
.direction
83 if self
.accel_frames
> 0:
84 self
.accel_frames
-= 1
91 if self
.wait_frames
> 0:
99 action
= self
.actions
[self
.pc
]
105 if self
.parent
is not None:
106 self
.parent
.copy_state(self
)
107 owner
.replace(self
, self
.parent
)
111 action
= self
.actions
[self
.pc
]
113 if isinstance(action
, parser
.Repeat
):
114 repeat
, (actions
, params
) = action(s_params
, rank
)
115 child
= Action(owner
, self
, actions
, params
, rank
, repeat
)
116 owner
.replace(self
, child
)
117 child
.step(owner
, rank
, created
, sin
, cos
)
120 elif isinstance(action
, (parser
.ActionDef
, parser
.ActionRef
)):
121 actions
, params
= action(s_params
, rank
)
122 child
= Action(owner
, self
, actions
, params
, rank
)
123 owner
.replace(self
, child
)
124 child
.step(owner
, rank
, created
, sin
, cos
)
127 elif isinstance(action
, (parser
.FireDef
, parser
.FireRef
)):
128 direction
, speed
, actions
, offset
= action(s_params
, rank
)
130 direction
, type = direction
131 if type == "aim" or type is None:
132 direction
+= owner
.aim
133 elif type == "sequence":
134 direction
+= self
.previous_fire_direction
135 elif type == "relative":
136 direction
+= owner
.direction
138 direction
= owner
.aim
139 self
.previous_fire_direction
= direction
143 if type == "sequence":
144 speed
+= self
.previous_fire_speed
145 elif type == "relative":
146 # The reference Noiz implementation uses
147 # prvFireSpeed here, but the standard is
148 # pretty clear -- "In case of the type is
149 # "relative", ... the speed is relative to the
150 # speed of this bullet."
154 self
.previous_fire_speed
= speed
156 x
, y
= owner
.x
, owner
.y
158 off_x
, off_y
= offset(s_params
, rank
)
159 if offset
.type == "relative":
162 x
+= c
* off_x
+ s
* off_y
163 y
+= s
* off_x
- c
* off_y
169 x
, y
, direction
, speed
, owner
.target
, actions
, self
, rank
)
170 created
.append(bullet
)
172 elif isinstance(action
, parser
.ChangeSpeed
):
173 frames
, (speed
, type) = action(s_params
, rank
)
174 self
.speed_frames
= frames
176 if type == "absolute":
178 elif type == "relative":
180 elif type == "sequence":
182 elif type == "relative":
183 self
.speed
= speed
/ frames
185 self
.speed
= (speed
- owner
.speed
) / frames
187 elif isinstance(action
, parser
.ChangeDirection
):
188 frames
, (direction
, type) = action(s_params
, rank
)
189 self
.direction_frames
= frames
191 if type == "sequence":
192 self
.direction
= direction
194 if type == "absolute":
195 direction
-= owner
.direction
196 elif type != "relative": # aim or default
198 direction
+= owner
.aim
- owner
.direction
200 # Normalize to [-pi, pi).
201 direction
= (direction
+ math
.pi
) % PI_2
- math
.pi
203 owner
.direction
+= direction
205 self
.direction
= direction
/ frames
207 elif isinstance(action
, parser
.Accel
):
208 frames
, horizontal
, vertical
= action(s_params
, rank
)
209 self
.accel_frames
= frames
211 mx
, type = horizontal
213 if type == "absolute":
215 elif type == "relative":
217 elif type == "sequence":
219 elif type == "absolute":
220 self
.mx
= (mx
- owner
.mx
) / frames
221 elif type == "relative":
222 self
.mx
= mx
/ frames
226 if type == "absolute":
228 elif type == "relative":
230 elif type == "sequence":
232 elif type == "absolute":
233 self
.my
= (my
- owner
.my
) / frames
234 elif type == "relative":
235 self
.my
= my
/ frames
237 elif isinstance(action
, parser
.Tag
):
238 owner
.tags
.add(action
.tag
)
240 elif isinstance(action
, parser
.Untag
):
242 owner
.tags
.remove(action
.tag
)
246 elif isinstance(action
, parser
.Wait
):
247 self
.wait_frames
= action(s_params
, rank
)
250 elif isinstance(action
, parser
.Vanish
):
254 class Bullet(object):
255 """Simple bullet implementation.
258 x, y - current X/Y position
259 px, py - X/Y position prior to the last step
260 mx, my - X/Y axis-oriented speed modifier ("acceleration")
261 direction - direction of movement, in radians
262 speed - speed of movement, in units per frame
263 target - object with .x and .y fields for "aim" directions
264 vanished - set to true by a <vanish> action
265 rank - game difficulty, 0 to 1, default 0.5
266 tags - string tags set by the running actions
268 Contructor Arguments:
269 x, y, direction, speed, target, rank - same as the attributes
270 actions - internal action list
271 parent - parent of actions, None for manually-created bullets
276 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
277 actions
=(), parent
=None, rank
=0.5):
282 self
.direction
= direction
284 self
.vanished
= False
288 # New bullets reset the parent hierarchy.
289 self
._actions
= [Action(self
, None, action
, params
, rank
)
290 for action
, params
in actions
]
293 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
294 "actions=%r, target=%r, vanished=%r)") % (
295 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
296 self
.direction
, self
.speed
, self
._actions
, self
.target
,
301 """Angle to the target, in radians."""
302 if self
.target
is None:
303 return self
.direction
305 return math
.atan2(self
.target
.x
- self
.x
, self
.y
- self
.target
.y
)
309 """Check if this bullet is finished running.
311 A bullet is finished when it has vanished, and all its
312 actions have finished.
314 If this is true, the bullet should be removed from the screen.
315 (You will probably want to cull it under other circumstances
318 if not self
.vanished
:
320 for action
in self
._actions
:
321 if not action
.finished
:
326 """Vanish this bullet and stop all actions."""
328 for action
in self
._actions
:
332 def replace(self
, old
, new
):
333 """Replace an active action with another.
335 This is mostly used by actions internally to queue children.
338 idx
= self
._actions
.index(old
)
342 self
._actions
[idx
] = new
344 def step(self
, sin
=math
.sin
, cos
=math
.cos
):
345 """Advance by one frame.
347 This updates the position and velocity, and may also set the
350 It returns any new bullets this bullet spawned during this step.
354 for action
in self
._actions
:
355 action
.step(self
, self
.rank
, created
, sin
, cos
)
359 self
.x
+= self
.mx
+ sin(self
.direction
) * self
.speed
360 self
.y
+= self
.my
- cos(self
.direction
) * self
.speed