from __future__ import division
from pygame.locals import *

import copy
import math
import pygame

import ai
import collision_response
import constants
import inventory
import physics
import random
import resources
import simdata
import ui
import vect



##############################################################################################################
class Image(pygame.sprite.Sprite):

    def __init__(self, world, location, image_string, direction=0):
        pygame.sprite.Sprite.__init__(self)

        self.world = world
        self.location = location
        self.direction = direction
        self.grid_ref = int(location[0]/self.world.grid.square_width -0.5), int(location[1]/self.world.grid.square_height -0.5)
        self.position = ui.screen_pos(self.world, self.location)
        self.load_image(self.world.images[image_string])
        self.drawn = False

        if image_string[0:3] == "mem":
            ## memorised sim image
            elevation = 999
            self.world.spriteGroups[-2].add(self)
        elif image_string == "cursor_image":
            elevation = 1000
            self.world.spriteGroups[-3].add(self)
        else:
            ## fog image
            elevation = 998
            self.world.spriteGroups[-2].add(self)
        self.elevation = elevation
        self.image_update()



    def load_image(self, image_data):
        """image attributes"""
        self.image_data = image_data
        self.image = image_data.scaled_image

    def image_update(self):
        #self.position = ui.screen_pos(self.world, self.location)
        #onscreen = ui.loc_on_screen(self.world, self.location)
        if ui.loc_on_screen(self.world, self.location):
            if not self.drawn:
                self.world.spriteGroups[self.elevation].add(self)
                self.drawn = True
            ## all images need to be updated
            ## rotate image
            self.direction %= 2*math.pi
            if not self.direction:
                self.image = self.image_data.scaled_image
            else:
                self.image = pygame.transform.rotate(self.image_data.scaled_image, self.direction*180/math.pi)
            self.rect = self.image.get_rect()
            ## place image on screen
            self.position = ui.screen_pos(self.world, self.location)
            self.rect.center = self.position
        else:
            if self.drawn:
                self.drawn = False
                self.world.spriteGroups[self.elevation].remove(self)

    def update(self):
        self.image_update()

##############################################################################################################
class Sim(pygame.sprite.Sprite):
    '''generic sprite that has generic methods for all sim sprites, probably need a cutdown version of this'''

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        pygame.sprite.Sprite.__init__(self)

        """world atttributes"""
        self.world = world
        self.sim_type = sim_type
        self.health = health
        self.avatar = avatar

        ## setup sim kinematics properties
        self.location = location
        self.position = ui.screen_pos(self.world, self.location)
        self.direction = direction
        self.speed = speed
        self.vel_dir = vel_dir

        self.acc = 0
        self.acc_dir = 0
        self.temp_acc = 0
        self.temp_acc_dir = 0
        self.physics_acc = 0
        self.physics_acc_dir = 0

        """sets up intial attributes for physics and opaqueness for grid"""
        simdata.load_physical_attributes(self)

        """ sets initial health """
        if self.health is None:
            self.health = self.max_health

        self.timers = set()

        self.initialise_sim_tiles()
        self.world.grid.add_sim(self)

        physics.get_cartesian(self)
        """keeps a list of screen positions for the current physics loop - useful for drawing trails"""
        self.physics_locations = [self.location]
        self.collide_group = []

        """places the image on screen"""
        self.drawn = False
        self.imagescale = 1
        self.imageUpdate()

        """ adds the sim to the game world for simulation """
        self.world.spriteGroups[-1].add(self)

        self.onfire = False
        self.timer_onfire = ui.timer(self.world, self, 0.5)

    def initialise_sim_tiles(self):
        """ adds a list for other viewable sims if sim can see, LOS mask, etc """
        if self.sight_radius:
            """ might want to make these dictionaries for ease of lookup when performing square calculations """
            self.sight_tiles = set() # these are tiles within our LOS, so they are potentially visible
            self.visible_tiles = set() # currently visible tiles
            self.shadowed_tiles = set() # currently shadowed tiles that otherwise would be visible
            self.opaque_tiles = set() # these tiles block sim sight, creating shadowed tiles
            self.memorised_tiles = set() # these tiles have been seen by the sim (potentially currently viewed)

            self.visible_sims = set() # set of sims in LOS_mask not in shadow
            self.memory = {} # here we remember what each square contains, the valiues in the dict are sets of sim_types


    def change_elevation(self, new_elevation = 0):
        if new_elevation is self.elevation:
            pass
        else:
            if self in self.world.spriteGroups[self.elevation]:
                self.world.spriteGroups[self.elevation].remove(self)
                self.world.spriteGroups[new_elevation].add(self)
            self.elevation = new_elevation

    def load_image(self, image_data):
        """image attributes"""
        self.image_data = image_data
        self.image = image_data.scaled_image

        self.radius = image_data.radius
        self.lengthx = image_data.lengthx
        self.lengthy = image_data.lengthy

    def turn(self):
        pass

    def update_acceleration(self):
        pass

    def checkInput(self):
        pass

    def imageUpdate(self):
        #self.position = ui.screen_pos(self.world, self.location)
        #onscreen = ui.pos_on_screen(self.world, self.position)
        #onscreen = ui.loc_on_screen(self.world, self.location)
        if ui.loc_on_screen(self.world, self.location) and self.grid_viewed:

            # rendered refers to drawing from grid
            if not self.drawn:
                if self.health >=0:
                    self.drawn = True
                    self.world.spriteGroups[self.elevation].add(self)

            # all images need to be updated
            # rotate image
            self.direction %= 2*math.pi
            self.scaled_image = self.image_data.scaled_image
            if not self.direction:
                self.image = self.scaled_image
            else:
                self.image = pygame.transform.rotate(self.scaled_image, self.direction*180/math.pi)
            self.rect = self.image.get_rect()
            # place image on screen
            self.position = ui.screen_pos(self.world, self.location)
            self.rect.center = self.position

        else:
            if self.drawn:
                self.drawn = False
                self.world.spriteGroups[self.elevation].remove(self)

    def collision(self):
        pass


    def horizontal_bar(self, bcolor, fcolor, offset, width, parameterisation):
        aw, ah = offset
        x,y = self.position
        w,h = aw*self.world.scale, ah*self.world.scale
        self.world.screen.fill(bcolor,[x-w,y-h,2*w,width])
        self.world.screen.fill(fcolor,[x-w,y-h,int(2*w*(parameterisation)),width])

    def vertical_bar(self, bcolor, fcolor, offset, width, parameterisation):
        aw, ah = offset
        x,y = self.position
        w,h = aw*self.world.scale, ah*self.world.scale
        self.world.screen.fill(bcolor,[x-w,y-h,width,2*h])
        self.world.screen.fill(fcolor,[x-w,y-h,width,int(2*h*(parameterisation))])

    def draw_bars(self):
        pass

    def draw_overlay(self):
        pass

    def damage(self, value, type = None):
        """ to figure out the damage to be applied to the sim """

        if type is None:
            self.health -= value
        if type is "melee":
            self.health -= value
        if type is "kinetic":
            self.health -= value/100
        if type is "burn":
            self.health -= value*self.attrs['thermo']['burn_damage']
            self.timer_onfire.set()

    def check_removal(self):
        pass

    def remove_sim(self):
        """ removes sim from world and attempts to unload all associated resources"""
        """ updates score """
        if self.world.player:
            if self.faction is "horde":
                self.world.num_zom -=1
                if self.world.player:
                    self.world.player.score += self.points
            elif self.faction is "survivors":
                self.world.num_hum -=1
                self.world.player.score += self.points

        """ sets various data so object wont be updated and will be destroyed - should not be necessary """
        self.physics_time_remaining = 0
        self.mobile = False
        if self.health >= 0:
            self.health = -1


        """ remove sim from grid """
        self.world.grid.remove_sim(self)
        """ unload sim tiles """
        if self.sight_radius > 0:
            self.world.grid.remove_sim_tiles(self)
        self.mobile = False

        """ remove all timers """

        for timer in self.timers.copy():
            self.world.timers.discard(timer)
            self.timers.discard(timer)

        """ remove from sprite groups """
        self.world.spriteGroups[self.elevation].remove(self)
        self.world.spriteGroups[-1].remove(self)
        self.kill()

    def check_world_boundary(self):
        boundary_down = boundary_up = boundary_left = boundary_right = False

        if self.location[1] < self.radius:
            boundary_down = True

        if self.location[0]  < self.radius:
            boundary_left = True

        if self.location[0] > self.world.dimensions[0] - self.radius:
            boundary_right = True

        if self.location[1] > self.world.dimensions[1] - self.radius :
            boundary_up = True

        if self.boundary_method is "stop":
            if boundary_left:
                self.location = (self.radius,self.location[1])
            if boundary_right:
                self.location = (self.world.dimensions[0] - self.radius,self.location[1])
            if boundary_down:
                self.location = (self.location[0],self.radius)
            if boundary_up:
                self.location = (self.location[0],self.world.dimensions[1] - self.radius)

        if self.boundary_method is "pass":
            pass

        if self.boundary_method is "kill":
            if boundary_down:
                self.velocity = (0,0)
                self.speed = 0
                self.location = (self.location[0],self.radius)
            if boundary_up:
                self.velocity = (0,0)
                self.speed = 0
                self.location = (self.location[0],self.world.dimensions[1] - self.radius)
            if boundary_left:
                self.velocity = (0,0)
                self.speed = 0
                self.location = (self.radius,self.location[1])
            if boundary_right:
                self.velocity = (0,0)
                self.speed = 0
                self.location = (self.world.dimensions[0] - self.radius,self.location[1])

        if self.boundary_method is "bounce":
            if boundary_down:
                self.velocity = (self.velocity[0],-self.velocity[1])
            if boundary_up:
                 self.velocity = (self.velocity[0],-self.velocity[1])
            if boundary_left:
                self.velocity = (-self.velocity[0],self.velocity[1])
            if boundary_right:
                self.velocity = (-self.velocity[0],self.velocity[1])
            physics.get_polar_speed(self)

        if self.boundary_method is "wrap":
            if boundary_left:
                self.location = (self.world.dimensions[0] - self.radius,self.location[1])
            if boundary_right:
                self.location = (self.radius,self.location[1])
            if boundary_down:
                self.location = (self.location[0],self.world.dimensions[1] - self.radius)
            if boundary_up:
                self.location = (self.location[0],self.radius)

    def check_onfire(self):
        if self.timer_onfire.check():
            self.onfire = False
        else:
            self.onfire = True

    def updateSim(self):
        if not self.world.paused:
            """performs all the updates all sprites need"""
            self.check_removal()
            if self.health >= 0:
                self.check_onfire()
                """ this condition added to stop "removed" things from updating """
                if self.avatar:
                    self.checkInput()
                if self.mobile:
                    physics.update_kinematics(self)
        #if self.health >= 0:
        """ we allow negative health image updates for "dead" effects when game is paused"""
        self.imageUpdate()

##############################################################################################################
class Weapon(Sim):

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)

        self.hold_fire = False
        self.health = 1
        self.update_user(None)
        self.discharge = False

        self.timer_recock = ui.timer(self.world, self, self.recock_time)
        self.timer_reload = ui.timer(self.world, self, self.reload_time)
        self.timer_aimcheck = ui.timer(self.world, self, self.recock_time)


    def check_firing_line(self):
        if self.timer_aimcheck.check() and self.clip_ammo > 0:
            """ this stops the survivor from checking each turn by adding a pause """
            self.timer_aimcheck.set()
            """set up checking balls """
            target_location = self.user.target_location
            ini_check_radius = 0.6
            step_distance = ini_check_radius*0.5
            recoil = self.recoil
            vect_to_target = vect.add(1,target_location,-1,self.user.location)
            unit_vect_to_target = vect.normalise(vect_to_target)
            dist_to_target = vect.distance_to(target_location,self.user.location)
            """ we ignore the last step location, becasue if there is a survivor there, they are dead anyway"""
            count_balls = int(dist_to_target/step_distance)
            firing_line =[]
            """ we ignore the first step location, to ignore us. """
            for i in xrange(count_balls):
                dist = (i+1)*step_distance*(1+i*recoil*2)
                if dist < dist_to_target:
                    radius = dist*recoil*2
                    check_location = vect.add(1,self.user.location,dist,unit_vect_to_target)
                    firing_line.append((check_location, radius))

            self.hold_fire = False

            check_fire_soln = True
            """ check each ball in term for friendly survivors """
            while check_fire_soln:
                for check_pair in firing_line:
                    """ draws balls where checks are performed """
                    location = check_pair[0]
                    check_radius = max(check_pair[1],ini_check_radius)

                    if self.world.draw_sprite_paths:
                        ball_position = ui.screen_pos(self.world,location)
                        pygame.draw.circle(self.world.screen, (0,0,0), ball_position, max(int(check_radius*self.world.PPM*self.world.scale),1), 1)

                    for object in self.user.visible_sims:#self.world.spriteGroups[-1]:
                        if object.sim_type is "survivor":# or (object.sim_type is "concrete_block" and not self.user.avatar):
                            if object is self.user:
                                pass
                            else:
                                if vect.distance_to(location, object.location) < check_radius:
                                    self.hold_fire = True
                                    check_fire_soln = False

                check_fire_soln = False

    def fire_gun(self):
        """ made so we can precisely control when gun is fired
        """
        self.discharge = True

    def discharge_round(self):
        if self.discharge:
            self.check_firing_line()
            if self.timer_recock.check() and self.clip_ammo > 0 and not self.hold_fire:

                vect_to_target = vect.add(1,self.user.target_location,-1,self.user.location)
                angle_to_target = math.atan2(vect_to_target[1], vect_to_target[0])
                """ find location to put muzzle flash """
                muzzle_location = vect.add(1,self.user.location,1,vect.normalise(vect_to_target,self.user.radius+self.length))
                #muzzle_flash_location = vect.add(1,self.user.location,1,vect.normalise(vect_to_target,self.user.radius+self.length))
                """ add bullets """
                for bullet in range(self.bullets):
                    recoil_angle = random.gauss(0,self.recoil)
                    self.world.bullet = self.world.add_sim(self.sim_type+"_bullet", muzzle_location, angle_to_target+recoil_angle, self.muzzle_velocity)

                resources.play_sound(self.sound_shoot, resources.attenuate_sound(self))
                """ make muzzle flash when fired """
                self.world.muzzle_flash = self.world.add_sim(self.sim_type+"_muzzle_flash", muzzle_location, angle_to_target+recoil_angle)

                self.timer_recock.set()

                self.clip_ammo -= 1
##                self.ammo -= 1
##                if self.user.ammo < 0:
##                    self.user.ammo = 0
                if self.clip_ammo <= 0:
                    self.reload_weapon()

                self.discharge = False

    def reload_weapon(self):
        ''' starts the reloading process '''
        ##print "started reloading"
        if self.timer_reload.check():
            ##print "good time to reload"
            if self.clip_ammo < self.clip_size:
                ##print "%s, clip_ammo %i, clip_size %i, ammo: %i" %(self.sim_type, self.clip_ammo, self.clip_size, self.ammo)
                if self.ammo>0:
                    self.transfer_ammo(self.clip_ammo*self.ammo_use)
                    self.clip_ammo = 0
                    self.timer_reload.set()
                    if self.sound_reload:
                        resources.play_sound(self.sound_reload, resources.attenuate_sound(self))

    def reload(self):
        if self.user.avatar and not (pygame.mouse.get_pressed()[0] or pygame.key.get_pressed()[pygame.K_LCTRL]) and self.firemode is "semi":
            self.timer_recock.set(0)
            self.timer_aimcheck.set(0)

        self.update_ammo()

        if self.ammo:
            if self.timer_reload.check() and not self.clip_ammo:
                self.clip_ammo = min(self.clip_size, self.ammo)
                # remove the clip_ammo from the ammo_stash
                self.transfer_ammo(-self.clip_ammo*self.ammo_use)

    def transfer_ammo(self, amount):
        stash = self.find_ammo_stash()
        if stash:
            stash.stack_count += amount

    def find_ammo_stash(self):
        ''' find the associated ammo stash
        will extend this to check is using "universal ammo"
        and if not the return the particular ammo type
        '''
        if 'ammo' in self.user.inv.list_types():
            return self.user.inv.list_items('ammo')[0]

    def check_ammo(self):
        stash = self.find_ammo_stash()
        if stash is None:
            ammo = 0
        else:
            ammo = stash.stack_count
        return ammo

    def update_ammo(self):
        self.ammo = int(self.check_ammo()/max(1,self.ammo_use))

    def collision(self):
        count = 0
        """ we break to stop the pickup being picked up by more than one sim at once - it can happen if many in collide group """
        while True:
            for object in self.collide_group:
                if object is not self:
                    # lets survivors pickup weapons
                    if object.sim_type is "survivor":
                        if self.sim_type in object.inv.list_items():
                            pass
                        else:
                            object.inv.add(self)
                            self.update_user(object, object.inv)
                            self.world.grid.remove_sim(self)
                            self.collidable = False
                            self.collide_group = []
                            if not object.weapon:
                                object.switch_weapon_to(self.sim_type)
                            break
                    """ push pickups away from walls, etc"""
                    if not object.mobile:
                        if object.collidable:
                            collision_response.repulse(self,object)
                            collision_response.circle_projection(object,self)
                    if object.sim_cat is "weapon":
                        collision_response.repulse(self,object)
                        #collision_response.circle_projection(object,self)
            break
        #if count > 1: print count

    def update_location(self):
        dir_vect = math.cos(self.user.direction), math.sin(self.user.direction)
        self.location = vect.add(1,self.user.location,self.user.radius, dir_vect)
        self.direction = self.user.direction
        self.world.grid.moveSim(self)

    def update_user(self, new_user = None, inventory = None):
        self.user = new_user
        if new_user:
            if inventory:
                self.grid_viewed = False
                self.motive = True
                self.mobile = False
                self.collidable = False
            else:
                self.load_image(self.world.images[self.sim_type+"_image"])
                self.change_elevation(2)
                self.grid_viewed = True
                self.motive = True
                self.mobile = False
                self.collidable = False
                if not self.clip_ammo:
                    self.timer_reload.set()
                else:
                    self.timer_reload.set(0)

        else:
            self.load_image(self.world.images["pickup_"+self.sim_type+"_image"])
            self.change_elevation(0)
            self.grid_viewed = True
            self.motive = False
            self.mobile = True
            self.collidable = True


    def check_removal(self):
        if self.health < 1:
            self.health = 1

    def update(self):
        if self.user:
            self.reload()
            self.update_location()
            self.discharge_round()
            self.discharge = False
        self.check_removal()
        self.updateSim()


##############################################################################################################

class Pistol(Weapon):
    """ We setup the Pistol as a specific weapon as it doesn't require ammo
    We may depecrate this later by moving to specific ammo types for specific weapons
    """
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Weapon.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def update_ammo(self):
        self.ammo = 10

##############################################################################################################

class LaserR(Weapon):
    """ We setup the Laser as a specific weapon as it generates its own ammo
    We may depecrate this later by moving to specific ammo types for specific weapons
    """
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Weapon.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def update_ammo(self):
        self.ammo = min(1000, self.ammo + 5*self.world.dt)

    def transfer_ammo(self, amount):
##        stash = None
##        if stash:
            self.ammo += amount

##############################################################################################################
class Biped(Sim):

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)


        self.inv = inventory.Inventory()

    def sprint(self):
        if self.sprinting and self.sprint_energy>0:
            self.speed_mod = 1+self.sprint_energy/self.max_sprint_energy
            self.sprint_energy -= self.world.dt*self.speed
        else:
            self.speed_mod = 1

        if self.sprint_energy < self.max_sprint_energy:
            self.sprint_energy = min(self.sprint_energy+self.world.dt*self.fitness, self.max_sprint_energy)


    def update_sim_acc(self):
        self.sim_acc = self.acc_profile(self)

    def turn(self):
        """get sprite to turn in right direction"""
        if not self.avatar:
            if self.target is None:
                self.turn_destination = self.destination
            else:
                 self.turn_destination = self.target.location
            distance = vect.distance_to(self.turn_destination,self.location)
            self.rads_to_dest = vect.angle_to(self.turn_destination,self.location)
            if distance <0.1:
                pass
            else:
                if math.fabs(self.direction-self.rads_to_dest) > math.pi:
                    self.delta_dir = -(self.direction-self.rads_to_dest)
                else:
                    self.delta_dir = (self.direction-self.rads_to_dest)
                sign = vect.sign(self.delta_dir)
                amount = min(math.fabs(self.delta_dir),self.turn_rate*self.world.dt)
                self.direction  -=  sign*amount
        else:
            mouse_loc = ui.world_loc(self.world, pygame.mouse.get_pos())
            self.direction = vect.angle_to(mouse_loc,self.location)

            if pygame.key.get_pressed()[pygame.K_i]:
                delta = self.turn_rate*self.world.dt
                self.key_turn(delta)

            if pygame.key.get_pressed()[pygame.K_p]:
                self.world.mouse_scroll = False
                delta = -self.turn_rate*self.world.dt
                self.key_turn(delta)

    def update_acceleration(self):
        self.update_sim_acc()
        if self.avatar:
            left = pygame.key.get_pressed()[pygame.K_a]
            right = pygame.key.get_pressed()[pygame.K_d]
            up = pygame.key.get_pressed()[pygame.K_w]
            down = pygame.key.get_pressed()[pygame.K_s]
            dest = vect.normalise((-left+right,up-down))
        else:
            dest = vect.normalise(vect.add(1,self.destination,-1,self.location))

        dir = math.cos(self.direction), math.sin(self.direction)
        dot = vect.dot(dest, dir)
        a = self.agility
        b = 1
        dir_mod = max((dot+a)/(b+a),0) #a, b PICKED TO GIVE THE RIGHT SORT OF BEHAVIOUR, EMPIRICALLY FOUND

        self.acceleration = vect.add(dir_mod*self.sim_acc, dest, 1, self.acceleration)
        physics.get_polar_acc(self)


    def key_turn(self, delta):
        self.world.mouse_scroll = False
        new_aim = math.cos(self.direction+delta), math.sin(self.direction+delta)
        new_loc = vect.add(1,self.location, 5, new_aim)
        new_pos = ui.screen_pos(self.world, new_loc)
        pygame.mouse.set_pos(new_pos)

        ui.center_screen(self)

    def collision(self):

        for object in self.collide_group:
            if object is not self:
                if object.collidable and not object.penetrating:
                    if (object.attrs['physics']['shape'] is "circle"):
                        collision_response.sphere_collide(self,object)
                    if object.sim_cat is "biped":
                        self.interact(object)
                        collision_response.repulse(self,object)
                    if not object.mobile:
                        collision_response.circle_projection(object,self)
##                        if object.attrs['physics']['shape'] is "circle":
##                            collision_response.circle_projection(object,self)
##                        elif object.attrs['physics']['shape'] is "square":
##                            collision_response.square_projection(self, object)
##                        else:
##                            print object, "needs shape"

    def interact(self,object):
        pass

    def leave_corpse(self, corpse):
        if corpse:
            corpse.attrs['physics']['mass'] = self.attrs['physics']['mass']
            corpse.acceleration = self.acceleration
            corpse.temp_acceleration = self.temp_acceleration
            physics.get_polar_acc(corpse)
            physics.get_polar_temp_acc(corpse)
            self.remove_sim()

    def draw_bars(self):
        if self.drawn:
            if 0 <= self.health < self.max_health:
                self.horizontal_bar((0,0,0), (255,0,0), (20,-26), 4, int(self.health/self.max_health*100)/100)

    def draw_path(self):
        if self.world.draw_sprite_paths:
            self_pos = ui.screen_pos(self.world, self.location)
            dest_pos = ui.screen_pos(self.world, self.destination)
            pygame.draw.line(self.world.screen,self.ai_colour,self_pos,dest_pos)

    def update(self):
        self.updateSim()

##########################################################################################################################
class Survivor(Biped):

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Biped.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)

        self.add_ai()

        self.infected = False

        self.target = None
        self.search_time = 0
        self.acc_mod = 1


        self.weapon = None
        self.weapons = {}
        self.ammo = 0


        self.grenades = {}
        self.grenades["grenade_concussion"] = [8,0]
        self.grenades["grenade_incendiary"] = [9,0]
        self.grenades["grenade_molotov"] = [10,0]
        self.grenade = "grenade_molotov"

        self.grenade_ammo = 0
        self.grenade_time = 0
        self.prime_grenade = 0

        self.timer_melee = ui.timer(self.world, self, self.melee_delay)


        if self.avatar:
            self.score = 0
            self.load_image(self.world.images["civilian_player_image"])
            self.indicator_color = (255,255,255)


    def add_ai(self):
        self.timer_ai = ui.timer(self.world, self, random.randint(4,8))
        self.hit_direction = None
        self.hit = False

        self.ai = {}

        self.destination = self.location
        self.ai["move"] = ai.StateMachine()
        self.ai["move"].add_state(ai.StateSoldierExploring(self))
        self.ai["move"].add_state(ai.StateSoldierResting(self))
        self.ai["move"].add_state(ai.StateSoldierRetreating(self))
        self.ai["move"].add_state(ai.StateSoldierCollecting(self))
        self.ai["move"].add_state(ai.StateSoldierPanicking(self))
        self.ai["move"].set_state("survivor_exploring")

        self.ai["weapon"] = ai.StateMachine()
        self.ai["weapon"].add_state(ai.StateSoldierWeaponNone(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponPistol(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponSMG(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponAR(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponSR(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponLR(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponGR(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponMG(self))
        self.ai["weapon"].add_state(ai.StateSoldierWeaponSG(self))
        self.ai["weapon"].set_state("None")

    def check_removal(self):
        if self.health <0:
            self.drop_ammo(self.ammo, self.location)
            self.drop_items()
            if self.infected:
                self.world.add_sim("zombie_normal", self.location)
                remains = None
                self.remove_sim()
            else:
                remains = self.world.add_sim("remains", self.location, self.direction, self.speed, self.vel_dir)
            self.leave_corpse(remains)

    def draw_overlay(self):
        ui.draw_screen_edge_indicator(self.world, self)
        self.draw_path()
        self.draw_bars()

    def draw_bars(self):
        if self.drawn:
            if 0 <= self.health < self.max_health:
                self.horizontal_bar((0,0,0), (255,0,0), (20,-26), 4, int(self.health/self.max_health*100)/100)
            if self.ammo > 0:
                self.horizontal_bar((0,0,0), (255,255,0), (20,26), 4, min(self.ammo,self.max_ammo)/self.max_ammo)
            if self.weapon:
                """ draw clip ammo and stuff"""
                if self.weapon.timer_reload.check():
                    self.horizontal_bar((0,0,0), (255,255,0), (20,32), 4, self.weapon.clip_ammo/self.weapon.clip_size)
                else:
                    self.horizontal_bar((0,0,0), (255,255,0), (20,32), 4, 1-(self.weapon.timer_reload.get()/self.weapon.reload_time))

    def switch_weapon_down(self):
        if self.weapon:
            start = self.weapon.weapon_num
            new_key = None
            temp_num = 0
            for item in self.inv.list_items():
                if item.sim_cat is 'weapon':
                    if temp_num < item.weapon_num < start:
                        temp_num = item.weapon_num
                        new_key = item.sim_type
            if new_key:
                self.switch_weapon_to(new_key)

    def switch_weapon_up(self):
        if self.weapon:
            start = self.weapon.weapon_num
            new_key = None
            temp_num = 100
            for item in self.inv.list_items():
                if item.sim_cat is 'weapon':
                    if start < item.weapon_num < temp_num:
                        temp_num = item.weapon_num
                        new_key = item.sim_type
            if new_key:
                self.switch_weapon_to(new_key)


    def switch_weapon_to(self, weapon_type = "Pistol"):
        if weapon_type in self.inv.list_types():
            #if self.weapons[weapon_type][0]>0:
                if self.weapon:
                    if self.weapon.sim_type != weapon_type:
                        self.weapon.update_user(self, self.inv)
                        self.weapon = self.inv.list_items(weapon_type)[0]
                        self.weapon.update_user(self)
                else:
                    self.weapon = self.inv.list_items(weapon_type)[0]
                    self.weapon.update_user(self)

    def shoot(self):
        if self.sprinting:
            self.prime_grenade = False
        else:
            if self.avatar:

                if (pygame.mouse.get_pressed()[2] or pygame.key.get_pressed()[pygame.K_LALT]) and not self.grenade_time:
                    self.grenade_aim()
                    self.prime_grenade = True

                if (not pygame.mouse.get_pressed()[2] and not pygame.key.get_pressed()[pygame.K_LALT]) and self.prime_grenade:
                    self.target_location = ui.world_loc(self.world, pygame.mouse.get_pos())
                    self.throw_grenade()
                    self.prime_grenade = False

            elif not self.avatar:
                if self.target is None:
                    pass
                else:
                    if self.weapon:
                        self.target_location = self.target.location
                        if vect.distance_to(self.target_location, self.location)>self.radius+self.weapon.length:
                            if math.fabs(vect.angle_to(self.target_location, self.location)-self.direction)<0.1:
                                self.weapon.fire_gun()
                    self.attack(self.target)

    def grenade_aim(self):
        scale = self.world.PPM*self.world.scale
        pygame.draw.circle(self.world.screen, (255,255,255), self.position, int(self.throw_radius*scale), 1)

    def throw_grenade(self):
        if not self.sprinting:
            if not self.grenade_time and self.grenade_ammo > 0:
                vect_to_target = vect.add(1,self.target_location,-1,self.location)
                dist_to_target = vect.length(vect_to_target)
                if  dist_to_target <= self.throw_radius:
                    radius = min(self.throw_radius, dist_to_target)
                    Destination = vect.add(1,self.location, radius, vect.normalise(vect_to_target))
                    angle_to_target = math.atan2(vect_to_target[1], vect_to_target[0])
                    grenade_location = vect.add(1,self.location,1,vect.normalise(vect_to_target,self.radius/2))
                    self.world.grenade = self.world.add_sim(self.grenade,  grenade_location, vel_dir = angle_to_target, health = None)
                    self.world.grenade.initialise_physics(Destination)
                    self.grenade_time += 0.1
                    self.grenade_ammo -=1

    def reload(self):
        for key in self.grenades.keys():
            self.grenades[key][1] = self.grenade_ammo
        if self.grenade_time > 0:
            self.grenade_time -=self.world.dt
            if self.grenade_time < 0:
                self.grenade_time = 0

    def pickup_weapon(self):
        pass

    def drop_items(self):
##        if self.weapon:
##            self.weapon.update_user(None)
##            self.inv.rem(item)
##            self.weapon = None
        for item in copy.copy(self.inv.list_items()):
            lx,ly = self.location
            location = lx+random.uniform(-1,1), ly+random.uniform(-1,1)
            self.drop_item(item, location)

    def share_inventory(self,other):
        if other.faction is 'survivors':
            if other.weapon is None:
                if len(self.weapons.keys())>1:
                    for key in self.weapons.keys():
                        if key is not self.weapon.sim_type:
                            self.drop_item(key, other.location)
                            break

    def drop_item(self, item, location):
        #new_weapon = self.world.add_sim(weapon_type, location, 0)
        if item.sim_cat is 'weapon':
            item.update_user(None)
        self.inv.rem(item)

    def drop_ammo(self, amount, location):
        if self.ammo > 0:
            sprite = self.world.add_sim("ammo", location)
            sprite.ammo = amount
            self.ammo -= amount

    def interact(self,object):
        self.attack(object)
        self.share_inventory(object)

    def attack(self, object):
        if object:
            if object.faction is "horde":
                if self.timer_melee.check():
                    vector = vect.add(1,object.location,-1,self.location)
                    if self.radius+object.radius+self.melee_range > vect.length(vector):
                        dir = math.cos(self.direction), math.sin(self.direction)
                        unit_vect = vect.normalise(vector)
                        if vect.dot(unit_vect, dir)>self.melee_arc:
                            self.timer_melee.set()
                            object.damage(self.melee_damage, "melee")
                            location = vect.add(1,self.location,self.radius, dir)
                            self.world.rip = self.world.add_sim("rip", location, self.direction)
                            self.world.blood = self.world.add_sim("blood", location, self.direction)
                            resources.play_sound(self.world.sounds["rip"], resources.attenuate_sound(self))
                            physics.temp_force(object, self.attrs['physics']['mass']*constants.GRAVITY*15, self.direction)

    def update(self):
        if not self.world.paused:
            if not self.avatar:
                for key in self.ai.keys():
                    self.ai[key].think()
            self.shoot()
            self.reload()
            self.sprint()
        self.updateSim()

##########################################################################################################################
class Zombie(Biped):

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Biped.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)

        self.target = None
        self.search_time = 0
        self.acc_mod = 1

        self.add_ai()
        self.timer_melee = ui.timer(self.world, self, self.melee_delay)

    def add_ai(self):
        self.timer_ai = ui.timer(self.world, self, random.randint(2,6))
        self.hit_direction = None
        self.hit = False

        self.ai = {}

        self.destination = self.location
        self.ai["move"] = ai.StateMachine()
        self.ai["move"].add_state(ai.StateZombieExploring(self))
        self.ai["move"].add_state(ai.StateZombieChasing(self))
        self.ai["move"].add_state(ai.StateZombieCollecting(self))
        self.ai["move"].add_state(ai.StateZombieResting(self))
        self.ai["move"].add_state(ai.StateZombiePanicking(self))
        self.ai["move"].set_state("zombie_exploring")

    def interact(self, object):
        self.attack(object)

    def attack(self,object):
        if object:
            if object.zombie_food > 0:
                if self.timer_melee.check():
                    vector = vect.add(1,object.location,-1,self.location)
                    if vect.length(vector) < self.radius+self.melee_range+object.radius:
                        dir = math.cos(self.direction), math.sin(self.direction)
                        unit_vect = vect.normalise(vector)
                        if vect.dot(unit_vect, dir)>self.melee_arc:
                            self.timer_melee.set()
                            object.damage(self.melee_damage, "melee")
                            location = vect.add(1,self.location,self.radius, dir)
                            self.world.rip = self.world.add_sim("rip", location, self.direction)
                            self.world.blood = self.world.add_sim("blood", location, self.direction)

                            if object.sim_cat is "pickup":
                                self.health = min(self.health+self.melee_damage, self.max_health)
                                if random.random()<0.2:
                                    resources.play_sound(self.world.sounds["rip_carcass"], resources.attenuate_sound(self))
                            else:
                                resources.play_sound(self.world.sounds["rip"], resources.attenuate_sound(self))
                            object.infected = True

    def check_removal(self):
        if self.health < 0:
            remains = self.world.add_sim(self.sim_type+"_corpse", self.location, self.direction, self.speed, self.vel_dir)
            self.leave_corpse(remains)

    def draw_overlay(self):
        if not self.world.num_hives or self.grid_viewed:
            ui.draw_screen_edge_indicator(self.world, self)
        self.draw_path()
        self.draw_bars()

    def update(self):
        if not self.world.paused:
            if not self.avatar:
                for key in self.ai.keys():
                    self.ai[key].think()
            self.sprint()
        self.updateSim()

##########################################################################################################################
class Bullet (Sim):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)


        self.speed *= random.gauss(1,0.1)
        self.vel_dir = self.direction
        physics.get_cartesian(self)
        self.orig_location = self.location

        self.currently_penetrating = False

        self.draw_locations = []

    def collision(self):
        blocking_objects = False
        for object in self.collide_group:
            if object is self:
                pass
            elif object.collidable and not object.penetrating:
                collision_response.bullet_strike(self,object)
                if object.block:
                    blocking_objects = True

        if not blocking_objects:
            ## the bullet must not be around any blocks, so if it was
            ## penetrating them it cannot be anymore
            self.currently_penetrating = False

    def draw_trail(self):
        """draw lines behind sprite for previous main loop"""
        if len(self.draw_locations)<2:
            pass
        else:
            draw_positions = []
            for location in self.draw_locations:
                draw_positions.append(ui.screen_pos(self.world, location))

            thickness = max(1,int(self.world.scale*self.trail_thickness))
            pygame.draw.lines(self.world.screen, self.color, False, draw_positions, thickness)


    def check_removal(self):
        if self.health < 0 or self.speed < 30:
            self.remove_sim()

    def update(self):
        """changes direction bullet image is facing"""
        self.direction = self.vel_dir
        self.updateSim()
        self.draw_trail()

##########################################################################################################################
class LaserBullet (Bullet):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Bullet.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

        self.vel_dir = self.direction
        physics.get_cartesian(self)
        self.orig_location = self.location

        self.currently_penetrating = False

    def collision(self):
        blocking_objects = False
        for object in self.collide_group:
            if object is self:
                pass
            elif object.collidable and not object.penetrating:
                collision_response.laser_strike(self,object)
                blocking_objects = True
            elif object.opaque:
                collision_response.laser_strike(self,object)

        if not blocking_objects:
            # the bullet must not be around any blocks, so if it was penetrating them
            # it cannot be anymore
            self.currently_penetrating = False
##############################################################################################################
class Grenade(Sim):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)


    def initialise_physics(self, destination):
        self.height = 1
        distance = vect.distance_to(self.location, destination)

        """ i've removed grenade aerodynamic physics, because I want an an
        analytically solvable solution for its trajectory """
        t = self.health
        g = constants.GRAVITY
        h = self.height
        self.vy = (3/8*g*t+math.sqrt(1/64*g**2*t**2+g*h/4))
        if not t:
            self.speed = 0
        else:
            self.speed = distance/t
        physics.get_cartesian(self)

    def find_height(self):
        if self.height > 0:
            self.vy = self.vy - (constants.GRAVITY +self.attrs['physics']['drag_term_2']*self.vy**2*vect.sign(self.vy) / self.attrs['physics']['mass']) * self.world.dt
            self.height =  self.height + self.vy * self.world.dt
        else:
            self.height = 0
            self.friction_0 = 100*constants.GRAVITY
            self.friction_1 = 100.9*constants.GRAVITY
            self.change_elevation(0)
            self.speed = 0
            physics.get_cartesian(self)
        self.imagescale = 1+self.height/4

    def scale_image(self, scale):
        if self.drawn:
            self.image = pygame.transform.scale(self.image, (int(self.rect.width*scale),int(self.rect.height*scale)))
            self.rect = self.image.get_rect()
            self.rect.center = self.position

    def explode(self):
        pass

    def collision(self):
        if self.height < 1:
            self.colliding = True
            self.penetrating = False
            for object in self.collide_group:
                if object.collidable:
                    if object is self:
                        pass
                    elif object.mobile:
                        collision_response.sphere_collide(self,object)
                        collision_response.repulse(self,object)
                    elif not object.mobile:
                        collision_response.sphere_collide(self,object)
                        collision_response.circle_projection(object,self)

    def update(self):
        self.find_height()
        self.draw_bars()
        if self.health < 0:
            self.explode()
        if self.speed != 0:
            self.direction+=10*self.world.dt
        self.updateSim()
        self.scale_image(self.imagescale)
        self.health -= self.decay_rate*self.world.dt

##############################################################################################################
class GrenadeConcussion(Grenade):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False, destination = (0,0)):
        Grenade.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def explode(self):
        self.world.Shockwave.add_cell(self.grid_location, [constants.CONCUSSION_PRESSURE,0,0])
        self.sound_explosion.play()
        self.remove_sim()

##############################################################################################################
class GrenadeIncendiary(Grenade):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False, destination = (0,0)):
        Grenade.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def explode(self):
        self.world.Heat.add_cell(self.grid_location, constants.INCENDIARY_TEMP)
        self.sound_explosion.play()
        self.remove_sim()

##############################################################################################################
class GrenadeMolotov(Grenade):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False, destination = (0,0)):
        Grenade.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def explode(self):

        location = vect.add(1,self.grid_location,1,(0.5,0.5))
        self.world.add_sim("petrol", location)
        self.world.Heat.add_cell(self.grid_location, 10**2)

        count = 0
        while count<9:
        #for i in xrange(1,9):
            offset = 0.5+random.uniform(-2,2),0.5+random.uniform(-2,2)
            location = vect.add(1,self.grid_location,1,offset)
            sim = self.world.add_sim("petrol", location)
            if sim:
                count+=1

        self.sound_explosion.play()
        self.remove_sim()

##############################################################################################################
class Effect(Sim):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def check_removal(self):
        if self.health < 0:
            self.remove_sim()

    def update(self):
        self.updateSim()
        self.health -= self.decay_rate*self.world.dt

##############################################################################################################
class Pickup(Sim):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def check_pickup(self):
        pass

    def collision(self):
        for object in self.collide_group:
            if object != self:
                self.check_pickup()
                # push pickups away from walls, etc
                if not object.mobile:
                    if object.collidable and not object.penetrating:
                        collision_response.sphere_collide(object,self)
                        collision_response.repulse(self,object)
                        collision_response.square_projection(object,self)

    def check_removal(self):
        if self.health < 0:
            if self.decay_rate>=0:
                self.remove_sim()
            else:
                self.health = 1
        if self.health > self.max_health:
            self.health = self.max_health

    def update(self):
        #self.collision()
        self.health -= self.decay_rate*self.world.dt
        self.updateSim()

##############################################################################################################
class Medkit(Pickup):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Pickup.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def check_pickup(self):
        for object in self.collide_group:
            if object is not self:
                if object.faction is "survivors":
                    if object.health < object.max_health:
                        transfer_health = min(object.max_health/10*self.world.dt, object.max_health-object.health, self.stack_count)
                        object.health += transfer_health
                        self.stack_count -= transfer_health
                        if self.stack_count <= 0:
                            self.remove_sim()

##############################################################################################################
class Ammo(Pickup):
    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Pickup.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def check_pickup(self):
        for object in self.collide_group:
            if object is not self:
                if object.faction is "survivors":
                    if object.ammo < object.max_ammo or object.grenade_ammo < object.max_grenades:

                        if self.sim_type in object.inv.list_types():
                            stash = object.inv.list_items(self.sim_type)[0]
                            max_left = stash.stack_max - stash.stack_count
                        else:
                            max_left = self.stack_count

                        transfer_ammo = min(100*self.world.dt, max_left, self.stack_count)
                        #object.ammo += transfer_ammo
                        object.inv.add(self,transfer_ammo)
                        self.stack_count -= transfer_ammo
                        object.grenade_ammo = min(object.grenade_ammo+1, object.max_grenades)
                        if self.stack_count <= 0:
                            self.remove_sim()

##############################################################################################################
class Block(Sim):

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Sim.__init__(self, world, sim_type, location, direction, speed, vel_dir, health, avatar)

    def draw_overlay(self):
        if self.world.draw_sprite_paths:
            self.draw_bars()

    def check_removal(self):
        if self.health < 0:
            self.remove_sim()

    def update(self):
        self.updateSim()

##############################################################################################################
class ZombieHive(Block):

    def __init__(self, world, sim_type, location = (0,0), direction = 0, speed = 0, vel_dir = 0, health = None, avatar = False):
        Block.__init__(self,world, sim_type, location, direction, speed, vel_dir, health, avatar)

        self.timer_spawn = ui.timer(self.world, self, random.uniform(0,5))

    def make_zombies(self):
        if self.timer_spawn.check():
            #print "making zombies"
            zombie_count = 0
            while zombie_count < random.randint(1,8) and self.world.num_zom < constants.NUM_ZOMBIES:
                self.make_zombie()
                zombie_count+=1
                self.world.sounds["make_zombie"].play()
            self.timer_spawn.set(random.uniform(zombie_count/2.0,zombie_count*2))

    def make_zombie(self):
        rand = random.random()
        a = 0.05
        b = 0.3
        if 0<= rand < a:
            sim_type = "zombie_massive"
        elif a<= rand < b:
            sim_type = "zombie_fast"
        elif b <= rand <=1:
            sim_type = "zombie_normal"
        while True:
            for sq_ref in self.world.grid.squares[self.grid_location].face_squares:
                sim = self.world.add_sim_partition_square(self.world.Partitions.max_partition, sq_ref, sim_type)
                if sim:
                    break
            break

    def draw_bars(self):
        if self.drawn:
            if 0 <= self.health < self.max_health:
                self.horizontal_bar((0,0,0), (255,0,0), (20,-26), 4, int(self.health/self.max_health*100)/100)

    def draw_overlay(self):
        self.draw_bars()

    def check_removal(self):
        if self.health < 0:
            self.remove_sim()
            self.world.num_hives -=1

    def update(self):
        self.make_zombies()
        self.updateSim()

##############################################################################################################
class TextBox (pygame.sprite.Sprite):

    def __init__(self, world, pos, placement = "topleft", color=(255,255,255), font_size = 20, dynamic = 0):
        pygame.sprite.Sprite.__init__(self)
        self.world = world
        self.font_size = font_size
        self.dynamic = dynamic
        self.font = pygame.font.Font("./res/freesansbold.ttf", self.font_size)
        self.text = ""
        self.pos = pos
        self.color = color
        self.sim_type = None
        self.placement = placement

        self.world.spriteGroups[-3].add(self)
        self.world.spriteGroups[1000].add(self)

    def dynamic_update(self):
        if self.dynamic:
            #self.font = pygame.font.Font("./res/freesansbold.ttf", self.font_size*self.world.scale*self.world.PPM)
            self.center = ui.screen_pos(self.world, self.location)
            x,y = self.location
            grid_location = int(x/self.world.grid.square_width),int(y/self.world.grid.square_height)
            square = self.world.grid.squares[grid_location]
            self.text = "%.0f" % (len(square.sims_sighting_tile))

    def update(self):
        self.dynamic_update()
        self.image = self.font.render(self.text,1,self.color)
        self.rect = self.image.get_rect()

        if self.placement is "topleft":
            self.rect.topleft = self.pos
        elif self.placement is "center":
            self.rect.center = self.pos

