import pyxel
import random

WORLD_WIDTH = 1080
WORLD_HEIGHT = 720
VIEW_WIDTH = 360
VIEW_HEIGHT = 240

BG_IMG_W = 216
BG_IMG_H = 144
BG_SCALE = 5  # 216*5=1080, 144*5=720


def clamp(value, minimum, maximum):
    return max(minimum, min(value, maximum))


class BackgroundLayer:
    def __init__(self, pyxres_path, parallax_x=1.0, parallax_y=1.0):
        pyxel.load(
            pyxres_path,
            exclude_tilemaps=True,
            exclude_sounds=True,
            exclude_musics=True,
        )

        src = pyxel.Image(BG_IMG_W, BG_IMG_H)
        src.blt(0, 0, pyxel.images[0], 0, 0, BG_IMG_W, BG_IMG_H)

        self.image = pyxel.Image(WORLD_WIDTH, WORLD_HEIGHT)

        for sy in range(BG_IMG_H):
            for sx in range(BG_IMG_W):
                col = src.pget(sx, sy)
                for dy in range(BG_SCALE):
                    for dx in range(BG_SCALE):
                        self.image.pset(sx * BG_SCALE + dx, sy * BG_SCALE + dy, col)

        self.parallax_x = parallax_x
        self.parallax_y = parallax_y

    def draw(self, camera_x, camera_y, colkey=0):
        src_u = int(camera_x * self.parallax_x)
        src_v = int(camera_y * self.parallax_y)
        pyxel.blt(0, 0, self.image, src_u, src_v, VIEW_WIDTH, VIEW_HEIGHT, colkey)


class Game:
    def __init__(self):
        pyxel.init(VIEW_WIDTH, VIEW_HEIGHT, title="Trunk the boy")
        pyxel.load("res.pyxres")

        self.player_x = 20
        self.player_y = 80
        self.player_w = 16
        self.player_h = 16
        self.camera_x = 0
        self.camera_y = 0

        self.vel_x = 0
        self.vel_y = 0

        self.speed = 15
        self.jump_power = -5
        self.gravity = 0.4
        self.accel_ground = 0.8
        self.accel_air = 0.6
        self.friction_air = 0.85
        self.friction_ground = 0.8
        self.max_speed = 30

        self.on_ground = False
        self.tile_size = 8

        self.solid_tiles = [
            (2, 2), (2, 3), (3, 2), (3, 3),
            (2, 12), (2, 13), (3, 12), (3, 13),
            (2, 14), (3, 14), (2, 15), (3, 15),
        ]

        self.speed_tiles = [(2, 14), (3, 14), (2, 15), (3, 15)]
        self.speed_boost_timer = 0
        self.speed_boost_duration = 60

        self.on_wall_left = False
        self.on_wall_right = False
        self.wall_slide_speed = 1.2
        self.wall_jump_x = 3.5
        self.wall_jump_y = -5
        self.debug_text = ""
        self.facing_left = False

        self.current_level = 0
        self.teleport_cooldown = 0

        # Leben
        self.max_health = 3
        self.health = 3
        self.invincible_timer = 0
        self.invincible_duration = 45

        # Schaden / Tod Animation
        self.hurt_frames = [
            (40, 96), (56, 96), (72, 96), (88, 96),
            (104, 96), (120, 96), (136, 96)
        ]
        self.death_frames = [
            (40, 112), (56, 112), (72, 112), (88, 112),
            (104, 112), (120, 112), (136, 112)
        ]
        self.hurt_timer = 0
        self.hurt_duration = len(self.hurt_frames)
        self.death_timer = 0
        self.death_duration = len(self.death_frames)
        self.death_hold_duration = 120
        self.death_hold_timer = 0
        self.is_dead_anim = False

        # Herz-Sprite: Tile (5,30) -> Pixel (40,240)
        self.heart_u = 40
        self.heart_v = 240

        # Spawnpunkte je Level
        self.level_spawns = {
            0: (20, 80),
            1: (0, 0),
            2: (0, 0),
        }

        # Todeshöhe je Level
        self.level_death_y = {
            0: 40 * self.tile_size,
            1: 60 * self.tile_size,
            2: 60 * self.tile_size,
        }

        # Animationen Spieler
        self.idle_frames = [
            (40, 80),
            (56, 80),
            (72, 80),
            (88, 80)
        ]

        self.jump_up_frames = [
            (40, 32),
            (56, 32),
            (72, 32),
            (88, 32),
            (104, 32),
            (120, 32),
            (136, 32),
            (152, 32),
            (168, 32),
            (184, 32)
        ]

        self.jump_down_frames = [
            (40, 48),
            (56, 48),
            (72, 48),
            (88, 48),
            (104, 48),
            (120, 48),
            (136, 48),
            (152, 48),
            (168, 48),
            (184, 48)
        ]

        # Angriff
        self.attack_frames = [
            (40, 192),
            (56, 192),
            (72, 192),
            (88, 192),
            (104, 192),
            (120, 192),
        ]
        self.attack_duration = 6
        self.attack_timer = 0
        self.attack_hit_ids = set()

        self.land_timer = 0
        self.was_on_ground = False

        # Lava in Level 0
        self.lava_frames = [
            [(2, 4), (3, 4), (2, 5), (3, 5)],
            [(2, 6), (3, 6), (2, 7), (3, 7)],
        ]
        self.lava_positions = [
            (46, 16),
            (48, 16),
            (50, 16),
        ]

        # Wasser in Level 0
        self.water_frames = [
            [(2, 8), (3, 8), (2, 9), (3, 9)],
            [(2, 10), (3, 10), (2, 11), (3, 11)],
        ]
        self.water_positions = [
            (58, 14),
            (60, 14),
        ]

        # Portal
        self.portal_frames = [
            [(2, 16), (3, 16), (2, 17), (3, 17)],
            [(2, 18), (3, 18), (2, 19), (3, 19)],
        ]

        self.portal_positions_level0 = [
            (114, 32),
        ]

        self.portal_positions_level1 = [
            (114, 32),
        ]

        # Zielblock in Level 3 (current_level == 2)
        self.goal_frames_pixels = [
            (0, 64),   # Tile (0,8)
            (0, 80),   # Tile (0,10)
        ]
        self.goal_positions = [
            (56, 6),
            (58, 6),
            (60, 6),
        ]

        # Endzustand
        self.game_finished = False
        self.finish_message = "Well done, you helped Super Slime Boy reach his pond"

        # Introbildschirm
        self.show_intro = True

        # Springendes Lava-Monster in Level 0
        self.lava_monster_idle_frames = [
            (40, 128),
            (40, 144),
        ]
        self.lava_monster_jump_frames = [
            (56, 128),
            (72, 128),
            (88, 128),
            (104, 128),
        ]
        self.lava_monster_fall_frames = [
            (120, 128),
            (136, 128),
            (152, 128),
            (168, 128),
            (184, 128),
        ]

        self.lava_monster_x = 48 * self.tile_size
        self.lava_monster_base_y = 16 * self.tile_size - 8
        self.lava_monster_y = self.lava_monster_base_y
        self.lava_monster_vel_y = 0
        self.lava_monster_wait_timer = 0
        self.lava_monster_wait_duration = 60
        self.lava_monster_is_jumping = False
        self.lava_monster_jump_power = -6.2
        self.lava_monster_gravity = 0.28

        # Fallendes Objekt nur in Level 1
        self.fall_frames = [
            (40, 176),
            (56, 176),
        ]
        self.fall_x = 23 * self.tile_size
        self.fall_start_y = 7 * self.tile_size
        self.fall_end_y = 50 * self.tile_size
        self.fall_y = self.fall_start_y
        self.fall_vel_y = 0
        self.fall_gravity = 0.35
        self.fall_active = False
        self.fall_repeat_timer = 0
        self.fall_repeat_duration = 90

        # NPCs in Level 2
        self.npcs = [
            {
                "id": 0,
                "level": 2,
                "start_x": 32 * self.tile_size,
                "start_y": 16 * self.tile_size,
                "end_x": 50 * self.tile_size,
                "speed": 1.0,
                "frames": [(40, 208), (56, 208), (72, 208), (88, 208)],
                "active": True,
                "respawn_timer": 0,
                "facing_left": False,
            },
            {
                "id": 1,
                "level": 2,
                "start_x": 50 * self.tile_size,
                "start_y": 10 * self.tile_size,
                "end_x": 32 * self.tile_size,
                "speed": 1.0,
                "frames": [(40, 224), (56, 224), (72, 224), (88, 224)],
                "active": True,
                "respawn_timer": 0,
                "facing_left": True,
            }
        ]

        for npc in self.npcs:
            npc["x"] = npc["start_x"]
            npc["y"] = npc["start_y"]

        # Hintergrund-Layer laden
        self.backgrounds = [
            BackgroundLayer("back.pyxres", parallax_x=0.60, parallax_y=1.0),
            BackgroundLayer("mid.pyxres", parallax_x=0.80, parallax_y=1.0),
            BackgroundLayer("front.pyxres", parallax_x=1.00, parallax_y=1.0),
        ]

        # Hauptgrafik wieder laden
        pyxel.load(
            "res.pyxres",
            exclude_tilemaps=True,
            exclude_sounds=True,
            exclude_musics=True,
        )

        pyxel.run(self.update, self.draw)

    def random_respawn_time(self):
        return random.randint(60, 90)

    def load_level(self, level_index, spawn_x, spawn_y, keep_health=True):
        self.current_level = level_index
        self.player_x = spawn_x
        self.player_y = spawn_y
        self.vel_x = 0
        self.vel_y = 0
        self.camera_x = 0
        self.camera_y = 0
        self.on_ground = False
        self.on_wall_left = False
        self.on_wall_right = False
        self.land_timer = 0
        self.was_on_ground = False
        self.speed_boost_timer = 0
        self.teleport_cooldown = 15
        self.invincible_timer = 0
        self.attack_timer = 0
        self.attack_hit_ids.clear()
        self.hurt_timer = 0
        self.death_timer = 0
        self.death_hold_timer = 0
        self.is_dead_anim = False
        self.game_finished = False

        if not keep_health:
            self.health = self.max_health

        self.fall_y = self.fall_start_y
        self.fall_vel_y = 0
        self.fall_active = False
        self.fall_repeat_timer = 0

        for npc in self.npcs:
            npc["x"] = npc["start_x"]
            npc["y"] = npc["start_y"]
            npc["active"] = True
            npc["respawn_timer"] = 0

    def respawn_current_level(self):
        spawn_x, spawn_y = self.level_spawns[self.current_level]
        self.health = self.max_health
        self.load_level(self.current_level, spawn_x, spawn_y, keep_health=True)

    def start_death_animation(self):
        if self.is_dead_anim:
            return
        self.is_dead_anim = True
        self.death_timer = self.death_duration
        self.death_hold_timer = self.death_hold_duration
        self.attack_timer = 0
        self.hurt_timer = 0
        self.vel_x = 0
        self.vel_y = 0

    def rects_overlap(self, ax, ay, aw, ah, bx, by, bw, bh):
        return (
            ax + aw > bx and
            ax < bx + bw and
            ay + ah > by and
            ay < by + bh
        )

    def get_tile(self, x, y):
        tile_x = int(x // self.tile_size)
        tile_y = int(y // self.tile_size)

        if tile_x < 0 or tile_y < 0:
            return None

        return pyxel.tilemaps[self.current_level].pget(tile_x, tile_y)

    def is_solid(self, x, y):
        tile_x = int(x // self.tile_size)
        tile_y = int(y // self.tile_size)

        if tile_x < 0 or tile_y < 0:
            self.debug_text = "außerhalb"
            return False

        u, v = pyxel.tilemaps[self.current_level].pget(tile_x, tile_y)
        self.debug_text = f"map=({tile_x},{tile_y}) img=({u},{v}) lvl={self.current_level}"
        return (u, v) in self.solid_tiles

    def on_speed_tile(self):
        points = [
            (self.player_x + 1, self.player_y + self.player_h + 1),
            (self.player_x + self.player_w - 2, self.player_y + self.player_h + 1),
        ]

        for x, y in points:
            tile = self.get_tile(x, y)
            if tile is not None and tile in self.speed_tiles:
                return True

        return False

    def collide_down(self, x, y):
        return (
            self.is_solid(x + 1, y + self.player_h) or
            self.is_solid(x + self.player_w - 2, y + self.player_h)
        )

    def collide_up(self, x, y):
        return (
            self.is_solid(x + 1, y) or
            self.is_solid(x + self.player_w - 2, y)
        )

    def collide_left(self, x, y):
        return (
            self.is_solid(x - 1, y + 1) or
            self.is_solid(x - 1, y + self.player_h - 2)
        )

    def collide_right(self, x, y):
        return (
            self.is_solid(x + self.player_w, y + 1) or
            self.is_solid(x + self.player_w, y + self.player_h - 2)
        )

    def animate_lava(self):
        if self.current_level != 0:
            return

        frame_index = (pyxel.frame_count // 12) % len(self.lava_frames)
        frame = self.lava_frames[frame_index]

        for map_x, map_y in self.lava_positions:
            pyxel.tilemaps[self.current_level].pset(map_x, map_y, frame[0])
            pyxel.tilemaps[self.current_level].pset(map_x + 1, map_y, frame[1])
            pyxel.tilemaps[self.current_level].pset(map_x, map_y + 1, frame[2])
            pyxel.tilemaps[self.current_level].pset(map_x + 1, map_y + 1, frame[3])

    def animate_water(self):
        if self.current_level != 0:
            return

        frame_index = (pyxel.frame_count // 12) % len(self.water_frames)
        frame = self.water_frames[frame_index]

        for map_x, map_y in self.water_positions:
            pyxel.tilemaps[self.current_level].pset(map_x, map_y, frame[0])
            pyxel.tilemaps[self.current_level].pset(map_x + 1, map_y, frame[1])
            pyxel.tilemaps[self.current_level].pset(map_x, map_y + 1, frame[2])
            pyxel.tilemaps[self.current_level].pset(map_x + 1, map_y + 1, frame[3])

    def animate_portals(self):
        frame_index = (pyxel.frame_count // 12) % len(self.portal_frames)
        frame = self.portal_frames[frame_index]

        if self.current_level == 0:
            positions = self.portal_positions_level0
        elif self.current_level == 1:
            positions = self.portal_positions_level1
        else:
            return

        for map_x, map_y in positions:
            pyxel.tilemaps[self.current_level].pset(map_x, map_y, frame[0])
            pyxel.tilemaps[self.current_level].pset(map_x + 1, map_y, frame[1])
            pyxel.tilemaps[self.current_level].pset(map_x, map_y + 1, frame[2])
            pyxel.tilemaps[self.current_level].pset(map_x + 1, map_y + 1, frame[3])

    def touching_water(self):
        if self.current_level != 0:
            return False

        water_tiles = {
            (2, 8), (3, 8), (2, 9), (3, 9),
            (2, 10), (3, 10), (2, 11), (3, 11),
        }

        points = [
            (self.player_x + 1, self.player_y + 1),
            (self.player_x + self.player_w - 2, self.player_y + 1),
            (self.player_x + 1, self.player_y + self.player_h - 2),
            (self.player_x + self.player_w - 2, self.player_y + self.player_h - 2),
        ]

        for x, y in points:
            tile = self.get_tile(x, y)
            if tile is not None and tile in water_tiles:
                return True

        return False

    def touching_lava(self):
        if self.current_level != 0:
            return False

        lava_tiles = {
            (2, 4), (3, 4), (2, 5), (3, 5),
            (2, 6), (3, 6), (2, 7), (3, 7),
        }

        points = [
            (self.player_x + 1, self.player_y + 1),
            (self.player_x + self.player_w - 2, self.player_y + 1),
            (self.player_x + 1, self.player_y + self.player_h - 2),
            (self.player_x + self.player_w - 2, self.player_y + self.player_h - 2),
        ]

        for x, y in points:
            tile = self.get_tile(x, y)
            if tile is not None and tile in lava_tiles:
                return True

        return False

    def touching_portal(self):
        if self.current_level == 0:
            valid_positions = self.portal_positions_level0
        elif self.current_level == 1:
            valid_positions = self.portal_positions_level1
        else:
            return False

        portal_tiles = {
            (2, 16), (3, 16), (2, 17), (3, 17),
            (2, 18), (3, 18), (2, 19), (3, 19),
        }

        points = [
            (self.player_x + 1, self.player_y + 1),
            (self.player_x + self.player_w - 2, self.player_y + 1),
            (self.player_x + 1, self.player_y + self.player_h - 2),
            (self.player_x + self.player_w - 2, self.player_y + self.player_h - 2),
        ]

        for x, y in points:
            tile = self.get_tile(x, y)
            if tile is not None and tile in portal_tiles:
                return True

        for map_x, map_y in valid_positions:
            px = map_x * self.tile_size
            py = map_y * self.tile_size
            if (
                self.player_x + self.player_w > px and
                self.player_x < px + 16 and
                self.player_y + self.player_h > py and
                self.player_y < py + 16
            ):
                return True

        return False

    def touching_goal_block(self):
        if self.current_level != 2:
            return False

        for map_x, map_y in self.goal_positions:
            px = map_x * self.tile_size
            py = map_y * self.tile_size

            if self.rects_overlap(
                self.player_x, self.player_y, self.player_w, self.player_h,
                px, py, 16, 16
            ):
                return True

        return False

    def update_lava_monster(self):
        if self.current_level != 0:
            return

        if not self.lava_monster_is_jumping:
            self.lava_monster_wait_timer += 1
            if self.lava_monster_wait_timer >= self.lava_monster_wait_duration:
                self.lava_monster_wait_timer = 0
                self.lava_monster_is_jumping = True
                self.lava_monster_vel_y = self.lava_monster_jump_power
                self.lava_monster_y = self.lava_monster_base_y
        else:
            self.lava_monster_vel_y += self.lava_monster_gravity
            self.lava_monster_y += self.lava_monster_vel_y

            if self.lava_monster_y >= self.lava_monster_base_y:
                self.lava_monster_y = self.lava_monster_base_y
                self.lava_monster_vel_y = 0
                self.lava_monster_is_jumping = False

    def update_falling_object(self):
        if self.current_level != 1:
            self.fall_y = self.fall_start_y
            self.fall_vel_y = 0
            self.fall_active = False
            self.fall_repeat_timer = 0
            return

        if not self.fall_active:
            self.fall_repeat_timer += 1
            if self.fall_repeat_timer >= self.fall_repeat_duration:
                self.fall_repeat_timer = 0
                self.fall_active = True
                self.fall_y = self.fall_start_y
                self.fall_vel_y = 0
        else:
            self.fall_vel_y += self.fall_gravity
            self.fall_y += self.fall_vel_y

            if self.fall_y >= self.fall_end_y:
                self.fall_active = False
                self.fall_y = self.fall_start_y
                self.fall_vel_y = 0

    def update_npcs(self):
        for npc in self.npcs:
            if npc["level"] != self.current_level:
                continue

            if not npc["active"]:
                npc["respawn_timer"] -= 1
                if npc["respawn_timer"] <= 0:
                    npc["active"] = True
                    npc["x"] = npc["start_x"]
                    npc["y"] = npc["start_y"]
                continue

            if npc["start_x"] < npc["end_x"]:
                npc["x"] += npc["speed"]
                npc["facing_left"] = False

                if npc["x"] >= npc["end_x"]:
                    npc["active"] = False
                    npc["respawn_timer"] = self.random_respawn_time()
            else:
                npc["x"] -= npc["speed"]
                npc["facing_left"] = True

                if npc["x"] <= npc["end_x"]:
                    npc["active"] = False
                    npc["respawn_timer"] = self.random_respawn_time()

    def player_touches_level2_npc(self):
        for npc in self.npcs:
            if npc["level"] != self.current_level or not npc["active"]:
                continue

            if self.rects_overlap(
                self.player_x, self.player_y, self.player_w, self.player_h,
                npc["x"], npc["y"], 16, 16
            ):
                return True

        return False

    def player_touches_lava_monster(self):
        if self.current_level != 0:
            return False

        return self.rects_overlap(
            self.player_x, self.player_y, self.player_w, self.player_h,
            self.lava_monster_x, self.lava_monster_y, 16, 16
        )

    def player_touches_falling_object(self):
        if self.current_level != 1 or not self.fall_active:
            return False

        return self.rects_overlap(
            self.player_x, self.player_y, self.player_w, self.player_h,
            self.fall_x, self.fall_y, 16, 16
        )

    def player_touches_enemy(self):
        return (
            self.player_touches_level2_npc() or
            self.player_touches_lava_monster() or
            self.player_touches_falling_object()
        )

    def take_damage(self, amount=1):
        if self.invincible_timer > 0 or self.is_dead_anim:
            return

        self.health -= amount
        self.invincible_timer = self.invincible_duration
        self.hurt_timer = self.hurt_duration

        if self.health <= 0:
            self.start_death_animation()

    def get_attack_rect(self):
        if self.facing_left:
            return (self.player_x - 12, self.player_y + 4, 12, 8)
        return (self.player_x + self.player_w, self.player_y + 4, 12, 8)

    def update_attack_hits(self):
        if self.attack_timer <= 0 or self.current_level != 2:
            return

        ax, ay, aw, ah = self.get_attack_rect()

        for npc in self.npcs:
            if npc["level"] != 2 or not npc["active"]:
                continue
            if npc["id"] in self.attack_hit_ids:
                continue

            if self.rects_overlap(ax, ay, aw, ah, npc["x"], npc["y"], 16, 16):
                npc["active"] = False
                npc["respawn_timer"] = self.random_respawn_time()
                self.attack_hit_ids.add(npc["id"])

    def move_x(self, amount):
        if amount == 0:
            return

        direction = 1 if amount > 0 else -1
        steps = int(abs(amount))
        remainder = abs(amount) - steps

        for _ in range(steps):
            new_x = self.player_x + direction

            if direction > 0:
                if self.collide_right(new_x, self.player_y):
                    self.on_wall_right = True
                    tile_x = int((new_x + self.player_w) // self.tile_size)
                    self.player_x = tile_x * self.tile_size - self.player_w
                    self.vel_x = 0
                    return
            else:
                if self.collide_left(new_x, self.player_y):
                    self.on_wall_left = True
                    tile_x = int(new_x // self.tile_size)
                    self.player_x = (tile_x + 1) * self.tile_size
                    self.vel_x = 0
                    return

            self.player_x = new_x

        if remainder > 0:
            new_x = self.player_x + direction * remainder

            if direction > 0:
                if self.collide_right(new_x, self.player_y):
                    self.on_wall_right = True
                    tile_x = int((new_x + self.player_w) // self.tile_size)
                    self.player_x = tile_x * self.tile_size - self.player_w
                    self.vel_x = 0
                    return
            else:
                if self.collide_left(new_x, self.player_y):
                    self.on_wall_left = True
                    tile_x = int(new_x // self.tile_size)
                    self.player_x = (tile_x + 1) * self.tile_size
                    self.vel_x = 0
                    return

            self.player_x = new_x

    def move_y(self, amount):
        if amount == 0:
            return

        direction = 1 if amount > 0 else -1
        steps = int(abs(amount))
        remainder = abs(amount) - steps

        self.on_ground = False

        for _ in range(steps):
            new_y = self.player_y + direction

            if direction > 0:
                if self.collide_down(self.player_x, new_y):
                    tile_y = int((new_y + self.player_h) // self.tile_size)
                    self.player_y = tile_y * self.tile_size - self.player_h
                    self.vel_y = 0
                    self.on_ground = True
                    return
            else:
                if self.collide_up(self.player_x, new_y):
                    tile_y = int(new_y // self.tile_size)
                    self.player_y = (tile_y + 1) * self.tile_size
                    self.vel_y = 0
                    return

            self.player_y = new_y

        if remainder > 0:
            new_y = self.player_y + direction * remainder

            if direction > 0:
                if self.collide_down(self.player_x, new_y):
                    tile_y = int((new_y + self.player_h) // self.tile_size)
                    self.player_y = tile_y * self.tile_size - self.player_h
                    self.vel_y = 0
                    self.on_ground = True
                    return
            else:
                if self.collide_up(self.player_x, new_y):
                    tile_y = int(new_y // self.tile_size)
                    self.player_y = (tile_y + 1) * self.tile_size
                    self.vel_y = 0
                    return

            self.player_y = new_y

    def update(self):
        if pyxel.btnp(pyxel.KEY_Q):
            pyxel.quit()

        if self.show_intro:
            if (
                pyxel.btnp(pyxel.KEY_SPACE)
                or pyxel.btnp(pyxel.KEY_RETURN)
                or pyxel.btnp(pyxel.KEY_KP_ENTER)
            ):
                self.show_intro = False
            return

        if self.game_finished:
            if pyxel.btnp(pyxel.KEY_R):
                self.load_level(0, 20, 80, keep_health=False)
            return

        if self.is_dead_anim:
            if self.death_timer > 0:
                self.death_timer -= 1
            elif self.death_hold_timer > 0:
                self.death_hold_timer -= 1
            else:
                self.respawn_current_level()
            return

        if self.invincible_timer > 0:
            self.invincible_timer -= 1

        if self.hurt_timer > 0:
            self.hurt_timer -= 1

        if self.teleport_cooldown > 0:
            self.teleport_cooldown -= 1

        if self.attack_timer > 0:
            self.attack_timer -= 1

        if self.on_speed_tile():
            self.speed_boost_timer = self.speed_boost_duration
        elif self.speed_boost_timer > 0:
            self.speed_boost_timer -= 1

        self.animate_lava()
        self.animate_water()
        self.animate_portals()
        self.update_lava_monster()
        self.update_falling_object()
        self.update_npcs()

        if self.player_touches_enemy():
            self.take_damage(1)

        if self.player_y > self.level_death_y[self.current_level]:
            self.respawn_current_level()
            return

        if self.touching_lava():
            self.health = 0
            self.start_death_animation()
            return

        if self.teleport_cooldown == 0 and self.touching_water():
            self.load_level(1, 0, 0, keep_health=True)
            return

        if self.teleport_cooldown == 0 and self.touching_portal():
            if self.current_level == 0:
                self.load_level(1, 0, 0, keep_health=True)
            elif self.current_level == 1:
                self.load_level(2, 0, 0, keep_health=True)
            return

        if self.touching_goal_block():
            self.game_finished = True
            self.vel_x = 0
            self.vel_y = 0
            return

        self.on_wall_left = not self.on_ground and self.collide_left(self.player_x, self.player_y)
        self.on_wall_right = not self.on_ground and self.collide_right(self.player_x, self.player_y)

        if self.attack_timer == 0 and pyxel.btnp(pyxel.KEY_J):
            self.attack_timer = self.attack_duration
            self.attack_hit_ids.clear()
            self.vel_x = 0

        if self.on_speed_tile() or self.speed_boost_timer > 0:
            current_accel_ground = 1.4
            current_accel_air = 1.0
            current_friction_ground = 0.92
            current_friction_air = 0.92
            current_max_speed = 45
        else:
            current_accel_ground = self.accel_ground
            current_accel_air = self.accel_air
            current_friction_ground = self.friction_ground
            current_friction_air = self.friction_air
            current_max_speed = self.max_speed

        if self.on_ground:
            accel = current_accel_ground
            friction = current_friction_ground
        else:
            accel = current_accel_air
            friction = current_friction_air

        if self.attack_timer > 0 or self.hurt_timer > 0:
            self.vel_x = 0
        else:
            if pyxel.btn(pyxel.KEY_A):
                self.vel_x -= accel
                self.facing_left = True

            if pyxel.btn(pyxel.KEY_D):
                self.vel_x += accel
                self.facing_left = False

            self.vel_x *= friction

            if self.vel_x > current_max_speed:
                self.vel_x = current_max_speed
            if self.vel_x < -current_max_speed:
                self.vel_x = -current_max_speed

            if pyxel.btnp(pyxel.KEY_SPACE):
                if self.on_ground:
                    self.vel_y = self.jump_power
                    self.on_ground = False

                elif self.on_wall_left:
                    self.vel_y = self.wall_jump_y
                    self.vel_x = self.wall_jump_x

                elif self.on_wall_right:
                    self.vel_y = self.wall_jump_y
                    self.vel_x = -self.wall_jump_x

        self.move_x(self.vel_x)
        self.player_x = max(0, self.player_x)

        self.vel_y += self.gravity
        if not self.on_ground and (self.on_wall_left or self.on_wall_right):
            if self.vel_y > self.wall_slide_speed:
                self.vel_y = self.wall_slide_speed

        self.move_y(self.vel_y)

        self.update_attack_hits()

        if self.on_ground and not self.was_on_ground:
            self.land_timer = 8

        self.was_on_ground = self.on_ground

        if self.land_timer > 0:
            self.land_timer -= 1

        target_x = self.player_x - 130
        target_y = self.player_y - 100
        self.camera_x = clamp(target_x, 0, WORLD_WIDTH - VIEW_WIDTH)
        self.camera_y = clamp(target_y, 0, WORLD_HEIGHT - VIEW_HEIGHT)

    def draw_npcs(self):
        for npc in self.npcs:
            if npc["level"] != self.current_level or not npc["active"]:
                continue

            frame_index = (pyxel.frame_count // 6) % len(npc["frames"])
            u, v = npc["frames"][frame_index]
            width = -16 if npc["facing_left"] else 16

            pyxel.blt(
                npc["x"] - self.camera_x,
                npc["y"] - self.camera_y,
                0,
                u,
                v,
                width,
                16,
                4
            )

    def draw_goal_block(self):
        if self.current_level != 2:
            return

        frame_index = (pyxel.frame_count // 20) % len(self.goal_frames_pixels)
        u, v = self.goal_frames_pixels[frame_index]

        for map_x, map_y in self.goal_positions:
            px = map_x * self.tile_size - self.camera_x
            py = map_y * self.tile_size - self.camera_y

            pyxel.blt(
                px,
                py,
                0,
                u,
                v,
                16,
                16,
                4
            )

    def draw_hearts(self):
        for i in range(self.health):
            pyxel.blt(
                5 + i * 18,
                5,
                0,
                self.heart_u,
                self.heart_v,
                16,
                16,
                4
            )

    def draw_player(self):
        if self.is_dead_anim:
            if self.death_timer > 0:
                frame_index = self.death_duration - self.death_timer
            else:
                frame_index = len(self.death_frames) - 1

            frame_index = max(0, min(frame_index, len(self.death_frames) - 1))
            u, v = self.death_frames[frame_index]
            width = -16 if self.facing_left else 16

        elif self.hurt_timer > 0:
            frame_index = self.hurt_duration - self.hurt_timer
            frame_index = max(0, min(frame_index, len(self.hurt_frames) - 1))
            u, v = self.hurt_frames[frame_index]
            width = -16 if self.facing_left else 16

        elif self.attack_timer > 0:
            frame_index = self.attack_duration - self.attack_timer
            frame_index = max(0, min(frame_index, len(self.attack_frames) - 1))
            u, v = self.attack_frames[frame_index]
            width = -16 if self.facing_left else 16

        else:
            if self.on_ground:
                if self.land_timer > 0:
                    u, v = self.jump_down_frames[-1]
                elif abs(self.vel_x) > 0.3:
                    u, v = self.idle_frames[(pyxel.frame_count // 8) % len(self.idle_frames)]
                else:
                    u, v = self.idle_frames[0]
            else:
                if self.vel_y < 0:
                    frame = min(
                        pyxel.frame_count // 3 % len(self.jump_up_frames),
                        len(self.jump_up_frames) - 1
                    )
                    u, v = self.jump_up_frames[frame]
                else:
                    frame = min(
                        pyxel.frame_count // 3 % len(self.jump_down_frames),
                        len(self.jump_down_frames) - 1
                    )
                    u, v = self.jump_down_frames[frame]

            width = -16 if self.facing_left else 16

        draw_player = True
        if self.invincible_timer > 0 and not self.is_dead_anim:
            draw_player = (pyxel.frame_count // 3) % 2 == 0

        if draw_player:
            pyxel.blt(
                self.player_x - self.camera_x,
                self.player_y - self.camera_y,
                0,
                u,
                v,
                width,
                16,
                4
            )

    def draw(self):
        pyxel.cls(0)

        for bg in self.backgrounds:
            bg.draw(self.camera_x, self.camera_y, colkey=0)

        pyxel.bltm(0, 0, self.current_level, self.camera_x, self.camera_y, VIEW_WIDTH, VIEW_HEIGHT, 4)

        self.draw_goal_block()

        if self.current_level == 0:
            if not self.lava_monster_is_jumping:
                monster_u, monster_v = self.lava_monster_idle_frames[
                    (pyxel.frame_count // 12) % len(self.lava_monster_idle_frames)
                ]
            else:
                if self.lava_monster_vel_y < 0:
                    frame = min(
                        (pyxel.frame_count // 4) % len(self.lava_monster_jump_frames),
                        len(self.lava_monster_jump_frames) - 1
                    )
                    monster_u, monster_v = self.lava_monster_jump_frames[frame]
                else:
                    frame = min(
                        (pyxel.frame_count // 4) % len(self.lava_monster_fall_frames),
                        len(self.lava_monster_fall_frames) - 1
                    )
                    monster_u, monster_v = self.lava_monster_fall_frames[frame]

            pyxel.blt(
                self.lava_monster_x - self.camera_x,
                self.lava_monster_y - self.camera_y,
                0,
                monster_u,
                monster_v,
                16,
                16,
                4
            )

        if self.current_level == 1 and self.fall_active:
            fall_u, fall_v = self.fall_frames[(pyxel.frame_count // 10) % len(self.fall_frames)]
            pyxel.blt(
                self.fall_x - self.camera_x,
                self.fall_y - self.camera_y,
                0,
                fall_u,
                fall_v,
                16,
                16,
                4
            )

        self.draw_npcs()
        self.draw_player()
        self.draw_hearts()
        pyxel.text(5, 24, self.debug_text, 7)

        if self.game_finished:
            pyxel.rect(20, 90, 320, 60, 0)
            pyxel.rectb(20, 90, 320, 60, 7)
            pyxel.text(32, 108, self.finish_message, 7)
            pyxel.text(120, 128, "Press R to restart", 10)

        if self.show_intro:
            pyxel.rect(10, 20, 340, 150, 0)
            pyxel.rectb(10, 20, 340, 150, 7)
            pyxel.text(110, 35, "SUPER SLIME BOY", 7)
            pyxel.text(35, 65, "Walljump ist moeglich", 7)
            pyxel.text(35, 80, "Steuern mit WASD", 7)
            pyxel.text(35, 95, "Attacke mit J", 7)
            pyxel.text(35, 110, "Jump mit Leertaste", 7)
            pyxel.text(35, 125, "Ziel des Spiels: den Teich erreichen", 7)
            pyxel.text(95, 150, "Druecke SPACE oder ENTER", 10)


Game()