#! /usr/bin/env python ## Copyright (C) 2001 David Clark ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 2 ## of the License, or (at your option) any later version. ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import pygame, pygame.image, pygame.mixer, pygame.font import sys, math, os.path, string, whrandom, getopt from pygame.locals import * import pygame.draw # We use the following sound and image container classes to ensure that # we don't fill memory with identical surfaces for each on-screen object. class Img: pass class Snd: pass class Player: # Our hero speed = 60.000 # pixels per second # each element of the frame_list tuple is a tuple of animation frames # for that state. frame_list = ( (pygame.Rect(0, 0, 16, 16), pygame.Rect(0, 0, 16, 16)), (pygame.Rect(16, 0, 16, 16), pygame.Rect(32, 0, 16, 16)), (pygame.Rect(48, 0, 16, 16), pygame.Rect(64, 0, 16, 16)), (pygame.Rect(80, 0, 16, 16), pygame.Rect(96, 0, 16, 16)), (pygame.Rect(112, 0, 16, 16), pygame.Rect(128, 0, 16, 16)) ) def __init__(self): self.rect = pygame.Rect(0, 0, 16, 16) self.colliderect = pygame.Rect(4, 0, 8, 16) self.position = (0.000, 0.000) self.vel_x = 0.000 self.vel_y = 0.000 self.frame = 0 self.state = 0 self.damaged = 0 self.health = 100 self.score = 0 self.surface = Img.player self.weapon_switch_time = 0 self.weapon_switch_duration = 600 self.animate_time = 0 self.animate_time_duration = 100 self.current_weapon = 0 self.damage_surface = Img.damage self.pain_time = 0 self.pain_duration = 500 self.weapons = [Pistol(), Machinegun(), Lasergun(), Rocketlauncher(), Flamethrower(), Grenadethrower(), Bombdropper()] def think(self): if self.animate_time <= timer.current_time: self.animate() self.animate_time = timer.current_time + self.animate_time_duration return def draw(self): self.rect.topleft = self.position self.colliderect.left = self.rect.left + 4 self.colliderect.top = self.rect.top + 0 if self.damaged > 0: display.blit(self.damage_surface, self.rect.topleft) display.blit(self.surface, self.rect.topleft, Player.frame_list[self.state][self.frame]) dirtyrects.append(self.rect) self.damaged -= 1 return def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) return def move(self, dest_xy): # Move player to the supplied coordinates. Does not check for collisions - you # could put a player in a wall or outside the map with this. self.position = dest_xy self.rect.topleft = dest_xy self.colliderect.left = self.rect.left + 4 self.colliderect.top = self.rect.top + 0 return def walk(self): # Move player to the supplied coordinates. Only allows movement if the destination # is clear. new_x = self.position[0] + self.vel_x new_y = self.position[1] + self.vel_y tmp_rect = pygame.Rect(new_x, new_y, 16, 16) tmp_colliderect = pygame.Rect(new_x + 4, new_y + 0, 8, 16) for monster in g_monsters: if tmp_colliderect.colliderect(monster.colliderect): return (0.0, 0.0) if tmp_colliderect.collidelist(level.impassable_rects) == -1: self.position = (new_x, new_y) self.rect.topleft = self.position self.colliderect.topleft = tmp_colliderect.topleft else: return (0.0, 0.0) for item in g_items: if tmp_colliderect.colliderect(item.rect): item.pickup() return (self.vel_x, self.vel_y) def animate(self): if self.frame == len(self.frame_list[self.state]) - 1: self.frame = 0 else: self.frame += 1 return def choose_next_weapon(self): if self.weapon_switch_time <= timer.current_time: # ok to switch while 1: self.current_weapon += 1 if self.current_weapon == 7: self.current_weapon = 0 if self.weapons[self.current_weapon].ammo > 0: break statusbar.draw(1, 1, 0) self.weapon_switch_time = timer.current_time + self.weapon_switch_duration if sound: play_sound(Snd.weapon_switch, 0.5) return def choose_prev_weapon(self): if self.weapon_switch_time <= timer.current_time: # ok to switch while 1: self.current_weapon -= 1 if self.current_weapon == -1: self.current_weapon = 6 if self.weapons[self.current_weapon].ammo > 0: break statusbar.draw(1, 1, 0) self.weapon_switch_time = timer.current_time + self.weapon_switch_duration if sound: play_sound(Snd.weapon_switch, 0.5) return def take_damage(self, damage): if not GOD: # This is the best line of code I've ever written :) self.health -= damage statusbar.draw(0, 0, 1) self.damaged = 15 if self.pain_time <= timer.current_time: if sound: play_sound(whrandom.choice(Snd.player_pain), 1.0) self.pain_time = timer.current_time + self.pain_duration return def animate_death(self): # we take over the game loop, and draw the player's death. Then we end. self.surface = Img.player_death if sound: play_sound(Snd.player_death, 1.0) #background.blit(display, (0,0)) for src_offset in (0, 16, 32, 48, 64, 80, 96, 112): self.erase() display.blit(self.surface, self.rect.topleft, (src_offset, 0, 16, 16)) pygame.display.update(self.rect) pygame.time.delay(500) # Display Game over and wait 2.5 seconds font = pygame.font.Font(os.path.join('images', 'younffp_.ttf'), 24) game_over_message = font.render('Game Over!', 1, (255, 255, 255)) display.fill((0, 0, 216, 255), (256, 220, 128, 40)) display.fill((0, 0, 128, 255), (260, 224, 120, 32)) display.blit(game_over_message, (264, 228)) pygame.display.flip() pygame.time.delay(2500) return class Crosshair: # I played with 5 different crosshair movement techniques, and included # the best two in the game. The crosshair becomes unusably sluggish under # high load, since it's not normalized for frame rate. This should be fixed. acceldrag = 0.98 mousedrag = 0.02 def __init__(self, pos_x, pos_y): self.pos_x = float(pos_x) self.pos_y = float(pos_y) self.move_x = self.move_y = 0.0 self.rect = pygame.Rect(pos_x, pos_y, 16, 16) self.surface = Img.crosshair def think(self): pass def draw(self): display.blit(self.surface, self.rect.topleft) dirtyrects.append(pygame.Rect(self.rect)) def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) def move(self, mouse_move_x, mouse_move_y, player_movement): self.move_x += mouse_move_x * Crosshair.mousedrag self.move_y += mouse_move_y * Crosshair.mousedrag self.pos_x += self.move_x self.pos_y += self.move_y self.move_x *= Crosshair.acceldrag self.move_y *= Crosshair.acceldrag if ALT_MOUSE: self.pos_x += player_movement[0] self.pos_y += player_movement[1] if self.pos_x < 0.0: self.pos_x = 0.0 elif self.pos_x > 624.0: self.pos_x = 624.0 if self.pos_y < 0.0: self.pos_y = 0.0 elif self.pos_y > 448.0: self.pos_y = 448.0 self.rect.topleft = (self.pos_x, self.pos_y) class Statusbar: def __init__(self): self.weapon_rect = pygame.Rect(0, 465, 112, 16) self.health_rect = pygame.Rect(490, 465, 150, 16) self.ammo_rect = pygame.Rect(300, 465, 150, 16) def draw(self, weapons_need_updating, ammo_needs_updating, health_needs_updating): if weapons_need_updating: for weapon_index in range(0, 7): if player.current_weapon == weapon_index: display.blit(Img.weapons_selected, (weapon_index * 16, 465), player.weapons[weapon_index].rect) else: display.blit(Img.weapons, (weapon_index * 16, 465), player.weapons[weapon_index].rect) dirtyrects.append(self.weapon_rect) if health_needs_updating: if player.health <= 1: return display.blit(Img.health_empty, self.health_rect.topleft) display.blit(Img.health_full, self.health_rect.topleft, (0,0, player.health, 16)) dirtyrects.append(self.health_rect) if ammo_needs_updating: ammo_font_numbers = ammo_font.render(str(player.weapons[player.current_weapon].ammo), 1, WHITE) display.blit(ammo_font_erase, self.ammo_rect.topleft) display.blit(ammo_font_numbers, self.ammo_rect.topleft) dirtyrects.append(self.ammo_rect) return def rampup(self): # Animate the health bar increasing from 0 to the player's current health. for temphealth in range(0, player.health): display.blit(Img.health_full, self.health_rect.topleft, (0,0, temphealth, 16)) pygame.display.update(self.health_rect) pygame.time.delay(10) return class Pistol: def __init__(self): self.reload_duration = 750 self.reload_time = 0 self.rect = pygame.Rect(0, 0, 16, 16) self.ammo = 999 # unlimited def fire(self, position_xy, angle): if self.reload_time <= timer.current_time: if self.ammo > 0: # not needed since we always have ammo. for i in range(0, 3): # more of a shotgun, really. myangle = angle + whrandom.randint(-4, 4) vel_x = (math.cos(math.pi * myangle / 180)) * Bullet.speed vel_y = (math.sin(math.pi * myangle / 180)) * Bullet.speed bullets.append(Bullet(position_xy[0], position_xy[1], vel_x, vel_y, 1)) if sound: play_sound(Snd.shotgun, 0.25) self.reload_time = timer.current_time + self.reload_duration class Machinegun: def __init__(self): self.reload_duration = 100 self.reload_time = 0 self.rect = pygame.Rect(16, 0, 16, 16) self.ammo = 50 if DEBUG: self.ammo = 200 def fire(self, position_xy, angle): if self.reload_time <= timer.current_time: if self.ammo > 0: self.ammo -= 1 vel_x = (math.cos(math.pi * angle / 180)) * Bullet.speed vel_y = (math.sin(math.pi * angle / 180)) * Bullet.speed bullets.append(Bullet(int(position_xy[0]), int(position_xy[1]), vel_x, vel_y, 1)) self.reload_time = timer.current_time + self.reload_duration if sound: play_sound(Snd.machinegun, 0.25) statusbar.draw(0, 1, 0) class Lasergun: def __init__(self): self.reload_duration = 1000 self.reload_time = 0 self.rect = pygame.Rect(32, 0, 16, 16) self.ammo = 5 if DEBUG: self.ammo = 25 def fire(self, position_xy, angle): if self.reload_time <= timer.current_time: if self.ammo > 0: self.ammo -= 1 delta_y = (math.sin(math.pi * angle / 180)) delta_x = (math.cos(math.pi * angle / 180)) laserbeams.append(Laserbeam(position_xy[0], position_xy[1], delta_x, delta_y, 1)) self.reload_time = timer.current_time + self.reload_duration if sound: play_sound(Snd.laser, 1.0) statusbar.draw(0, 1, 0) class Rocketlauncher: def __init__(self): self.reload_duration = 2000 self.reload_time = 0 self.rect = pygame.Rect(48, 0, 16, 16) self.ammo = 5 if DEBUG: self.ammo = 25 def fire(self, position_xy, angle): if self.reload_time <= timer.current_time: if self.ammo > 0: self.ammo -= 1 vel_x = (math.cos(math.pi * angle / 180)) * Rocket.speed vel_y = (math.sin(math.pi * angle / 180)) * Rocket.speed g_rockets.append(Rocket(position_xy[0], position_xy[1], vel_x, vel_y, angle, 1)) self.reload_time = timer.current_time + self.reload_duration if sound: play_sound(Snd.rocket, 0.25) statusbar.draw(0, 1, 0) class Flamethrower: def __init__(self): self.reload_time = 0 self.reload_duration = 100 self.rect = pygame.Rect(64, 0, 16, 16) self.ammo = 25 if DEBUG: self.ammo = 100 def fire(self, position_xy, angle): if self.reload_time <= timer.current_time: if self.ammo > 0: self.ammo -= 1 vel_x = (math.cos(math.pi * angle / 180)) * Flame.speed vel_y = (math.sin(math.pi * angle / 180)) * Flame.speed g_flames.append(Flame(position_xy[0], position_xy[1], vel_x, vel_y)) self.reload_time = timer.current_time + self.reload_duration if sound: play_sound(Snd.flame, 0.25) statusbar.draw(0, 1, 0) class Grenadethrower: def __init__(self): self.reload_time = 0 self.reload_duration = 750 self.rect = pygame.Rect(80, 0, 16, 16) self.ammo = 10 if DEBUG: self.ammo = 25 def fire(self, origin_xy, target_xy): if self.reload_time <= timer.current_time: if self.ammo > 0: self.ammo -= 1 g_grenades.append(Grenade(origin_xy[0], origin_xy[1], target_xy[0], target_xy[1])) self.reload_time = timer.current_time + self.reload_duration if sound: play_sound(Snd.grenade, 0.75) statusbar.draw(0, 1, 0) class Bombdropper: def __init__(self): self.reload_time = 0 self.reload_duration = 3000 self.rect = pygame.Rect(96, 0, 16, 16) self.ammo = 1 if DEBUG: self.ammo = 1 def fire(self, position_xy, angle): if self.reload_time <= timer.current_time: if self.ammo > 0: self.ammo -= 1 self.reload_time = timer.current_time + self.reload_duration g_bombs.append(Bomb(position_xy[0], position_xy[1])) statusbar.draw(0, 1, 0) class Bullet: speed = 200.000 def __init__(self, position_x, position_y, vel_x, vel_y, friendly): self.rect = pygame.Rect(position_x, position_y, 1, 1) self.position_x = position_x self.position_y = position_y self.vel_x = vel_x self.vel_y = vel_y self.damage = 5 self.friendly = friendly self.alive = 1 def erase(self): display.fill(background.get_at(self.rect.topleft), self.rect) dirtyrects.append(pygame.Rect(self.rect)) def draw(self): display.fill(WHITE, self.rect) dirtyrects.append(self.rect) def move(self): self.position_x += (self.vel_x * timer.frame_duration) self.position_y += (self.vel_y * timer.frame_duration) self.rect.topleft = (self.position_x, self.position_y) if not self.rect.colliderect(screenrect): self.alive = 0 if self.rect.collidelist(level.impassable_rects) != -1: self.alive = 0 g_decals.append(Bullet_Hole(self.rect.topleft)) if self.friendly: for monster in g_monsters: if self.rect.colliderect(monster.colliderect): self.alive = 0 g_decals.append(Blood(self.rect.topleft)) monster.take_damage(self.damage, 0) else: if self.rect.colliderect(player.colliderect): self.alive = 0 g_decals.append(Blood(self.rect.topleft)) player.take_damage(self.damage / 4) return def think(self): pass # bullets don't think class Laserbeam: def __init__(self, position_x, position_y, delta_x, delta_y, friendly): self.pixels = [] self.damage = 40 self.colour_number = 15 self.fade_duration = 100 self.fade_time = 0 self.debug_time = 0 self.origin_x = position_x self.origin_y = position_y self.delta_x = delta_x self.delta_y = delta_y self.friendly = friendly self.origin_rect = pygame.Rect((position_x, position_y, 1, 1)) self.terminus_rect = pygame.Rect((position_x, position_y, 1, 1)) self.calculate_pixels() def calculate_pixels(self): if (abs(self.delta_x) > abs(self.delta_y)): # line mainly horizontal x = int(self.origin_x) y = self.origin_y while 1: self.terminus_rect.topleft = (x, y) if not screenrect.contains(self.terminus_rect): return self.pixels.append(self.terminus_rect.topleft) if self.terminus_rect.collidelist(level.impassable_rects) != -1: g_decals.append(Bullet_Hole(self.terminus_rect.topleft)) return if self.friendly: for monster in g_monsters: if self.terminus_rect.colliderect(monster.colliderect): g_decals.append(Blood(self.terminus_rect.topleft)) monster.take_damage(self.damage, 0) return else: if self.terminus_rect.colliderect(player.colliderect): g_decals.append(Blood(self.terminus_rect.topleft)) player.take_damage(self.damage) return if self.delta_x > 0: x += 1 else: x -= 1 y += self.delta_y * abs(1/ self.delta_x) else: # line mainly vertical x = self.origin_x y = int(self.origin_y) while 1: self.terminus_rect.topleft = (x, y) if not screenrect.contains(self.terminus_rect): return self.pixels.append(self.terminus_rect.topleft) if self.terminus_rect.collidelist(level.impassable_rects) != -1: g_decals.append(Bullet_Hole(self.terminus_rect.topleft)) return if self.friendly: for monster in g_monsters: if self.terminus_rect.colliderect(monster.colliderect): g_decals.append(Blood(self.terminus_rect.topleft)) monster.take_damage(self.damage, 0) return else: if self.terminus_rect.colliderect(player.colliderect): g_decals.append(Blood(self.terminus_rect.topleft)) player.take_damage(self.damage) return if self.delta_y > 0: y += 1 else: y -= 1 x += self.delta_x * abs(1 / self.delta_y) def draw(self): rect = pygame.draw.line(display, (self.colour_number * 16, 0, 0), self.origin_rect.topleft, self.terminus_rect.topleft) dirtyrects.append(rect) def erase(self): for pixel in self.pixels: display.fill(background.get_at(pixel), (pixel[0], pixel[1], 1, 1)) dirtyrects.append(self.origin_rect.union(self.terminus_rect)) def think(self): if self.fade_time > timer.current_time: return else: self.colour_number -= 1 self.fade_time = timer.current_time + self.fade_duration class Rocket: speed = 100.000 damage = 20 def __init__(self, position_x, position_y, vel_x, vel_y, angle, friendly): self.position_x = position_x - 8 self.position_y = position_y - 8 self.smoke_interval = 200 self.smoke_time = 0 self.vel_x = vel_x self.vel_y = vel_y self.damage = Rocket.damage self.surface = Img.rocket self.friendly = friendly self.rect = pygame.Rect(position_x, position_y, 16, 16) self.colliderect = pygame.Rect(self.rect.left + 4, self.rect.top + 4, 8, 8) # determine src_rect and colliderect from angle # This can be improved, since the checks are sequential. if angle < 0: angle += 360 if angle >= 337 or angle < 22: self.src_rect = pygame.Rect(0, 0, 16, 16) elif angle >= 22 and angle < 67: self.src_rect = pygame.Rect(16, 0, 16, 16) elif angle >= 67 and angle < 112: self.src_rect = pygame.Rect(32, 0, 16, 16) elif angle >= 112 and angle < 157: self.src_rect = pygame.Rect(48, 0, 16, 16) elif angle >= 157 and angle < 202: self.src_rect = pygame.Rect(64, 0, 16, 16) elif angle >= 202 and angle < 247: self.src_rect = pygame.Rect(80, 0, 16, 16) elif angle >= 247 and angle < 292: self.src_rect = pygame.Rect(96, 0, 16, 16) elif angle >= 292 and angle < 337: self.src_rect = pygame.Rect(112, 0, 16, 16) self.alive = 1 def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) def draw(self): display.blit(self.surface, self.rect.topleft, self.src_rect) dirtyrects.append(self.rect) def move(self): self.position_x += (self.vel_x * timer.frame_duration) self.position_y += (self.vel_y * timer.frame_duration) self.rect.topleft = (self.position_x, self.position_y) self.colliderect.topleft = (self.rect.left + 4, self.rect.top + 4) if not screenrect.contains(self.rect): self.alive = 0 if self.colliderect.collidelist(level.impassable_rects) != -1: self.alive = 0 g_explosions.append(Explosion(self.rect.center, self.friendly)) if self.friendly: for monster in g_monsters: if self.colliderect.colliderect(monster.colliderect): self.alive = 0 g_explosions.append(Explosion(self.rect.center, self.friendly)) monster.take_damage(self.damage, 0) if not self.friendly: if self.colliderect.colliderect(player.colliderect): self.alive = 0 g_explosions.append(Explosion(self.rect.center, self.friendly)) player.take_damage(self.damage) return def think(self): if self.smoke_time > timer.current_time: return else: #g_decals.append(Bullet_Hole(self.rect.center)) g_smokes.append(Smoke(self.rect.center)) self.smoke_time = timer.current_time + self.smoke_interval return class Flame: speed = 150.000 damage = 5 def __init__(self, position_x, position_y, vel_x, vel_y): self.position_x = position_x - 4 self.position_y = position_y - 4 self.vel_x = vel_x self.vel_y = vel_y self.damage = Flame.damage self.surface = Img.flame self.rect = pygame.Rect(position_x, position_y, 8, 8) self.colliderect = pygame.Rect(self.rect.left + 1, self.rect.top + 1, 6, 6) self.alive = 1 self.travel_duration = 750 self.die_time = timer.current_time + self.travel_duration def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) def draw(self): display.blit(self.surface, self.rect.topleft) dirtyrects.append(self.rect) def move(self): self.position_x += (self.vel_x * timer.frame_duration) self.position_y += (self.vel_y * timer.frame_duration) self.rect.topleft = (self.position_x, self.position_y) self.colliderect.topleft = (self.rect.left + 1, self.rect.top + 1) if not screenrect.contains(self.rect): self.alive = 0 if self.colliderect.collidelist(level.impassable_rects) != -1: self.alive = 0 self.check_for_fire(self.rect) for monster in g_monsters: if self.colliderect.colliderect(monster.colliderect): self.alive = 0 monster.take_damage(self.damage, 0) self.check_for_fire(self.rect) return def think(self): if self.die_time > timer.current_time: return else: self.check_for_fire(self.rect) self.alive = 0 def check_for_fire(self, rect): for fire in g_fires: if rect.colliderect(fire.rect): fire.time_to_die = timer.current_time + 3000 return g_fires.append(Fire(rect.center)) return class Grenade: speed = 200.000 def __init__(self, origin_x, origin_y, target_x, target_y): self.position_x = origin_x - 8 self.position_y = origin_y - 8 self.target_x = target_x self.target_y = target_y self.surface = Img.grenade self.src_rect = pygame.Rect((0, 0, 16, 16)) self.rect = pygame.Rect(self.position_x, self.position_y, 16, 16) self.frame_time = 0 self.frame_interval = 250 self.vel_x = (target_x - origin_x) / 2 if self.vel_x > 200: self.vel_x = 200 self.vel_y = (target_y - origin_y) / 2 if self.vel_y > 200: self.vel_y = 200 self.alive = 1 def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) def draw(self): display.blit(self.surface, self.rect.topleft, self.src_rect) dirtyrects.append(self.rect) def move(self): self.position_x += (self.vel_x * timer.frame_duration) self.position_y += (self.vel_y * timer.frame_duration) self.rect.topleft = (self.position_x, self.position_y) if not screenrect.contains(self.rect): self.alive = 0 def think(self): if self.frame_time > timer.current_time: return else: self.src_rect.left += 16 if self.src_rect.left > self.surface.get_width(): self.alive = 0 g_explosions.append(Explosion(self.rect.center, 1)) else: self.frame_time = timer.current_time + self.frame_interval return class Bomb: def __init__(self, position_x, position_y): self.position_x = position_x - 8 self.position_y = position_y - 8 self.damage = 190 self.surface = Img.bomb self.src_rect = pygame.Rect((0, 0, 16, 16)) self.rect = pygame.Rect(position_x, position_y, 16, 16) self.frame_time = 0 self.frame_interval = 750 self.alive = 1 def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) def draw(self): display.blit(self.surface, self.rect.topleft, self.src_rect) dirtyrects.append(self.rect) def think(self): if self.frame_time > timer.current_time: return else: self.src_rect.left += 16 if self.src_rect.left >= self.surface.get_width(): self.alive = 0 self.blast() else: self.frame_time = timer.current_time + self.frame_interval return def blast(self): if sound: play_sound(Snd.bombblast, 0.5) # special effects tempsurf = pygame.Surface((640, 480)) tempdisplay = pygame.Surface((640, 480)) tempdisplay.blit(display, (0, 0)) tempdisplay.blit(background, self.rect.topleft, self.rect) tempsurf.fill((255, 255, 255)) for myalpha in range(254, 0, -16): tempsurf.set_alpha(myalpha) display.blit(tempdisplay, (0, 0)) display.blit(tempsurf, (0, 0)) pygame.display.update() display.blit(tempdisplay, (0, 0)) pygame.display.update() for monster in g_monsters: if can_see(self, monster): monster.take_damage(self.damage, 1) if can_see(self, player): player.take_damage(self.damage / 2) class Decal_Point: def __init__(self, position_xy): self.rect = pygame.Rect((position_xy[0], position_xy[1], 1, 1)) self.colour_number = 10 self.fade_time = 0 self.fade_duration = 250 self.current_colour = [255, 255, 0] # overridden by actual class self.step = [0, 0, 0] def calculate_step(self): final_colour = background.get_at(self.rect.topleft) for index in range(0,3): self.step[index] = ((final_colour[index] - self.current_colour[index]) / 10) def think(self): # decals fade to the background colour if self.fade_time > timer.current_time: return else: if self.colour_number > 1: for index in range(0,3): self.current_colour[index] = self.current_colour[index] + self.step[index] self.colour_number -= 1 self.fade_time = timer.current_time + self.fade_duration else: self.current_colour = background.get_at(self.rect.topleft) return def draw(self): display.fill(self.current_colour, self.rect) dirtyrects.append(self.rect) class Bullet_Hole(Decal_Point): def __init__(self, position_xy): Decal_Point.__init__(self, position_xy) self.current_colour = [255, 255, 0] self.calculate_step() class Blood(Decal_Point): def __init__(self, position_xy): Decal_Point.__init__(self, position_xy) self.current_colour = [255, 0, 0] self.calculate_step() self.fade_duration = 500 class Fire: def __init__(self, center_xy): self.rect = pygame.Rect((0, 0, 16, 16)) self.rect.center = center_xy self.src_rect = pygame.Rect((0, 0, 16, 16)) self.surface = Img.fire self.frame_time = 0 self.frame_duration = 250 self.hurt_time = 0 self.hurt_interval = 250 self.time_to_die = timer.current_time + 3000 self.alive = 1 self.damage = 5 def think(self): if self.time_to_die <= timer.current_time: self.alive = 0 return if self.frame_time <= timer.current_time: self.frame_time = timer.current_time + self.frame_duration if self.src_rect.left == 0: self.src_rect.left = 16 else: self.src_rect.left = 0 if self.hurt_time <= timer.current_time: self.hurt_time = timer.current_time + self.hurt_interval for monster in g_monsters: if self.rect.colliderect(monster.rect): monster.take_damage(self.damage, 0) if self.rect.colliderect(player.rect): player.take_damage(self.damage) def draw(self): display.blit(self.surface, self.rect.topleft, self.src_rect) dirtyrects.append(self.rect) def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(self.rect) class Smoke: def __init__(self, center_xy): self.rect = pygame.Rect((0, 0, 8, 8)) self.rect.center = center_xy self.fade_steps = (255, 224, 192, 160, 128, 96, 64, 32, 0) self.surface = Img.smoke self.fade_number = 0 self.fade_time = 0 self.fade_duration = 250 self.alpha = 255 self.alive = 1 def think(self): if self.fade_time > timer.current_time: return else: self.alpha = self.fade_steps[self.fade_number] self.fade_number += 1 if self.fade_number == 9: self.alive = 0 self.fade_time = timer.current_time + self.fade_duration return def draw(self): Img.smoke.set_alpha(self.alpha, pygame.RLEACCEL) display.blit(self.surface, self.rect.topleft) dirtyrects.append(self.rect) def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(self.rect) class Explosion: def __init__(self, center_xy, friendly): self.rect = pygame.Rect((0, 0, 64, 64)) self.colliderect = pygame.Rect((0, 0, 48, 48)) self.rect.center = center_xy self.colliderect.center = center_xy self.x_overlap = 0 self.y_overlap = 0 if self.rect.left < 0: self.x_overlap = abs(self.rect.left) if self.rect.top < 0: self.y_overlap = abs(self.rect.top) self.frame_offset = -64 self.surface = Img.explosion self.frame_time = 0 self.frame_duration = 60 self.alive = 1 self.friendly = friendly self.damage = 300 self.rect = self.rect.clip(screenrect) if sound: play_sound(Snd.explosion, 0.3) def think(self): if self.frame_time > timer.current_time: return else: if self.friendly: for monster in g_monsters: if self.colliderect.colliderect(monster.colliderect): monster.take_damage(self.damage * timer.frame_duration, 0) if self.rect.colliderect(player.colliderect): player.take_damage(self.damage * timer.frame_duration) self.frame_offset += 64 if self.frame_offset > 1024: self.alive = 0 self.frame_time = timer.current_time + self.frame_duration return def draw(self): display.blit(self.surface, self.rect.topleft, (self.frame_offset + self.x_overlap, self.y_overlap, 64 - self.x_overlap, 64 - self.y_overlap)) dirtyrects.append(self.rect) def erase(self): display.blit(background, self.rect.topleft, self.rect) class Monster: def __init__(self, rect, possession): self.rect = rect self.srcrect = pygame.Rect((0, 0, 16, 16)) self.possession = possession self.alive = 1 self.frame_time = 0 self.think_time = 0 self.position_x = self.rect.left * 1.000 self.position_y = self.rect.top * 1.000 self.direction = 0 self.dying = 0 self.dying_interval = 250 self.dying_time = 0 self.immune_from_explosives = 0 if sound: self.dying_sound = Snd.hunterdeath def draw(self): display.blit(self.surface, self.rect.topleft, self.srcrect) dirtyrects.append(self.rect) return def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(pygame.Rect(self.rect)) return def take_damage(self, damage, explosive): if explosive: if self.immune_from_explosives: return if not self.dying: self.health -= damage if self.health < 1: self.dying = 1 self.surface = self.dying_surface if sound: play_sound(self.dying_sound, 1.0) return def walk(self): if not self.dying: # calculate the new position delta_x = (math.cos(math.pi * self.direction / 180)) * self.speed * timer.frame_duration delta_y = (math.sin(math.pi * self.direction / 180)) * self.speed * timer.frame_duration new_position_x = self.position_x + delta_x new_position_y = self.position_y + delta_y # test the new position for collisions: test_rect = pygame.Rect((new_position_x, new_position_y, 16, 16)) test_colliderect = pygame.Rect((test_rect.left + 1, test_rect.top + 1, 14, 14)) if test_colliderect.collidelist(level.impassable_rects) != -1: # We hit a wall; turn 90 degrees and continue turn = whrandom.choice((90, -90)) self.direction += turn if self.direction > 360: self.direction -= 360 elif self.direction < 0: self.direction += 360 return for monster in g_monsters: if monster is not self: # We hit a monster; turn 90 degrees and continue if test_colliderect.colliderect(monster.colliderect): turn = whrandom.choice((90, -90)) self.direction += turn if self.direction > 360: self.direction -= 360 elif self.direction < 0: self.direction += 360 return if test_colliderect.colliderect(player.colliderect): # We ran into the player. Do melee attack if we have one if self.damage: player.take_damage(self.damage * timer.frame_duration) return # no collisions, save the new position self.position_x = new_position_x self.position_y = new_position_y self.rect.topleft = (self.position_x, self.position_y) self.colliderect.left = self.rect.left + 1 self.colliderect.top = self.rect.top + 1 return class Shooter(Monster): think_interval = 750 speed = 40 health = 90 damage = 0 def __init__(self, rect, possession): Monster.__init__(self, rect, possession) self.surface = Img.shooter self.dying_surface = Img.shooter_dying self.colliderect = pygame.Rect(self.rect.left + 1, self.rect.top + 1, 14, 14) self.srcrect = pygame.Rect((0, 0, 16, 16)) self.shoot_time = 0 self.damage = Shooter.damage self.health = Shooter.health self.speed = Shooter.speed def think(self): if not self.dying: if timer.current_time >= self.think_time: self.think_time = timer.current_time + Hunter.think_interval if can_see(self, player): self.direction = calculate_angle(self, player) vel_x = (math.cos(math.pi * self.direction / 180)) * Bullet.speed vel_y = (math.sin(math.pi * self.direction / 180)) * Bullet.speed bullets.append(Bullet(self.rect.center[0], self.rect.center[1], vel_x, vel_y, 0)) if sound: play_sound(Snd.shotgun, 0.25) else: self.direction = whrandom.randint(0, 360) self.animate() def animate(self): if not self.dying: # determine src_rect from direction # This can be improved, since the checks are sequential. angle = self.direction if angle < 0: angle += 360 if angle >= 337 or angle < 22: self.srcrect.left = 0 elif angle >= 22 and angle < 67: self.srcrect.left = 16 elif angle >= 67 and angle < 112: self.srcrect.left = 32 elif angle >= 112 and angle < 157: self.srcrect.left = 48 elif angle >= 157 and angle < 202: self.srcrect.left = 64 elif angle >= 202 and angle < 247: self.srcrect.left = 80 elif angle >= 247 and angle < 292: self.srcrect.left = 96 elif angle >= 292 and angle < 337: self.srcrect.left = 112 else: if timer.current_time >= self.dying_time: self.dying_time = timer.current_time + self.dying_interval self.srcrect.left += 16 if self.srcrect.left > self.surface.get_width(): self.alive = 0 if self.possession: g_items.append(self.possession(self.rect.topleft)) return class Hunter(Monster): frame_duration = 200 think_interval = 1000 speed = 80 health = 70 damage = 40 def __init__(self, rect, possession): Monster.__init__(self, rect, possession) self.surface = Img.hunter self.dying_surface = Img.hunter_dying self.colliderect = pygame.Rect(self.rect.left + 1, self.rect.top +1, 14, 14) self.damage = Hunter.damage self.health = Hunter.health self.speed = Hunter.speed self.dying_interval = 250 self.dying_time = 0 def think(self): if not self.dying: if timer.current_time >= self.frame_time: self.frame_time = timer.current_time + Hunter.frame_duration if timer.current_time >= self.think_time: self.think_time = timer.current_time + Hunter.think_interval if can_see(self, player): self.direction = calculate_angle(self, player) else: self.direction = whrandom.randint(0, 360) self.animate() def animate(self): if not self.dying: self.srcrect.left += 16 if self.srcrect.left == 32: self.srcrect.left = 0 else: if timer.current_time >= self.dying_time: self.dying_time = timer.current_time + self.dying_interval self.srcrect.left += 16 if self.srcrect.left > self.surface.get_width(): self.alive = 0 if self.possession: g_items.append(self.possession(self.rect.topleft)) return class Gunner(Monster): think_interval = 200 speed = 25 health = 100 damage = 0 def __init__(self, rect, possession): Monster.__init__(self, rect, possession) self.surface = Img.gunner self.dying_surface = Img.gunner_dying self.colliderect = pygame.Rect(self.rect.left + 1, self.rect.top + 1, 14, 14) self.srcrect = pygame.Rect((0, 0, 16, 16)) self.shoot_time = 0 self.damage = Gunner.damage self.health = Gunner.health self.speed = Gunner.speed def think(self): if not self.dying: if timer.current_time >= self.think_time: self.think_time = timer.current_time + Gunner.think_interval if can_see(self, player): self.direction = calculate_angle(self, player) angle = self.direction + whrandom.randint(-5, 5) vel_x = (math.cos(math.pi * angle / 180)) * Bullet.speed vel_y = (math.sin(math.pi * angle / 180)) * Bullet.speed bullets.append(Bullet(self.rect.center[0], self.rect.center[1], vel_x, vel_y, 0)) if sound: play_sound(Snd.machinegun, 0.25) else: self.direction = whrandom.randint(0, 360) self.animate() def animate(self): if not self.dying: # determine src_rect from direction # This can be improved, since the checks are sequential. angle = self.direction if angle < 0: angle += 360 if angle >= 337 or angle < 22: self.srcrect.left = 0 elif angle >= 22 and angle < 67: self.srcrect.left = 16 elif angle >= 67 and angle < 112: self.srcrect.left = 32 elif angle >= 112 and angle < 157: self.srcrect.left = 48 elif angle >= 157 and angle < 202: self.srcrect.left = 64 elif angle >= 202 and angle < 247: self.srcrect.left = 80 elif angle >= 247 and angle < 292: self.srcrect.left = 96 elif angle >= 292 and angle < 337: self.srcrect.left = 112 else: if timer.current_time >= self.dying_time: self.dying_time = timer.current_time + self.dying_interval self.srcrect.left += 16 if self.srcrect.left > self.surface.get_width(): self.alive = 0 if self.possession: g_items.append(self.possession(self.rect.topleft)) return class Spider(Monster): speed = 40 health = 80 damage = 0 def __init__(self, rect, possession): Monster.__init__(self, rect, possession) self.surface = Img.spider self.dying_surface = Img.spider_dying self.colliderect = pygame.Rect(self.rect.left + 1, self.rect.top + 1, 14, 14) self.srcrect = pygame.Rect((0, 0, 16, 16)) self.turn_time = 0 self.turn_interval = 500 self.shoot_time = 0 self.shoot_interval = 1000 self.state = 0 # 0 = visible # 1 = fading # 2 = invisible # 3 = unfading self.frame_time = 0 self.frame_interval = 250 self.frame_number = 0 self.fade_time = 0 self.fade_interval = 100 self.alpha = 255 self.damage = Spider.damage self.health = Spider.health self.speed = Spider.speed def think(self): # what will the spider do this frame? if not self.dying: if self.state == 1: if self.fade_time <= timer.current_time: self.fade_time = timer.current_time + self.fade_interval self.alpha -= 32 if self.alpha <= 0: self.state = 2 self.alpha = 0 if self.state == 3: if self.fade_time <= timer.current_time: self.fade_time = timer.current_time + self.fade_interval self.alpha += 32 if self.alpha >= 255: self.state = 0 self.alpha = 255 if self.frame_time <= timer.current_time: self.frame_time = timer.current_time + self.frame_interval if self.frame_number == 1: self.frame_number = 0 else: self.frame_number = 1 if self.turn_time <= timer.current_time: self.turn_time = timer.current_time + self.turn_interval if can_see(self, player): self.direction = calculate_angle(self, player) if self.state == 2: self.state = 3 else: self.direction = whrandom.randint(0, 360) if self.state == 0: self.state = 1 if self.shoot_time <= timer.current_time and self.state == 0: self.shoot_time = timer.current_time + self.shoot_interval angle = calculate_angle(self, player) angle += whrandom.randint(-15, 15) delta_y = (math.sin(math.pi * angle / 180)) delta_x = (math.cos(math.pi * angle / 180)) laserbeams.append(Laserbeam(self.rect.center[0], self.rect.center[1], delta_x, delta_y, 0)) if sound: play_sound(Snd.laser, 1.0) self.animate() return def draw(self): Img.spider.set_alpha(self.alpha, pygame.RLEACCEL) display.blit(self.surface, self.rect.topleft, self.srcrect) dirtyrects.append(self.rect) return def animate(self): # choose frame based on time and direction if not self.dying: # determine src_rect from direction # This can be improved, since the checks are sequential. angle = self.direction if angle < 0: angle += 360 if angle >= 315 or angle < 45: self.srcrect.left = 0 elif angle >= 45 and angle < 135: self.srcrect.left = 32 elif angle >= 135 and angle < 225: self.srcrect.left = 64 elif angle >= 225 and angle < 315: self.srcrect.left = 96 if self.frame_number == 1: self.srcrect.left += 16 else: self.alpha = 255 if timer.current_time >= self.dying_time: self.dying_time = timer.current_time + self.dying_interval self.srcrect.left += 16 if self.srcrect.left > self.surface.get_width(): self.alive = 0 if self.possession: g_items.append(self.possession(self.rect.topleft)) return class Robot(Monster): speed = 30 health = 240 damage = 0 def __init__(self, rect, possession): Monster.__init__(self, rect, possession) self.surface = Img.robot self.dying_surface = Img.robot_dying self.colliderect = pygame.Rect(self.rect.left + 1, self.rect.top + 1, 14, 14) self.srcrect = pygame.Rect((0, 0, 16, 16)) self.turn_time = 0 self.turn_interval = 500 self.shoot_time = 0 self.shoot_interval = 1000 self.frame_time = 0 self.frame_interval = 250 self.frame_number = 0 self.damage = Robot.damage self.health = Robot.health self.speed = Robot.speed self.immune_from_explosives = 1 def think(self): # what will the robot do this frame? if not self.dying: if self.frame_time <= timer.current_time: self.frame_time = timer.current_time + self.frame_interval if self.frame_number == 1: self.frame_number = 0 else: self.frame_number = 1 if self.turn_time <= timer.current_time: self.turn_time = timer.current_time + self.turn_interval if can_see(self, player): self.direction = calculate_angle(self, player) else: self.direction = whrandom.randint(0, 360) if self.shoot_time <= timer.current_time: self.shoot_time = timer.current_time + self.shoot_interval if can_see(self, player): angle = calculate_angle(self, player) rocketangle = angle + whrandom.randint(-30, 30) vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) rocketangle = angle + whrandom.randint(-30, 30) vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) if sound: play_sound(Snd.rocket, 0.25) self.animate() return def animate(self): # choose frame based on time and direction if not self.dying: # determine src_rect from direction # This can be improved, since the checks are sequential. angle = self.direction if angle < 0: angle += 360 if angle >= 315 or angle < 45: self.srcrect.left = 0 elif angle >= 45 and angle < 135: self.srcrect.left = 32 elif angle >= 135 and angle < 225: self.srcrect.left = 64 elif angle >= 225 and angle < 315: self.srcrect.left = 96 if self.frame_number == 1: self.srcrect.left += 16 else: if timer.current_time >= self.dying_time: self.dying_time = timer.current_time + self.dying_interval self.srcrect.left += 16 if self.srcrect.left > self.surface.get_width(): self.alive = 0 if self.possession: g_items.append(self.possession(self.rect.topleft)) return class Gun: def __init__(self): self.rect = pygame.Rect((0, 0, 2, 2)) class Boss(Monster): def __init__(self, rect, possession): Monster.__init__(self, rect, possession) self.surface = Img.boss self.dying_surface = Img.boss_dying self.colliderect = pygame.Rect(self.rect.left + 12, self.rect.top, 36, 64) self.srcrect = pygame.Rect((0, 0, 64, 64)) self.turn_time = 0 self.turn_interval = 500 self.guns_shoot_time = 0 self.guns_shoot_interval = 125 self.rockets_shoot_time = 0 self.rockets_shoot_interval = 3000 self.frame_time = 0 self.frame_interval = 250 self.frame_number = 0 self.damage = 20 self.health = 800 self.speed = 20 self.gun1 = Gun() self.gun2 = Gun() self.death_count = 32 self.immune_from_explosives = 1 def animate(self): # choose frame based on time and direction if not self.dying: # determine src_rect from direction # This can be improved, since the checks are sequential. angle = self.direction if angle < 0: angle += 360 if angle >= 315 or angle < 45: self.srcrect.left = 0 self.gun1.rect.left = self.rect.left + 45 self.gun1.rect.top = self.rect.top + 28 self.gun2.rect.left = self.rect.left + 52 self.gun2.rect.top = self.rect.top + 27 elif angle >= 45 and angle < 135: self.srcrect.left = 256 self.gun1.rect.left = self.rect.left + 7 self.gun1.rect.top = self.rect.top + 24 self.gun2.rect.left = self.rect.left + 52 self.gun2.rect.top = self.rect.top + 25 elif angle >= 135 and angle < 225: self.srcrect.left = 512 self.gun1.rect.left = self.rect.left + 17 self.gun1.rect.top = self.rect.top + 28 self.gun2.rect.left = self.rect.left + 12 self.gun2.rect.top = self.rect.top + 27 elif angle >= 225 and angle < 315: self.srcrect.left = 768 self.gun1.rect.left = self.rect.left + 7 self.gun1.rect.top = self.rect.top + 24 self.gun2.rect.left = self.rect.left + 52 self.gun2.rect.top = self.rect.top + 25 if self.frame_number > 0: self.srcrect.left += 64 * self.frame_number else: if timer.current_time >= self.dying_time: self.dying_time = timer.current_time + self.dying_interval if self.death_count > 0: self.death_count -= 1 myx = whrandom.randint(self.rect.left, self.rect.right) myy = whrandom.randint(self.rect.top, self.rect.bottom) g_explosions.append(Explosion((myx, myy), 1)) else: self.alive = 0 end_game_victory() return def think(self): # what will the robot do this frame? if not self.dying: if self.frame_time <= timer.current_time: self.frame_time = timer.current_time + self.frame_interval if self.frame_number == 3: self.frame_number = 0 else: self.frame_number += 1 if self.turn_time <= timer.current_time: self.turn_time = timer.current_time + self.turn_interval self.direction = calculate_angle(self, player) if self.guns_shoot_time <= timer.current_time: self.guns_shoot_time = timer.current_time + self.guns_shoot_interval angle1 = calculate_angle(self.gun1, player) angle2 = calculate_angle(self.gun2, player) vel_x = (math.cos(math.pi * angle1 / 180)) * Bullet.speed vel_y = (math.sin(math.pi * angle1 / 180)) * Bullet.speed bullets.append(Bullet(self.gun1.rect.center[0], self.gun1.rect.center[1], vel_x, vel_y, 0)) vel_x = (math.cos(math.pi * angle2 / 180)) * Bullet.speed vel_y = (math.sin(math.pi * angle2 / 180)) * Bullet.speed bullets.append(Bullet(self.gun2.rect.center[0], self.gun2.rect.center[1], vel_x, vel_y, 0)) if sound: play_sound(Snd.machinegun, 0.25) if self.rockets_shoot_time <= timer.current_time: self.rockets_shoot_time = timer.current_time + self.rockets_shoot_interval angle = calculate_angle(self, player) rocketangle = angle vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed * 1 vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed * 1 g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) rocketangle = angle + whrandom.randint(-30, 30) vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed * 1 vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed * 1 g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) rocketangle = angle - 50 vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed * 1 vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed * 1 g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) rocketangle = angle + 50 vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed * 1 vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed * 1 g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) rocketangle = angle - 80 vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed * 1 vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed * 1 g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) rocketangle = angle + 80 vel_x = (math.cos(math.pi * rocketangle / 180)) * Rocket.speed * 1 vel_y = (math.sin(math.pi * rocketangle / 180)) * Rocket.speed * 1 g_rockets.append(Rocket(self.rect.center[0], self.rect.center[1], vel_x, vel_y, rocketangle, 0)) if sound: play_sound(Snd.rocket, 0.25) self.animate() return def walk(self): if not self.dying: # calculate the new position delta_x = (math.cos(math.pi * self.direction / 180)) * self.speed * timer.frame_duration delta_y = (math.sin(math.pi * self.direction / 180)) * self.speed * timer.frame_duration new_position_x = self.position_x + delta_x new_position_y = self.position_y + delta_y # test the new position for collisions: test_rect = pygame.Rect((new_position_x, new_position_y, 64, 64)) test_colliderect = pygame.Rect((test_rect.left + 4, test_rect.top + 4, 56, 56)) if test_colliderect.collidelist(level.impassable_rects) != -1: # We hit a wall; turn 90 degrees and continue turn = whrandom.choice((90, -90)) self.direction += turn if self.direction > 360: self.direction -= 360 elif self.direction < 0: self.direction += 360 return if test_colliderect.colliderect(player.colliderect): # We ran into the player. Do melee attack if we have one if self.damage: player.take_damage(self.damage * timer.frame_duration) return # no collisions, save the new position self.position_x = new_position_x self.position_y = new_position_y self.rect.topleft = (self.position_x, self.position_y) self.colliderect.left = self.rect.left + 1 self.colliderect.top = self.rect.top + 1 return class Item: def __init__(self, location): self.rect = pygame.Rect((location, (16, 16))) self.alive = 1 def draw(self): display.blit(self.surface, self.rect.topleft) dirtyrects.append(self.rect) return def erase(self): display.blit(background, self.rect.topleft, self.rect) dirtyrects.append(self.rect) return class BulletAmmo(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.bulletammo def pickup(self): weapon = player.weapons[1] if self.alive: if sound: play_sound(Snd.pickup, 0.5) self.alive = 0 weapon.ammo += 40 if weapon.ammo > 200: weapon.ammo = 200 statusbar.draw(0, 1, 0) return class LaserAmmo(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.laserammo def pickup(self): weapon = player.weapons[2] if self.alive: if sound: play_sound(Snd.pickup, 0.5) self.alive = 0 weapon.ammo += 5 if weapon.ammo > 25: weapon.ammo = 25 statusbar.draw(0, 1, 0) return class FlamerAmmo(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.flamerammo def pickup(self): weapon = player.weapons[4] if self.alive: if sound: play_sound(Snd.pickup, 0.5) self.alive = 0 weapon.ammo += 25 if weapon.ammo > 100: weapon.ammo = 100 statusbar.draw(0, 1, 0) return class GrenadeAmmo(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.grenadeammo def pickup(self): weapon = player.weapons[5] if self.alive: if sound: play_sound(Snd.pickup, 0.5) self.alive = 0 weapon.ammo += 5 if weapon.ammo > 25: weapon.ammo = 25 statusbar.draw(0, 1, 0) return class BombAmmo(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.bombammo def pickup(self): weapon = player.weapons[6] if self.alive: if sound: play_sound(Snd.pickup, 0.5) self.alive = 0 weapon.ammo += 1 if weapon.ammo > 1: weapon.ammo = 1 statusbar.draw(0, 1, 0) return pass class RocketAmmo(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.rocketammo def pickup(self): rl = player.weapons[3] if self.alive: if sound: play_sound(Snd.pickup, 0.5) self.alive = 0 rl.ammo += 5 if rl.ammo > 25: rl.ammo = 25 statusbar.draw(0, 1, 0) return class HealthKit(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.healthkit def pickup(self): if self.alive: if sound: play_sound(Snd.pickup_health, 1.0) self.alive = 0 player.health += 25 if player.health > 100: player.health = 100 statusbar.draw(0, 0, 1) return class Key(Item): def __init__(self, location): Item.__init__(self, location) self.surface = Img.key def pickup(self): if self.alive: self.alive = 0 for exit_gate in level.exit_gates: exit_gate.up() exit_gate.draw() dirtyrects.append(exit_gate.rect) class Level: def __init__(self, number): self.number = number self.tiles = [] self.entrance_gates = [] self.exit_gates = [] self.spawntiles = [] self.impassable_rects = [] self.los_blocking_rects = [] self.start_tile = None self.entry_tile = None self.exit_tile = None self.monster_queue = {} self.spawn_time = 0 self.spawn_interval = 5000 self.spawn_interval_number = -1 def load_map_file(self, number): # establish the platform-independent filename filename = os.path.join('levels', str(number) + '.lvl') try: map_file = open(filename, 'r') except IOError: raise SystemExit, "Fatal Error: Filename %s not found. Could not load map" % filename section = None rownum = 0 while 1: # per line line = map_file.readline() if line == '': break else: line = line.strip() if line == '' or line.startswith('#'): continue if line.startswith(':Map'): section = 'map' continue elif line.startswith(':Monsters'): section = 'monsters' continue if section == 'map': # parse map line if len(line) != 40: # 40 columns plus an \n raise SystemExit, "Fatal Error: Map row number %s has illegal length: %s." % (str(rownum), str(len(line))) charnum = 0 for char in line: if char == "X": self.tiles.append(Block(charnum, rownum)) elif char == ".": self.tiles.append(Blank(charnum,rownum)) elif char == "I": this_tile = Entrance(charnum, rownum) self.tiles.append(this_tile) self.entry_tile = this_tile elif char == "O": this_tile = Exit(charnum, rownum) self.tiles.append(this_tile) self.exit_tile = this_tile elif char == "G": this_tile = Entrance_gate(charnum, rownum) self.tiles.append(this_tile) self.entrance_gates.append(this_tile) elif char == "g": this_tile = Exit_gate(charnum, rownum) self.tiles.append(this_tile) self.exit_gates.append(this_tile) elif char == "S": this_tile = Blank(charnum, rownum) self.tiles.append(this_tile) self.start_tile = this_tile elif char == "M": this_tile = Blank(charnum, rownum) self.tiles.append(this_tile) self.spawntiles.append(this_tile) else: raise SystemExit, "Map read error - Unknown character: " + str(charnum) charnum += 1 rownum += 1 elif section == 'monsters': # parse monster line # turn entry into a list of monsters, free from crap monsters = line.split(',') monsters = [monster.strip() for monster in monsters] for monster in monsters: # Do input checking # Which 5-second interval? try: turn = int(monster[0:-2]) except ValueError: raise SystemExit, "Map read error: Invalid turn number for monster %s. Must be 0+." % monster # Store pointer to monster's class type = monster[-2:-1] if not type in 'shgarB-': raise SystemExit, "Map read error: Invalid monster type: %s." % type if type == 's': type = Shooter image = Img.shooter elif type == 'h': type = Hunter image = Img.hunter elif type == 'g': type = Gunner image = Img.gunner elif type == 'a': type = Spider image = Img.spider elif type == 'r': type = Robot image = Img.robot elif type == 'B': type = Boss image = Img.boss elif type == '-': type = None image = None # What's he carrying? '-' means nothing. possession = monster[-1:] if not possession in 'blfgBr-k+': raise SystemExit, "Map read error: Invalid monster possession type: %s." % possession if possession == 'b': possession = BulletAmmo possession_image = Img.bulletammo elif possession == 'l': possession = LaserAmmo possession_image = Img.laserammo elif possession == 'f': possession = FlamerAmmo possession_image = Img.flamerammo elif possession == 'g': possession = GrenadeAmmo possession_image = Img.grenadeammo elif possession == 'B': possession = BombAmmo possession_image = Img.bombammo elif possession == 'r': possession = RocketAmmo possession_image = Img.rocketammo elif possession == '+': possession = HealthKit possession_image = Img.healthkit elif possession == '-': possession = None possession_image = None elif possession == 'k': possession = Key possession_image = Img.key # load the monster into g_monsterqueue if self.monster_queue.has_key(turn): self.monster_queue[turn].append((type, image, possession, possession_image)) else: self.monster_queue[turn] = [(type, image, possession, possession_image)] # Check for common mapmaking errors. if self.start_tile == None: raise SystemExit, "Fatal Error: Map # %s load error: No player start position" % str(number) if self.entry_tile == None: raise SystemExit, "Fatal Error: Map # %s load error: No player entry position" % str(number) if len(self.tiles) != 1160: raise SystemExit, "Fatal Error: Wrong number of map tiles: %s (should be 1160)." % str(len(self.tiles)) return def calculate_impassable_rects(self): self.impassable_rects = [] for tile in self.tiles: if tile.walkable == 0: self.impassable_rects.append(tile.rect) return def calculate_los_blocking_rects(self): self.los_blocking_rects = [] for tile in self.tiles: if (tile.column > 0 and tile.column < 39 and tile.row > 0 and tile.row < 28): if tile.walkable == 0: self.los_blocking_rects.append(tile.rect) return def draw_to_display(self): for tile in self.tiles: if tile.visible: tile.draw() def introduce(self): background.fill(BLACK) display.blit(background, (0, 0)) if sound: play_sound(Snd.level_intro_music, 1.0, -1) # Display level number and wait 2.5 seconds font = pygame.font.Font(os.path.join('images', 'younffp_.ttf'), 24) level_intro_message = font.render('Level: ' + str(current_level_number), 1, (255, 255, 255)) display.fill((0, 0, 216, 255), (272, 220, 96, 40)) display.fill((0, 0, 128, 255), (276, 224, 88, 32)) display.blit(level_intro_message, (280, 228)) pygame.display.flip() if not DEBUG: pygame.time.delay(2500) # draw level, gates down self.draw_to_display() # If this is the last level, draw the boss if self.number == 10: hisrect = pygame.Rect((296, 64, 64, 64)) display.blit(Img.boss, hisrect.topleft, (320, 0, 64, 64)) g_monsters.append(Boss(hisrect, None)) pygame.display.flip() if not DEBUG: pygame.time.delay(2500) # draw level, entrance gates up for entrance_gate in self.entrance_gates: entrance_gate.up() entrance_gate.draw() pygame.display.update(entrance_gate.rect) # draw player at the level's entry point, move and animate # him to the start point, then return. player.move(self.entry_tile.rect.topleft) # set the player's state depending on his location relative # to the start point. if self.entry_tile.rect.left == self.start_tile.rect.left: player.vel_x = 0 if self.entry_tile.rect.top > self.start_tile.rect.top: player.state = 1 player.vel_y = -1 else: player.state = 3 player.vel_y = 1 else: player.vel_y = 0 if self.entry_tile.rect.left < self.start_tile.rect.left: player.state = 2 player.vel_x = 1 else: player.state = 4 player.vel_x = -1 # Get him to the start tile while player.rect.topleft <> self.start_tile.rect.topleft: dirtyrects = [] player.erase() dirtyrects.append(pygame.Rect(player.rect)) player.walk() if player.animate_time <= timer.current_time: player.animate() player.animate_time = timer.current_time + player.animate_time_duration player.draw() dirtyrects.append(player.rect) pygame.display.update(dirtyrects) if not DEBUG: pygame.time.delay(25) timer.set_current_time() # check for quit if pygame.event.peek(pygame.QUIT): end_game_quit() keys = pygame.key.get_pressed() for key in keymap.KEY_ESC: if keys[key]: end_game_quit() for entrance_gate in self.entrance_gates: entrance_gate.down() entrance_gate.draw() pygame.display.update(entrance_gate.rect) player.state = 0 player.vel_x = 0 player.vel_y = 0 player.animate() player.erase() player.draw() statusbar.rampup() statusbar.draw(1, 1, 1) if sound: Snd.level_intro_music.fadeout(2000) def unload_map(self): self = None def check_to_spawn(self, current_time): if len(self.spawntiles) == 0: return if current_time < self.spawn_time: return self.spawn_interval_number += 1 self.spawn_time = timer.current_time + self.spawn_interval try: monsters = self.monster_queue[self.spawn_interval_number] if sound: play_sound(Snd.spawngate, 1.0) for monster in monsters: g_spawngates.append(Spawngate(monster[0], monster[1], monster[2], monster[3])) except KeyError: return class Tile: def __init__(self, column, row): self.rect = pygame.Rect(column * 16, row * 16, 16, 16) self.walkable = 0 self.visible = 1 self.column = column self.row = row def draw(self): display.blit(self.surface, self.rect.topleft) background.blit(self.surface, self.rect.topleft) class Block(Tile): def __init__(self, column, row): Tile.__init__(self, column, row) self.surface = Img.block self.walkable = 0 class Blank(Tile): def __init__(self, column, row): Tile.__init__(self, column, row) self.walkable = 1 self.surface = Img.blank class Entrance(Tile): def __init__(self, column, row): Tile.__init__(self, column, row) self.surface = Img.blank self.walkable = 1 class Exit(Tile): def __init__(self, column, row): Tile.__init__(self, column, row) self.surface = Img.blank self.walkable = 1 class Gate(Tile): def __init__(self, column, row): Tile.__init__(self, column, row) self.surface = Img.gate self.walkable = 0 def up(self): if sound: Snd.gate_up.stop() play_sound(Snd.gate_up, 1.0) self.walkable = 1 self.surface = Img.blank level.calculate_impassable_rects() level.calculate_los_blocking_rects() def down(self): if sound: Snd.gate_down.stop() play_sound(Snd.gate_down, 0.75) self.walkable = 0 self.surface = Img.gate level.calculate_impassable_rects() level.calculate_los_blocking_rects() class Entrance_gate(Gate): pass class Exit_gate(Gate): pass class Spawngate: def __init__(self, monster_type, monster_image, monster_possession, monster_possession_image): self.monster_type = monster_type self.monster_possession = monster_possession self.monster_surface = monster_image self.monster_possession_surface = monster_possession_image self.surface = Img.spawngate self.rect = pygame.Rect((-1, -1, 16, 16)) self.srcrect = pygame.Rect((0, 0, 16, 16)) self.frame_duration = 100 self.frame_time = 0 self.alive = 1 # find a location for the spawngate while 1: self.rect.topleft = whrandom.choice(level.spawntiles).rect.topleft good_position = 1 if self.rect.colliderect(player.rect): good_position = 0 continue for monster in g_monsters: if self.rect.colliderect(monster.rect): good_position = 0 for spawngate in g_spawngates: if spawngate is not self: if self.rect.colliderect(spawngate.rect): good_position = 0 if good_position: break else: continue return def draw(self): if self.srcrect.left > 127: if self.monster_type != None: display.blit(self.monster_surface, self.rect.topleft, (0, 0, 16, 16)) elif self.monster_possession != None: display.blit(self.monster_possession_surface, self.rect.topleft) display.blit(self.surface, self.rect.topleft, self.srcrect) dirtyrects.append(self.rect) return def erase(self): display.blit(background, self.rect.topleft, self.rect) return def animate(self): self.srcrect.left += 16 if self.srcrect.left >= 240: self.alive = 0 self.spawn_monster() self.frame_time = timer.current_time + self.frame_duration def think(self): if timer.current_time <= self.frame_time: return else: self.animate() def spawn_monster(self): if self.monster_type: g_monsters.append(self.monster_type(self.rect, self.monster_possession)) elif self.monster_possession: g_items.append(self.monster_possession(self.rect.topleft)) class Timer: def __init__(self): self.last_frame_ms = 0 self.this_frame_ms = 0 self.frame_duration = 0.000 self.current_time = 0 def calculate_frame_duration(self): self.this_frame_ms = pygame.time.get_ticks() self.frame_duration = ((self.this_frame_ms - self.last_frame_ms) / 1000.000) self.last_frame_ms = self.this_frame_ms return def set_current_time(self): self.current_time = pygame.time.get_ticks() class Keymap: def __init__(self): self.KEY_UP = (K_KP8, K_w, K_UP) self.KEY_DOWN = (K_KP2, K_s, K_DOWN) self.KEY_LEFT = (K_KP4, K_a, K_LEFT) self.KEY_RIGHT = (K_KP6, K_d, K_RIGHT) self.KEY_ESC = (K_ESCAPE,) self.KEY_NEXTWEAPON = (K_KP_ENTER, K_e, K_RSHIFT) self.KEY_PREVWEAPON = (K_KP_PLUS, K_q, K_RCTRL) self.KEY_SCREENSHOT = (K_F5,) # Defined Constants SCREEN_WIDTH = 640 SCREEN_HEIGHT = 480 WHITE = (255, 255, 255, 255) BLACK = (0, 0, 0, 255) LASER_RED = (255, 0, 0, 255) # command-line options GOD = 0 DEBUG = 0 WINDOWSTYLE = 0 ALT_MOUSE = 0 def show_title_screen(): if sound: pygame.mixer.music.load(os.path.join('sound', 'schizo.xm')) pygame.mixer.music.play(-1) display.blit(Img.titlescreen, (0,0)) textborder = pygame.Surface((404, 42)).convert() textborder.fill((180, 180, 180)) display.blit(textborder, (118, 418)) pygame.display.update() # Title text titlerect = pygame.Rect((192, 110, 248, 90)) titleback = pygame.Surface((248, 90)).convert() titleback.fill(BLACK) titletext = title_font.render('Twitch', 0, (216, 216, 216), (BLACK)) # story text intro_text = (" ....TWITCH.... " + ".......Rescue the fair Penelope from the evil clutches of the cybernetic " + "overlord Hippopoticus and his army of bio-robotic monsters! Arrow keys or " + "asdw keys to move, mouse to aim, and left mouse button to fire. ESC to " + "quit. q and e or right shift and ctrl to change weapons. By David Clark: " + "silenus@telus.net. Props to ShredWheat, slouken, Tusker, Mark Baker and #sdl......") textrect = pygame.Rect((120, 420, 400, 34)) textback = pygame.Surface((400, 34)).convert() textback.fill(BLACK) text_surface = text_font.render(intro_text, 0, (180, 180, 180), BLACK) text_offset = 0.0 while 1: display.blit(titleback, (192, 110)) x = 240 + whrandom.randint(0, 4) y = 114 + whrandom.randint(0, 4) display.blit(titletext, (x, y)) display.blit(textback, (textrect.topleft)) display.blit(text_surface, (textrect.left, textrect.top + 4), (text_offset, 0, 400, 34)) text_offset += 0.5 if text_offset > text_surface.get_width(): text_offset = 0.0 pygame.display.update((titlerect, textrect)) pygame.event.peek() keys = pygame.key.get_pressed() for key in keymap.KEY_ESC: if keys[key]: end_game_quit() if pygame.mouse.get_pressed()[0]: if sound: pygame.mixer.music.fadeout(1000) pygame.time.delay(1000) break return def end_game_victory(): if sound: pygame.mixer.music.load(os.path.join('sound', 'fealingnull.xm')) pygame.mixer.music.play(-1) display.fill(BLACK) # display victory message font = pygame.font.Font(os.path.join('images', 'younffp_.ttf'), 24) level_intro_message = font.render('Victory! ', 1, (255, 255, 255)) display.fill((0, 0, 216, 255), (272, 220, 96, 40)) display.fill((0, 0, 128, 255), (276, 224, 88, 32)) display.blit(level_intro_message, (280, 228)) pygame.display.flip() if not DEBUG: pygame.time.delay(2500) # draw player display.blit(Img.player, (272, 100), (48, 0, 16, 16)) # draw penelope display.blit(Img.penelope, (352, 100), (0, 0, 16, 16)) pygame.display.flip() # fade in heart. blackback = pygame.Surface((16, 16)) blackback.fill(BLACK) for alpha in range (0, 256, 2): Img.heart.set_alpha(alpha) display.blit(blackback, (312, 100), (0, 0, 16, 16)) display.blit(Img.heart, (312, 100), (0, 0, 16, 16)) pygame.display.update((312, 100, 16, 16)) pygame.time.delay(50) while 1: pygame.event.peek() keys = pygame.key.get_pressed() for key in keymap.KEY_ESC: if keys[key]: end_game_quit() def play_sound(sound, volume, loop = 0): if sound: try: sound.play(loop).set_volume(volume) except AttributeError: # no free channel pass # play nothing. return # Constants for line-segment tests DONT_INTERSECT = 0 COLINEAR = -1 def have_same_signs(a, b): return ((a ^ b) >= 0) def line_seg_intersect(line1point1, line1point2, line2point1, line2point2): x1 = line1point1[0] y1 = line1point1[1] x2 = line1point2[0] y2 = line1point2[1] x3 = line2point1[0] y3 = line2point1[1] x4 = line2point2[0] y4 = line2point2[1] a1 = y2 - y1 b1 = x1 - x2 c1 = (x2 * y1) - (x1 * y2) r3 = (a1 * x3) + (b1 * y3) + c1 r4 = (a1 * x4) + (b1 * y4) + c1 if ((r3 != 0) and (r4 != 0) and have_same_signs(r3, r4)): return(DONT_INTERSECT) a2 = y4 - y3 b2 = x3 - x4 c2 = x4 * y3 - x3 * y4 r1 = a2 * x1 + b2 * y1 + c2 r2 = a2 * x2 + b2 * y2 + c2 if ((r1 != 0) and (r2 != 0) and have_same_signs(r1, r2)): return(DONT_INTERSECT) denom = (a1 * b2) - (a2 * b1) if denom == 0: return(COLINEAR) elif denom < 0: offset = (-1 * denom / 2) else: offset = denom / 2 num = (b1 * c2) - (b2 * c1) if num < 0: x = (num - offset) / denom else: x = (num + offset) / denom num = (a2 * c1) - (a1 * c2) if num <0: y = (num - offset) / denom else: y = (num - offset) / denom return (x, y) def can_see(source, target): los_line_p1 = source.rect.center los_line_p2 = target.rect.center # check each candidate rect against this los line. If any of them # intersect, the los is blocked. for rect in level.los_blocking_rects: block_p1 = rect.topleft block_p2 = rect.bottomright if line_seg_intersect(los_line_p1, los_line_p2, block_p1, block_p2): return 0 block_p1 = rect.topright block_p2 = rect.bottomleft if line_seg_intersect(los_line_p1, los_line_p2, block_p1, block_p2): return 0 return 1 def debug_wait(): # DEBUG #wait for user to hit enter at the console raw_input() def load_image(file, transparent = 0): # Cross - platform image file loading as a utility function. # load_image( filename as a string, transparent toggle as boolean # 1 = transparent, 0 = opaque.) file = os.path.join('images', file) try: surface = pygame.image.load(file) except RuntimeError: raise SystemExit, pygame.get_error() if transparent: corner = surface.get_at((0, 0)) surface.set_colorkey(corner, pygame.RLEACCEL) return surface.convert() def process_input(): if pygame.event.peek(pygame.QUIT): end_game_quit() keystate = pygame.key.get_pressed() for key in keymap.KEY_ESC: if keystate[key]: end_game_quit() player.vel_x = 0.000 player.vel_y = 0.000 player.state = 0 player_vel = timer.frame_duration * Player.speed for key in keymap.KEY_UP: if keystate[key]: player.vel_y -= player_vel player.state = 1 for key in keymap.KEY_DOWN: if keystate[key]: player.vel_y += player_vel player.state = 3 for key in keymap.KEY_LEFT: if keystate[key]: player.vel_x -= player_vel player.state = 4 for key in keymap.KEY_RIGHT: if keystate[key]: player.vel_x += player_vel player.state = 2 for key in keymap.KEY_NEXTWEAPON: if keystate[key]: player.choose_next_weapon() for key in keymap.KEY_PREVWEAPON: if keystate[key]: player.choose_prev_weapon() if pygame.mouse.get_pressed()[0]: if player.current_weapon == 5: player.weapons[5].fire(player.rect.center, crosshair.rect.center) else: player.weapons[player.current_weapon].fire(player.rect.center, calculate_angle(player, crosshair)) for key in keymap.KEY_SCREENSHOT: if keystate[key]: take_screenshot() pygame.event.pump() return def calculate_angle(source_obj, target_obj): delta_x = target_obj.rect.left - source_obj.rect.left * 1.000 delta_y = target_obj.rect.top - source_obj.rect.top * 1.000 if delta_x == 0: delta_x = 0.1 angle = int(math.atan(delta_y / delta_x) * 180 / math.pi) if delta_x < 0: angle += 180 if angle < 0: angle += 360 return angle def calculate_range(source_obj, target_obj): delta_x = target_obj.rect.left - source_obj.rect.left * 1.000 delta_y = target_obj.rect.top - source_obj.rect.top * 1.000 range = math.sqrt(delta_x**2 + delta_y**2) return range def process_options(): global GOD global DEBUG global WINDOWSTYLE global ALT_MOUSE global current_level_number global sound try: opts, args = getopt.getopt(sys.argv[1:], "hgdfmsl:") except getopt.GetoptError: # print help information and exit: usage() sys.exit(2) for o, a in opts: if o in ("-h", "--help"): usage() sys.exit() if o in ("-g", "--god"): GOD = 1 if o in ("-d", "--debug"): DEBUG = 1 if o in ("-f", "--fullscreen"): WINDOWSTYLE = pygame.FULLSCREEN if o in ("-m", "--altmouse"): ALT_MOUSE = 1 if o in ("-l", "--level"): current_level_number = int(a) - 1 if o in ("-s", "--silent"): sound = 0 return def usage(): print "Twitch.py - A cross-platform topdown shooter for pygame and SDL." print "usage: twitch.py [options]" print "\n" print "Option: -h or --help : Print this help text." print " -d or --debug : Debug mode (eliminates pauses)." print " -g or --god : God mode (player takes no damage)." print " -f or --fullscreen : Fullscreen mode." print " -m or --altmouse: Relative mouse mode." print " -s or --silent: No sound." print " -l levelnumber or --level levelnumber: Start at given level number (1-10)" sys.exit(0) def take_screenshot(): try: display.save('screenshot.bmp') except pygame.error: print "Error: couldn't write screenshot.bmp to current directory." def end_game_quit(): print "Exit - quitting." sys.exit() if __name__ == "__main__": current_level_number = 0 sound = 1 # process command-line arguments process_options() if DEBUG: if DEBUG: print "Debug mode ON." if GOD: print "God mode ON." if WINDOWSTYLE == pygame.FULLSCREEN: print "Fullscreen mode ON." if ALT_MOUSE: print "Alt mouse mode activated." # Prepares the video and sound subsystems try: pygame.display.init() display = pygame.display.set_mode( (SCREEN_WIDTH, SCREEN_HEIGHT), WINDOWSTYLE ) except: raise SystemExit, "Unable to acquire 640 x 480 video surface. Sorry." background = pygame.Surface( ( SCREEN_WIDTH, SCREEN_HEIGHT) ) screenrect = pygame.Rect(0, 0, SCREEN_WIDTH, (SCREEN_HEIGHT - 16)) if sound: try: pygame.mixer.init() sound = 1 except: print "Unable to initialize sound subsystem. Proceeding without sound." sound = 0 try: pygame.font.init() except: raise SystemExit, "Twitch requires font support. Please see README" # make the mouse behave pygame.event.set_grab(1) pygame.mouse.set_visible(0) # Load keymap from the disk keymap = Keymap() # suppress the mouse pointer if we're fullscreen if WINDOWSTYLE == pygame.FULLSCREEN: pygame.mouse.set_visible(0) # Load video and sound resources from disk Img.titlescreen = load_image('title.png', 0) Img.player = load_image('player.png', 1) Img.damage = load_image('damage.png', 1) Img.player_death = load_image('player_death.png', 1) Img.gate = load_image('gate.png', 1) Img.weapons = load_image('weapons.png', 0) Img.weapons_selected = load_image('weaponsel.png', 0) Img.health_full = load_image('healthfull.png', 0) Img.health_empty = load_image('healthempty.png', 0) Img.crosshair = load_image('crosshair.png', 1) Img.spawngate = load_image('spawngate.png', 1) Img.hunter = load_image('hunter.png', 1) Img.hunter_dying = load_image('hunter_dying.png', 1) Img.shooter = load_image('shooter.png', 1) Img.shooter_dying = load_image('shooter_dying.png', 1) Img.gunner = load_image('gunner.png', 1) Img.gunner_dying = load_image('gunner_dying.png', 1) Img.spider = load_image('spider.png', 1) Img.spider_dying = load_image('spider_dying.png', 1) Img.robot = load_image('robot.png', 1) Img.robot_dying = load_image('robot_dying.png', 1) Img.boss = load_image('boss.png', 1) Img.boss_dying = load_image('boss_dying.png', 1) Img.rocket = load_image('rocket.png', 1) Img.grenade = load_image('grenade.png', 1) Img.bomb = load_image('bomb.png', 1) Img.flame = load_image('flame.png', 1) Img.fire = load_image('fire.png', 1) Img.smoke = load_image('smoke.png', 1) Img.explosion = load_image('explosion.png', 1) Img.key = load_image('key.png', 1) Img.healthkit = load_image('healthkit.png', 1) Img.bulletammo = load_image('bulletammo.png', 1) Img.laserammo = load_image('laserammo.png', 1) Img.rocketammo = load_image('rocketammo.png', 1) Img.flamerammo = load_image('flamerammo.png', 1) Img.grenadeammo = load_image('grenadeammo.png', 1) Img.bombammo = load_image('bombammo.png', 1) Img.heart = load_image('heart.png', 1) Img.penelope = load_image('penelope.png', 1) title_font = pygame.font.Font(os.path.join('images', 'younffp_.ttf'), 48) text_font = pygame.font.Font(os.path.join('images', 'younffp_.ttf'), 28) ammo_font = pygame.font.Font(os.path.join('images', 'younffp_.ttf'), 16) ammo_font_erase = ammo_font.render(' ', 0, BLACK, BLACK) if sound: Snd.level_intro_music = pygame.mixer.Sound(os.path.join('sound', 'levelintro.wav')) Snd.rocket = pygame.mixer.Sound(os.path.join('sound', 'rocket.wav')) Snd.laser = pygame.mixer.Sound(os.path.join('sound', 'laser.wav')) Snd.shotgun = pygame.mixer.Sound(os.path.join('sound', 'shotgun.wav')) Snd.machinegun = pygame.mixer.Sound(os.path.join('sound', 'mg.wav')) Snd.grenade = pygame.mixer.Sound(os.path.join('sound', 'gchuck.wav')) Snd.spawngate = pygame.mixer.Sound(os.path.join('sound', 'spawngate.wav')) Snd.bombblast = pygame.mixer.Sound(os.path.join('sound', 'bombblast.wav')) Snd.pickup = pygame.mixer.Sound(os.path.join('sound', 'pickup.wav')) Snd.pickup_health = pygame.mixer.Sound(os.path.join('sound', 'pickuphealth.wav')) Snd.hunterdeath = pygame.mixer.Sound(os.path.join('sound', 'hunterdeath.wav')) Snd.explosion = pygame.mixer.Sound(os.path.join('sound', 'explosion.wav')) Snd.flame = pygame.mixer.Sound(os.path.join('sound', 'flame.wav')) Snd.weapon_switch = pygame.mixer.Sound(os.path.join('sound', 'weaponswitch.wav')) Snd.player_death = pygame.mixer.Sound(os.path.join('sound', 'playerdeath.wav')) Snd.player_pain = [] Snd.player_pain.append(pygame.mixer.Sound(os.path.join('sound', 'pain1.wav'))) Snd.player_pain.append(pygame.mixer.Sound(os.path.join('sound', 'pain2.wav'))) Snd.player_pain.append(pygame.mixer.Sound(os.path.join('sound', 'pain3.wav'))) Snd.gate_down = pygame.mixer.Sound(os.path.join('sound', 'gatedown.wav')) Snd.gate_up = pygame.mixer.Sound(os.path.join('sound', 'gateup.wav')) show_title_screen() # Create dirtyrects[] global for easy access dirtyrects = [] player = Player() statusbar = Statusbar() crosshair = Crosshair(320, 240) timer = Timer() while 1: bullets = [] laserbeams = [] g_rockets = [] g_grenades = [] g_bombs = [] g_items = [] g_decals = [] g_smokes = [] g_explosions = [] g_monsters = [] g_spawngates = [] g_flames = [] g_fires = [] current_level_number += 1 # load current block and blank graphics if current_level_number >= 1 and current_level_number < 4: Img.blank = load_image('blank1.png', 0) Img.block = load_image('block1.png', 0) elif current_level_number >= 4 and current_level_number < 7: Img.blank = load_image('blank2.png', 0) Img.block = load_image('block2.png', 0) elif current_level_number >= 7 and current_level_number < 10: Img.blank = load_image('blank3.png', 0) Img.block = load_image('block3.png', 0) else: Img.blank = load_image('blank4.png', 0) Img.block = load_image('block4.png', 0) level = Level(current_level_number) level.load_map_file(current_level_number) level.calculate_impassable_rects() level.introduce() pygame.display.flip() pygame.event.pump() timer.set_current_time() timer.calculate_frame_duration() # compensate for 1st frame weirdness when changing levels timer.calculate_frame_duration() # Action loop while 1: timer.set_current_time() dirtyrects = [] # let everyone think player.think() crosshair.think() [monster.think() for monster in g_monsters] [decal.think() for decal in g_decals] [smoke.think() for smoke in g_smokes] [explosion.think() for explosion in g_explosions] [laserbeam.think() for laserbeam in laserbeams] [rocket.think() for rocket in g_rockets] [grenade.think() for grenade in g_grenades] [spawngate.think() for spawngate in g_spawngates] [bomb.think() for bomb in g_bombs] [fire.think() for fire in g_fires] [flame.think() for flame in g_flames] # see whether we want to spawn in a monster level.check_to_spawn(timer.current_time) process_input() mouse_delta_x, mouse_delta_y = pygame.mouse.get_rel() # erase everything player.erase() crosshair.erase() [item.erase() for item in g_items] [monster.erase() for monster in g_monsters] [bullet.erase() for bullet in bullets] [laserbeam.erase() for laserbeam in laserbeams if laserbeam.colour_number <= 0] [rocket.erase() for rocket in g_rockets] [grenade.erase() for grenade in g_grenades] [bomb.erase() for bomb in g_bombs] [smoke.erase() for smoke in g_smokes] [explosion.erase() for explosion in g_explosions] [spawngate.erase() for spawngate in g_spawngates] [flame.erase() for flame in g_flames] [fire.erase() for fire in g_fires] # move everything movement = player.walk() crosshair.move(mouse_delta_x, mouse_delta_y, movement) [monster.walk() for monster in g_monsters] [bullet.move() for bullet in bullets] [rocket.move() for rocket in g_rockets] [grenade.move() for grenade in g_grenades] [flame.move() for flame in g_flames] # draw everything player.draw() crosshair.draw() [item.draw() for item in g_items if item.alive] [monster.draw() for monster in g_monsters] [bullet.draw() for bullet in bullets if bullet.alive] [rocket.draw() for rocket in g_rockets if rocket.alive] [grenade.draw() for grenade in g_grenades if grenade.alive] [laserbeam.draw() for laserbeam in laserbeams if laserbeam.colour_number > 0] [decal.draw() for decal in g_decals] [flame.draw() for flame in g_flames if flame.alive] [fire.draw() for fire in g_fires if fire.alive] [bomb.draw() for bomb in g_bombs if bomb.alive] [smoke.draw() for smoke in g_smokes if smoke.alive] [explosion.draw() for explosion in g_explosions if explosion.alive] [spawngate.draw() for spawngate in g_spawngates] # Draw all our pretty pictures pygame.display.update(dirtyrects) # Cull the dead g_monsters = [monster for monster in g_monsters if monster.alive] bullets = [bullet for bullet in bullets if bullet.alive] laserbeams = [laserbeam for laserbeam in laserbeams if laserbeam.colour_number > 0] g_rockets = [rocket for rocket in g_rockets if rocket.alive] g_grenades = [grenade for grenade in g_grenades if grenade.alive] g_bombs = [bomb for bomb in g_bombs if bomb.alive] g_decals = [decal for decal in g_decals if decal.colour_number > 1] g_smokes = [smoke for smoke in g_smokes if smoke.alive] g_explosions = [explosion for explosion in g_explosions if explosion.alive] g_spawngates = [spawngate for spawngate in g_spawngates if spawngate.alive] g_flames = [flame for flame in g_flames if flame.alive] g_fires = [fire for fire in g_fires if fire.alive] timer.calculate_frame_duration() # check for end-of-level if level.number != 10: if player.colliderect.colliderect(level.exit_tile.rect): break # check for player death if player.health < 1: player.animate_death() end_game_quit() end_game_quit()