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 -- "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(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
.Tag
):
228 owner
.tags
.add(action
.tag
)
230 elif isinstance(action
, parser
.Untag
):
232 owner
.tags
.remove(action
.tag
)
236 elif isinstance(action
, parser
.Wait
):
237 self
.wait_frames
= action(self
.params
, rank
)
240 elif isinstance(action
, parser
.Vanish
):
246 class Bullet(object):
247 """Simple bullet implementation.
250 x, y - current X/Y position
251 px, py - X/Y position prior to the last step
252 mx, my - X/Y axis-oriented speed modifier ("acceleration")
253 direction - direction of movement, in radians
254 speed - speed of movement, in units per frame
255 target - object with .x and .y fields for "aim" directions
256 vanished - set to true by a <vanish> action
257 rank - game difficulty, 0 to 1, default 0.5
258 tags - string tags set by the running actions
260 Contructor Arguments:
261 x, y, direction, speed, target, rank - same as the attributes
262 actions - internal action list
263 parent - parent of actions, None for manually-created bullets
268 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
269 actions
=(), parent
=None, rank
=0.5):
274 self
.direction
= direction
276 self
.vanished
= False
280 # New bullets reset the parent hierarchy.
281 self
._actions
= [Action(self
, None, action
, params
, rank
)
282 for action
, params
in actions
]
285 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
286 "actions=%r, target=%r, vanished=%r)") % (
287 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
288 self
.direction
, self
.speed
, self
._actions
, self
.target
,
293 """Angle to the target, in radians."""
294 if self
.target
is None:
295 return self
.direction
297 return math
.atan2(self
.target
.x
- self
.x
, self
.y
- self
.target
.y
)
301 """Check if this bullet is finished running.
303 A bullet is finished when it has vanished, and all its
304 actions have finished.
306 If this is true, the bullet should be removed from the screen.
307 (You will probably want to cull it under other circumstances
310 if not self
.vanished
:
312 for action
in self
._actions
:
313 if not action
.finished
:
318 """Vanish this bullet and stop all actions."""
320 for action
in self
._actions
:
324 def replace(self
, old
, new
):
325 """Replace an active action with another.
327 This is mostly used by actions internally to queue children.
330 idx
= self
._actions
.index(old
)
334 self
._actions
[idx
] = new
337 """Advance by one frame.
339 This updates the position and velocity, and may also set the
342 It returns any new bullets this bullet spawned during this step.
346 for action
in self
._actions
:
347 created
.extend(action
.step(self
, self
.rank
))
351 self
.x
+= self
.mx
+ math
.sin(self
.direction
) * self
.speed
352 self
.y
+= self
.my
- math
.cos(self
.direction
) * self
.speed