the cutting room mirror

Bookworm Adventures Volume 2

This article has a talk page!
view on tcrf.net

This page contains notes for the game Bookworm Adventures Volume 2.

To do:
Compare with compiled variants.

The scripts/common directory has the following uncompiled Lua scripts.

App.lua:

class App;

def gApp;
def gCApp;

function App:Init(theCObj)
{
    def mCObj = theCObj;
    gApp = self;
    gCApp = theCObj;
}

function App:WrapWidget(theClass, theDefaultClass, theCObj)
{
    if (theClass == nil)
        theClass = theDefaultClass;
    local aNewObj = theClass:new(theCObj);
    return aNewObj;}

function App:CreateLuaWidget(theClass)
{
    return WrapWidget(theClass, LuaWidget,
        mCObj:CreateLuaWidget());
}

function App:CreateHyperlinkWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaHyperlinkWidget, 
        mCObj:CreateLuaHyperlinkWidget(theId, theListener.mCObj));
}

function App:CreateLabelWidget(theListener, theClass)
{
    return WrapWidget(theClass, LuaLabelWidget, 
        mCObj:CreateLuaLabelWidget());
}

function App:CreateButtonWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaButtonWidget, 
        mCObj:CreateLuaButtonWidget(theId, theListener.mCObj));
}

function App:CreateCheckboxWidget(theUncheckedImage, theCheckedImage, theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaCheckboxWidget, 
        mCObj:CreateLuaCheckboxWidget(theUncheckedImage, theCheckedImage, theId, theListener.mCObj));
}

function App:CreateScrollbarWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaScrollbarWidget, 
        mCObj:CreateLuaScrollbarWidget(theId, theListener.mCObj));
}

function App:CreateEditWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaEditWidget, 
        mCObj:CreateLuaEditWidget(theId, theListener.mCObj));
}

function App:CreateListWidget(theId, theFont, theListener, theClass)
{
    return WrapWidget(theClass, LuaListWidget, 
        mCObj:CreateLuaListWidget(theId, theFont, theListener.mCObj));
}

function App:CreateDialog(theComponentImage, theButtonComponentImage,
    theId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode, theClass)
{
    local aWidget = WrapWidget(theClass, LuaWidget, 
        mCObj:CreateLuaDialog(theComponentImage, theButtonComponentImage,
        theId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode));
    gCApp:AddDialog(aWidget.mCObj);
    return aWidget;
}

function App:DialogButtonPress(theDialogId, theButtonId)
{
}
    
function App:DialogButtonDepress(theDialogId, theButtonId)
{
    mCObj:KillDialog(theDialogId);
}

RegisterLinkedClass("App", App);

ClassLink.lua:

def gLinkedClassConstructors = {};
def gLinkedClasses = {};
def gLinkedObjectTable = {};
def gLinkedDynCallTable = {};

def gLinkedClassNames = {};

function RegisterLinkedClass(theClassName, theClass)
{
    //print("RegisterLinkedClass: "..theClassName);
    gLinkedClasses[theClassName] = theClass;
}

function RegisterLinkedConstructor(theClassName, theConstructor)
{
    gLinkedClassConstructors[theClassName] = theConstructor;
}

function CreateLinkedObject(theClassName, theCObj)  
{
    if (gLinkedObjectTable[theCObj] != nil)
        return;
    
    gLinkedClassNames[theCObj] = theClassName;
    
    local aClass = gLinkedClasses[theClassName];
    if (aClass)
    {
        gLinkedDynCallTable.a = aClass["new"];
        local aLuaObject = gLinkedDynCallTable@aClass:a(theCObj);
        gLinkedObjectTable[theCObj] = aLuaObject;
    }
    else
    {
        local aConstructor = gLinkedClassConstructors[theClassName];
        local aLuaObject = aConstructor(theCObj);
        gLinkedObjectTable[theCObj] = aLuaObject;
    }
}

function SetLinkedObject(theCObj, theLuaObject)
{
    :gLinkedObjectTable[theCObj] = theLuaObject;
}

function DeleteLinkedObject(theCObj)
{
    local anObj = :gLinkedObjectTable[theCObj];
    if (anObj == nil)
        return;
    
    //print("DeleteLinkedObject: class = "..gLinkedClassNames[theCObj]..", address = "..tostring(theCObj));
    gLinkedClassNames[theCObj] = nil;
    
    if (table.contains(anObj, "CObjDeleted"))
        anObj:CObjDeleted();

    :gLinkedObjectTable[theCObj] = nil;
}

function CallLinkedMethod(theCObject, theMethodName, ...)
{
    local aLuaObject = gLinkedObjectTable[theCObject];  
    :gLinkedDynCallTable.a = aLuaObject[theMethodName]; 
    return gLinkedDynCallTable@aLuaObject:a(unpack(arg));
}

function GetLinkedLuaObject(theCObject)
{
    return gLinkedObjectTable[theCObject];
}

Common.lua:

require("ClassLink.lua");
require("LuaApp.lua");
require("LuaWidget.lua");
require("DProps.lua");
require("LuaCommonWidgets.lua");
require("Consts.lua");
require("QRand.lua");

function Color(r, g, b, a)
{
    if (a == nil)
        return {r=r, g=g, b=b, a=255};
    else
        return {r=r, g=g, b=b, a=a};
}

function IColor(r, g, b, a)
{
    if (a ~= nil)
        return (a*0x1000000)+(r*0x10000)+(g*0x100)+b;
    else
        return (0xFF000000)+(r*0x10000)+(g*0x100)+b;
}

function Rect(x, y, width, height)
{
    return {mX=x, mY=y, mWidth=width, mHeight=height};
}

function RectContains(theRect, x, y)
{
    return ((x >= theRect.mX) && (y >= theRect.mY) &&
        (x < theRect.mX + theRect.mWidth) && (y < theRect.mY + theRect.mHeight));
}

function RectUnpack(theRect)
{
    return theRect.mX, theRect.mY, theRect.mWidth, theRect.mHeight;
}

function RectIntersects(theRect1, theRect2)
{
    return !((theRect2.mX + theRect2.mWidth <= theRect1.mX) ||
            (theRect2.mY + theRect2.mHeight <= theRect1.mY) ||
            (theRect2.mX >= theRect1.mX + theRect1.mWidth) ||
            (theRect2.mY >= theRect1.mY + theRect1.mHeight));
}

function RectIntersection(theRect1, theRect2)
{
    local x1 = Math.max(theRect1.mX, theRect2.mX);
    local x2 = Math.min(theRect1.mX + theRect1.mWidth, theRect2.mX + theRect2.mWidth);
    local y1 = Math.max(theRect1.mY, theRect2.mY);
    local y2 = Math.min(theRect1.mY + theRect1.mHeight, theRect2.mY + theRect2.mHeight);
    if (x2 - x1 < 0 || y2 - y1 < 0)
        return Rect(0,0,0,0);
    else
        return Rect(x1, y1, x2 - x1, y2 - y1);
}

function Point(x, y) { return {mX = x, mY = y}; }
function Vector(x, y) { return {mX = x, mY = y}; }
function VecDotProd(vec1, vec2) { return vec1.mX*vec2.mX + vec1.mY*vec2.mY; }
function VecMagnitude(theVec) { return Math.sqrt(VecDotProd(theVec, theVec)); }
function VecNormalize(theVec) { local aMag = VecMagnitude(theVec); return VecScale(theVec, 1/aMag); }
function VecScale(theVec, theScale) { return { mX = theVec.mX*theScale, mY = theVec.mY*theScale }; }


function RandFloat()
{
    return (Rand()%100000000)/100000000.0;
}

function DupTable(theTable)
{
    local aNewTab = {};
    for aKey, aVal in theTable
    {
        aNewTab[aKey] = aVal;
    }
    return aNewTab;
}

function GetDirectoryFromPos(thePos)
{
    local aFileName = string.sub(thePos[0], 1);

    local aSlashPos = 0;
    for (i < string.len(aFileName))
        if (string.sub(aFileName, i, 1) == "\\")
            aSlashPos = i;
    
    return string.sub(aFileName, 0, aSlashPos);
}

function URLEncode(theString)
{
    local aHexChars = "0123456789ABCDEF";
    local aString = "";
    
    if (theString == nil) return aString;

    for (i < string.len(theString))
    {
        local aPattern = string.sub(theString,i,1);
        if (string.find(aPattern, "[%w]") == nil) aPattern = "%"..aPattern; // escape non-alphanumeric characters
        if (string.sub(theString,i,1) == " ")
            aString = aString.."+";
        else if (string.find("?&%+\r\n\t", aPattern) != nil)
        {
            aString = aString.."%";
            aString = aString..string.sub(aHexChars,bit.band(int(string.byte(theString,i)/16),0xF),1);
            aString = aString..string.sub(aHexChars,bit.band(int(string.byte(theString,i)),0xF),1);
        }           
        else
        {
            aString = aString..string.sub(theString,i,1);
        }
    }
    return aString;
}

Consts.lua:

def KEYCODE = {};
setprot(KEYCODE);
const KEYCODE.TAB               = 0x09;
const KEYCODE.RETURN            = 0x0D;
const KEYCODE.SHIFT             = 0x10;
const KEYCODE.CONTROL           = 0x11;
const KEYCODE.MENU              = 0x12;
const KEYCODE.ALT               = 0x12;
const KEYCODE.ESCAPE            = 0x1B;
const KEYCODE.PGUP              = 0x21;
const KEYCODE.PGDN              = 0x22;
const KEYCODE.END               = 0x23;
const KEYCODE.HOME              = 0x24;
const KEYCODE.LEFT              = 0x25;
const KEYCODE.UP                = 0x26;
const KEYCODE.RIGHT             = 0x27;
const KEYCODE.DOWN              = 0x28;
const KEYCODE.INSERT            = 0x2D;
const KEYCODE.DELETE            = 0x2E;

def D3DIMAGEFLAGS = {};
setprot(D3DIMAGEFLAGS);
const D3DIMAGEFLAGS.MinimizeNumSubdivisions = 0x0001;
const D3DIMAGEFLAGS.Use64By64Subdivisions   = 0x0002;
const D3DIMAGEFLAGS.UseA4R4G4B4             = 0x0004;
const D3DIMAGEFLAGS.UseA8R8G8B8             = 0x0008;

def CURSOR = {};
setprot(CURSOR);
const CURSOR.POINTER            = 0;
const CURSOR.HAND               = 1;
const CURSOR.DRAGGING           = 2;
const CURSOR.TEXT               = 3;
const CURSOR.CIRCLE_SLASH       = 4;
const CURSOR.SIZEALL            = 5;
const CURSOR.SIZENESW           = 6;
const CURSOR.SIZENS             = 7;
const CURSOR.SIZENWSE           = 8;
const CURSOR.SIZEWE             = 9;
const CURSOR.WAIT               = 10;
const CURSOR.NONE               = 11;
const CURSOR.CUSTOM             = 12;

def DIALOG = {};
setprot(DIALOG);
def DIALOG.BUTTONS = {};
setprot(DIALOG.BUTTONS);
const DIALOG.BUTTONS.NONE       = 0;
const DIALOG.BUTTONS.YES_NO     = 1;
const DIALOG.BUTTONS.OK_CANCEL  = 2;
const DIALOG.BUTTONS.FOOTER     = 3;
def DIALOG.ID = {};
setprot(DIALOG.ID);
const DIALOG.ID.YES             = 1000;
const DIALOG.ID.NO              = 1001;
const DIALOG.ID.OK              = 1000;
const DIALOG.ID.CANCEL          = 1001;
const DIALOG.ID.FOOTER          = 1000;

def DRAWMODE = {};
setprot(DRAWMODE);
const DRAWMODE.NORMAL           = 0;
const DRAWMODE.ADDITIVE         = 1;

DProps.lua:

class DProps : LuaWidget;

class DPropsShared
{
    def mWidgetTypes = {};
    def mWidgetTypeOrder = {};
    def mObjects = {};
    def mWidgetToLuaWidget = {};
    def mCurAddWidgetType;
    def mLastAddWidgetX;
    def mLastAddWidgetY;
    def mGridOn;
    def mGridSizeX;
    def mGridSizeY;
    def mGridOfsX;
    def mGridOfsY;
}

function DPropsShared:Load()
{
    mLastAddWidgetX = gCApp:RegistryRead("LastAddWidgetX");
    mLastAddWidgetY = gCApp:RegistryRead("LastAddWidgetY");
    mGridOn = gCApp:RegistryRead("GridOn", false);
    mGridSizeX = gCApp:RegistryRead("GridSizeX", 8);
    mGridSizeY = gCApp:RegistryRead("GridSizeY", 8);
    mGridOfsX = gCApp:RegistryRead("GridOfsX", 0);
    mGridOfsY = gCApp:RegistryRead("GridOfsY", 0);
}

function DPropsShared:Save()
{
    gCApp:RegistryWrite("LastAddWidgetX", mLastAddWidgetX);
    gCApp:RegistryWrite("LastAddWidgetY", mLastAddWidgetY);
    gCApp:RegistryWrite("GridOn", mGridOn);
    gCApp:RegistryWrite("GridSizeX", mGridSizeX);
    gCApp:RegistryWrite("GridSizeY", mGridSizeY);
    gCApp:RegistryWrite("GridOfsX", mGridOfsX);
    gCApp:RegistryWrite("GridOfsY", mGridOfsY);
}

DProps.mShared = DPropsShared:new();
DProps.mEditing = false;

function dptrack(thePos, theTrackInfo)
{
    local anObj = theTrackInfo[0];
    
    // Get object tab
    local anObjTab = DProps.mShared.mObjects[anObj];
    
    // Get file table
    local aFileTab = anObjTab.mFiles[thePos[0]];
    if (aFileTab == nil)
    {
        aFileTab = {};
        anObjTab.mFiles[thePos[0]] = aFileTab;
    }
    
    // Store setting information
    aFileTab[thePos[1]] = {theTrackInfo[1], theTrackInfo[2], theTrackInfo[3], theTrackInfo[4]};
    
    // Get props table
    for (aPropNum < *theTrackInfo[3])
    {
        anObjTab.mProps[theTrackInfo[3][aPropNum]] = theTrackInfo[4][aPropNum];
        local aPropsPos = anObjTab.mPropsPos[theTrackInfo[3][aPropNum]];
        if (aPropsPos != nil)
            anObjTab.mFiles[aPropsPos[0]][aPropsPos[1]] = nil; // Remove the old entry
        anObjTab.mPropsPos[theTrackInfo[3][aPropNum]] = thePos;
    }
}

function DProps.AddWidgetType(theName, theClass, theImage, theImageNum)
{
    if (*DProps.mShared.mWidgetTypeOrder == 0)
        DProps.mShared.mCurAddWidgetType = theName;
    
    if (DProps.mShared.mWidgetTypes[theName] == nil)
        DProps.mShared.mWidgetTypeOrder[*DProps.mShared.mWidgetTypeOrder] = theName;
    
    DProps.mShared.mWidgetTypes[theName] = 
        {mName=theName, mClass=theClass, mImage=theImage, mImageNum=theImageNum};
}

function DProps.WidgetCreated(theWidget)
{
    local aCWidget = theWidget.mCObj:GetWidget();
    DProps.mShared.mWidgetToLuaWidget[aCWidget] = theWidget;
    
    local anObjTab = {mProps={}, mPropsPos={}, mFiles={}, mDrawOrder=0};
    DProps.mShared.mObjects[theWidget] = anObjTab;
}

function DProps.WidgetDeleted(theWidget)
{
    DProps.mShared.mObjects[theWidget] = nil;
    local aCWidget = theWidget.mCObj:GetWidget();
    DProps.mShared.mWidgetToLuaWidget[aCWidget] = nil;
}

//////////////////////////////////////////////////////////////////////////

function DProps.DProps_Loader(theCObj)
{
    DProps.mShared:Load();
    require("DProps_Editor.lua");
    return DProps:new(theCObj);
}

RegisterLinkedConstructor("DPropsWidget", DProps.DProps_Loader);

LuaApp.lua:

class LuaApp;

def gApp;
def gCApp;

function LuaApp:Init(theCObj)
{
    def mCObj = theCObj;
    gApp = self;
    gCApp = theCObj;
}

function LuaApp:WrapWidget(theClass, theDefaultClass, theCObj)
{
    if (theClass == nil)
        theClass = theDefaultClass;
    local aNewObj = theClass:new(theCObj);
    return aNewObj;}

function LuaApp:CreateLuaWidget(theClass)
{
    return WrapWidget(theClass, LuaWidget,
        mCObj:CreateLuaWidget());
}

function LuaApp:CreateHyperlinkWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaHyperlinkWidget, 
        mCObj:CreateLuaHyperlinkWidget(theId, theListener.mCObj));
}

function LuaApp:CreateTextWidget(theClass)
{
    return WrapWidget(theClass, LuaTextWidget, 
        mCObj:CreateLuaTextWidget());
}

function LuaApp:CreateButtonWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaButtonWidget, 
        mCObj:CreateLuaButtonWidget(theId, theListener.mCObj));
}

function LuaApp:CreateCheckboxWidget(theUncheckedImage, theCheckedImage, theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaCheckboxWidget, 
        mCObj:CreateLuaCheckboxWidget(theUncheckedImage, theCheckedImage, theId, theListener.mCObj));
}

function LuaApp:CreateScrollbarWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaScrollbarWidget, 
        mCObj:CreateLuaScrollbarWidget(theId, theListener.mCObj));
}

function LuaApp:CreateEditWidget(theId, theListener, theClass)
{
    return WrapWidget(theClass, LuaEditWidget, 
        mCObj:CreateLuaEditWidget(theId, theListener.mCObj));
}

function LuaApp:CreateListWidget(theId, theFont, theListener, theClass)
{
    return WrapWidget(theClass, LuaListWidget, 
        mCObj:CreateLuaListWidget(theId, theFont, theListener.mCObj));
}

function LuaApp:CreateDialog(theComponentImage, theButtonComponentImage,
    theId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode, theClass)
{
    local aWidget = WrapWidget(theClass, LuaWidget, 
        mCObj:CreateLuaDialog(theComponentImage, theButtonComponentImage,
        theId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode));
    gCApp:AddDialog(aWidget.mCObj);
    return aWidget;
}

function LuaApp:DialogButtonPress(theDialogId, theButtonId)
{
}
    
function LuaApp:DialogButtonDepress(theDialogId, theButtonId)
{
    mCObj:KillDialog(theDialogId);
}

function LuaApp:DebugKeyDown(theKey)
{
    return mCObj:DebugKeyDown(theKey);
}

RegisterLinkedClass("LuaApp", LuaApp);

LuaCommonWidgets.lua:

//////////////////////////////////////////////////////////////////////////

class LuaDialog : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaDialog:Create(theComponentImage, theButtonComponentImage,
    theId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode)
{
    local aWidget = gApp:CreateDialog(theComponentImage, theButtonComponentImage,
        theId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode, self);
    gCApp:GetWidgetManager():AddWidget(aWidget.mCObj);
    return aWidget;
}

//////////////////////////////////////////////////////////////////////////

class LuaButtonWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaButtonWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateButtonWidget(theId, theParent, self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaButtonWidget:SetLabel(theStr)
{
    mCObj:SetLabel(theStr);
}

function LuaButtonWidget:SetLabel_DYN(thePos, theStr)
{
    SetLabel(theStr);
    dptrack(thePos, {self, SetLabel, 0, {"Label"}, {theStr}});
}

function LuaButtonWidget.DPGetDefaultSize()
{
    return 80, 24;
}

function LuaButtonWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaButtonWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:SetLabel_DYN(thePos, "Button");
    thePos[1]++;
    aWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight);
    
    return aWidget, {"local aButton = LuaButtonWidget:Create(-1, self);",
        "aButton:SetLabel_DYN(__POS, \"Button\");",
        "aButton:Resize_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");"};
}

//////////////////////////////////////////////////////////////////////////

class LuaCheckboxWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaCheckboxWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateCheckboxWidget(nil, nil, theId, theParent, self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaCheckboxWidget.DPGetDefaultSize()
{
    return 16, 16;
}

function LuaCheckboxWidget:SetChecked(checked)
{
    mCObj:SetChecked(checked);
}

function LuaCheckboxWidget:SetChecked_DYN(thePos, checked)
{
    SetChecked(checked);
    dptrack(thePos, {self, SetChecked, 0, {"Checked"}, {checked}});
}

function LuaCheckboxWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaCheckboxWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight);
    thePos[1]++; 
    aWidget:SetChecked_DYN(thePos, false);
    
    return aWidget, {"local aCheckbox = LuaCheckboxWidget:Create(-1, self);",
        "aCheckbox:SetChecked_DYN(__POS, false);",
        "aCheckbox:Resize_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");"};
}

//////////////////////////////////////////////////////////////////////////

class LuaEditWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaEditWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateEditWidget(theId, theParent, self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaEditWidget.DPGetDefaultSize()
{
    return 80, 24;
}

function LuaEditWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaEditWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight);
    
    return aWidget, {"local aEdit = LuaEditWidget:Create(-1, self);",
        "aEdit:Resize_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");"};
}

//////////////////////////////////////////////////////////////////////////

class LuaListWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaListWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateListWidget(theId, nil, theParent, self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaListWidget.DPGetDefaultSize()
{
    return 80, 64;
}

function LuaListWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaListWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight);
    
    return aWidget, {"local aList = LuaListWidget:Create(-1, self);",
        "aList:Resize_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");"};
}

//////////////////////////////////////////////////////////////////////////

class LuaTextWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaTextWidget:Create(theParent)
{
    local aWidget = gApp:CreateTextWidget(self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaTextWidget:IsPointVisible(theX, theY)
{
    // Allow us to select it if we're editing
    return DProps.mEditing;
}

function LuaTextWidget:SetLabel(theStr)
{
    mCObj:SetLabel(theStr);
}

function LuaTextWidget:SetLabel_DYN(thePos, theStr)
{
    SetLabel(theStr);
    dptrack(thePos, {self, SetLabel, 0, {"Label"}, {theStr}});
}

function LuaTextWidget:SetLineSpacing(theLineSpacing)
{
    mCObj:SetLineSpacing(theLineSpacing);
}

function LuaTextWidget:SetLineSpacing_DYN(thePos, theLineSpacing)
{
    SetLineSpacing(theLineSpacing);
    dptrack(thePos, {self, SetLineSpacing, 0, {"Line Spacing"}, {theLineSpacing}});
}

function LuaTextWidget:SetJustification(theJustification)
{
    mCObj:SetJustification(theJustification);
}

function LuaTextWidget:SetJustification_DYN(thePos, theJustification)
{
    SetJustification(theJustification);
    dptrack(thePos, {self, SetJustification, 0, {"Justification"}, {theJustification}});
}

function LuaTextWidget.DPGetDefaultSize()
{
    return 128, 16;
}

function LuaTextWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaTextWidget:Create(theParent);
    thePos[1]++; 
    aWidget:SetLabel_DYN(thePos, "Text");
    thePos[1]++;
    aWidget:SetLineSpacing_DYN(thePos, -1);
    thePos[1]++;
    aWidget:SetJustification_DYN(thePos, -1);
    thePos[1]++;
    aWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight);
    thePos[1]++;
    aWidget:SetColors_DYN(thePos, {0xFFFFFFFF});
    
    return aWidget, {"local aText = LuaTextWidget:Create(self);",
        "aText:SetLabel_DYN(__POS, \"Text\");",
        "aText:SetLineSpacing_DYN(__POS, -1);",
        "aText:SetJustification_DYN(__POS, -1);",
        "aText:Resize_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");",
        "aText:SetColors_DYN(__POS, {0xFFFFFFFF});"};
}

//////////////////////////////////////////////////////////////////////////

class LuaScrollbarWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaScrollbarWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateScrollbarWidget(theId, theParent, self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaScrollbarWidget:ResizeScrollbar(theX, theY, theWidth, theHeight)
{
    mCObj:ResizeScrollbar(theX, theY, theWidth, theHeight);
}

function LuaScrollbarWidget:ResizeScrollbar_DYN(thePos, theX, theY, theWidth, theHeight)
{
    ResizeScrollbar(theX, theY, theWidth, theHeight);
    dptrack(thePos, {self, LuaScrollbarWidget.ResizeScrollbar, 0, {"X", "Y", "Width", "Height"}, {theX, theY, theWidth, theHeight}});
}

function LuaScrollbarWidget.DPGetDefaultSize()
{
    return 16, 64;
}

function LuaScrollbarWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaScrollbarWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:ResizeScrollbar_DYN(thePos, theX, theY, theWidth, theHeight);
    
    return aWidget, {"local aScrollbar = LuaScrollbarWidget:Create(-1, self);",
        "aScrollbar:ResizeScrollbar_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");"};
}

//////////////////////////////////////////////////////////////////////////

class LuaHorzScrollbarWidget : LuaScrollbarWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaHorzScrollbarWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateScrollbarWidget(theId, theParent, self);
    theParent:AddWidget(aWidget);
    aWidget.mCObj:SetHorizontal(true);
    return aWidget;
}

function LuaHorzScrollbarWidget.DPGetDefaultSize()
{
    return 64, 16;
}

function LuaHorzScrollbarWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaHorzScrollbarWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:ResizeScrollbar_DYN(thePos, theX, theY, theWidth, theHeight);
    
    return aWidget, {"local aScrollbar = LuaHorzScrollbarWidget:Create(-1, self);",
        "aScrollbar:ResizeScrollbar_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");"};
}

//////////////////////////////////////////////////////////////////////////

class LuaHyperlinkWidget : LuaWidget(theCObj)
{
    super:Init(theCObj);
}

function LuaHyperlinkWidget:Create(theId, theParent)
{
    local aWidget = gApp:CreateHyperlinkWidget(theId, theParent, self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaHyperlinkWidget:SetLabel(theStr)
{
    mCObj:SetLabel(theStr);
}

function LuaHyperlinkWidget:SetLabel_DYN(thePos, theStr)
{
    SetLabel(theStr);
    dptrack(thePos, {self, SetLabel, 0, {"Label"}, {theStr}});
}

function LuaHyperlinkWidget.DPGetDefaultSize()
{
    return 128, 20;
}

function LuaHyperlinkWidget.DPGetCreateDefault(thePos, theParent, theX, theY, theWidth, theHeight)
{
    local aWidget = LuaHyperlinkWidget:Create(-1, theParent);
    thePos[1]++; 
    aWidget:SetLabel_DYN(thePos, "Hyperlink");
    thePos[1]++;
    aWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight);
    thePos[1]++;
    aWidget:SetColors_DYN(thePos, {0xFFFFFF00, 0xFFFFFFFF});
    
    return aWidget, {"local aHyperlink = LuaHyperlinkWidget:Create(-1, self);",
        "aHyperlink:SetLabel_DYN(__POS, \"Hyperlink\");",
        "aHyperlink:Resize_DYN(__POS, " .. theX .. ", " .. theY .. ", " .. theWidth .. ", " .. theHeight .. ");",
        "aHyperlink:SetColors_DYN(__POS, {0xFFFFFF00, 0xFFFFFFFF});"};
}

//////////////////////////////////////////////////////////////////////////

DProps.AddWidgetType("Button", LuaButtonWidget, nil, 0);
DProps.AddWidgetType("Checkbox", LuaCheckboxWidget, nil, 1);
DProps.AddWidgetType("Edit", LuaEditWidget, nil, 2);
DProps.AddWidgetType("List", LuaListWidget, nil, 3);
DProps.AddWidgetType("Text", LuaTextWidget, nil, 4);
DProps.AddWidgetType("Horz Scrollbar", LuaHorzScrollbarWidget, nil, 5);
DProps.AddWidgetType("Vert Scrollbar", LuaScrollbarWidget, nil, 6);
DProps.AddWidgetType("Hyperlink", LuaHyperlinkWidget, nil, 7);

//////////////////////////////////////////////////////////////////////////

RegisterLinkedClass("LuaButtonWidget", LuaButtonWidget);

LuaWidget.lua:

class LuaWidget(theCObj)
{
    def mCObj = theCObj;
    def mUpdateCnt = 0;
    def mChildren = {};
    setprot(mChildren, "l");
    
    DProps.WidgetCreated(self);
    
    :SetLinkedObject(theCObj, self);
    theCObj:RegisterObject();
}

function LuaWidget:Create(theParent)
{
    local aWidget = gApp:CreateLuaWidget(self);
    theParent:AddWidget(aWidget);
    return aWidget;
}

function LuaWidget:CObjDeleted()
{
    DProps.WidgetDeleted(self);
}

//////////////////////////////////////////////////////////////////////////

function LuaWidget:Update()
{
    mUpdateCnt++;
    mCObj:Update();
}

function LuaWidget:Draw(g)
{
    mCObj:Draw();
}

function LuaWidget:DrawOverlay(g)
{
    mCObj:DrawOverlay();
}

function LuaWidget:IsPointVisible(theX, theY)
{
    return true;
}

function LuaWidget:MouseEnter()
{
    mCObj:MouseEnter();
}

function LuaWidget:MouseLeave()
{
    mCObj:MouseLeave();
}

function LuaWidget:MouseMove(theX, theY)
{
    mCObj:MouseMove(theX, theY);
}

function LuaWidget:MouseDrag(theX, theY)
{
    mCObj:MouseDrag(theX, theY);
}

function LuaWidget:MouseDown(theX, theY, theBtnNum, theClickCount)  
{
    mCObj:MouseDown(theX, theY, theBtnNum, theClickCount);
}

function LuaWidget:MouseUp(theX, theY, theBtnNum, theClickCount)
{
    mCObj:MouseUp(theX, theY, theBtnNum, theClickCount);
}

function LuaWidget:MouseWheel(theDelta)
{
    mCObj:MouseWheel(theDelta);
}

function LuaWidget:KeyChar(theChar)
{
    mCObj:KeyChar(theChar);
}

function LuaWidget:KeyDown(theKey)
{
    mCObj:KeyDown(theKey);
}

function LuaWidget:KeyUp(theKey)
{
    mCObj:KeyUp(theKey);
}

function LuaWidget:GotFocus()
{
    mCObj:GotFocus();
}

function LuaWidget:LostFocus()
{
    mCObj:LostFocus();
}

function LuaWidget:WantsFocus()
{
    return  mCObj:WantsFocus();
}

///
function LuaWidget:CheckboxChecked(theId, checked)
{
    mCObj:CheckboxChecked(theId, checked);
}

function LuaWidget:ButtonPress(theBtnId, theClickCount)
{
    mCObj:ButtonPress(theBtnId, theClickCount);
}

function LuaWidget:ButtonDepress(theBtnId)
{
    mCObj:ButtonDepress(theBtnId);
}

function LuaWidget:ButtonMouseEnter(theId)
{
    mCObj:ButtonMouseEnter(theId);
}

function LuaWidget:ButtonMouseLeave(theId)
{
    mCObj:ButtonMouseLeave(theId);
}

function LuaWidget:ButtonMouseMove(theId, theX, theY)
{
    mCObj:ButtonMouseMove(theId, theX, theY);
}

function LuaWidget:ButtonDownTick(theId, theClickCount)
{
    mCObj:ButtonDownTick(theId, theClickCount);
}

function LuaWidget:ScrollPosition(theId, thePosition)
{
    mCObj:ScrollPosition(theId, thePosition);
}

function LuaWidget:EditWidgetText(theId, theString)
{
    mCObj:EditWidgetText(theId, theString);
}

function LuaWidget:AllowKey(theId, theKey)
{
    return mCObj:AllowKey(theId, theKey);
}

function LuaWidget:AllowChar(theId, theChar)
{
    return mCObj:AllowChar(theId, theChar);
}

function LuaWidget:AllowText(theId, theText)
{
    return mCObj:AllowText(theId, theText);
}

function LuaWidget:ListClicked(theId, theIdx, theClickCount)
{
    mCObj:ListClicked(theId, theIdx, theClickCount);
}

function LuaWidget:ListClosed(theId)
{
    mCObj:ListClosed(theId);
}

function LuaWidget:ListHiliteChanged(theId, theOldIdx, theNewIdx)
{
    mCObj:ListHiliteChanged(theId, theOldIdx, theNewIdx);
}

//////////////////////////////////////////////////////////////////////////

function LuaWidget:Resize(theX, theY, theWidth, theHeight)
{
    mCObj:Resize(theX, theY, theWidth, theHeight);
}

function LuaWidget:Resize_DYN(thePos, theX, theY, theWidth, theHeight)
{
    Resize(theX, theY, theWidth, theHeight);
    dptrack(thePos, {self, LuaWidget.Resize, 0, {"X", "Y", "Width", "Height"}, {theX, theY, theWidth, theHeight}});
}

function LuaWidget:Move(theX, theY)
{
    mCObj:Resize(theX, theY, mCObj:GetWidth(), mCObj:GetHeight());
}

function LuaWidget:SetColors(theColors)
{
    mCObj:SetColors(theColors);
}

function LuaWidget:SetColors_DYN(thePos, theColors)
{
    mCObj:SetColors(theColors);
    dptrack(thePos, {self, LuaWidget.SetColors, 0, {"Colors"}, {theColors}});
}

function LuaWidget:AddWidget(theWidget)
{
    if (table.contains(theWidget.mCObj, "GetID"))
        mChildren[theWidget.mCObj:GetID()] = theWidget;
    mCObj:AddWidget(theWidget.mCObj);
}

function LuaWidget:RemoveWidget(theWidget)
{
    if (table.contains(theWidget.mCObj, "GetID"))
        mChildren[theWidget.mCObj:GetID()] = nil;
    mCObj:RemoveWidget(theWidget.mCObj);
}


//////////////////////////////////////////////////////////////////////////

RegisterLinkedClass("LuaWidget", LuaWidget);

QRand.lua:

//def mQRand = QRand:new(0.25, 0.5, 0.75, 0.1);

class QRand(...)
{
    def mUpdateCnt = 0;
    
    def mWeights = {};
    def mLastHitUpdate = {};
    def mPrevLastHitUpdate = {};
    def mCurSway = {};
    def mLastIndex = -1;
    
    SetWeights(unpack(arg));
}

function QRand:SetWeights(...)
{
    if (arg.n == 1)
    {
        mWeights[0] = 1.0 - arg[0];
        mWeights[1] = arg[0];
    }
    else
    {
        local aTotal = 0.0;
        for (anArgNum < arg.n)
        {
            mWeights[anArgNum] = arg[anArgNum];
            aTotal += mWeights[anArgNum];
        }
        
        for (aWeightNum < *mWeights)
            mWeights[aWeightNum] /= aTotal;
    }
    
    for (i = *mLastHitUpdate, *mWeights-1, 1)
    {
        mLastHitUpdate[i] = 0;
        mPrevLastHitUpdate[i] = 0;
    }
}

function QRand:Next()
{
    mUpdateCnt++;

    //local aStr = "";
    local aTotalWeight = 0.0;
    for (aResultNum,aWeight in mWeights)
    {
        if (aWeight != 0)
        {
            local anAvgLength = 1.0 / aWeight;
            local aPctOff = 1.0 + (((mUpdateCnt - mLastHitUpdate[aResultNum]) - anAvgLength) / anAvgLength);
            local aPrevPctOff = 1.0 + (((mUpdateCnt - mPrevLastHitUpdate[aResultNum]) - anAvgLength*2) / (anAvgLength*2));
            
            local aSway = aWeight * math.max(math.min(aPctOff*0.75 + aPrevPctOff*0.25, 3.0), 0.333);
            //aSway = aWeight; // Comment this in to get a normal random number generator
            mCurSway[aResultNum] = aSway;
            aTotalWeight += aSway;
            //aStr = aStr .. aSway .. " ";
        }
        else
            mCurSway[aResultNum] = 0;
    }
        
    
    
    local aRand = math.random() * aTotalWeight;
    local anIdx = 0;
    while (aRand > mCurSway[anIdx])
    {
        aRand -= mCurSway[anIdx];
        anIdx++;
    }
    
    mPrevLastHitUpdate[anIdx] = mLastHitUpdate[anIdx];
    mLastHitUpdate[anIdx] = mUpdateCnt;
    
    mLastIndex = anIdx;
    return anIdx;
}