👨‍💻 dev pet animation

development

fukurou

the supreme coder
ADMIN

🧩 GOAL​


A desktop pet / VTuber window that:


  • floats on the desktop
  • animates (idle, blink, talk)
  • stays always‑on‑top
  • runs at 60 FPS
  • never blocks your main program
  • communicates with your main logic (e.g., “talk”, “blink”, “angry”, “idle”)

This is the same pattern used by commercial VTuber overlays.




🧠 ARCHITECTURE (THE ONLY ONE THAT NEVER JAMS)​


Main Program (LivinGrimoire engine)


  • async
  • skills
  • STT
  • TTS
  • logic

VTuberRenderer (separate process)


  • handles window
  • handles animation
  • handles transparency
  • handles FPS loop

Communication channel


  • multiprocessing.Queue
  • or multiprocessing.Pipe

This is the canonical pattern for non‑blocking UI.




🛠️ WALKTHROUGH STEP 1​


Create the VTuber Renderer Process​


vtuber_renderer.py:

Python:
import pygame
import multiprocessing
import time
import os

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames for animation
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle
    w, h = current.get_size()

    screen = pygame.display.set_mode((w, h), pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    last_blink = time.time()

    while True:
        # Handle commands from main program
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "talk":
                current = talk
            elif cmd == "idle":
                current = idle
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                break

        # Auto-blink every 4 seconds
        if time.time() - last_blink > 4:
            current = blink
            pygame.display.update()
            time.sleep(0.1)
            current = idle
            last_blink = time.time()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return

        screen.blit(current, (0, 0))
        pygame.display.update()
        time.sleep(1/60)  # 60 FPS

    pygame.quit()


This is a real desktop pet loop:


  • 60 FPS
  • idle/talk/blink states
  • non-blocking
  • accepts commands



🛠️ WALKTHROUGH STEP 2​


Start the VTuber process from your main program​


main.py:

Python:
import multiprocessing
import time
from vtuber_renderer import vtuber_process

if __name__ == "__main__":
    cmd_queue = multiprocessing.Queue()
    p = multiprocessing.Process(target=vtuber_process, args=(cmd_queue,))
    p.start()

    # Example: your main program runs normally
    while True:
        print("Main program running...")

        # Make the VTuber talk
        cmd_queue.put("talk")
        time.sleep(1)

        # Back to idle
        cmd_queue.put("idle")
        time.sleep(2)


Result:
Your main program runs smoothly, and the VTuber window animates independently.




🛠️ WALKTHROUGH STEP 3​


Integrate with LivinGrimoire Skill System​


You wrap the renderer in a skill:

Python:
class VTuberSkill:
    def __init__(self):
        self.cmd_queue = multiprocessing.Queue()
        self.p = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.p.start()

    def talk(self):
        self.cmd_queue.put("talk")

    def idle(self):
        self.cmd_queue.put("idle")

    def blink(self):
        self.cmd_queue.put("blink")

    def kill(self):
        self.cmd_queue.put("quit")
        self.p.join()


Now any skill can do:

vtuber_skill.talk()
vtuber_skill.idle()


This is exactly how you integrate a desktop pet into a modular skill engine.




🧊 WALKTHROUGH STEP 4​


Add transparency + always-on-top (optional)​


If you want the pet floating on the desktop:


  • Windows: use pywin32 to set layered window
  • Linux: use xlib
  • Mac: use NSWindow flags

I can give you the exact code for your OS.




🧨 WHY THIS NEVER JAMS​


Because:


  • Rendering is in another process
  • Python GIL cannot block it
  • Your AI logic can spike CPU and the VTuber stays smooth
  • Communication is async and non-blocking

This is the correct architecture for a desktop VTuber.
 
Last edited:

fukurou

the supreme coder
ADMIN
update
renderer:
Python:
import pygame
import time

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    # Force a fixed window size (change to whatever you want)
    WINDOW_SIZE = (400, 600)

    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    # Allow keyboard events even with NOFRAME
    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(True)

    last_blink = time.time()

    running = True
    while running:

        # Commands from main
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "talk":
                current = talk
            elif cmd == "idle":
                current = idle
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # Auto-blink
        if time.time() - last_blink > 4:
            current = blink
            time.sleep(0.1)
            current = idle
            last_blink = time.time()

        # Events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break

            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # Stretch image to window size
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()

main:
Python:
import multiprocessing
import time
from vtuber_renderer import vtuber_process

if __name__ == "__main__":
    cmd_queue = multiprocessing.Queue()
    p = multiprocessing.Process(target=vtuber_process, args=(cmd_queue,))
    p.start()

    running = True

    while running:
        # Check for quit signal from renderer
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "quit":
                print("Renderer requested exit.")
                running = False
                break

        print("Main program running...")

        cmd_queue.put("talk")
        time.sleep(1)

        cmd_queue.put("idle")
        time.sleep(2)

    # Cleanup
    cmd_queue.put("quit")
    p.join()
    print("Exited cleanly.")
 

fukurou

the supreme coder
ADMIN
update 2, image on bottom left, mouse unstuck:
Python:
import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    # Window size
    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    # Allow keyboard events
    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)  # <-- FIX mouse trapping

    # ---- POSITION WINDOW BOTTOM LEFT ----
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32

    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)

    x = 0
    y = screen_h - WINDOW_SIZE[1]

    ctypes.windll.user32.SetWindowPos(
        hwnd, None, x, y, 0, 0, 0x0001
    )
    # -------------------------------------

    last_blink = time.time()
    running = True

    while running:

        # Commands from main
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "talk":
                current = talk
            elif cmd == "idle":
                current = idle
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # Auto-blink
        if time.time() - last_blink > 4:
            current = blink
            time.sleep(0.1)
            current = idle
            last_blink = time.time()

        # Events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break

            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # Stretch image
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()

Python:
import multiprocessing
import time
from vtuber_renderer import vtuber_process

if __name__ == "__main__":
    cmd_queue = multiprocessing.Queue()
    p = multiprocessing.Process(target=vtuber_process, args=(cmd_queue,))
    p.start()

    running = True

    while running:
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "quit":
                print("Renderer requested exit.")
                running = False
                break

        print("Main program running...")

        cmd_queue.put("talk")
        time.sleep(1)

        cmd_queue.put("idle")
        time.sleep(2)

    cmd_queue.put("quit")
    p.join()
    print("Exited cleanly.")
 

fukurou

the supreme coder
ADMIN
skill code:
Python:
import multiprocessing

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        # Start pygame renderer in a SEPARATE PROCESS
        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        # Animation frames in order
        self.frames = ["idle", "talk", "blink"]
        self.index = 0

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):
        # Pick next frame
        frame = self.frames[self.index]

        # Send it to renderer
        self.cmd_queue.put(frame)

        # Advance index
        self.index = (self.index + 1) % len(self.frames)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles pet animation frames every tick."
        elif param == "triggers":
            return "none (automatic cycling)"
        return "note unavailable"

vtuber_renderer file:
Python:
import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    # Window size
    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    # Allow keyboard events
    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)  # <-- FIX mouse trapping

    # ---- POSITION WINDOW BOTTOM LEFT ----
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32

    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)

    x = 0
    y = screen_h - WINDOW_SIZE[1]

    ctypes.windll.user32.SetWindowPos(
        hwnd, None, x, y, 0, 0, 0x0001
    )
    # -------------------------------------

    last_blink = time.time()
    running = True

    while running:

        # Commands from main
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "talk":
                current = talk
            elif cmd == "idle":
                current = idle
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # Auto-blink
        if time.time() - last_blink > 4:
            current = blink
            time.sleep(0.1)
            current = idle
            last_blink = time.time()

        # Events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break

            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # Stretch image
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()
 

fukurou

the supreme coder
ADMIN
skill
Python:
import multiprocessing

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        # Start pygame renderer in a SEPARATE PROCESS
        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        # Animation frames in order
        self.frames = ["idle", "talk", "blink"]
        self.index = 0

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):
        # Pick next frame
        frame = self.frames[self.index]

        # Send it to renderer
        self.cmd_queue.put(frame)

        # Advance index
        self.index = (self.index + 1) % len(self.frames)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles pet animation frames every tick."
        elif param == "triggers":
            return "none (automatic cycling)"
        return "note unavailable"
renderer:
Python:
# --- silence pygame startup spam ---
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    # Window size
    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    # Allow keyboard events
    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)  # <-- FIX mouse trapping

    # ---- POSITION WINDOW BOTTOM LEFT ----
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32

    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)

    x = 0
    # y = screen_h - WINDOW_SIZE[1]
    y = 0

    ctypes.windll.user32.SetWindowPos(
        hwnd, None, x, y, 0, 0, 0x0001
    )
    # -------------------------------------

    last_blink = time.time()
    running = True

    while running:

        # Commands from main
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "talk":
                current = talk
            elif cmd == "idle":
                current = idle
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # Auto-blink
        if time.time() - last_blink > 4:
            current = blink
            time.sleep(0.1)
            current = idle
            last_blink = time.time()

        # Events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break

            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # Stretch image
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()
 

fukurou

the supreme coder
ADMIN
Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        # Start pygame renderer in a SEPARATE PROCESS
        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        # Auto-close when program exits
        atexit.register(self.close)

        # Animation frames in order
        self.frames = ["idle", "talk", "blink"]
        self.index = 0

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        # Pick next frame
        frame = self.frames[self.index]

        # Send it to renderer
        self.cmd_queue.put(frame)

        # Advance index
        self.index = (self.index + 1) % len(self.frames)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles pet animation frames every tick."
        elif param == "triggers":
            return "none (automatic cycling)"
        return "note unavailable"

vtuber renderer:

Python:
# --- silence pygame startup spam ---
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    # Window size
    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    # Allow keyboard events
    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)  # <-- FIX mouse trapping

    # ---- POSITION WINDOW BOTTOM LEFT ----
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32

    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)

    x = 0
    # y = screen_h - WINDOW_SIZE[1]
    y = 0

    ctypes.windll.user32.SetWindowPos(
        hwnd, None, x, y, 0, 0, 0x0001
    )
    # -------------------------------------

    last_blink = time.time()
    running = True

    while running:

        # Commands from main
        if not cmd_queue.empty():
            cmd = cmd_queue.get()
            if cmd == "talk":
                current = talk
            elif cmd == "idle":
                current = idle
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # Auto-blink
        if time.time() - last_blink > 4:
            current = blink
            time.sleep(0.1)
            current = idle
            last_blink = time.time()

        # Events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break

            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # Stretch image
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()
 

fukurou

the supreme coder
ADMIN
skill experiment with one turn animation cycle

Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        # Start pygame renderer in a SEPARATE PROCESS
        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        # Auto-close when program exits
        atexit.register(self.close)

        # Full animation sequence
        self.full_cycle = ["idle", "talk", "blink", "idle"]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        # Send a FULL animation cycle instantly (non-blocking)
        for frame in self.full_cycle:
            self.cmd_queue.put(frame)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Sends a full animation cycle every tick."
        elif param == "triggers":
            return "none (automatic cycling)"
        return "note unavailable"
 

fukurou

the supreme coder
ADMIN
Python:
# --- silence pygame startup spam ---
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)

    # Window position
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32
    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)
    ctypes.windll.user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, 0x0001)

    # --- NEW animation state ---
    animation_queue = []
    animation_index = 0
    animation_counter = 0

    last_blink = time.time()
    running = True

    while running:

        # --- COMMAND HANDLING ---
        if not cmd_queue.empty():
            cmd = cmd_queue.get()

            # NEW: animation script
            if isinstance(cmd, list):
                animation_queue = cmd
                animation_index = 0
                animation_counter = 0

            elif cmd == "idle":
                current = idle
            elif cmd == "talk":
                current = talk
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # --- PLAY ANIMATION SCRIPT ---
        if animation_queue:
            frame_name, duration = animation_queue[animation_index]

            if frame_name == "idle":
                current = idle
            elif frame_name == "talk":
                current = talk
            elif frame_name == "blink":
                current = blink

            animation_counter += 1

            if animation_counter >= duration:
                animation_counter = 0
                animation_index += 1

                if animation_index >= len(animation_queue):
                    animation_queue = []
                    animation_index = 0

        # --- AUTO BLINK (only if not animating) ---
        if not animation_queue and time.time() - last_blink > 4:
            current = blink
            last_blink = time.time()

        # --- EVENTS ---
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # --- DRAW ---
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()

new skill code: with animation shit inside the ass

Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        # Start pygame renderer in a SEPARATE PROCESS
        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        # Auto-close when program exits
        atexit.register(self.close)

        # (frame_name, duration_in_frames_at_60fps)
        # tweak these numbers to taste
        # self.full_cycle = [
        #     ("idle", 40),
        #     ("talk", 20),
        #     ("blink", 10),
        #     ("idle", 40)
        # ]
        self.full_cycle = [
            ("idle", 12),
            ("pause", 6),

            ("talk", 8),
            ("pause", 6),

            ("blink", 4),
            ("pause", 6),

            ("idle", 12),
            ("pause", 6)
        ]

    def close(self):
        # Try to shut down renderer cleanly
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):
        # Non-blocking: just enqueue a full animation script
        # Renderer consumes it over time at 60 FPS
        if len(ear)>0:
            self.cmd_queue.put(self.full_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Sends a timed full animation cycle to the pet renderer every tick."
        elif param == "triggers":
            return "none (automatic cycling)"
        return "note unavailable"
 

fukurou

the supreme coder
ADMIN
with dirs:
renderer:
Python:
# --- silence pygame startup spam ---
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("DLC/images/happy/idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)

    # Window position
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32
    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)
    ctypes.windll.user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, 0x0001)

    # --- NEW animation state ---
    animation_queue = []
    animation_index = 0
    animation_counter = 0

    last_blink = time.time()
    running = True

    while running:

        # --- COMMAND HANDLING ---
        if not cmd_queue.empty():
            cmd = cmd_queue.get()

            # NEW: animation script
            if isinstance(cmd, list):
                animation_queue = cmd
                animation_index = 0
                animation_counter = 0

            elif cmd == "idle":
                current = idle
            elif cmd == "talk":
                current = talk
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # --- PLAY ANIMATION SCRIPT ---
        if animation_queue:
            frame_name, duration = animation_queue[animation_index]

            if frame_name == "idle":
                current = idle
            elif frame_name == "talk":
                current = talk
            elif frame_name == "blink":
                current = blink

            animation_counter += 1

            if animation_counter >= duration:
                animation_counter = 0
                animation_index += 1

                if animation_index >= len(animation_queue):
                    animation_queue = []
                    animation_index = 0

        # --- AUTO BLINK (only if not animating) ---
        if not animation_queue and time.time() - last_blink > 4:
            current = blink
            last_blink = time.time()

        # --- EVENTS ---
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # --- DRAW ---
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()

skill:
Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        # Start pygame renderer in a SEPARATE PROCESS
        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        # Auto-close when program exits
        atexit.register(self.close)

        # (frame_name, duration_in_frames_at_60fps)
        # tweak these numbers to taste
        # self.full_cycle = [
        #     ("idle", 40),
        #     ("talk", 20),
        #     ("blink", 10),
        #     ("idle", 40)
        # ]
        self.full_cycle = [
            ("idle", 12),
            ("pause", 6),

            ("talk", 8),
            ("pause", 6),

            ("blink", 4),
            ("pause", 6),

            ("idle", 12),
            ("pause", 6)
        ]

    def close(self):
        # Try to shut down renderer cleanly
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):
        # Non-blocking: just enqueue a full animation script
        # Renderer consumes it over time at 60 FPS
        if len(ear)>0:
            self.cmd_queue.put(self.full_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Sends a timed full animation cycle to the pet renderer every tick."
        elif param == "triggers":
            return "none (automatic cycling)"
        return "note unavailable"
(skill code the same as last post)
 

fukurou

the supreme coder
ADMIN
skill code upgrade:
Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()

        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        atexit.register(self.close)

        # --- Animation scripts (60 FPS durations) ---
        self.idle_cycle = [
            ("idle", 60),
            ("blink", 10),
            ("idle", 60)
        ]

        self.talk_cycle = [
            ("talk", 20),  # was 10
            ("blink_talk", 8),  # was 4
            ("talk", 20),  # was 10
            ("idle", 16)  # was 8
        ]

        self.long_talk_cycle = [
            # --- first run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8),

            # --- second run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8)
        ]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        if len(ear) > 0:
            self.cmd_queue.put(self.long_talk_cycle)
        else:
            self.cmd_queue.put(self.idle_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles idle or talking animations depending on ear input."
        elif param == "triggers":
            return "none (automatic)"
        return "note unavailable"
 

fukurou

the supreme coder
ADMIN
skill update:
Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.AXPython import PercentDripper
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()
        self.dripper = PercentDripper()
        self.dripper.setLimit(20)

        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        atexit.register(self.close)

        # --- Animation scripts (60 FPS durations) ---
        self.idle_cycle = [
            ("idle", 60),
            ("blink", 10),
            ("idle", 60)
        ]

        self.talk_cycle = [
            ("talk", 20),  # was 10
            ("blink_talk", 8),  # was 4
            ("talk", 20),  # was 10
            ("idle", 16)  # was 8
        ]

        self.long_talk_cycle = [
            # --- first run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8),

            # --- second run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8)
        ]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        if len(ear) > 0:
            words = ear.strip().split()
            for _ in words:
                # talk animation
                self.cmd_queue.put(self.long_talk_cycle)
        else:
            if self.dripper.drip():
                self.cmd_queue.put(self.idle_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles idle or talking animations depending on ear input."
        elif param == "triggers":
            return "none (automatic)"
        return "note unavailable"
 

fukurou

the supreme coder
ADMIN
Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.AXPython import PercentDripper
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()
        self.dripper = PercentDripper()
        self.dripper.setLimit(20)

        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        atexit.register(self.close)

        # --- Animation scripts (60 FPS durations) ---
        self.idle_cycle = [
            ("idle", 60),
            ("blink", 10),
            ("idle", 60)
        ]

        self.talk_cycle = [
            ("talk", 20),  # was 10
            ("blink_talk", 8),  # was 4
            ("talk", 20),  # was 10
            ("idle", 16)  # was 8
        ]

        self.long_talk_cycle = [
            # --- first run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8),

            # --- second run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8)
        ]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        if len(ear) > 0:
            words = ear.strip().split()

            full_script = []
            for _ in words:
                full_script += self.long_talk_cycle
                full_script += [("idle", 6)]  # closed mouth between words

            self.cmd_queue.put(full_script)
            return

        else:
            if self.dripper.drip():
                self.cmd_queue.put(self.idle_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles idle or talking animations depending on ear input."
        elif param == "triggers":
            return "none (automatic)"
        return "note unavailable"
skill
 

fukurou

the supreme coder
ADMIN
renderer:
Python:
# --- silence pygame startup spam ---
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes

def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("DLC/images/happy/idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)

    # Window position
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32
    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)
    ctypes.windll.user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, 0x0001)

    # --- NEW animation state ---
    animation_queue = []
    animation_index = 0
    animation_counter = 0

    last_blink = time.time()
    running = True

    while running:

        # --- COMMAND HANDLING ---
        if not cmd_queue.empty():
            cmd = cmd_queue.get()

            # NEW: animation script
            if isinstance(cmd, list):
                animation_queue = cmd
                animation_index = 0
                animation_counter = 0

            elif cmd == "idle":
                current = idle
            elif cmd == "talk":
                current = talk
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # --- PLAY ANIMATION SCRIPT ---
        if animation_queue:
            frame_name, duration = animation_queue[animation_index]

            if frame_name == "idle":
                current = idle
            elif frame_name == "talk":
                current = talk
            elif frame_name == "blink":
                current = blink

            animation_counter += 1

            if animation_counter >= duration:
                animation_counter = 0
                animation_index += 1

                if animation_index >= len(animation_queue):
                    animation_queue = []
                    animation_index = 0

        # --- AUTO BLINK (only if not animating) ---
        if not animation_queue and time.time() - last_blink > 4:
            current = idle
            last_blink = time.time()

        # --- EVENTS ---
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # --- DRAW ---
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()

skill:
Python:
import multiprocessing
import atexit

from DLC.vtuber_renderer import vtuber_process
from LivinGrimoirePacket.AXPython import PercentDripper
from LivinGrimoirePacket.LivinGrimoire import Skill


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()
        self.dripper = PercentDripper()
        self.dripper.setLimit(20)

        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        atexit.register(self.close)

        # --- Animation scripts (60 FPS durations) ---
        self.idle_cycle = [
            ("idle", 60),
            ("blink", 10),
            ("idle", 60)
        ]

        self.talk_cycle = [
            ("talk", 10),  # was 10
            ("blink_talk", 8),  # was 4
            ("talk", 10),  # was 10
            ("idle", 16)  # was 8
        ]

        self.long_talk_cycle = [
            # --- first run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8),

            # --- second run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8)
        ]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        if len(ear) > 0:
            words = ear.strip().split()

            full_script = []
            for _ in words:
                full_script += self.talk_cycle
                full_script += [("idle", 6)]  # closed mouth between words

            self.cmd_queue.put(full_script)
            return

        else:
            if self.dripper.drip():
                self.cmd_queue.put(self.idle_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles idle or talking animations depending on ear input."
        elif param == "triggers":
            return "none (automatic)"
        return "note unavailable"
 

owly

闇の伝説
Staff member
戦闘 コーダー
now! I use my spell card!!!
polymarization to fuse the 2 code together!

Python:
# --- silence pygame startup spam ---
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes
import multiprocessing
import atexit

from LivinGrimoirePacket.AXPython import PercentDripper
from LivinGrimoirePacket.LivinGrimoire import Skill


def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("DLC/images/happy/idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)

    # Window position
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32
    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)
    ctypes.windll.user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, 0x0001)

    # --- NEW animation state ---
    animation_queue = []
    animation_index = 0
    animation_counter = 0

    last_blink = time.time()
    running = True

    while running:

        # --- COMMAND HANDLING ---
        if not cmd_queue.empty():
            cmd = cmd_queue.get()

            # NEW: animation script
            if isinstance(cmd, list):
                animation_queue = cmd
                animation_index = 0
                animation_counter = 0

            elif cmd == "idle":
                current = idle
            elif cmd == "talk":
                current = talk
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # --- PLAY ANIMATION SCRIPT ---
        if animation_queue:
            frame_name, duration = animation_queue[animation_index]

            if frame_name == "idle":
                current = idle
            elif frame_name == "talk":
                current = talk
            elif frame_name == "blink":
                current = blink

            animation_counter += 1

            if animation_counter >= duration:
                animation_counter = 0
                animation_index += 1

                if animation_index >= len(animation_queue):
                    animation_queue = []
                    animation_index = 0

        # --- AUTO BLINK (only if not animating) ---
        if not animation_queue and time.time() - last_blink > 4:
            current = idle
            last_blink = time.time()

        # --- EVENTS ---
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # --- DRAW ---
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1/60)

    pygame.quit()


class DiPetCycle(Skill):
    def __init__(self):
        super().__init__()
        self.dripper = PercentDripper()
        self.dripper.setLimit(20)

        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        atexit.register(self.close)

        # --- Animation scripts (60 FPS durations) ---
        self.idle_cycle = [
            ("idle", 60),
            ("blink", 10),
            ("idle", 60)
        ]

        self.talk_cycle = [
            ("talk", 10),  # was 10
            ("blink_talk", 8),  # was 4
            ("talk", 10),  # was 10
            ("idle", 16)  # was 8
        ]

        self.long_talk_cycle = [
            # --- first run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8),

            # --- second run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8)
        ]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        if len(ear) > 0:
            words = ear.strip().split()

            full_script = []
            for _ in words:
                full_script += self.talk_cycle
                full_script += [("idle", 6)]  # closed mouth between words

            self.cmd_queue.put(full_script)
            return

        else:
            if self.dripper.drip():
                self.cmd_queue.put(self.idle_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles idle or talking animations depending on ear input."
        elif param == "triggers":
            return "none (automatic)"
        return "note unavailable"


# Optional: If you want to run this directly for testing
if __name__ == "__main__":
    pet = DiPetCycle()
    
    # Keep the main thread alive
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pet.close()
 

fukurou

the supreme coder
ADMIN
Python:
# --- silence pygame startup spam ---
import os

os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"

import warnings

warnings.filterwarnings("ignore", category=UserWarning, module="pygame")

import pygame
import time
import ctypes
import multiprocessing
import atexit

from LivinGrimoirePacket.AXPython import PercentDripper
from LivinGrimoirePacket.LivinGrimoire import Skill


def vtuber_process(cmd_queue):
    pygame.init()

    # Load frames
    idle = pygame.image.load("DLC/images/happy/idle.png")
    talk = pygame.image.load("talk.png")
    blink = pygame.image.load("blink.png")

    current = idle

    WINDOW_SIZE = (400, 600)
    screen = pygame.display.set_mode(WINDOW_SIZE, pygame.NOFRAME)
    pygame.display.set_caption("DesktopPet")

    pygame.event.set_allowed([pygame.QUIT, pygame.KEYDOWN])
    pygame.event.set_grab(False)

    # Window position
    hwnd = pygame.display.get_wm_info()["window"]
    user32 = ctypes.windll.user32
    screen_w = user32.GetSystemMetrics(0)
    screen_h = user32.GetSystemMetrics(1)
    ctypes.windll.user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, 0x0001)

    # --- NEW animation state ---
    animation_queue = []
    animation_index = 0
    animation_counter = 0

    last_blink = time.time()
    running = True

    while running:

        # --- COMMAND HANDLING ---
        if not cmd_queue.empty():
            cmd = cmd_queue.get()

            # NEW: animation script
            if isinstance(cmd, list):
                animation_queue = cmd
                animation_index = 0
                animation_counter = 0

            elif cmd == "idle":
                current = idle
            elif cmd == "talk":
                current = talk
            elif cmd == "blink":
                current = blink
            elif cmd == "quit":
                running = False
                break

        # --- PLAY ANIMATION SCRIPT ---
        if animation_queue:
            frame_name, duration = animation_queue[animation_index]

            if frame_name == "idle":
                current = idle
            elif frame_name == "talk":
                current = talk
            elif frame_name == "blink":
                current = blink

            animation_counter += 1

            if animation_counter >= duration:
                animation_counter = 0
                animation_index += 1

                if animation_index >= len(animation_queue):
                    animation_queue = []
                    animation_index = 0

        # --- AUTO BLINK (only if not animating) ---
        if not animation_queue and time.time() - last_blink > 4:
            current = idle
            last_blink = time.time()

        # --- EVENTS ---
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                cmd_queue.put("quit")
                running = False
                break
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                cmd_queue.put("quit")
                running = False
                break

        # --- DRAW ---
        scaled = pygame.transform.smoothscale(current, WINDOW_SIZE)
        screen.blit(scaled, (0, 0))
        pygame.display.update()

        time.sleep(1 / 60)

    pygame.quit()


class DiPNGTuber(Skill):
    # add animation to the software
    def __init__(self):
        super().__init__()
        self.set_skill_lobe(2)  # output lobe
        self.dripper = PercentDripper()
        self.dripper.setLimit(20)

        self.cmd_queue = multiprocessing.Queue()
        self.process = multiprocessing.Process(
            target=vtuber_process,
            args=(self.cmd_queue,)
        )
        self.process.start()

        atexit.register(self.close)

        # --- Animation scripts (60 FPS durations) ---
        self.idle_cycle = [
            ("idle", 60),
            ("blink", 10),
            ("idle", 60)
        ]

        self.talk_cycle = [
            ("talk", 10),  # was 10
            ("blink_talk", 8),  # was 4
            ("talk", 10),  # was 10
            ("idle", 16)  # was 8
        ]

        self.long_talk_cycle = [
            # --- first run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8),

            # --- second run ---
            ("talk", 10),
            ("blink_talk", 4),
            ("talk", 10),
            ("idle", 8)
        ]

    def close(self):
        try:
            self.cmd_queue.put("quit")
        except:
            pass

        try:
            if self.process.is_alive():
                self.process.kill()
        except:
            pass

    # Called once every 2 seconds by your engine
    def input(self, ear: str, skin: str, eye: str):

        if len(ear) > 0:
            words = ear.strip().split()

            full_script = []
            for _ in words:
                full_script += self.talk_cycle
                full_script += [("idle", 6)]  # closed mouth between words

            self.cmd_queue.put(full_script)
            return

        else:
            if self.dripper.drip():
                self.cmd_queue.put(self.idle_cycle)

    def skillNotes(self, param: str) -> str:
        if param == "notes":
            return "Cycles idle or talking animations depending on ear input."
        elif param == "triggers":
            return "none (automatic)"
        return "note unavailable"


# Optional: If you want to run this directly for testing
if __name__ == "__main__":
    pet = DiPNGTuber()

    # Keep the main thread alive
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pet.close()

DLC:
Python:
from DLC.vtuber_skill import DiPNGTuber
from LivinGrimoirePacket.LivinGrimoire import Brain


def add_DLC_skills(brain: Brain):
    brain.add_skill(DiPNGTuber())
 
Top