Combat code, some scripting, broken network, and fixed some bugs.

This was SVN commit r1301.
This commit is contained in:
MarkT 2004-11-11 07:09:32 +00:00
parent 9b47d446d2
commit 793794649f
55 changed files with 1836 additions and 533 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -15,7 +15,7 @@ public:
~CMeshManager();
CModelDef *GetMesh(const char *filename);
int ReleaseMesh(CModelDef *mesh);
int ReleaseMesh(CModelDef* mesh);
private:
mesh_map m_MeshMap;
};

View File

@ -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;

View File

@ -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; }

View File

@ -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
{

View File

@ -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;

View File

@ -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())

View File

@ -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,

View File

@ -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>

View File

@ -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 {

View File

@ -14,7 +14,7 @@
enum ELogMethod
{
NORMAL,
MESSAGE=NORMAL,
MESSAGE = NORMAL,
ERROR,
WARNING
};

View File

@ -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 );
}

View File

@ -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();

View File

@ -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 );
}

View File

@ -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 );

View File

@ -3,6 +3,7 @@
#include "types.h"
#include "CStr.h"
#include "scripting/JSSerialization.h"
enum ENetMessageType
{

View File

@ -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;

View File

@ -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);

View File

@ -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) ));
}

View File

@ -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;

View File

@ -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 );

View File

@ -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)

View File

@ -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 )

View File

@ -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 );

View 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 );
}
};

View File

@ -31,6 +31,7 @@ JSFunc getGlobal;
JSFunc setCursor;
JSFunc GetGameObject;
JSFunc startServer;
JSFunc joinGame;
JSFunc startGame;

View File

@ -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 );
}
}
}

View File

@ -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>

View File

@ -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

View File

@ -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];

View File

@ -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 )

View File

@ -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 );

View File

@ -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 );
}

View File

@ -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

View File

@ -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 )

View File

@ -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();

View File

@ -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();
}

View File

@ -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 );

View File

@ -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
*/

View File

@ -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];

View File

@ -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.

View 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 );
}
};

View 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 );
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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
{

View File

@ -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 );

View File

@ -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();

View File

@ -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 );

View File

@ -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 )

View File

@ -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

View File

@ -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;
}