Page 1 of 1

Flying Mounts

Posted: Sun Jun 15, 2025 4:48 am
by dragon slayer
How to Add a Flying Mount in UOX3

If you want to make a mount capable of flight in UOX3, follow the steps below to add the required scripts:

Place the following two scripts in your custom JS folder:

flyinggump.js

flyingmount.js

Open your file_association.scp file and add the following entries to register the scripts:

(Add these lines after the existing entry for 15007=server/house/houseAddonUse.js)
16000=custom/flyinggump.js
16001=custom/flyingmount.js
Update the NPC Definition
In your mounts.dfn, add the following entries to any mount you want to make flyable:
custominttag=isFlyingMount 1
SCRIPT=16001
Once these steps are complete, your UOX3 server will be ready to handle flying mounts using the new scripts.
const Directions = {
    NONE: 0,
    UP: 1,
    NORTH: 2,
    RIGHT: 3,
    EAST: 4,
    DOWN: 5,
    SOUTH: 6,
    LEFT: 7,
    WEST: 8
};

function showFlyingGump(pUser, activeDir)
{
    var gump = new Gump;
    gump.AddPage(0);
    gump.AddBackground(30, 30, 200, 200, 5054);
   
    var directions = [
        { dir: Directions.UP, x: 85, y: 40, btn: 0x1194 },
        { dir: Directions.NORTH, x: 115, y: 50, btn: 0x1195 },
        { dir: Directions.RIGHT, x: 130, y: 80, btn: 0x1196 },
        { dir: Directions.EAST, x: 115, y: 115, btn: 0x1197 },
        { dir: Directions.DOWN, x: 85, y: 130, btn: 0x1198 },
        { dir: Directions.SOUTH, x: 50, y: 115, btn: 0x1199 },
        { dir: Directions.LEFT, x: 40, y: 80, btn: 0x119A },
        { dir: Directions.WEST, x: 50, y: 50, btn: 0x119B },
        { dir: Directions.NONE, x: 85, y: 85, btn: 0x1193 } // STOP button in center
    ];
   
    for (var i = 0; i < directions.length; i++)
    {
        var entry = directions[i];
        gump.AddButton(entry.x, entry.y, entry.btn, entry.btn, 1, 0, entry.dir);
        if (entry.dir == activeDir)
        {
            gump.AddGump(entry.x, entry.y, entry.btn, 69);
        }
    }

    gump.Send(pUser.socket);
    gump.Free();
}

function GetFacingDirectionFromButton(buttonID)
{
    switch(buttonID)
    {
        case 2: return 0;  // NORTH
        case 3: return 1;  // RIGHT (Northeast)
        case 4: return 2;  // EAST
        case 6: return 4;  // SOUTH
        case 7: return 5;  // LEFT (Southwest)
        case 8: return 6;  // WEST
        default: return null; // STOP, UP, DOWN have no facing
    }
}

function onGumpPress(pSock, buttonID)
{
    var pChar = pSock.currentChar;
   
    if(!ValidateObject(pChar))
        return;

    if(!pChar.GetTag("FlyingMounted"))
        return;

    if(buttonID == 0 && !pChar.GetTag("FlyingMounted")) // Stop button
        return;

    pChar.SetTag("flyingDirection", buttonID);
    showFlyingGump(pChar, buttonID);

    var facing = GetFacingDirectionFromButton(buttonID);
    if (facing != null)
    {
        pChar.direction = facing;
        //pChar.SysMessage("DEBUG: ButtonID=" + buttonID + " | Facing=" + facing);
    }

    if (!pChar.GetTag("isFlyingTimerRunning"))
    {
        pChar.SetTag("isFlyingTimerRunning", true);
        pChar.StartTimer(500, 1, 16000);
    }
}

function onTimer(pChar, timerID)
{
    if (timerID != 1)
        return;

    var dir = parseInt(pChar.GetTag("flyingDirection"), 10);
    if (dir == null || dir == 0)
    {
        pChar.KillTimers(1);
        pChar.SetTag("isFlyingTimerRunning", null);
        return;
    }

    if (pChar.dead)
    {
        pChar.KillTimers(1);
        pChar.SetTag("isFlyingTimerRunning", null);
        pChar.SetTag("flyingDirection", null);
        return;
    }


    if (!pChar.isOnline)
    {
       pChar.KillTimers(1);
       pChar.SetTag("isFlyingTimerRunning", null);
       pChar.SetTag("flyingDirection", null);
        return;
    }

    var newX = pChar.x;
    var newY = pChar.y;
    var newZ = pChar.z;

    switch(dir)
    {
        case Directions.UP:    newZ++; break;
        case Directions.DOWN:  newZ--; break;
        case Directions.NORTH: newY--; break;
        case Directions.SOUTH: newY++; break;
        case Directions.EAST:  newX++; break;
        case Directions.WEST:  newX--; break;
        case Directions.RIGHT: newX++; newY--; break;
        case Directions.LEFT:  newX--; newY++; break;
    }

    pChar.DoAction(24);
    pChar.SoundEffect(0x2D0, true);
    // Optional: Add trail effect
    // pChar.StaticEffect(0x377A, 0, 0);

    if (IsSafeFlying(pChar, newX, newY, newZ))
    {
        pChar.Teleport(newX, newY, newZ);
        pChar.StartTimer(300, 1, 16000); // continue flight if successful
    }
    else
    {
        // Flight blocked — stop flying
        pChar.KillTimers(1);
        pChar.SetTag("isFlyingTimerRunning", null);
        pChar.SetTag("flyingDirection", null);
        pChar.SysMessage("Flight path blocked.");
    }
}

function IsSafeFlying(pChar, newX, newY, newZ)
{
    var world = pChar.worldnumber;
    var instance = pChar.instanceID;
    const MAX_FLIGHT_HEIGHT = 140;

    //pChar.SysMessage("Testing flying to: X=" + newX + " Y=" + newY + " Z=" + newZ);

    var mapBlock = DoesMapBlock(newX, newY, newZ, world, false, false, false, false);
    var staticFlag = CheckStaticFlag(newX, newY, newZ, world, 6);
    var dynamicBlock = DoesDynamicBlock(newX, newY, newZ, world, instance, false, false, false, false);

    //if (mapBlock)
    //   pChar.SysMessage("Map Blocked");

    //if (staticFlag)
     //   pChar.SysMessage("Static Blocked");

    //if (dynamicBlock)
     //   pChar.SysMessage("Dynamic Blocked");

         // Block flying into dungeon zone
    if (newX >= 5117)
        return false;

    // Block Lost Lands no-fly zone
    if (newX >= 5123 && newX <= 6140 && newY >= 2305 && newY <= 4093)
        return false;

     var bounds = GetMaxBounds(world);

    // Prevent out-of-map flying
    if (newX < 0 || newY < 0 || newX >= bounds.maxX || newY >= bounds.maxY)
        return false;

    if (newZ >= MAX_FLIGHT_HEIGHT)
        return false;

    if( newZ <= GetMapElevation( newX, newY, world ))
        return false;

    if (mapBlock || staticFlag || dynamicBlock)
        return false;

    return true;
}

function GetMaxBounds(world)
{
    switch(world)
    {
        case 0: return { maxX: 6144, maxY: 4096 }; // Felucca
        case 1: return { maxX: 6144, maxY: 4096 }; // Trammel
        case 2: return { maxX: 2304, maxY: 1600 }; // Ilshenar
        case 3: return { maxX: 2560, maxY: 2048 }; // Malas
        case 4: return { maxX: 1448, maxY: 1448 }; // Tokuno
        case 5: return { maxX: 1280, maxY: 4096 }; // TerMur
        default: return { maxX: 6144, maxY: 4096 }; // default fallback
    }
}
function onCharDoubleClick(pUser, targChar)
{
    // Only handle this for flying mounts
    if (!targChar.GetTag("isFlyingMount"))
        return false;

    if (!pUser.InRange(targChar, 1))
    {
        pUser.SysMessage("You are too far away.");
        return false;
    }

    TriggerEvent(16000, "showFlyingGump", pUser, 0);
    pUser.SetTag("FlyingMounted", true);

    return true;
}

function onDismount( pUser, npcMount )
{
    // Dismounting, so let's stop the disco effect
    pUser.KillJSTimer( 1, 16000 );
    pUser.SetTag("FlyingMounted", null);
}