Example #1
0
    def __init__(self, player_file):
        self.file = player_file
        # Stats
        self.stats = Stats(STATS, defaults=DEF_PLAYER)
        # Stats object for held weapon/tool
        self.item_stats = Stats(TOOL_STATS)
        self.stats.add_stats(self.item_stats)
        # Inventory
        self.inventory = PlayerInventory(self)
        self.item_used, self.use_time = None, 0
        self.used_left = True
        self.first_swing = True
        # Player image
        self.surface = c.scale_to_fit(
            pg.image.load("res/player/player_pig.png"), w=1.5 * BLOCK_W)
        # Rectangle and block dimensions
        self.rect = pg.Rect((0, 0), self.surface.get_size())
        self.dim = [1.5, self.rect.h / BLOCK_W]
        # Player sprite for world map
        self.sprite = c.scale_to_fit(self.surface, w=c.SPRITE_W, h=c.SPRITE_W)
        # Arm sprite
        self.arm = pg.Surface((int(self.rect.w / 4), int(self.rect.h / 3)))
        self.arm.fill((200, 128, 0))
        # This determines the area in which items are collected
        self.collection_range = Rect(0, 0, 6 * BLOCK_W, 6 * BLOCK_W)
        # This determines the area that you can place blocks
        self.placement_range = Rect(0, 0, 7 * BLOCK_W, 7 * BLOCK_W)
        # Physics variables
        self.pos = [0, 0]
        self.v = [0., 0.]
        self.a = [0., 20.]
        # Collisions  in x and y
        self.collisions = [0, 0]
        # Distance fallen
        self.fall_dist = 0
        # Attack immunity
        self.immunity = 0
        # Respawn counter
        self.respawn_counter = 0
        # Whether we can move or not
        self.can_move = True
        # Stores an active ui and position of source block if applicable
        self.active_ui = None
        self.active_block = [0, 0]
        self.active_ui_pos = [0, 0]
        self.dragging_ui = False
        # Crafting UI
        self.crafting_ui = CraftingUI(self)
        self.crafting_open = True
        # If the world map is open or not
        self.map_open = False
        # Map object
        self.map = None

        self.set_pos((0, 0))
Example #2
0
 def __init__(self):
     super().__init__(name="Cat",
                      w=3,
                      img=MOB + "cat.png",
                      rarity=1,
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=15,
                                  max_speedx=2,
                                  jump_speed=9))
Example #3
0
 def __init__(self):
     super().__init__(name="Zombie",
                      w=1.5,
                      aggressive=True,
                      img=MOB + "zombie.png",
                      rarity=2,
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=50,
                                  damage=40,
                                  defense=5))
Example #4
0
    def __init__(self,
                 name="No Name",
                 w=1,
                 aggressive=False,
                 rarity=1,
                 img="",
                 sprite="",
                 stats=Stats(ENEMY_STATS)):
        self.rarity = rarity
        # Stores entity info
        self.name = name
        self.stats = stats
        self.aggressive = aggressive
        # Toggles gravity's affect on this entity
        self.zero_gravity = False
        # Toggles if this entity interacts with blocks
        self.hits_blocks = True
        # Toggles knockback immunity
        self.no_knockback = False
        # Is a boss
        self.boss = False

        # Is there drag
        self.drag = False

        # Movement variables
        self.pos = [0., 0.]
        self.v = [0., 0.]
        self.a = [0., 20.]
        # Direction of image
        self.direction = math.copysign(1, self.v[0])
        self.time = 0
        self.immunity = 0
        self.collisions = [0, 0]

        # Load image
        if isfile(img):
            self.set_image(scale_to_fit(pg.image.load(img), w=w * BLOCK_W))
        else:
            self.img = pg.Surface((int(w * BLOCK_W), int(w * BLOCK_W)))
        self.dim = (w, self.img.get_size()[1] / BLOCK_W)
        # Load map sprite
        if sprite == "":
            self.sprite = None
        elif isfile(sprite) and (sprite.endswith(".png")
                                 or sprite.endswith(".jpg")):
            self.sprite = scale_to_fit(pg.image.load(sprite),
                                       w=SPRITE_W,
                                       h=SPRITE_W)
        else:
            self.sprite = pg.Surface((SPRITE_W, SPRITE_W))
        # Hit box
        self.rect = pg.Rect((0, 0), self.img.get_size())
Example #5
0
 def __init__(self):
     super().__init__(name="Doom Bunny",
                      w=1,
                      aggressive=True,
                      img=MOB + "doom_bunny.png",
                      rarity=3,
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=5,
                                  damage=100,
                                  defense=1,
                                  jump_speed=15,
                                  maxspeedx=7))
Example #6
0
 def __init__(self):
     super().__init__(name="Birdie",
                      w=.75,
                      aggressive=False,
                      img=MOB + "birdie.png",
                      rarity=1,
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=5,
                                  max_speedx=5,
                                  max_speedy=5,
                                  acceleration=5,
                                  jump_speed=10))
Example #7
0
 def __init__(self):
     super().__init__(name="Helicopter",
                      w=1.5,
                      aggressive=True,
                      img=MOB + "helicopter.png",
                      rarity=1,
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=5,
                                  damage=25,
                                  defense=1,
                                  max_speedx=7,
                                  max_speedy=7,
                                  acceleration=5))
Example #8
0
 def __init__(self, element=MagicContainer.NONE, level=1):
     element_name = MagicContainer.ELEMENT_NAMES[element]
     super().__init__(name="%s Mage" % element_name,
                      w=1.5,
                      img="%s%s_mage.png" % (MOB, element_name),
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=100,
                                  max_speedx=1))
     self.element = element
     self.level = level
     self.magic = 0
     self.target = (-1, -1)
     self.transfer_cooldown = 0
 def __init__(self,
              idx,
              upgrade_tree,
              stats=Stats(WEAPON_STATS),
              projectiles=(),
              **kwargs):
     super().__init__(idx, upgrade_tree, **kwargs)
     self.swing = True
     self.is_weapon = True
     self.stats = stats
     self.projectiles = projectiles
     self.max_stack = 1
     # Get inventory image
     from pygame.transform import scale, rotate
     from Tools.constants import INV_IMG_W
     self.inv_img = scale(rotate(self.image, 45), (INV_IMG_W, INV_IMG_W))
Example #10
0
 def __init__(self):
     super().__init__(name="Main Boss",
                      aggressive=True,
                      w=3,
                      rarity=3,
                      img=MOB + "main_boss/shadow_dude_0.png",
                      sprite=MOB + "main_boss/shadow_dude_0.png",
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=2,
                                  damage=100,
                                  defense=25,
                                  max_speedy=30))
     self.no_knockback = True
     self.stage = self.jump_count = self.launch_angle = 0
     self.launch_target = [0, 0]
     # Start in launch mode
     self.launch()
Example #11
0
 def __init__(self):
     super().__init__(name="Dragon",
                      aggressive=True,
                      w=5,
                      rarity=3,
                      img=MOB + "dragon/dragon_0.png",
                      sprite=MOB + "dragon/dragon_0.png",
                      stats=Stats(ENEMY_STATS,
                                  defaults=DEF_MOB,
                                  hp=100,
                                  damage=65,
                                  defense=10,
                                  max_speedx=15,
                                  max_speed_y=15))
     self.rising_anim = OscillateAnimation(folder=MOB + "dragon/",
                                           dim=self.img.get_size(),
                                           delay=.1)
     self.attacking_img = scale_to_fit(pg.image.load(MOB +
                                                     "dragon_attack.png"),
                                       w=5 * BLOCK_W)
     self.zero_gravity = True
     self.hits_blocks = False
     self.no_knockback = True
     self.stage = 0
Example #12
0
 def __init__(self, player):
     super().__init__(self.DIM, max_stack=1)
     # These stat objects are created ONCE
     self.stats = [Stats(STATS) for i in range(len(self.ARMOR_ORDER))]
     for s in self.stats:
         player.stats.add_stats(s)
Example #13
0
class Player:
    def __init__(self, player_file):
        self.file = player_file
        # Stats
        self.stats = Stats(STATS, defaults=DEF_PLAYER)
        # Stats object for held weapon/tool
        self.item_stats = Stats(TOOL_STATS)
        self.stats.add_stats(self.item_stats)
        # Inventory
        self.inventory = PlayerInventory(self)
        self.item_used, self.use_time = None, 0
        self.used_left = True
        self.first_swing = True
        # Player image
        self.surface = c.scale_to_fit(
            pg.image.load("res/player/player_pig.png"), w=1.5 * BLOCK_W)
        # Rectangle and block dimensions
        self.rect = pg.Rect((0, 0), self.surface.get_size())
        self.dim = [1.5, self.rect.h / BLOCK_W]
        # Player sprite for world map
        self.sprite = c.scale_to_fit(self.surface, w=c.SPRITE_W, h=c.SPRITE_W)
        # Arm sprite
        self.arm = pg.Surface((int(self.rect.w / 4), int(self.rect.h / 3)))
        self.arm.fill((200, 128, 0))
        # This determines the area in which items are collected
        self.collection_range = Rect(0, 0, 6 * BLOCK_W, 6 * BLOCK_W)
        # This determines the area that you can place blocks
        self.placement_range = Rect(0, 0, 7 * BLOCK_W, 7 * BLOCK_W)
        # Physics variables
        self.pos = [0, 0]
        self.v = [0., 0.]
        self.a = [0., 20.]
        # Collisions  in x and y
        self.collisions = [0, 0]
        # Distance fallen
        self.fall_dist = 0
        # Attack immunity
        self.immunity = 0
        # Respawn counter
        self.respawn_counter = 0
        # Whether we can move or not
        self.can_move = True
        # Stores an active ui and position of source block if applicable
        self.active_ui = None
        self.active_block = [0, 0]
        self.active_ui_pos = [0, 0]
        self.dragging_ui = False
        # Crafting UI
        self.crafting_ui = CraftingUI(self)
        self.crafting_open = True
        # If the world map is open or not
        self.map_open = False
        # Map object
        self.map = None

        self.set_pos((0, 0))

    @property
    def immune(self):
        return self.immunity > 0 or self.respawn_counter > 0

    def load(self):
        with open(self.file.full_file, "rb+") as data_file:
            data = data_file.read()
            self.inventory.load(data)

    def write(self):
        # See load() for info order
        with open(self.file.full_file, "wb+") as file:
            file.write(self.inventory.write())

    def set_map_source(self, surface):
        self.map = Map(surface)
        self.map.set_center(self.rect.center)

    # Interprets events during normal gameplay
    def run_main(self, events):
        # Update mouse position
        pos = pg.mouse.get_pos()

        # If we are dead, decrement respawn counter
        if self.respawn_counter > 0:
            self.respawn_counter -= game_vars.dt
            if self.respawn_counter <= 0:
                self.respawn()
        # Otherwise interpret events
        else:
            mouse = list(pg.mouse.get_pressed())
            keys = list(pg.key.get_pressed())
            mods = pg.key.get_mods()

            # Open the crafting ui if the inventory is open, crafting should be open,
            # and there is no current ui
            if self.inventory.open and self.crafting_open:
                if not self.active_ui:
                    self.set_active_ui(self.crafting_ui)
            # Close the crafting ui if it is open and the inventory is closed of it
            # should not be open
            elif self.active_ui is self.crafting_ui:
                self.set_active_ui(None)

            # Send events to the active ui
            if self.active_ui:
                self.active_ui.tick()
                if self.dragging_ui:
                    # Check if we are done dragging
                    if not mouse[BUTTON_RIGHT - 1]:
                        self.dragging_ui = False
                    else:
                        w, h = self.active_ui.rect.size
                        # Move x
                        x = self.active_ui_pos[
                            0] * c.screen_w + game_vars.d_mouse[0]
                        x = max(w // 2, min(x, c.screen_w - w // 2))
                        self.active_ui_pos[0] = x / c.screen_w
                        self.active_ui.rect.centerx = x
                        # Move y
                        y = self.active_ui_pos[
                            1] * c.screen_h + game_vars.d_mouse[1]
                        y = max(h // 2, min(y, c.screen_h - h // 2))
                        self.active_ui_pos[1] = y / c.screen_h
                        self.active_ui.rect.centery = y
                else:
                    self.active_ui.process_events(events, mouse, keys)

            # Process events
            for e in events:
                if self.use_time <= 0 and e.type == MOUSEBUTTONUP:
                    # Scroll the inventory
                    if e.button == BUTTON_WHEELUP:
                        self.inventory.scroll(True)
                    elif e.button == BUTTON_WHEELDOWN:
                        self.inventory.scroll(False)
                elif e.type == MOUSEBUTTONDOWN and e.button == BUTTON_RIGHT:
                    if mods & KMOD_SHIFT and self.active_ui and self.active_ui.can_drag and \
                            self.active_ui.rect.collidepoint(*pos):
                        self.dragging_ui = True
                elif e.type == KEYDOWN:
                    # Try to jump
                    if e.key == K_SPACE and self.can_move:
                        if game_vars.touching_blocks_y(self.pos, self.dim,
                                                       False):
                            self.v[1] = -15
                    # Close current active ui
                    if e.key == K_ESCAPE and self.active_ui and self.active_ui != self.crafting_ui:
                        self.active_ui.on_exit()
                        self.active_ui = None
                    # Inventory buttons
                    elif self.use_time <= 0:
                        self.inventory.key_pressed(e.key)
                elif e.type == KEYUP:
                    # Open map
                    if e.key == K_m:
                        self.map_open = True
                        self.map.set_center(
                            [p / BLOCK_W for p in self.rect.center])
                        self.map.zoom = 1

            # Check keys
            if keys[K_RIGHT]:
                self.map.zoom += game_vars.dt * 10
                if self.map.zoom > 5:
                    self.map.zoom = 5
            if keys[K_LEFT]:
                self.map.zoom -= game_vars.dt * 10
                if self.map.zoom < 1:
                    self.map.zoom = 1

            # No item in use and not clicking tile ui
            if self.use_time <= 0 and (
                    self.active_ui is None
                    or not self.active_ui.rect.collidepoint(*pos)):
                # Mouse click events
                if mouse[BUTTON_LEFT - 1]:
                    self.left_click()
                else:
                    self.first_swing = True
                    if mouse[BUTTON_RIGHT - 1]:
                        self.right_click()
                    else:
                        self.inventory.holding_r = 0

            # If we are using an item, let it handle the use time
            if self.item_used is not None:
                self.item_used.on_tick()
                # Check if we are done using the item
                if self.use_time <= 0:
                    self.item_used = None
            # Otherwise decrement the use time
            elif self.use_time >= 0:
                self.use_time -= game_vars.dt

            # Check if we can move
            if self.can_move:
                acc = self.stats.get_stat("acceleration")
                if keys[K_a] and not keys[K_d]:
                    self.a[0] = -acc
                elif keys[K_d] and not keys[K_a]:
                    self.a[0] = acc
                if keys[K_SPACE] and self.collisions[1] == 1:
                    self.v[1] = -self.stats.get_stat("jump_speed")
                self.move()

    # Draws pre-ui visuals
    def draw_pre_ui(self, rect):
        pos = pg.mouse.get_pos()
        global_pos = [pos[0] + rect.x, pos[1] + rect.y]

        display = pg.display.get_surface()
        # Draw shadow of placeable item if applicable
        if self.placement_range.collidepoint(*global_pos):
            item = self.inventory.get_current_item()
            if item.is_item and game_vars.items[item.item_id].placeable:
                tile = game_vars.tiles[game_vars.items[item.item_id].block_id]
                img = tile.image.copy().convert_alpha()
                img.fill((255, 255, 255, 128), None, BLEND_RGBA_MULT)
                # Find top left of current block in px
                img_pos = [(int(m / BLOCK_W) * BLOCK_W) - p
                           for m, p in zip(global_pos, rect.topleft)]
                display.blit(img, img_pos)

        # Draw player
        display.blit(self.surface,
                     (self.pos[0] - rect.x, self.pos[1] - rect.y))

        # Draw item being used
        if self.item_used is not None:
            center = [self.rect.centerx - rect.x, self.rect.centery - rect.y]
            self.item_used.use_anim(self.use_time, self.arm, self.used_left,
                                    center, rect)

    # Draws ui
    def draw_ui(self, rect):
        display = pg.display.get_surface()
        pos = pg.mouse.get_pos()

        # Draw inventory
        self.inventory.draw(pos)

        # Draw block ui
        if self.active_ui is not None:
            self.active_ui.draw()

        # Draw other UI
        life_text = "{} / {} HP".format(self.stats.hp,
                                        self.stats.get_stat("hp"))
        # Draw stats
        text = c.ui_font.render(life_text, 1, (255, 255, 255))
        text_rect = text.get_rect()
        text_rect.right = rect.w
        display.blit(text, text_rect)
        # TODO: Stats

        # Get minimap
        self.map.set_center([p / BLOCK_W for p in self.rect.center])
        self.map.draw_map(
            pg.Rect(c.screen_w - c.MAP_W, text_rect.h, c.MAP_W, c.MAP_W))

        # Draw selected item under cursor if there is one
        item = self.inventory.get_held_item()
        if item.is_item:
            img_rect = pg.Rect(0, 0, c.INV_IMG_W, c.INV_IMG_W)
            img_rect.center = pos
            img = game_vars.items[item.item_id].inv_img
            display.blit(img, img.get_rect(center=img_rect.center))
            text = c.inv_font.render(str(item.amnt), 1, (255, 255, 255))
            display.blit(text, text.get_rect(bottomright=img_rect.bottomright))

        # Draw 'You Died' text
        if self.respawn_counter > 0:
            font = c.get_scaled_font(c.MIN_W // 2, -1, "You Died")
            text = font.render("You Died", 1, (0, 0, 0))
            display.blit(
                text, text.get_rect(center=(c.screen_w // 2, c.screen_h // 2)))

    # Interprets events when the world map is open
    def run_map(self, events):
        pos = pg.mouse.get_pos()

        for e in events:
            if e.type == MOUSEBUTTONUP:
                # Spawn player at clicked location
                if e.button == BUTTON_RIGHT:
                    # Get screen center
                    center = (c.screen_w / 2, c.screen_h / 2)
                    # Get vector from screen center to mouse position in blocks
                    delta = [(pos[i] - center[i]) / self.map.zoom
                             for i in (0, 1)]
                    # Calculate block position of the map position under the map
                    new_pos = [self.map.center[i] + delta[i] for i in (0, 1)]
                    world_dim = game_vars.world_dim()
                    for i in (0, 1):
                        if new_pos[i] < 0:
                            new_pos[i] = 0
                        elif new_pos[i] >= world_dim[i]:
                            new_pos[i] = world_dim[i] - 1
                        new_pos[i] *= BLOCK_W
                    self.set_pos(new_pos)
                    self.map_open = False
                # Zoom in on map
                elif e.button == BUTTON_WHEELUP:
                    if self.map.zoom < 10:
                        self.map.zoom += .5
                # Zoom out on map
                elif e.button == BUTTON_WHEELDOWN:
                    if self.map.zoom > 1:
                        self.map.zoom -= .5
            elif e.type == KEYUP:
                # Leave map
                if e.key == K_ESCAPE or e.key == K_m:
                    self.map.set_center(
                        [p / BLOCK_W for p in self.rect.center])
                    self.map_open = False

        # Move the map, drawing the map automatically puts center in world bounds
        move = game_vars.dt * 100
        keys = pg.key.get_pressed()
        if keys[K_a]:
            self.map.center[0] -= move
        if keys[K_d]:
            self.map.center[0] += move
        if keys[K_w]:
            self.map.center[1] -= move
        if keys[K_s]:
            self.map.center[1] += move

        self.move()

    # Draws ui when the world map is open
    def draw_map(self):
        self.map.draw_map(pg.display.get_surface().get_rect())

    def on_resize(self):
        if self.active_ui:
            self.active_ui.on_resize()
            if self.active_ui.can_drag:
                self.active_ui.rect.centerx = self.active_ui_pos[0] * c.screen_w
                self.active_ui.rect.centery = self.active_ui_pos[1] * c.screen_h

    def set_active_ui(self, ui):
        self.active_ui = ui
        if ui:
            self.active_ui_pos = [
                ui.rect.centerx / c.screen_w, ui.rect.centery / c.screen_h
            ]

    def move(self):
        if game_vars.dt == 0:
            return

        self.immunity -= game_vars.dt

        # Do movement
        d = [0., 0.]
        # For each direction
        for i in range(2):
            # Calculate displacement
            d[i] = BLOCK_W * game_vars.dt * (self.v[i] +
                                             self.a[i] * game_vars.dt / 2)
            # Calculate velocity
            self.v[i] += self.a[i] * game_vars.dt
            spd = self.stats.get_stat("max_speed" + ("x" if i == 0 else "y"))
            if abs(self.v[i]) > spd:
                self.v[i] = copysign(spd, self.v[i])

        # Try to add a drag force, if something is manually setting force, this will have no effect
        if self.v[0] != 0:
            self.a[0] = -8 * self.v[0]

        prev_d, prev_pos = d.copy(), self.pos.copy()
        # Check for collisions and set new position
        game_vars.check_collisions(self.pos, self.dim, d)
        self.set_pos(self.pos)

        # Get actual change in position
        d = [self.pos[0] - prev_pos[0], self.pos[1] - prev_pos[1]]
        # Check collisions in x and y, a collision occurred if we should have moved but didn't
        self.collisions = [0, 0]
        for i in range(2):
            # If we didn't move the full distance, we hit something
            if abs(d[i] - prev_d[i]) > c.ROUND_ERR:
                self.collisions[i] = int(copysign(1, prev_d[i] - d[i]))
            # If we didn't move, check collision based on acceleration
            elif d[i] == prev_d[i] == 0 and self.a[i] != 0:
                truth = self.a[i] < 0
                if i == 0:
                    result = game_vars.touching_blocks_x(
                        self.pos, self.dim, truth)
                else:
                    result = game_vars.touching_blocks_y(
                        self.pos, self.dim, truth)
                self.collisions[i] = copysign(1, self.a[i]) if result else 0
            # Update velocity
            if self.collisions[i] != 0:
                self.v[i] = 0

        # Check if we are touching the ground
        if self.collisions[1] == 0:
            # If we are falling, add to our fall distance
            if self.v[1] > 0:
                self.fall_dist += d[1]
            else:
                self.fall_dist = 0
        elif self.collisions[1] == 1:
            # If our fall distance was great enough, do fall damage
            if self.fall_dist > 10 * BLOCK_W:
                self.hit(int(self.fall_dist / BLOCK_W) - 10, None)
            self.fall_dist = 0

    def set_pos(self, topleft):
        self.pos = list(topleft)
        self.rect.topleft = topleft
        self.placement_range.center = self.rect.center
        self.collection_range.center = self.rect.center

    def left_click(self):
        pos = pg.mouse.get_pos()
        if not self.inventory.left_click(pos):
            item = self.inventory.get_current_item()
            if item.is_item:
                item_obj = game_vars.items[item.item_id]
                self.used_left = game_vars.global_mouse_pos(
                )[0] < self.rect.centerx
                if item_obj.left_click and (self.first_swing
                                            or item_obj.auto_use):
                    self.first_swing = False
                    # Use item
                    item_obj.on_left_click()
                    self.item_used = item_obj
                    self.use_time = item_obj.use_time
            else:
                self.break_block(*game_vars.global_mouse_pos(blocks=True))
                self.use_time = .5

    def right_click(self):
        pos = pg.mouse.get_pos()
        global_pos = game_vars.global_mouse_pos()
        if not self.inventory.right_click(pos):
            block_x, block_y = game_vars.get_topleft(
                *[p // BLOCK_W for p in global_pos])
            tile = game_vars.tiles[game_vars.get_block_at(block_x, block_y)]
            # First attempt to activate the tile
            # Make sure we didn't click the same block more than once
            if tile.clickable and self.placement_range.collidepoint(*global_pos) and \
                    (not tile.has_ui or self.active_ui is None or self.active_ui.block_pos != [block_x, block_y]):
                if tile.activate((block_x, block_y)):
                    return
            # Then try to drop the cursor item
            if self.inventory.selected_item.is_item:
                # Check if we dropped an item
                drop = self.inventory.drop_item()
                if drop is not None:
                    # This determines if we clicked to the left or right of the player
                    left = global_pos[0] < self.rect.centerx
                    game_vars.drop_item(drop, left)
            # If there is no cursor item, use the current hotbar item
            else:
                item = self.inventory.get_current_item()
                if item.is_item:
                    item_obj = game_vars.items[item.item_id]
                    if item_obj.right_click and (self.first_swing
                                                 or item_obj.auto_use):
                        self.first_swing = False
                        # Use item
                        item_obj.on_right_click()
                        self.item_used = item_obj
                        self.use_time = item_obj.use_time

    def break_block(self, block_x, block_y):
        # Make sure we aren't hitting air
        block_x, block_y = game_vars.get_topleft(block_x, block_y)
        block = game_vars.get_block_at(block_x, block_y)
        if block == AIR:
            return
        tile = game_vars.tiles[block]
        # Make sure this block does not have a ui open
        if self.active_ui is not None and self.active_ui.block_pos == [
                block_x, block_y
        ]:
            return False
        # Make sure the block is in range and check if we destroyed the block
        power = self.stats.get_stat("power")
        block_rect = Rect(block_x * BLOCK_W, block_y * BLOCK_W,
                          BLOCK_W * tile.dim[0], BLOCK_W * tile.dim[1])
        if self.placement_range.collidepoint(*block_rect.center) and tile.hit(
                block_x, block_y, power):
            return game_vars.break_block(block_x, block_y)
        return False

    def place_block(self, block_x, block_y, idx):
        # Check if we can place the block
        if self.placement_range.collidepoint(*game_vars.global_mouse_pos()):
            return game_vars.place_block(block_x, block_y, idx)
        return False

    def pick_up(self, item):
        if self.collection_range.colliderect(item.rect):
            space = self.inventory.room_for_item(item.info)
            if len(space) != 0:
                item.attract(self.rect.center)
                if abs(self.rect.centerx - item.rect.centerx) <= 1 and \
                        abs(self.rect.centery - item.rect.centery) <= 1:
                    return self.inventory.pick_up_item(item.info, space)
        else:
            item.pulled_in = False
        return False

    # Get current damage
    @property
    def damage(self):
        return self.stats.get_stat("damage")

    # Check if we hit the desired target
    def hit_target(self, rect):
        if self.item_used is None or self.item_used.polygon is None:
            return False
        else:
            return self.item_used.polygon.collides_polygon(rect)

    # Deals damage and knockback to the player
    def hit(self, dmg, centerx):
        defense = self.stats.get_stat("defense")
        dmg = max(0, dmg - defense)
        self.stats.hp -= dmg
        game_vars.add_damage_text(dmg, self.rect.center)
        if self.stats.hp <= 0:
            self.respawn_counter = 5
            self.map_open = False
        else:
            self.immunity = 1
            if centerx is not None:
                self.v = [copysign(3, self.rect.centerx - centerx), -3]

    def spawn(self):
        spawn = game_vars.world.spawn
        self.set_pos((spawn[0] * BLOCK_W, spawn[1] * BLOCK_W))
        for x in range(spawn[0], ceil(spawn[0] + self.dim[0])):
            for y in range(spawn[1], ceil(spawn[1] + self.dim[1])):
                if game_vars.get_block_at(x, y) not in game_vars.non_solid:
                    self.break_block(x, y)

    def respawn(self):
        self.stats.hp = self.stats.get_stat("hp")
        self.immunity = 5
        self.v = [0, 0]
        self.spawn()
 def __init__(self, idx, upgrade_tree, **kwargs):
     # Default it tool stats
     if "stats" not in kwargs:
         kwargs["stats"] = Stats(TOOL_STATS)
     super().__init__(idx, upgrade_tree, **kwargs)
     self.breaks_blocks = True