import pygame
import random
# from viecher import Skeleton, Zombie

pygame.font.init()
fonts = {
    'medieval': 'medieval.ttf',
    'minecraft': 'Minecraft Evenings.otf',
    '3dpixel': '3D-Pixel.ttf',
    '8bit': '8bitlim.ttf',
    '8bito': '8blimro.ttf',
    'arcade': 'ARCADECLASSIC.ttf',
    'modern_game': 'astron boy video.otf',
    'modern': 'astron boy.otf',
    'wonder': 'Beyond Wonderland.ttf',
    'curved': 'Digitag.ttf',
    'simple': 'DisposableDroidBB.ttf',
    'rounded': 'dpcomic.ttf',
    'playfull': 'Endalian Script.ttf',
    'blocky': 'FREAKSOFNATURE.ttf',
    'catchy': 'Future TimeSplitters.otf',
    'simple_wide': 'Halo3.ttf',
    'simple_fat': 'INVASION2000.ttf',
    'very_gamy': 'ka1.ttf',
    'simple_round': 'Karma Suture.otf',
    'mono': 'manaspc.ttf',
    'damaged': 'Merchant Copy.ttf',
    'big_natural': 'MorialCitadel.TTF',
    'spacy': 'nasalization-rg.otf',
    'sci-fi': 'neuropol.otf',
    'hollow_big_edge': 'papercut.ttf',
    'space_shuttle': 'pdark.ttf',
    'thin': 'PixelFJVerdana12pt.ttf',
    'random': 'Seattle Avenue.ttf',
    'pixel': 'yoster.ttf'
}

class Button():
    def __init__(self, x, y, width, height, image, font, font_size, buttonText='', onclickFunction=None, onePress=False, attributes=None):
        self.font = pygame.font.Font(f'fonts/{fonts[font]}', font_size)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.attributes = attributes
        self.onclickFunction = onclickFunction
        self.onePress = onePress
        self.alreadyPressed = False

        with open(f'art/images/box/{image}', 'r') as tb:
            self.box = pygame.image.load(tb)
            self.box = pygame.transform.scale(self.box, (width, height))


        self.buttonRect = pygame.Rect(self.x, self.y, self.width, self.height)

        self.buttonSurf = self.font.render(buttonText, True, '#baab80')

    def update(self, screen):
        mousePos = pygame.mouse.get_pos()
        if self.buttonRect.collidepoint(mousePos):
            if pygame.mouse.get_pressed(3)[0]:
                if self.onePress:
                    self.onclickFunction()
                elif not self.alreadyPressed:
                    if self.attributes:
                        self.onclickFunction(*self.attributes)
                        self.alreadyPressed = True
                    else:
                        self.onclickFunction()
                        self.alreadyPressed = True
            else:
                self.alreadyPressed = False
        self.box.blit(self.buttonSurf, [
            self.buttonRect.width/2 - self.buttonSurf.get_rect().width/2,
            self.buttonRect.height/2 - self.buttonSurf.get_rect().height/2
        ])
        screen.blit(self.box, self.buttonRect)

class Label():
    def __init__(self, x, y, width, height, text, font='simple', font_size=20, font_color = '#1e90ff', sprite = 'label.png') -> None:
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.font = pygame.font.Font(f'fonts/{fonts[font]}', font_size)
        self.font_color = font_color
        self.text = text
        self.hidden = False
        self.sprite = sprite
        with open(f'art/images/box/{sprite}', 'r') as tb:
            self.box = pygame.image.load(tb)
            self.box = pygame.transform.scale(self.box, (width, height))
        

    def draw(self, screen):
        if self.hidden:
            return
        with open(f'art/images/box/{self.sprite}', 'r') as tb:
            self.box = pygame.image.load(tb)
            self.box = pygame.transform.scale(self.box, (self.width,self.height))
        self.labelRect = pygame.Rect(self.x, self.y, self.width, self.height)
        self.labelSurf = self.font.render(self.text, True, self.font_color)
        self.box.blit(self.labelSurf, [
            self.labelRect.width / 2 - self.labelSurf.get_rect().width / 2,
            self.labelRect.height / 2 - self.labelSurf.get_rect().height / 2
        ])
        screen.blit(self.box, self.labelRect)



class DropDown():
    def __init__(self, x, y, width, height, font, font_size, color_menu, color_option, main, options):
        self.rect = pygame.Rect(x, y, width, height)
        self.font = pygame.font.Font(f'fonts/{fonts[font]}', font_size)
        self.main = main
        self.options = options
        self.draw_menu = False
        self.menu_active = False
        self.active_option = -1

        with open('art/images/box/textbox.png', 'r') as tb:
            self.box = pygame.image.load(tb)
            self.box = pygame.transform.scale(self.box, (width, height))

    def draw(self, screen):
        #pygame.draw.rect(screen, self.color_menu[self.menu_active], self.rect, 0)
        surface = self.font.render(self.main, 1, (0, 0, 0))
        self.box.blit(surface, [
            self.rect.width/2 - surface.get_rect().width/2,
            self.rect.height/2 - surface.get_rect().height/2
        ])
        screen.blit(self.box, surface.get_rect(center = self.rect.center))

        if self.draw_menu:
            for i, text in enumerate(self.options):
                rect = self.rect.copy()
                rect.y += (i+1) * self.rect.height
                rect.x = self.rect.x
                #pygame.draw.rect(screen, self.color_option[1 if i == self.active_option else 0], rect, 0)
                #msg = self.font.render(text, 1, (0, 0, 0))
                #screen.blit(msg, msg.get_rect(center = rect.center))
                surface = self.font.render(text, 1, (0, 0, 0))
                self.box.blit(surface, [
                    rect.width/2 - surface.get_rect().width/2,
                    rect.height/2 - surface.get_rect().height/2
                ])
                screen.blit(self.box, rect)

    def update(self, event_list):
        mpos = pygame.mouse.get_pos()
        self.menu_active = self.rect.collidepoint(mpos)
        self.active_option = -1
        for i in range(len(self.options)):
            rect = self.rect.copy()
            rect.y += (i+1) * self.rect.height
            if rect.collidepoint(mpos):
                self.active_option = i
                break

        if not self.menu_active and self.active_option == -1:
            self.draw_menu = False
        #self.draw_menu = True
        #return -1
        if pygame.mouse.get_pressed(num_buttons=3)[0]:
            if self.menu_active:
                self.draw_menu = not self.draw_menu
            elif self.draw_menu and self.active_option >= 0:
                self.draw_menu = False
                return self.active_option
        return -1


class GameObjects():
    def __init__(self, name:str, _type:str, bg, objects:list, WIDTH, HEIGHT) -> None:
        self.name = name
        self.type = _type
        self.background = bg
        if bg != None:
            if WIDTH != None and HEIGHT != None:
                with open(bg, 'r') as bg:
                    self.background = pygame.transform.scale(pygame.image.load(bg), [WIDTH, HEIGHT])
            else:
                with open(bg, 'r') as bg:
                    self.background = pygame.transform.scale2x(pygame.image.load(bg)) 
        self.objects = objects

    def update(self, objects):
        return
    def draw(self, screen):
        return

class Scene(GameObjects):
    def __init__(self, name:str, _type:str, bg, objects:list, WIDTH, HEIGHT, level:list) -> None:
        super().__init__(name, _type, bg, objects, WIDTH, HEIGHT)
        self.level = level
        self.current_level = 0
        self.update()

    def update(self, change:int=None, objects=None):
        obj = self.level[self.current_level].update(change, objects)
        self.background = self.level[self.current_level].background
        """if isinstance(self.objects, list):
            for obj in self.objects[0] + self.objects[1] + self.objects[2]:
                obj.update()"""
        
        return obj
        
    def draw(self, screen):
        """if isinstance(self.objects, list):
            for obj in self.objects[0] + self.objects[1] + self.objects[2] + self.objects[3]:
                obj.draw(screen)"""
        self.level[self.current_level].draw(screen)

    def getObjects(self):
        return self.level[self.current_level].getObjects()
        

class Stage(GameObjects):
    def __init__(self, name: str, _type: str, bg, objects: list, WIDTH, HEIGHT, stage:str, rooms:list) -> None:
        super().__init__(name, _type, bg, objects, WIDTH, HEIGHT)
        self.WIDTH = WIDTH
        self.HEIGHT = HEIGHT
        self.stage = stage
        self.rooms = rooms
        self.current = 0
        self.sortRooms(WIDTH)
        for room in self.rooms:
            if room.id == self.current:
                room.objectCreation(WIDTH, HEIGHT)
    
    def update(self, target, objects):
        if target is not None:
            self.current = int(target)
            for room in self.rooms:
                if room.id == self.current:
                    obj = room.objectCreation(self.WIDTH, self.HEIGHT)
                    self.background = room.background
                    return obj
                
        for room in self.rooms:
            if room.id == self.current:
                room.update(objects)
                self.background = room.background
        
    def draw(self, screen):
        for room in self.rooms:
            if room.id == self.current:
                room.draw(screen)

    def getObjects(self):
        for room in self.rooms:
            if room.id == self.current:
                return room.getObjects()
    
    def sortRooms(self, WIDTH):
        rooms = self.rooms
        for i, room in enumerate(rooms):
            if room.type != 'boss':
                i += 1
                rand = random.randint(0, 25)
                if rand < 7.5:
                    if rooms[i].id <= room.id: i += 1
                    room.exits.append([rooms[i].id, rooms[i].type])
                elif rand < 20:
                    if rooms[i].id <= room.id: i += 1
                    room.exits.append([rooms[i].id, rooms[i].type])
                    if rand % 2 == 0: i += 1
                    if not i >= len(self.rooms) - 2:
                        room.exits.append([rooms[i + 1].id, rooms[i + 1].type])
                else:
                    if rooms[i].id <= room.id: i += 1
                    room.exits.append([rooms[i].id, rooms[i].type])
                    if rand % 2 == 0: i += 1
                    if not i >= len(self.rooms) - 2:
                        room.exits.append([rooms[i + 1].id, rooms[i + 1].type])
                        if not i >= len(self.rooms) - 3:
                            room.exits.append([rooms[i + 2].id, rooms[i + 2].type])
            
        for room in self.rooms:
            room.createDoors(WIDTH)

class Room(GameObjects):
    def __init__(self, name:str, _type:str, bg, objects:list, WIDTH, HEIGHT, id:int) -> None:
        super().__init__(name, _type, bg, objects, WIDTH, HEIGHT)
        self.exits = []
        self.id = id
        self.doors = []
        if self.type == 'normal' or self.type == 'boss':
            self.locked = True
            #self.objectCreation(WIDTH, HEIGHT)
        else:
            self.locked = False
    
    def genWalls(self, WIDTH, HEIGHT):
        walls = []
        walls.append(Obstacle('wall_l', 'wall', None, True, 32, 32, True, WIDTH=4, HEIGHT=HEIGHT))
        walls.append(Obstacle('wall_r', 'wall', None, True, WIDTH + 28, 32, True, WIDTH=4, HEIGHT=HEIGHT))
        walls.append(Obstacle('wall_t', 'wall', None, True, 32, 32, True, WIDTH=WIDTH, HEIGHT=4))
        walls.append(Obstacle('wall_b', 'wall', None, True, 32, HEIGHT + 28, True, WIDTH=WIDTH, HEIGHT=4))
        return walls

    def createDoors(self, WIDTH):
        if not self.type == 'boss':
            if len(self.exits) == 1:
                self.doors.append(Door(f'door{self.id}', self.exits[0][1], random.randint(64, round(WIDTH * 0.75)), self.exits[0][0]))
            elif len(self.exits) == 2:
                self.doors.append(Door(f'door{self.id}', self.exits[0][1], random.randint(64, round(WIDTH * 0.45)), self.exits[0][0]))
                self.doors.append(Door(f'door{self.id}', self.exits[1][1], random.randint(round(WIDTH * 0.5), round(WIDTH * 0.9)), self.exits[1][0]))
            else:
                self.doors.append(Door(f'door{self.id}', self.exits[0][1], random.randint(64, round(WIDTH * 0.3)), self.exits[0][0]))
                self.doors.append(Door(f'door{self.id}', self.exits[1][1], random.randint(round(WIDTH * 0.33), round(WIDTH * 0.6)), self.exits[1][0]))
                self.doors.append(Door(f'door{self.id}', self.exits[2][1], random.randint(round(WIDTH * 0.63), round(WIDTH * 0.95)), self.exits[2][0]))

            self.objects.append(self.doors)

    def objectCreation(self, WIDTH, HEIGHT):
        if self.type != 'boss':
            self.objects[1] = [Skeleton('skeleton', random.randint(40, 60), random.randint(50, WIDTH - 50), random.randint(50, HEIGHT - 50), 5, 1, 1, 2, 200) for i in range(0,random.randint(2, 5))] + [Zombie('zombie', random.randint(40, 60), random.randint(50, WIDTH-50), random.randint(50, HEIGHT-50), 5, 1, 1, 1, 25) for i in range(0,random.randint(2, 5))]
            npcs = [NPC('vivi', 100, 'people/vivi.png', 0, 400, 600),
                    NPC('fairy', 100, 'people/fairy.png', 0, 200, 200)]
            rand = random.randint(0, 30)
            if rand <= 25 and rand >= 15:
                self.objects[2] = [random.choice(npcs)] 
        else:
            self.objects[1] = [Boss('reddy', 40, WIDTH / 2 - 16, HEIGHT /2 - 32, 50, 0.1, 1, 5, 5000)]
        self.objects[4] = [wall for wall in self.genWalls(WIDTH, HEIGHT)]
        return self.objects


    def update(self, objects):
        if objects is not None:
            self.objects = objects
        if not self.objects[1]:
            if self.type == 'boss':
                print('yeahhh, you killed the boss')
                self.objects[0][0].level.level = 100
            self.locked = False
            for door in self.doors:
                door.update(False)
        return
    
    def draw(self, screen):
        """screen.blit(self.background, (32, 32))
        if isinstance(self.objects, list):
            for obj in self.objects[3] + self.objects[0] + self.objects[1] + self.objects[2]:
                obj.draw(screen)"""
        for door in self.doors:
            door.draw(screen)

    def getObjects(self):
        return self.objects

class Obstacle(GameObjects):
    def __init__(self, name: str, _type: str, bg, collision: bool, x: int, y: int, hidden: bool=False, objects: list = None, WIDTH=None, HEIGHT=None) -> None:
        super().__init__(name, _type, bg, objects, WIDTH, HEIGHT)
        self.collision = collision
        self.hidden = hidden
        self.width = WIDTH
        self.height = HEIGHT
        if self.background is not None:
            self.rect = pygame.Rect((x, y), self.background.get_size())
        else:
            self.rect = pygame.Rect(x, y, WIDTH, HEIGHT)
    
    def draw(self, screen):
        if not self.hidden:
            screen.blit(self.background, self.rect)

class Door(GameObjects):
    def __init__(self, name: str, _type: str, x: int, target:int, y: int=16, objects: list=None, WIDTH=None, HEIGHT=None, islocked=True) -> None:
        super().__init__(name, _type, f'art/images/background/door_{_type}.png', objects, WIDTH, HEIGHT)
        self.rect = pygame.Rect((x, y), self.background.get_size())
        self.locked = islocked
        self.target = target

    def draw(self, screen):
        screen.blit(self.background, self.rect)
    
    def update(self, islocked=True):
        self.locked = islocked



#Viecher
vec = pygame.math.Vector2
fps = 60

class Objects():
    def __init__(self, name, ms, sprite, x, y) -> None:
        self.name = name
        self.speed = ms
        with open(f'art/images/{sprite}') as i:
            self.sprite = pygame.transform.scale2x(pygame.image.load(i)) 
        self.x = x
        self.y = y
        self.hidden = False
        self.rect = pygame.Rect(self.x, self.y, self.sprite.get_width(), self.sprite.get_height())
    
    def draw(self, screen):
        if self.hidden:
            return
        self.rect.x, self.rect.y = self.x, self.y
        screen.blit(self.sprite, self.rect)
        #pygame.draw.rect(screen, '#ef0120', self.rect,  2)

class NPC(Objects):
    def __init__(self, name, ms, sprite, convo_scene, x, y) -> None:
        super().__init__(name, ms, sprite, x, y)
        self.talking = False
        self.hidden = False
        self.conversation = Convo(self, convo_scene)
        self.lastUpdate = pygame.time.get_ticks()
    
    def talk(self, objects):
        self.talking = True
        objects[0][0].talking = True
    
    def draw(self, screen):
        super().draw(screen)
        if self.talking:
            self.conversation.draw(screen)
    
    def update(self, keys, objects):
        if self.name == 'oldlady':
            if self.conversation.convo_scene == 0 and 'rat' in objects[0][0].killed and objects[1]==[]:
                self.conversation.convo_scene = 1
        if self.lastUpdate + 200 < pygame.time.get_ticks():
            if self.talking:
                self.conversation.update(keys, objects)
                self.lastUpdate = pygame.time.get_ticks()
            else:
                touches = pygame.sprite.spritecollideany(self, objects[0])
                if touches is not None and keys[pygame.K_f] and isinstance(touches, MainCharacter):
                    self.talk(objects) 
                    self.lastUpdate = pygame.time.get_ticks()


class Convo(Label):
    def __init__(self, npc, convo_scene, text='', x = 140, y = 600, width = 1000, height = 100, font='simple', font_size = 20) -> None:
        super().__init__(x, y, width, height, text, font, font_size)
        self.convo_act=0
        self.npc = npc
        self.convo_scene = convo_scene
        self.convos = [
            ['oldlady', 0, ['There are so many rats here.', 'I wish someone would to something against that.','An experienced fighter could kill them.', 'For them it only takes a mouseclick.']],
            ['oldlady', 1, ['Oh, did you kill all the rats?', 'You must be the chosen one!', 'It would be nice if you would go and talk to the village elder.']],
            ['elder', 0, ['Who are you?', 'You want to help us?', 'We have a serious problem with monsters.', 'One day they appeared out of nowhere and started attacking.', 'When you jump into the portal over there,', 'You will be send to a place with monsters.', 'PLEASE help us!']],
            ['elder', 1, ['Who are you?', 'You want to help us?', 'We have a serious problem with monsters.', 'One day they appeared out of nowhere and started attacking.', 'When you jump into the portal over there,', 'You will be send to a place with monsters.', 'PLEASE help us!']],
            ['vivi', 0, ['Wer bist du denn?', 'Aber du kannst gerne aus dem Fenster springen, solange es nicht in meinem Unterricht ist.']]
        ]

    def draw(self, screen):
        self.text = self.findConversation()[2][self.convo_act]
        super().draw(screen)


    def findConversation(self):
        for convo in self.convos:
            if convo[0] == self.npc.name and convo[1] == self.convo_scene:
                return convo
        return ['ERROR']

    def update(self, keys, objects):
        if keys[pygame.K_f]:
            convo = self.findConversation()
            if self.convo_act + 1 < len(convo[2]):
                self.text = convo[2][self.convo_act]
                self.convo_act += 1
            else:
                if convo[0] == 'oldlady':
                    if convo[1] == 0:
                        for i in range(0,5):
                            objects[1].append(Rat('rat', random.randint(150,250), 800, 400+i*20, 1, 1, 1, 100, 25))
                    elif convo[1] == 1:
                        objects[0][0].level.level = 50
                        while 'rat' in objects[0][0].killed: objects[0][0].killed.remove('rat')
                elif convo[0] == 'elder':
                    if convo[1] == 0:
                        objects[4].append(Obstacle('portal', 'interactable', 'art/images/background/portal.png', False, 700, 300))
                        self.convo_scene += 1
                elif convo[0] == 'vivi':
                    objects[0][0].health.health = 0
                self.convo_act = 0
                self.npc.talking = False
                objects[0][0].talking = False


class Fighter(Objects):
    def __init__(self, name, ms, sprite, x, y, health, damage, level, asp, atr) -> None:
        super().__init__(name, ms, sprite, x, y)
        self.health = health
        self.damage = damage
        self.level = level
        self.attack_speed = asp
        self.attack_range = atr
        self.lastHurt = pygame.time.get_ticks()
        self.lastAttack = pygame.time.get_ticks()
        self.hurtCooldown = 0


class MainCharacter(Fighter):
    def __init__(self, name, ms, sprite, x, y, health, damage, level, asp, atr, killed=[]) -> None:
        super().__init__(name, ms, sprite, x, y, health, damage, level, asp, atr)
        self.book = Book(0, 0, [], None, None)
        self.talking = False
        self.level = Level(1000, 38, 150, 40, level, f'will to live: {level}%', 'simple', 20)
        self.health = Hearts(health, sprite=['fullheart.png', 'fullheart.png', 'fullheart.png', 'fullheart.png', 'fullheart.png'], x=900, y=50, hurtCooldown=self.hurtCooldown)
        self.thinks = Thinks(self.x + 20, self.y - 50, 150, 100, 'brr I\'m freezing')
        self.freezing = True
        self.killed = killed  # amount of mobs that were killed
        self.load_frames(f'art/images/people/oldmanwalk.png', f'art/images/people/oldmanattack.png')
        self.current_frame = 0
        self.animation_speed = 0.1  
        self.last_frame_update = pygame.time.get_ticks()
        self.rect = pygame.Rect(x, y, self.walk_frames[0].get_width(), self.walk_frames[0].get_height())  
        self.is_attacking = False

    def load_frames(self, walk_sprite_sheet, attack_sprite_sheet):
        self.walk_frames = self.load_animation_frames(walk_sprite_sheet, frame_width=40, frame_height=66)
        self.attack_frames = self.load_animation_frames(attack_sprite_sheet, frame_width=66, frame_height=64)  # Adjust frame width and height as needed

    def load_animation_frames(self, sprite_sheet, frame_width, frame_height):
        sprite_sheet = pygame.transform.scale2x(pygame.image.load(sprite_sheet))
        animation_frames = []
        if frame_width == 40:
            frames_coordinates = [
                (frame_width, 0),   
                (frame_width*2, 0),  
                (frame_width*3, 0),
                (frame_width*4, 0),
                (frame_width*5, 0),
                (frame_width*6, 0),
                (frame_width*7, 0),
                (frame_width*8, 0),
            ]
        else:
            frames_coordinates = [
                (frame_width, 0),   
                (frame_width*2, 0),  
                (frame_width*3, 0),
                (frame_width*4, 0),
                (frame_width*5, 0),
                (frame_width*6, 0)
            ]
        for x, y in frames_coordinates:
            frame = pygame.Surface((frame_width, frame_height), pygame.SRCALPHA)
            frame.blit(sprite_sheet, (0, 0), (x, y, frame_width, frame_height))
            animation_frames.append(frame)
        return animation_frames


    def draw(self, screen):
        if self.hidden:
            return
        current_time = pygame.time.get_ticks()
        if self.is_attacking:
            animation_frames = self.attack_frames
        else:
            animation_frames = self.walk_frames

        if current_time - self.last_frame_update > self.animation_speed * 1000:
            self.current_frame = (self.current_frame + 1) % len(animation_frames)
            self.last_frame_update = current_time
        current_frame_image = animation_frames[self.current_frame]
        screen.blit(current_frame_image, (self.x, self.y))

        self.rect.x, self.rect.y = self.x, self.y
        self.health.draw(screen)
        self.level.draw(screen)
        self.book.draw(screen)
        #pygame.draw.rect(screen, '#e900fa', self.rect, 2)
        if self.thinks.hidden == False:
            self.thinks.draw(screen, self.x + 20, self.y - 100)

    def hurt(self, damage, objects):
        if not self.talking:
            self.health.hurt(damage)
    
    def obstacle_interaction(self, objects):
        if len(objects) <= 5:
            objects.append([])
        touches = pygame.sprite.spritecollideany(self, objects[4] + objects[5])
        if touches is not None:
            if touches.name == 'fireplace':
                self.freezing = False
            elif touches.name == 'portal' and self.level.level != 1:
                return 'play'
            elif touches.name == 'house' and self.level.level != 1:
                self.x = 500
                self.y = 400
                return 'house'
            elif 'wall' in touches.name:
                return 'wall'
            elif isinstance(touches, Door):
                if not touches.locked:
                    return f'door-{touches.target}'
        else:
            return True
    
    def walk(self, keys, objects):
        moveto = vec(0, 0)
        if keys[pygame.K_w] or keys[pygame.K_UP]:
            moveto += vec(0, -1)
        if keys[pygame.K_a] or keys[pygame.K_LEFT]:
            moveto += vec(-1, 0)
        if keys[pygame.K_s] or keys[pygame.K_DOWN]:
            moveto += vec(0, 1)
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            moveto += vec(1, 0)
        if not moveto == vec(0, 0):
            moveto.scale_to_length(self.speed)

        self.x += moveto[0] / fps
        self.y += moveto[1] / fps 
        touches = pygame.sprite.spritecollideany(self, objects[4])
        if touches is not None and not isinstance(touches, Weapons):
            if isinstance(touches, Obstacle):
                if not touches.collision:
                    # print(touches.name)
                    return
                if touches.type == 'wall':
                    if touches.name == 'wall_l':
                        self.x += (2 + (self.x - touches.rect.x))
                    elif touches.name == 'wall_r':
                        self.x -= (2 + self.rect.width - (touches.rect.x - self.x))
                    if touches.name == 'wall_t':
                        self.y += (2 + (self.y - touches.rect.y))
                    elif touches.name == 'wall_b':
                        self.y -= (2 + self.rect.height - (touches.rect.y - self.y))
                    return
                    

            if self.x <= touches.rect.x: self.x -= (self.rect.width - (touches.rect.x - self.x))
            elif self.x > touches.rect.x: self.x += (self.rect.width - (self.x - touches.rect.x - touches.rect.width * 0.7))
            #if self.y <= touches.y: pass
            #elif self.y > touches.y: pass
            #self.x -= moveto[0] * 2 / fps
            #self.y -= moveto[1] * 2 / fps

        """
        if self.x <= 32:
            self.x = 33
        elif self.x >= objects[3][0].width - 32:
            self.x = objects[3][0].width - 32 - self.rect.width + 1
        if self.y <= 32:
            self.y = 33
        elif self.y >= objects[3][0].height - 32:
            self.y = objects[3][0].height - 32 - self.rect.height + 1
        """
    
    def attack(self, obj, mouse):
        if self.lastAttack + self.attack_speed * 1000 < pygame.time.get_ticks():
            moveto = mouse - vec(self.x, self.y)
            if self.book.current_sp == 'fireball':
                weapon = Fireball('fb1', 100, self.x, self.y, moveto, 5)
            elif self.book.current_sp == 'windslash':
                weapon = Windslash('ws1', 100, self.x, self.y, moveto, 10)
            else:
                weapon = Punch('punch', 100, self.x, self.y, moveto, 1, Mobs, life_ticks=500)
            obj[3].append(weapon)
            self.lastAttack = pygame.time.get_ticks()
            if not self.is_attacking:
                self.current_frame = 0
                self.is_attacking = True

    def update(self, keys, mouse, objects):
        if not self.talking:
            is_moving = False
            if keys[pygame.K_w] or keys[pygame.K_UP] or keys[pygame.K_a] or keys[pygame.K_LEFT] or keys[pygame.K_s] or keys[pygame.K_DOWN] or keys[pygame.K_d] or keys[pygame.K_RIGHT]:
                is_moving = True
            if is_moving:
                self.walk(keys, objects)
                self.is_attacking = False
            else:
                current_time = pygame.time.get_ticks()
                if current_time - self.last_frame_update > self.animation_speed * 1000:
                    self.current_frame = (self.current_frame + 1) % len(self.walk_frames)
                    self.last_frame_update = current_time
                if not self.is_attacking:
                    self.current_frame = 0
            if pygame.mouse.get_pressed()[0]:
                self.attack(objects, vec(mouse))
            if self.is_attacking:
                if self.current_frame == len(self.attack_frames) - 1:
                    self.is_attacking = False
                    self.current_frame = 0
        self.thinks.update(objects, self)
        if self.health.health <= 0:
            return 'village'
        else:
            return self.obstacle_interaction(objects)

class Hearts():
    def __init__(self, health, sprite, x, y, hurtCooldown) -> None:
        self.x = x
        self.y = y
        self.health = health
        self.lastHurt = pygame.time.get_ticks()
        self.hurtCooldown = hurtCooldown
        self.hidden = False
        self.sprite=[]
        for parts in sprite:
            with open(f'art/images/main_attributes/{parts}') as i:
                self.sprite.append(pygame.image.load(i))
        self.rect = []
        for each in self.sprite:
            self.rect.append(pygame.Rect(self.x, self.y, each.get_width(), each.get_height()))
        
    def hurt(self,damage):
        if self.lastHurt + self.hurtCooldown < pygame.time.get_ticks():
            self.health -= damage
            self.lastHurt = pygame.time.get_ticks()
            self.update()

    def draw(self, screen):
        if self.hidden:
            return
        for i in range(0, 5):
            self.rect[i].x, self.rect[i].y = self.x + i * 20, self.y
            screen.blit(self.sprite[i], self.rect[i])
    
    def update(self):
        sprite = []
        for i in range(0, 5):
            if round(self.health) >= 4 + 4 * i:
                sprite.append('fullheart.png')
            elif round(self.health) == 3 + 4 * i:
                sprite.append('dreiviertelheart.png')
            elif round(self.health) >= 2 + 4 * i:
                sprite.append('halfheart.png')
            elif round(self.health) >= 1 + 4 * i:
                sprite.append('viertelheart.png')
            elif round(self.health) <= 4 * i:
                sprite.append('noheart.png')  
        self.sprite = []
        for parts in sprite:
            with open(f'art/images/main_attributes/{parts}') as i:
                self.sprite.append(pygame.image.load(i))


class Level(Label):
    def __init__(self, x, y, width, height, level, text, font='simple', font_size=20, font_color='#1e90ff', sprite='label.png') -> None:
        super().__init__(x, y, width, height, text, font, font_size, font_color, sprite)
        self.level = level
    def draw(self, screen):
        self.text = f'will to live: {self.level}%'
        super().draw(screen)

class Thinks(Label):
    def __init__(self, x, y, width, height, text, font='simple', font_size=15, font_color='#000000', sprite='thinks.png') -> None:
        super().__init__(x, y, width, height, text, font, font_size, font_color, sprite)
        self.scene = 0

    def draw(self, screen, x, y):
        if self.hidden:
            return
        self.x = x
        self.y = y
        super().draw(screen)
    
    def update(self, objects, main):
        if not self.hidden:
            if self.scene == 0 and not main.freezing:
                self.scene = 1
                self.hidden = True
            elif self.scene == 1 and main.talking:
                self.scene = 2
                self.hidden = True
        if self.scene == 1:
            touches = pygame.sprite.spritecollideany(main, objects[2])
            if touches is not None and isinstance(touches, NPC):
                self.text = 'I should press \"f\"'
                self.hidden = False
            else:
                self.hidden = False
                self.text = 'the lady over there'

class Book():
    def __init__(self, x, y, spells, current_spell, current_shield) -> None:
        with open(f'art/images/main_attributes/book.png') as i:
            self.sprite = pygame.image.load(i)
            self.sprite = pygame.transform.scale(self.sprite, (1280, 720))
        self.x = x
        self.y = y
        self.hidden = False
        self.rect = pygame.Rect(self.x, self.y, self.sprite.get_width(), self.sprite.get_height())
        self.sp_list = spells
        self.current_sp = current_spell
        self.text_left = ["Dear User, ", "in case you fell on the ground too hard,", "here is a quick reminder:", 
                            "You are a homeless person. One cold day","you went to the library to warm up yourself.", 
                            "There you got bored and found and opened me.", "This lead to you being thrown into this world.", 
                            "But you can find a way out of here again."] 
        self.text_right = ["This book will help you to survive.", "You can open and close me when pressing e.",
                           "Click on a picture to choose your spell.",
                           "Talk to fairies to unlock new spells!"]
        self.buttons=[]
        self.buttons_y = 400
        self.buttons_x = 800

    
    def draw(self, screen):
        if self.hidden:
            return
        self.rect.x, self.rect.y = self.x, self.y
        screen.blit(self.sprite, self.rect)
        text_left_y = 100
        text_right_y = 100
        for text in self.text_left:
            label = Label(100, text_left_y, 500, 50, text, font_color='#000000', sprite='empty.png')
            label.draw(screen)
            text_left_y += 50
        for text in self.text_right:
            label = Label(680, text_right_y, 500, 50, text, font_color='#000000', sprite='empty.png')
            label.draw(screen)
            text_right_y += 50
        for button in self.buttons:
            button.update(screen)
    
    def addspell(self, spell):
        if spell not in self.sp_list:
            self.sp_list.append(spell)
            self.current_sp = spell
            self.buttons.append(Button(self.buttons_x, self.buttons_y, 58, 50, f'{spell}_icon.png', 'medieval', 23, attributes=[spell], onclickFunction=self.update_spell))
            self.buttons_y += 100

    def update_spell(self, spell):
        self.current_sp = spell

    def update(self):
        pass

class Mobs(Fighter):
    def __init__(self, name, ms, sprite, x, y, health, damage, level, asp, atr, drops) -> None:
        super().__init__(name, ms, sprite, x, y, health, damage, level, asp, atr)
        self.drops = drops * (self.level / 2)
    
    def chase(self, obj):
        x = obj[0][0].x
        y = obj[0][0].y
        moveto = vec(x, y) - vec(self.x, self.y)
        if not (moveto).length() <= self.attack_range:
            moveto.scale_to_length(self.speed)
            self.x += moveto[0] / fps
            self.y += moveto[1] / fps
            touches = pygame.sprite.spritecollideany(self, obj[4])
            if touches is not None and not isinstance(touches, Weapons):
                if isinstance(touches, Obstacle):
                    if not touches.collision:
                        return
                    if touches.type == 'wall':
                        if touches.name == 'wall_l':
                            self.x += (2 + (self.x - touches.rect.x))
                        elif touches.name == 'wall_r':
                            self.x -= (2 + self.rect.width - (touches.rect.x - self.x))
                        if touches.name == 'wall_t':
                            self.y += (2 + (self.y - touches.rect.y))
                        elif touches.name == 'wall_b':
                            self.y -= (2 + self.rect.height - (touches.rect.y - self.y))

                    if self.x <= touches.rect.x: self.x -= (self.rect.width - (touches.rect.x - self.x))
                    elif self.x > touches.rect.x: self.x += (self.rect.width - (self.x - touches.rect.x - touches.rect.width * 0.66))
        else:
            self.attack(moveto, obj)
    
    
    def hurt(self, damage, objects):
        self.health -= damage
        if self.health <= 0:
            objects[0][0].killed.append(self.name)
            self.hidden = True
            objects[1].remove(self)
    
    
    def update(self, obj):
        self.chase(obj)


class Skeleton(Mobs):
    def __init__(self, name, ms, x, y, health, damage, level, asp, atr, sprite = 'people/skeleton.png', drops=0) -> None:
        super().__init__(name, ms, sprite, x, y, health, damage, level, asp, atr, drops)
    
    def attack(self, moveto, obj):
        if self.lastAttack + self.attack_speed * 1000 < pygame.time.get_ticks():
            obj[3].append(Arrow("arrow", 200, self.x, self.y, moveto, self.damage))
            self.lastAttack = pygame.time.get_ticks()
    

class Zombie(Mobs):
    def __init__(self, name, ms, x, y, health, damage, level, asp, atr, sprite_sheet='people/zombiewalk.png', drops=0) -> None:
        super().__init__(name, ms, sprite_sheet, x, y, health, damage, level, asp, atr, drops)
        self.load_frames(f'art/images/{sprite_sheet}') 
        self.current_frame = 0
        self.animation_speed = 250 
        self.last_frame_update = pygame.time.get_ticks()
        self.hidden = False
        self.rect = pygame.Rect(x, y, 40, 64)

    def load_frames(self, sprite_sheet):
        sprite_sheet = pygame.transform.scale2x(pygame.image.load(sprite_sheet).convert_alpha())
        frame_width = 40  
        frame_height = 64
        frames_coordinates = [(40, 0),(80, 0),(120, 0),(160, 0),(200, 0),(240, 0),(280, 0), (320, 0)]
        self.animation_frames = []
        for x, y in frames_coordinates:
            frame = pygame.Surface((frame_width, frame_height), pygame.SRCALPHA)
            frame.blit(sprite_sheet, (0, 0), (x, y, frame_width, frame_height))
            self.animation_frames.append(frame)

    def draw(self, screen):
        if self.hidden:
            return
        current_time = pygame.time.get_ticks()
        if current_time - self.last_frame_update > self.animation_speed:
            self.current_frame = (self.current_frame + 1) % len(self.animation_frames)
            self.last_frame_update = current_time
        
        current_frame_image = self.animation_frames[self.current_frame]
        screen.blit(current_frame_image, (self.x, self.y))

        self.rect.topleft = (self.x, self.y)
        #pygame.draw.rect(screen, '#ef0120', self.rect, 2)
    
    
    def attack(self, moveto, obj):
        if self.lastAttack + self.attack_speed * 1000 < pygame.time.get_ticks():
            obj[3].append(Punch('punch', 100, self.x, self.y, moveto, self.damage, MainCharacter))
            self.lastAttack = pygame.time.get_ticks()

class Rat(Mobs):
    def __init__(self, name, ms, x, y, health, damage, level, asp, atr, sprite='people/rat.png', drops=0) -> None:
        super().__init__(name, ms, sprite, x, y, health, damage, level, asp, atr, drops)
    
    def attack(self, moveto, obj):
        if self.lastAttack + self.attack_speed * 1000 < pygame.time.get_ticks():
            obj[3].append(Punch('punch', 100, self.x, self.y, moveto, self.damage, MainCharacter))
            self.lastAttack = pygame.time.get_ticks()

class Boss(Mobs):
    def __init__(self, name, ms, x, y, health, damage, level, asp, atr, sprite='people/reddy.png', drops=0) -> None:
        super().__init__(name, ms, sprite, x, y, health, damage, level, asp, atr, drops)

    def attack(self, moveto, obj):
        if self.lastAttack + self.attack_speed * 1000 < pygame.time.get_ticks():
            obj[3].append(RedBlob("blob", 50,  self.x, self.y, moveto, self.damage))
            self.lastAttack = pygame.time.get_ticks()

    def hurt(self, damage, objects, ticks):
        if ticks > 1000:
            damage /= 2
        self.health -= damage
        if self.health <= 0:
            objects[0][0].killed.append(self.name)
            self.hidden = True
            objects[1].remove(self)        


class Weapons(Objects):
    def __init__(self, name, ms, sprite, x, y, moveto, damage, life_ticks) -> None:
        super().__init__(name, ms, sprite, x, y)
        self.moveto = moveto
        self.damage = damage
        self.life_ticks= life_ticks
        self.spawn_tick = pygame.time.get_ticks()
        pos = vec(1,0)
        angle = pos.angle_to(moveto)
        
        self.sprite = pygame.transform.rotate(self.sprite, -angle)
    
    def die(self, objects, kills):
        touches = pygame.sprite.spritecollideany(self, objects[0] + objects[1])
        if kills == Mobs:
            if touches is not None and isinstance(touches, Boss):
                touches.hurt(self.damage, objects, self.life_ticks)
                self.hidden = True
                if self in objects[3]:
                    objects[3].remove(self)
                return

        if touches is not None and isinstance(touches, kills):
            touches.hurt(self.damage, objects)
            self.hidden = True
            if self in objects[3]:
                objects[3].remove(self)
            
    def move(self, objects):
        self.moveto.scale_to_length(self.speed)
        self.x += self.moveto[0] / fps
        self.y += self.moveto[1] / fps
        if pygame.time.get_ticks() - self.spawn_tick > self.life_ticks:
            self.hidden = True
            objects[3].remove(self)

class Spells(Weapons):
    def __init__(self, name, ms, sprite, x, y, moveto, damage, life_ticks) -> None:
        super().__init__(name, ms, sprite, x, y, moveto, damage, life_ticks)

class Fireball(Spells):
    def __init__(self, name, ms, x, y, moveto, damage, sprite = 'weapons/fireball.png', life_ticks=5000) -> None:
        super().__init__(name, ms, sprite, x, y, moveto, damage, life_ticks)
    
    def update(self, objects):
        self.move(objects)
        self.die(objects, Mobs)

class Windslash(Spells):
    def __init__(self, name, ms, x, y, moveto, damage, sprite = 'weapons/windslash.png', life_ticks=1000) -> None:
        super().__init__(name, ms, sprite, x, y, moveto, damage, life_ticks)
    
    def update(self, objects):
        self.move(objects)
        self.die(objects, Mobs)
    
    def move(self, objects):
        super().move(objects)
        self.moveto = self.moveto.rotate(5)

class RedBlob(Spells):
    def __init__(self, name, ms, x, y, moveto, damage, sprite = 'weapons/redblob.png', life_ticks=50000) -> None:
        super().__init__(name, ms, sprite, x, y, moveto, damage, life_ticks)
    

    def die(self, objects, kills):
        touches = pygame.sprite.spritecollideany(self, objects[0] + objects[1])
        if touches is not None and isinstance(touches, kills):
            touches.hurt(self.damage, objects)

    def update(self, objects):
        self.move(objects)
        self.die(objects, MainCharacter)


class Arrow(Weapons):
    def __init__(self, name, ms, x, y, moveto, damage, sprite = 'weapons/arrow.png', life_ticks=5000) -> None:
        super().__init__(name, ms, sprite, x, y, moveto, damage, life_ticks)
    
    def update(self, objects):
        self.move(objects)
        self.die(objects, MainCharacter)


class Punch(Weapons):
    def __init__(self, name, ms, x, y, moveto, damage, kills, sprite = 'weapons/empty.png', life_ticks=100) -> None:
        super().__init__(name, ms, sprite, x, y, moveto, damage, life_ticks)
        self.kills = kills
    
    def update(self, objects):
        self.move(objects)
        self.die(objects, self.kills)
