summaryrefslogtreecommitdiff
path: root/bubbob/bonuses.py
diff options
context:
space:
mode:
Diffstat (limited to 'bubbob/bonuses.py')
-rw-r--r--bubbob/bonuses.py2504
1 files changed, 2504 insertions, 0 deletions
diff --git a/bubbob/bonuses.py b/bubbob/bonuses.py
new file mode 100644
index 0000000..6873188
--- /dev/null
+++ b/bubbob/bonuses.py
@@ -0,0 +1,2504 @@
+from __future__ import generators
+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)<self.ico.w*2//3 and
+ abs(self.y-s.y)<self.ico.h):
+ wait += 5
+ for t in range(wait):
+ yield None
+ for i in range(25):
+ if i == 7:
+ self.nooverlap = 0
+ self.step(0, -2)
+ yield None
+ if self.y <= 0:
+ break
+ for i in range(20):
+ yield None
+ self.kill()
+
+
+class Parabolic(ActiveSprite):
+ fallstraight = 0
+ fallspeed = 4
+
+ def moving(self, y_amplitude = -8.0):
+ bottom_up = self.fallspeed < 0
+ dxy = [(random.random()-0.5) * 15.0,
+ (random.random()+0.5) * y_amplitude * (1,-1)[bottom_up]]
+ if bottom_up:
+ kw = {'gravity': -0.3}
+ else:
+ kw = {}
+ for n in self.parabolic(dxy, self.fallstraight, **kw):
+ progress = self.parabole_progress = dxy[1] * (1,-1)[bottom_up]
+ yield n
+ if progress >= 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 self.bubbles.values():
+ bubble.pop()
+ Bonus.kill(self)
+
+ def taken(self, dragon):
+ poplist = [dragon]
+ for bubble in 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 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 y<dy]
+ if lst: north[dy] = max(lst)
+ lst = [y for x, y in self.bubbles_pos if x==dx and y>dy]
+ if lst: south[dy] = min(lst)
+ lst = [x for x, y in self.bubbles_pos if x<dx and y==dy]
+ if lst: west[dx] = max(lst)
+ lst = [x for x, y in self.bubbles_pos if x>dx 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 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 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
+ Extensions = [s for s in os.listdir(LocalDir)
+ if s.startswith('ext') and
+ os.path.isdir(os.path.join(LocalDir, 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 = 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 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,
+ BubPlayer.__dict__.items(),
+ gamesrv.sprites,
+ gamesrv.sprites_by_n,
+ ps,
+ 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()) * 914971L
+ self.randombase2 = hash(localrandom.random()) * 914971L
+ 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 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 self.ghosts.values():
+ ghost.integrate()
+
+ def flush_ghosts(self):
+ if self.latest_entries:
+ for ghost in 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 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 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 __builtin__
+__builtin__.__cheat = __cheat