Skip to content
ZSolarDev edited this page Nov 12, 2026 · 4 revisions

Simple Text Rendering

Spr exposes a function called drawSimpleText, which allows you to draw any text you want at any color and resolution at any position with a set font and clip. Below is a simple demo of how to use it to render the current song in the PV select menu.

package;

import diva.Diva;
import diva.types.display.spr.Spr;
import diva.types.pv_db.PvDB;

class Main {
    // Stores the name of the currently selected PV.
    static var curPvName:String = '';

    // Mod DLL entrypoint.
    public static function main() {
        Diva.initMod(); // Initialize the mod.
        
        // Make a hook that runs code when the PV select menu updates.
        Diva.hookManager.registerMenuHook(OnMenuUpdate((ctx) -> {
            // Use the exposed PV ID of the currently selected PV, and use it to
            // find its PV entry in the database to retrieve its name as a string.
            curPvName = PvDB.findPVEntry(ctx.selection.pvId)[0].songName.toString();
        }));

        // Make a hook that runs code when the PV select menu is being drawn.
        Diva.hookManager.registerMenuHook(OnMenuDraw(() -> {
            // Draw simple text with parameters:
            // x: center(cause center param is true), y: 100, res: 2(smaller res = bigger text), 
            // prio: 20, text: (text below), center: true(centers on x axis), color: white, and no clip.
            Spr.drawSimpleText(0, 100, 2, 20, 'Current song: $curPvName', true, 0xFFFFFFFF, null);
        }));
    }
}

This demo produces the following result.

As you can see, there is our "Current song:" text, centered at the top of the screen.


Task System

The task system is simple to use, with all you need being a class which extends diva.task.DTask, and to register it via Diva.taskManager.registerTask(name, task);. Below is a demo which draws text using the task system.

import diva.Diva;
import diva.task.DTask;
import diva.types.display.spr.Spr;

class Main {
    // Mod DLL entrypoint.
    public static function main() {
        Diva.initMod(); // Initialize the mod.
        // Register our new test task.
        Diva.taskManager.registerTask('test', new TestTask());
    }
}

// Make a new task called TestTask, extending the base DTask.
class TestTask extends DTask {
    // Override the draw function, which is called every frame before presenting the current frame.
    // Unlike other available draw hooks available, this one isn't restricted to a specific game state.
    // The same thing applies to a task's update function.
    override public function draw() {
        // Draw simple text with parameters:
        // x: center(cause center param is true), y: 150, res: 5(smaller res = bigger text),
        // prio: 20, text: (text below), center: true(centers on x axis), color: white, and no clip.
        Spr.drawSimpleText(0, 150, 5, 20, 'Drawing from a task.', true, 0xFFFFFFFF, null);
    }
}

This demo produces the following result.

As you can see, there is our "Drawing from a task." text, centered at the top of the screen. A task has more capability than just drawing, and to see that, look here.


ImGui

Hx4Diva features built-in ImGui support, done via linc_imgui as well as linc_directx. ImGui is fairly simple to use, so I suggest to use it for any debug UI's you need. Below is a simple demo, showing the ImGui demo window.

package;

import diva.Diva;
import imgui.ImGui;
import imgui.ImGuiDx11;

class Main {
    // Mod DLL entrypoint.
    public static function main() {
        Diva.initMod(); // Initialize the mod.

        // Make a hook that runs code on Direct3D initialization.
        Diva.hookManager.registerModHook(D3DInit((swapChain, device, deviceContext) -> {
            // Initialize ImGui with the swap chain, device, and device context.
            ImGuiDx11.init(swapChain, device, deviceContext);
        }));

        // Make a hook that runs code on each frame.
        Diva.hookManager.registerModHook(OnFrame((swapChain) -> {
            // Begin ImGui, show the demo window, and end ImGui with the swap chain.
            ImGuiDx11.begin();
            ImGui.showDemoWindow();
            ImGuiDx11.end(swapChain);
        }));
    }
}

This demo produces the following result.

As you can see, the ImGui demo UI is fully functional.


AETs

AET's put simply is Project DIVA's system for loading animated sprites, and it comes from Adobe After Effects.

Adding Custom AETs

You can create your own AET elements with the use of AetContainer. It's an abstraction that makes it easier to interact with Project DIVA's AET system, and it originates from the mod New Classics.. To get layer names, you should download MikuMikuModel from MikuMikuLibrary, open your Project DIVA cpk, look under one of the rom's, go to 2d, and open any aet___.bin file. You can dig deep into the tree to fin each composition and its layer names. Below is a demo of how to create your own AET element in gameplay.

package;

import diva.Diva;
import diva.types.display.aet.Aet;
import diva.types.display.aet.AetElementContainer;
import diva.types.display.aet.AetFlags;
import diva.types.display.aet.AetResMode;

class Main {
    // If the AET has started playing or not.
    static var played = false;
    // The container for the AET element.
    static var container:AetElementContainer = null;

    // Mod DLL entrypoint.
    public static function main() {
        Diva.initMod(); // Initialize the mod.

        // Make a hook that runs code once the current PV starts.
        Diva.hookManager.registerGameHook(OnPvInitComplete(() -> {
            // Create a new element container, and initialize played.
            container = new AetElementContainer();
            played = false;
        }));

        // Make a hook that runs code about each frame that the PV is being updated.
        Diva.hookManager.registerGameHook(OnPvUpdate(() -> {
            // If the container isnt initialized or it has already started,
            // don't do anything.
            if (container == null || played)
                return;

            // Assign a variable to the element to prevent the need
            // to re-fetch it multiple times (clean.)
            var element = container.element;
            // Find the ID of the scene we want to get our layer from (AET_GAM_CMN_MAIN),
            // and set the element of the scene to the ID we fetched.
            element.setScene(Aet.findAetScene('AET_GAM_CMN_MAIN')[0].id);
            // Set the layer we want to play on the element, with name: tgtbg_sankaku,
            // flags: Loop, prio: 5, resMode; FHD(1920x1080), with no start/end markers or defined position.
            // This function also stsrts playing the AET. This returns a bool, with false if
            // it failed to load the AET for whatever reason.
            element.setLayer('tgtbg_sankaku', AetFlags.Loop, 5, AetResMode.Res_FHD, '', '', null);
            // Make the element half transparent.
            element.setOpacity(0.5);
            // Mark played as true, so we don't set this up constantly.
            played = true;
        }));

        // Make a hook that runs code once the gameplay exits back to the menu.
        Diva.hookManager.registerGameHook(OnPvExit(() -> {
            // Clean up the container when leaving the PV, so we don't
            // leak it or carry it over into the next song.
            if (container != null)
                container.destroy();
            container = null;
        }));
    }
}

This demo produces the following result.

As you can see, there is a rainbow half-transparent spinning triangle, looping it animation forever. That's our custom AET element. A good resource for base-game AET scene and set names and ID's are located at DIVA Mod Archive, with the spreadsheet for sets being here, and the spreadsheet for scenes being here.

Modifying Existing AETs in Gameplay

In addition to creating your own AET elements, you can also modify already existing ones. Modifying existing ones means you don't have the simplicity that AetContainer/AetElement offers, but the simplicity isn't as necessary, because the AET isn't owned by you by default. Because you can't use AetContainer or AetElement, you need to interact with them directly, via Aet. Each target in the game actually exposes its target AET, the button's AET, and the AET of the target effect. In the demo below, I'll be modifying the target's AET.

package;

import cpp.RawPointer;
import diva.Diva;
import diva.types.Vec3;
import diva.types.display.aet.Aet;

class Main {
    // Mod DLL entrypoint.
    public static function main() {
        Diva.initMod(); // Initialize the mod.

        // Make a hook that runs code when each target's kiseki is being drawn.
        // Technically it's specifically for drawing it's kiseki, but we really
        // just need a hook that runs for every target alive, and this will satisfy
        // that condition.
        Diva.hookManager.registerGameHook(OnDrawKiseki((ctx) -> {
            // Put the target's AET into a variable so we can use it
            // without having to get it from the context every time. (clean)
            var targetAet = ctx.target.targetAet;

            // Stop the target's AET, and make its "clock hand" go to 360 degrees.
            Aet.setPlay(targetAet, false);
            Aet.setFrame(targetAet, 360);

            // Create a new scale for the target's AET, and set it.
            var scale = new Vec3(1.5, 1.5, 1.5);
            // We need to use addressOf() to get a pointer to the scale variable,
            // as that's what the function expects.
            Aet.setScale(targetAet, RawPointer.addressOf(scale));
        }));
    }
}

This demo produces the following result.

As you can see, each target has its "clock hand" pointed straight up at 360 degrees, and their scale is larger.

Clone this wiki locally