import random, os, math import random as random_module import gamesrv import images import boards from boards import * from images import ActiveSprite from mnstrmap import GreenAndBlue, Bonuses, Diamonds, Stars, BigImages from mnstrmap import PotionBonuses, Fire from player import BubPlayer questionmarklist = ['questionmark3', 'questionmark4', 'questionmark5', 'questionmark4', 'questionmark3', 'questionmark2', 'questionmark1', 'questionmark2'] class Bonus(ActiveSprite): bubblable = 1 touchable = 1 points = 750 timeout = 250 sound = 'Fruit' endaction = None multiply = 1 killgens = 1 def __init__(self, x, y, nimage=None, points=None, falling=1): if nimage is not None: self.nimage = nimage if points is not None: self.points = points ActiveSprite.__init__(self, images.sprget(self.nimage), x, y) self.taken_by = [] self.gen.append(self.timeouter()) if falling: self.gen.append(self.faller()) def buildoutcome(self): return (self.__class__,) def faller(self): while self.y < boards.bheight: if onground_nobottom(self.x, self.y): yield None yield None else: self.move(self.x, (self.y+4) & ~3) yield None self.kill() def timeouter(self): for i in range(self.timeout): yield None if self.timeout: self.kill() def touched(self, dragon): dx, dy, dw, dh = dragon.x, dragon.y, dragon.ico.w, dragon.ico.h if (dx + dw > self.x + 10 and dy + dh > self.y + 8 and self.x + self.ico.w > dx + 10 and self.y + self.ico.h > dy + 10): self.reallytouched(dragon) def reallytouched(self, dragon): if not self.taken_by: if self.killgens: self.gen = [] self.gen.append(self.taking()) sound = self.sound if sound: if isinstance(sound, str): sound = getattr(images.Snd, sound) self.play(sound) if dragon not in self.taken_by: self.taken_by.append(dragon) if isinstance(self, (RandomBonus, MonsterBonus)): s_bonus = dragon.bubber.stats.setdefault('bonus', {}) s_bonus[self.nimage] = s_bonus.get(self.nimage, 0) + 1 def taking(self, follow_dragons=0, delay=1): from player import Dragon for t in range(delay): yield None # time to be taken by several dragons if self.points: for p in self.taken_by: if follow_dragons and p.alive: s = p else: s = self points(s.x + s.ico.w//2, s.y + s.ico.h//2 - CELL, p, self.points) dragons = [d for d in self.taken_by if isinstance(d, Dragon)] if self.taken1(dragons) != -1: self.kill() def taken1(self, dragons): for d in dragons * self.multiply: if d.alive: self.taken(d) def taken(self, dragon): pass def in_bubble(self, bubble): self.untouchable() bubble.move(self.x, self.y) bubble.to_front() self.to_front() self.gen = [self.bubbling(bubble, self.ico)] self.move(bubble.x+8, bubble.y+8, images.sprget('questionmark3')) self.setimages(self.cyclic(questionmarklist, 2)) def bubbling(self, bubble, ico): while not hasattr(bubble, 'poplist'): self.move(bubble.x+8, bubble.y+8) yield None if bubble.poplist is not None: dragon = bubble.poplist[0] if dragon is not None: self.play(images.Snd.Yippee) if dragon not in self.taken_by: self.taken_by.append(dragon) if self.points > 10: dragon.bubber.givepoints(self.points - 10) pn = dragon.bubber.pn if self.points in GreenAndBlue.points[pn]: Points(bubble.x + bubble.ico.w//2, bubble.y, pn, self.points) self.taken1(BubPlayer.DragonList) p = Parabolic(ico, bubble.x, bubble.y) p.gen.append(p.moving(-1.0)) self.kill() def is_on_ground(self): return onground(self.x, self.y) def points(x, y, dragon, points): dragon.bubber.givepoints(abs(points)) pn = dragon.bubber.pn if points in GreenAndBlue.points[pn]: Points(x, y, pn, points) class Points(ActiveSprite): def __init__(self, x, y, pn, points): ico = images.sprget(GreenAndBlue.points[pn][points]) ActiveSprite.__init__(self, ico, x - ico.w//2, max(8, y)) self.nooverlap = 1 self.gen.append(self.raiser()) def raiser(self): wait = 0 for s in images.ActiveSprites: if s is self: break if (isinstance(s, Points) and s.nooverlap and abs(self.x-s.x)= 4.0 and self.fallstraight: del self.parabole_progress self.gen.append(self.falling()) return self.kill() def falling(self): nx, ny = vertical_warp(self.x, self.y & ~3) if self.fallspeed < 0: groundtest = underground else: groundtest = onground while not groundtest(nx, ny): ny += self.fallspeed nx, ny1 = vertical_warp(nx, ny) if ny1 != ny: ny = ny1 self.wrapped_around() self.move(nx, ny) yield None self.move(nx, ny) self.build() self.kill() def killmonsters(self, poplist): from monsters import Monster while 1: for s in self.touching(0): if isinstance(s, Monster): s.argh(poplist) yield None def build(self): pass def wrapped_around(self): pass class Parabolic2(Parabolic): points = 0 def __init__(self, x, y, imglist, imgspeed=3, onplace=0, y_amplitude=-8.0): Parabolic.__init__(self, images.sprget(imglist[0]), x, y) if onplace: self.gen.append(self.falling()) else: self.gen.append(self.moving(y_amplitude)) if len(imglist) > 1: self.setimages(self.cyclic(imglist, imgspeed)) def touched(self, dragon, rect=None): if self.points: points(self.x + self.ico.w/2, self.y + self.ico.h/2 - CELL, dragon, self.points) self.kill() class BonusMaker(Parabolic2): fallstraight = 1 touchable = 1 def __init__(self, x, y, imglist, imgspeed=3, onplace=0, outcome=None): assert outcome self.outcome = outcome if outcome == (Flower2,): self.fallspeed = -self.fallspeed Parabolic2.__init__(self, x, y, imglist, imgspeed, onplace) def falling(self): cls = self.outcome[0] if issubclass(cls, Megabonus): self.build() return self.die([]) else: return Parabolic2.falling(self) def wrapped_around(self): cls = self.outcome[0] if issubclass(cls, RandomBonus) and not boards.curboard.playingboard: self.kill() def build(self): cls = self.outcome[0] args = self.outcome[1:] if issubclass(cls, RandomBonus) and not boards.curboard.playingboard: return None else: return cls(self.x, self.y, *args) def touched(self, dragon, rect=None): pass def in_bubble(self, bubble): bonus = self.build() self.kill() if bonus: bonus.in_bubble(bubble) return bonus class BonusMakerExtraStar(ActiveSprite): def __init__(self, x, y, sx, sy, colorname): self.sx = sx self.sy = sy imglist = [('smstar', colorname, k) for k in range(2)] ActiveSprite.__init__(self, images.sprget(imglist[-1]), x + HALFCELL, y + HALFCELL) self.setimages(self.cyclic(imglist, speed=2)) def follow_bonusmaker(self, bm): for t in range(4): yield None if hasattr(bm, 'parabole_progress'): break else: self.kill() return start = bm.parabole_progress if start < 3.9: while bm.alive and hasattr(bm, 'parabole_progress'): f = (bm.parabole_progress-start) / (4.0-start) self.move(bm.x + HALFCELL + int(f*self.sx), bm.y + HALFCELL + int(f*self.sy)) yield None self.kill() class MonsterBonus(Bonus): def __init__(self, x, y, multiple, forceimg=0): self.level = multiple if multiple >= len(Bonuses.monster_bonuses): multiple = len(Bonuses.monster_bonuses) - 1 img, pts = Bonuses.monster_bonuses[multiple] Bonus.__init__(self, x, y, forceimg or img, pts) def buildoutcome(self): return (self.__class__, self.level) def taken(self, dragon): dragon.carrybonus(self, 543) class IceMonsterBonus(MonsterBonus): def __init__(self, x, y, multiple): self.level = multiple if multiple >= 1: img, pts = Bonuses.violet_ice, 750 else: img, pts = Bonuses.cyan_ice, 700 Bonus.__init__(self, x, y, img, pts) class DustStar(ActiveSprite): localrandom = random.Random() def __init__(self, x, y, basedx, basedy, big=1, clock=0): self.colorname = self.localrandom.choice(Stars.COLORS) self.imgspeed = self.localrandom.randrange(3, 6) self.rotation_reversed = self.localrandom.random() < 0.5 ico, imggen = self.select_ico(getattr(Stars, self.colorname)) ActiveSprite.__init__(self, ico, x, y) self.setimages(imggen) self.gen.append(self.fly(basedx, basedy, big)) if not big: self.make_small() elif clock: self.setimages(None) self.seticon(images.sprget(Bonuses.clock)) def select_ico(self, imglist): if self.rotation_reversed: imglist = list(imglist) imglist.reverse() return (images.sprget(imglist[-1]), self.cyclic(imglist, self.imgspeed)) def make_small(self): images = [('smstar', self.colorname, k) for k in range(2)] ico, imggen = self.select_ico(images) self.seticon(ico) self.setimages(imggen) def fly(self, dx, dy, big): random = self.localrandom dx += (random.random() - 0.5) * 2.8 dy += (random.random() - 0.5) * 2.8 fx = self.x fy = self.y if big: j = 0 else: j = 2 while j < 3: ttl = random.expovariate(1.0 / 12) if ttl > 35: ttl = 35 for i in range(int(ttl)+4): fx += dx fy += dy self.move(int(fx), int(fy)) yield None if j == 0: self.make_small() fx += 8 fy += 8 j += 1 self.kill() class RandomBonus(Bonus): timeout = 500 class TemporaryBonus(RandomBonus): captime = 0 bonusleveldivider = 2 def taken(self, dragon): dragon.dcap[self.capname] += 1 self.carried(dragon) def carried(self, dragon): captime = self.captime if boards.curboard.bonuslevel: captime = (captime or 999) // self.bonusleveldivider if captime: dragon.carrybonus(self, captime) else: dragon.carrybonus(self) self.endaction = None def endaction(self, dragon): if dragon.dcap[self.capname] >= 1: dragon.dcap[self.capname] -= 1 class ShoeSpeed(RandomBonus): "Fast Runner. Cumulative increase of horizontal speed." nimage = Bonuses.shoe bigbonus = {'multiply': 3} bigdoc = "Run Really Fast." def taken(self, dragon): dragon.dcap['hspeed'] += 1 dragon.carrybonus(self) class CoffeeSpeed(RandomBonus): "Caffeine. Cumulative increase of the horizontal speed and fire rate." nimage = Bonuses.coffee big = 0 bigbonus = {'big': 1, 'multiply': 3} bigdoc = "Super-Excited! Break through walls!" def taken(self, dragon): dragon.dcap['hspeed'] += 0.5 dragon.dcap['firerate'] += 1 if self.big: dragon.dcap['breakwalls'] = 1 dragon.carrybonus(self) class Butterfly(TemporaryBonus): "Lunar Gravity. Allows you to jump twice as high as before." nimage = Bonuses.butterfly big = 0 bigbonus = {'big': 1} bigdoc = "Butterflies all around." def taken1(self, dragons): if self.big: import mnstrmap, monsters for i in range(17): monsters.Butterfly(mnstrmap.Butterfly, self.x + random.randrange(-40, 41), self.y + random.randrange(-30, 31), random.choice([-1, 1])) else: TemporaryBonus.taken1(self, dragons) def taken(self, dragon): dragon.dcap['gravity'] *= 0.5 self.carried(dragon) def endaction(self, dragon): dragon.dcap['gravity'] *= 2.0 class Cocktail(TemporaryBonus): "Short Lived Bubbles. Makes your bubbles explode more quickly." nimage = Bonuses.cocktail points = 2000 capname = 'bubbledelay' bigbonus = {'multiply': 3} bigdoc = "Makes your bubbles explode at once. Dangerous!" class Extend(RandomBonus): "E X T E N D. Gives you your missing letters and clear the level. " nimage = Bonuses.extend points = 0 big = 0 bigbonus = {'big': 1} bigdoc = "A lot of letter bubbles! Run! Run!" def taken1(self, dragons): if self.big: self.letterexplosion() else: RandomBonus.taken1(self, dragons) def taken(self, dragon): from bubbles import extend_name names = [extend_name(l) for l in range(6)] missing = [name for name in names if name not in dragon.bubber.letters] x = dragon.x + dragon.ico.w//2 y = dragon.y points(x, y, dragon, 10000*len(missing)) for l in range(6): if extend_name(l) in missing: dragon.bubber.giveletter(l, promize=0) def letterexplosion(self): from bubbles import LetterBubble playercount = len([p for p in BubPlayer.PlayerList if p.isplaying()]) N = 3 + (playercount > 3) angles = [i*(2.0*math.pi/N) for i in range(N)] for l, dx, dy in [(0, 5, 9), (1, 16, 10), (2, 26, 8), (3, 7, 23), (4, 15, 24), (5, 25, 24)]: delta = 2.0*math.pi * random.random() angles = [angle-delta for angle in angles] x = self.x + self.ico.w//2 + 3*(dx-16) y = self.y + self.ico.h//2 + 3*(dy-16) for angle in angles: bubble = LetterBubble(None, l) bubble.thrown_bubble(x, y, 7.0 + 4.0 * random.random(), (math.cos(angle), math.sin(angle))) class HeartPoison(RandomBonus): "Heart Poison. Freeze all free monsters." nimage = Bonuses.heart_poison big = 0 bigbonus = {'big': 1} bigdoc = "Freeze all other players too!" def taken1(self, dragons): import monsters monsters.freeze_em_all() if self.big: def heart_pause(dragon, gen): for i in range(222): yield None dragon.gen = gen for d in BubPlayer.DragonList: if d not in dragons: d.gen = [heart_pause(d, d.gen)] class VioletNecklace(RandomBonus): "Monster Duplicator. Double the number of free monsters." points = 650 nimage = Bonuses.violet_necklace big = 0 bigbonus = {'big': 1} bigdoc = "This level's boring, let's bring even more monsters..." def taken1(self, dragons): if self.big: import monsters, mnstrmap mlist = 2*['Nasty', 'Monky', 'Springy', 'Orcy', 'Gramy', 'Blitzy'] wrange = (boards.bwidth - 8*CELL) // 2 for dir in [1, -1]: for i in range(len(mlist)): name = mlist[i] mdef = getattr(mnstrmap, name) cls = getattr(monsters, name) x = wrange * i // len(mlist) if dir == 1: x = 2*CELL + HALFCELL + x else: x = boards.bwidth - 4*CELL - HALFCELL - x y = -2*CELL - i * 2*CELL cls(mdef, x, y, dir) else: for s in BubPlayer.MonsterList[:]: if s.regular(): for i in range(self.multiply): s.__class__(s.mdef, s.x, s.y, -s.dir * (-1)**i) class WandBonus(RandomBonus): "Wand/Chest. Turn the bubble into bonuses at the end of the level." nimages = [Bonuses.brown_wand, Bonuses.yellow_wand, Bonuses.green_wand, Bonuses.violet_wand, Bonuses.blue_wand, Bonuses.red_wand, Bonuses.violet_chest, Bonuses.blue_chest, Bonuses.red_chest, Bonuses.yellow_chest, ] Modes = [ (Bonuses.brown_wand, 750, Bonuses.cyan_ice, 700, BigImages.cyan_ice, 20000), (Bonuses.yellow_wand, 750, Bonuses.violet_ice, 750, BigImages.violet_ice, 20000), (Bonuses.green_wand, 750, Bonuses.peach2, 800, BigImages.peach2, 30000), (Bonuses.violet_wand, 750, Bonuses.pastec2, 850, BigImages.pastec2, 30000), (Bonuses.blue_wand, 750, Bonuses.cream_pie, 900, BigImages.cream_pie, 40000), (Bonuses.red_wand, 750, Bonuses.sugar_pie, 950, BigImages.sugar_pie, 40000), (Bonuses.violet_chest, 2000, Diamonds.violet, 6000, BigImages.violet, 60000), (Bonuses.blue_chest, 2000, Diamonds.blue, 7000, BigImages.blue, 60000), (Bonuses.red_chest, 2000, Diamonds.red, 8000, BigImages.red, 70000), (Bonuses.yellow_chest, 2000, Diamonds.yellow, 9000, BigImages.yellow, 70000), ] def __init__(self, x, y): self.mode = random.choice(WandBonus.Modes) RandomBonus.__init__(self, x, y, *self.mode[:2]) def taken1(self, dragons): BubPlayer.BubblesBecome = self.bubble_outcome BubPlayer.MegaBonus = self.mega_bonus def bubble_outcome(self, bubble): if bubble.pop(): x = bubble.x if x < 2*CELL: x = 2*CELL elif x > boards.bwidth - 4*CELL: x = boards.bwidth - 4*CELL Bonus(x, bubble.y, *self.mode[2:4]) def mega_bonus(self): nico, npoints = self.mode[4:6] ico = images.sprget(nico) x = random.randrange(0, boards.bwidth-ico.w) mb = Megabonus(x, -ico.h, nico, npoints) mb.outcome = (Bonus,) + self.mode[2:4] mb.outcome_image = self.mode[2] WandBonus1 = WandBonus # increase probability class Megabonus(Bonus): touchable = 0 vspeed = 6 sound = 'Extra' coverwithbonus = 99 fallerdelay = 71 def faller(self): self.fullpoints = self.points self.bubbles = {} for t in range(self.fallerdelay): yield None self.ready_to_go() self.bubbles_pos = list(self.bubbles_position()) self.gen.append(self.animate_bubbles()) y0 = self.y - HALFCELL ymax = boards.bheight - CELL - self.ico.h self.touchable = 1 ny = self.y while self.y >= y0: if self.vspeed: ny += self.vspeed if ny > ymax: ny = ymax self.vspeed = 0 self.move(self.x, int(ny)) yield None self.kill() def ready_to_go(self): pass def is_on_ground(self): return self.y == boards.bheight - CELL - self.ico.h def kill(self): for bubble in list(self.bubbles.values()): bubble.pop() Bonus.kill(self) def taken(self, dragon): poplist = [dragon] for bubble in list(self.bubbles.values()): bubble.pop(poplist) def bubbles_position(self): import time; start=time.time() cx = self.ico.w//2 - CELL cy = self.ico.h//2 - CELL positions = [] pi2 = math.pi * 2 dist = 10.0 for i in range(31): while 1: angle = random.random() * pi2 nx = cx + int(dist*math.sin(angle)) ny = cy + int(dist*math.cos(angle)) for ox, oy in positions: if (nx-ox)*(nx-ox) + (ny-oy)*(ny-oy) < 220: dist += 0.3 break else: break positions.append((nx, ny)) #print time.time()-start return positions ## nx = 5 ## ny = 6 ## xmargin = 2 ## ymargin = 7 ## xstep = (self.ico.w+2*xmargin-2*CELL) / float(nx-1) ## ystep = (self.ico.h+2*ymargin-2*CELL) / float(ny-1) ## for dx in range(nx): ## corner = dx in [0, nx-1] ## for dy in range(corner, ny-corner): ## dx1 = int(dx*xstep)-xmargin ## dy1 = int(dy*ystep)-ymargin ## yield (dx1 + random.randrange(-2,3), ## dy1 + random.randrange(-2,3)) def nearest_free_point(self, x0, y0): distlst = [((x0-x)*(x0-x)+(y0-y)*(y0-y)+random.random(), x, y) for x, y in self.bubbles_pos if (x, y) not in self.bubbles] if distlst: ignored, dx, dy = min(distlst) return dx, dy else: return None, None def in_bubble(self, bubble): if not self.touchable: return # bubbling a BonusMaker about to make a big bonus dx, dy = self.nearest_free_point(bubble.x-self.x, bubble.y-self.y) if dx is not None: self.cover_bubble(dx, dy, bubble.d.bubber) self.gen.append(self.cover_bubbles(bubble.d.bubber)) bubble.kill() def cover_bubbles(self, bubber): while 1: for t in range(2): yield None bubbles = [dxy for dxy, b in list(self.bubbles.items()) if b.bubber is bubber] if not bubbles: break dx, dy = self.nearest_free_point(*random.choice(bubbles)) if dx is None: break self.cover_bubble(dx, dy, bubber) self.untouchable() def cover_bubble(self, dx, dy, bubber): if (dx, dy) in self.bubbles: return from bubbles import Bubble if len(self.bubbles) & 1: MegabonusBubble = Bubble elif self.coverwithbonus: self.coverwithbonus -= 1 outcome = self.outcome outcome_image = self.outcome_image class MegabonusBubble(Bubble): def popped(self, dragon): BonusMaker(self.x, self.y, [outcome_image], outcome=outcome) return 10 else: MegabonusBubble = Bubble nimages = GreenAndBlue.normal_bubbles[bubber.pn] b = MegabonusBubble(images.sprget(nimages[1]), self.x+dx, self.y+dy) b.dx = dx b.dy = dy b.bubber = bubber b.nimages = nimages self.bubbles[dx, dy] = b self.timeout = 0 f = float(len(self.bubbles)) / len(self.bubbles_pos) self.vspeed = -0.73*f + self.vspeed*(1.0-f) self.points = int(self.fullpoints*(1.0-f) / 10000.0 + 0.9999) * 10000 def animate_bubbles(self): if 0: # disabled clipping d = {} for dx, dy in self.bubbles_pos: d[dx] = d[dy] = None north = d.copy() south = d.copy() west = d.copy() east = d.copy() del d for dx, dy in self.bubbles_pos: lst = [y for x, y in self.bubbles_pos if x==dx and ydy] if lst: south[dy] = min(lst) lst = [x for x, y in self.bubbles_pos if xdx and y==dy] if lst: east[dx] = min(lst) W = 2*CELL H = 2*CELL bubbles = self.bubbles while 1: for cycle in [1]*8 + [2]*10 + [1]*8 + [0]*10: yield None for (dx, dy), bubble in list(bubbles.items()): if not hasattr(bubble, 'poplist'): if 0: # disabled clipping if (dx, north[dy]) in bubbles: margin_n = (north[dy]+H-dy)//2 else: margin_n = 0 if (dx, south[dy]) in bubbles: margin_s = (dy+H-south[dy])//2 else: margin_s = 0 if (west[dx], dy) in bubbles: margin_w = (west[dx]+W-dx)//2 else: margin_w = 0 if (east[dx], dy) in bubbles: margin_e = (dx+W-east[dx])//2 else: margin_e = 0 r = (margin_w, margin_n, W-margin_w-margin_e, H-margin_n-margin_s) bubble.move(self.x + bubble.dx + margin_w, self.y + bubble.dy + margin_n, images.sprget_subrect( bubble.nimages[cycle], r)) else: bubble.move(self.x + bubble.dx, self.y + bubble.dy, images.sprget(bubble.nimages[cycle])) elif len(bubbles) == len(self.bubbles_pos): self.pop_bubbles(bubble.poplist) return def pop_bubbles(self, poplist): def bubble_timeout(bubble, vspeed): ny = bubble.y for t in range(random.randrange(15,25)): if hasattr(bubble, 'poplist'): return ny += vspeed bubble.move(bubble.x, int(ny)) yield None bubble.pop(poplist) for bubble in list(self.bubbles.values()): bubble.gen.append(bubble_timeout(bubble, self.vspeed)) self.bubbles.clear() self.kill() class Cactus(RandomBonus): "Cactus. Drop a big version of a random bonus." points = 600 nimage = 'cactus' extra_cheat_arg = None bigbonus = {'multiply': 3} bigdoc = "Let's get more big bonuses!" def taken1(self, dragons): count = 0 while count < self.multiply: args = () if self.extra_cheat_arg: cls = globals()[self.extra_cheat_arg] self.extra_cheat_arg = None elif bigclockticker and bigclockticker.state == 'pre': cls = Clock args = (1,) else: cls = random.choice(Classes) if makecactusbonus(cls, *args): count += 1 cactusbonussound() #Cactus1 = Cactus # increase probability OFFSCREEN = -3*CELL def makecactusbonus(cls, *args): bonus = cls(OFFSCREEN, 0, *args) if not bonus.alive or getattr(bonus, 'bigbonus', None) is None: if bonus.alive: bonus.kill() return None bonus.__dict__.update(bonus.bigbonus) bonus.untouchable() bonus.gen = [] megacls = bonus.bigbonus.get('megacls', Cactusbonus) mb = megacls(0, -3*CELL, 'cactus', 10000) # temp image mb.outcome = (cls,) + (args or bonus.bigbonus.get('outcome_args', ())) mb.outcome_image = bonus.nimage mb.bonus = bonus mb.gen.append(mb.prepare_image()) mb.gen.append(mb.remove_if_no_bonus()) return mb def cactusbonussound(): gamesrv.set_musics([], []) boards.curboard.set_musics(prefix=[images.music_modern]) boards.curboard.set_musics() class Cactusbonus(Megabonus): coverwithbonus = 5 def prepare_image(self): while images.computebiggericon(self.bonus.ico) is None: yield None def remove_if_no_bonus(self): while self.bonus.alive: yield None self.kill() def ready_to_go(self): ico = images.biggericon(self.bonus.ico) x = random.randrange(0, boards.bwidth-ico.w) self.move(x, -ico.h, ico) def taken1(self, dragons): d1 = list(dragons) Megabonus.taken1(self, dragons) if self.bonus.alive: x = self.x + self.ico.w//2 - CELL y = self.y + self.ico.h//2 - CELL self.bonus.move(x, y) res = self.bonus.taken1(d1) self.untouchable() if res == -1: self.taken_by = [] self.gen.append(self.touchdelay(10)) self.bonus.move(OFFSCREEN, 0) else: self.bonus.kill() return res def kill(self): Megabonus.kill(self) if self.bonus.alive: self.bonus.kill() class LongDurationCactusbonus(Cactusbonus): timeout = 500 killgens = 0 def starexplosion(x, y, multiplyer, killmonsters=0, outcomes=[]): outcomes = list(outcomes) poplist = [None] for i in range(multiplyer): colors = list(Stars.COLORS) random.shuffle(colors) for colorname in colors: images = getattr(Stars, colorname) if outcomes: outcome = outcomes.pop() extra_stars = [] if hasattr(outcome[0], 'extra_stars_location'): for sx, sy in outcome[0].extra_stars_location: extra_stars.append(BonusMakerExtraStar(x, y, sx, sy, colorname)) bm = BonusMaker(x, y, images, outcome=outcome) for star in extra_stars: star.gen.append(star.follow_bonusmaker(bm)) else: b = Parabolic2(x, y, images) if killmonsters: b.gen.append(b.killmonsters(poplist)) class HomingStar(ActiveSprite): def __init__(self, x, y, colorname, poplist): imglist = getattr(Stars, colorname) ActiveSprite.__init__(self, images.sprget(imglist[0]), x, y) self.colorname = colorname self.setimages(self.cyclic(imglist, 2)) self.gen.append(self.homing(poplist)) def homing(self, poplist): from monsters import Monster target = None vx = (random.random() - 0.5) * 6.6 vy = (random.random() - 0.5) * 4.4 nx = self.x ny = self.y counter = 10 while 1: if random.random() < 0.02: target = None if target is None or not target.alive: bestdist = 1E10 for s in BubPlayer.MonsterList: if isinstance(s, Monster): dx = s.x - nx dy = s.y - ny dist = dx*dx + dy*dy + (random.random() * 25432.1) if dist < bestdist: bestdist = dist target = s if target is None: break dx = target.x - nx dy = target.y - ny dist = dx*dx + dy*dy if dist <= 3*CELL*CELL: target.argh(poplist) break yield None vx = (vx + dx * 0.005) * 0.96 vy = (vy + dy * 0.005) * 0.96 nx += vx ny += vy self.move(int(nx), int(ny)) if counter: counter -= 1 else: img = ('smstar', self.colorname, random.randrange(2)) s = ActiveSprite(images.sprget(img), self.x + 8, self.y + 8) s.gen.append(s.die([None], speed=10)) counter = 3 self.kill() class Book(RandomBonus): "Magic Bomb. Makes a magical explosion killing touched monsters." points = 2000 nimage = Bonuses.book big = 0 bigbonus = {'big': 1} bigdoc = "Homing Magical Stars." def taken1(self, dragons): if self.big: poplist = [None] x = self.x + (self.ico.w - 2*CELL) // 2 y = self.y + (self.ico.h - 2*CELL) // 2 colors = list(Stars.COLORS) random.shuffle(colors) for colorname in colors + colors[len(colors)//2:]: HomingStar(x, y, colorname, poplist) else: starexplosion(self.x, self.y, self.multiply, killmonsters=1) class Potion(RandomBonus): "Potions. Clear the level and fill its top with bonuses." nimages = [Bonuses.red_potion, Bonuses.green_potion, Bonuses.yellow_potion, 'potion4'] Potions = [(Bonuses.red_potion, 150, [(PotionBonuses.coin, 350), (PotionBonuses.rainbow, 600)]), (Bonuses.green_potion, 350, [(PotionBonuses.flower, 1000), (PotionBonuses.trefle, 2000)]), (Bonuses.yellow_potion, 550, [(PotionBonuses.green_note, 2000), (PotionBonuses.blue_note, 3000)]), ('potion4', 750, None), ] LocalDir = os.path.dirname(__file__) or os.curdir # print(f"LocalDir = {LocalDir}") Extensions = [s for s in os.listdir(os.path.dirname(__file__) or os.curdir) if s.startswith('ext') and os.path.isdir(os.path.join(os.path.dirname(__file__) or os.curdir, s))] random.shuffle(Extensions) extra_cheat_arg = None big = 0 bigdoc = "Fill the whole level with bonuses." def __init__(self, x, y): p_normal = 3 if boards.curboard.bonuslevel: p_extension = 2 # make extensions rare in the bonus level else: p_extension = 5 if self.extra_cheat_arg: Potion.Extensions.append(self.extra_cheat_arg) p_normal = 0 if not Potion.Extensions: p_extension = 0 choices = [] for mode in Potion.Potions: if mode[2] is None: p = p_extension else: p = p_normal choices += [mode] * p self.mode = random.choice(choices) if self.mode[2] is not None: self.bigbonus = {'big': 1} RandomBonus.__init__(self, x, y, *self.mode[:2]) def taken1(self, dragons): blist = self.mode[2] if blist is not None: if random.random() < 0.6: blist = [random.choice(blist)] boards.replace_boardgen(boards.potion_fill(blist, self.big)) else: n_players = len([p for p in BubPlayer.PlayerList if p.isplaying()]) while Potion.Extensions: ext = Potion.Extensions.pop() #print "Trying potion:", ext ext = __import__(ext, globals(),locals(), ['run','min_players']) if n_players >= ext.min_players: ext.run() boards.BoardGen.append(boards.extra_bkgnd_black(self.x, self.y)) #print "Accepted because:", n_players, ">=", ext.min_players break else: #print "Rejected because:", n_players, "<", ext.min_players pass class FireBubble(RandomBonus): "Fire Bubbles. Makes you fire napalm bubbles." nimage = Bonuses.hamburger bubkind = 'FireBubble' bubcount = 10 bigbonus = {'bubkind': 'BigFireBubble'} bigdoc = "Makes you shoot fire - you're a dragon after all." def taken(self, dragon): dragon.dcap['shootbubbles'] = [self.bubkind] * self.bubcount dragon.carrybonus(self) class WaterBubble(FireBubble): "Water Bubbles. Your bubbles will now be filled with water." nimage = Bonuses.beer bubkind = 'WaterBubble' bigbonus = {'bubkind': 'SnookerBubble'} bigdoc = "Snooker balls." class LightningBubble(FireBubble): "Lightning Bubbles." nimage = Bonuses.french_fries bubkind = 'LightningBubble' bigbonus = {'bubkind': 'BigLightBubble'} bigdoc = "Even-more-lightning Bubbles." class Megadiamond(Megabonus): nimage = BigImages.red points = 20000 fallerdelay = 0 outcome = (MonsterBonus, -1) outcome_image = Bonuses.monster_bonuses[-1][0] extra_stars_location = [ (-24,-28),(0,-28),(24,-28), (-40,-11), (40,-11), (-32, 8), (32, 8), (-16,23), (16,23), (0,38), ] def __init__(self, x, y): ico = images.sprget(self.nimage) x -= (ico.w - 2*CELL) // 2 y -= (ico.h - 2*CELL) // 2 Megabonus.__init__(self, x, y) class Door(RandomBonus): "Magic Door. Let bonuses come in!" points = 1000 nimage = Bonuses.door diamond_outcome = (MonsterBonus, -1) bigbonus = {'diamond_outcome': (Megadiamond,)} bigdoc = "Let bigger bonuses come in!" def taken1(self, dragons): starexplosion(self.x, self.y, 2, outcomes = [self.diamond_outcome] * 10) class LongFire(RandomBonus): "Long Fire. Increase the range of your bubble throw out." nimage = Bonuses.softice1 big = 0 bigbonus = {'big': 1} bigdoc = "Throw bubbles that split into more bubbles." def taken(self, dragon): if self.big: dragon.dcap['shootbubbles'] = ['MoreBubblesBubble'] * 10 else: dragon.dcap['shootthrust'] *= 1.5 dragon.carrybonus(self) class Glue(RandomBonus): "Glue. Triple fire." nimage = 'glue' points = 850 big = 0 bigbonus = {'big': 1} bigdoc = "Heptuple fire. (That's 7.)" def taken(self, dragon): if self.big: dragon.dcap['flower'] = -16 # heptuple fire elif dragon.dcap['flower'] >= 0: dragon.dcap['flower'] = -1 # triple fire else: dragon.dcap['flower'] -= 1 # cumulative effect dragon.carrybonus(self) class ShortFire(RandomBonus): "Short Fire. Shorten the range of your bubble throw out." nimage = Bonuses.softice2 points = 300 factor = 1 / 1.5 bigbonus = {'factor': 0} bigdoc = "What occurs if you throw bubbles at range zero?" def taken(self, dragon): dragon.dcap['shootthrust'] *= self.factor dragon.carrybonus(self) class HighSpeedFire(RandomBonus): "High Speed Fire. Increase your fire rate." nimage = Bonuses.custard_pie points = 700 bigbonus = {'multiply': 4} bigdoc = "Machine-gun speed!" def taken(self, dragon): dragon.dcap['firerate'] += 1.5 dragon.carrybonus(self) class Mushroom(TemporaryBonus): "Bouncy Bouncy. Makes you jump continuously." nimage = Bonuses.mushroom points = 900 capname = 'pinball' captime = 625 bigbonus = {'captime': captime*2, 'multiply': 2} bigdoc = "The same, but even more annoying." class AutoFire(TemporaryBonus): "Auto Fire. Makes you fire continuously." nimage = Bonuses.rape points = 800 capname = 'autofire' captime = 675 big = 0 bigbonus = {'big': 1} bigdoc = "Adds many bubbles to the level." def taken1(self, dragons): if self.big: boards.extra_boardgen(boards.extra_bubbles(900)) else: TemporaryBonus.taken1(self, dragons) class Insect(RandomBonus): "Crush World." nimage = Bonuses.insect big = 0 bigbonus = {'big': 1} bigdoc = "What if the level looked like that instead... Or like that... Or..." def taken1(self, dragons): if self.big: if dragons: d = random.choice(dragons) cx, cy = d.x, d.y else: cx, cy = None, None boards.extra_boardgen(boards.extra_make_random_level(cx, cy)) else: boards.extra_boardgen(boards.extra_walls_falling()) class Ring(TemporaryBonus): "The One Ring." nimage = Bonuses.ring points = 4000 capname = 'ring' captime = 700 bonusleveldivider = 5 bigbonus = {'multiply': 3} bigdoc = "Where am I?" class GreenPepper(TemporaryBonus): "Hot Pepper. Run! Run! That burns." nimage = Bonuses.green_pepper capname = 'hotstuff' captime = 100 bigbonus = {'captime': 250, 'multiply': 2} bigdoc = "That burns a lot!" class Lollipop(TemporaryBonus): "Yo Man! Makes you walk backward." nimage = Bonuses.lollipop big = 0 bigbonus = {'big': 1} bigdoc = "Just swapping 'left' and 'right' is not confusing enough." def taken(self, dragon): dragon.dcap['left2right'] = -dragon.dcap['left2right'] if self.big: perm = list(range(4)) while perm[0] == 0 or perm[1] == 1 or perm[2] == 2 or perm[3] == 3: random.shuffle(perm) names = ('key_left', 'key_right', 'key_jump', 'key_fire') dragon.dcap['key_right'] = names[perm[0]] dragon.dcap['key_left'] = names[perm[1]] dragon.dcap['key_jump'] = names[perm[2]] dragon.dcap['key_fire'] = names[perm[3]] self.carried(dragon) def endaction(self, dragon): dragon.dcap['left2right'] = -dragon.dcap['left2right'] for name in ('key_left', 'key_right', 'key_jump', 'key_fire'): dragon.dcap[name] = name class Chickpea(TemporaryBonus): "Basilik. Allows you to touch the monsters." nimage = Bonuses.chickpea points = 800 capname = 'overlayglasses' captime = 400 big = 0 bigbonus = {'big': 1} bigdoc = "Turn off the light." def taken1(self, dragons): if self.big: boards.extra_boardgen(boards.extra_light_off(597), 1) else: TemporaryBonus.taken1(self, dragons) def taken(self, dragon): TemporaryBonus.taken(self, dragon) dragon.dcap['shield'] += 420 class IceCream(RandomBonus): "Icecream. An icecream which is so good you'll always want more." nimages = [Bonuses.icecream6, Bonuses.icecream5, Bonuses.icecream4, Bonuses.icecream3] IceCreams = [(Bonuses.icecream6, 250), (Bonuses.icecream5, 500), (Bonuses.icecream4, 1000), (Bonuses.icecream3, 2000)] big = 0 bigbonus = {'big': 1} bigdoc = "BIG ice creams!" def __init__(self, x, y, generation=0): self.generation = generation RandomBonus.__init__(self, x, y, *self.IceCreams[generation]) def taken1(self, dragons): nextgen = self.generation + 1 if nextgen < len(self.IceCreams): for i in range(2): if self.big: makecactusbonus(IceCream, nextgen) else: x, y = chooseground(200) if x is None: return IceCream(x, y, nextgen) if self.big: cactusbonussound() class Grenade(RandomBonus): "Barbecue." nimage = Bonuses.grenade points = 550 big = 0 bigbonus = {'big': 1} bigdoc = "360-degree flames." def taken1(self, dragons): from bubbles import FireFlame poplist = [None] for y in range(1, boards.height-1): for x in range(2, boards.width-2): if bget(x,y) != ' ': continue if bget(x,y+1) == '#': FireFlame(x, y, poplist) elif self.big: if bget(x,y-1) == '#': FireFlame(x, y, poplist, flip='vflip') elif bget(x-1,y) == '#': FireFlame(x, y, poplist, flip='cw') elif bget(x+1,y) == '#': FireFlame(x, y, poplist, flip='ccw') class Conch(RandomBonus): "Sea Shell. Let's bring the sea here!" nimage = Bonuses.conch points = 650 big = 0 bigbonus = {'big': 1} bigdoc = "Aquarium." def taken1(self, dragons): if self.big: gen = boards.extra_aquarium else: gen = boards.extra_water_flood boards.extra_boardgen(gen()) def fire_rain(x, poplist): from bubbles import FireDrop FireDrop(x, -CELL, poplist) def water_rain(x, poplist): from bubbles import watercell watercell(x, 0, poplist) def ball_rain(x, poplist): from bubbles import SpinningBall SpinningBall(x, -CELL, poplist) class Umbrella(RandomBonus): "Umbrellas. Beware of what's going to fall on everyone's head!" nimages = [Bonuses.brown_umbrella, Bonuses.grey_umbrella, Bonuses.violet_umbrella] Umbrellas = [(Bonuses.brown_umbrella, 900, fire_rain, 10, 60), (Bonuses.grey_umbrella, 950, water_rain, 5, 60), (Bonuses.violet_umbrella, 1000, ball_rain, 9, 120)] bigbonus = {'multiply': 3.1416} bigdoc = "It's raining hard." def __init__(self, x, y): self.mode = random.choice(Umbrella.Umbrellas) RandomBonus.__init__(self, x, y, *self.mode[:2]) def taken1(self, dragons): for i in range(self.multiply): boards.extra_boardgen(self.raining()) def raining(self): builder, drops, timemax = self.mode[2:] timemax = int(timemax * math.sqrt(self.multiply)) drops = int(drops * self.multiply) times = [random.randrange(0, timemax) for i in range(drops)] poplist = [None] for t in range(timemax): for i in range(times.count(t)): x = random.randrange(2*CELL, bwidth-3*CELL+1) builder(x, poplist) yield 0 class Fruits(RandomBonus): "Fruits. A small little bonus. But the size doesn't matter, does it? If you're lucky enough you might get a great shower!" nimages = [Bonuses.kirsh, Bonuses.erdbeer, Bonuses.tomato, Bonuses.apple, Bonuses.corn, Bonuses.radish] bubblable = 0 sound = 'Extra' Fruits = [(Bonuses.kirsh, 100), #(Bonuses.icecream1, 150), (Bonuses.erdbeer, 150), #(Bonuses.fish1, 250), (Bonuses.tomato, 200), #(Bonuses.donut, 250), (Bonuses.apple, 250), (Bonuses.corn, 300), #(Bonuses.icecream2, 600), (Bonuses.radish, 350), ] def __init__(self, x, y): # x and y ignored ! fine = 0 for i in range(20): x0 = random.randint(3, boards.width-5) y0 = random.randint(1, boards.height-3) for xt in range(x0-1, x0+3): if xt == x0-1 or xt == x0+2: yplus = 1 else: yplus = 0 for yt in range(y0+yplus, y0+4-yplus): if bget(xt,yt) != ' ': break else: continue break else: x, y = x0*CELL, y0*CELL fine = 1 break mode = random.choice(Fruits.Fruits) RandomBonus.__init__(self, x, y, falling=0, *mode) self.repeatcount = 0 if not fine: self.kill() elif random.random() < 0.04: self.superfruit = mode self.sound = 'Shh' self.points = 0 self.repeatcount = random.randrange(50,100) def taken1(self, dragons): if self.repeatcount: image, points = self.superfruit f = Parabolic2(self.x, self.y, [image], y_amplitude = -1.5) f.points = points f.touchable = 1 self.repeatcount -= 1 self.gen.append(self.taking(1, 2)) return -1 Fruits1 = Fruits # increase probability Fruits2 = Fruits Fruits3 = Fruits Fruits4 = Fruits Fruits5 = Fruits Fruits6 = Fruits class BlueNecklace(RandomBonus): "Self Duplicator. Mirror yourself." points = 1000 nimage = Bonuses.blue_necklace copies = 1 bigbonus = {'copies': 3} bigdoc = "Mirrors vertically too." def taken(self, dragon): dragons = [dragon] modes = [(-1, 1), (1, -1), (-1, -1)][:self.copies] modes.reverse() dcap = dragon.dcap.copy() for sign, gravity in modes: if len(dragon.bubber.dragons) >= 7: break # avoid burning the server with two much dragons d1 = self.makecopy(dragon, sign, gravity, dcap) dragons.append(d1) d1 = random.choice(dragons) d1.carrybonus(self, 250) def makecopy(self, dragon, sign=-1, gravity=1, dcap=None): from player import Dragon dcap = dcap or dragon.dcap d = Dragon(dragon.bubber, dragon.x, dragon.y, -dragon.dir, dcap) d.dcap['left2right'] = sign * dcap['left2right'] d.dcap['gravity'] = gravity * dcap['gravity'] d.up = dragon.up s = (dcap['shield'] + 12) & ~3 dragon.dcap['shield'] = s+2 if sign*gravity > 0: s += 2 d.dcap['shield'] = s dragon.bubber.dragons.append(d) return d class Monsterer(RandomBonus): "Monsterificator. Let's play on the other side!" nimages = [Bonuses.red_crux, Bonuses.blue_crux] Sizes = [(Bonuses.red_crux, 800), (Bonuses.blue_crux, 850)] mlist = [['Nasty', 'Monky', 'Springy', 'Orcy'], ['Ghosty', 'Flappy', 'Gramy', 'Blitzy'] ] big = 0 bigbonus = {'big': 1} bigdoc = "Ta, ta ta, ta, taaaaaa..." def __init__(self, x, y): self.mode = random.choice([0,1]) RandomBonus.__init__(self, x, y, *self.Sizes[self.mode]) def taken(self, dragon): mcls = random.choice(self.mlist[self.mode]) dragon.become_monster(mcls, self.big) Monsterer1 = Monsterer # increase probability class Bubblizer(RandomBonus): "Bubblizer." points = 750 nimage = Bonuses.gold_crux big = 0 bigbonus = {'big': 1} bigdoc = "Special powers for your bubble." def taken(self, dragon): args = (dragon.bubber.pn,) if self.big: from bubbles import SnookerBubble, BigLightBubble bcls = random.choice([SnookerBubble, BigLightBubble]) if bcls is SnookerBubble: args = (dragon, dragon.x, dragon.y, 1000000) else: from bubbles import FireBubble, WaterBubble, LightningBubble bcls = random.choice([FireBubble, WaterBubble, LightningBubble]) b = bcls(*args) b.move(dragon.x, dragon.y) if not dragon.become_bubblingeyes(b): b.kill() class Carrot(RandomBonus): "Angry Monster. Turns all free monsters angry." nimage = Bonuses.carrot points = 950 ghost = 0 bigbonus = {'ghost': 1} bigdoc = "What do angry monsters turn into if you don't hurry up?" def taken1(self, dragons): from monsters import Monster lst = [s for s in images.ActiveSprites if isinstance(s, Monster) and s.regular()] if lst: if self.ghost: images.Snd.Hell.play() for s in lst: s.become_ghost() else: for s in lst: s.angry = [s.genangry()] s.resetimages() class Egg(RandomBonus): "Teleporter. Exchange yourself with somebody else." nimage = Bonuses.egg big = 0 bigbonus = {'big': 1} bigdoc = "Exchange colors too." def taken1(self, dragons): if self.big: self.exchange_bubbers() else: self.exchange_dragons(dragons) def exchange_dragons(self, dragons): dragons = [d for d in dragons if d in d.bubber.dragons] alldragons = [d for d in BubPlayer.DragonList if d in d.bubber.dragons] others = [d for d in alldragons if d not in dragons] xchg = {} random.shuffle(dragons) random.shuffle(others) while dragons and others: d1 = dragons.pop() d2 = others.pop() xchg[d1] = d2.bubber xchg[d2] = d1.bubber if len(dragons) > 1: copy = dragons[:] for i in range(10): random.shuffle(copy) for j in range(len(dragons)): if dragons[j] == copy[j]: break else: break for d1, d2 in zip(dragons, copy): xchg[d1] = d2.bubber elif len(dragons) == 1: x, y = chooseground(200) if x is not None: d1 = dragons[0] d1.move(x, y) d1.dcap['shield'] = 50 for d1, bubber2 in list(xchg.items()): d1.bubber.dragons.remove(d1) d1.bubber = bubber2 bubber2.dragons.append(d1) d1.dcap['shield'] = 50 def exchange_bubbers(self): self.exchange_dragons(list(BubPlayer.DragonList)) players = [p for p in BubPlayer.PlayerList if p.isplaying()] if len(players) > 1: while 1: copy = players[:] random.shuffle(copy) for j in range(len(players)): if players[j] is copy[j]: break else: break for b1, b2 in zip(players, copy): for d in b1.dragons: d.dcap['bubbericons'] = b2 class Bomb(RandomBonus): "Baaoouuuummmm! Explode that wall!" nimage = Bonuses.bomb bigbonus = {'multiply': 3.8} bigdoc = "Makes a BIG hole." def taken1(self, dragons): bomb_explosion(self.x, self.y, self.multiply) def bomb_explosion(x0, y0, multiply=1, starmul=2): RADIUS = 3.9 * CELL * math.sqrt(multiply) Radius2 = RADIUS * RADIUS brd = boards.curboard cx = x0 + HALFCELL cy = y0 + HALFCELL - RADIUS/2 for y in range(0, brd.height): dy1 = abs(y*CELL - cy) dy2 = abs((y-(brd.height-1))*CELL - cy) dy3 = abs((y+(brd.height-1))*CELL - cy) dy = min(dy1, dy2, dy3) for x in range(2, brd.width-2): dx = x*CELL - cx if dx*dx + dy*dy < Radius2: try: brd.killwall(x,y) except KeyError: pass brd.reorder_walls() starexplosion(x0, y0, starmul) gen = boards.extra_display_repulse(x0+CELL, y0+CELL, 15000 * multiply, 1000 * multiply) boards.extra_boardgen(gen) class Ham(RandomBonus): "Protein. Let's build something!" nimage = Bonuses.ham bigbonus = {'multiply': 3.4} bigdoc = "Builds something BIG." def taken1(self, dragons): RADIUS = 3.9 * CELL * math.sqrt(self.multiply) Radius2 = RADIUS * RADIUS brd = boards.curboard cx = self.x + HALFCELL cy = self.y + HALFCELL - RADIUS/2 xylist = [] for y in range(0, brd.height): dy1 = abs(y*CELL - cy) dy2 = abs((y-(brd.height-1))*CELL - cy) dy3 = abs((y+(brd.height-1))*CELL - cy) dy = min(dy1, dy2, dy3) for x in range(2, brd.width-2): dx = x*CELL - cx if dx*dx + dy*dy < Radius2: if (y,x) not in brd.walls_by_pos and random.random() < 0.5: brd.putwall(x,y) xylist.append((x, y)) brd.reorder_walls() boards.extra_boardgen(boards.single_blocks_falling(xylist)) gen = boards.extra_display_repulse(self.x+CELL, self.y+CELL, 5000 * self.multiply, 1000 * self.multiply) boards.extra_boardgen(gen) class Chestnut(RandomBonus): "Relativity. Speed up or slow down the game." nimage = Bonuses.chestnut sound = None big = 0 bigbonus = {'big': 1} bigdoc = "Relative relativity - not the same one for players and monsters." def taken1(self, dragons): timeout = 500 if not self.big: ft = random.choice([0.5, 2.0]) boards.set_frametime(ft) if ft == 2.0: timeout = 430 else: if random.randrange(0, 2) == 1: # super-fast game boards.set_frametime(0.25) timeout = 1800 else: # board unchanged, players slower boards.set_frametime(1.0, privtime=250) timeout = 800 BubPlayer.MultiplyerReset = BubPlayer.FrameCounter + timeout self.play(images.Snd.Fruit) try: import statesaver except ImportError: print("'statesaver' module not compiled, no clock bonus") Clock = None else: import new try: from statesaver import standard_build # PyPy except ImportError: def standard_build(self): return new.instance(self.__class__) boards.Copyable.inst_build = standard_build gamesrv.Sprite.inst_build = standard_build def copygamestate(): # makes a copy of the game state. ps = [] for p1 in BubPlayer.PlayerList: #if p1.isplaying(): d = p1.__dict__.copy() for key in BubPlayer.TRANSIENT_DATA: if key in d: del d[key] ps.append(d) #else: # ps.append(None) topstate = ( [g for g in boards.BoardGen if not g.gi_running], boards.curboard, images.ActiveSprites, images.SpritesByLoc, list(BubPlayer.__dict__.items()), gamesrv.sprites, gamesrv.sprites_by_n, ps, list(images.Snd.__dict__.items()), ) #import pdb; pdb.set_trace() return statesaver.copy(topstate) def restoregamestate(savedstate): (boards.BoardGen, boards.curboard, images.ActiveSprites, images.SpritesByLoc, BubPlayerdictitems, gamesrv.sprites, gamesrv.sprites_by_n, ps, imagesSnddictitems, ) = savedstate for key, value in BubPlayerdictitems: try: setattr(BubPlayer, key, value) except (AttributeError, TypeError): pass for key, value in imagesSnddictitems: try: setattr(images.Snd, key, value) except (AttributeError, TypeError): pass for p, d in zip(BubPlayer.PlayerList, ps): #if d is None: # p.reset() #else: p.__dict__.update(d) if not p.isplaying(): p.zarkoff() class Clock(RandomBonus): "Time Machine. Let's do it again!" touchable = 0 points = 0 nimage = Bonuses.clock ticker = None bigdoc = "Let's do the whole level again - with the help of ghosts from your own past." def __init__(self, x, y, big=0): RandomBonus.__init__(self, -boards.bwidth, 0) #print "starting clock" self.savedstate = None self.savedscreens = [] if bigclockticker: if not big: self.kill() # confusion between the two levels of saving return if x == OFFSCREEN: if bigclockticker.state == 'pre': self.bigbonus = {'ticker': bigclockticker} bigclockticker.state = 'seen' else: # when taken, this has the same effect as the big clock self.ticker = bigclockticker self.move(x, y) self.touchable = 1 return self.gen = [self.delayed_show()] def delayed_show(self): boards.extra_boardgen(self.state_saver()) for i in range(10): yield None if self.savedstate is not None: for i in range(55): yield None x, y = chooseground(200) if x is not None: self.move(x, y) self.touchable = 1 self.gen.append(self.timeouter()) self.gen.append(self.faller()) return self.kill() def taken1(self, dragons): if self.ticker: return self.ticker.taken(dragons) savedstate = self.savedstate self.savedstate = None if savedstate is not None: boards.replace_boardgen(self.state_restorer(savedstate, self.savedscreens, self)) self.untouchable() return -1 def state_saver(self): # called from BoardGen self.savedstate = copygamestate() while self.alive: gamesrv.sprites[0] = '' data = ''.join(gamesrv.sprites) self.savedscreens.append(data) yield 0 yield 0 self.savedscreens.append(data) yield 0 yield 0 self.savedscreens = [] def state_restorer(self, savedstate, savedscreens, blinkme): # called from BoardGen from player import scoreboard status = 0 for t in range(10): if not (t & 1): gamesrv.sprites[0] = '' savedscreens.append(''.join(gamesrv.sprites)) time = boards.normal_frame() for i in range(t): status += 1 if status % 3 == 0 and blinkme.alive: if status % 6 == 0: blinkme.step(boards.bwidth, 0) else: blinkme.step(-boards.bwidth, 0) yield time yield boards.force_singlegen() yield 15.0 for p1 in BubPlayer.PlayerList: del p1.dragons[:] delay = 8.5 gamesrv.clearsprites() while savedscreens: gamesrv.sprites[:] = ['', savedscreens.pop()] if delay > 0.6: delay *= 0.9 yield delay yield 15.0 restoregamestate(savedstate) scoreboard() yield 2.5 class DragonGhost(ActiveSprite): def __init__(self, entry): ActiveSprite.__init__(self, entry.ico, entry.x, entry.y) def setentry(self, entry): #ico = images.make_darker(entry.ico, True) self.lastx = self.x self.lasty = self.y self.move(entry.x, entry.y, entry.ico) self.entry = entry self.bubber = entry.d.bubber self.dir = entry.dir self.poplist = [self] def integrate(self): self.play(images.Snd.Shh) for j in range(15): DustStar(self.x, self.y, 0, -3, clock=j==14) def disintegrate(self): self.play(images.Snd.Shh) dx = self.x - self.lastx dy = self.y - self.lasty if dx < -4: dx = -4 if dy < -4: dy = -4 if dx > 4: dx = 4 if dy > 4: dy = 4 for j in range(15): DustStar(self.x, self.y, dx, dy, clock=j==14) self.kill() def bottom_up(self): return self.entry.dcap['gravity'] < 0.0 class SavedDragonEntry(object): __slots__ = ['d', 'x', 'y', 'ico', 'flag', 'dir', 'dcap'] def __init__(self, d, flag, dir, dcap): self.d = d self.x = d.x self.y = d.y self.ico = d.ico self.flag = flag self.dir = dir self.dcap = dcap class SavedFrameEntry(object): __slots__ = ['saved_next', 'tick', 'dragonlist', 'shoots1'] def __init__(self, tick, dragonlist): self.saved_next = None self.tick = tick self.dragonlist = dragonlist self.shoots1 = [] class BigClockTicker: dragonlist = None tick = 1000 def __init__(self): global random random = random_module.Random() localrandom = DustStar.localrandom self.state = 'pre' self.randombase1 = hash(localrandom.random()) * 914971 self.randombase2 = hash(localrandom.random()) * 914971 self.saved_next = None self.saved_last = self random.seed(self.randombase1) random_module.seed(self.randombase2) self.latest_entries = {} def common_tick(self, entry): self.dragonlist = entry.dragonlist random.seed(self.randombase1 - entry.tick) random_module.seed(self.randombase2 - entry.tick) bonus_frame_tick() random.seed(self.randombase1 + entry.tick) random_module.seed(self.randombase2 + entry.tick) def save_frame_tick(self): entry = self.save_frame() self.common_tick(entry) def save_frame(self): from player import Dragon from bubbles import DragonBubble tick = self.saved_last.tick + 1 dragonlist = [] new_entries = {} for bubber in BubPlayer.PlayerList: if bubber.isplaying(): for d in bubber.dragons: try: dcap = self.latest_entries[d].dcap except KeyError: dcap = None dir = getattr(d, 'dir', 1) cur_dcap = getattr(d, 'dcap', Dragon.DCAP) if dcap != cur_dcap: dcap = cur_dcap.copy() if isinstance(d, Dragon): if d.monstervisible(): flag = 'visible' else: flag = 'hidden' else: flag = 'other' entry = SavedDragonEntry(d, flag, dir, dcap) new_entries[d] = entry dragonlist.append(entry) self.latest_entries = new_entries entry = SavedFrameEntry(tick, dragonlist) self.saved_last.saved_next = entry self.saved_last = entry return entry def taken(self, dragons): boards.replace_boardgen(self.jump_to_past()) def jump_to_past(self): self.state = 'restoring' boards.replace_boardgen(boards.next_board(fastreenter=True), 1) def restore(self): self.ghosts = {} random.seed(self.randombase1) random_module.seed(self.randombase2) def show_ghosts(self, dragonlist, interact): new_ghosts = {} for entry in dragonlist: try: ghost = self.ghosts[entry.d] except KeyError: ghost = DragonGhost(entry) ghost.setentry(entry) new_ghosts[entry.d] = ghost if (interact and entry.flag != 'other' and not entry.dcap.get('infinite_shield')): touching = images.touching(entry.x+1, entry.y+1, 30, 30) touching.reverse() for s in touching: if isinstance(s, interact): s.touched(ghost) for d, ghost in list(self.ghosts.items()): if d not in new_ghosts: ghost.kill() self.ghosts = new_ghosts def restore_frame_tick(self): from bubbles import Bubble, DragonBubble interact = (Bonus, Parabolic2, Bubble) self.save_frame() entry = self.saved_next self.saved_next = entry.saved_next self.common_tick(entry) self.show_ghosts(entry.dragonlist, interact) for args in entry.shoots1: DragonBubble(*args) if self.state == 'restoring' and self.ghosts: self.state = 'post' for ghost in list(self.ghosts.values()): ghost.integrate() def flush_ghosts(self): if self.latest_entries: for ghost in list(self.ghosts.values()): ghost.disintegrate() self.latest_entries.clear() self.dragonlist = None bigclockticker = None class MultiStones(RandomBonus): "Gems. Very demanded stones. It will take time to pick it up." nimages = [Bonuses.emerald, Bonuses.sapphire, Bonuses.ruby] Stones = [(Bonuses.emerald, 1000), (Bonuses.sapphire, 2000), (Bonuses.ruby, 3000), ] killgens = 0 big = 0 bigdoc = "Stones so big you will jump of joy picking them up." def __init__(self, x, y, mode=None): mode = mode or random.choice(MultiStones.Stones) RandomBonus.__init__(self, x, y, *mode) self.bigbonus = {'big': 1, 'outcome_args': (mode,), 'megacls': LongDurationCactusbonus} self.multi = 10 def taken1(self, dragons): if self.big: self.repulse(dragons) self.multi -= (len(dragons) or 1) if self.multi > 0: self.taken_by = [] self.untouchable() self.gen.append(self.touchdelay(5)) return -1 # don't go away def repulse(self, dragons): for d in dragons: repulse_dragon(d) def repulse_dragon(d): if hasattr(d, 'become_bubblingeyes'): from bubbles import Bubble ico = images.sprget(GreenAndBlue.normal_bubbles[d.bubber.pn][0]) b = Bubble(ico, d.x, d.y) d.become_bubblingeyes(b) b.pop() class Slippy(TemporaryBonus): "Greased Feet. Do you want some ice skating?" nimage = Bonuses.orange_thing points = 900 capname = 'slippy' captime = 606 bigbonus = {'multiply': 3} bigdoc = "Zip zip zip bouncing off walls!" class Aubergine(TemporaryBonus): "Mirror. The left hand is the one with the thumb on the right, right?" nimage = Bonuses.aubergine big = 0 bigbonus = {'big': 1, 'multiply': 2} bigdoc = "Super Bonus-catching teleport ability." def taken(self, dragon): if self.big: dragon.dcap['teleport'] = dragon.bubber.pcap['teleport'] = 1 else: dragon.dcap['lookforward'] = -dragon.dcap['lookforward'] self.carried(dragon) def endaction(self, dragon): if self.big: pass else: dragon.dcap['lookforward'] = -dragon.dcap['lookforward'] class WhiteCarrot(TemporaryBonus): "Fly. Become a great flying dragon!" nimage = Bonuses.white_carrot points = 650 capname = 'fly' captime = 650 bigbonus = {'capname': 'jumpdown', 'captime': 999999} bigdoc = "Jump down off the ground!" def taken(self, dragon): TemporaryBonus.taken(self, dragon) dragon.bubber.pcap['jumpdown'] = dragon.dcap['jumpdown'] class AmphetamineSpeed(TemporaryBonus): "Amphetamine Dose. Increase of your general speed!" nimage = Bonuses.tin points = 700 bigbonus = {'multiply': 3} bigdoc = "Let's move!" def taken(self, dragon): dragon.angry = dragon.angry + [dragon.genangry()] dragon.carrybonus(self, 633) def endaction(self, dragon): dragon.angry = dragon.angry[1:] class Sugar1(Bonus): nimage = Bonuses.yellow_sugar timeout = 2600 points = 250 def taken(self, dragon): #if boards.curboard.wastingplay is None: dragon.carrybonus(self, 99999) #else: # from player import scoreboard # dragon.bubber.bonbons += 1 # scoreboard() class Sugar2(Sugar1): timeout = 2500 points = 500 nimage = Bonuses.blue_sugar class Pear(RandomBonus): "Pear. Will explode into sugars for your pockets but watch out or you'll lose them!" points = 1000 nimage = Bonuses.green_thing bigbonus = {'multiply': 4} bigdoc = "The more the better." def taken1(self, dragons): starexplosion(self.x, self.y, 3 * self.multiply, outcomes = [random.choice([(Sugar1,), (Sugar2,)]) for i in range(18 * self.multiply)]) class Megalightning(ActiveSprite): def __init__(self, dragon): ActiveSprite.__init__(self, images.sprget(BigImages.blitz), gamesrv.game.width, gamesrv.game.height) self.gen.append(self.killing(dragon)) def killing(self, dragon): from monsters import Monster from bubbles import Bubble poplist = [dragon] while 1: for s in self.touching(10): if isinstance(s, Monster): s.argh(poplist) elif isinstance(s, Bubble): s.pop(poplist) yield None yield None def moving_to(self, x1, y1): x0 = self.x y0 = self.y x1 += CELL - self.ico.w//2 y1 += CELL - self.ico.h//2 deltax = x1 - x0 if deltax > -100: deltax = -100 deltay = y1 - y0 a = - deltay / float(deltax*deltax) b = 2 * deltay / float(deltax) for x in range(self.x, -self.ico.w, -13): x1 = x - x0 self.move(x, y0 + int((a*x1+b)*x1)) yield None self.kill() class Fish2(RandomBonus): "Rotten Fish. Will blast monsters up to here, so move it around!" points = 3000 nimage = Bonuses.fish2 big = 0 bigbonus = {'big': 1} bigdoc = "Gives seven blasts." def taken1(self, dragons): dragon = random.choice(dragons or [None]) if not self.big: m = Megalightning(dragon) m.gen.append(m.moving_to(self.x, self.y)) else: N = 7 base = random.random() * 2*math.pi angles = [base + (math.pi*2 * n)/N for n in range(N)] random.shuffle(angles) for angle in angles: m = Megalightning(dragon) dx = 13 * math.cos(angle) dy = 12 * math.sin(angle) maxlive = max((gamesrv.game.width + m.ico.w) // 13, (gamesrv.game.height + m.ico.h) // 12) m.move(self.x + (self.ico.w - m.ico.w) // 2 - int(dx*maxlive), self.y + (self.ico.h - m.ico.h) // 2 - int(dy*maxlive)) m.gen.append(m.straightline(dx, dy)) m.gen.append(m.die([None], maxlive*2)) class Sheep(RandomBonus): "Sheep. What a stupid beast!" nimage = 'sheep-sm' points = 800 big = 0 bigbonus = {'big': 1} bigdoc = "You're a sheep. Let's bounce around." def __init__(self, x, y): RandomBonus.__init__(self, x, y) if boards.curboard.bonuslevel: self.kill() def taken1(self, dragons): if not self.big: self.points0 = {} for p in BubPlayer.PlayerList: self.points0[p] = p.points BubPlayer.LeaveBonus = self.boardleave() else: from player import Dragon BubPlayer.SuperSheep = True for p in BubPlayer.PlayerList: for d in p.dragons: if isinstance(d, Dragon): d.become_monster('Sheep') def boardleave(self): from player import BubPlayer BubPlayer.OverridePlayerIcon = images.sprget(self.nimage) gamesrv.set_musics([], []) images.Snd.Yippee.play() slist = [] ico = images.sprget('sheep-big') for p in BubPlayer.PlayerList: if p.isplaying() and p.dragons: d = random.choice(p.dragons) dx = (d.ico.w - ico.w) // 2 dy = (d.ico.h - ico.h) // 2 s = ActiveSprite(ico, d.x + dx, d.y + dy) dir = getattr(d, 'dir', None) if dir not in [-1, 1]: dir = random.choice([-1, 1]) s.gen.append(s.parabolic([dir, -2.0])) slist.append(s) for d in p.dragons[:]: d.kill() delta = {} for p in BubPlayer.PlayerList: if p.points or p.isplaying(): delta[p] = 2 * (self.points0[p] - p.points) vy = 0 while delta or slist: ndelta = {} for p, dp in list(delta.items()): if dp: d1 = max(-250, min(250, dp)) p.givepoints(d1) if p.points > 0: ndelta[p] = dp - d1 delta = ndelta images.action(slist) slist = [s for s in slist if s.y < boards.bheight] yield 1 class Flower(RandomBonus): "Flower. Fire in all directions." nimage = 'flower' points = 800 big = 0 bigbonus = {'big': 1, 'multiply': 5} bigdoc = "Rotational Bubble Thrower (tm)." def taken(self, dragon): if self.big: dragon.dcap['bigflower'] = -99 dragon.dcap['autofire'] = 22 else: dragon.dcap['flower'] += 12 dragon.carrybonus(self) class Flower2(TemporaryBonus): "Bottom-up Flower. Turn you upside-down." nimage = 'flower2' points = 1000 big = 0 bigbonus = {'big': 1} bigdoc = "Turn the level upside-down." def __init__(self, *args): RandomBonus.__init__(self, *args) if self.x != OFFSCREEN: while not underground(self.x, self.y): self.step(0, -CELL) if self.y < 0: self.kill() return def taken1(self, dragons): if self.big: boards.extra_boardgen(boards.extra_swap_up_down()) else: RandomBonus.taken1(self, dragons) def faller(self): while self.y >= 0: if underground(self.x, self.y): yield None yield None else: self.move(self.x, (self.y-1) & ~3) yield None self.kill() def taken(self, dragon): dragon.dcap['gravity'] *= -1.0 self.carried(dragon) def endaction(self, dragon): dragon.dcap['gravity'] *= -1.0 def is_on_ground(self): return underground(self.x, self.y) ##class Moebius(RandomBonus): ## "Moebius Band. Bottom left is top right and bottom right is top left... or vice-versa." ## nimage = 'moebius' ## points = 900 ## def taken1(self, dragons): ## BubPlayer.Moebius = not BubPlayer.Moebius class StarBubble(FireBubble): "Star Bubbles. Makes you fire bonus bubbles." nimage = 'moebius' bubkind = 'StarBubble' bubcount = 3 bigbonus = {'bubcount': 10} bigdoc = "More bonus bubbles => more confusion." class Donut(RandomBonus): "Donut. Catch every free monster in a bubble." nimage = Bonuses.donut points = 950 big = 0 bigbonus = {'big': 1} bigdoc = "Catch dragons too." def taken1(self, dragons): extra_boardgen(boards.extra_catch_all_monsters(dragons, self.big)) if self.big: # catch all dragons as well from bubbles import NormalBubble for dragon in BubPlayer.DragonList[:]: b = NormalBubble(dragon, dragon.x, dragon.y, 542) if not dragon.become_bubblingeyes(b): b.kill() Classes = [c for c in list(globals().values()) if type(c)==type(RandomBonus) and issubclass(c, RandomBonus)] Classes.remove(RandomBonus) Classes.remove(TemporaryBonus) Cheat = [] #Classes = [Cactus, Insect] # CHEAT AllOutcomes = ([(c,) for c in Classes if c is not Fruits] + 2 * [(MonsterBonus, lvl) for lvl in range(len(Bonuses.monster_bonuses))]) for c in Classes: assert (getattr(c, 'points', 0) or 100) in GreenAndBlue.points[0], c def getdragonlist(): if bigclockticker and bigclockticker.dragonlist is not None: return [entry for entry in bigclockticker.dragonlist if entry.flag != 'other'] else: return BubPlayer.DragonList def getvisibledragonlist(): if bigclockticker and bigclockticker.dragonlist is not None: return [entry for entry in bigclockticker.dragonlist if entry.flag == 'visible'] else: return [d for d in BubPlayer.DragonList if d.monstervisible()] def record_shot(args): if bigclockticker: entry = bigclockticker.saved_last if hasattr(entry, 'shoots1'): entry.shoots1.append(args) def chooseground(tries=15): avoidlist = getdragonlist() for i in range(tries): x0 = random.randint(2, boards.width-4) y0 = random.randint(1, boards.height-3) if (' ' == bget(x0,y0+1) == bget(x0+1,y0+1) and '#' == bget(x0,y0+2) == bget(x0+1,y0+2)): x0 *= CELL y0 *= CELL for dragon in avoidlist: if abs(dragon.x-x0) < 3*CELL and abs(dragon.y-y0) < 3*CELL: break else: return x0, y0 else: return None, None def newbonus(): others = [s for s in images.ActiveSprites if isinstance(s, RandomBonus)] if others: return if BubPlayer.SuperSheep: return x, y = chooseground() if x is None: return cls = random.choice(Classes) cls(x, y) ##def newbonus(): ## others = [s for s in images.ActiveSprites if isinstance(s, RandomBonus)] ## if others: ## return ## for cls in Classes: ## x, y = chooseground(200) ## if x is not None: ## cls(x, y) def cheatnew(): if Cheat: x, y = chooseground() if x is None: return cls = random.choice(Cheat) if not isinstance(cls, tuple): cls = cls, else: Cheat.remove(cls) if len(cls) > 1: class C(cls[0]): extra_cheat_arg = cls[1] cls = (C,) cls[0](x, y) def bonus_frame_tick(): if random.random() < 0.04: cheatnew() if random.random() < 0.15: newbonus() else: import bubbles bubbles.newbubble() def start_normal_play(): global bigclockticker if bigclockticker and bigclockticker.state == 'restoring': bigclockticker.restore() return bigclockticker.restore_frame_tick if (Clock and not boards.curboard.bonuslevel and random.choice(Classes) is Clock): bigclockticker = BigClockTicker() return bigclockticker.save_frame_tick else: bigclockticker = None return bonus_frame_tick def end_normal_play(): if bigclockticker and bigclockticker.state == 'post': bigclockticker.flush_ghosts() # hack hack hack! def __cheat(c): c = c.split(',') c[0] = globals()[c[0]] assert issubclass(c[0], Bonus) Cheat.append(tuple(c)) import builtins builtins.__cheat = __cheat