"""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 pickle
import os

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

    # 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

    # Flags to determine whether sprite is falling/jumping/offscreen
    falling = 0
    jumping = 0
    offscreen = 0

    # Rate of fall
    fallrate = 20

    # Rate of jumping
    jumprate = 30
    currentjump = 0

    # Ammount of decay in jumping
    decay = 1
    currentdecay = 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

    # 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 = []

    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]
 

        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"""

        # Create string containing full path to file
        fullname = os.path.join('../data', 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 = pickle.load(f)

        # Get the name of the image file (The second thing in the character file)
        self.image = pickle.load(f)
        

        # Get the char name, used to get the soundid from the sound system
        self.name = pickle.load(f)

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

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

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

        # Get the ai flags
        self.aiflags = pickle.load(f)

        # Get the prjectile offset 
        self.bulletxyoff = pickle.load(f)

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

        # Get the damage that this char does
        damage = pickle.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

        # 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]) )

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

        # 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]


            # 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[2]:
                        self.adjposscr(x=overwidth)
                        self.adjposmap(x=overwidth)
                else:
                    if not tiledata[3]:
                        self.adjposscr(x=-overwidth)
                        self.adjposmap(x=-overwidth)
            else:
                # If overlaping rect is at top of self, move self down, else move self up (By colrect height)
                if top:
                    if not tiledata[4]:
                        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[1]:
                        self.falling = 0
                        self.adjposscr(y=-overheight)
                        self.adjposmap(y=-overheight)
                else:
                    if not tiledata[1]:
                        self.adjposscr(y=-overheight)
                        self.adjposmap(y=-overheight)

        if tiledata[5]:
            self.dodamage()

        if tiledata[6]:
            self.setslow()

    def dodamage(self):
        """Tally and resolve damage"""

        print "Damaged."

    def setslow(self):
        """Set a slowdown rate for char"""

        print "Slowed."

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

        if self.offscreen:
            return

        # 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 ( self.cur_cel < len(self.cel_list[self.cur_anim] ) - 1):
            self.cur_cel += 1
        else:
            self.cur_cel = 0
    
    def resolve(self):
        """Resolve velocities, forces, and flags on object into final value for x and y"""

        if self.offscreen:
            return

        # If the character is falling, set accelleration
        if self.falling == 1:
            self.yaccel = self.fallrate
        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
            # 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

    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"""

        #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 getclick(self, mouse=(0, 0)):
        """Determine whether or not a click has been recieved. Returns non-zero if click was recieved."""

        # If any of the mouse positions is outside of our object's screenrect, return, the click was not recieved
        if mouse[0] < self.screenrect[0]:
            return(0)
        elif mouse[0] > self.screenrect[2]:
            return(0)
        elif mouse[1] < self.screenrect[1]:
            return(0)
        elif mouse[1] > self.screenrect[3]:
            return(0)

        return (1)

class char(base_entity):

    state = { 'up':0, 'down':0, 'left':0, 'right':0 }

    def jump_on(self):
        #self.cur_anim = 0
        #self.cur_cel = 0
        self.jumping = 1

    def jump_off(self):
        #self.cur_anim = 0
        #self.cur_cel = 0
        self.jumping = 0

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

    def up_off(self):
        if self.state['up'] == 1:
            self.state['up'] = 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.cur_anim = 0
        self.cur_cel = 0
        self.xvel = 0
        self.yvel = 0
        self.yvel += 10

    def down_off(self):
        if self.state['down'] == 1:
            self.state['down'] = 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.cur_anim = 3
        self.cur_cel = 0
        self.xvel = 0
        self.yvel = 0
        self.xvel -= 10
       
    def left_off(self):
        if self.state['left'] == 1:
            self.state['left'] = 0
            self.cur_anim = 2
            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.cur_anim = 1
        self.cur_cel = 0
        self.xvel = 0
        self.yvel = 0
        self.xvel += 10

    def right_off(self):
        if self.state['right'] == 1:
            self.state['right'] = 0
            self.cur_anim = 0
            self.cur_cel = 0
            self.xvel = 0


    cel_list = [ (0, 0), (0, 1, 2, 3, 4, 5), (13, 13), (13, 12, 11, 10, 9, 8) ]
