"""game_entities.py
   Code for game characters and objects

   Adventures on Planet Zephulor by Aaron Hachez. 

   All code, artwork, and sound copyright 2003, Aaron Hachez.

   ----------------------------------------------------------------------------------------

   This file is part of "Adventures on Planet Zephulor".

   A copy of the GNU General Public License should be included with any
   redistribution of this program in a file named "COPYING.txt".

   "Adventures on Planet Zephulor" 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.

   "Adventures on Planet Zephulor" 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 "Adventures on Planet Zephulor"; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   ----------------------------------------------------------------------------------------

   "Adventures on Planet Zephulor" uses the following libraries:

   PyGame
   SDL

   Both are at the time of this writing, licensed under the GNU LGPL. Please see
   the included file LGPL for details 
"""

import myimg
import pygame
import cPickle
import os
import game_projectiles
import game_hud

from game_sound import entsfxlist
from game_config import config

class base_entity:
    """Basic game entity"""

    ### Data unrelated to actual gameplay

    # The sprite data file used to load this char
    myfile = None

    # Image file name
    image = None

    # The image file attributes (numtiles=0, colorkey=-1, tilewidth=32, tileheight=32, top=0, left=0, bottom=0, right=0)
    imageattrib = None

    ### Data related to gameplay

    # The id tag for this sprite
    id = None

    # The name of the sprite for sound purposes
    name = None

    # This will hold the uniq id of the sound list for this char
    soundid = None

    # x/y velocity base for movement
    xvel = 0
    yvel = 0

    # Total resolved velocity (i.e. final velocity after acceleration and other forces have been applied.)
    xtvel = 0
    ytvel = 0

    # Acceleration
    xaccel = 0
    yaccel = 0

    # Movement speeds
    upspeed = 5
    downspeed = 5
    leftspeed = 5
    rightspeed = 5

    # Flags to determine whether sprite is falling/jumping/offscreen
    falling = 0
    jumping = 0
    offscreen = 0
    
    # Flag to determine whether or not the player is holding down the jump key
    jumpkey = 0

    # Rate of fall
    fallrate = 15

    # Rate of jumping
    jumprate = 20
    currentjump = 0

    # Ammount of decay in jumping
    decay = 1
    currentdecay = 0

    # Player hit points, i.e. how much damage it takes to kill
    hitpoints = 20

    # Whether or not the player is in the proccess of dying
    dying = 0

    # Whether or not the player is dead
    dead = 0

    # The ammount of damage delt by char
    dodamage = 1

    # Base damage to do
    basedodamage = 0

    # The ammount of damate delt by char's bullets
    bulletdamage = 1

    # While this is greater than 0, player is safe from damage
    damagesafe = 0

    # The slowing rate, added to x velocity in resolve
    slowrate = 0

    # The max number of bullets that may be on screen
    bulletlimit = None

    # Flags for ai actions
    aiflags = None

    # Whether or not char is running or chasing
    running = 0
    chasing = 0

    # A pointer to the bullet group for this char
    bulletgroup = None

    # Animation index info
    # These are indexes to which animation to play for each action.
    animrightstill = 0
    animrightmove = 1
    animleftstill = 2
    animleftmove = 3
    animupstill = -1
    animupmove = -1
    animdownstill = -1
    animdownmove = -1
    animjumpleft = 4
    animjumpright = 5
    animfallleft = 4
    animfallright = 5
    animdamagedleft = -1
    animdamagedright = -1
    animslowedleft = -1
    animslowedright = -1
    animkneelleft = 6
    animkneelright = 7
    animdeathleft = 6
    animdeathright = 7

    # A list of sound indexes
    sndshoot = 0
    snddamaged = 1
    sndhealed = 2
    snddie = 3

    # The beginning position of the player
    begscreenrect = None
    begmaprect = None
    begboundrect = None

    # Saves the old boundrect for kneeling
    boundrectold = 0

    # Current x1/y1,x2/y2 position
    screenrect = []

    # Screen rect width and height
    screenrectwidth = 0
    screenrectheight = 0

    # Last x1/y1,x2/y2 position
    lastrect = []

    # Sprite width and height
    width = 32
    height = 32

    # The offset to use in creating the bounding rect
    boundoffset = []

    # Bounding rect
    boundrect = []

    # Bounding rect width and height
    boundrectwidth = 0
    boundrectheight = 0

    # Map location rect
    maprect = []

    # Map rect width and height
    maprectwidth = 0
    maprectheight = 0

    # Bullet offset, and knealing bullet offset.
    # There are four numbers for each offset, the first two
    # are for the left facing offset, the second pair
    # are for the right facing offset.
    # NOTE: Offsets must take the size of the projectile into account
    bulletxyoff = []
    bulletkneelxyoff = []

    # A list of animation frames to use
    cel_list = []

    # Current animation and cel to run
    cur_anim = 0
    cur_cel = 0

    # A list of tile images
    tiles = []

    # Current state of char
    state = None

    # The score this character has racked up
    score = 0

    # At what score the player gets a bonus
    scorebonus = 40

    def __init__(self, filename, xystart=(0,0)):
        """Initialize object
        xystart must be specified by map or other external source if not zero.
        """
        # Load the sprite file, this populates all of the data we use below
        # If load succeeded
        if self.load(filename) != None:
            # Adjust screen rect to proper start position
            self.screenrect[0] += xystart[0]
            self.screenrect[1] += xystart[1]
            self.screenrect[2] += xystart[0]
            self.screenrect[3] += xystart[1]
    
            # Adjust bound rect to proper start position
            self.boundrect[0] += xystart[0]
            self.boundrect[1] += xystart[1]
            self.boundrect[2] += xystart[0]
            self.boundrect[3] += xystart[1]

            # Adjust map rect to proper start position
            self.maprect[0] += xystart[0]
            self.maprect[1] += xystart[1]
            self.maprect[2] += xystart[0]
            self.maprect[3] += xystart[1]

            self.state = { 'up':0, 'down':0, 'left':0, 'right':0 , 'moving_y':0, 'moving_x':0, 'kneeling':0 }

            # Record the original starting positions
            self.begscreenrect = self.screenrect[:]
            self.begmaprect = self.maprect[:]
            self.begboundrect = self.boundrect[:]

            # Debug
            #self.begscreenrect.append(filename)
            #self.begmaprect.append(filename)
            #self.begboundrect.append(filename)

            # Debug
            #if self.name == 'char1':
            #    #print self.screenrect, xystart[0], xystart[1]

            self.state['right'] = 1
        else:
            print "Error loading file: " + `filename` + "!"

    def load(self, filename):
        """Load a sprite file and populate data from said sprite file, cannot be used outside of init function"""

        # This is a stub function
        # New function for each char type
        pass

    def draw(self, screen):
        """Draw sprite to screen"""
        
        # Stub function
        pass

    def ifcollidechar(self, overlaprect, otherchar, top, left):
        """Handle collision between two chars"""

        # This is a stub, collision is handled seperately for each char type
        pass

    def ifcollide(self, overlaprect, tiledata, top, left):
        """Handle collision"""

        if self.dying or self.dead:
            return

        # If tile data is passed in null, create a bogus tile object. This needs to be done more elegantly.
        if tiledata == None:
            tiledata = [ 0, 0, 0, 0, 0, 0, 0, 0 ]

        # If this tile is passable, don't do any actual adjustment
        if not tiledata[0]:
            overwidth = overlaprect[2] - overlaprect[0]
            overheight = overlaprect[3] - overlaprect[1]

            flag = 0

            # if overlaping rect is higher than wide, move object left or right
            # else overlaping rect is wider than high and object is flagged as falling, move object up or down
            if overwidth < overheight:

                # If overlaping rect is at left of self, move self right, else move self left
                if left:
                    if not tiledata[1]:
                        self.adjposscr(x=overwidth)
                        self.adjposmap(x=overwidth)

                        # If not main char, turn around and go the other way
                        if self.id != 0 and not self.chasing and not self.running:
                            self.right_on()

                    else:
                        flag = 1
                else:
                    if not tiledata[3]:
                        self.adjposscr(x=-overwidth)
                        self.adjposmap(x=-overwidth)

                        # If not main char, turn around and go the other way
                        if self.id != 0 and not self.chasing and not self.running:
                            self.left_on()
                    else:
                        flag = 1
               
                # If the tile was either passable on the right or left, and the top of the overlap rect
                # is within 16 pixels from the bottem of the bounding rect, do collision tests for top/bottom 
                if flag and (self.boundrect[3] - 16) < overlaprect[1]:
                    # Debug
                    #print "FLAG", self.boundrect[3], overlaprect[3]

                    # If overlaping rect is at top of self, move self down, else move self up (By colrect height)
                    if top:
                        # If the tiledata does not indicate that the tile is passable in the up direction,
                        # and the char is not falling (Can't fall and hit your head on the celing at the same time)
                        if not tiledata[4] and not self.falling:
                            self.adjposscr(y=overheight)
                            self.adjposmap(y=overheight)
                            self.jumping = 0
                    elif self.falling == 1:
                        # Set falling to off, we've had a collison in the downward direction
                        if not tiledata[2]:
                            #self.falling = 0
                            self.fall_off()
                            self.adjposscr(y=-overheight)
                            self.adjposmap(y=-overheight)
                    else:
                        if not tiledata[2] and not self.jumping:
                            self.adjposscr(y=-overheight)
                            self.adjposmap(y=-overheight)
            else:
                # If overlaping rect is at top of self, move self down, else move self up (By colrect height)
                if top:
                    # If the tiledata does not indicate that the tile is passable in the up direction,
                    # and the char is not falling (Can't fall and hit your head on the celing at the same time)
                    if not tiledata[4] and not self.falling:
                        self.adjposscr(y=overheight)
                        self.adjposmap(y=overheight)
                        self.jumping = 0
                elif self.falling == 1:
                    # Set falling to off, we've had a collison in the downward direction
                    if not tiledata[2]:
                        #self.falling = 0
                        self.fall_off()
                        self.adjposscr(y=-overheight)
                        self.adjposmap(y=-overheight)
                else:
                    if not tiledata[2] and not self.jumping:
                        self.adjposscr(y=-overheight)
                        self.adjposmap(y=-overheight)

        # If the damage flag is set, do the damage
        if tiledata[5]:
            # Do damage equiv to tiledata flag
            self.getdamage(tiledata[5])

        # If the slow flag is set, do slowed
        if tiledata[6]:
            # Set slow equiv to tiledata flag
            self.setslow(tiledata[6])

    def addbullet(self):
        """Add a bullet to the list"""

        # Check to see whether or not we are at our max bullet quotient
        if len(self.bulletgroup.bulletlist) < self.bulletlimit:
    
            sendxvel = 0
            sendyvel = 0

            screenx = 0
            screeny = 0
            mapx = 0
            mapy = 0

            # Determine the direction facing, and set velocities and final bullet
            # start position.
            if self.state['left']:
                sendxvel = -20

                # Start with top right coords
                screenx = self.screenrect[0]
                screeny = self.screenrect[1]
                mapx = self.maprect[0]
                mapy = self.maprect[1]

                if self.state['kneeling'] and not self.state['moving_x'] and not self.state['moving_y']:
                    # Adjust by offset
                    screenx += self.bulletkneelxyoff[0]
                    screeny += self.bulletkneelxyoff[1]
                    mapx += self.bulletkneelxyoff[0]
                    mapy += self.bulletkneelxyoff[1]
                else:
                    # Adjust by offset
                    screenx += self.bulletxyoff[0]
                    screeny += self.bulletxyoff[1]
                    mapx += self.bulletxyoff[0]
                    mapy += self.bulletxyoff[1]

            elif self.state['right']:
                sendxvel = 20

                # Start with top left coords
                screenx = self.screenrect[2]
                screeny = self.screenrect[1]
                mapx = self.maprect[2]
                mapy = self.maprect[1]

                if self.state['kneeling'] and not self.state['moving_x'] and not self.state['moving_y']:
                    # adjust by offset
                    screenx -= self.bulletkneelxyoff[2]
                    screeny += self.bulletkneelxyoff[3]
                    mapx -= self.bulletkneelxyoff[2]
                    mapy += self.bulletkneelxyoff[3]
                else:
                    # adjust by offset
                    screenx -= self.bulletxyoff[2]
                    screeny += self.bulletxyoff[3]
                    mapx -= self.bulletxyoff[2]
                    mapy += self.bulletxyoff[3]

            # Play shooting sound
            entsfxlist.play(self.soundid, self.sndshoot)

            # Add a bullet to the list, at current position, at the current damage level
            self.bulletgroup.add((screenx,
                                  screeny),
                                 (mapx,
                                  mapy),
                                 (sendxvel,
                                  sendyvel),
                                 self.bulletdamage)

    def getdamage(self, damage=0):
        """Tally and resolve damage"""

        # Stub function
        pass

    def setslow(self, slow=0):
        """Set a slowdown rate for char"""

        self.slowrate = slow
            
        #self.cur_cel = 0
        #self.cur_anim = self.animslowed

        # Debug
        #print "Slowed.", self.slowrate

    def update(self):
        """Update maprect, screenrect, boundrect"""

        # If offscreen or dead, don't do anything
        if self.offscreen or self.dead:
            return

        if not self.dying:

            # Do resolution of forces
            self.resolve()

            # Record last position
            self.lastrect = self.screenrect[:]

            # We must update all of the associated rects
            
            # Screen rect, the position on screen
            self.screenrect[0] += self.xtvel
            self.screenrect[1] += self.ytvel
            self.screenrect[2] += self.xtvel
            self.screenrect[3] += self.ytvel

            # Bound rect, the actual bounding box
            self.boundrect[0] += self.xtvel
            self.boundrect[1] += self.ytvel
            self.boundrect[2] += self.xtvel
            self.boundrect[3] += self.ytvel
            
            # Map rect, the postion on the map
            self.maprect[0] += self.xtvel
            self.maprect[1] += self.ytvel
            self.maprect[2] += self.xtvel
            self.maprect[3] += self.ytvel

            # Reset total x/y velocities
            self.xtvel = 0
            self.ytvel = 0

            # If damage safe is on, decrement it
            if self.damagesafe > 0:
                self.damagesafe -= 1

            # If there is a slowrate set, unset it. If we are still on the
            # thing that is slowing us, this will be re-set in collision detection
            if self.slowrate:
                self.slowrate = 0

            if self.hitpoints <= 0:
                self.die_on()

        if ( self.cur_cel < len(self.cel_list[self.cur_anim] ) - 1):
            self.cur_cel += 1
        else:
            # If in the process of dying, this is the final animation frame, don't update
            if self.dying:
                self.dead = 1
            # Else, reset
            else:
                self.cur_cel = 0

        # If this is the main char
        if self.id == 0:
            # Okay, this is slightly cheesy, but the hud keeps track of the player's score, based upon
            # how many enemy hit points have been taken, which is passed to the hud by the enemy char. 
            # Internally the playerchar gets it's bonuses from this score.
            self.score = game_hud.mainhud.update(self.hitpoints, self.bulletdamage)

            if self.score >= self.scorebonus:
                self.hitpoints += 1

                # Reset the score
                game_hud.mainhud.pscore = 0
    
    def resolve(self):
        """Resolve velocities, forces, and flags on object into final value for x and y"""

        # If the character is falling, set accelleration
        if self.falling == 1:
            self.yaccel = self.fallrate
            self.fall_on()
        elif self.falling == 0:
            self.yaccel = 0

        # If the character is jumping
        if self.jumping == 1 and self.falling == 0:
            # Set incremental decay
            self.currentdecay -= self.decay
           
            # If the current jump has not been set
            if self.currentjump == 0:
                # Set rate to jump
                self.currentjump = self.jumprate
            # Debug
            #print self.currentjump, self.currentdecay, self.currentjump + self.currentdecay

            # Set y accelleration, i.e. resolve all the jump figures into one
            self.yaccel -= self.currentjump + self.currentdecay

            # If we've reached the peak of our jump turn off jumping.
            if self.yaccel > 0:
                self.yaccel = 0
                self.jumping = 0
                self.currentdecay = 0
                self.currentjump = 0
        elif self.jumping == 0:
            # Reset jump data, usually because of collision in mid jump
            self.currentjump = 0
            self.currentdecay = 0
        else:
            # Else, turn jumping off, i.e. user pressed jump key while falling.
            self.jumping = 0

        self.xtvel = self.xvel + self.xaccel
        self.ytvel = self.yvel + self.yaccel

        # This slows us in the proper direction
        if self.xtvel > 0:
            self.xtvel -= self.slowrate
        elif self.xtvel < 0:
            self.xtvel += self.slowrate

    def adjposscr(self, x=None, y=None):
        """If x (or y) move screen coords by value of x (or y). Default arguments are x=None and y=None"""

        # Debug
        #if self.id == 0:
        #print "ADJUSTPOS", x, y, self.screenrect, self.boundrect, self.maprect

        # Add x adjustment to xpos
        if x != None:
            self.screenrect[0] += x
            self.screenrect[2] += x

        # Add y adjustment to ypos
        if y != None:
            self.screenrect[1] += y
            self.screenrect[3] += y

    def adjposmap(self, x=None, y=None):
        """If x (or y) move map and bound by value of x (or y). Default arguments are x=None and y=None"""

        # Add x adjustment to xpos
        if x != None:
            self.maprect[0] += x
            self.maprect[2] += x
            self.boundrect[0] += x
            self.boundrect[2] += x

        # Add y adjustment to ypos
        if y != None:
            self.maprect[1] += y
            self.maprect[3] += y
            self.boundrect[1] += y
            self.boundrect[3] += y

    def die_on(self):
        self.dying = 1

        if self.state['left']:
            self.cur_anim = self.animdeathleft

            # Debug
            #print self.cur_anim, "LEFT"
        elif self.state['right']:
            self.cur_anim = self.animdeathright

            # Debug
            #print self.cur_anim, "RIGHT"

        entsfxlist.play(self.soundid, self.snddie)

        self.cur_cel = 0

    def kneel_on(self):
        self.state['kneeling'] = 1
        if self.falling == 0 and self.jumping == 0:
            #print self.boundrect[1],
            self.boundrectold = self.boundrect[1]
            self.boundrect[1] += self.bulletkneelxyoff[1]
            #print "ON", self.boundrect[1]

    def kneel_off(self):
        self.state['kneeling'] = 0
        if self.boundrectold != 0:
            self.boundrect[1] -= self.bulletkneelxyoff[1]
            self.boundrectold = 0
            #print "OFF", self.boundrect[1]

    def fall_on(self):
        if self.state['left']:
            self.cur_anim = self.animfallleft
        elif self.state['right']:
            self.cur_anim = self.animfallright

        self.cur_cel = 0
        self.falling = 1

    def fall_off(self):
        # If moving, then set to a moving animation
        if self.state['moving_x']:
            if self.state['left']:
                self.cur_anim = self.animleftmove
            elif self.state['right']:
                self.cur_anim = self.animrightmove
        
        # Else if kneeling set
        elif self.state['kneeling']:
            if self.boundrectold == 0:
                self.boundrectold = self.boundrect[1]
                self.boundrect[1] += self.bulletkneelxyoff[1]
                #print "ON", self.boundrect[1]
            if self.state['left']:
                self.cur_anim = self.animkneelleft
            elif self.state['right']:
                self.cur_anim = self.animkneelright
       
        # Else set to a still animation
        else:
            if self.state['left']:
                self.cur_anim = self.animleftstill
        #        self.cur_cel = 0
            elif self.state['right']:
                self.cur_anim = self.animrightstill
        #        self.cur_cel = 0

        self.falling = 0

    def jump_on(self):
        if self.state['left']:
            self.cur_anim = self.animjumpleft
        elif self.state['right']:
            self.cur_anim = self.animjumpright

        self.cur_cel = 0
        self.jumping = 1

    def jump_off(self):
        self.jumping = 0

    def up_on(self):
        self.state['up'] = 1
        self.state['down'] = 0
        self.state['left'] = 0
        self.state['right'] = 0
        self.state['moving_y'] = 1
        self.cur_anim = 0
        self.cur_cel = 0
        self.xvel = 0
        self.yvel = 0
        self.yvel -= self.upspeed

    def up_off(self):
        if self.state['up'] == 1:
          #  self.state['up'] = 0
            self.state['moving_y'] = 0
            self.cur_anim = 0
            self.cur_cel = 0
            self.yvel = 0

    def down_on(self):
        self.state['up'] = 0
        self.state['down'] = 1
        self.state['left'] = 0
        self.state['right'] = 0
        self.state['moving_y'] = 1
        self.cur_anim = 0
        self.cur_cel = 0
        self.xvel = 0
        self.yvel = 0
        self.yvel += self.downspeed

    def down_off(self):
        if self.state['down'] == 1:
          #  self.state['down'] = 0
            self.state['moving_y'] = 0
            self.cur_anim = 0
            self.cur_cel = 0
            self.yvel = 0

    def left_on(self):
        self.state['up'] = 0
        self.state['down'] = 0
        self.state['left'] = 1
        self.state['right'] = 0
        self.state['moving_x'] = 1

        # If the char is jumping
        if self.jumping:
            # If the animation is not already set to animjumpleft
            if not self.cur_anim == self.animjumpleft:
                self.cur_anim = self.animjumpleft
                self.cur_cel = 0

        # If the char is falling
        elif self.falling:
            # If the animation is not already set to animfallleft
            if not self.cur_anim == self.animfallleft:
                self.cur_anim = self.animfallleft
                self.cur_cel = 0

        # Else set walk cycle animation
        else:
            self.cur_anim = self.animleftmove
            self.cur_cel = 0

        self.xvel = 0
        self.yvel = 0
        self.xvel -= self.leftspeed
       
    def left_off(self):
        if self.state['left'] == 1:
          #  self.state['left'] = 0
            self.state['moving_x'] = 0

            # If our char is jumping or falling, don't change animation
            if not self.jumping and not self.falling:
                self.cur_anim = self.animleftstill
                self.cur_cel = 0

            self.xvel = 0

    def right_on(self):
        self.state['up'] = 0
        self.state['down'] = 0
        self.state['left'] = 0
        self.state['right'] = 1
        self.state['moving_x'] = 1

        # If the char is jumping
        if self.jumping:
            # If the animation is not already set to animjumpright
            if not self.cur_anim == self.animjumpright:
                self.cur_anim = self.animjumpright
                self.cur_cel = 0

        # If the char is falling
        elif self.falling:
            # If the animation is not already set to animfallright
            if not self.cur_anim == self.animfallright:
                self.cur_anim = self.animfallright
                self.cur_cel = 0

        # Else set walk cycle animation
        else:
            self.cur_anim = self.animrightmove
            self.cur_cel = 0

        self.xvel = 0
        self.yvel = 0
        self.xvel += self.rightspeed

    def right_off(self):
        if self.state['right'] == 1:
          #  self.state['right'] = 0
            self.state['moving_x'] = 0
        
            # If our char is jumping or falling, don't change animation
            if not self.jumping and not self.falling:
                self.cur_anim = self.animrightstill
                self.cur_cel = 0

            self.xvel = 0

class char(base_entity):

    # Char's starting hitpoints, and hitpoints should it be re-loaded
    hitpoints = 2
    basehitpoints = 2

    # The speed at which the char should move
    upspeed = 5
    downspeed = 5
    leftspeed = 5
    rightspeed = 5

    # How often a shooter should shoot
    fireinterval = 53
    
    # What to re-set fireinterval to
    firebase = 53

    # we can have up to 10 enemy bullets on screen at once
    bulletlimit = 10

    # How far away before reacting (Horizontal and vertical)
    chasereacth = 500
    chasereactv = 100

    runreacth = 200
    runreactv = 100

    # Whether or not we respawn on restart, 0 no, 1 yes
    respawn = 0

    # These are possible player bonuses, received on contact with the entity
    # The first two are permanent bonuses, the second is a timed bonus
    jumpbonus = 0
    firebonus = 0

    invulnbonus = 0

    # Whether or not this item is always damaged by touching the player
    everdamage = 0

    # Whether or not this monster is a level boss
    boss = 0

    def load(self, filename):
        """Load a sprite file and populate data from said sprite file, cannot be used outside of init function"""

        # Create string containing full path to file
        fullname = os.path.join(config.datadir, filename)

        # Try to load the sprite file, and catch any exceptions
        try:

            f = file(fullname, 'r')
        except:
            print "Error loading " + `fullname` + "."
            return None

        # Record the name of the character data file
        self.myfile = filename

        # Get the image file attributes (numtiles=0, colorkey=-1, tilewidth=32, tileheight=32, top=0, left=0, bottom=0, right=0)
        self.imageattrib = cPickle.load(f)

        # Get the name of the image file (The second thing in the character file)
        self.image = cPickle.load(f)
        
        # Get the char name, used to get the soundid from the sound system
        self.name = cPickle.load(f)

        # Get the entity bounding box offset
        self.boundoffset = cPickle.load(f)

        # Get the animation cel frame list
        self.cel_list = cPickle.load(f)

        # Get the animation index list
        animindexlist = cPickle.load(f)

        # Get the ai flags
        self.aiflags = cPickle.load(f)
       
        # Get the prjectile offset 
        self.bulletxyoff = cPickle.load(f)

        # Get the projectile offset for knealing char
        self.bulletkneelxyoff = cPickle.load(f)

        # Get the data for speed, chasereact distance, etc.
        dataflags = cPickle.load(f)

        f.close()

        # Record the width and height of sprite
        self.width = self.imageattrib['tilewidth']
        self.height = self.imageattrib['tileheight']

        # Load the image file data
        self.tiles = myimg.loadtiles(  self.image, \
                                       numtiles    = self.imageattrib['numtiles'], \
                                       colorkey    = self.imageattrib['colorkey'], \
                                       tilewidth   = self.imageattrib['tilewidth'], \
                                       tileheight  = self.imageattrib['tileheight'], \
                                       top         = self.imageattrib['top'], \
                                       left        = self.imageattrib['left'], \
                                       bottom      = self.imageattrib['bottom'], \
                                       right       = self.imageattrib['right'], \
                                       border      = 1 )

        # Init screen rect
        self.screenrect = [ 0, 0, self.width, self.height ]

        # Set width and height of screenrect
        self.screenrectwidth = self.width
        self.screenrectheight = self.height

        # Init bounding rect
        self.boundrect = [ 0, 0, 0, 0 ]

        # The boundoffset is initiially just the x/y offsets for each side
        # boundoffset[0] is how many pixels from the left side the bounding rect should be,
        # boundoffset[3] is how many pixels from the bottom the bounding rect should be, etc...
        # After init, it is the actual rect, and is updated in all of the update and move functions normally
        self.boundrect[0] = self.screenrect[0] + self.boundoffset[0]
        self.boundrect[1] = self.screenrect[1] + self.boundoffset[1]
        self.boundrect[2] = self.screenrect[2] - self.boundoffset[2]
        self.boundrect[3] = self.screenrect[3] - self.boundoffset[3]
 
        # Get width and height of bounding rect
        self.boundrectwidth = self.boundrect[2] - self.boundrect[0]
        self.boundrectheight = self.boundrect[3] - self.boundrect[1]

        # Init maprect
        self.maprect = self.screenrect[:]

        # Map rect is same width and height as sprite images
        self.maprectwidth = self.width
        self.maprectheight = self.height

        # Animation index info
        # These are indexes to which animation to play for each action.
        self.animrightstill     = animindexlist['rightstill']
        self.animrightmove      = animindexlist['rightmove']
        self.animleftstill      = animindexlist['leftstill']
        self.animleftmove       = animindexlist['leftmove']
        self.animupstill        = animindexlist['upstill']
        self.animupmove         = animindexlist['upmove']
        self.animdownstill      = animindexlist['downstill']
        self.animdownmove       = animindexlist['downmove']
        self.animjumpleft       = animindexlist['jumpleft']
        self.animjumpright      = animindexlist['jumpright']
        self.animfallleft       = animindexlist['fallleft']
        self.animfallright      = animindexlist['fallright']
        self.animdamagedleft    = animindexlist['damagedleft']
        self.animdamagedright   = animindexlist['damagedright']
        self.animslowedleft     = animindexlist['slowedleft']
        self.animslowedright    = animindexlist['slowedright']
        self.animkneelleft      = animindexlist['kneelleft']
        self.animkneelright     = animindexlist['kneelright']
        self.animdeathleft      = animindexlist['deathleft']
        self.animdeathright     = animindexlist['deathright']

        # Populate data from dataflags
        self.dodamage = dataflags['dodamage']
        self.basedodamage = dataflags['dodamage']
        self.bulletdamage = dataflags['bulletdamage']
        # Starting hitpoints, and hitpoints should it be re-loaded
        self.hitpoints = dataflags['hitpoints']
        self.basehitpoints = dataflags['basehitpoints']
        # The speed at which the char should move
        self.upspeed = dataflags['upspeed']
        self.downspeed = dataflags['downspeed']
        self.leftspeed = dataflags['leftspeed']
        self.rightspeed = dataflags['rightspeed']
        # How often a shooter should shoot
        self.fireinterval = dataflags['fireinterval']
        # What to re-set fireinterval to
        self.firebase = dataflags['firebase']
        # we can have up to 10 enemy bullets on screen at once
        self.bulletlimit = dataflags['bulletlimit']
        # How far away before reacting (Horizontal and vertical)
        self.chasereacth = dataflags['chasereacth']
        self.chasereactv = dataflags['chasereactv']
        self.runreacth = dataflags['runreacth']
        self.runreactv = dataflags['runreactv']
        # Whether or not we respawn on restart, 0 no, 1 yes
        self.respawn = dataflags['respawn']
        # Whether or not this item is always damaged by contact with the player
        self.everdamage = dataflags['everdamage']
        # Jump, Fire, and Invuln Bonuses
        self.jumpbonus = dataflags['jumpbonus']
        self.firebonus = dataflags['firebonus']
        self.invulnbonus = dataflags['invulnbonus']
        # How spongy the char is, i.e. how bouncy
        self.sponginess = dataflags['sponginess']
        # Whether or not the char is inanimate
        self.inanimate = dataflags['inanimate']

        if dataflags.has_key('boss'):
            # Whether or not this is a level boss, i.e. must be killed before exiting the level
            self.boss = dataflags['boss']
        
        # Get our sound id from the sound system
        self.soundid = entsfxlist.getsndid(self.name)

        # Return success
        return (1)

    def setslow(self, slow=0):
        """Set a slowdown rate for char"""

        self.slowrate = (self.leftspeed / 2)
            
        #self.cur_cel = 0
        #self.cur_anim = self.animslowed

        # Debug
        #print "Slowed.", self.slowrate

    def getdamage(self, damage=0):
        """Tally and resolve damage"""

        if self.inanimate == 1:
            return

        if self.dying or self.dead:
            return

        # If damagesafe is not on
        if self.damagesafe <= 0:

            self.hitpoints -= damage

            self.cur_cel = 0

            #self.cur_anim = self.animdamaged

            # Debug
            #print "Enemy Damaged", self.hitpoints
            game_hud.mainhud.ehlvl = self.hitpoints
            game_hud.mainhud.pscore += damage

            # This if statment keeps the damage sound from playing when the entity is dead
            if self.hitpoints > 0:
                entsfxlist.play(self.soundid, self.snddamaged)

    def draw(self, screen):
        """Draw sprite to screen"""

        if self.offscreen:
            return

        screen.blit( self.tiles[ self.cel_list[ self.cur_anim ][ self.cur_cel ]  ], (self.screenrect[0], self.screenrect[1]) )

    def reset(self):
        """Reset the char"""

        # Note, NPC's are left dead on restarting the level, unless respawn is set
        if self.respawn:
            self.dead = 0
            self.dying = 0
        
        self.screenrect = self.begscreenrect[:]
        self.maprect = self.begmaprect[:]
        self.boundrect = self.begboundrect[:]
        
        self.hitpoints = self.basehitpoints
        self.dodamage = self.basedodamage

        # Since we are moronic enemy, we all turn left, unless dead or dying
        if not self.dead and not self.dying:
            self.left_on()

    def ai(self, player):
        """Perform ai functions for enemies"""

        if self.inanimate == 1:
            return

        if self.dying or self.dead:
            return

        # This is temp, so we can control the ai
        #self.aiflags = [ 0, 1, 0, 0, 0, 1 ]

        # Flags to tell other ai functions what we have done
        self.chasing = 0
        self.running = 0

        # Run distance pad
        pad = 10

        # Chase pad
        cpad = 60

        # At this point, the only thing the AI will do is cause the
        # character to move in one direction until it encounters a wall or drop off,
        # and then turn around and repeat the process.
        # By the time it is done, it should offer the ability to shoot at random intervals, 
        # or when the main character is in sight. It should also offer some inteligence in
        # terms of possibly trying to jump to block the player, and or shoot at the player on another level.
        # I do not currently have any plans to make it any smarter, i.e. following the char by jumping etc,
        # though there may be an option to have the ai go in one direction untill it runs into a wall or runs off of the level
        #
        # Notes: The enemy ai should cause the enemy to avoid damage, i.e. turn around rather than run into spikes
        # etc... It might also include the ability to try and avoid projectiles from the player.
        # There is alot to be done in this regard, so I don't know how much I will actually accomplish in this regard.

        # The above may be moot... The types defined below should allow for different ai actions
        # The type will eventually be loaded from the char data, note, this should be a flag list, much like tile data list
        # that way the types can be mixed
        #
        # Types as currently planned:
        # Nimrod = 0    : This enemey does nothing but run back and forth
        # Chaser = 1    : This is an enemy who turns and moves towards the character when they are within a certain distance
        # Runner = 2    : This is an enemy who runs away when within a certain distance of the player
        # Shooter = 3   : This is an enemy who shoots at random intervals
        # Jumper = 4    : This is an enemy who attempts to jump over things
        # Sitter = 5    : This is an enemy who just sits there, not running back and forth
        #
        # Notes on (Planned for) combinations: 
        # Sitter/Chaser: Waits until player is close, then runs twoards player
        # Shooter/Jumper: Trys to jump over things, and shoots at random intervals

        if self.aiflags[0]:
            # This is a nimrod, do nothing
            pass
        
        if self.aiflags[1]:
            # If player is near, turn towards him/her

            # Find out if the player is within reaction distance
            if player.boundrect[1] < (self.boundrect[1] - self.chasereactv):
                pass
            elif player.boundrect[3] > (self.boundrect[3] + self.chasereactv):
                pass
            elif player.boundrect[0] < (self.boundrect[0] - self.chasereacth):
                pass
            elif player.boundrect[2] > (self.boundrect[2] + self.chasereacth):
                pass
            else:

                # If the player is on the left
                if player.boundrect[0] < (self.boundrect[0] - cpad):
                    if not self.state['left'] or not self.state['moving_x']:
                        self.left_on()
    
                elif player.boundrect[2] > (self.boundrect[2] + cpad):
                    if not self.state['right'] or not self.state['moving_x']:
                        self.right_on()
                
                self.chasing = 1

        if self.aiflags[2]:
            # If player is near, run away from him/her
            # Basically an exact reverse of chaser

            # Find out if the player is within reaction distance
            if player.boundrect[1] < (self.boundrect[1] - self.runreactv):
                pass
            elif player.boundrect[3] > (self.boundrect[3] + self.runreactv):
                pass
            elif player.boundrect[0] < (self.boundrect[0] - self.runreacth):
                pass
            elif player.boundrect[2] > (self.boundrect[2] + self.runreacth):
                pass
            else:
                # If the player is on the left
                if player.boundrect[0] < (self.boundrect[0] - pad):
                    if not self.state['right'] or not self.state['moving_x']:
                        self.right_on()
    
                elif player.boundrect[2] > (self.boundrect[2] + pad):
                    if not self.state['left'] or not self.state['moving_x']:
                        self.left_on()

                self.running = 1

        if self.aiflags[3]:

            if self.fireinterval == 0:
                # Shoot at intervals
                self.addbullet()
                self.fireinterval = self.firebase
            else:
                self.fireinterval = self.fireinterval - 1

        if self.aiflags[4]:
            pass

        if self.aiflags[5]:
            if not self.chasing and not self.running:
                if self.state['up'] and self.state['moving_y']:
                    self.up_off()
                if self.state['down'] and self.state['moving_y']:
                    self.down_off()
                if self.state['left'] and self.state['moving_x']:
                    self.left_off()
                if self.state['right'] and self.state['moving_x']:
                    self.right_off()

class player(base_entity):

    # The starting hitpoints
    basehitpoints = 4

    # The actual hitpoints
    hitpoints = 4

    # The damage this char does
    dodamage = 1
    bulletdamage = 1

    upspeed = 10
    downspeed = 10
    leftspeed = 10
    rightspeed = 10

    # We may only have bulletlimit player bullet(s) onscreen at any time
    bulletlimit = 2

    # This is the index into which animation to play over char when safe
    animsafeleft    = 0
    animsaferight   = 0

    def load(self, filename):
        """Load a sprite file and populate data from said sprite file, cannot be used outside of init function"""

        # Create string containing full path to file
        fullname = os.path.join(config.datadir, filename)

        # Try to load the sprite file, and catch any exceptions
        try:

            f = file(fullname, 'r')
        except:
            print "Error loading " + `fullname` + "."
            return None

        # Record the name of the character data file
        self.myfile = filename

        # Get the image file attributes (numtiles=0, colorkey=-1, tilewidth=32, tileheight=32, top=0, left=0, bottom=0, right=0)
        self.imageattrib = cPickle.load(f)

        # Get the name of the image file (The second thing in the character file)
        self.image = cPickle.load(f)
        
        # Get the char name, used to get the soundid from the sound system
        self.name = cPickle.load(f)

        # Get the entity bounding box offset
        self.boundoffset = cPickle.load(f)

        # Get the animation cel frame list
        self.cel_list = cPickle.load(f)

        # Get the animation index list
        animindexlist = cPickle.load(f)

        # Get the ai flags
        self.aiflags = cPickle.load(f)
       
        # Get the prjectile offset 
        self.bulletxyoff = cPickle.load(f)

        # Get the projectile offset for knealing char
        self.bulletkneelxyoff = cPickle.load(f)

        # Get the data for hitpoints, speed, etc.
        dataflags = cPickle.load(f)

        f.close()

        # Record the width and height of sprite
        self.width = self.imageattrib['tilewidth']
        self.height = self.imageattrib['tileheight']

        # Load the image file data
        self.tiles = myimg.loadtiles(  self.image, \
                                       numtiles    = self.imageattrib['numtiles'], \
                                       colorkey    = self.imageattrib['colorkey'], \
                                       tilewidth   = self.imageattrib['tilewidth'], \
                                       tileheight  = self.imageattrib['tileheight'], \
                                       top         = self.imageattrib['top'], \
                                       left        = self.imageattrib['left'], \
                                       bottom      = self.imageattrib['bottom'], \
                                       right       = self.imageattrib['right'], \
                                       border      = 1 )

        # Init screen rect
        self.screenrect = [ 0, 0, self.width, self.height ]

        # Set width and height of screenrect
        self.screenrectwidth = self.width
        self.screenrectheight = self.height

        # Init bounding rect
        self.boundrect = [ 0, 0, 0, 0 ]

        # The boundoffset is initiially just the x/y offsets for each side
        # boundoffset[0] is how many pixels from the left side the bounding rect should be,
        # boundoffset[3] is how many pixels from the bottom the bounding rect should be, etc...
        # After init, it is the actual rect, and is updated in all of the update and move functions normally
        self.boundrect[0] = self.screenrect[0] + self.boundoffset[0]
        self.boundrect[1] = self.screenrect[1] + self.boundoffset[1]
        self.boundrect[2] = self.screenrect[2] - self.boundoffset[2]
        self.boundrect[3] = self.screenrect[3] - self.boundoffset[3]
 
        # Get width and height of bounding rect
        self.boundrectwidth = self.boundrect[2] - self.boundrect[0]
        self.boundrectheight = self.boundrect[3] - self.boundrect[1]

        # Init maprect
        self.maprect = self.screenrect[:]

        # Map rect is same width and height as sprite images
        self.maprectwidth = self.width
        self.maprectheight = self.height

        # Animation index info
        # These are indexes to which animation to play for each action.
        self.animrightstill     = animindexlist['rightstill']
        self.animrightmove      = animindexlist['rightmove']
        self.animleftstill      = animindexlist['leftstill']
        self.animleftmove       = animindexlist['leftmove']
        self.animupstill        = animindexlist['upstill']
        self.animupmove         = animindexlist['upmove']
        self.animdownstill      = animindexlist['downstill']
        self.animdownmove       = animindexlist['downmove']
        self.animjumpleft       = animindexlist['jumpleft']
        self.animjumpright      = animindexlist['jumpright']
        self.animfallleft       = animindexlist['fallleft']
        self.animfallright      = animindexlist['fallright']
        self.animdamagedleft    = animindexlist['damagedleft']
        self.animdamagedright   = animindexlist['damagedright']
        self.animslowedleft     = animindexlist['slowedleft']
        self.animslowedright    = animindexlist['slowedright']
        self.animkneelleft      = animindexlist['kneelleft']
        self.animkneelright     = animindexlist['kneelright']
        self.animdeathleft      = animindexlist['deathleft']
        self.animdeathright     = animindexlist['deathright']
        self.animsafeleft       = animindexlist['safeleft']
        self.animsaferight      = animindexlist['saferight']
        self.animkneelsafeleft  = animindexlist['kneelsafeleft']
        self.animkneelsaferight = animindexlist['kneelsaferight']

        # Now populate all of the hitpoint data etc.
        self.dodamage = dataflags['dodamage']
        self.basedodamage = dataflags['dodamage']
        self.bulletdamage = dataflags['bulletdamage']
        # The starting hitpoints
        self.basehitpoints = dataflags['basehitpoints']
        # The actual hitpoints
        self.hitpoints = dataflags['hitpoints']
        # Speed
        self.upspeed = dataflags['upspeed']
        self.downspeed = dataflags['downspeed']
        self.leftspeed = dataflags['leftspeed']
        self.rightspeed = dataflags['rightspeed']
        # We may only have bulletlimit player bullet(s) onscreen at any time
        self.bulletlimit = dataflags['bulletlimit']

        # Get our sound id from the sound system
        self.soundid = entsfxlist.getsndid(self.name)

        # Return success
        return (1)

    def draw(self, screen):
        """Draw sprite to screen"""

        if self.offscreen:
            return

        screen.blit( self.tiles[ self.cel_list[ self.cur_anim ][ self.cur_cel ]  ], (self.screenrect[0], self.screenrect[1]) )

        # This prevents damage safe from displaying at the end of a safe cycle, or if the safe is from
        # the landing during a jump
        if self.damagesafe > 10:
            if self.state['right']:

                if self.state['kneeling']:
                    screen.blit( self.tiles[ self.animkneelsaferight ], (self.screenrect[0], self.screenrect[1]) )
                else:
                    screen.blit( self.tiles[ self.animsaferight ], (self.screenrect[0], self.screenrect[1]) )

            elif self.state['left']:
                if self.state['kneeling']:
                    screen.blit( self.tiles[ self.animkneelsafeleft ], (self.screenrect[0], self.screenrect[1]) )
                else:
                    screen.blit( self.tiles[ self.animsafeleft ], (self.screenrect[0], self.screenrect[1]) )

    def ifcollidechar(self, overlaprect, otherchar, top, left):
        """Handle collision between main char and another char"""

        if self.dying or self.dead:
            return

        # If the "otherchar" is a bullet, take collision damage and destroy bullet
        if otherchar.id == 9:
            self.getdamage(otherchar.dodamage)
            otherchar.getdamage(10)
        # If the other char is inanimate
        elif otherchar.inanimate == 1:
            return
        # If collision is at the top, or sides, get damage, else if at the bottom, the other char will be taking damage
        elif not self.falling:
            self.getdamage(otherchar.dodamage)

            # Give the character a semi-permanent jump bonus (For the moment, it will be until the end of the level)
            if otherchar.jumpbonus:
                self.jumprate += otherchar.jumpbonus
                # Zero out the bonus so we don't get it twice
                otherchar.jumpbonus = 0

            # Give the character a permanent fire power bonus
            if otherchar.firebonus:
                self.bulletdamage += otherchar.firebonus
                self.dodamage += otherchar.firebonus
                # Zero out the bonus so we don't get it twice
                otherchar.firebonus = 0

            # Give the character a temporary invlunerabilty bonus
            if otherchar.invulnbonus:
                self.damagesafe = otherchar.invulnbonus
                # Zero out the bonus so we don't get it twice
                otherchar.invulnbonus = 0

            # If everdamage is set, we null the enemies damage payload at the first hit
            if otherchar.everdamage:
                otherchar.getdamage(self.dodamage)
                otherchar.dodamage = 0

        else:
            # This is why we have the jump key
            # if we move the jumpkey check to just above the jump_on below
            # then we will still do damage to the enemies even if the player
            # isn't jumping
            if self.falling and self.jumpkey:
                if not otherchar.everdamage:
                    otherchar.getdamage(self.dodamage)
                otherchar.cur_anim = 0
                otherchar.cur_cel = 0

                self.jump_on()
                self.falling = 0

                # Set the rate to jump, based on the other char's sponginess
                self.currentjump = otherchar.sponginess
                
                # Only add damage if not already greater than 10
                if self.damagesafe < 10:
                    self.damagesafe = 10

    def getdamage(self, damage=0):
        """Tally and resolve damage"""

        if self.dying or self.dead:
            return

        # If damagesafe is not on
        if self.damagesafe <= 0 and damage > 0:

            self.hitpoints -= damage

            if self.state['left']:
                self.cur_anim = self.animdamagedleft
            elif self.state['right']:
                self.cur_anim = self.animdamagedright

            self.cur_cel = 0

            # Debug
            #print "Damaged", self.hitpoints

            self.damagesafe = 30

            # This check prevents the damage sound from being played when the char is dead
            if self.hitpoints > 0:
                entsfxlist.play(self.soundid, self.snddamaged)

        # Else, it's heal, not damage
        elif damage < 0:

            self.hitpoints -= damage

    def reset(self):
        """Reset the player"""
        
        game_hud.mainhud.ehlvl = 0
        game_hud.mainhud.pscore = 0

        self.kneel_off()

        self.dead = 0
        self.dying = 0

        self.jumping = 0
        self.falling = 0

        # Debug
        #print self.screenrect, self.begscreenrect
        #print self.maprect, self.begmaprect
        #print self.boundrect, self.begboundrect

        self.screenrect = self.begscreenrect[:]
        self.maprect = self.begmaprect[:]
        self.boundrect = self.begboundrect[:]
#        self.screenrect = [32, 352, 128, 448]
#        self.maprect = [32, 864, 128, 960]
#        self.boundrect = [71, 876, 89, 960]
        
        self.hitpoints = self.basehitpoints

