forked from 0ad/0ad
Combat code, some scripting, broken network, and fixed some bugs.
This was SVN commit r1301.
This commit is contained in:
parent
9b47d446d2
commit
793794649f
@ -34,7 +34,9 @@ CGameView::CGameView(CGame *pGame):
|
||||
m_Camera(),
|
||||
m_ViewScrollSpeed(60),
|
||||
m_ViewRotateSensitivity(0.002f),
|
||||
m_ViewRotateSensitivityKeyboard(1.0f),
|
||||
m_ViewRotateAboutTargetSensitivity(0.010f),
|
||||
m_ViewRotateAboutTargetSensitivityKeyboard(2.0f),
|
||||
m_ViewDragSensitivity(0.5f),
|
||||
m_ViewZoomSensitivityWheel(16.0f),
|
||||
m_ViewZoomSensitivity(256.0f),
|
||||
@ -69,7 +71,9 @@ void CGameView::Initialize(CGameAttributes *pAttribs)
|
||||
|
||||
getViewParameter( "view.scroll.speed", m_ViewScrollSpeed );
|
||||
getViewParameter( "view.rotate.speed", m_ViewRotateSensitivity );
|
||||
getViewParameter( "view.rotate.keyboard.speed", m_ViewRotateSensitivityKeyboard );
|
||||
getViewParameter( "view.rotate.abouttarget.speed", m_ViewRotateAboutTargetSensitivity );
|
||||
getViewParameter( "view.rotate.keyboard.abouttarget.speed", m_ViewRotateAboutTargetSensitivityKeyboard );
|
||||
getViewParameter( "view.drag.speed", m_ViewDragSensitivity );
|
||||
getViewParameter( "view.zoom.speed", m_ViewZoomSensitivity );
|
||||
getViewParameter( "view.zoom.wheel.speed", m_ViewZoomSensitivityWheel );
|
||||
@ -243,7 +247,7 @@ void CGameView::Update(float DeltaTime)
|
||||
forwards_horizontal.Y = 0.0f;
|
||||
forwards_horizontal.Normalize();
|
||||
|
||||
if( hotkeys[HOTKEY_CAMERA_ROTATE] )
|
||||
if( hotkeys[HOTKEY_CAMERA_ROTATE] || hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] )
|
||||
{
|
||||
// Ctrl + middle-drag or left-and-right-drag to rotate view
|
||||
|
||||
@ -252,11 +256,36 @@ void CGameView::Update(float DeltaTime)
|
||||
m_Camera.m_Orientation.Translate(position*-1);
|
||||
|
||||
// Sideways rotation
|
||||
m_Camera.m_Orientation.RotateY(m_ViewRotateSensitivity * (float)(mouse_dx));
|
||||
|
||||
float rightways = 0.0f;
|
||||
if( hotkeys[HOTKEY_CAMERA_ROTATE] )
|
||||
rightways = (float)mouse_dx * m_ViewRotateSensitivity;
|
||||
if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] )
|
||||
{
|
||||
if( hotkeys[HOTKEY_CAMERA_LEFT] )
|
||||
rightways -= m_ViewRotateSensitivityKeyboard * DeltaTime;
|
||||
if( hotkeys[HOTKEY_CAMERA_RIGHT] )
|
||||
rightways += m_ViewRotateSensitivityKeyboard * DeltaTime;
|
||||
}
|
||||
|
||||
m_Camera.m_Orientation.RotateY( rightways );
|
||||
|
||||
// Up/down rotation
|
||||
|
||||
float upways = 0.0f;
|
||||
if( hotkeys[HOTKEY_CAMERA_ROTATE] )
|
||||
upways = (float)mouse_dy * m_ViewRotateSensitivity;
|
||||
if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] )
|
||||
{
|
||||
if( hotkeys[HOTKEY_CAMERA_UP] )
|
||||
upways -= m_ViewRotateSensitivityKeyboard * DeltaTime;
|
||||
if( hotkeys[HOTKEY_CAMERA_DOWN] )
|
||||
upways += m_ViewRotateSensitivityKeyboard * DeltaTime;
|
||||
}
|
||||
|
||||
CQuaternion temp;
|
||||
temp.FromAxisAngle(rightwards, m_ViewRotateSensitivity * (float)(mouse_dy));
|
||||
temp.FromAxisAngle(rightwards, upways);
|
||||
|
||||
m_Camera.m_Orientation.Rotate(temp);
|
||||
|
||||
// Retranslate back to the right position
|
||||
@ -270,11 +299,16 @@ void CGameView::Update(float DeltaTime)
|
||||
|
||||
CQuaternion rotateH, rotateV; CMatrix3D rotateM;
|
||||
|
||||
// Side-to-side rotation
|
||||
rotateH.FromAxisAngle( upwards, m_ViewRotateAboutTargetSensitivity * (float)mouse_dx );
|
||||
// Sideways rotation
|
||||
|
||||
float rightways = (float)mouse_dx * m_ViewRotateAboutTargetSensitivity;
|
||||
|
||||
rotateH.FromAxisAngle( upwards, rightways );
|
||||
|
||||
// Up-down rotation
|
||||
rotateV.FromAxisAngle( rightwards, m_ViewRotateAboutTargetSensitivity * (float)mouse_dy );
|
||||
// Up/down rotation
|
||||
|
||||
float upways = (float)mouse_dy * m_ViewRotateAboutTargetSensitivity;
|
||||
rotateV.FromAxisAngle( rightwards, upways );
|
||||
|
||||
rotateH *= rotateV;
|
||||
rotateH.ToMatrix( rotateM );
|
||||
@ -295,6 +329,55 @@ void CGameView::Update(float DeltaTime)
|
||||
m_Camera.m_Orientation.Translate( m_CameraPivot + delta );
|
||||
}
|
||||
|
||||
}
|
||||
else if( hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD] )
|
||||
{
|
||||
// Split up because the keyboard controls use the centre of the screen, not the mouse position.
|
||||
CVector3D origin = m_Camera.m_Orientation.GetTranslation();
|
||||
CVector3D pivot = m_Camera.GetFocus();
|
||||
CVector3D delta = origin - pivot;
|
||||
|
||||
CQuaternion rotateH, rotateV; CMatrix3D rotateM;
|
||||
|
||||
// Sideways rotation
|
||||
|
||||
float rightways = 0.0f;
|
||||
if( hotkeys[HOTKEY_CAMERA_LEFT] )
|
||||
rightways -= m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime;
|
||||
if( hotkeys[HOTKEY_CAMERA_RIGHT] )
|
||||
rightways += m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime;
|
||||
|
||||
rotateH.FromAxisAngle( upwards, rightways );
|
||||
|
||||
// Up/down rotation
|
||||
|
||||
float upways = 0.0f;
|
||||
if( hotkeys[HOTKEY_CAMERA_UP] )
|
||||
upways -= m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime;
|
||||
if( hotkeys[HOTKEY_CAMERA_DOWN] )
|
||||
upways += m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime;
|
||||
|
||||
rotateV.FromAxisAngle( rightwards, upways );
|
||||
|
||||
rotateH *= rotateV;
|
||||
rotateH.ToMatrix( rotateM );
|
||||
|
||||
delta = rotateM.Rotate( delta );
|
||||
|
||||
// Lock the inclination to a rather arbitrary values (for the sake of graphical decency)
|
||||
|
||||
float scan = sqrt( delta.X * delta.X + delta.Z * delta.Z ) / delta.Y;
|
||||
if( ( scan >= 0.5f ) )
|
||||
{
|
||||
// Move the camera to the origin (in preparation for rotation )
|
||||
m_Camera.m_Orientation.Translate( origin * -1.0f );
|
||||
|
||||
m_Camera.m_Orientation.Rotate( rotateH );
|
||||
|
||||
// Move the camera back to where it belongs
|
||||
m_Camera.m_Orientation.Translate( pivot + delta );
|
||||
}
|
||||
|
||||
}
|
||||
else if( hotkeys[HOTKEY_CAMERA_PAN] )
|
||||
{
|
||||
@ -321,15 +404,18 @@ void CGameView::Update(float DeltaTime)
|
||||
|
||||
// Keyboard movement (added to mouse movement, so you can go faster if you want)
|
||||
|
||||
if( hotkeys[HOTKEY_CAMERA_PAN_RIGHT] )
|
||||
m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime));
|
||||
if( hotkeys[HOTKEY_CAMERA_PAN_LEFT] )
|
||||
m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime));
|
||||
if( hotkeys[HOTKEY_CAMERA_PAN_KEYBOARD] )
|
||||
{
|
||||
if( hotkeys[HOTKEY_CAMERA_RIGHT] )
|
||||
m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime));
|
||||
if( hotkeys[HOTKEY_CAMERA_LEFT] )
|
||||
m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime));
|
||||
|
||||
if( hotkeys[HOTKEY_CAMERA_PAN_BACKWARD] )
|
||||
m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime));
|
||||
if( hotkeys[HOTKEY_CAMERA_PAN_FORWARD] )
|
||||
m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime));
|
||||
if( hotkeys[HOTKEY_CAMERA_DOWN] )
|
||||
m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime));
|
||||
if( hotkeys[HOTKEY_CAMERA_UP] )
|
||||
m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime));
|
||||
}
|
||||
|
||||
// Smoothed zooming (move a certain percentage towards the desired zoom distance every frame)
|
||||
|
||||
|
@ -21,7 +21,9 @@ class CGameView
|
||||
// Settings
|
||||
float m_ViewScrollSpeed;
|
||||
float m_ViewRotateSensitivity;
|
||||
float m_ViewRotateSensitivityKeyboard;
|
||||
float m_ViewRotateAboutTargetSensitivity;
|
||||
float m_ViewRotateAboutTargetSensitivityKeyboard;
|
||||
float m_ViewDragSensitivity;
|
||||
float m_ViewZoomSensitivityWheel;
|
||||
float m_ViewZoomSensitivity;
|
||||
|
@ -43,14 +43,29 @@ CModelDef *CMeshManager::GetMesh(const char *filename)
|
||||
}
|
||||
}
|
||||
|
||||
int CMeshManager::ReleaseMesh(CModelDef *mesh)
|
||||
int CMeshManager::ReleaseMesh(CModelDef* mesh)
|
||||
{
|
||||
if(!mesh)
|
||||
return 0;
|
||||
|
||||
mesh_map::iterator iter = m_MeshMap.find(mesh->m_Hash);
|
||||
if(iter == m_MeshMap.end())
|
||||
return 0;
|
||||
// FIXME: Someone sort this out. I'm tired.
|
||||
// Looks like it might be a multiple delete; not sure best way
|
||||
// to resolve it. MT.
|
||||
|
||||
mesh_map::iterator iter;
|
||||
|
||||
try
|
||||
{
|
||||
iter = m_MeshMap.find(mesh->m_Hash);
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
debug_out( "FIXME: Do something with %s(%d)", __FILE__, __LINE__ );
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
if(iter == m_MeshMap.end())
|
||||
return 0;
|
||||
|
||||
mesh->m_RefCount--;
|
||||
if(mesh->m_RefCount <= 0)
|
||||
|
@ -15,7 +15,7 @@ public:
|
||||
~CMeshManager();
|
||||
|
||||
CModelDef *GetMesh(const char *filename);
|
||||
int ReleaseMesh(CModelDef *mesh);
|
||||
int ReleaseMesh(CModelDef* mesh);
|
||||
private:
|
||||
mesh_map m_MeshMap;
|
||||
};
|
||||
|
@ -195,6 +195,8 @@ void CModel::Update(float time)
|
||||
|
||||
float duration=m_Anim->m_AnimDef->GetDuration();
|
||||
if (m_AnimTime>duration) {
|
||||
if( m_Flags & MODELFLAG_NOLOOPANIMATION )
|
||||
SetAnimation( NULL );
|
||||
m_AnimTime=(float) fmod(m_AnimTime,duration);
|
||||
}
|
||||
|
||||
@ -248,10 +250,14 @@ void CModel::GenerateBoneMatrices()
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SetAnimation: set the given animation as the current animation on this model;
|
||||
// return false on error, else true
|
||||
bool CModel::SetAnimation(CSkeletonAnim* anim)
|
||||
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once)
|
||||
{
|
||||
m_Anim=anim;
|
||||
if (m_Anim) {
|
||||
m_Flags &= ~MODELFLAG_NOLOOPANIMATION;
|
||||
if( once )
|
||||
m_Flags |= MODELFLAG_NOLOOPANIMATION;
|
||||
|
||||
if (!m_BoneMatrices) {
|
||||
// not boned, can't animate
|
||||
return false;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "Material.h"
|
||||
|
||||
#define MODELFLAG_CASTSHADOWS (1<<0)
|
||||
#define MODELFLAG_NOLOOPANIMATION (1<<1)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CModel: basically, a mesh object - holds the texturing and skinning
|
||||
@ -58,7 +59,7 @@ public:
|
||||
CMaterial &GetMaterial() { return m_Material; }
|
||||
|
||||
// set the given animation as the current animation on this model
|
||||
bool SetAnimation(CSkeletonAnim* anim);
|
||||
bool SetAnimation(CSkeletonAnim* anim, bool once = false);
|
||||
// get the currently playing animation, if any
|
||||
CSkeletonAnim* GetAnimation() { return m_Anim; }
|
||||
|
||||
|
@ -19,6 +19,7 @@ CObjectEntry::CObjectEntry(int type) : m_Model(0), m_Type(type)
|
||||
m_IdleAnim=0;
|
||||
m_WalkAnim=0;
|
||||
m_DeathAnim=0;
|
||||
m_CorpseAnim=0;
|
||||
m_MeleeAnim=0;
|
||||
m_RangedAnim=0;
|
||||
m_Properties.m_CastShadows=true;
|
||||
@ -84,6 +85,12 @@ bool CObjectEntry::BuildModel()
|
||||
m_IdleAnim = m_Animations[t].m_AnimData;
|
||||
if( m_Animations[t].m_AnimName.LowerCase() == CStr( "walk" ) )
|
||||
m_WalkAnim = m_Animations[t].m_AnimData;
|
||||
if( m_Animations[t].m_AnimName.LowerCase() == CStr( "attack" ) )
|
||||
m_MeleeAnim = m_Animations[t].m_AnimData;
|
||||
if( m_Animations[t].m_AnimName.LowerCase() == CStr( "death" ) )
|
||||
m_DeathAnim = m_Animations[t].m_AnimData;
|
||||
if( m_Animations[t].m_AnimName.LowerCase() == CStr( "decay" ) )
|
||||
m_CorpseAnim = m_Animations[t].m_AnimData;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -59,6 +59,8 @@ public:
|
||||
CSkeletonAnim* m_DeathAnim;
|
||||
CSkeletonAnim* m_MeleeAnim;
|
||||
CSkeletonAnim* m_RangedAnim;
|
||||
CSkeletonAnim* m_CorpseAnim;
|
||||
|
||||
CSkeletonAnim* GetNamedAnimation( CStr animationName );
|
||||
// list of props attached to object
|
||||
std::vector<Prop> m_Props;
|
||||
|
@ -1196,9 +1196,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
|
||||
ATTR(style);
|
||||
ATTR(type);
|
||||
ATTR(name);
|
||||
// MT - temp tag
|
||||
ATTR(hotkey);
|
||||
// -- MT
|
||||
ATTR(z);
|
||||
ATTR(on);
|
||||
ATTR(file);
|
||||
@ -1233,9 +1231,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
|
||||
bool NameSet = false;
|
||||
bool ManuallySetZ = false; // if z has been manually set, this turn true
|
||||
|
||||
// MT - temp tag
|
||||
CStr hotkeyTag;
|
||||
// -- MT
|
||||
|
||||
// Now we can iterate all attributes and store
|
||||
for (i=0; i<attributes.Count; ++i)
|
||||
@ -1258,11 +1254,9 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
|
||||
continue;
|
||||
}
|
||||
|
||||
// MT - temp tag
|
||||
// Wire up the hotkey tag, if it has one
|
||||
if (attr.Name == attr_hotkey)
|
||||
hotkeyTag = attr.Value;
|
||||
// -- MT
|
||||
|
||||
if (attr.Name == attr_z)
|
||||
ManuallySetZ = true;
|
||||
@ -1327,11 +1321,9 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
|
||||
++m_InternalNameNumber;
|
||||
}
|
||||
|
||||
// MT - temp tag
|
||||
// Attempt to register the hotkey tag, if one was provided
|
||||
if( hotkeyTag.Length() )
|
||||
hotkeyRegisterGUIObject( object->GetName(), hotkeyTag );
|
||||
// -- MT
|
||||
|
||||
CStrW caption (Element.getText());
|
||||
if (caption.Length())
|
||||
|
@ -52,7 +52,6 @@
|
||||
# pragma comment(lib, "alut.lib") // alutLoadWAVMemory
|
||||
#endif
|
||||
|
||||
|
||||
// components:
|
||||
// - alc*: OpenAL context
|
||||
// readies OpenAL for use; allows specifying the device.
|
||||
@ -81,7 +80,6 @@
|
||||
// - vm*: voice management
|
||||
// grants the most currently 'important' sounds a hardware voice.
|
||||
|
||||
|
||||
static bool al_initialized = false;
|
||||
// indicates OpenAL is ready for use. checked by other components
|
||||
// when deciding if they can pass settings changes to OpenAL directly,
|
||||
|
@ -554,7 +554,7 @@ static void Render()
|
||||
|
||||
static void InitDefaultGameAttributes()
|
||||
{
|
||||
g_GameAttributes.SetValue("mapFile", L"test01.pmp");
|
||||
g_GameAttributes.SetValue( "mapFile", L"combattest.pmp" );
|
||||
}
|
||||
|
||||
|
||||
@ -644,7 +644,7 @@ static void ParseArgs(int argc, char* argv[])
|
||||
break;
|
||||
case 'm':
|
||||
if(strncmp(name, "m=", 2) == 0)
|
||||
g_GameAttributes.SetValue("mapFile", CStr(argv[i]+3));
|
||||
g_GameAttributes.SetValue( "mapFile", CStr( argv[i] + 3 ) );
|
||||
break;
|
||||
case 'n':
|
||||
if(strncmp(name, "novbo", 5) == 0)
|
||||
@ -689,16 +689,22 @@ static void InitScripting()
|
||||
|
||||
// Register the JavaScript interfaces with the runtime
|
||||
CEntity::ScriptingInit();
|
||||
|
||||
CBaseEntity::ScriptingInit();
|
||||
|
||||
JSI_IGUIObject::init();
|
||||
JSI_GUITypes::init();
|
||||
JSI_Vector3D::init();
|
||||
EntityCollection::Init( "EntityCollection" );
|
||||
// PlayerCollection::Init( "PlayerCollection" );
|
||||
CDamageType::ScriptingInit();
|
||||
CJSPropertyAccessor<CEntity>::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor<T> is already being compiled for T = CEntity.
|
||||
CScriptEvent::ScriptingInit();
|
||||
|
||||
g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 );
|
||||
g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO );
|
||||
g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL );
|
||||
g_ScriptingHost.DefineConstant( "ORDER_ATTACK", CEntityOrder::ORDER_ATTACK_MELEE );
|
||||
|
||||
JSI_Camera::init();
|
||||
JSI_Console::init();
|
||||
}
|
||||
@ -795,6 +801,9 @@ static void Shutdown()
|
||||
{
|
||||
psShutdown(); // Must delete g_GUI before g_ScriptingHost
|
||||
|
||||
// Release script references to the globals before ScriptingHost shuts down
|
||||
// g_GameAttributes.ReleaseScriptObject();
|
||||
|
||||
if (g_Game)
|
||||
delete g_Game;
|
||||
|
||||
@ -1036,8 +1045,6 @@ PREVTSC=CURTSC;
|
||||
_CrtSetBreakAlloc(36367);
|
||||
//*/
|
||||
|
||||
// Initialize entities
|
||||
|
||||
in_add_handler(handler);
|
||||
in_add_handler(game_view_handler);
|
||||
|
||||
@ -1186,9 +1193,9 @@ static void Frame()
|
||||
// Choose when to override the standard exception handling behaviour
|
||||
// (opening the debugger when available, or crashing when not) with
|
||||
// code that generates a crash log/dump.
|
||||
#if defined(_WIN32) && ( defined(NDEBUG) || defined(TESTING) )
|
||||
# define CUSTOM_EXCEPTION_HANDLER
|
||||
#endif
|
||||
//#if defined(_WIN32) && ( defined(NDEBUG) || defined(TESTING) )
|
||||
// # define CUSTOM_EXCEPTION_HANDLER
|
||||
// #endif
|
||||
|
||||
#ifdef CUSTOM_EXCEPTION_HANDLER
|
||||
#include <excpt.h>
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include "Network/Client.h"
|
||||
#include "Network/Server.h"
|
||||
|
||||
#include "Interact.h"
|
||||
|
||||
extern bool keys[SDLK_LAST];
|
||||
|
||||
CConsole::CConsole()
|
||||
@ -497,14 +499,24 @@ void CConsole::ProcessBuffer(const wchar_t* szLine){
|
||||
{
|
||||
// Process it as JavaScript
|
||||
|
||||
g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ) );
|
||||
// (Cheating) run it as the first selected entity, if there is one.
|
||||
JSObject* RunAs = NULL;
|
||||
if( g_Selection.m_selected.size() )
|
||||
RunAs = g_Selection.m_selected[0]->GetScript();
|
||||
|
||||
g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ), L"Console", RunAs );
|
||||
|
||||
}
|
||||
else if (szLine[0] == '?')
|
||||
{
|
||||
// Process it as JavaScript and display the result
|
||||
|
||||
jsval rval = g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ) );
|
||||
// (Cheating) run it as the first selected entity, if there is one.
|
||||
JSObject* RunAs = NULL;
|
||||
if( g_Selection.m_selected.size() )
|
||||
RunAs = g_Selection.m_selected[0]->GetScript();
|
||||
|
||||
jsval rval = g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ), L"Console", RunAs );
|
||||
if (rval)
|
||||
{
|
||||
try {
|
||||
|
@ -14,7 +14,7 @@
|
||||
enum ELogMethod
|
||||
{
|
||||
NORMAL,
|
||||
MESSAGE=NORMAL,
|
||||
MESSAGE = NORMAL,
|
||||
ERROR,
|
||||
WARNING
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "Hotkey.h"
|
||||
#include "input.h"
|
||||
#include "ConfigDB.h"
|
||||
#include "CLogger.h"
|
||||
#include "CConsole.h"
|
||||
#include "CStr.h"
|
||||
|
||||
@ -16,6 +17,7 @@ bool unified[5];
|
||||
struct SHotkeyMapping
|
||||
{
|
||||
int mapsTo;
|
||||
bool negation;
|
||||
std::vector<int> requires;
|
||||
};
|
||||
|
||||
@ -66,12 +68,15 @@ static SHotkeyInfo hotkeyInfo[] =
|
||||
{ HOTKEY_CAMERA_ZOOM_WHEEL_IN, "camera.zoom.wheel.in", MOUSE_WHEELUP, 0 },
|
||||
{ HOTKEY_CAMERA_ZOOM_WHEEL_OUT, "camera.zoom.wheel.out", MOUSE_WHEELDOWN, 0 },
|
||||
{ HOTKEY_CAMERA_ROTATE, "camera.rotate", 0, 0 },
|
||||
{ HOTKEY_CAMERA_ROTATE_KEYBOARD, "camera.rotate.keyboard", 0, 0 },
|
||||
{ HOTKEY_CAMERA_ROTATE_ABOUT_TARGET, "camera.rotate.abouttarget", 0, 0 },
|
||||
{ HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD, "camera.rotate.abouttarget.keyboard", 0, 0 },
|
||||
{ HOTKEY_CAMERA_PAN, "camera.pan", MOUSE_MIDDLE, 0 },
|
||||
{ HOTKEY_CAMERA_PAN_LEFT, "camera.pan.left", SDLK_LEFT, 0 },
|
||||
{ HOTKEY_CAMERA_PAN_RIGHT, "camera.pan.right", SDLK_RIGHT, 0 },
|
||||
{ HOTKEY_CAMERA_PAN_FORWARD, "camera.pan.forward", SDLK_UP, 0 },
|
||||
{ HOTKEY_CAMERA_PAN_BACKWARD, "camera.pan.backward", SDLK_DOWN, 0 },
|
||||
{ HOTKEY_CAMERA_PAN_KEYBOARD, "camera.pan.keyboard", 0, 0 },
|
||||
{ HOTKEY_CAMERA_LEFT, "camera.left", SDLK_LEFT, 0 },
|
||||
{ HOTKEY_CAMERA_RIGHT, "camera.right", SDLK_RIGHT, 0 },
|
||||
{ HOTKEY_CAMERA_UP, "camera.up", SDLK_UP, 0 },
|
||||
{ HOTKEY_CAMERA_DOWN, "camera.down", SDLK_DOWN, 0 },
|
||||
{ HOTKEY_CAMERA_BOOKMARK_0, "camera.bookmark.0", SDLK_F5, 0, },
|
||||
{ HOTKEY_CAMERA_BOOKMARK_1, "camera.bookmark.1", SDLK_F6, 0, },
|
||||
{ HOTKEY_CAMERA_BOOKMARK_2, "camera.bookmark.2", SDLK_F7, 0, },
|
||||
@ -128,6 +133,7 @@ static SHotkeyInfo hotkeyInfo[] =
|
||||
struct SHotkeyMappingGUI
|
||||
{
|
||||
CStr mapsTo;
|
||||
bool negation;
|
||||
std::vector<int> requires;
|
||||
};
|
||||
|
||||
@ -153,7 +159,7 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 )
|
||||
|
||||
CConfigValueSet::iterator it;
|
||||
CParser multikeyParser;
|
||||
multikeyParser.InputTaskType( "multikey", "<_$value_+_>_$value" );
|
||||
multikeyParser.InputTaskType( "multikey", "<[!$arg(_negate)][~$arg(_negate)]$value_+_>_[!$arg(_negate)][~$arg(_negate)]$value" );
|
||||
|
||||
// Iterate through the bindings for this event...
|
||||
|
||||
@ -169,15 +175,39 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 )
|
||||
|
||||
// Iterate through multiple-key bindings (e.g. Ctrl+I)
|
||||
|
||||
bool negateNext = false;
|
||||
|
||||
for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ )
|
||||
{
|
||||
|
||||
if( multikeyIdentifier.GetArgString( (int)t, hotkey ) )
|
||||
{
|
||||
mapping = getKeyCode( hotkey ); // Attempt decode as key name
|
||||
if( hotkey == "_negate" )
|
||||
{
|
||||
negateNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt decode as key name
|
||||
mapping = getKeyCode( hotkey );
|
||||
|
||||
// Attempt to decode as a negation of a keyname
|
||||
// Yes, it's going a bit far, perhaps.
|
||||
// Too powerful for most uses, probably.
|
||||
// However, it got some hardcoding out of the engine.
|
||||
// Thus it makes me happy.
|
||||
|
||||
if( !mapping )
|
||||
if( !it->GetInt( mapping ) ) // Attempt decode as key code
|
||||
{
|
||||
LOG( WARNING, "hotkey", "Couldn't map '%s'", hotkey.c_str() );
|
||||
continue;
|
||||
}
|
||||
|
||||
if( negateNext ) mapping |= HOTKEY_NEGATION_FLAG;
|
||||
|
||||
negateNext = false;
|
||||
|
||||
keyCombination.push_back( mapping );
|
||||
}
|
||||
}
|
||||
@ -190,10 +220,12 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 )
|
||||
for( itKey = keyCombination.begin(); itKey != keyCombination.end(); itKey++ )
|
||||
{
|
||||
bindName.mapsTo = hotkeyName;
|
||||
bindName.negation = (bool)( *itKey & HOTKEY_NEGATION_FLAG );
|
||||
bindName.requires.clear();
|
||||
if( integerMapping != -1 )
|
||||
{
|
||||
bindCode.mapsTo = integerMapping;
|
||||
bindCode.negation = (bool)( *itKey & HOTKEY_NEGATION_FLAG );
|
||||
bindCode.requires.clear();
|
||||
}
|
||||
for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); itKey2++ )
|
||||
@ -207,9 +239,9 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 )
|
||||
}
|
||||
}
|
||||
|
||||
hotkeyMapGUI[*itKey].push_back( bindName );
|
||||
hotkeyMapGUI[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindName );
|
||||
if( integerMapping != -1 )
|
||||
hotkeyMap[*itKey].push_back( bindCode );
|
||||
hotkeyMap[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindCode );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -221,6 +253,8 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 )
|
||||
bind[1].mapsTo = integerMapping;
|
||||
bind[0].requires.clear();
|
||||
bind[1].requires.clear();
|
||||
bind[0].negation = false;
|
||||
bind[1].negation = false;
|
||||
hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping1 ].push_back( bind[0] );
|
||||
if( hotkeyInfo[integerMapping].defaultmapping2 )
|
||||
hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping2 ].push_back( bind[1] );
|
||||
@ -230,9 +264,35 @@ void loadHotkeys()
|
||||
{
|
||||
initKeyNameMap();
|
||||
|
||||
for( int i = 0; i < HOTKEY_LAST; i++ )
|
||||
int i;
|
||||
|
||||
for( i = 0; i < HOTKEY_LAST; i++ )
|
||||
setBindings( hotkeyInfo[i].name, i );
|
||||
|
||||
// Set up the state of the hotkeys given no key is down.
|
||||
// i.e. find those hotkeys triggered by all negations.
|
||||
|
||||
std::vector<SHotkeyMapping>::iterator it;
|
||||
std::vector<int>::iterator j;
|
||||
bool allNegated;
|
||||
|
||||
for( i = 1; i < HK_MAX_KEYCODES; i++ )
|
||||
{
|
||||
for( it = hotkeyMap[i].begin(); it != hotkeyMap[i].end(); it++ )
|
||||
{
|
||||
if( !it->negation )
|
||||
continue;
|
||||
|
||||
allNegated = true;
|
||||
|
||||
for( j = it->requires.begin(); j != it->requires.end(); j++ )
|
||||
if( !( *j & HOTKEY_NEGATION_FLAG ) )
|
||||
allNegated = false;
|
||||
|
||||
if( allNegated )
|
||||
hotkeys[it->mapsTo] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hotkeyRegisterGUIObject( const CStr& objName, const CStr& hotkeyName )
|
||||
@ -329,153 +389,180 @@ int hotkeyInputHandler( const SDL_Event* ev )
|
||||
// matching the conditions (i.e. the event with the highest number of auxiliary
|
||||
// keys, providing they're all down)
|
||||
|
||||
if( ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN ) )
|
||||
{
|
||||
// SDL-events bit
|
||||
bool typeKeyDown = ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN );
|
||||
|
||||
// -- KEYDOWN SECTION --
|
||||
|
||||
// SDL-events bit
|
||||
|
||||
unsigned int closestMap;
|
||||
size_t closestMapMatch = 0;
|
||||
|
||||
for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ )
|
||||
{
|
||||
// If a key has been pressed, and this event triggers on it's release, skip it.
|
||||
// Similarly, if the key's been released and the event triggers on a keypress, skip it.
|
||||
if( it->negation == typeKeyDown )
|
||||
continue;
|
||||
|
||||
// Check to see if all auxiliary keys are down
|
||||
|
||||
unsigned int closestMap;
|
||||
size_t closestMapMatch = 0;
|
||||
std::vector<int>::iterator itKey;
|
||||
bool accept = true;
|
||||
isCapturable = true;
|
||||
|
||||
for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ )
|
||||
{
|
||||
// Check to see if all auxiliary keys are down
|
||||
|
||||
std::vector<int>::iterator itKey;
|
||||
bool accept = true;
|
||||
isCapturable = true;
|
||||
|
||||
for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ )
|
||||
{
|
||||
if( *itKey < SDLK_LAST )
|
||||
{
|
||||
if( !keys[*itKey] ) accept = false;
|
||||
}
|
||||
else if( *itKey < UNIFIED_SHIFT )
|
||||
{
|
||||
if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false;
|
||||
}
|
||||
else
|
||||
if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false;
|
||||
|
||||
// If this event requires a multiple keypress (with the exception
|
||||
// of shift+key combinations) the console won't inhibit it.
|
||||
if( ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) )
|
||||
isCapturable = false;
|
||||
}
|
||||
|
||||
if( it->mapsTo == HOTKEY_CONSOLE_TOGGLE ) isCapturable = false; // Because that would be silly.
|
||||
|
||||
if( accept && !( isCapturable && consoleCapture ) )
|
||||
{
|
||||
hotkeys[it->mapsTo] = true;
|
||||
if( it->requires.size() >= closestMapMatch )
|
||||
{
|
||||
// Only if it's a more precise match, and it either isn't capturable or the console won't capture it.
|
||||
closestMap = it->mapsTo;
|
||||
closestMapMatch = it->requires.size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( closestMapMatch )
|
||||
for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ )
|
||||
{
|
||||
hotkeyNotification.type = SDL_HOTKEYDOWN;
|
||||
hotkeyNotification.user.code = closestMap;
|
||||
SDL_PushEvent( &hotkeyNotification );
|
||||
}
|
||||
// GUI bit... could do with some optimization later.
|
||||
int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit
|
||||
bool rqdState = !( *itKey & HOTKEY_NEGATION_FLAG );
|
||||
|
||||
CStr closestMapName = -1;
|
||||
closestMapMatch = 0;
|
||||
// assert( !rqdState );
|
||||
|
||||
for( itGUI = hotkeyMapGUI[keycode].begin(); itGUI != hotkeyMapGUI[keycode].end(); itGUI++ )
|
||||
{
|
||||
// Check to see if all auxiliary keys are down
|
||||
|
||||
std::vector<int>::iterator itKey;
|
||||
bool accept = true;
|
||||
isCapturable = true;
|
||||
|
||||
for( itKey = itGUI->requires.begin(); itKey != itGUI->requires.end(); itKey++ )
|
||||
if( keyCode < SDLK_LAST )
|
||||
{
|
||||
if( *itKey < SDLK_LAST )
|
||||
{
|
||||
if( !keys[*itKey] ) accept = false;
|
||||
}
|
||||
else if( *itKey < UNIFIED_SHIFT )
|
||||
{
|
||||
if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false;
|
||||
}
|
||||
else
|
||||
if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false;
|
||||
|
||||
// If this event requires a multiple keypress (with the exception
|
||||
// of shift+key combinations) the console won't inhibit it.
|
||||
if( ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) )
|
||||
isCapturable = false;
|
||||
if( keys[keyCode] != rqdState ) accept = false;
|
||||
}
|
||||
|
||||
if( accept && !( isCapturable && consoleCapture ) )
|
||||
else if( *itKey < UNIFIED_SHIFT )
|
||||
{
|
||||
if( itGUI->requires.size() >= closestMapMatch )
|
||||
{
|
||||
closestMapName = itGUI->mapsTo;
|
||||
closestMapMatch = itGUI->requires.size() + 1;
|
||||
}
|
||||
if( mouseButtons[keyCode-SDLK_LAST] != rqdState ) accept = false;
|
||||
}
|
||||
}
|
||||
// GUI-objects bit
|
||||
// This fragment is an obvious candidate for rewriting when speed becomes an issue.
|
||||
else
|
||||
if( unified[keyCode-UNIFIED_SHIFT] != rqdState ) accept = false;
|
||||
|
||||
if( closestMapMatch )
|
||||
// If this event requires a multiple keypress (with the exception
|
||||
// of shift+key combinations) the console won't inhibit it.
|
||||
if( rqdState && ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) )
|
||||
isCapturable = false;
|
||||
}
|
||||
|
||||
if( it->mapsTo == HOTKEY_CONSOLE_TOGGLE ) isCapturable = false; // Because that would be silly.
|
||||
|
||||
if( accept && !( isCapturable && consoleCapture ) )
|
||||
{
|
||||
GUIHotkeyMap::iterator map_it;
|
||||
GUIObjectList::iterator obj_it;
|
||||
map_it = guiHotkeyMap.find( closestMapName );
|
||||
if( map_it != guiHotkeyMap.end() )
|
||||
hotkeys[it->mapsTo] = true;
|
||||
if( it->requires.size() >= closestMapMatch )
|
||||
{
|
||||
GUIObjectList& targets = map_it->second;
|
||||
for( obj_it = targets.begin(); obj_it != targets.end(); obj_it++ )
|
||||
{
|
||||
hotkeyNotification.type = SDL_GUIHOTKEYPRESS;
|
||||
hotkeyNotification.user.code = (intptr_t)&(*obj_it);
|
||||
SDL_PushEvent( &hotkeyNotification );
|
||||
}
|
||||
}
|
||||
// Only if it's a more precise match, and it either isn't capturable or the console won't capture it.
|
||||
closestMap = it->mapsTo;
|
||||
closestMapMatch = it->requires.size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if( closestMapMatch )
|
||||
{
|
||||
for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ )
|
||||
hotkeyNotification.type = SDL_HOTKEYDOWN;
|
||||
hotkeyNotification.user.code = closestMap;
|
||||
SDL_PushEvent( &hotkeyNotification );
|
||||
}
|
||||
// GUI bit... could do with some optimization later.
|
||||
|
||||
CStr closestMapName = -1;
|
||||
closestMapMatch = 0;
|
||||
|
||||
for( itGUI = hotkeyMapGUI[keycode].begin(); itGUI != hotkeyMapGUI[keycode].end(); itGUI++ )
|
||||
{
|
||||
// If a key has been pressed, and this event triggers on it's release, skip it.
|
||||
// Similarly, if the key's been released and the event triggers on a keypress, skip it.
|
||||
if( itGUI->negation == typeKeyDown )
|
||||
continue;
|
||||
|
||||
// Check to see if all auxiliary keys are down
|
||||
|
||||
std::vector<int>::iterator itKey;
|
||||
bool accept = true;
|
||||
isCapturable = true;
|
||||
|
||||
for( itKey = itGUI->requires.begin(); itKey != itGUI->requires.end(); itKey++ )
|
||||
{
|
||||
// Check to see if all auxiliary keys are down
|
||||
int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit
|
||||
bool rqdState = !( *itKey & HOTKEY_NEGATION_FLAG );
|
||||
|
||||
std::vector<int>::iterator itKey;
|
||||
bool accept = true;
|
||||
|
||||
for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ )
|
||||
if( keyCode < SDLK_LAST )
|
||||
{
|
||||
if( *itKey < SDLK_LAST )
|
||||
{
|
||||
if( !keys[*itKey] ) accept = false;
|
||||
}
|
||||
else if( *itKey < UNIFIED_SHIFT )
|
||||
{
|
||||
if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false;
|
||||
}
|
||||
else
|
||||
if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false;
|
||||
if( keys[keyCode] != rqdState ) accept = false;
|
||||
}
|
||||
|
||||
if( accept )
|
||||
else if( *itKey < UNIFIED_SHIFT )
|
||||
{
|
||||
hotkeys[it->mapsTo] = false;
|
||||
hotkeyNotification.type = SDL_HOTKEYUP;
|
||||
hotkeyNotification.user.code = it->mapsTo;
|
||||
if( mouseButtons[keyCode-SDLK_LAST] != rqdState ) accept = false;
|
||||
}
|
||||
else
|
||||
if( unified[keyCode-UNIFIED_SHIFT] != rqdState ) accept = false;
|
||||
|
||||
// If this event requires a multiple keypress (with the exception
|
||||
// of shift+key combinations) the console won't inhibit it.
|
||||
if( rqdState && ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) )
|
||||
isCapturable = false;
|
||||
}
|
||||
|
||||
if( accept && !( isCapturable && consoleCapture ) )
|
||||
{
|
||||
if( itGUI->requires.size() >= closestMapMatch )
|
||||
{
|
||||
closestMapName = itGUI->mapsTo;
|
||||
closestMapMatch = itGUI->requires.size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// GUI-objects bit
|
||||
// This fragment is an obvious candidate for rewriting when speed becomes an issue.
|
||||
|
||||
if( closestMapMatch )
|
||||
{
|
||||
GUIHotkeyMap::iterator map_it;
|
||||
GUIObjectList::iterator obj_it;
|
||||
map_it = guiHotkeyMap.find( closestMapName );
|
||||
if( map_it != guiHotkeyMap.end() )
|
||||
{
|
||||
GUIObjectList& targets = map_it->second;
|
||||
for( obj_it = targets.begin(); obj_it != targets.end(); obj_it++ )
|
||||
{
|
||||
hotkeyNotification.type = SDL_GUIHOTKEYPRESS;
|
||||
hotkeyNotification.user.code = (intptr_t)&(*obj_it);
|
||||
SDL_PushEvent( &hotkeyNotification );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- KEYUP SECTION --
|
||||
|
||||
for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ )
|
||||
{
|
||||
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't
|
||||
// use this key negated => skip them
|
||||
// If it's a keyup event, won't cause HotKeyUps in anything that does use
|
||||
// this key negated => skip them too.
|
||||
if( it->negation != typeKeyDown )
|
||||
continue;
|
||||
|
||||
// Check to see if all auxiliary keys are down
|
||||
|
||||
std::vector<int>::iterator itKey;
|
||||
bool accept = true;
|
||||
|
||||
for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ )
|
||||
{
|
||||
if( *itKey < SDLK_LAST )
|
||||
{
|
||||
if( !keys[*itKey] ) accept = false;
|
||||
}
|
||||
else if( *itKey < UNIFIED_SHIFT )
|
||||
{
|
||||
if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false;
|
||||
}
|
||||
else
|
||||
if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false;
|
||||
}
|
||||
|
||||
if( accept )
|
||||
{
|
||||
hotkeys[it->mapsTo] = false;
|
||||
hotkeyNotification.type = SDL_HOTKEYUP;
|
||||
hotkeyNotification.user.code = it->mapsTo;
|
||||
SDL_PushEvent( &hotkeyNotification );
|
||||
}
|
||||
}
|
||||
|
||||
return( EV_PASS );
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,15 @@ enum
|
||||
HOTKEY_CAMERA_ZOOM_WHEEL_IN,
|
||||
HOTKEY_CAMERA_ZOOM_WHEEL_OUT,
|
||||
HOTKEY_CAMERA_ROTATE,
|
||||
HOTKEY_CAMERA_ROTATE_KEYBOARD,
|
||||
HOTKEY_CAMERA_ROTATE_ABOUT_TARGET,
|
||||
HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD,
|
||||
HOTKEY_CAMERA_PAN,
|
||||
HOTKEY_CAMERA_PAN_LEFT,
|
||||
HOTKEY_CAMERA_PAN_RIGHT,
|
||||
HOTKEY_CAMERA_PAN_FORWARD,
|
||||
HOTKEY_CAMERA_PAN_BACKWARD,
|
||||
HOTKEY_CAMERA_PAN_KEYBOARD,
|
||||
HOTKEY_CAMERA_LEFT,
|
||||
HOTKEY_CAMERA_RIGHT,
|
||||
HOTKEY_CAMERA_UP,
|
||||
HOTKEY_CAMERA_DOWN,
|
||||
HOTKEY_CAMERA_BOOKMARK_0,
|
||||
HOTKEY_CAMERA_BOOKMARK_1,
|
||||
HOTKEY_CAMERA_BOOKMARK_2,
|
||||
@ -92,6 +95,8 @@ enum
|
||||
HOTKEY_PLAYMUSIC,
|
||||
|
||||
HOTKEY_LAST,
|
||||
|
||||
HOTKEY_NEGATION_FLAG = 65536
|
||||
};
|
||||
|
||||
void loadHotkeys();
|
||||
|
@ -13,6 +13,7 @@ extern CConsole* g_Console;
|
||||
extern int mouse_x, mouse_y;
|
||||
extern bool keys[SDLK_LAST];
|
||||
extern bool g_active;
|
||||
extern CStr g_CursorName;
|
||||
|
||||
static const float SELECT_DBLCLICK_RATE = 0.5f;
|
||||
const int ORDER_DELAY = 5;
|
||||
@ -23,6 +24,7 @@ void CSelectedEntities::addSelection( HEntity entity )
|
||||
assert( !isSelected( entity ) );
|
||||
m_selected.push_back( entity );
|
||||
entity->m_selected = true;
|
||||
m_selectionChanged = true;
|
||||
}
|
||||
|
||||
void CSelectedEntities::removeSelection( HEntity entity )
|
||||
@ -36,6 +38,7 @@ void CSelectedEntities::removeSelection( HEntity entity )
|
||||
if( (*it) == entity )
|
||||
{
|
||||
m_selected.erase( it );
|
||||
m_selectionChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -126,6 +129,9 @@ void CSelectedEntities::renderOverlays()
|
||||
case CEntityOrder::ORDER_PATROL:
|
||||
glwprintf( L"Patrol to" );
|
||||
break;
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE:
|
||||
glwprintf( L"Attack" );
|
||||
break;
|
||||
}
|
||||
|
||||
glDisable( GL_TEXTURE_2D );
|
||||
@ -146,6 +152,7 @@ void CSelectedEntities::clearSelection()
|
||||
for( it = m_selected.begin(); it < m_selected.end(); it++ )
|
||||
(*it)->m_selected = false;
|
||||
m_selected.clear();
|
||||
m_selectionChanged = true;
|
||||
}
|
||||
|
||||
void CSelectedEntities::removeAll( HEntity entity )
|
||||
@ -158,6 +165,7 @@ void CSelectedEntities::removeAll( HEntity entity )
|
||||
if( (*it) == entity )
|
||||
{
|
||||
m_selected.erase( it );
|
||||
m_selectionChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -168,6 +176,7 @@ void CSelectedEntities::removeAll( HEntity entity )
|
||||
if( (*it) == entity )
|
||||
{
|
||||
m_groups[group].erase( it );
|
||||
m_selectionChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -242,12 +251,18 @@ void CSelectedEntities::addToGroup( i8 groupid, HEntity entity )
|
||||
|
||||
void CSelectedEntities::loadGroup( i8 groupid )
|
||||
{
|
||||
if( m_group == groupid )
|
||||
return;
|
||||
|
||||
clearSelection();
|
||||
m_selected = m_groups[groupid];
|
||||
|
||||
std::vector<HEntity>::iterator it;
|
||||
for( it = m_selected.begin(); it < m_selected.end(); it++ )
|
||||
(*it)->m_selected = true;
|
||||
m_group = groupid;
|
||||
|
||||
m_selectionChanged = true;
|
||||
}
|
||||
|
||||
void CSelectedEntities::addGroup( i8 groupid )
|
||||
@ -329,6 +344,52 @@ CVector3D CSelectedEntities::getGroupPosition( i8 groupid )
|
||||
|
||||
void CSelectedEntities::update()
|
||||
{
|
||||
if( m_selectionChanged || g_Mouseover.m_targetChanged )
|
||||
{
|
||||
// Can't order anything off the map
|
||||
if( !g_Game->GetWorld()->GetTerrain()->isOnMap( g_Mouseover.m_worldposition ) )
|
||||
{
|
||||
m_contextOrder = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Quick count to see which is the modal default order.
|
||||
|
||||
int defaultPoll[CEntityOrder::ORDER_LAST];
|
||||
int t, vote;
|
||||
for( t = 0; t < CEntityOrder::ORDER_LAST; t++ )
|
||||
defaultPoll[t] = 0;
|
||||
|
||||
std::vector<HEntity>::iterator it;
|
||||
for( it = m_selected.begin(); it < m_selected.end(); it++ )
|
||||
{
|
||||
vote = (*it)->defaultOrder( g_Mouseover.m_target );
|
||||
if( ( vote >= 0 ) && ( vote < CEntityOrder::ORDER_LAST ) )
|
||||
defaultPoll[vote]++;
|
||||
}
|
||||
|
||||
vote = -1;
|
||||
for( t = 0; t < CEntityOrder::ORDER_LAST; t++ )
|
||||
{
|
||||
if( ( vote == -1 ) || ( defaultPoll[t] > defaultPoll[vote] ) )
|
||||
vote = t;
|
||||
}
|
||||
|
||||
m_contextOrder = vote;
|
||||
switch( m_contextOrder )
|
||||
{
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE:
|
||||
g_CursorName = "action-attack";
|
||||
break;
|
||||
default:
|
||||
g_CursorName = "arrow-default";
|
||||
break;
|
||||
}
|
||||
|
||||
m_selectionChanged = false;
|
||||
g_Mouseover.m_targetChanged = false;
|
||||
}
|
||||
/*
|
||||
if( !isContextValid( m_contextOrder ) )
|
||||
{
|
||||
// This order isn't valid for the current selection and/or target.
|
||||
@ -339,6 +400,7 @@ void CSelectedEntities::update()
|
||||
}
|
||||
m_contextOrder = -1;
|
||||
}
|
||||
*/
|
||||
|
||||
if( ( m_group_highlight != -1 ) && getGroupCount( m_group_highlight ) )
|
||||
g_Game->GetView()->SetCameraTarget( getGroupPosition( m_group_highlight ) );
|
||||
@ -413,15 +475,31 @@ void CSelectedEntities::contextOrder( bool pushQueue )
|
||||
switch( m_contextOrder )
|
||||
{
|
||||
// PATROL order: temporatily disabled until we define the network command for it
|
||||
// case CEntityOrder::ORDER_PATROL:
|
||||
case CEntityOrder::ORDER_PATROL:
|
||||
case CEntityOrder::ORDER_GOTO:
|
||||
{
|
||||
context.m_data[0].location = g_Mouseover.m_worldposition;
|
||||
break;
|
||||
/*
|
||||
CGotoCommand *msg=new CGotoCommand();
|
||||
msg->m_Entity=m_selected[0]->me;
|
||||
msg->m_TargetX=(u32)g_Mouseover.m_worldposition.x;
|
||||
msg->m_TargetY=(u32)g_Mouseover.m_worldposition.y;
|
||||
g_Game->GetSimulation()->QueueLocalCommand(msg);
|
||||
break;
|
||||
*/
|
||||
}
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE:
|
||||
{
|
||||
context.m_data[0].entity = g_Mouseover.m_target;
|
||||
for( it = m_selected.begin(); it < m_selected.end(); it++ )
|
||||
if( (*it)->acceptsOrder( m_contextOrder, g_Mouseover.m_target ) )
|
||||
{
|
||||
if( !pushQueue )
|
||||
(*it)->clearOrders();
|
||||
(*it)->pushOrder( context );
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@ -447,8 +525,7 @@ void CSelectedEntities::contextOrder( bool pushQueue )
|
||||
pathfinder?
|
||||
*/
|
||||
|
||||
/*
|
||||
float radius = 2.0f * sqrt( (float)m_selected.size() - 1 ); // A decent enough approximation
|
||||
float radius = 2.0f * sqrt( (float)m_selected.size() - 1 );
|
||||
|
||||
float _x, _y;
|
||||
|
||||
@ -478,9 +555,12 @@ void CSelectedEntities::contextOrder( bool pushQueue )
|
||||
if( contextRandomized.m_data[0].location.y >= mapsize )
|
||||
contextRandomized.m_data[0].location.y = mapsize;
|
||||
|
||||
g_Scheduler.pushFrame( ORDER_DELAY, (*it)->me, new CMessageOrder( contextRandomized, pushQueue ) );
|
||||
if( !pushQueue )
|
||||
(*it)->clearOrders();
|
||||
|
||||
(*it)->pushOrder( contextRandomized );
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
void CMouseoverEntities::update( float timestep )
|
||||
@ -495,13 +575,19 @@ void CMouseoverEntities::update( float timestep )
|
||||
|
||||
m_worldposition = pCamera->GetWorldCoordinates();
|
||||
|
||||
if( hit && hit->GetEntity() )
|
||||
if( hit && hit->GetEntity() && hit->GetEntity()->m_extant )
|
||||
{
|
||||
m_target = hit->GetEntity()->me;
|
||||
}
|
||||
else
|
||||
m_target = HEntity();
|
||||
|
||||
if( m_target != m_lastTarget )
|
||||
{
|
||||
m_targetChanged = true;
|
||||
m_lastTarget = m_target;
|
||||
}
|
||||
|
||||
if( m_viewall )
|
||||
{
|
||||
// 'O' key. Show selection outlines for all player units on screen
|
||||
@ -515,7 +601,8 @@ void CMouseoverEntities::update( float timestep )
|
||||
std::vector<HEntity>::iterator it;
|
||||
|
||||
for( it = onscreen->begin(); it < onscreen->end(); it++ )
|
||||
m_mouseover.push_back( SMouseoverFader( *it, m_fademaximum, false ) );
|
||||
if( (*it)->m_extant )
|
||||
m_mouseover.push_back( SMouseoverFader( *it, m_fademaximum, false ) );
|
||||
|
||||
delete( onscreen );
|
||||
}
|
||||
@ -540,6 +627,9 @@ void CMouseoverEntities::update( float timestep )
|
||||
|
||||
for( it = onscreen->begin(); it < onscreen->end(); it++ )
|
||||
{
|
||||
if( !(*it)->m_extant )
|
||||
continue;
|
||||
|
||||
CVector3D worldspace = (*it)->m_graphics_position;
|
||||
|
||||
float x, y;
|
||||
@ -655,9 +745,8 @@ void CMouseoverEntities::expandAcrossScreen()
|
||||
m_mouseover.clear();
|
||||
std::vector<HEntity>::iterator it;
|
||||
for( it = activeset->begin(); it < activeset->end(); it++ )
|
||||
{
|
||||
m_mouseover.push_back( SMouseoverFader( *it ) );
|
||||
}
|
||||
if( (*it)->m_extant )
|
||||
m_mouseover.push_back( SMouseoverFader( *it ) );
|
||||
delete( activeset );
|
||||
}
|
||||
|
||||
@ -667,9 +756,8 @@ void CMouseoverEntities::expandAcrossWorld()
|
||||
m_mouseover.clear();
|
||||
std::vector<HEntity>::iterator it;
|
||||
for( it = activeset->begin(); it < activeset->end(); it++ )
|
||||
{
|
||||
m_mouseover.push_back( SMouseoverFader( *it ) );
|
||||
}
|
||||
if( (*it)->m_extant )
|
||||
m_mouseover.push_back( SMouseoverFader( *it ) );
|
||||
delete( activeset );
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,18 @@
|
||||
|
||||
struct CSelectedEntities : public Singleton<CSelectedEntities>
|
||||
{
|
||||
CSelectedEntities() { clearSelection(); m_group = -1; m_group_highlight = -1; m_contextOrder = -1; }
|
||||
CSelectedEntities()
|
||||
{
|
||||
clearSelection();
|
||||
m_group = -1;
|
||||
m_group_highlight = -1;
|
||||
m_contextOrder = -1;
|
||||
m_selectionChanged = true;
|
||||
}
|
||||
std::vector<HEntity> m_selected;
|
||||
std::vector<HEntity> m_groups[MAX_GROUPS];
|
||||
i8 m_group, m_group_highlight;
|
||||
bool m_selectionChanged;
|
||||
int m_contextOrder;
|
||||
|
||||
void addSelection( HEntity entity );
|
||||
@ -75,6 +83,8 @@ struct CMouseoverEntities : public Singleton<CMouseoverEntities>
|
||||
float m_fademaximum;
|
||||
CVector2D m_worldposition;
|
||||
HEntity m_target;
|
||||
HEntity m_lastTarget;
|
||||
bool m_targetChanged;
|
||||
|
||||
bool m_bandbox, m_viewall;
|
||||
u16 m_x1, m_y1, m_x2, m_y2;
|
||||
@ -87,7 +97,7 @@ struct CMouseoverEntities : public Singleton<CMouseoverEntities>
|
||||
m_fadeoutrate = 2.0f;
|
||||
m_fademaximum = 0.5f;
|
||||
m_mouseover.clear();
|
||||
m_target;
|
||||
m_targetChanged = true;
|
||||
}
|
||||
std::vector<SMouseoverFader> m_mouseover;
|
||||
void update( float timestep );
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "types.h"
|
||||
#include "CStr.h"
|
||||
#include "scripting/JSSerialization.h"
|
||||
|
||||
enum ENetMessageType
|
||||
{
|
||||
|
@ -33,6 +33,8 @@ JSString* StringConvert::wstring_to_jsstring(JSContext* cx, const std::wstring&
|
||||
JSString* StringConvert::wchars_to_jsstring(JSContext* cx, const wchar_t* chars)
|
||||
{
|
||||
size_t len = wcslen(chars);
|
||||
if( !len )
|
||||
return( JSVAL_TO_STRING( JS_GetEmptyStringValue( cx ) ) );
|
||||
jschar* data = (jschar*)JS_malloc(cx, len*sizeof(jschar));
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
@ -21,7 +21,9 @@ void CWorld::Initialize(CGameAttributes *pAttribs)
|
||||
g_EntityTemplateCollection.loadTemplates();
|
||||
|
||||
CStr mapfilename("maps/scenarios/");
|
||||
mapfilename += (CStr)pAttribs->GetValue("mapFile");
|
||||
|
||||
mapfilename += (CStr)pAttribs->GetValue( "mapFile" );
|
||||
|
||||
try {
|
||||
CMapReader reader;
|
||||
reader.LoadMap(mapfilename, &m_Terrain, &m_UnitManager, &g_LightEnv);
|
||||
|
@ -1,4 +1,4 @@
|
||||
// $Id: XeroXMB.cpp,v 1.7 2004/10/07 20:49:25 philip Exp $
|
||||
// $Id: XeroXMB.cpp,v 1.8 2004/11/11 07:09:32 markt Exp $
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
@ -198,7 +198,7 @@ XMBElement XMBElementList::item(const int id)
|
||||
Pos += *(int*)Pos;
|
||||
}
|
||||
// Cache information about this node
|
||||
m_LastItemID = id;
|
||||
m_LastItemID = id;
|
||||
m_LastPointer = Pos;
|
||||
|
||||
return XMBElement(Pos);
|
||||
@ -245,5 +245,5 @@ XMBAttribute XMBAttributeList::item(const int id)
|
||||
m_LastItemID = id;
|
||||
m_LastPointer = Pos;
|
||||
|
||||
return XMBAttribute(*(int*)Pos, utf16string( (utf16_t*)(Pos+8) ));
|
||||
return XMBAttribute(*(int*)Pos, utf16string( (utf16_t*)(Pos+8) ));
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* $Id: XeroXMB.h,v 1.6 2004/10/07 20:49:25 philip Exp $
|
||||
/* $Id: XeroXMB.h,v 1.7 2004/11/11 07:09:32 markt Exp $
|
||||
|
||||
Xeromyces - XMB reading library
|
||||
|
||||
@ -200,7 +200,7 @@ class XMBAttributeList
|
||||
{
|
||||
public:
|
||||
XMBAttributeList(char* offset, int count)
|
||||
: Count(count), m_Pointer(offset) {};
|
||||
: Count(count), m_Pointer(offset), m_LastItemID( -2 ) {};
|
||||
|
||||
// Get the attribute value directly (unlike Xerces)
|
||||
utf16string getNamedItem(const int AttributeName) const;
|
||||
|
@ -158,7 +158,7 @@ template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::G
|
||||
return( JS_TRUE );
|
||||
}
|
||||
|
||||
*vp = ToJSVal<T>( set->at( index ) );
|
||||
*vp = ToJSVal( set->at( index ) );
|
||||
|
||||
return( JS_TRUE );
|
||||
}
|
||||
@ -276,7 +276,7 @@ template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::S
|
||||
int i = 0;
|
||||
|
||||
for( it = Set->begin(); it != Set->end(); it++ )
|
||||
if( Predicate.Run( ToScript<T>( &( *it ) ) ) )
|
||||
if( Predicate.Run( ToScript( (T*)&( *it ) ) ) )
|
||||
CollectionData->m_Data->push_back( *it );
|
||||
|
||||
*rval = OBJECT_TO_JSVAL( Collection );
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
// Target (currently unused)
|
||||
// EventTarget* m_Target;
|
||||
|
||||
// Listening object currently being processed (currentky unused)
|
||||
// Listening object currently being processed (currently unused)
|
||||
// EventTarget* m_CurrentTarget;
|
||||
|
||||
// Phase type (currently unused)
|
||||
|
@ -33,9 +33,9 @@ template<> JSObject* ToScript<CBaseEntity*>( CBaseEntity** Native )
|
||||
return( ToScript<CBaseEntity>( *Native ) );
|
||||
}
|
||||
|
||||
// CObjectEntry*
|
||||
// CObjectEntry
|
||||
|
||||
template<> bool ToPrimitive<CObjectEntry*>( JSContext* cx, jsval v, CObjectEntry*& Storage )
|
||||
template<> bool ToPrimitive<CObjectEntry>( JSContext* cx, jsval v, CObjectEntry*& Storage )
|
||||
{
|
||||
CStrW ActorName;
|
||||
if( !ToPrimitive<CStrW>( cx, v, ActorName ) )
|
||||
@ -44,7 +44,7 @@ template<> bool ToPrimitive<CObjectEntry*>( JSContext* cx, jsval v, CObjectEntry
|
||||
return( true );
|
||||
}
|
||||
|
||||
template<> jsval ToJSVal<CObjectEntry*>( CObjectEntry*& Native )
|
||||
template<> jsval ToJSVal<CObjectEntry>( CObjectEntry*& Native )
|
||||
{
|
||||
if( !Native )
|
||||
return( ToJSVal<CStrW>( CStrW( L"[No actor]" ) ) );
|
||||
@ -80,19 +80,19 @@ template<> bool ToPrimitive<CScriptObject>( JSContext* cx, jsval v, CScriptObjec
|
||||
return( true );
|
||||
}
|
||||
|
||||
// i32
|
||||
// int
|
||||
|
||||
template<> jsval ToJSVal<i32>( const i32& Native )
|
||||
template<> jsval ToJSVal<int>( const int& Native )
|
||||
{
|
||||
return( INT_TO_JSVAL( Native ) );
|
||||
}
|
||||
|
||||
template<> jsval ToJSVal<i32>( i32& Native )
|
||||
template<> jsval ToJSVal<int>( int& Native )
|
||||
{
|
||||
return( INT_TO_JSVAL( Native ) );
|
||||
}
|
||||
|
||||
template<> bool ToPrimitive<i32>( JSContext* cx, jsval v, i32& Storage )
|
||||
template<> bool ToPrimitive<int>( JSContext* cx, jsval v, int& Storage )
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -105,6 +105,33 @@ template<> bool ToPrimitive<i32>( JSContext* cx, jsval v, i32& Storage )
|
||||
return( true );
|
||||
}
|
||||
|
||||
// uint
|
||||
|
||||
template<> jsval ToJSVal<uint>( const uint& Native )
|
||||
{
|
||||
return( INT_TO_JSVAL( Native ) );
|
||||
}
|
||||
|
||||
template<> jsval ToJSVal<uint>( uint& Native )
|
||||
{
|
||||
return( INT_TO_JSVAL( Native ) );
|
||||
}
|
||||
|
||||
template<> bool ToPrimitive<uint>( JSContext* cx, jsval v, uint& Storage )
|
||||
{
|
||||
try
|
||||
{
|
||||
int t = g_ScriptingHost.ValueToInt( v );
|
||||
if( t < 0 ) return( false );
|
||||
Storage = t;
|
||||
}
|
||||
catch( PSERROR_Scripting_ConversionFailed )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
return( true );
|
||||
}
|
||||
|
||||
// double
|
||||
|
||||
template<> jsval ToJSVal<double>( const double& Native )
|
||||
|
@ -22,6 +22,12 @@ class CVector3D;
|
||||
|
||||
template<typename T> T* ToNative( JSContext* cx, JSObject* obj )
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
if( OBJECT_TO_JSVAL( obj ) == JSVAL_NULL )
|
||||
return( NULL );
|
||||
assert( JS_GetClass( obj ) == &T::JSI_class );
|
||||
return( (T*)JS_GetPrivate( cx, obj ) );
|
||||
#endif
|
||||
return( (T*)JS_GetInstancePrivate( cx, obj, &T::JSI_class, NULL ) );
|
||||
}
|
||||
|
||||
@ -46,14 +52,35 @@ template<typename T> bool ToPrimitive( JSContext* cx, jsval v, T& Storage )
|
||||
return( true );
|
||||
}
|
||||
|
||||
// Handle pointer-to-objects sensibly (by automatically dereferencing them one level)
|
||||
template<typename T> bool ToPrimitive( JSContext* cx, jsval v, T*& Storage )
|
||||
{
|
||||
T* Native = ToNative<T>( v );
|
||||
if( !Native ) return( false );
|
||||
Storage = Native;
|
||||
return( true );
|
||||
}
|
||||
|
||||
/*
|
||||
template<typename T> JSObject* ToScript( T** Native )
|
||||
{
|
||||
return( ToScript( *Native ) );
|
||||
}
|
||||
*/
|
||||
|
||||
template<typename T> inline T ToPrimitive( JSContext* cx, jsval v ) { T Temp; ToPrimitive( cx, v, Temp ); return( Temp ); }
|
||||
template<typename T> inline T ToPrimitive( jsval v ) { return( ToPrimitive( g_ScriptingHost.GetContext(), v ) ); }
|
||||
template<typename T> inline T ToPrimitive( jsval v ) { return( ToPrimitive<T>( g_ScriptingHost.GetContext(), v ) ); }
|
||||
|
||||
template<typename T> jsval ToJSVal( T& Native )
|
||||
{
|
||||
return( OBJECT_TO_JSVAL( ToScript<T>( &Native ) ) );
|
||||
}
|
||||
|
||||
template<typename T> jsval ToJSVal( T*& Native )
|
||||
{
|
||||
return( OBJECT_TO_JSVAL( ToScript<T>( Native ) ) );
|
||||
}
|
||||
|
||||
template<typename T> jsval ToJSVal( const T& Native );
|
||||
|
||||
// -----
|
||||
@ -71,8 +98,8 @@ template<> bool ToPrimitive<CBaseEntity*>( JSContext* cx, jsval v, CBaseEntity*&
|
||||
template<> JSObject* ToScript<CBaseEntity*>( CBaseEntity** Native );
|
||||
|
||||
// CObjectEntry
|
||||
template<> bool ToPrimitive<CObjectEntry*>( JSContext* cx, jsval v, CObjectEntry*& Storage );
|
||||
template<> jsval ToJSVal<CObjectEntry*>( CObjectEntry*& Native );
|
||||
template<> bool ToPrimitive<CObjectEntry>( JSContext* cx, jsval v, CObjectEntry*& Storage );
|
||||
template<> jsval ToJSVal<CObjectEntry>( CObjectEntry*& Native );
|
||||
|
||||
// HEntity
|
||||
template<> HEntity* ToNative<HEntity>( JSContext* cx, JSObject* obj );
|
||||
@ -82,10 +109,15 @@ template<> JSObject* ToScript<HEntity>( HEntity* Native );
|
||||
template<> bool ToPrimitive<CScriptObject>( JSContext* cx, jsval v, CScriptObject& Storage );
|
||||
template<> jsval ToJSVal<CScriptObject>( CScriptObject& Native );
|
||||
|
||||
// i32
|
||||
template<> bool ToPrimitive<int>( JSContext* cx, jsval v, i32& Storage );
|
||||
template<> jsval ToJSVal<int>( const i32& Native );
|
||||
template<> jsval ToJSVal<int>( i32& Native );
|
||||
// int
|
||||
template<> bool ToPrimitive<int>( JSContext* cx, jsval v, int& Storage );
|
||||
template<> jsval ToJSVal<int>( const int& Native );
|
||||
template<> jsval ToJSVal<int>( int& Native );
|
||||
|
||||
// uint
|
||||
template<> bool ToPrimitive<uint>( JSContext* cx, jsval v, uint& Storage );
|
||||
template<> jsval ToJSVal<uint>( const uint& Native );
|
||||
template<> jsval ToJSVal<uint>( uint& Native );
|
||||
|
||||
// double
|
||||
template<> bool ToPrimitive<double>( JSContext* cx, jsval v, double& Storage );
|
||||
|
134
source/scripting/JSSerialization.h
Executable file
134
source/scripting/JSSerialization.h
Executable file
@ -0,0 +1,134 @@
|
||||
// Functions for (de)serialization of jsvals
|
||||
//
|
||||
// Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk)
|
||||
|
||||
#include "Serialization.h"
|
||||
#include "JSConversions.h"
|
||||
#include "CStr.h"
|
||||
|
||||
class jsval_ser : public ISerializable
|
||||
{
|
||||
enum
|
||||
{
|
||||
TAG_BOOLEAN_FALSE,
|
||||
TAG_BOOLEAN_TRUE,
|
||||
TAG_INT,
|
||||
TAG_DOUBLE,
|
||||
TAG_STRING,
|
||||
TAG_NOT_SERIALIZABLE = -1
|
||||
} m_tag;
|
||||
jsval m_data;
|
||||
public:
|
||||
jsval_ser() : m_tag( TAG_NOT_SERIALIZABLE )
|
||||
{
|
||||
}
|
||||
jsval_ser( jsval data ) : m_data( data )
|
||||
{
|
||||
if( m_data == JSVAL_FALSE )
|
||||
m_tag = TAG_BOOLEAN_FALSE;
|
||||
if( m_data == JSVAL_TRUE )
|
||||
m_tag = TAG_BOOLEAN_TRUE;
|
||||
if( JSVAL_IS_INT( m_data ) )
|
||||
m_tag = TAG_INT;
|
||||
if( JSVAL_IS_DOUBLE( m_data ) )
|
||||
m_tag = TAG_DOUBLE;
|
||||
if( JSVAL_IS_STRING( m_data ) )
|
||||
m_tag = TAG_STRING;
|
||||
m_tag = TAG_NOT_SERIALIZABLE;
|
||||
}
|
||||
operator jsval() const
|
||||
{
|
||||
return( m_data );
|
||||
}
|
||||
operator CStr() const
|
||||
{
|
||||
return( ToPrimitive<CStrW>( m_data ) );
|
||||
}
|
||||
uint GetSerializedLength() const
|
||||
{
|
||||
switch( m_tag )
|
||||
{
|
||||
case TAG_BOOLEAN_FALSE:
|
||||
case TAG_BOOLEAN_TRUE:
|
||||
return( 1 );
|
||||
case TAG_INT:
|
||||
return( 5 );
|
||||
case TAG_DOUBLE:
|
||||
return( 9 );
|
||||
case TAG_STRING:
|
||||
return( 1 + (ToPrimitive<CStrW>(m_data)).GetSerializedLength() );
|
||||
default:
|
||||
assert( 0 && "An attempt was made to serialize a jsval other than a number, boolean or string." );
|
||||
return( 1 );
|
||||
}
|
||||
}
|
||||
u8* Serialize( u8* buffer ) const
|
||||
{
|
||||
Serialize_int_1( buffer, m_tag );
|
||||
switch( m_tag )
|
||||
{
|
||||
case TAG_BOOLEAN_FALSE:
|
||||
case TAG_BOOLEAN_TRUE:
|
||||
break;
|
||||
case TAG_INT:
|
||||
{
|
||||
u32 ival = JSVAL_TO_INT( m_data );
|
||||
Serialize_int_4( buffer, ival );
|
||||
}
|
||||
break;
|
||||
case TAG_DOUBLE:
|
||||
// Ehm. I think this works, but I can't say as it's something I've tried before.
|
||||
{
|
||||
u64 ival = *( (u64*)JSVAL_TO_DOUBLE( m_data ) );
|
||||
Serialize_int_8( buffer, ival );
|
||||
}
|
||||
break;
|
||||
case TAG_STRING:
|
||||
buffer = ( ToPrimitive<CStrW>( m_data ) ).Serialize( buffer );
|
||||
break;
|
||||
default:
|
||||
assert( 0 && "An attempt was made to serialize a jsval other than a number, boolean or string." );
|
||||
break;
|
||||
}
|
||||
return( buffer );
|
||||
}
|
||||
const u8* Deserialize( const u8* buffer, const u8* end )
|
||||
{
|
||||
Deserialize_int_1( buffer, (u8&)m_tag );
|
||||
switch( m_tag )
|
||||
{
|
||||
case TAG_BOOLEAN_FALSE:
|
||||
m_data = JSVAL_FALSE;
|
||||
break;
|
||||
case TAG_BOOLEAN_TRUE:
|
||||
m_data = JSVAL_TRUE;
|
||||
break;
|
||||
case TAG_INT:
|
||||
{
|
||||
u32 ival;
|
||||
Deserialize_int_4( buffer, ival );
|
||||
m_data = INT_TO_JSVAL( ival );
|
||||
}
|
||||
break;
|
||||
case TAG_DOUBLE:
|
||||
// Ehm. I think this works, but I can't say as it's something I've tried before.
|
||||
{
|
||||
u64 ival;
|
||||
Deserialize_int_8( buffer, ival );
|
||||
JS_NewDoubleValue( g_ScriptingHost.GetContext(), *( (double*)(&ival) ), &m_data );
|
||||
}
|
||||
break;
|
||||
case TAG_STRING:
|
||||
{
|
||||
CStrW ival;
|
||||
buffer = ival.Deserialize( buffer, end );
|
||||
m_data = ToJSVal<CStrW>( ival );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert( 0 && "An attempt was made to deserialize a jsval other than a number, boolean or string." );
|
||||
break;
|
||||
}
|
||||
return( buffer );
|
||||
}
|
||||
};
|
@ -31,6 +31,7 @@ JSFunc getGlobal;
|
||||
|
||||
JSFunc setCursor;
|
||||
|
||||
JSFunc GetGameObject;
|
||||
JSFunc startServer;
|
||||
JSFunc joinGame;
|
||||
JSFunc startGame;
|
||||
|
@ -4,13 +4,40 @@
|
||||
//
|
||||
// Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk)
|
||||
//
|
||||
// I really, really hope this is the last time I touch this code.
|
||||
// General idea:
|
||||
//
|
||||
// IJSProperty is the interface representing a property of an object.
|
||||
// Objects contain a mapping of names->IJSProperties
|
||||
// Some IJSProperties wrap C++ variables that are declared by the engine
|
||||
// Others wrap pairs of getter/setter functions
|
||||
// Most, however, wrap a jsval and are defined by scripts and XML files.
|
||||
// Objects may also have a parent object. If an attempt is made to
|
||||
// access a property that doesn't exist in this object, the parent's object
|
||||
// is checked, and so on.
|
||||
// To allow this parent system to work for C++ properties, when a C++
|
||||
// is set on an object, it's also set on any object that inherits it
|
||||
// (Unless that object specifies its own value for that property, or
|
||||
// the property is marked as being uninheritable)
|
||||
|
||||
// Objects and properties may be flagged read-only, causing all attempts to
|
||||
// set values to become no-ops. If an object is read-only, all properties are
|
||||
// - even ones that are flagged as writable.
|
||||
|
||||
// Usage: Create a class CSomething inheriting CJSObject<CSomething>
|
||||
// In CSomething's constructor, add properties to the new object with
|
||||
// AddProperty( name, pointer-to-C++-variable ).
|
||||
// Also, ScriptingInit( "Some Name" ) must be called at initialization
|
||||
// - put it in main. There's also AddMethod<ReturnType, NativeFunction>( Name,
|
||||
// MinArgs ) - call that at initialization, too.
|
||||
// If you include data members or functions that return types that JSConversions.h
|
||||
// doesn't handle sensibly, you need to make it do so.
|
||||
// If you're looking for examples, DOMEvent.h is the simplest user of this class
|
||||
// CBaseEntity and CEntity do also (and use other stuff not mentioned above)
|
||||
// but are more complex.
|
||||
|
||||
#include "scripting/ScriptingHost.h"
|
||||
#include "JSConversions.h"
|
||||
|
||||
// The Last Redesign
|
||||
|
||||
#ifndef SCRIPTABLE_INCLUDED
|
||||
#define SCRIPTABLE_INCLUDED
|
||||
|
||||
@ -31,7 +58,7 @@ public:
|
||||
|
||||
jsval Get() { return( Get( g_ScriptingHost.GetContext() ) ); }
|
||||
void Set( jsval Value ) { return( Set( g_ScriptingHost.GetContext(), Value ) ); }
|
||||
|
||||
|
||||
virtual ~IJSProperty() {}
|
||||
};
|
||||
|
||||
@ -44,6 +71,10 @@ public:
|
||||
// Used for freshen/update
|
||||
typedef void (IJSObject::*NotifyFn)();
|
||||
|
||||
// Property getters and setters
|
||||
typedef jsval (IJSObject::*GetFn)();
|
||||
typedef void (IJSObject::*SetFn)( jsval );
|
||||
|
||||
// Properties of this object
|
||||
PropertyTable m_Properties;
|
||||
|
||||
@ -55,26 +86,28 @@ public:
|
||||
|
||||
// Set the base, and rebuild
|
||||
void SetBase( IJSObject* m_Parent );
|
||||
|
||||
// Rebuild any intrinsic (mapped-to-C++-variable) properties
|
||||
virtual void Rebuild() = 0;
|
||||
|
||||
// Check for a property
|
||||
virtual IJSProperty* HasProperty( CStrW PropertyName ) = 0;
|
||||
|
||||
// Add a property (inherits value from parent)
|
||||
virtual void ReplicateProperty( CStrW PropertyName, jsval Value ) = 0;
|
||||
// Retrieve the value of a property
|
||||
virtual void GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) = 0;
|
||||
|
||||
// Add a property (with immediate value)
|
||||
virtual void AddProperty( CStrW PropertyName, jsval Value ) = 0;
|
||||
virtual void AddProperty( CStrW PropertyName, CStrW Value ) = 0;
|
||||
};
|
||||
|
||||
template<typename T, bool AllowInheritance = false, bool ReadOnly = false> class CJSObject;
|
||||
template<typename T, bool ReadOnly = false> class CJSObject;
|
||||
|
||||
template<typename T> class CJSPropertyAccessor
|
||||
{
|
||||
T* m_Owner;
|
||||
CStrW m_PropertyRoot;
|
||||
template<typename Q, bool AllowInheritance, bool ReadOnly> friend class CJSObject;
|
||||
template<typename Q, bool ReadOnly> friend class CJSObject;
|
||||
|
||||
public:
|
||||
CJSPropertyAccessor( T* Owner, CStrW PropertyRoot )
|
||||
@ -95,6 +128,7 @@ public:
|
||||
if( !Instance ) return( JS_TRUE );
|
||||
|
||||
CStrW PropName = Instance->m_PropertyRoot + CStrW( L"." ) + g_ScriptingHost.ValueToUCString( id );
|
||||
|
||||
Instance->m_Owner->GetProperty( cx, PropName, vp );
|
||||
|
||||
return( JS_TRUE );
|
||||
@ -115,7 +149,21 @@ public:
|
||||
CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj );
|
||||
if( !Instance ) return( JS_TRUE );
|
||||
|
||||
*rval = Instance->m_Owner->m_Properties[Instance->m_PropertyRoot]->Get( cx );
|
||||
// Check all along the inheritance tree
|
||||
// Possible optimization: Store the hashed value over the lookups
|
||||
IJSObject* Target = Instance->m_Owner;
|
||||
IJSProperty* Property;
|
||||
|
||||
while( Target )
|
||||
{
|
||||
Property = Target->HasProperty( Instance->m_PropertyRoot );
|
||||
if( Property )
|
||||
{
|
||||
*rval = Property->Get( cx );
|
||||
break;
|
||||
}
|
||||
Target = Target->m_Parent;
|
||||
}
|
||||
|
||||
return( JS_TRUE );
|
||||
}
|
||||
@ -124,7 +172,23 @@ public:
|
||||
CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj );
|
||||
if( !Instance ) return( JS_TRUE );
|
||||
|
||||
JSString* str = JS_ValueToString( cx, Instance->m_Owner->m_Properties[Instance->m_PropertyRoot]->Get( cx ) );
|
||||
// Check all along the inheritance tree
|
||||
// TODO: Optimization: Store the hashed value over the lookups
|
||||
IJSObject* Target = Instance->m_Owner;
|
||||
IJSProperty* Property;
|
||||
JSString* str;
|
||||
|
||||
while( Target )
|
||||
{
|
||||
Property = Target->HasProperty( Instance->m_PropertyRoot );
|
||||
if( Property )
|
||||
{
|
||||
str = JS_ValueToString( cx, Property->Get( cx ) );
|
||||
break;
|
||||
}
|
||||
Target = Target->m_Parent;
|
||||
}
|
||||
|
||||
*rval = STRING_TO_JSVAL( str );
|
||||
|
||||
return( JS_TRUE );
|
||||
@ -148,7 +212,6 @@ template<typename T> JSClass CJSPropertyAccessor<T>::JSI_Class = {
|
||||
NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
|
||||
template<typename T, bool ReadOnly> class CJSProperty : public IJSProperty
|
||||
{
|
||||
T* m_Data;
|
||||
@ -175,7 +238,7 @@ public:
|
||||
jsval Get( JSContext* cx )
|
||||
{
|
||||
if( m_Freshen ) (m_Owner->*m_Freshen)();
|
||||
return( ToJSVal<T>( *m_Data ) );
|
||||
return( ToJSVal( *m_Data ) );
|
||||
}
|
||||
void ImmediateCopy( IJSProperty* Copy )
|
||||
{
|
||||
@ -183,29 +246,46 @@ public:
|
||||
}
|
||||
void Set( JSContext* cx, jsval Value )
|
||||
{
|
||||
if( !ReadOnly ) // I think all our compilers are intelligent enough to optimize this away.
|
||||
if( !ReadOnly )
|
||||
{
|
||||
if( m_Freshen ) (m_Owner->*m_Freshen)();
|
||||
if( ToPrimitive<T>( cx, Value, *m_Data ) )
|
||||
if( ToPrimitive( cx, Value, *m_Data ) )
|
||||
if( m_Update ) (m_Owner->*m_Update)();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class CJSValProperty : public IJSProperty
|
||||
class CJSReflector
|
||||
{
|
||||
template<typename Q, bool AllowInheritance, bool ReadOnly> friend class CJSObject;
|
||||
template<typename Q, bool ReadOnly> friend class CJSObject;
|
||||
JSObject* m_JSAccessor;
|
||||
};
|
||||
|
||||
class CJSDynamicProperty : public IJSProperty
|
||||
{
|
||||
template<typename Q, bool ReadOnly> friend class CJSObject;
|
||||
|
||||
JSObject* m_JSAccessor;
|
||||
public:
|
||||
CJSDynamicProperty()
|
||||
{
|
||||
m_JSAccessor = NULL;
|
||||
m_Intrinsic = false;
|
||||
}
|
||||
};
|
||||
|
||||
class CJSValProperty : public CJSDynamicProperty
|
||||
{
|
||||
template<typename Q, bool ReadOnly> friend class CJSObject;
|
||||
|
||||
jsval m_Data;
|
||||
JSObject* m_JSAccessor;
|
||||
|
||||
public:
|
||||
CJSValProperty( jsval Data, bool Inherited )
|
||||
{
|
||||
m_Inherited = Inherited;
|
||||
m_Data = Data;
|
||||
m_Intrinsic = false;
|
||||
m_JSAccessor = NULL;
|
||||
Root();
|
||||
}
|
||||
~CJSValProperty()
|
||||
@ -215,12 +295,16 @@ public:
|
||||
void Root()
|
||||
{
|
||||
if( JSVAL_IS_GCTHING( m_Data ) )
|
||||
JS_AddRoot( g_ScriptingHost.GetContext(), &m_Data );
|
||||
#ifndef NDEBUG
|
||||
JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_Data, "jsval property" );
|
||||
#else
|
||||
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
|
||||
#endif
|
||||
}
|
||||
void Uproot()
|
||||
{
|
||||
if( JSVAL_IS_GCTHING( m_Data ) )
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), &m_Data );
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
|
||||
}
|
||||
jsval Get( JSContext* cx )
|
||||
{
|
||||
@ -240,6 +324,43 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class CJSFunctionProperty : public IJSProperty
|
||||
{
|
||||
IJSObject* m_Owner;
|
||||
|
||||
// Function on Owner to get the value
|
||||
IJSObject::GetFn m_Getter;
|
||||
|
||||
// Function on Owner to set the value
|
||||
IJSObject::SetFn m_Setter;
|
||||
|
||||
public:
|
||||
CJSFunctionProperty( IJSObject* Owner, IJSObject::GetFn Getter, IJSObject::SetFn Setter )
|
||||
{
|
||||
m_Inherited = false;
|
||||
m_Intrinsic = true;
|
||||
m_Owner = Owner;
|
||||
m_Getter = Getter;
|
||||
m_Setter = Setter;
|
||||
// Must at least be able to read
|
||||
assert( m_Owner && m_Getter );
|
||||
}
|
||||
jsval Get( JSContext* cx )
|
||||
{
|
||||
return( (m_Owner->*m_Getter)() );
|
||||
}
|
||||
void Set( JSContext* cx, jsval Value )
|
||||
{
|
||||
if( m_Setter )
|
||||
(m_Owner->*m_Setter)( Value );
|
||||
}
|
||||
void ImmediateCopy( IJSProperty* Copy )
|
||||
{
|
||||
assert( 0 && "ImmediateCopy called on a property wrapping getter/setter functions" );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Wrapper around native functions that are attached to CJSObjects
|
||||
|
||||
template<typename T, typename RType, RType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )> class CNativeFunction
|
||||
@ -257,10 +378,19 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, bool AllowInheritance, bool ReadOnly> class CJSObject : public IJSObject
|
||||
template<typename T, bool ReadOnly> class CJSObject : public IJSObject
|
||||
{
|
||||
typedef STL_HASH_MAP<CStrW, CJSReflector*, CStrW_hash_compare> ReflectorTable;
|
||||
|
||||
JSObject* m_JS;
|
||||
ReflectorTable m_Reflectors;
|
||||
|
||||
public:
|
||||
// Whether native code is responsible for managing this object.
|
||||
// Script constructors should clear this *BEFORE* creating a JS
|
||||
// mirror (otherwise it'll be rooted).
|
||||
|
||||
bool m_EngineOwned;
|
||||
|
||||
// JS Property access
|
||||
void GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp );
|
||||
@ -275,25 +405,23 @@ public:
|
||||
// Already exists
|
||||
prop->Set( cx, *vp );
|
||||
|
||||
if( AllowInheritance )
|
||||
// If it's a C++ property, reflect this change in objects that inherit this.
|
||||
if( prop->m_AllowsInheritance && prop->m_Intrinsic )
|
||||
{
|
||||
if( prop->m_AllowsInheritance )
|
||||
{
|
||||
// Run along and update this property in any inheritors
|
||||
InheritorsList UpdateSet( m_Inheritors );
|
||||
InheritorsList UpdateSet( m_Inheritors );
|
||||
|
||||
while( !UpdateSet.empty() )
|
||||
while( !UpdateSet.empty() )
|
||||
{
|
||||
IJSObject* UpdateObj = UpdateSet.back();
|
||||
UpdateSet.pop_back();
|
||||
IJSProperty* UpdateProp = UpdateObj->m_Properties[PropertyName];
|
||||
// Property must exist, also be a C++ property, and not have its value specified.
|
||||
if( UpdateProp && UpdateProp->m_Intrinsic && UpdateProp->m_Inherited )
|
||||
{
|
||||
IJSObject* UpdateObj = UpdateSet.back();
|
||||
UpdateSet.pop_back();
|
||||
IJSProperty* UpdateProp = UpdateObj->m_Properties[PropertyName];
|
||||
if( UpdateProp->m_Inherited )
|
||||
{
|
||||
UpdateProp->Set( cx, *vp );
|
||||
InheritorsList::iterator it2;
|
||||
for( it2 = UpdateObj->m_Inheritors.begin(); it2 != UpdateObj->m_Inheritors.end(); it2++ )
|
||||
UpdateSet.push_back( *it2 );
|
||||
}
|
||||
UpdateProp->Set( cx, *vp );
|
||||
InheritorsList::iterator it2;
|
||||
for( it2 = UpdateObj->m_Inheritors.begin(); it2 != UpdateObj->m_Inheritors.end(); it2++ )
|
||||
UpdateSet.push_back( *it2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,7 +439,7 @@ public:
|
||||
//
|
||||
static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
|
||||
{
|
||||
CJSObject<T, AllowInheritance, ReadOnly>* Instance = ToNative<T>( cx, obj );
|
||||
CJSObject<T, ReadOnly>* Instance = ToNative<T>( cx, obj );
|
||||
if( !Instance )
|
||||
return( JS_TRUE );
|
||||
|
||||
@ -323,7 +451,7 @@ public:
|
||||
}
|
||||
static JSBool JSSetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
|
||||
{
|
||||
CJSObject<T, AllowInheritance, ReadOnly>* Instance = ToNative<T>( cx, obj );
|
||||
CJSObject<T, ReadOnly>* Instance = ToNative<T>( cx, obj );
|
||||
if( !Instance )
|
||||
return( JS_TRUE );
|
||||
|
||||
@ -347,14 +475,17 @@ public:
|
||||
|
||||
delete[]( JSI_methods );
|
||||
}
|
||||
private:
|
||||
void CreateScriptObject()
|
||||
|
||||
static void DefaultFinalize( JSContext *cx, JSObject *obj )
|
||||
{
|
||||
assert( !m_JS );
|
||||
m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
|
||||
JS_AddRoot( g_ScriptingHost.GetContext(), m_JS );
|
||||
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, this );
|
||||
CJSObject<T, ReadOnly>* Instance = ToNative< CJSObject<T,ReadOnly> >( cx, obj );
|
||||
if( !Instance || Instance->m_EngineOwned )
|
||||
return;
|
||||
|
||||
delete( Instance );
|
||||
JS_SetPrivate( cx, obj, NULL );
|
||||
}
|
||||
|
||||
public:
|
||||
static JSClass JSI_class;
|
||||
JSObject* GetScript()
|
||||
@ -363,89 +494,132 @@ public:
|
||||
CreateScriptObject();
|
||||
return( m_JS );
|
||||
}
|
||||
// Creating and releasing script objects is done automatically most of the time, but you
|
||||
// can do it explicitly.
|
||||
void CreateScriptObject()
|
||||
{
|
||||
if( !m_JS )
|
||||
{
|
||||
m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
|
||||
if( m_EngineOwned )
|
||||
{
|
||||
#ifndef NDEBUG // Name the GC roots something more useful than 'ScriptableObject.h'
|
||||
JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_JS, JSI_class.name );
|
||||
#else
|
||||
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_JS );
|
||||
#endif
|
||||
}
|
||||
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, this );
|
||||
}
|
||||
}
|
||||
void ReleaseScriptObject()
|
||||
{
|
||||
if( m_JS )
|
||||
{
|
||||
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL );
|
||||
if( m_EngineOwned )
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), &m_JS );
|
||||
m_JS = NULL;
|
||||
}
|
||||
}
|
||||
private:
|
||||
static JSPropertySpec JSI_props[];
|
||||
static std::vector<JSFunctionSpec> m_Methods;
|
||||
static void JSFinalize( JSContext* cx, JSObject* obj );
|
||||
|
||||
public:
|
||||
CJSObject()
|
||||
{
|
||||
m_Parent = NULL;
|
||||
m_JS = NULL;
|
||||
m_EngineOwned = true;
|
||||
}
|
||||
~CJSObject()
|
||||
virtual ~CJSObject()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
void Shutdown()
|
||||
{
|
||||
PropertyTable::iterator it;
|
||||
for( it = m_Properties.begin(); it != m_Properties.end(); it++ )
|
||||
{
|
||||
if( !it->second->m_Intrinsic )
|
||||
{
|
||||
CJSValProperty* extProp = (CJSValProperty*)it->second;
|
||||
CJSDynamicProperty* extProp = (CJSValProperty*)it->second;
|
||||
if( extProp->m_JSAccessor )
|
||||
{
|
||||
CJSPropertyAccessor< CJSObject<T, AllowInheritance, ReadOnly> >* accessor = (CJSPropertyAccessor< CJSObject<T, AllowInheritance, ReadOnly> >*)JS_GetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor );
|
||||
CJSPropertyAccessor< CJSObject<T, ReadOnly> >* accessor = (CJSPropertyAccessor< CJSObject<T, ReadOnly> >*)JS_GetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor );
|
||||
assert( accessor );
|
||||
delete( accessor );
|
||||
JS_SetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor, NULL );
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), extProp->m_JSAccessor );
|
||||
}
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), &( extProp->m_JSAccessor ) );
|
||||
}
|
||||
}
|
||||
delete( it->second );
|
||||
}
|
||||
if( m_JS )
|
||||
|
||||
|
||||
ReflectorTable::iterator it_a;
|
||||
for( it_a = m_Reflectors.begin(); it_a != m_Reflectors.end(); it_a++ )
|
||||
{
|
||||
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL );
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), m_JS );
|
||||
}
|
||||
CJSPropertyAccessor< CJSObject<T, ReadOnly> >* accessor = (CJSPropertyAccessor< CJSObject<T, ReadOnly> >*)JS_GetPrivate( g_ScriptingHost.GetContext(), it_a->second->m_JSAccessor );
|
||||
assert( accessor );
|
||||
delete( accessor );
|
||||
JS_SetPrivate( g_ScriptingHost.GetContext(), it_a->second->m_JSAccessor, NULL );
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it_a->second->m_JSAccessor ) );
|
||||
delete( it_a->second );
|
||||
}
|
||||
ReleaseScriptObject();
|
||||
}
|
||||
void SetBase( IJSObject* Parent )
|
||||
{
|
||||
if( AllowInheritance )
|
||||
{
|
||||
if( m_Parent )
|
||||
{
|
||||
if( m_Parent )
|
||||
{
|
||||
// Remove this from the list of our parent's inheritors
|
||||
InheritorsList::iterator it;
|
||||
for( it = m_Parent->m_Inheritors.begin(); it != m_Parent->m_Inheritors.end(); it++ )
|
||||
if( (*it) == this )
|
||||
m_Parent->m_Inheritors.erase( it );
|
||||
// TODO: Remove any properties we were inheriting from this parent that we didn't specify ourselves
|
||||
}
|
||||
m_Parent = Parent;
|
||||
if( m_Parent )
|
||||
{
|
||||
// Place this in the list of our parent's inheritors
|
||||
m_Parent->m_Inheritors.push_back( this );
|
||||
Rebuild();
|
||||
}
|
||||
// Remove this from the list of our parent's inheritors
|
||||
InheritorsList::iterator it;
|
||||
for( it = m_Parent->m_Inheritors.begin(); it != m_Parent->m_Inheritors.end(); it++ )
|
||||
if( (*it) == this )
|
||||
{
|
||||
m_Parent->m_Inheritors.erase( it );
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_Parent = Parent;
|
||||
if( m_Parent )
|
||||
{
|
||||
// Place this in the list of our parent's inheritors
|
||||
m_Parent->m_Inheritors.push_back( this );
|
||||
Rebuild();
|
||||
}
|
||||
}
|
||||
void Rebuild()
|
||||
{
|
||||
if( AllowInheritance )
|
||||
PropertyTable::iterator it;
|
||||
// For each intrinsic property we have,
|
||||
for( it = m_Properties.begin(); it != m_Properties.end(); it++ )
|
||||
{
|
||||
PropertyTable::iterator it;
|
||||
// For each property in the parent
|
||||
for( it = m_Parent->m_Properties.begin(); it != m_Parent->m_Properties.end(); it++ )
|
||||
if( !it->second->m_Intrinsic || !it->second->m_Inherited )
|
||||
continue;
|
||||
|
||||
// Attempt to locate it in the parent
|
||||
IJSProperty* cp = m_Parent->HasProperty( it->first );
|
||||
|
||||
// If it doesn't have it, we've inherited from an object of a different type
|
||||
// This isn't allowed at the moment; but I don't have an totally convincing
|
||||
// reason for forbidding it entirely. Mind, I can't think of any use for it,
|
||||
// either.
|
||||
// If it can be inherited, inherit it.
|
||||
if( cp && cp->m_AllowsInheritance )
|
||||
{
|
||||
if( !it->second->m_AllowsInheritance )
|
||||
continue;
|
||||
PropertyTable::iterator cp;
|
||||
// Attempt to locate it in this object
|
||||
cp = m_Properties.find( it->first );
|
||||
if( cp != m_Properties.end() )
|
||||
{
|
||||
if( cp->second->m_Inherited )
|
||||
cp->second->ImmediateCopy( it->second );
|
||||
}
|
||||
else
|
||||
m_Properties[it->first] = new CJSValProperty( it->second->Get(), true );
|
||||
assert( cp->m_Intrinsic );
|
||||
it->second->ImmediateCopy( cp );
|
||||
}
|
||||
InheritorsList::iterator c;
|
||||
for( c = m_Inheritors.begin(); c != m_Inheritors.end(); c++ )
|
||||
(*c)->Rebuild();
|
||||
}
|
||||
|
||||
// Now recurse.
|
||||
InheritorsList::iterator c;
|
||||
for( c = m_Inheritors.begin(); c != m_Inheritors.end(); c++ )
|
||||
(*c)->Rebuild();
|
||||
|
||||
}
|
||||
IJSProperty* HasProperty( CStrW PropertyName )
|
||||
{
|
||||
@ -456,33 +630,22 @@ public:
|
||||
return( it->second );
|
||||
}
|
||||
|
||||
void ReplicateProperty( CStrW PropertyName, jsval Value )
|
||||
{
|
||||
if( AllowInheritance )
|
||||
{
|
||||
m_Properties[PropertyName] = new CJSValProperty( Value, true );
|
||||
// Run through our descendants to add the property to all of them that don't
|
||||
// already have it.
|
||||
InheritorsList::iterator it;
|
||||
for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ )
|
||||
if( !((*it)->HasProperty( PropertyName ) ) )
|
||||
(*it)->ReplicateProperty( PropertyName, Value );
|
||||
}
|
||||
}
|
||||
|
||||
void AddProperty( CStrW PropertyName, jsval Value )
|
||||
{
|
||||
assert( !HasProperty( PropertyName ) );
|
||||
m_Properties[PropertyName] = new CJSValProperty( Value, false );
|
||||
CJSDynamicProperty* newProp = new CJSValProperty( Value, false );
|
||||
m_Properties[PropertyName] = newProp;
|
||||
|
||||
if( AllowInheritance )
|
||||
ReflectorTable::iterator it;
|
||||
it = m_Reflectors.find( PropertyName );
|
||||
if( it != m_Reflectors.end() )
|
||||
{
|
||||
// Run through our descendants to add the property to all of them that don't
|
||||
// already have it.
|
||||
InheritorsList::iterator it;
|
||||
for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ )
|
||||
if( !((*it)->HasProperty( PropertyName ) ) )
|
||||
(*it)->ReplicateProperty( PropertyName, Value );
|
||||
// We had an accessor pointing to this property before it was defined.
|
||||
newProp->m_JSAccessor = it->second->m_JSAccessor;
|
||||
JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it->second->m_JSAccessor ) );
|
||||
JS_AddRoot( g_ScriptingHost.GetContext(), &( newProp->m_JSAccessor ) );
|
||||
delete( it->second );
|
||||
m_Reflectors.erase( it );
|
||||
}
|
||||
}
|
||||
|
||||
@ -490,58 +653,102 @@ public:
|
||||
{
|
||||
AddProperty( PropertyName, ToJSVal<CStrW>( Value ) );
|
||||
}
|
||||
void AddProperty( CStrW PropertyName, GetFn Getter, SetFn Setter = NULL )
|
||||
{
|
||||
m_Properties[PropertyName] = new CJSFunctionProperty( this, Getter, Setter );
|
||||
}
|
||||
template<typename ReturnType, ReturnType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
|
||||
static void AddMethod( const char* Name, uintN MinArgs )
|
||||
{
|
||||
JSFunctionSpec FnInfo = { Name, CNativeFunction<T, ReturnType, NativeFunction>::JSFunction, MinArgs, 0, 0 };
|
||||
T::m_Methods.push_back( FnInfo );
|
||||
}
|
||||
template<typename PropType> void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = AllowInheritance, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
|
||||
template<typename PropType> void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
|
||||
{
|
||||
m_Properties[PropertyName] = new CJSProperty<PropType, ReadOnly>( Native, this, PropAllowInheritance, Update, Refresh );
|
||||
}
|
||||
template<typename PropType> void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = AllowInheritance, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
|
||||
template<typename PropType> void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
|
||||
{
|
||||
assert( !( PropAllowInheritance && !AllowInheritance ) );
|
||||
m_Properties[PropertyName] = new CJSProperty<PropType, true>( Native, this, PropAllowInheritance, Update, Refresh );
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, bool AllowInheritance, bool ReadOnly> JSClass CJSObject<T, AllowInheritance, ReadOnly>::JSI_class = {
|
||||
template<typename T, bool ReadOnly> JSClass CJSObject<T, ReadOnly>::JSI_class = {
|
||||
NULL, JSCLASS_HAS_PRIVATE,
|
||||
JS_PropertyStub, JS_PropertyStub,
|
||||
JSGetProperty, JSSetProperty,
|
||||
JS_EnumerateStub, JS_ResolveStub,
|
||||
JS_ConvertStub, JS_FinalizeStub,
|
||||
JS_ConvertStub, DefaultFinalize,
|
||||
NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
template<typename T, bool AllowInheritance, bool ReadOnly> JSPropertySpec CJSObject<T, AllowInheritance, ReadOnly>::JSI_props[] = {
|
||||
template<typename T, bool ReadOnly> JSPropertySpec CJSObject<T, ReadOnly>::JSI_props[] = {
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
template<typename T, bool AllowInheritance, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, AllowInheritance, ReadOnly>::m_Methods;
|
||||
template<typename T, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, ReadOnly>::m_Methods;
|
||||
|
||||
template<typename T, bool AllowInheritance, bool ReadOnly> void CJSObject<T, AllowInheritance, ReadOnly>::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
|
||||
template<typename T, bool ReadOnly> void CJSObject<T, ReadOnly>::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
|
||||
{
|
||||
IJSProperty* Property = HasProperty( PropertyName );
|
||||
if( Property )
|
||||
if( Property && Property->m_Intrinsic )
|
||||
{
|
||||
if( Property->m_Intrinsic )
|
||||
*vp = Property->Get( cx );
|
||||
}
|
||||
else
|
||||
{
|
||||
CJSDynamicProperty* extProp;
|
||||
|
||||
if( Property )
|
||||
{
|
||||
*vp = Property->Get( cx );
|
||||
}
|
||||
else
|
||||
{
|
||||
CJSValProperty* extProp = (CJSValProperty*)Property;
|
||||
extProp = (CJSValProperty*)Property;
|
||||
|
||||
if( !extProp->m_JSAccessor )
|
||||
{
|
||||
extProp->m_JSAccessor = CJSPropertyAccessor< CJSObject<T, AllowInheritance> >::CreateAccessor( cx, this, PropertyName );
|
||||
JS_AddRoot( cx, extProp->m_JSAccessor );
|
||||
extProp->m_JSAccessor = CJSPropertyAccessor< CJSObject<T, ReadOnly> >::CreateAccessor( cx, this, PropertyName );
|
||||
JS_AddNamedRoot( cx, &extProp->m_JSAccessor, "property accessor" );
|
||||
}
|
||||
|
||||
*vp = OBJECT_TO_JSVAL( extProp->m_JSAccessor );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check to see if it exists on a parent
|
||||
IJSObject* check = m_Parent;
|
||||
while( check )
|
||||
{
|
||||
if( check->HasProperty( PropertyName ) ) break;
|
||||
check = check->m_Parent;
|
||||
}
|
||||
|
||||
if( !check )
|
||||
return;
|
||||
|
||||
// FIXME: Fiddle a way so this /doesn't/ require multiple kilobytes
|
||||
// of memory. Can't think of any better way to do it yet. Problem is
|
||||
// that script may access a property that isn't defined locally, but
|
||||
// is defined by an ancestor. We can't return an accessor to the
|
||||
// ancestor's property, because then if it's altered it affects that
|
||||
// object, not this. At the moment, creating a 'reflector' property
|
||||
// accessor that references /this/ object to be returned to script.
|
||||
|
||||
// (N.B. Can't just put JSObjects* in the table -> table entries can
|
||||
// move -> root no longer refers to the JSObject.)
|
||||
|
||||
ReflectorTable::iterator it;
|
||||
it = m_Reflectors.find( PropertyName );
|
||||
|
||||
if( it == m_Reflectors.end() )
|
||||
{
|
||||
CJSReflector* reflector = new CJSReflector();
|
||||
reflector->m_JSAccessor = CJSPropertyAccessor< CJSObject<T, ReadOnly> >::CreateAccessor( cx, this, PropertyName );
|
||||
JS_AddRoot( cx, &reflector->m_JSAccessor );
|
||||
m_Reflectors.insert( std::pair<CStrW, CJSReflector*>( PropertyName, reflector ) );
|
||||
*vp = OBJECT_TO_JSVAL( reflector->m_JSAccessor );
|
||||
}
|
||||
else
|
||||
*vp = OBJECT_TO_JSVAL( it->second->m_JSAccessor );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ ERROR_TYPE(Scripting_DefineType, CreationFailed);
|
||||
// Make JS debugging a little easier by automatically naming GC roots
|
||||
#ifndef NDEBUG
|
||||
// Don't simply #define NAME_ALL_GC_ROOTS, because jsapi.h is horridly broken
|
||||
# define JS_AddRoot(cx, rp) JS_AddNamedRoot((cx), (rp), __FILE__)
|
||||
# define JS_AddRoot(cx, rp) JS_AddNamedRoot((cx), (rp), __FILE__ )
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
@ -15,7 +15,11 @@ CBaseEntity::CBaseEntity()
|
||||
AddProperty( L"parent", (CBaseEntity**)&m_base, false );
|
||||
AddProperty( L"actions.move.speed", &m_speed );
|
||||
AddProperty( L"actions.move.turningradius", &m_turningRadius );
|
||||
AddProperty( L"actions.attack.range", &m_meleeRange );
|
||||
AddProperty( L"actions.attack.rangemin", &m_meleeRangeMin );
|
||||
AddProperty( L"actor", &m_actorObject );
|
||||
AddProperty( L"traits.extant", &m_extant );
|
||||
AddProperty( L"traits.corpse", &m_corpse );
|
||||
|
||||
for( int t = 0; t < EVENT_LAST; t++ )
|
||||
AddProperty( EventNames[t], &m_EventHandlers[t] );
|
||||
@ -23,6 +27,11 @@ CBaseEntity::CBaseEntity()
|
||||
m_base = NULL;
|
||||
|
||||
m_actorObject = NULL;
|
||||
|
||||
// Initialize, make life a little easier on the scriptors
|
||||
m_speed = m_turningRadius = m_meleeRange = m_meleeRangeMin = 0.0f;
|
||||
m_extant = true; m_corpse = NULL;
|
||||
|
||||
m_bound_type = CBoundingObject::BOUND_NONE;
|
||||
m_bound_circle = NULL;
|
||||
m_bound_box = NULL;
|
||||
@ -244,8 +253,8 @@ void CBaseEntity::XMLLoadProperty( const CXeromyces& XeroFile, const XMBElement&
|
||||
|
||||
void CBaseEntity::ScriptingInit()
|
||||
{
|
||||
AddMethod<jsval, &CBaseEntity::ToString>( "toString", 0 );
|
||||
CJSObject<CBaseEntity, true>::ScriptingInit( "EntityTemplate" );
|
||||
AddMethod<jsval, ToString>( "toString", 0 );
|
||||
CJSObject<CBaseEntity>::ScriptingInit( "EntityTemplate" );
|
||||
}
|
||||
|
||||
// Script-bound functions
|
||||
|
@ -27,7 +27,7 @@
|
||||
#include "ScriptObject.h"
|
||||
#include "Xeromyces.h"
|
||||
|
||||
class CBaseEntity : public CJSObject<CBaseEntity, true>
|
||||
class CBaseEntity : public CJSObject<CBaseEntity>
|
||||
{
|
||||
public:
|
||||
CBaseEntity();
|
||||
@ -40,10 +40,14 @@ public:
|
||||
// Base stats
|
||||
|
||||
CBaseEntity* m_base;
|
||||
CStrW m_corpse;
|
||||
bool m_extant;
|
||||
|
||||
CStrW m_Base_Name; // <- We don't guarantee the order XML files will be loaded in, so we'll store the name of the
|
||||
// parent entity referenced, then, after all files are loaded, attempt to match names to objects.
|
||||
|
||||
CObjectEntry* m_actorObject;
|
||||
|
||||
|
||||
CStrW m_Tag;
|
||||
CBoundingCircle* m_bound_circle;
|
||||
@ -51,6 +55,9 @@ public:
|
||||
CBoundingObject::EBoundingType m_bound_type;
|
||||
|
||||
float m_speed;
|
||||
float m_meleeRange;
|
||||
float m_meleeRangeMin;
|
||||
|
||||
float m_turningRadius;
|
||||
CScriptObject m_EventHandlers[EVENT_LAST];
|
||||
|
||||
|
@ -48,22 +48,46 @@ void CBaseEntityCollection::loadTemplates()
|
||||
|
||||
// Fix up parent links in the templates.
|
||||
|
||||
std::vector<CBaseEntity*>::iterator it;
|
||||
for( it = m_templates.begin(); it != m_templates.end(); it++ )
|
||||
std::vector<CBaseEntity*>::iterator it, it_done;
|
||||
std::vector<CBaseEntity*> done;
|
||||
|
||||
// TODO: MT: Circular references check.
|
||||
|
||||
while( done.size() < m_templates.size() )
|
||||
{
|
||||
if( !( (*it)->m_Base_Name.Length() ) )
|
||||
continue;
|
||||
|
||||
CBaseEntity* Base = getTemplate( (*it)->m_Base_Name );
|
||||
if( Base )
|
||||
for( it = m_templates.begin(); it != m_templates.end(); it++ )
|
||||
{
|
||||
(*it)->m_base = Base;
|
||||
(*it)->loadBase();
|
||||
}
|
||||
else
|
||||
LOG( WARNING, LOG_CATEGORY, "Parent template %s does not exist in template %s", CStr8( (*it)->m_Base_Name ).c_str(), CStr8( (*it)->m_Tag ).c_str() );
|
||||
}
|
||||
if( !( (*it)->m_Base_Name.Length() ) )
|
||||
{
|
||||
done.push_back( *it );
|
||||
continue;
|
||||
}
|
||||
|
||||
CBaseEntity* Base = getTemplate( (*it)->m_Base_Name );
|
||||
if( Base )
|
||||
{
|
||||
// Check whether it's been loaded yet.
|
||||
for( it_done = done.begin(); it_done != done.end(); it_done++ )
|
||||
{
|
||||
if( *it_done == Base )
|
||||
{
|
||||
(*it)->m_base = Base;
|
||||
(*it)->loadBase();
|
||||
Base = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !Base )
|
||||
{
|
||||
// Done
|
||||
done.push_back( *it );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG( WARNING, LOG_CATEGORY, "Parent template %s does not exist in template %s", CStr8( (*it)->m_Base_Name ).c_str(), CStr8( (*it)->m_Tag ).c_str() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CBaseEntityCollection::LoadDirectory( Handle directory, CStr pathname )
|
||||
|
@ -94,7 +94,9 @@ bool getRayIntersection( const CVector2D& source, const CVector2D& forward, cons
|
||||
assert( (*it)->m_bounds );
|
||||
if( (*it)->m_bounds == destinationCollisionObject ) continue;
|
||||
|
||||
if( (*it)->m_moving ) continue;
|
||||
// TODO MT: Replace this with something based on whether the unit is actually moving.
|
||||
if( (*it)->m_orderQueue.size() ) continue;
|
||||
|
||||
CBoundingObject* obj = (*it)->m_bounds;
|
||||
delta = obj->m_pos - source;
|
||||
closestApproach = delta.dot( right );
|
||||
|
@ -29,11 +29,13 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation )
|
||||
AddProperty( L"actions.move.speed", &m_speed );
|
||||
AddProperty( L"selected", &m_selected, false, (NotifyFn)&CEntity::checkSelection );
|
||||
AddProperty( L"group", &m_grouped, false, (NotifyFn)&CEntity::checkGroup );
|
||||
AddProperty( L"extant", &m_extant, false, (NotifyFn)&CEntity::checkExtant );
|
||||
AddProperty( L"traits.extant", &m_extant );
|
||||
AddProperty( L"traits.corpse", &m_corpse );
|
||||
AddProperty( L"actions.move.turningradius", &m_turningRadius );
|
||||
AddProperty( L"actions.attack.range", &m_meleeRange );
|
||||
AddProperty( L"actions.attack.rangemin", &m_meleeRangeMin );
|
||||
AddProperty( L"position", &m_graphics_position, false, (NotifyFn)&CEntity::teleport );
|
||||
AddProperty( L"orientation", &m_graphics_orientation, false, (NotifyFn)&CEntity::reorient );
|
||||
|
||||
|
||||
for( int t = 0; t < EVENT_LAST; t++ )
|
||||
AddProperty( EventNames[t], &m_EventHandlers[t] );
|
||||
@ -42,7 +44,8 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation )
|
||||
m_actor = NULL;
|
||||
m_bounds = NULL;
|
||||
|
||||
m_moving = false;
|
||||
m_lastState = -1;
|
||||
m_transition = true;
|
||||
|
||||
m_base = base;
|
||||
|
||||
@ -57,8 +60,7 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation )
|
||||
m_graphics_position = m_position;
|
||||
m_graphics_orientation = m_orientation;
|
||||
|
||||
m_extant = true;
|
||||
m_extant_mirror = true;
|
||||
m_destroyed = false;
|
||||
|
||||
m_selected = false;
|
||||
|
||||
@ -114,9 +116,9 @@ void CEntity::kill()
|
||||
if( m_bounds ) delete( m_bounds );
|
||||
m_bounds = NULL;
|
||||
|
||||
m_extant = false;
|
||||
m_extant_mirror = false;
|
||||
|
||||
m_destroyed = true;
|
||||
Shutdown();
|
||||
|
||||
if( m_actor )
|
||||
{
|
||||
g_UnitMan.RemoveUnit( m_actor );
|
||||
@ -154,10 +156,17 @@ void CEntity::update( size_t timestep )
|
||||
m_position_previous = m_position;
|
||||
m_orientation_previous = m_orientation;
|
||||
|
||||
// The process[...] functions return 'true' if the order at the top of the stack
|
||||
// still needs to be (re-)evaluated; else 'false' to terminate the processing of
|
||||
// this entity in this timestep.
|
||||
|
||||
while( !m_orderQueue.empty() )
|
||||
{
|
||||
CEntityOrder* current = &m_orderQueue.front();
|
||||
|
||||
m_transition = ( current->m_type != m_lastState );
|
||||
m_lastState = current->m_type;
|
||||
|
||||
switch( current->m_type )
|
||||
{
|
||||
case CEntityOrder::ORDER_GOTO_NOPATHING:
|
||||
@ -165,24 +174,55 @@ void CEntity::update( size_t timestep )
|
||||
case CEntityOrder::ORDER_GOTO_SMOOTHED:
|
||||
if( processGotoNoPathing( current, timestep ) ) break;
|
||||
return;
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE:
|
||||
if( processAttackMeleeNoPathing( current, timestep ) ) break;
|
||||
return;
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING:
|
||||
if( processAttackMeleeNoPathing( current, timestep ) ) break;
|
||||
return;
|
||||
case CEntityOrder::ORDER_GOTO:
|
||||
if( processGoto( current, timestep ) ) break;
|
||||
return;
|
||||
case CEntityOrder::ORDER_PATROL:
|
||||
if( processPatrol( current, timestep ) ) break;
|
||||
return;
|
||||
case CEntityOrder::ORDER_PATH_END_MARKER:
|
||||
m_orderQueue.pop_front();
|
||||
break;
|
||||
default:
|
||||
assert( 0 && "Invalid entity order" );
|
||||
}
|
||||
}
|
||||
|
||||
if( m_moving )
|
||||
if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() )
|
||||
{
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_IdleAnim );
|
||||
m_moving = false;
|
||||
if( m_extant )
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_IdleAnim );
|
||||
else
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_CorpseAnim );
|
||||
m_lastState = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void CEntity::Initialize()
|
||||
{
|
||||
CEventInitialize evt;
|
||||
DispatchEvent( &evt );
|
||||
}
|
||||
|
||||
void CEntity::Tick()
|
||||
{
|
||||
CEventTick evt;
|
||||
DispatchEvent( &evt );
|
||||
}
|
||||
|
||||
void CEntity::Damage( CDamageType& damage, CEntity* inflictor )
|
||||
{
|
||||
CEventDamage evt( inflictor, &damage );
|
||||
DispatchEvent( &evt );
|
||||
}
|
||||
|
||||
/*
|
||||
void CEntity::dispatch( const CMessage* msg )
|
||||
{
|
||||
|
||||
@ -197,7 +237,9 @@ void CEntity::dispatch( const CMessage* msg )
|
||||
case CMessage::EMSG_INIT:
|
||||
{
|
||||
CEventInitialize Init;
|
||||
DispatchEvent( &Init );
|
||||
if( !DispatchEvent( &Init ) )
|
||||
break;
|
||||
|
||||
if( m_base->m_Tag == CStrW( L"Prometheus Dude" ) )
|
||||
{
|
||||
if( getCollisionObject( this ) )
|
||||
@ -206,23 +248,6 @@ void CEntity::dispatch( const CMessage* msg )
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
/*
|
||||
std::vector<HEntity>* waypoints = g_EntityManager.matches( isWaypoint );
|
||||
while( !waypoints->empty() )
|
||||
{
|
||||
CEntityOrder patrol;
|
||||
size_t id = rand() % waypoints->size();
|
||||
std::vector<HEntity>::iterator it = waypoints->begin();
|
||||
it += id;
|
||||
HEntity waypoint = *it;
|
||||
patrol.m_type = CEntityOrder::ORDER_PATROL;
|
||||
patrol.m_data[0].location.x = waypoint->m_position.X;
|
||||
patrol.m_data[0].location.y = waypoint->m_position.Z;
|
||||
pushOrder( patrol );
|
||||
waypoints->erase( it );
|
||||
}
|
||||
delete( waypoints );
|
||||
*/
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -233,13 +258,15 @@ void CEntity::dispatch( const CMessage* msg )
|
||||
clearOrders();
|
||||
pushOrder( m->order );
|
||||
break;
|
||||
case CMessage::EMSG_DAMAGE:
|
||||
CEntityOrder* o;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
bool CEntity::DispatchEvent( CScriptEvent* evt )
|
||||
{
|
||||
m_EventHandlers[evt->m_TypeCode].DispatchEvent( GetScript(), evt );
|
||||
return( false );
|
||||
return( m_EventHandlers[evt->m_TypeCode].DispatchEvent( GetScript(), evt ) );
|
||||
}
|
||||
|
||||
void CEntity::clearOrders()
|
||||
@ -252,16 +279,30 @@ void CEntity::pushOrder( CEntityOrder& order )
|
||||
m_orderQueue.push_back( order );
|
||||
}
|
||||
|
||||
int CEntity::defaultOrder( CEntity* orderTarget )
|
||||
{
|
||||
CEventTargetChanged evt( orderTarget );
|
||||
DispatchEvent( &evt );
|
||||
return( evt.m_defaultAction );
|
||||
}
|
||||
|
||||
bool CEntity::acceptsOrder( int orderType, CEntity* orderTarget )
|
||||
{
|
||||
CEventPrepareOrder evt( orderTarget, orderType );
|
||||
return( DispatchEvent( &evt ) );
|
||||
|
||||
/*
|
||||
// Hardcoding...
|
||||
switch( orderType )
|
||||
{
|
||||
case CEntityOrder::ORDER_GOTO:
|
||||
case CEntityOrder::ORDER_PATROL:
|
||||
return( m_speed > 0.0f );
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE:
|
||||
return( orderTarget && ( m_meleeRange > 0.0f ) );
|
||||
}
|
||||
return( false );
|
||||
*/
|
||||
}
|
||||
|
||||
void CEntity::repath()
|
||||
@ -318,13 +359,6 @@ void CEntity::checkGroup()
|
||||
g_Selection.changeGroup( me, m_grouped );
|
||||
}
|
||||
|
||||
void CEntity::checkExtant()
|
||||
{
|
||||
if( m_extant && !( (bool)m_extant_mirror ) )
|
||||
kill();
|
||||
// Sorry. Dead stuff stays dead.
|
||||
}
|
||||
|
||||
void CEntity::interpolate( float relativeoffset )
|
||||
{
|
||||
m_graphics_position = Interpolate<CVector3D>( m_position_previous, m_position, relativeoffset );
|
||||
@ -521,10 +555,13 @@ void CEntity::renderSelectionOutline( float alpha )
|
||||
|
||||
void CEntity::ScriptingInit()
|
||||
{
|
||||
AddMethod<jsval, &CEntity::ToString>( "toString", 0 );
|
||||
AddMethod<bool, &CEntity::OrderSingle>( "order", 1 );
|
||||
AddMethod<bool, &CEntity::OrderQueued>( "orderQueued", 1 );
|
||||
CJSObject<CEntity, true>::ScriptingInit( "Entity", Construct, 2 );
|
||||
AddMethod<jsval, ToString>( "toString", 0 );
|
||||
AddMethod<bool, OrderSingle>( "order", 1 );
|
||||
AddMethod<bool, OrderQueued>( "orderQueued", 1 );
|
||||
AddMethod<bool, Kill>( "kill", 0 );
|
||||
AddMethod<bool, Damage>( "damage", 1 );
|
||||
AddMethod<bool, IsIdle>( "isIdle", 0 );
|
||||
CJSObject<CEntity>::ScriptingInit( "Entity", Construct, 2 );
|
||||
}
|
||||
|
||||
// Script constructor
|
||||
@ -582,8 +619,7 @@ JSBool CEntity::Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsva
|
||||
|
||||
HEntity handle = g_EntityManager.create( baseEntity, position, orientation );
|
||||
|
||||
CMessage message( CMessage::EMSG_INIT );
|
||||
handle->dispatch( &message );
|
||||
handle->Initialize();
|
||||
|
||||
*rval = ToJSVal<CEntity>( *handle );
|
||||
return( JS_TRUE );
|
||||
@ -640,11 +676,87 @@ bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued )
|
||||
JS_ReportError( cx, "Invalid location" );
|
||||
return( false );
|
||||
}
|
||||
g_Scheduler.pushFrame( ORDER_DELAY, me, new CMessageOrder( newOrder, Queued ) );
|
||||
return( true );
|
||||
break;
|
||||
case CEntityOrder::ORDER_ATTACK_MELEE:
|
||||
if( argc < 1 )
|
||||
{
|
||||
JS_ReportError( cx, "Too few parameters" );
|
||||
return( false );
|
||||
}
|
||||
CEntity* target;
|
||||
target = ToNative<CEntity>( argv[1] );
|
||||
if( !target )
|
||||
{
|
||||
JS_ReportError( cx, "Invalid target" );
|
||||
return( false );
|
||||
}
|
||||
newOrder.m_data[0].entity = target->me;
|
||||
break;
|
||||
default:
|
||||
JS_ReportError( cx, "Invalid order type" );
|
||||
return( false );
|
||||
}
|
||||
|
||||
if( !Queued )
|
||||
clearOrders();
|
||||
pushOrder( newOrder );
|
||||
|
||||
return( true );
|
||||
}
|
||||
|
||||
bool CEntity::Damage( JSContext* cx, uintN argc, jsval* argv )
|
||||
{
|
||||
CEntity* inflictor = NULL;
|
||||
|
||||
if( argc >= 4 )
|
||||
inflictor = ToNative<CEntity>( argv[3] );
|
||||
|
||||
if( argc >= 3 )
|
||||
{
|
||||
Damage( CDamageType( ToPrimitive<float>( argv[0] ), ToPrimitive<float>( argv[1] ), ToPrimitive<float>( argv[2] ) ), inflictor );
|
||||
return( true );
|
||||
}
|
||||
|
||||
if( argc >= 2 )
|
||||
inflictor = ToNative<CEntity>( argv[1] );
|
||||
|
||||
// If it's a DamageType, use that. Otherwise, see if it's a float, if so, use
|
||||
// that as the 'typeless' unblockable damage type.
|
||||
|
||||
CDamageType* dmg = ToNative<CDamageType>( argv[0] );
|
||||
|
||||
if( !dmg )
|
||||
{
|
||||
float dmgN;
|
||||
if( !ToPrimitive<float>( cx, argv[0], dmgN ) )
|
||||
return( false );
|
||||
|
||||
Damage( CDamageType( dmgN ), inflictor );
|
||||
return( true );
|
||||
}
|
||||
|
||||
Damage( *dmg, inflictor );
|
||||
return( true );
|
||||
}
|
||||
|
||||
bool CEntity::Kill( JSContext* cx, uintN argc, jsval* argv )
|
||||
{
|
||||
// Change this entity's template to the corpse entity - but note
|
||||
// we don't fiddle with the actors or bounding information that we
|
||||
// usually do when changing templates.
|
||||
|
||||
CBaseEntity* corpse = g_EntityTemplateCollection.getTemplate( m_corpse );
|
||||
if( corpse )
|
||||
{
|
||||
m_base = corpse;
|
||||
SetBase( m_base );
|
||||
}
|
||||
|
||||
g_Selection.removeAll( me );
|
||||
|
||||
clearOrders();
|
||||
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_DeathAnim, true );
|
||||
|
||||
return( true );
|
||||
}
|
||||
|
@ -45,22 +45,33 @@
|
||||
#include "EntityMessage.h"
|
||||
#include "EventHandlers.h"
|
||||
|
||||
class CEntityManager;
|
||||
#include "EntitySupport.h"
|
||||
|
||||
class CEntity : public CJSObject<CEntity, true>
|
||||
// TODO MT: Put this is /some/ sort of order...
|
||||
|
||||
class CEntity : public CJSObject<CEntity>
|
||||
{
|
||||
friend class CEntityManager;
|
||||
public:
|
||||
// Intrinsic properties
|
||||
CBaseEntity* m_base;
|
||||
|
||||
// The entity to switch to when this dies.
|
||||
CStrW m_corpse;
|
||||
|
||||
float m_speed;
|
||||
float m_turningRadius;
|
||||
float m_meleeRange;
|
||||
float m_meleeRangeMin;
|
||||
bool m_selected;
|
||||
i32 m_grouped;
|
||||
|
||||
bool m_extant; // Don't want JS to have direct write-access to these. (Things that should be done might not be)
|
||||
bool m_extant_mirror; // plus this way limits the number of nasty semantics to work around.
|
||||
// If this unit has been removed from the gameworld but has still
|
||||
// has references.
|
||||
bool m_destroyed;
|
||||
|
||||
// If this unit is still active in the gameworld - i.e. not a corpse.
|
||||
bool m_extant;
|
||||
|
||||
//-- Interpolated property
|
||||
CVector3D m_position;
|
||||
@ -95,13 +106,19 @@ public:
|
||||
bool DispatchEvent( CScriptEvent* evt );
|
||||
|
||||
CUnit* m_actor;
|
||||
bool m_moving;
|
||||
|
||||
// State transition in the FSM (animations should be reset)
|
||||
bool m_transition;
|
||||
int m_lastState;
|
||||
|
||||
std::deque<CEntityOrder> m_orderQueue;
|
||||
|
||||
private:
|
||||
CEntity( CBaseEntity* base, CVector3D position, float orientation );
|
||||
|
||||
EGotoSituation processGotoHelper( CEntityOrder* current, size_t timestep_milli, HEntity& collide );
|
||||
bool processAttackMelee( CEntityOrder* current, size_t timestep_milli );
|
||||
bool processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_milli );
|
||||
bool processGotoNoPathing( CEntityOrder* current, size_t timestep_milli );
|
||||
bool processGoto( CEntityOrder* current, size_t timestep_milli );
|
||||
bool processPatrol( CEntityOrder* current, size_t timestep_milli );
|
||||
@ -112,9 +129,6 @@ public:
|
||||
// Handle-to-self.
|
||||
HEntity me;
|
||||
|
||||
// Process an event
|
||||
void dispatch( const CMessage* msg );
|
||||
|
||||
// Updates gameplay information for the specified timestep
|
||||
void update( size_t timestep_millis );
|
||||
// Updates graphical information for a point between the last and current simulation frame; 0 < relativeoffset < 1.
|
||||
@ -123,6 +137,15 @@ public:
|
||||
// Removes entity from the gameworld and deallocates it, but not neccessarily immediately.
|
||||
void kill();
|
||||
|
||||
// Process initialization
|
||||
void Initialize();
|
||||
|
||||
// Process tick.
|
||||
void Tick();
|
||||
|
||||
// Process damage
|
||||
void Damage( CDamageType& damage, CEntity* inflictor = NULL );
|
||||
|
||||
void snapToGround();
|
||||
void updateActorTransforms();
|
||||
|
||||
@ -138,10 +161,12 @@ public:
|
||||
|
||||
void reorient(); // Orientation
|
||||
void teleport(); // Fixes things if the position is changed by something externally.
|
||||
void checkSelection(); // In case anyone tries to select/deselect this through JavaScript. You'd think they'd have something better to do.
|
||||
void checkSelection(); // In case anyone tries to select/deselect this through JavaScript.
|
||||
void checkGroup(); // Groups
|
||||
void checkExtant(); // Existance
|
||||
|
||||
// Returns the default action of the entity upon the target (or -1 if none apply)
|
||||
int CEntity::defaultOrder( CEntity* orderTarget );
|
||||
// Returns whether the entity is capable of performing the given orderType on the target.
|
||||
bool acceptsOrder( int orderType, CEntity* orderTarget );
|
||||
|
||||
@ -164,7 +189,12 @@ public:
|
||||
{
|
||||
return( Order( cx, argc, argv, true ) );
|
||||
}
|
||||
|
||||
bool Damage( JSContext* cx, uintN argc, jsval* argv );
|
||||
bool Kill( JSContext* cx, uintN argc, jsval* argv );
|
||||
bool IsIdle( JSContext* cx, uintN argc, jsval* argv )
|
||||
{
|
||||
return( m_orderQueue.empty() );
|
||||
}
|
||||
static void ScriptingInit();
|
||||
};
|
||||
|
||||
@ -178,5 +208,4 @@ extern int SELECTION_CIRCLE_POINTS;
|
||||
extern int SELECTION_BOX_POINTS;
|
||||
extern int SELECTION_SMOOTHNESS_UNIFIED;
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -45,6 +45,24 @@ bool HEntity::operator ==( const HEntity& test ) const
|
||||
return( m_handle == test.m_handle );
|
||||
}
|
||||
|
||||
HEntity::operator bool() const
|
||||
{
|
||||
if( m_handle == INVALID_HANDLE )
|
||||
return( false );
|
||||
|
||||
assert( g_EntityManager.m_entities[m_handle].m_refcount );
|
||||
return( !g_EntityManager.m_entities[m_handle].m_entity->m_destroyed );
|
||||
}
|
||||
|
||||
bool HEntity::operator!() const
|
||||
{
|
||||
if( m_handle == INVALID_HANDLE )
|
||||
return( true );
|
||||
|
||||
assert( g_EntityManager.m_entities[m_handle].m_refcount );
|
||||
return( g_EntityManager.m_entities[m_handle].m_entity->m_destroyed );
|
||||
}
|
||||
|
||||
void HEntity::addRef()
|
||||
{
|
||||
if( m_handle != INVALID_HANDLE )
|
||||
|
@ -53,7 +53,8 @@ public:
|
||||
void operator=( const HEntity& copy );
|
||||
bool operator==( const HEntity& test ) const;
|
||||
bool operator!=( const HEntity& test ) const { return( !operator==( test ) ); }
|
||||
operator bool() const { return( m_handle != INVALID_HANDLE ); }
|
||||
operator bool() const;
|
||||
bool operator!() const;
|
||||
operator CEntity*() const;
|
||||
~HEntity();
|
||||
|
||||
|
@ -54,7 +54,7 @@ std::vector<HEntity>* CEntityManager::matches( EntityPredicate predicate )
|
||||
{
|
||||
std::vector<HEntity>* matchlist = new std::vector<HEntity>;
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
if( predicate( m_entities[i].m_entity ) )
|
||||
matchlist->push_back( HEntity( i ) );
|
||||
return( matchlist );
|
||||
@ -64,7 +64,7 @@ std::vector<HEntity>* CEntityManager::matches( EntityPredicate predicate1, Entit
|
||||
{
|
||||
std::vector<HEntity>* matchlist = new std::vector<HEntity>;
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
if( predicate1( m_entities[i].m_entity ) && predicate2( m_entities[i].m_entity ) )
|
||||
matchlist->push_back( HEntity( i ) );
|
||||
return( matchlist );
|
||||
@ -74,17 +74,33 @@ std::vector<HEntity>* CEntityManager::getExtant()
|
||||
{
|
||||
std::vector<HEntity>* activelist = new std::vector<HEntity>;
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
activelist->push_back( HEntity( i ) );
|
||||
return( activelist );
|
||||
}
|
||||
|
||||
/*
|
||||
void CEntityManager::dispatchAll( CMessage* msg )
|
||||
{
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
m_entities[i].m_entity->dispatch( msg );
|
||||
}
|
||||
*/
|
||||
|
||||
void CEntityManager::InitializeAll()
|
||||
{
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
m_entities[i].m_entity->Initialize();
|
||||
}
|
||||
|
||||
void CEntityManager::TickAll()
|
||||
{
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
m_entities[i].m_entity->Tick();
|
||||
}
|
||||
|
||||
void CEntityManager::updateAll( size_t timestep )
|
||||
{
|
||||
@ -93,25 +109,24 @@ void CEntityManager::updateAll( size_t timestep )
|
||||
delete( *it );
|
||||
m_reaper.clear();
|
||||
|
||||
CMessage Tick_msg( CMessage::EMSG_TICK );
|
||||
dispatchAll( &Tick_msg );
|
||||
TickAll();
|
||||
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
m_entities[i].m_entity->update( timestep );
|
||||
}
|
||||
|
||||
void CEntityManager::interpolateAll( float relativeoffset )
|
||||
{
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
m_entities[i].m_entity->interpolate( relativeoffset );
|
||||
}
|
||||
|
||||
void CEntityManager::renderAll()
|
||||
{
|
||||
for( int i = 0; i < MAX_HANDLES; i++ )
|
||||
if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant )
|
||||
if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed )
|
||||
m_entities[i].m_entity->render();
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,8 @@ public:
|
||||
HEntity* getByHandle( u16 index );
|
||||
void updateAll( size_t timestep );
|
||||
void interpolateAll( float relativeoffset );
|
||||
void dispatchAll( CMessage* msg );
|
||||
void InitializeAll();
|
||||
void TickAll();
|
||||
void renderAll();
|
||||
std::vector<HEntity>* matches( EntityPredicate predicate );
|
||||
std::vector<HEntity>* matches( EntityPredicate predicate1, EntityPredicate predicate2 );
|
||||
|
@ -4,8 +4,7 @@
|
||||
//
|
||||
// Entity message structure.
|
||||
//
|
||||
// Usage: Currently, does not support any data to be included with messages.
|
||||
// Message types are currently: EMSG_TICK: Unused.
|
||||
// Usage: Message types are currently: EMSG_TICK: Sent once per sim frame.
|
||||
// EMSG_INIT: When a new entity is instantiated.
|
||||
// At map loading, do not issue this message immediately
|
||||
// for each entity as it is loaded; instead, wait for all
|
||||
@ -13,10 +12,12 @@
|
||||
// of them simultaneously.
|
||||
// EMSG_ORDER:To push a message into the entity's order queue
|
||||
|
||||
/*
|
||||
#ifndef MESSAGING_INCLUDED
|
||||
#define MESSAGING_INCLUDED
|
||||
|
||||
#include "EntityOrders.h"
|
||||
#include "EntitySupport.h"
|
||||
|
||||
struct CMessage
|
||||
{
|
||||
@ -25,6 +26,7 @@ struct CMessage
|
||||
EMSG_TICK,
|
||||
EMSG_INIT,
|
||||
EMSG_ORDER,
|
||||
EMSG_DAMAGE
|
||||
} type;
|
||||
CMessage( EMessageType _type )
|
||||
{
|
||||
@ -39,4 +41,12 @@ struct CMessageOrder : public CMessage
|
||||
bool queue;
|
||||
};
|
||||
|
||||
struct CMessageDamage : public CMessage
|
||||
{
|
||||
CMessageDamage( HEntity _inflictor, CDamageType _damage ) : CMessage( EMSG_DAMAGE ), inflictor( inflictor ), damage( damage ) {}
|
||||
HEntity inflictor;
|
||||
CDamageType damage;
|
||||
}
|
||||
|
||||
#endif
|
||||
*/
|
@ -23,6 +23,10 @@
|
||||
// order queue after it's executed. In this way, the entity will
|
||||
// circle round a list of patrol points.
|
||||
// Create this order when a standard patrol order is required.
|
||||
// ORDER_ATTACK_MELEE: Move towards target entity; start bashing it when close enough.
|
||||
// If we collide with something (=> line-of-sight tracking no longer
|
||||
// sufficient) spawns a ORDER_GOTO to target's location and pushes it
|
||||
// immediately in front of this order.
|
||||
//
|
||||
// Entities which exhaust all orders from their queue go to idle status; there is no specific order
|
||||
// type for this status.
|
||||
@ -52,6 +56,9 @@ public:
|
||||
ORDER_GOTO_COLLISION,
|
||||
ORDER_GOTO,
|
||||
ORDER_PATROL,
|
||||
ORDER_ATTACK_MELEE,
|
||||
ORDER_ATTACK_MELEE_NOPATHING,
|
||||
ORDER_PATH_END_MARKER,
|
||||
ORDER_LAST
|
||||
} m_type;
|
||||
SOrderData m_data[ORDER_MAX_DATA];
|
||||
|
@ -11,7 +11,21 @@
|
||||
|
||||
#include "Game.h"
|
||||
|
||||
bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_millis )
|
||||
enum EGotoSituation
|
||||
{
|
||||
NORMAL = 0,
|
||||
ALREADY_AT_DESTINATION,
|
||||
REACHED_DESTINATION,
|
||||
COLLISION_WITH_DESTINATION,
|
||||
COLLISION_NEAR_DESTINATION,
|
||||
COLLISION_OVERLAPPING_OBJECTS,
|
||||
COLLISION_OTHER,
|
||||
WOULD_LEAVE_MAP
|
||||
};
|
||||
|
||||
// Does all the shared processing for line-of-sight gotos
|
||||
|
||||
EGotoSituation CEntity::processGotoHelper( CEntityOrder* current, size_t timestep_millis, HEntity& collide )
|
||||
{
|
||||
float timestep=timestep_millis/1000.0f;
|
||||
|
||||
@ -22,15 +36,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
float len = delta.length();
|
||||
|
||||
if( len < 0.1f )
|
||||
{
|
||||
if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION )
|
||||
{
|
||||
repath();
|
||||
}
|
||||
else
|
||||
m_orderQueue.pop_front();
|
||||
return( false );
|
||||
}
|
||||
return( ALREADY_AT_DESTINATION );
|
||||
|
||||
// Curve smoothing.
|
||||
// Here there be trig.
|
||||
@ -74,8 +80,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
// place until we're looking the right way. At least, that's what
|
||||
// seems logical. But in most cases that looks worse. So actually,
|
||||
// let's not.
|
||||
|
||||
|
||||
|
||||
if( current->m_type != CEntityOrder::ORDER_GOTO_SMOOTHED )
|
||||
m_orientation = m_targetorientation;
|
||||
|
||||
@ -86,13 +91,16 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
m_orientation = m_targetorientation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if( m_bounds->m_type == CBoundingObject::BOUND_OABB )
|
||||
((CBoundingBox*)m_bounds)->setOrientation( m_ahead );
|
||||
|
||||
EGotoSituation rc = NORMAL;
|
||||
|
||||
if( scale > len )
|
||||
{
|
||||
scale = len;
|
||||
rc = REACHED_DESTINATION;
|
||||
}
|
||||
|
||||
delta = m_ahead * scale;
|
||||
|
||||
@ -103,7 +111,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
|
||||
m_bounds->setPosition( m_position.X, m_position.Z );
|
||||
|
||||
HEntity collide = getCollisionObject( this );
|
||||
collide = getCollisionObject( this );
|
||||
|
||||
if( collide )
|
||||
{
|
||||
@ -123,17 +131,13 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
|
||||
// Erm... do nothing?
|
||||
|
||||
return( false );
|
||||
return( COLLISION_OVERLAPPING_OBJECTS );
|
||||
}
|
||||
|
||||
// No. Is our destination within the obstacle?
|
||||
if( collide->m_bounds->contains( current->m_data[0].location ) )
|
||||
{
|
||||
// Yes? All well and good, then. Stop here.
|
||||
m_orderQueue.pop_front();
|
||||
return( false );
|
||||
}
|
||||
|
||||
return( COLLISION_WITH_DESTINATION );
|
||||
|
||||
// No. Are we nearing our destination, do we wish to stop there, and is it obstructed?
|
||||
|
||||
if( ( m_orderQueue.size() == 1 ) && ( len <= 10.0f ) )
|
||||
@ -142,30 +146,84 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
if( getCollisionObject( &destinationObs ) )
|
||||
{
|
||||
// Yes. (Chances are a bunch of units were tasked to the same destination)
|
||||
// Here's a wierd idea: (I hope it works)
|
||||
// Spiral round the destination until a free point is found.
|
||||
float interval = destinationObs.m_radius;
|
||||
float r = interval, theta = 0.0f, delta;
|
||||
float _x = current->m_data[0].location.x, _y = current->m_data[0].location.y;
|
||||
|
||||
while( true )
|
||||
{
|
||||
delta = interval / r;
|
||||
theta += delta;
|
||||
r += ( interval * delta ) / ( 2 * PI );
|
||||
destinationObs.setPosition( _x + r * cosf( theta ), _y + r * sinf( theta ) );
|
||||
if( !getCollisionObject( &destinationObs ) ) break;
|
||||
}
|
||||
|
||||
// Reset our destination
|
||||
current->m_data[0].location.x = _x + r * cosf( theta );
|
||||
current->m_data[0].location.y = _y + r * sinf( theta );
|
||||
|
||||
return( false );
|
||||
return( COLLISION_NEAR_DESTINATION );
|
||||
}
|
||||
}
|
||||
|
||||
// No? Path around it.
|
||||
// No?
|
||||
return( COLLISION_OTHER );
|
||||
|
||||
}
|
||||
|
||||
// Will we step off the map?
|
||||
if( !g_Game->GetWorld()->GetTerrain()->isOnMap( m_position.X, m_position.Z ) )
|
||||
{
|
||||
// Yes. That's not a particularly good idea, either.
|
||||
|
||||
m_position.X -= delta.x;
|
||||
m_position.Z -= delta.y;
|
||||
m_bounds->setPosition( m_position.X, m_position.Z );
|
||||
|
||||
// All things being equal, we should only get here while on a collision path
|
||||
// (No destination should be off the map)
|
||||
|
||||
return( WOULD_LEAVE_MAP );
|
||||
}
|
||||
|
||||
// No. I suppose it's OK to go there, then. *disappointed*
|
||||
|
||||
return( rc );
|
||||
}
|
||||
|
||||
|
||||
bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_millis )
|
||||
{
|
||||
HEntity collide;
|
||||
switch( processGotoHelper( current, timestep_millis, collide ) )
|
||||
{
|
||||
case ALREADY_AT_DESTINATION:
|
||||
// If on a collision path; decide where to go next. Otherwise, proceed to the next waypoint.
|
||||
if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION )
|
||||
{
|
||||
repath();
|
||||
}
|
||||
else
|
||||
m_orderQueue.pop_front();
|
||||
return( false );
|
||||
case COLLISION_OVERLAPPING_OBJECTS:
|
||||
return( false );
|
||||
case COLLISION_WITH_DESTINATION:
|
||||
// We're here...
|
||||
m_orderQueue.pop_front();
|
||||
return( false );
|
||||
case COLLISION_NEAR_DESTINATION:
|
||||
{
|
||||
// Here's a wierd idea: (I hope it works)
|
||||
// Spiral round the destination until a free point is found.
|
||||
CBoundingCircle destinationObs( current->m_data[0].location.x, current->m_data[0].location.y, m_bounds->m_radius );
|
||||
|
||||
float interval = destinationObs.m_radius;
|
||||
float r = interval, theta = 0.0f, delta;
|
||||
float _x = current->m_data[0].location.x, _y = current->m_data[0].location.y;
|
||||
|
||||
while( true )
|
||||
{
|
||||
delta = interval / r;
|
||||
theta += delta;
|
||||
r += ( interval * delta ) / ( 2 * PI );
|
||||
destinationObs.setPosition( _x + r * cosf( theta ), _y + r * sinf( theta ) );
|
||||
if( !getCollisionObject( &destinationObs ) ) break;
|
||||
}
|
||||
|
||||
// Reset our destination
|
||||
current->m_data[0].location.x = _x + r * cosf( theta );
|
||||
current->m_data[0].location.y = _y + r * sinf( theta );
|
||||
|
||||
return( false );
|
||||
}
|
||||
case COLLISION_OTHER:
|
||||
{
|
||||
// Path around it.
|
||||
|
||||
CEntityOrder avoidance;
|
||||
avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION;
|
||||
@ -194,27 +252,145 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
|
||||
m_orderQueue.pop_front();
|
||||
m_orderQueue.push_front( avoidance );
|
||||
return( false );
|
||||
|
||||
}
|
||||
|
||||
// Will we step off the map?
|
||||
if( !g_Game->GetWorld()->GetTerrain()->isOnMap( m_position.X, m_position.Z ) )
|
||||
{
|
||||
// Yes. That's not a particularly good idea, either.
|
||||
|
||||
m_position.X -= delta.x;
|
||||
m_position.Z -= delta.y;
|
||||
m_bounds->setPosition( m_position.X, m_position.Z );
|
||||
|
||||
// All things being equal, we should only get here while on a collision path
|
||||
// (No destination should be off the map)
|
||||
|
||||
case WOULD_LEAVE_MAP:
|
||||
// Just stop here, repath if necessary.
|
||||
|
||||
m_orderQueue.pop_front();
|
||||
return( false );
|
||||
default:
|
||||
return( false );
|
||||
}
|
||||
}
|
||||
|
||||
bool CEntity::processAttackMelee( CEntityOrder* current, size_t timestep_millis )
|
||||
{
|
||||
m_orderQueue.pop_front();
|
||||
|
||||
if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant )
|
||||
return( false );
|
||||
|
||||
current->m_data[0].location = current->m_data[0].entity->m_position;
|
||||
|
||||
if( ( current->m_data[0].location - m_position ).length() < m_meleeRange )
|
||||
{
|
||||
current->m_type = CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING;
|
||||
return( true );
|
||||
}
|
||||
|
||||
// No. I suppose it's OK to go there, then. *disappointed*
|
||||
if( m_transition )
|
||||
{
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim );
|
||||
// Animation desync
|
||||
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
|
||||
}
|
||||
|
||||
// The pathfinder will push its result back into this unit's queue.
|
||||
|
||||
g_Pathfinder.requestMeleeAttackPath( me, current->m_data[0].entity );
|
||||
|
||||
return( true );
|
||||
}
|
||||
|
||||
bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_millis )
|
||||
{
|
||||
// Target's dead? Then our work here is done.
|
||||
if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant )
|
||||
{
|
||||
m_orderQueue.pop_front();
|
||||
return( false );
|
||||
}
|
||||
|
||||
// Still playing attack animation? Suspend processing.
|
||||
if( m_actor->GetModel()->GetAnimation() == m_actor->GetObject()->m_MeleeAnim )
|
||||
return( false );
|
||||
|
||||
// Just transitioned? No animation? (=> melee just finished) Play walk.
|
||||
if( m_transition || !m_actor->GetModel()->GetAnimation() )
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim );
|
||||
|
||||
CVector2D delta = current->m_data[0].entity->m_position - m_position;
|
||||
|
||||
float adjRange = m_meleeRange + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius;
|
||||
float adjMinRange = m_meleeRangeMin + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius;
|
||||
|
||||
if( delta.within( adjMinRange ) )
|
||||
{
|
||||
// Too close... do nothing.
|
||||
return( false );
|
||||
}
|
||||
|
||||
if( !delta.within( adjRange ) )
|
||||
{
|
||||
// Too far away at the moment, chase after the target...
|
||||
// We're aiming to end up at a location just inside our maximum range
|
||||
// (is this good enough?)
|
||||
|
||||
delta = delta.normalize() * ( adjRange - m_bounds->m_radius );
|
||||
|
||||
current->m_data[0].location = (CVector2D)current->m_data[0].entity->m_position - delta;
|
||||
|
||||
HEntity collide;
|
||||
switch( processGotoHelper( current, timestep_millis, collide ) )
|
||||
{
|
||||
case ALREADY_AT_DESTINATION:
|
||||
case REACHED_DESTINATION:
|
||||
case COLLISION_WITH_DESTINATION:
|
||||
// Not too far any more...
|
||||
break;
|
||||
case NORMAL:
|
||||
// May or may not be close enough, check...
|
||||
// (Assuming the delta above will never take us within minimum range)
|
||||
delta = current->m_data[0].entity->m_position - m_position;
|
||||
if( delta.within( adjRange ) )
|
||||
break;
|
||||
// Otherwise, continue chasing
|
||||
return( false );
|
||||
default:
|
||||
// Path around it.
|
||||
|
||||
CEntityOrder avoidance;
|
||||
avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION;
|
||||
CVector2D right;
|
||||
right.x = m_ahead.y; right.y = -m_ahead.x;
|
||||
CVector2D avoidancePosition;
|
||||
|
||||
// Which is the shortest diversion, going left or right?
|
||||
// (Weight a little towards the right, to stop both units dodging the same way)
|
||||
|
||||
if( ( collide->m_bounds->m_pos - m_bounds->m_pos ).dot( right ) < 1 )
|
||||
{
|
||||
// Turn right.
|
||||
avoidancePosition = collide->m_bounds->m_pos + right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Turn left.
|
||||
avoidancePosition = collide->m_bounds->m_pos - right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f );
|
||||
}
|
||||
|
||||
// Create a short path representing this detour
|
||||
|
||||
avoidance.m_data[0].location = avoidancePosition;
|
||||
if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION )
|
||||
m_orderQueue.pop_front();
|
||||
m_orderQueue.push_front( avoidance );
|
||||
return( false );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close enough, but turn to face them.
|
||||
m_orientation = atan2( delta.x, delta.y );
|
||||
m_ahead = delta.normalize();
|
||||
}
|
||||
|
||||
// Now we've got this far:
|
||||
// Pointy end goes into the other man...
|
||||
|
||||
CEventAttack AttackEvent( current->m_data[0].entity );
|
||||
|
||||
if( DispatchEvent( &AttackEvent ) )
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_MeleeAnim, true );
|
||||
|
||||
return( false );
|
||||
}
|
||||
@ -231,11 +407,11 @@ bool CEntity::processGoto( CEntityOrder* current, size_t timestep_millis )
|
||||
if( ( path_to - pos ).length() < 0.1f )
|
||||
return( false );
|
||||
|
||||
if( !m_moving )
|
||||
if( m_transition )
|
||||
{
|
||||
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim );
|
||||
// Animation desync
|
||||
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
|
||||
m_moving = true;
|
||||
}
|
||||
|
||||
// The pathfinder will push its result back into this unit's queue.
|
||||
|
71
source/simulation/EntitySupport.h
Executable file
71
source/simulation/EntitySupport.h
Executable file
@ -0,0 +1,71 @@
|
||||
// Supporting data types for CEntity and related
|
||||
|
||||
class CEntityManager;
|
||||
enum EGotoSituation;
|
||||
|
||||
class CDamageType : public CJSObject<CDamageType>
|
||||
{
|
||||
public:
|
||||
float m_Crush;
|
||||
float m_Hack;
|
||||
float m_Pierce;
|
||||
float m_Typeless;
|
||||
CDamageType()
|
||||
{
|
||||
Init( 0.0f, 0.0f, 0.0f, 0.0f );
|
||||
}
|
||||
virtual ~CDamageType() {}
|
||||
CDamageType( float Crush, float Hack, float Pierce )
|
||||
{
|
||||
Init( Crush, Hack, Pierce, 0.0f );
|
||||
}
|
||||
CDamageType( float Typeless )
|
||||
{
|
||||
Init( 0.0f, 0.0f, 0.0f, Typeless );
|
||||
}
|
||||
CDamageType( float Crush, float Hack, float Pierce, float Typeless )
|
||||
{
|
||||
Init( Crush, Hack, Pierce, Typeless );
|
||||
}
|
||||
void Init( float Crush, float Hack, float Pierce, float Typeless )
|
||||
{
|
||||
m_Crush = Crush;
|
||||
m_Hack = Hack;
|
||||
m_Pierce = Pierce;
|
||||
m_Typeless = Typeless;
|
||||
AddProperty<float>( L"crush", &m_Crush );
|
||||
AddProperty<float>( L"hack", &m_Hack );
|
||||
AddProperty<float>( L"pierce", &m_Pierce );
|
||||
AddProperty<float>( L"typeless", &m_Typeless );
|
||||
}
|
||||
static void ScriptingInit()
|
||||
{
|
||||
CJSObject<CDamageType>::ScriptingInit( "DamageType", Construct, 3 );
|
||||
}
|
||||
static JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval )
|
||||
{
|
||||
CDamageType* dt;
|
||||
|
||||
if( argc == 0 )
|
||||
dt = new CDamageType();
|
||||
else if( argc == 1 )
|
||||
dt = new CDamageType( ToPrimitive<float>( argv[0] ) );
|
||||
else if( argc == 3 )
|
||||
dt = new CDamageType( ToPrimitive<float>( argv[0] ),
|
||||
ToPrimitive<float>( argv[1] ),
|
||||
ToPrimitive<float>( argv[2] ) );
|
||||
else if( argc == 4 )
|
||||
dt = new CDamageType( ToPrimitive<float>( argv[0] ),
|
||||
ToPrimitive<float>( argv[1] ),
|
||||
ToPrimitive<float>( argv[2] ),
|
||||
ToPrimitive<float>( argv[3] ) );
|
||||
else
|
||||
return( JS_FALSE );
|
||||
|
||||
dt->m_EngineOwned = false; // Let this object be deallocated when JS GCs it.
|
||||
|
||||
*rval = OBJECT_TO_JSVAL( dt->GetScript() );
|
||||
|
||||
return( JS_TRUE );
|
||||
}
|
||||
};
|
33
source/simulation/EventHandlers.cpp
Executable file
33
source/simulation/EventHandlers.cpp
Executable file
@ -0,0 +1,33 @@
|
||||
#include "precompiled.h"
|
||||
#include "EventHandlers.h"
|
||||
#include "Entity.h"
|
||||
|
||||
CEventAttack::CEventAttack( CEntity* target ) : CScriptEvent( L"attack", true, EVENT_ATTACK )
|
||||
{
|
||||
m_target = target;
|
||||
AddProperty( L"target", &m_target );
|
||||
}
|
||||
|
||||
CEventDamage::CEventDamage( CEntity* inflictor, CDamageType* damage ) : CScriptEvent( L"takesDamage", true, EVENT_DAMAGE )
|
||||
{
|
||||
m_inflictor = inflictor;
|
||||
m_damage = damage;
|
||||
AddReadOnlyProperty( L"inflictor", &m_inflictor );
|
||||
AddProperty( L"damage", &m_damage );
|
||||
}
|
||||
|
||||
CEventTargetChanged::CEventTargetChanged( CEntity* target ) : CScriptEvent( L"targetChanged", false, EVENT_TARGET_CHANGED )
|
||||
{
|
||||
m_target = target;
|
||||
m_defaultAction = -1;
|
||||
AddReadOnlyProperty( L"target", &m_target );
|
||||
AddProperty( L"defaultAction", &m_defaultAction );
|
||||
}
|
||||
|
||||
CEventPrepareOrder::CEventPrepareOrder( CEntity* target, int orderType ) : CScriptEvent( L"prepareOrder", true, EVENT_PREPARE_ORDER )
|
||||
{
|
||||
m_target = target;
|
||||
m_orderType = orderType;
|
||||
AddReadOnlyProperty( L"target", &m_target );
|
||||
AddReadOnlyProperty( L"orderType", &m_orderType );
|
||||
}
|
@ -6,28 +6,72 @@
|
||||
#define EVENT_HANDLERS_INCLUDED
|
||||
|
||||
#include "scripting/DOMEvent.h"
|
||||
#include "Vector3D.h"
|
||||
|
||||
class CDamageType;
|
||||
|
||||
enum EEventType
|
||||
{
|
||||
EVENT_INITIALIZE,
|
||||
EVENT_TICK,
|
||||
EVENT_ATTACK,
|
||||
EVENT_DAMAGE,
|
||||
EVENT_TARGET_CHANGED,
|
||||
EVENT_PREPARE_ORDER,
|
||||
EVENT_LAST,
|
||||
};
|
||||
|
||||
static const wchar_t* EventNames[] =
|
||||
{
|
||||
/* EVENT_INITIALIZE */ L"onInitialize",
|
||||
/* EVENT_TICK */ L"onTick"
|
||||
/* EVENT_TICK */ L"onTick",
|
||||
/* EVENT_ATTACK */ L"onAttack", /* This unit is the one doing the attacking... */
|
||||
/* EVENT_DAMAGE */ L"onTakesDamage",
|
||||
/* EVENT_TARGET_CHANGED */ L"onTargetChanged", /* If this unit is selected and the mouseover object changes */
|
||||
/* EVENT_PREPARE_ORDER */ L"onPrepareOrder" /* To check if a unit can execute a given order */
|
||||
};
|
||||
|
||||
class CEventInitialize : public CScriptEvent
|
||||
{
|
||||
public: CEventInitialize() : CScriptEvent( L"initialize", false, EVENT_INITIALIZE ) {}
|
||||
public:
|
||||
CEventInitialize() : CScriptEvent( L"initialize", false, EVENT_INITIALIZE ) {}
|
||||
};
|
||||
|
||||
class CEventTick : public CScriptEvent
|
||||
{
|
||||
public: CEventTick() : CScriptEvent( L"tick", false, EVENT_TICK ) {}
|
||||
public:
|
||||
CEventTick() : CScriptEvent( L"tick", false, EVENT_TICK ) {}
|
||||
};
|
||||
|
||||
class CEventAttack : public CScriptEvent
|
||||
{
|
||||
CEntity* m_target;
|
||||
public:
|
||||
CEventAttack( CEntity* target );
|
||||
};
|
||||
|
||||
class CEventDamage : public CScriptEvent
|
||||
{
|
||||
CEntity* m_inflictor;
|
||||
CDamageType* m_damage;
|
||||
public:
|
||||
CEventDamage( CEntity* inflictor, CDamageType* damage );
|
||||
};
|
||||
|
||||
class CEventTargetChanged : public CScriptEvent
|
||||
{
|
||||
CEntity* m_target;
|
||||
public:
|
||||
int m_defaultAction;
|
||||
CEventTargetChanged( CEntity* target );
|
||||
};
|
||||
|
||||
class CEventPrepareOrder : public CScriptEvent
|
||||
{
|
||||
CEntity* m_target;
|
||||
int m_orderType;
|
||||
public:
|
||||
CEventPrepareOrder( CEntity* target, int orderType );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -15,3 +15,21 @@ void CPathfindEngine::requestPath( HEntity entity, const CVector2D& destination
|
||||
{
|
||||
pathSparse( entity, destination );
|
||||
}
|
||||
|
||||
void CPathfindEngine::requestMeleeAttackPath( HEntity entity, HEntity target )
|
||||
{
|
||||
pathSparse( entity, target->m_position );
|
||||
// For attack orders, do some additional postprocessing (replace goto/nopathing
|
||||
// with attack/nopathing, up until the attack order marker)
|
||||
std::deque<CEntityOrder>::iterator it;
|
||||
for( it = entity->m_orderQueue.begin(); it != entity->m_orderQueue.end(); it++ )
|
||||
{
|
||||
if( it->m_type == CEntityOrder::ORDER_PATH_END_MARKER )
|
||||
break;
|
||||
if( it->m_type == CEntityOrder::ORDER_GOTO_NOPATHING )
|
||||
{
|
||||
it->m_type = CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING;
|
||||
it->m_data[0].entity = target;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,11 +17,18 @@
|
||||
|
||||
#define g_Pathfinder CPathfindEngine::GetSingleton()
|
||||
|
||||
enum EPathType
|
||||
{
|
||||
PF_STANDARD,
|
||||
PF_ATTACK_MELEE,
|
||||
};
|
||||
|
||||
class CPathfindEngine : public Singleton<CPathfindEngine>
|
||||
{
|
||||
public:
|
||||
CPathfindEngine();
|
||||
void requestPath( HEntity entity, const CVector2D& destination );
|
||||
void requestMeleeAttackPath( HEntity entity, HEntity target );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -45,7 +45,12 @@ bool sparsePathTree::slice()
|
||||
CVector2D forward = to - from;
|
||||
float len = forward.length();
|
||||
|
||||
assert( len != 0.0f );
|
||||
if( len == 0.0f )
|
||||
{
|
||||
// Too wierd. (Heavy traffic, obstacles in positions leading to this degenerate state.
|
||||
type = SPF_IMPOSSIBLE;
|
||||
return( true );
|
||||
}
|
||||
|
||||
forward /= len;
|
||||
CVector2D v_right = CVector2D( forward.y, -forward.x );
|
||||
@ -82,6 +87,7 @@ bool sparsePathTree::slice()
|
||||
float length = delta.length();
|
||||
|
||||
float offsetDistance = ( turningRadius * length / sqrt( length * length - turningRadius * turningRadius ) );
|
||||
|
||||
left = r.position - v_right * offsetDistance;
|
||||
right = r.position + v_right * offsetDistance;
|
||||
}
|
||||
@ -189,12 +195,15 @@ void sparsePathTree::pushResults( std::vector<CVector2D>& nodelist )
|
||||
}
|
||||
}
|
||||
|
||||
void nodeSmooth( HEntity entity, std::vector<CVector2D>& nodelist )
|
||||
void nodePostProcess( HEntity entity, std::vector<CVector2D>& nodelist )
|
||||
{
|
||||
std::vector<CVector2D>::iterator it;
|
||||
CVector2D next = nodelist.front();
|
||||
|
||||
CEntityOrder node;
|
||||
node.m_type = CEntityOrder::ORDER_PATH_END_MARKER;
|
||||
entity->m_orderQueue.push_front( node );
|
||||
|
||||
node.m_type = CEntityOrder::ORDER_GOTO_SMOOTHED;
|
||||
node.m_data[0].location = next;
|
||||
|
||||
@ -230,7 +239,7 @@ void pathSparse( HEntity entity, CVector2D destination )
|
||||
// Sanity check:
|
||||
if( source.length() < 0.01f ) return;
|
||||
|
||||
sparsePathTree sparseEngine( source, destination, entity, getContainingObject( destination ), SPF_RECURSION_DEPTH );
|
||||
sparsePathTree sparseEngine( source, destination, entity, getContainingObject( destination ), 3 );
|
||||
while( sparseEngine.type & sparsePathTree::SPF_OPEN ) sparseEngine.slice();
|
||||
|
||||
// assert( sparseEngine.type & sparsePathTree::SPF_SOLVED ); // Shouldn't be any impossible cases yet.
|
||||
@ -239,7 +248,7 @@ void pathSparse( HEntity entity, CVector2D destination )
|
||||
{
|
||||
sparseEngine.pushResults( pathnodes );
|
||||
pathnodes.push_back( source );
|
||||
nodeSmooth( entity, pathnodes );
|
||||
nodePostProcess( entity, pathnodes );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -68,7 +68,7 @@ struct sparsePathTree
|
||||
|
||||
extern int SPF_RECURSION_DEPTH;
|
||||
|
||||
void nodeSmooth( HEntity entity, std::vector<CVector2D>& nodelist );
|
||||
void nodePostProcess( HEntity entity, std::vector<CVector2D>& nodelist );
|
||||
void pathSparse( HEntity entity, CVector2D destination );
|
||||
bool pathSparseRecursive( HEntity entity, CVector2D from, CVector2D to, CBoundingObject* destinationCollisionObject );
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
size_t simulationTime;
|
||||
size_t frameCount;
|
||||
|
||||
/*
|
||||
void CScheduler::pushTime( size_t delay, const HEntity& destination, const CMessage* message )
|
||||
{
|
||||
timeMessage.push( SDispatchObjectMessage( destination, simulationTime + delay, message ) );
|
||||
@ -14,6 +15,7 @@ void CScheduler::pushFrame( size_t delay, const HEntity& destination, const CMes
|
||||
{
|
||||
frameMessage.push( SDispatchObjectMessage( destination, frameCount + delay, message ) );
|
||||
}
|
||||
*/
|
||||
|
||||
void CScheduler::pushTime( size_t delay, const CStrW& fragment, JSObject* operateOn )
|
||||
{
|
||||
@ -47,27 +49,6 @@ void CScheduler::pushInterval( size_t first, size_t interval, JSFunction* functi
|
||||
|
||||
void CScheduler::update(size_t simElapsed)
|
||||
{
|
||||
simulationTime += simElapsed;
|
||||
frameCount++;
|
||||
|
||||
while( !timeMessage.empty() )
|
||||
{
|
||||
SDispatchObjectMessage top = timeMessage.top();
|
||||
if( top.deliveryTime > simulationTime )
|
||||
break;
|
||||
timeMessage.pop();
|
||||
top.destination->dispatch( top.message );
|
||||
delete( top.message );
|
||||
}
|
||||
while( !frameMessage.empty() )
|
||||
{
|
||||
SDispatchObjectMessage top = frameMessage.top();
|
||||
if( top.deliveryTime > frameCount )
|
||||
break;
|
||||
frameMessage.pop();
|
||||
top.destination->dispatch( top.message );
|
||||
delete( top.message );
|
||||
}
|
||||
while( !timeScript.empty() )
|
||||
{
|
||||
SDispatchObjectScript top = timeScript.top();
|
||||
|
@ -30,6 +30,7 @@ struct SDispatchObject
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
struct SDispatchObjectMessage : public SDispatchObject
|
||||
{
|
||||
HEntity destination;
|
||||
@ -40,6 +41,7 @@ struct SDispatchObjectMessage : public SDispatchObject
|
||||
: SDispatchObject( _deliveryTime, recurrence ), destination( _destination ), message( _message ) {}
|
||||
|
||||
};
|
||||
*/
|
||||
|
||||
struct SDispatchObjectScript : public SDispatchObject
|
||||
{
|
||||
@ -63,14 +65,17 @@ struct SDispatchObjectFunction : public SDispatchObject
|
||||
|
||||
struct CScheduler : public Singleton<CScheduler>
|
||||
{
|
||||
std::priority_queue<SDispatchObjectMessage> timeMessage, frameMessage;
|
||||
// std::priority_queue<SDispatchObjectMessage> timeMessage, frameMessage;
|
||||
std::priority_queue<SDispatchObjectScript> timeScript, frameScript;
|
||||
std::priority_queue<SDispatchObjectFunction> timeFunction, frameFunction;
|
||||
|
||||
bool m_abortInterval;
|
||||
|
||||
/*
|
||||
void pushTime( size_t delay, const HEntity& destination, const CMessage* message );
|
||||
void pushFrame( size_t delay, const HEntity& destination, const CMessage* message );
|
||||
*/
|
||||
|
||||
void pushTime( size_t delay, const CStrW& fragment, JSObject* operateOn = NULL );
|
||||
void pushFrame( size_t delay, const CStrW& fragment, JSObject* operateOn = NULL );
|
||||
void pushInterval( size_t first, size_t interval, const CStrW& fragment, JSObject* operateOn = NULL );
|
||||
|
@ -69,13 +69,14 @@ bool CScriptObject::Run( JSObject* Context )
|
||||
return( g_ScriptingHost.ValueToBool( Temp ) );
|
||||
}
|
||||
|
||||
// Treat this as an event handler and dispatch an event to it.
|
||||
void CScriptObject::DispatchEvent( JSObject* Context, CScriptEvent* evt )
|
||||
// Treat this as an event handler and dispatch an event to it. Return !evt->m_cancelled, as a convenience.
|
||||
bool CScriptObject::DispatchEvent( JSObject* Context, CScriptEvent* evt )
|
||||
{
|
||||
jsval Temp;
|
||||
jsval EventObject = OBJECT_TO_JSVAL( evt->GetScript() );
|
||||
if( Function )
|
||||
JS_CallFunction( g_ScriptingHost.GetContext(), Context, Function, 1, &EventObject, &Temp );
|
||||
return( !evt->m_Cancelled );
|
||||
}
|
||||
|
||||
void CScriptObject::Compile( CStrW FileNameTag, CStrW FunctionBody )
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
bool Run( JSObject* Context );
|
||||
|
||||
// Treat this as an event handler and dispatch an event to it.
|
||||
void DispatchEvent( JSObject* Context, CScriptEvent* evt );
|
||||
bool DispatchEvent( JSObject* Context, CScriptEvent* evt );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -32,8 +32,7 @@ void CSimulation::Initialize(CGameAttributes *pAttribs)
|
||||
{
|
||||
m_pTurnManager->Initialize(m_pGame->GetNumPlayers());
|
||||
|
||||
CMessage init_msg (CMessage::EMSG_INIT);
|
||||
g_EntityManager.dispatchAll(&init_msg);
|
||||
g_EntityManager.InitializeAll();
|
||||
}
|
||||
|
||||
void CSimulation::Update(double frameTime)
|
||||
@ -89,7 +88,7 @@ uint CSimulation::TranslateMessage(CNetMessage *pMsg, uint clientMask, void *use
|
||||
entOrder.m_data[0].location.x=(float)msg->m_TargetX;
|
||||
entOrder.m_data[0].location.y=(float)msg->m_TargetY;
|
||||
CEntity *ent=msg->m_Entity;
|
||||
ent->dispatch(new CMessageOrder(entOrder));
|
||||
ent->pushOrder( entOrder );
|
||||
break;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user