1 """BulletML implementation."""
3 from __future__
import division
5 from math
import atan2
, sin
, cos
7 from bulletml
import parser
9 __all__
= ["Action", "Bullet"]
12 """Running action implementation.
14 To implement new actions, add a new element/class pair to
15 parser.ActionDef.CONSTRUCTORS. It should support FromXML,
16 __getstate__, and __setstate__, and 5-ary __call__:
18 def __call__(self, owner, action, params, rank, created)
20 Which will be called to execute it. This function should modify
21 owner, action, and created in-place, and return true if action
22 execution should stop for this bullet this frame.
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
):
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 # I'm still not sure what the aim check is supposed to do.
82 self
.direction_frames
-= 1
83 if self
.aiming
and self
.direction_frames
<= 0:
84 owner
.direction
+= owner
.aim
86 owner
.direction
+= self
.direction
88 if self
.accel_frames
> 0:
89 self
.accel_frames
-= 1
96 if self
.wait_frames
> 0:
104 action
= self
.actions
[self
.pc
]
110 if self
.parent
is not None:
111 self
.parent
.copy_state(self
)
112 owner
.replace(self
, self
.parent
)
116 action
= self
.actions
[self
.pc
]
118 if isinstance(action
, (parser
.ActionDef
, parser
.ActionRef
)):
119 actions
, params
= action(s_params
, rank
)
120 child
= self
.__class
__(owner
, self
, actions
, params
, rank
)
121 owner
.replace(self
, child
)
122 child
.step(owner
, created
)
125 elif action(owner
, self
, s_params
, rank
, created
):
128 class Bullet(object):
129 """Simple bullet implementation.
132 x, y - current X/Y position
133 px, py - X/Y position prior to the last step
134 mx, my - X/Y axis-oriented speed modifier ("acceleration")
135 direction - direction of movement, in radians
136 speed - speed of movement, in units per frame
137 target - object with .x and .y fields for "aim" directions
138 vanished - set to true by a <vanish> action
139 rank - game difficulty, 0 to 1, default 0.5
140 tags - string tags set by the running actions
141 appearance - string used to set bullet appearance
143 Contructor Arguments:
144 x, y, direction, speed, target, rank, tags, appearance
145 - same as the above attributes
146 actions - internal action list
147 Action - custom Action constructor
151 def __init__(self
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
152 actions
=(), rank
=0.5, tags
=(), appearance
=None,
158 self
.direction
= direction
160 self
.vanished
= False
163 self
.tags
= set(tags
)
164 self
.appearance
= appearance
165 # New bullets reset the parent hierarchy.
166 self
._actions
= [Action(self
, None, action
, params
, rank
)
167 for action
, params
in actions
]
170 def FromDocument(cls
, doc
, x
=0, y
=0, direction
=0, speed
=0, target
=None,
171 params
=(), rank
=0.5, Action
=Action
):
172 """Construct a new Bullet from a loaded BulletML document."""
173 actions
= [a(params
, rank
) for a
in doc
.actions
]
174 return cls(x
=x
, y
=y
, direction
=direction
, speed
=speed
,
175 target
=target
, actions
=actions
, rank
=rank
, Action
=Action
)
178 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
179 "actions=%r, target=%r, appearance=vanished=%r)") % (
180 type(self
).__name
__, self
.x
, self
.y
, (self
.mx
, self
.my
),
181 self
.direction
, self
.speed
, self
._actions
, self
.target
,
182 self
.appearance
, self
.vanished
)
186 """Angle to the target, in radians.
188 If the target does not exist or cannot be found, return 0.
191 target_x
= self
.target
.x
192 target_y
= self
.target
.y
193 except AttributeError:
196 return atan2(target_x
- self
.x
, target_y
- self
.y
)
200 """Check if this bullet is finished running.
202 A bullet is finished when it has vanished, and all its
203 actions have finished.
205 If this is true, the bullet should be removed from the screen.
206 (You will probably want to cull it under other circumstances
209 if not self
.vanished
:
211 for action
in self
._actions
:
212 if not action
.finished
:
217 """Vanish this bullet and stop all actions."""
219 for action
in self
._actions
:
223 def replace(self
, old
, new
):
224 """Replace an active action with another.
226 This is mostly used by actions internally to queue children.
229 idx
= self
._actions
.index(old
)
233 self
._actions
[idx
] = new
236 """Advance by one frame.
238 This updates the position and velocity, and may also set the
241 It returns any new bullets this bullet spawned during this step.
245 for action
in self
._actions
:
246 action
.step(self
, created
)
250 self
.x
+= self
.mx
+ sin(self
.direction
) * self
.speed
251 self
.y
+= -self
.my
+ cos(self
.direction
) * self
.speed