ad39e7bd0d047abcc5917c6829e5fabd7fa26e84
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
= 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 bullet
= Bullet(owner
.x
, owner
.y
, direction
, speed
,
157 owner
.target
, actions
, self
, rank
)
158 created
.append(bullet
)
160 elif isinstance(action
, parser
.ChangeSpeed
):
161 frames
, (speed
, type) = action(self
.params
, rank
)
162 self
.speed_frames
= frames
163 if type == "sequence":
165 elif type == "relative":
166 self
.speed
= speed
/ frames
168 self
.speed
= (speed
- owner
.speed
) / frames
170 elif isinstance(action
, parser
.ChangeDirection
):
171 frames
, (direction
, type) = action(self
.params
, rank
)
172 self
.direction_frames
= frames
174 if type == "sequence":
175 self
.direction
= direction
177 if type == "absolute":
179 direction
- owner
.direction
) % PI_2
180 elif type == "relative":
181 self
.direction
= direction
187 - owner
.direction
) % PI_2
189 if self
.direction
> math
.pi
:
190 self
.direction
-= PI_2
191 if self
.direction
< -math
.pi
:
192 self
.direction
+= PI_2
193 self
.direction
/= self
.direction_frames
195 elif isinstance(action
, parser
.Accel
):
196 frames
, horizontal
, vertical
= action(self
.params
, rank
)
197 self
.accel_frames
= frames
199 mx
, type = horizontal
200 if type == "sequence":
202 elif type == "absolute":
203 self
.mx
= (mx
- owner
.mx
) / frames
204 elif type == "relative":
205 self
.mx
= mx
/ frames
208 if type == "sequence":
210 elif type == "absolute":
211 self
.my
= (my
- owner
.my
) / frames
212 elif type == "relative":
213 self
.my
= my
/ frames
215 elif isinstance(action
, parser
.Wait
):
216 self
.wait_frames
= action(self
.params
, rank
)
219 elif isinstance(action
, parser
.Vanish
):
225 class Bullet(object):
226 """Simple bullet implementation.
229 x, y - current X/Y position
230 px, py - X/Y position prior to the last step
231 mx, my - X/Y axis-oriented speed modifier ("acceleration")
232 direction - direction of movement, in radians
233 speed - speed of movement, in units per frame
234 target - object with .x and .y fields for "aim" directions
235 vanished - set to true by a <vanish> action
236 rank - game difficulty, 0 to 1, default 0.5
238 Contructor Arguments:
239 x, y, direction, speed, target, rank - same as the attributes
240 actions - internal action list
241 parent - parent of actions, None for manually-created bullets
246 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
247 actions
=(), parent
=None, rank
=0.5):
252 self
.direction
= direction
254 self
.vanished
= False
257 # New bullets reset the parent hierarchy.
258 self
._actions
= [Action(self
, None, action
, params
, rank
)
259 for action
, params
in actions
]
262 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
263 "actions=%r, target=%r, vanished=%r)") % (
264 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
265 self
.direction
, self
.speed
, self
._actions
, self
.target
,
270 """Angle to the target, in radians."""
271 if self
.target
is None:
272 return self
.direction
274 return math
.atan2(self
.target
.x
- self
.x
, self
.y
- self
.target
.y
)
278 """Check if this bullet is finished running.
280 A bullet is finished when it has vanished, and all its
281 actions have finished.
283 If this is true, the bullet should be removed from the screen.
284 (You will probably want to cull it under other circumstances
287 if not self
.vanished
:
289 for action
in self
._actions
:
290 if not action
.finished
:
295 """Vanish this bullet and stop all actions."""
297 for action
in self
._actions
:
301 def replace(self
, old
, new
):
302 """Replace an active action with another.
304 This is mostly used by actions internally to queue children.
307 idx
= self
._actions
.index(old
)
311 self
._actions
[idx
] = new
314 """Advance by one frame.
316 This updates the position and velocity, and may also set the
319 It returns any new bullets this bullet spawned during this step.
323 for action
in self
._actions
:
324 created
.extend(action
.step(self
, self
.rank
))
328 self
.x
+= self
.mx
+ math
.sin(self
.direction
) * self
.speed
329 self
.y
+= self
.my
- math
.cos(self
.direction
) * self
.speed