changeset 20:8e8ec6a3b6e4

Make beasts aggressive
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Sat, 16 Mar 2013 00:14:14 +0100
parents 47be6695b781
children 5edb554f732b
files fhrl/__main__.py fhrl/digits.png fhrl/display.py fhrl/items.png fhrl/level.py fhrl/mobs.py fhrl/mobs2.png fhrl/resources.py fhrl/rocks.png fhrl/stuff.png
diffstat 10 files changed, 192 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/fhrl/__main__.py	Fri Mar 15 15:57:03 2013 +0100
+++ b/fhrl/__main__.py	Sat Mar 16 00:14:14 2013 +0100
@@ -9,7 +9,7 @@
 
 class Game(object):
     def __init__(self):
-        self.margin = 64
+        self.margin = 128
         self.display = Display()
         self.level = Level((32, 32))
         self.display.load_level(self.level)
@@ -29,8 +29,12 @@
                     break
                 else:
                     self.display.update()
+            survivors = []
             for m in self.level.monsters:
+                if m.health > 0:
+                    survivors.append(m)
                 self.display.animations.extend(m.act(self.level))
+            self.level.monsters = survivors
             if self.level.hero.rect.right > self.display.width - self.margin:
                 self.display.scroll_by(48, 0)
             elif self.level.hero.rect.left < self.margin:
@@ -39,6 +43,7 @@
                 self.display.scroll_by(0, 32)
             elif self.level.hero.rect.top < self.margin:
                 self.display.scroll_by(0, -32)
+            self.display.update_hud(self.level.hero)
             self.display.play_animations()
 
 pygame.display.init()
Binary file fhrl/digits.png has changed
--- a/fhrl/display.py	Fri Mar 15 15:57:03 2013 +0100
+++ b/fhrl/display.py	Sat Mar 16 00:14:14 2013 +0100
@@ -2,6 +2,7 @@
 
 import pygame
 from fhrl.resources import Resources
+from fhrl.mobs import Sprite, Number
 
 
 class SortedUpdates(pygame.sprite.RenderUpdates):
@@ -33,6 +34,7 @@
             self.sprites.add(i.init_sprite(self.resources))
         for s in level.stuff:
             self.sprites.add(s.init_sprite(self.resources))
+        self.show_hud(level.hero)
 
     def _scroll_anim(self, dx, dy):
         frame = 0
@@ -70,8 +72,10 @@
         screen.blit(self.background, rect, source)
 
     def update(self):
+        self.hud.clear(self.screen, self._sprite_clear)
         self.sprites.clear(self.screen, self._sprite_clear)
         dirty = self.sprites.draw(self.screen)
+        dirty += self.hud.draw(self.screen)
         pygame.display.update(dirty)
         self.clock.tick(24)
         for event in pygame.event.get():
@@ -84,6 +88,7 @@
     def update_all(self):
         self.screen.blit(self.background, (self.x, self.y))
         self.sprites.draw(self.screen)
+        self.hud.draw(self.screen)
         pygame.display.flip()
 
     def play_animations(self):
@@ -100,6 +105,42 @@
             if self.animations:
                 self.update()
 
+    def show_hud(self, hero):
+        self.hud = SortedUpdates()
+        self.health_icon = Sprite()
+        self.health_icon.set_image(self.resources.items[10])
+        self.health_icon.rect.topright = (48, 8)
+        self.health_number = Number(hero.health, self.resources)
+        self.health_number.rect.topleft = (48, 16)
+        self.hud.add(self.health_icon)
+        self.hud.add(self.health_number)
+        self.balls_icon = Sprite()
+        self.balls_icon.set_image(self.resources.items[8])
+        self.balls_icon.rect.topright = (144, 8)
+        self.balls_number = Number(hero.balls, self.resources)
+        self.balls_number.rect.topleft = (144, 16)
+        self.hud.add(self.balls_icon)
+        self.hud.add(self.balls_number)
+        self.gasoline_icon = Sprite()
+        self.gasoline_icon.set_image(self.resources.items[9])
+        self.gasoline_icon.rect.topright = (240, 8)
+        self.gasoline_number = Number(hero.gasoline, self.resources)
+        self.gasoline_number.rect.topleft = (240, 16)
+        self.hud.add(self.gasoline_icon)
+        self.hud.add(self.gasoline_number)
+        self.battery_icon = Sprite()
+        self.battery_icon.set_image(self.resources.items[11])
+        self.battery_icon.rect.topright = (336, 8)
+        self.battery_number = Number(hero.battery, self.resources)
+        self.battery_number.rect.topleft = (336, 16)
+        self.hud.add(self.battery_icon)
+        self.hud.add(self.battery_number)
+
+
+    def update_hud(self, hero):
+        self.health_number.set_number(hero.health)
+
+
     def render_level(self, surf, level):
         for y, row in enumerate(level.grid):
             for x, f in enumerate(row):
Binary file fhrl/items.png has changed
--- a/fhrl/level.py	Fri Mar 15 15:57:03 2013 +0100
+++ b/fhrl/level.py	Sat Mar 16 00:14:14 2013 +0100
@@ -84,8 +84,9 @@
                     kind = random.randrange(6, 8)
                     self.stuff.append(Stuff(kind, (x, y)))
                 else:
-                    kind = random.randrange(4)
+                    kind = random.choice([0, 1, 2, 3, 8, 9, 10])
                     self.stuff.append(Stuff(kind, (x, y)))
+                    self.grid[y][x] = 4
         x, y = self.free.pop()
         self.grid[y][x] = 5
 
--- a/fhrl/mobs.py	Fri Mar 15 15:57:03 2013 +0100
+++ b/fhrl/mobs.py	Sat Mar 16 00:14:14 2013 +0100
@@ -2,6 +2,7 @@
 
 import pygame
 import random
+import traceback
 
 
 DX = [0, 1, 0, -1, -1, 1, 1, -1]
@@ -9,6 +10,10 @@
 XYD = {(x, y):i for i, (x, y) in enumerate(zip(DX, DY))}
 
 
+def dist((x1, y1), (x2, y2)):
+    """Calculate the distance."""
+    return abs(x1 - x2) ** 2 + abs(y1 - y2) ** 2
+
 
 class ActException(Exception):
     """Base class for all acting-related exceptions."""
@@ -49,6 +54,30 @@
         self.rect.move_ip(pos)
 
 
+class Number(pygame.sprite.DirtySprite):
+    def __init__(self, number, resources):
+        super(Number, self).__init__()
+        self.rect = pygame.Rect(0, 0, 1, 1)
+        self.digits = resources.digits
+        self.set_number(number)
+
+    def set_number(self, number):
+        topleft = self.rect.topleft
+        digits = '%d' % abs(number)
+        self.image = pygame.Surface((12 * len(digits), 15), pygame.SRCALPHA)
+        for x, digit in enumerate(digits):
+            self.image.blit(self.digits[int(digit)], (x*12, 0))
+        self.rect = self.image.get_rect()
+        self.rect.topleft = topleft
+
+    def _float_anim(self, start):
+        self.rect.center = start
+        for i in xrange(8):
+            self.rect.move_ip(0, i - 8)
+            yield
+        self.kill()
+
+
 class Item(Sprite):
     def __init__(self, kind, (x, y)):
         super(Item, self).__init__()
@@ -67,6 +96,8 @@
         self.kill()
 
 
+
+
 class Stuff(Sprite):
     def __init__(self, kind, (x, y)):
         super(Stuff, self).__init__()
@@ -91,16 +122,50 @@
         self.rect.topleft = x * 48 - 24, y * 32 - 33
         self.x, self.y = x, y
         self.kind = kind
+        self.health = kind.get('health', 1)
+        self.power = kind.get('power', 0)
+        self.ai = kind.get('ai', None)
 
     def init_sprite(self, resources):
         self.set_image(resources.mobs[self.kind['sprite']])
         return self
 
+    def wound(self, power):
+        self.health -= power
+
+    def _die_anim(self):
+        dy = -4
+        for frame in xrange(8):
+            self.rect.move_ip(0, dy)
+            dy += 2
+            yield
+        self.kill()
+
     def _walk_anim(self, d):
         for frame in xrange(8):
             self.rect.move_ip(DX[d] * 6, DY[d] * 4)
             yield
 
+    def _attack_anim(self, d):
+        for frame in xrange(4):
+            self.rect.move_ip(DX[d] * 6, DY[d] * 4)
+            yield
+        for frame in xrange(4):
+            self.rect.move_ip(-DX[d] * 6, -DY[d] * 4)
+            yield
+
+    def attack(self, d, mob, display=None):
+        anims = []
+        if (self.x + DX[d], self.y + DY[d]) != (mob.x, mob.y):
+            raise ActException()
+        mob.wound(self.power)
+        if display:
+            n = Number(self.power, display.resources)
+            display.hud.add(n)
+            anims.append(n._float_anim(mob.rect.midtop))
+        anims.append(self._attack_anim(d))
+        return anims
+
     def walk(self, d, level):
         x = self.x + DX[d]
         y = self.y + DY[d]
@@ -113,20 +178,32 @@
                 raise ActBumpStuff((x, y), s)
             raise ActBlocked((x, y))
         self.x, self.y = x, y
-        return self._walk_anim(d)
+        return [self._walk_anim(d)]
 
     def act(self, level):
+        if self.health <= 0:
+            return [self._die_anim()]
+        if self.ai:
+            try:
+                return self.ai(self, level)
+            except ActException:
+                traceback.print_exc()
         try:
-            return [self.walk(random.randrange(4), level)]
+            return self.walk(random.randrange(4), level)
         except ActException:
             return []
 
 
 class Hero(Mob):
     def __init__(self, (x, y)):
-        super(Hero, self).__init__(0, (x, y))
+        super(Hero, self).__init__({}, (x, y))
         self.items = []
         self.arrow = Sprite()
+        self.balls = 10
+        self.gasoline = 10
+        self.battery = 10
+        self.health = 10
+        self.power = 1
 
     def init_sprite(self, resources):
         self.set_image(resources.mobs[0])
@@ -164,12 +241,14 @@
             display.sprites.add(self.arrow)
             return anims
         try:
-            anims.append(self.walk(direction, level))
+            anims.extend(self.walk(direction, level))
             item = level.find_item((self.x, self.y))
             if item:
                 anims.append(item._pick_anim(display))
                 self.items.append(item)
                 level.items.remove(item)
+        except ActBumpMob as e:
+            anims.extend(self.attack(direction, e.mob, display))
         except ActBumpStuff as e:
             if e.stuff.kind in (4, 6):
                 e.stuff.kind += 1
@@ -177,10 +256,35 @@
                 e.stuff.kind = 8
             anims.append(e.stuff._morph_anim(display))
         except ActException:
-            pass
+            return []
         return anims
 
 
+def ai_follow(mob, level):
+    possible = [
+        d for d in xrange(8)
+        if not (
+            level.is_blocked((mob.x + DX[d], mob.y + DY[d])) or
+            level.find_mob((mob.x + DX[d], mob.y + DY[d])) not in (None, level.hero)
+        )
+    ]
+    if not possible:
+        return []
+    random.shuffle(possible)
+    d = min(
+        possible,
+        key=lambda d: dist(
+            (level.hero.x, level.hero.y),
+            (mob.x + DX[d], mob.y + DY[d])
+        )
+    )
+    try:
+        return mob.walk(d, level)
+    except ActBumpMob as e:
+        if e.mob == level.hero:
+            return mob.attack(d, e.mob)
+        return []
+
 
 MONSTERS = {
     'guard': {
@@ -188,38 +292,47 @@
         'sprite': 1,
         'level': 1,
         'devolve': 'worker',
+        'health': 1,
     },
     'beast': {
         'name': "Horrible Beast",
         'sprite': 2,
         'level': 4,
         'devolve': 'fruit',
+        'health': 4,
+        'ai': ai_follow,
+        'power': 2,
     },
     'robot': {
         'name': "Killer Robot",
         'sprite': 3,
         'level': 3,
         'devolve': 'scrap',
+        'health': 5,
     },
     'professor': {
         'name': "Professor Vanilla",
         'sprite': 4,
         'level': 0,
+        'health': 10,
     },
     'worker': {
         'name': "Construction Worker",
         'sprite': 5,
         'level': 2,
+        'health': 3,
     },
     'fruit': {
         'name': "Spoiled Fruit",
         'sprite': 6,
         'level': 0,
+        'health': 1,
     },
     'scrap': {
         'name': "Ancient Robot",
         'sprite': 7,
         'level': 0,
+        'health': 1,
     },
 }
 
@@ -227,34 +340,58 @@
     'drill': {
         'name': "Drill",
         'sprite': 0,
+        'ammo': 'gasoline',
     },
     'beam': {
         'name': "Beam Emitter",
         'sprite': 1,
+        'ammo': 'battery',
     },
     'cannon': {
         'name': "Pirate Cannon",
         'sprite': 2,
+        'ammo': 'balls',
     },
     'teleporter': {
         'name': "Teleport Device",
         'sprite': 3,
+        'ammo': 'battery',
     },
     'flamer': {
         'name': "Flame Thrower",
         'sprite': 4,
+        'ammo': 'gasoline',
     },
     'vulcan': {
         'name': "Machine Gun",
         'sprite': 5,
+        'ammo': 'balls',
     },
     'deevo': {
         'name': "Deevolution Inducer",
         'sprite': 6,
+        'ammo': 'battery',
     },
     'toaster': {
         'name': "Doomsday Device",
         'sprite': 7,
+        'ammo': 'battery',
+    },
+    'balls': {
+        'name': "Cannon Balls",
+        'sprite': 8,
+    },
+    'gasoline': {
+        'name': "Gasoline",
+        'sprite': 9,
+    },
+    'medkit': {
+        'name': "First Aid Kit",
+        'sprite': 10,
+    },
+    'battery': {
+        'name': "Battery",
+        'sprite': 11,
     },
 }
 
Binary file fhrl/mobs2.png has changed
--- a/fhrl/resources.py	Fri Mar 15 15:57:03 2013 +0100
+++ b/fhrl/resources.py	Sat Mar 16 00:14:14 2013 +0100
@@ -13,6 +13,7 @@
         self.stuff = self.load_atlas('stuff.png', 48, 48)
         self.arrows = self.load_atlas('arrows.png', 48, 48)
         self.items = self.load_atlas('items.png', 48, 32)
+        self.digits = self.load_atlas('digits.png', 12, 15)
 
     @staticmethod
     def load_image(filename):
Binary file fhrl/rocks.png has changed
Binary file fhrl/stuff.png has changed