"""BulletML implementation. http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html """ from __future__ import division import math from bulletml import parser, errors # TODO(jfw): This is very non-Pythonic, it's pretty much just the # BulletML reference ActionImpl translated to Python. class Action(object): """Running action implementation.""" def __init__(self, owner, parent, actions, params, rank): self.actions = actions self.parent = parent self.repeat = 0 self.wait_frames = 0 self.speed = 0 self.speed_frames = 0 self.aim_direction = 0 self.direction = 0 self.direction_frames = 0 self.aiming = False self.aim_mx = 0 self.aim_my = 0 self.mx = 0 self.my = 0 self.owner = owner self.accel_frames = 0 self.previous_fire_direction = 0 self.previous_fire_speed = 0 self.params = params self.rank = rank self.pc = -1 if parent: self.copy_state(parent) def vanish(self): if self.parent: self.parent.vanish() self.repeat = 0 self.pc = None def copy_state(self, other): self.direction_frames = other.direction_frames self.direction = other.direction self.aiming = other.aiming self.speed_frames = other.speed_frames self.speed = other.speed self.accel_frames = other.accel_frames self.mx = other.mx self.my = other.my self.previous_fire_direction = other.previous_fire_direction self.previous_fire_speed = other.previous_fire_speed def step(self): created = [] if self.speed_frames > 0: self.speed_frames -= 1 self.owner.speed += self.speed if self.direction_frames > 0: self.direction_frames -= 1 if self.direction_frames <= 0: if self.aiming: self.owner.direction += self.owner.aim else: self.owner.direction += self.direction if self.accel_frames > 0: self.accel_frames -= 1 self.owner.mx += self.mx self.owner.my += self.my if self.pc is None: return created if self.wait_frames > 0: self.wait_frames -= 1 return created while True: self.pc += 1 if self.pc >= len(self.actions): self.repeat -= 1 if self.repeat <= 0: if self.parent is not None: self.parent.copy_state(self) self.owner.replace(self, self.parent) break else: self.pc = 0 action = self.actions[self.pc] if isinstance(action, parser.Repeat): repeat, (actions, params) = action(self.params, self.rank) child = Action(self.owner, self, actions, params, self.rank) child.repeat = repeat self.owner.replace(self, child) created.extend(child.step()) break elif isinstance(action, (parser.ActionDef, parser.ActionRef)): action, params = action(self.params, self.rank) child = Action(self.owner, self, actions, params, self.rank) self.owner.replace(self, child) created.extend(child.step()) break elif isinstance(action, (parser.FireDef, parser.FireRef)): direction, speed, actions = action(self.params, self.rank) if direction: direction, type = direction if type == "aim": direction += self.owner.aim elif type == "sequence": direction += self.previous_fire_direction elif type == "relative": direction += self.owner.direction else: direction = self.owner.aim self.previous_fire_direction = direction if speed: speed, type = speed if type == "sequence": speed += self.previous_fire_speed elif type == "relative": # FIXME(jfw): Reference impl uses prvFireSpeed # here? That doesn't seem right at all. speed += self.owner.speed else: speed = 1 self.previous_fire_speed = speed bullet = Bullet(self.owner.x, self.owner.y, direction, speed, self.owner.target, actions, self) created.append(bullet) elif isinstance(action, parser.ChangeSpeed): frames, (speed, type) = action(self.params, self.rank) self.speed_frames = frames if type == "sequence": self.speed = speed elif type == "relative": self.speed = speed / frames else: self.speed = (speed - self.owner.speed) / frames elif isinstance(action, parser.ChangeDirection): frames, (direction, type) = action(self.params, self.rank) self.direction_frames = frames self.aiming = False if type == "sequence": self.aiming = False self.direction = direction else: if type == "absolute": self.aiming = False self.direction = ( direction - self.owner.direction) % 360 elif type == "relative": self.aiming = False self.direction = direction else: self.aiming = True self.direction = ( direction + self.owner.aim - self.owner.direction) % 360 while self.direction > 180: self.direction -= 360 while self.direction < -180: self.direction += 360 self.direction /= self.direction_frames elif isinstance(action, parser.Accel): frames, horizontal, vertical = action(self.params, self.rank) self.accel_frames = frames if horizontal: mx, type = horizontal if type == "sequence": self.mx = mx elif type == "absolute": self.mx = (mx - bullet.mx) / frames elif type == "relative": self.mx = mx / frames if vertical: my, type = vertical if type == "sequence": self.my = my elif type == "absolute": self.my = (my - bullet.my) / frames elif type == "relative": self.my = my / frames elif isinstance(action, parser.Wait): self.wait_frames = action(self.params, self.rank) break elif isinstance(action, parser.Vanish): self.owner.vanish() break return created class Bullet(object): """Simple bullet implementation.""" def __init__(self, x=0, y=0, direction=0, speed=0, target=None, actions=(), parent=None): self.x = self.px = x self.y = self.py = y self.mx = 0 self.my = 0 self.direction = direction self.speed = speed self.vanished = False self.target = target self.actions = [] if actions and not parent: raise errors.Error for action, params in actions: self.actions.append( Action(self, parent, action, params, parent.rank)) def __repr__(self): return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, " "actions=%r, target=%r, vanished=%r)") % ( type(self).__name__, self.x, self.y, (self.mx, self.my), self.direction, self.speed, self.actions, self.target, self.vanished) @property def aim(self): """Angle to the target.""" if self.target is None: return self.direction else: return math.atan2(self.target.x - self.x, self.target.y - self.y) def vanish(self): """Vanish this bullet and stop all actions.""" self.vanished = True for action in self.actions: action.vanish() self.actions = [] def replace(self, old, new): try: idx = self.actions.index(old) except ValueError: pass else: self.actions[idx] = new def step(self): created = [] for action in self.actions: created.extend(action.step()) direction = math.degrees(self.direction) self.x += self.mx + math.sin(direction) * self.speed self.y += self.my + math.cos(direction) * self.speed return created