/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include <Python.h>
#include "cyMisc.h"
#include "pyGlueHelpers.h"
#include "pyKey.h"
#include "pyColor.h"
#include "pyPlayer.h"
#include "pyEnum.h"

// for enums
#include "plNetCommon/plNetCommon.h"
#include "plResMgr/plLocalization.h"
#include "plMessage/plLOSRequestMsg.h"


PYTHON_GLOBAL_METHOD_DEFINITION(PtYesNoDialog, args, "Params: selfkey,dialogMessage\nThis will display a Yes/No dialog to the user with the text dialogMessage\n"
            "This dialog _has_ to be answered by the user.\n"
            "And their answer will be returned in a Notify message.")
{
    PyObject* keyObj = NULL;
    PyObject* dialogMsgObj = NULL;
    if (!PyArg_ParseTuple(args, "OO", &keyObj, &dialogMsgObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtYesNoDialog expects a ptKey and a string or unicode string");
        PYTHON_RETURN_ERROR;
    }
    if (!pyKey::Check(keyObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtYesNoDialog expects a ptKey and a string or unicode string");
        PYTHON_RETURN_ERROR;
    }
    pyKey* key = pyKey::ConvertFrom(keyObj);
    if (PyUnicode_Check(dialogMsgObj))
    {
        int len = PyUnicode_GetSize(dialogMsgObj);
        wchar_t* text = new wchar_t[len + 1];
        PyUnicode_AsWideChar((PyUnicodeObject*)dialogMsgObj, text, len);
        text[len] = L'\0';
        cyMisc::YesNoDialog(*key, text);
        delete [] text;
        PYTHON_RETURN_NONE;
    }
    else if (PyString_Check(dialogMsgObj))
    {
        char* text = PyString_AsString(dialogMsgObj);
        cyMisc::YesNoDialog(*key, text);
        PYTHON_RETURN_NONE;
    }
    PyErr_SetString(PyExc_TypeError, "PtYesNoDialog expects a ptKey and a string or unicode string");
    PYTHON_RETURN_ERROR;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtRateIt, args, "Params: chronicleName,dialogPrompt,onceFlag\nShows a dialog with dialogPrompt and stores user input rating into chronicleName")
{
    char* chronicleName;
    char* dialogPrompt;
    char onceFlag;
    if (!PyArg_ParseTuple(args, "ssb", &chronicleName, &dialogPrompt, &onceFlag))
    {
        PyErr_SetString(PyExc_TypeError, "PtRateIt expects two strings and a boolean");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::RateIt(chronicleName, dialogPrompt, onceFlag != 0);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtExcludeRegionSet, args, "Params: senderKey,regionKey,state\nThis will set the state of an exclude region\n"
            "- 'senderKey' is a ptKey of the PythonFile component\n"
            "- 'regionKey' is a ptKey of the exclude region\n"
            "- 'state' is either kExRegRelease or kExRegClear")
{
    PyObject* senderObj = NULL;
    PyObject* regionObj = NULL;
    unsigned short stateVal;
    if (!PyArg_ParseTuple(args, "OOh", &senderObj, &regionObj, &stateVal))
    {
        PyErr_SetString(PyExc_TypeError, "PtExcludeRegionSet expects two ptKeys and a short");
        PYTHON_RETURN_ERROR;
    }
    if ((!pyKey::Check(senderObj)) || (!pyKey::Check(regionObj)))
    {
        PyErr_SetString(PyExc_TypeError, "PtExcludeRegionSet expects two ptKeys and a short");
        PYTHON_RETURN_ERROR;
    }
    pyKey* sender = pyKey::ConvertFrom(senderObj);
    pyKey* region = pyKey::ConvertFrom(regionObj);
    cyMisc::ExcludeRegionSet(*sender, *region, stateVal);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtExcludeRegionSetNow, args, "Params: senderKey,regionKey,state\nThis will set the state of an exclude region immediately on the server\n"
            "- 'senderKey' is a ptKey of the PythonFile component\n"
            "- 'regionKey' is a ptKey of the exclude region\n"
            "- 'state' is either kExRegRelease or kExRegClear")
{
    PyObject* senderObj = NULL;
    PyObject* regionObj = NULL;
    unsigned short stateVal;
    if (!PyArg_ParseTuple(args, "OOh", &senderObj, &regionObj, &stateVal))
    {
        PyErr_SetString(PyExc_TypeError, "PtExcludeRegionSetNow expects two ptKeys and a short");
        PYTHON_RETURN_ERROR;
    }
    if ((!pyKey::Check(senderObj)) || (!pyKey::Check(regionObj)))
    {
        PyErr_SetString(PyExc_TypeError, "PtExcludeRegionSetNow expects two ptKeys and a short");
        PYTHON_RETURN_ERROR;
    }
    pyKey* sender = pyKey::ConvertFrom(senderObj);
    pyKey* region = pyKey::ConvertFrom(regionObj);
    cyMisc::ExcludeRegionSetNow(*sender, *region, stateVal);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtAcceptInviteInGame, args, "Params: friendName,inviteKey\nSends a VaultTask to the server to perform the invite")
{
    char* friendName;
    char* inviteKey;
    if (!PyArg_ParseTuple(args, "ss", &friendName, &inviteKey))
    {
        PyErr_SetString(PyExc_TypeError, "PtAcceptInviteInGame expects two strings");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::AcceptInviteInGame(friendName, inviteKey);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION_NOARGS(PtGetTime, "Returns the number of seconds since the game was started.")
{
    return PyFloat_FromDouble(cyMisc::GetSeconds());
}

PYTHON_GLOBAL_METHOD_DEFINITION_NOARGS(PtGetGameTime, "Returns the system game time (frame based) in seconds.")
{
    return PyFloat_FromDouble(cyMisc::GetSysSeconds());
}

PYTHON_GLOBAL_METHOD_DEFINITION_NOARGS(PtGetFrameDeltaTime, "Returns the amount of time that has elapsed since last frame.")
{
    return PyFloat_FromDouble(cyMisc::GetDelSysSeconds());
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtPageInNode, args, "Params: nodeName, ageName=\"\"\nPages in node, or a list of nodes")
{
    PyObject* nodeNameObj = NULL;
    char* ageName = NULL;
    if (!PyArg_ParseTuple(args, "O|s", &nodeNameObj, &ageName))
    {
        PyErr_SetString(PyExc_TypeError, "PtPageInNode expects a string or list of strings, and optionally a string");
        PYTHON_RETURN_ERROR;
    }
    std::vector<std::string> nodeNames;
    if (PyString_Check(nodeNameObj))
    {
        nodeNames.push_back(PyString_AsString(nodeNameObj));
    }
    else if (PyList_Check(nodeNameObj))
    {
        int num = PyList_Size(nodeNameObj);
        for (int i = 0; i < num; i++)
        {
            PyObject* listItem = PyList_GetItem(nodeNameObj, i);
            if (!PyString_Check(listItem))
            {
                PyErr_SetString(PyExc_TypeError, "PtPageInNode expects a string or list of strings, and optionally a string");
                PYTHON_RETURN_ERROR;
            }
            nodeNames.push_back(PyString_AsString(listItem));
        }
    }
    else
    {
        PyErr_SetString(PyExc_TypeError, "PtPageInNode expects a string or list of strings, and optionally a string");
        PYTHON_RETURN_ERROR;
    }

    cyMisc::PageInNodes(nodeNames, ageName);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtPageOutNode, args, "Params: nodeName\nPages out a node")
{
    char* nodeName;
    if (!PyArg_ParseTuple(args, "s", &nodeName))
    {
        PyErr_SetString(PyExc_TypeError, "PtPageOutNode expects a string");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::PageOutNode(nodeName);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtLimitAvatarLOD, args, "Params: LODlimit\nSets avatar's LOD limit")
{
    int lodLimit;
    if (!PyArg_ParseTuple(args, "i", &lodLimit))
    {
        PyErr_SetString(PyExc_TypeError, "PtLimitAvatarLOD expects an integer");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::LimitAvatarLOD(lodLimit);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtFogSetDefColor, args, "Params: color\nSets default fog color")
{
    PyObject* colorObj = NULL;
    if (!PyArg_ParseTuple(args, "O", &colorObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtFogSetDefColor expects a ptColor object");
        PYTHON_RETURN_ERROR;
    }
    if (!pyColor::Check(colorObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtFogSetDefColor expects a ptColor object");
        PYTHON_RETURN_ERROR;
    }
    pyColor* color = pyColor::ConvertFrom(colorObj);
    cyMisc::FogSetDefColor(*color);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtFogSetDefLinear, args, "Params: start,end,density\nSet linear fog values")
{
    float start, end, density;
    if (!PyArg_ParseTuple(args, "fff", &start, &end, &density))
    {
        PyErr_SetString(PyExc_TypeError, "PtFogSetDefLinear expects three floats");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::FogSetDefLinear(start, end, density);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtFogSetDefExp, args, "Params: end,density\nSet exp fog values")
{
    float end, density;
    if (!PyArg_ParseTuple(args, "ff", &end, &density))
    {
        PyErr_SetString(PyExc_TypeError, "PtFogSetDefExp expects three floats");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::FogSetDefExp(end, density);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtFogSetDefExp2, args, "Params: end,density\nSet exp2 fog values")
{
    float end, density;
    if (!PyArg_ParseTuple(args, "ff", &end, &density))
    {
        PyErr_SetString(PyExc_TypeError, "PtFogSetDefExp2 expects three floats");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::FogSetDefExp2(end, density);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtLoadDialog, args, "Params: dialogName,selfKey=None,ageName=\"\"\nLoads a GUI dialog by name and optionally set the Notify proc key\n"
            "If the dialog is already loaded then it won't load it again")
{
    char* dialogName;
    PyObject* keyObj = NULL;
    char* ageName = NULL;
    if (!PyArg_ParseTuple(args, "s|Os", &dialogName, &keyObj, &ageName))
    {
        PyErr_SetString(PyExc_TypeError, "PtLoadDialog expects a string, and optionally a ptKey and second string");
        PYTHON_RETURN_ERROR;
    }
    if (keyObj)
    {
        if (!pyKey::Check(keyObj))
        {
            PyErr_SetString(PyExc_TypeError, "PtLoadDialog expects a string, and optionally a ptKey and second string");
            PYTHON_RETURN_ERROR;
        }
        pyKey* key = pyKey::ConvertFrom(keyObj);
        if (ageName)
            cyMisc::LoadDialogKA(dialogName, *key, ageName);
        else
            cyMisc::LoadDialogK(dialogName, *key);
    }
    else
        cyMisc::LoadDialog(dialogName);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtUnloadDialog, args, "Params: dialogName\nThis will unload the GUI dialog by name. If not loaded then nothing will happen")
{
    char* dialogName;
    if (!PyArg_ParseTuple(args, "s", &dialogName))
    {
        PyErr_SetString(PyExc_TypeError, "PtUnloadDialog expects a string");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::UnloadDialog(dialogName);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtIsDialogLoaded, args, "Params: dialogName\nTest to see if a GUI dialog is loaded, by name")
{
    char* dialogName;
    if (!PyArg_ParseTuple(args, "s", &dialogName))
    {
        PyErr_SetString(PyExc_TypeError, "PtIsDialogLoaded expects a string");
        PYTHON_RETURN_ERROR;
    }
    PYTHON_RETURN_BOOL(cyMisc::IsDialogLoaded(dialogName));
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtShowDialog, args, "Params: dialogName\nShow a GUI dialog by name (does not load dialog)")
{
    char* dialogName;
    if (!PyArg_ParseTuple(args, "s", &dialogName))
    {
        PyErr_SetString(PyExc_TypeError, "PtShowDialog expects a string");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::ShowDialog(dialogName);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtHideDialog, args, "Params: dialogName\nHide a GUI dialog by name (does not unload dialog)")
{
    char* dialogName;
    if (!PyArg_ParseTuple(args, "s", &dialogName))
    {
        PyErr_SetString(PyExc_TypeError, "PtHideDialog expects a string");
        PYTHON_RETURN_ERROR;
    }
    cyMisc::HideDialog(dialogName);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtGetDialogFromTagID, args, "Params: tagID\nReturns the dialog associated with the tagID")
{
    unsigned long tagID;
    if (!PyArg_ParseTuple(args, "l", &tagID))
    {
        PyErr_SetString(PyExc_TypeError, "PtGetDialogFromTagID expects a long");
        PYTHON_RETURN_ERROR;
    }
    return cyMisc::GetDialogFromTagID(tagID);
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtGetDialogFromString, args, "Params: dialogName\nGet a ptGUIDialog from its name")
{
    char* dialogName;
    if (!PyArg_ParseTuple(args, "s", &dialogName))
    {
        PyErr_SetString(PyExc_TypeError, "PtHideDialog expects a string");
        PYTHON_RETURN_ERROR;
    }
    return cyMisc::GetDialogFromString(dialogName);
}

PYTHON_GLOBAL_METHOD_DEFINITION_NOARGS(PtIsGUIModal, "Returns true if the GUI is displaying a modal dialog and blocking input")
{
    PYTHON_RETURN_BOOL(cyMisc::IsGUIModal());
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtSendPrivateChatList, args, "Params: chatList\nLock the local avatar into private vox messaging, and / or add new members to his chat list")
{
    PyObject* chatListObj = NULL;
    if (!PyArg_ParseTuple(args, "O", &chatListObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtSendPrivateChatList expects a list of ptPlayers");
        PYTHON_RETURN_ERROR;
    }

    std::vector<pyPlayer*> chatList;
    if (PyList_Check(chatListObj))
    {
        int listSize = PyList_Size(chatListObj);
        for (int i = 0; i < listSize; i++)
        {
            PyObject* listItem = PyList_GetItem(chatListObj, i);
            if (!pyPlayer::Check(listItem))
            {
                PyErr_SetString(PyExc_TypeError, "PtSendPrivateChatList expects a list of ptPlayers");
                PYTHON_RETURN_ERROR;
            }
            chatList.push_back(pyPlayer::ConvertFrom(listItem));
        }
    }
    else
    {
        PyErr_SetString(PyExc_TypeError, "PtSendPrivateChatList expects a list of ptPlayers");
        PYTHON_RETURN_ERROR;
    }

    cyMisc::SetPrivateChatList(chatList);
    PYTHON_RETURN_NONE;
}

PYTHON_GLOBAL_METHOD_DEFINITION(PtClearPrivateChatList, args, "Params: memberKey\nRemove the local avatar from private vox messaging, and / or clear members from his chat list")
{
    PyObject* keyObj = NULL;
    if (!PyArg_ParseTuple(args, "O", &keyObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtClearPrivateChatList expects a ptKey");
        PYTHON_RETURN_ERROR;
    }
    if (!pyKey::Check(keyObj))
    {
        PyErr_SetString(PyExc_TypeError, "PtClearPrivateChatList expects a ptKey");
        PYTHON_RETURN_ERROR;
    }
    pyKey* key = pyKey::ConvertFrom(keyObj);
    cyMisc::ClearPrivateChatList(*key);
    PYTHON_RETURN_NONE;
}

///////////////////////////////////////////////////////////////////////////
//
// AddPlasmaMethods - the python method definitions
//

void cyMisc::AddPlasmaMethods2(std::vector<PyMethodDef> &methods)
{
    PYTHON_GLOBAL_METHOD(methods, PtYesNoDialog);
    PYTHON_GLOBAL_METHOD(methods, PtRateIt);
    
    PYTHON_GLOBAL_METHOD(methods, PtExcludeRegionSet);
    PYTHON_GLOBAL_METHOD(methods, PtExcludeRegionSetNow);

    PYTHON_GLOBAL_METHOD(methods, PtAcceptInviteInGame);

    PYTHON_GLOBAL_METHOD_NOARGS(methods, PtGetTime);
    PYTHON_GLOBAL_METHOD_NOARGS(methods, PtGetGameTime);
    PYTHON_GLOBAL_METHOD_NOARGS(methods, PtGetFrameDeltaTime);

    PYTHON_GLOBAL_METHOD(methods, PtPageInNode);
    PYTHON_GLOBAL_METHOD(methods, PtPageOutNode);
    
    PYTHON_GLOBAL_METHOD(methods, PtLimitAvatarLOD);

    PYTHON_GLOBAL_METHOD(methods, PtFogSetDefColor);
    PYTHON_GLOBAL_METHOD(methods, PtFogSetDefLinear);
    PYTHON_GLOBAL_METHOD(methods, PtFogSetDefExp);
    PYTHON_GLOBAL_METHOD(methods, PtFogSetDefExp2);

    PYTHON_GLOBAL_METHOD(methods, PtLoadDialog);
    PYTHON_GLOBAL_METHOD(methods, PtUnloadDialog);
    PYTHON_GLOBAL_METHOD(methods, PtIsDialogLoaded);
    PYTHON_GLOBAL_METHOD(methods, PtShowDialog);
    PYTHON_GLOBAL_METHOD(methods, PtHideDialog);
    PYTHON_GLOBAL_METHOD(methods, PtGetDialogFromTagID);
    PYTHON_GLOBAL_METHOD(methods, PtGetDialogFromString);
    PYTHON_GLOBAL_METHOD_NOARGS(methods, PtIsGUIModal);

    PYTHON_GLOBAL_METHOD(methods, PtSendPrivateChatList);
    PYTHON_GLOBAL_METHOD(methods, PtClearPrivateChatList);
}

void cyMisc::AddPlasmaConstantsClasses(PyObject *m)
{
    PYTHON_ENUM_START(PtCCRPetitionType);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kGeneralHelp,plNetCommon::PetitionTypes::kGeneralHelp);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kBug,        plNetCommon::PetitionTypes::kBug);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kFeedback,   plNetCommon::PetitionTypes::kFeedback);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kExploit,    plNetCommon::PetitionTypes::kExploit);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kHarass,     plNetCommon::PetitionTypes::kHarass);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kStuck,      plNetCommon::PetitionTypes::kStuck);
    PYTHON_ENUM_ELEMENT(PtCCRPetitionType, kTechnical,  plNetCommon::PetitionTypes::kTechnical);
    PYTHON_ENUM_END(m, PtCCRPetitionType);
    
    PYTHON_ENUM_START(PtLanguage);
    PYTHON_ENUM_ELEMENT(PtLanguage, kEnglish,       plLocalization::kEnglish);
    PYTHON_ENUM_ELEMENT(PtLanguage, kFrench,        plLocalization::kFrench);
    PYTHON_ENUM_ELEMENT(PtLanguage, kGerman,        plLocalization::kGerman);
    PYTHON_ENUM_ELEMENT(PtLanguage, kSpanish,       plLocalization::kSpanish);
    PYTHON_ENUM_ELEMENT(PtLanguage, kItalian,       plLocalization::kItalian);
    PYTHON_ENUM_ELEMENT(PtLanguage, kJapanese,      plLocalization::kJapanese);
    PYTHON_ENUM_ELEMENT(PtLanguage, kNumLanguages,  plLocalization::kNumLanguages);
    PYTHON_ENUM_END(m, PtLanguage);
    
    PYTHON_ENUM_START(PtLOSReportType);
    PYTHON_ENUM_ELEMENT(PtLOSReportType, kReportHit,        plLOSRequestMsg::kReportHit);
    PYTHON_ENUM_ELEMENT(PtLOSReportType, kReportMiss,       plLOSRequestMsg::kReportMiss);
    PYTHON_ENUM_ELEMENT(PtLOSReportType, kReportHitOrMiss,  plLOSRequestMsg::kReportHitOrMiss);
    PYTHON_ENUM_END(m, PtLOSReportType);
    
    PYTHON_ENUM_START(PtLOSObjectType);
    PYTHON_ENUM_ELEMENT(PtLOSObjectType, kClickables,       kClickables);
    PYTHON_ENUM_ELEMENT(PtLOSObjectType, kCameraBlockers,   kCameraBlockers);
    PYTHON_ENUM_ELEMENT(PtLOSObjectType, kCustom,           kCustom);
    PYTHON_ENUM_ELEMENT(PtLOSObjectType, kShootable,        kShootable);
    PYTHON_ENUM_END(m, PtLOSObjectType);
}
