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
):
65 """Advance by one frame."""
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(self
.params
, rank
)
115 child
= Action(owner
, self
, actions
, params
, rank
, repeat
)
116 owner
.replace(self
, child
)
117 created
.extend(child
.step(owner
, rank
))
120 elif isinstance(action
, (parser
.ActionDef
, parser
.ActionRef
)):
121 actions
, params
= action(self
.params
, rank
)
122 child
= Action(owner
, self
, actions
, params
, rank
)
123 owner
.replace(self
, child
)
124 created
.extend(child
.step(owner
, rank
))
127 elif isinstance(action
, (parser
.FireDef
, parser
.FireRef
)):
128 direction
, speed
, actions
, offset
= action(self
.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 -- "0 means that the direction
149 # of this fire and the direction of the bullet
154 self
.previous_fire_speed
= speed
156 x
, y
= owner
.x
, owner
.y
158 off_x
, off_y
= offset(self
.params
, rank
)
159 if offset
.type == "relative":
160 sin
= math
.sin(direction
)
161 cos
= math
.cos(direction
)
162 x
+= cos
* off_x
+ sin
* off_y
163 y
+= sin
* off_x
- cos
* 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(self
.params
, rank
)
174 self
.speed_frames
= frames
175 if type == "sequence":
177 elif type == "relative":
178 self
.speed
= speed
/ frames
180 self
.speed
= (speed
- owner
.speed
) / frames
182 elif isinstance(action
, parser
.ChangeDirection
):
183 frames
, (direction
, type) = action(self
.params
, rank
)
184 self
.direction_frames
= frames
186 if type == "sequence":
187 self
.direction
= direction
189 if type == "absolute":
191 direction
- owner
.direction
) % PI_2
192 elif type == "relative":
193 self
.direction
= direction
199 - owner
.direction
) % PI_2
201 if self
.direction
> math
.pi
:
202 self
.direction
-= PI_2
203 if self
.direction
< -math
.pi
:
204 self
.direction
+= PI_2
205 self
.direction
/= self
.direction_frames
207 elif isinstance(action
, parser
.Accel
):
208 frames
, horizontal
, vertical
= action(self
.params
, rank
)
209 self
.accel_frames
= frames
211 mx
, type = horizontal
212 if type == "sequence":
214 elif type == "absolute":
215 self
.mx
= (mx
- owner
.mx
) / frames
216 elif type == "relative":
217 self
.mx
= mx
/ frames
220 if type == "sequence":
222 elif type == "absolute":
223 self
.my
= (my
- owner
.my
) / frames
224 elif type == "relative":
225 self
.my
= my
/ frames
227 elif isinstance(action
, parser
.Wait
):
228 self
.wait_frames
= action(self
.params
, rank
)
231 elif isinstance(action
, parser
.Vanish
):
237 class Bullet(object):
238 """Simple bullet implementation.
241 x, y - current X/Y position
242 px, py - X/Y position prior to the last step
243 mx, my - X/Y axis-oriented speed modifier ("acceleration")
244 direction - direction of movement, in radians
245 speed - speed of movement, in units per frame
246 target - object with .x and .y fields for "aim" directions
247 vanished - set to true by a <vanish> action
248 rank - game difficulty, 0 to 1, default 0.5
250 Contructor Arguments:
251 x, y, direction, speed, target, rank - same as the attributes
252 actions - internal action list
253 parent - parent of actions, None for manually-created bullets
258 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
259 actions
=(), parent
=None, rank
=0.5):
264 self
.direction
= direction
266 self
.vanished
= False
269 # New bullets reset the parent hierarchy.
270 self
._actions
= [Action(self
, None, action
, params
, rank
)
271 for action
, params
in actions
]
274 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
275 "actions=%r, target=%r, vanished=%r)") % (
276 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
277 self
.direction
, self
.speed
, self
._actions
, self
.target
,
282 """Angle to the target, in radians."""
283 if self
.target
is None:
284 return self
.direction
286 return math
.atan2(self
.target
.x
- self
.x
, self
.y
- self
.target
.y
)
290 """Check if this bullet is finished running.
292 A bullet is finished when it has vanished, and all its
293 actions have finished.
295 If this is true, the bullet should be removed from the screen.
296 (You will probably want to cull it under other circumstances
299 if not self
.vanished
:
301 for action
in self
._actions
:
302 if not action
.finished
:
307 """Vanish this bullet and stop all actions."""
309 for action
in self
._actions
:
313 def replace(self
, old
, new
):
314 """Replace an active action with another.
316 This is mostly used by actions internally to queue children.
319 idx
= self
._actions
.index(old
)
323 self
._actions
[idx
] = new
326 """Advance by one frame.
328 This updates the position and velocity, and may also set the
331 It returns any new bullets this bullet spawned during this step.
335 for action
in self
._actions
:
336 created
.extend(action
.step(self
, self
.rank
))
340 self
.x
+= self
.mx
+ math
.sin(self
.direction
) * self
.speed
341 self
.y
+= self
.my
- math
.cos(self
.direction
) * self
.speed