Bullet.step: Manage finished state here. Although this increases the time spent in...
[python-bulletml.git] / bulletml / impl.py
1 """BulletML implementation."""
2
3 from __future__ import division
4
5 from math import atan2, sin, cos
6
7 __all__ = ["Action", "Bullet"]
8
9 class Action(object):
10 """Running action implementation.
11
12 To implement new actions, add a new element/class pair to
13 parser.ActionDef.CONSTRUCTORS. It should support FromXML,
14 __getstate__, and __setstate__, and 5-ary __call__:
15
16 def __call__(self, owner, action, params, rank, created)
17
18 Which will be called to execute it. This function should modify
19 owner, action, and created in-place, and return true if action
20 execution should stop for this bullet this frame.
21
22 """
23
24 def __init__(self, parent, actions, params, rank, repeat=1):
25 self.actions = actions
26 self.parent = parent
27 self.repeat = repeat
28 self.wait_frames = 0
29 self.speed = 0
30 self.speed_frames = 0
31 self.direction = 0
32 self.direction_frames = 0
33 self.aiming = False
34 self.mx = 0
35 self.my = 0
36 self.accel_frames = 0
37 self.previous_fire_direction = 0
38 self.previous_fire_speed = 0
39 self.params = params
40 self.pc = -1
41 self.finished = False
42 if parent:
43 self.copy_state(parent)
44
45 def __repr__(self):
46 return "%s(pc=%r, actions=%r)" % (
47 type(self).__name__, self.pc, self.actions)
48
49 def vanish(self):
50 """End this action and its parents."""
51 if self.parent:
52 self.parent.vanish()
53 self.pc = None
54 self.finished = True
55
56 def copy_state(self, other):
57 """Copy fire/movement state from other to self."""
58 self.direction_frames = other.direction_frames
59 self.direction = other.direction
60 self.aiming = other.aiming
61 self.speed_frames = other.speed_frames
62 self.speed = other.speed
63 self.accel_frames = other.accel_frames
64 self.mx = other.mx
65 self.my = other.my
66 self.previous_fire_direction = other.previous_fire_direction
67 self.previous_fire_speed = other.previous_fire_speed
68
69 def step(self, owner, created):
70 """Advance by one frame."""
71
72 if self.speed_frames > 0:
73 self.speed_frames -= 1
74 owner.speed += self.speed
75
76 if self.direction_frames > 0:
77 self.direction_frames -= 1
78 # I'm still not sure what the aim check is supposed to do.
79 if self.aiming and self.direction_frames <= 0:
80 owner.direction += owner.aim
81 else:
82 owner.direction += self.direction
83
84 if self.accel_frames > 0:
85 self.accel_frames -= 1
86 owner.mx += self.mx
87 owner.my += self.my
88
89 if self.pc is None:
90 return
91
92 if self.wait_frames > 0:
93 self.wait_frames -= 1
94 return
95
96 s_params = self.params
97 rank = owner.rank
98
99 while True:
100 self.pc += 1
101
102 try:
103 action = self.actions[self.pc]
104 except IndexError:
105 self.repeat -= 1
106 if self.repeat <= 0:
107 self.pc = None
108 self.finished = True
109 if self.parent is not None:
110 self.parent.copy_state(self)
111 owner.replace(self, self.parent)
112 break
113 else:
114 self.pc = 0
115 action = self.actions[self.pc]
116
117 if action(owner, self, s_params, rank, created):
118 break
119
120 class Bullet(object):
121 """Simple bullet implementation.
122
123 Attributes:
124 x, y - current X/Y position
125 px, py - X/Y position prior to the last step
126 mx, my - X/Y axis-oriented speed modifier ("acceleration")
127 direction - direction of movement, in radians
128 speed - speed of movement, in units per frame
129 target - object with .x and .y fields for "aim" directions
130 vanished - set to true by a <vanish> action
131 rank - game difficulty, 0 to 1, default 0.5
132 tags - string tags set by the running actions
133 appearance - string used to set bullet appearance
134 radius - radius for collision
135 finished - true if all actions are finished and the bullet vanished
136
137 Contructor Arguments:
138 x, y, direction, speed, target, rank, tags, appearance, radius
139 - same as the above attributes
140 actions - internal action list
141 Action - custom Action constructor
142
143 """
144
145 def __init__(self, x=0, y=0, direction=0, speed=0, target=None,
146 actions=(), rank=0.5, tags=(), appearance=None,
147 radius=0.5):
148 self.x = self.px = x
149 self.y = self.py = y
150 self.radius = radius
151 self.mx = 0
152 self.my = 0
153 self.direction = direction
154 self.speed = speed
155 self.vanished = False
156 self.finished = False
157 self.target = target
158 self.rank = rank
159 self.tags = set(tags)
160 self.appearance = appearance
161 self.actions = list(actions)
162
163 @classmethod
164 def FromDocument(cls, doc, x=0, y=0, direction=0, speed=0, target=None,
165 params=(), rank=0.5, Action=Action):
166 """Construct a new Bullet from a loaded BulletML document."""
167 actions = [action(None, Action, params, rank)
168 for action in doc.actions]
169 return cls(x=x, y=y, direction=direction, speed=speed,
170 target=target, actions=actions, rank=rank)
171
172 def __repr__(self):
173 return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, "
174 "actions=%r, target=%r, appearance=%r, vanished=%r)") % (
175 type(self).__name__, self.x, self.y, (self.mx, self.my),
176 self.direction, self.speed, self.actions, self.target,
177 self.appearance, self.vanished)
178
179 @property
180 def aim(self):
181 """Angle to the target, in radians.
182
183 If the target does not exist or cannot be found, return 0.
184 """
185 try:
186 target_x = self.target.x
187 target_y = self.target.y
188 except AttributeError:
189 return 0
190 else:
191 return atan2(target_x - self.x, target_y - self.y)
192
193 def vanish(self):
194 """Vanish this bullet and stop all actions."""
195 self.vanished = True
196 for action in self.actions:
197 action.vanish()
198 self.actions = []
199
200 def replace(self, old, new):
201 """Replace an active action with another.
202
203 This is mostly used by actions internally to queue children.
204 """
205 try:
206 idx = self.actions.index(old)
207 except ValueError:
208 pass
209 else:
210 self.actions[idx] = new
211
212 def step(self):
213 """Advance by one frame.
214
215 This updates the position and velocity, and may also set the
216 vanished flag.
217
218 It returns any new bullets this bullet spawned during this step.
219 """
220 created = []
221
222 finished = self.vanished
223 for action in self.actions:
224 action.step(self, created)
225 finished = finished and action.finished
226 self.finished = finished
227
228 speed = self.speed
229 direction = self.direction
230 self.px = self.x
231 self.py = self.y
232 self.x += self.mx + sin(direction) * speed
233 self.y += -self.my + cos(direction) * speed
234
235 return created