Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp (revision 803) +++ ps/trunk/source/ps/Hotkey.cpp (revision 804) @@ -1,222 +1,332 @@ #include "precompiled.h" #include "Hotkey.h" #include "input.h" #include "ConfigDB.h" +#include "CConsole.h" +#include "CStr.h" -bool hotkeys[HOTKEY_LAST]; +extern CConsole* g_Console; extern bool keys[SDLK_LAST]; extern bool mouseButtons[5]; +/* SDL-type */ + struct SHotkeyMapping { int mapsTo; std::vector requires; }; -static std::vector hotkeyMap[SDLK_LAST + 5]; +typedef std::vector KeyMapping; + +// A mapping of keycodes onto sets of SDL event codes +static KeyMapping hotkeyMap[SDLK_LAST + 5]; + +// An array of the status of virtual keys +bool hotkeys[HOTKEY_LAST]; +// 'Keycodes' for the mouse buttons const int MOUSE_LEFT = SDLK_LAST + SDL_BUTTON_LEFT; const int MOUSE_RIGHT = SDLK_LAST + SDL_BUTTON_RIGHT; const int MOUSE_MIDDLE = SDLK_LAST + SDL_BUTTON_MIDDLE; const int MOUSE_WHEELUP = SDLK_LAST + SDL_BUTTON_WHEELUP; const int MOUSE_WHEELDOWN = SDLK_LAST + SDL_BUTTON_WHEELDOWN; struct SHotkeyInfo { int code; const char* name; int defaultmapping1, defaultmapping2; }; static SHotkeyInfo hotkeyInfo[] = { { HOTKEY_EXIT, "exit", SDLK_ESCAPE, 0 }, { HOTKEY_SCREENSHOT, "screenshot", SDLK_PRINT, 0 }, { HOTKEY_WIREFRAME, "wireframe", SDLK_w, 0 }, { HOTKEY_CAMERA_RESET, "camera.reset", SDLK_h, 0 }, { HOTKEY_CAMERA_ZOOM_IN, "camera.zoom.in", SDLK_PLUS, SDLK_KP_PLUS }, { HOTKEY_CAMERA_ZOOM_OUT, "camera.zoom.out", SDLK_MINUS, SDLK_KP_MINUS }, { 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_ABOUT_TARGET, "camera.rotate.abouttarget", 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_BOOKMARK_MODIFIER, "camera.bookmark.modifier", 0, 0 }, + { HOTKEY_CAMERA_BOOKMARK_SAVE, "camera.bookmark.save", 0, 0 }, + { HOTKEY_CAMERA_BOOKMARK_SNAP, "camera.bookmark.snap", 0, 0 }, { HOTKEY_CONSOLE_TOGGLE, "console.toggle", SDLK_F1, 0 }, { HOTKEY_SELECTION_ADD, "selection.add", SDLK_LSHIFT, SDLK_RSHIFT }, { HOTKEY_SELECTION_REMOVE, "selection.remove", SDLK_LCTRL, SDLK_RCTRL }, { HOTKEY_SELECTION_GROUP_ADD, "selection.group.add", SDLK_LSHIFT, SDLK_RSHIFT }, { HOTKEY_SELECTION_GROUP_SAVE, "selection.group.save", SDLK_LCTRL, SDLK_RCTRL }, { HOTKEY_SELECTION_GROUP_SNAP, "selection.group.snap", SDLK_LALT, SDLK_RALT }, { HOTKEY_SELECTION_SNAP, "selection.snap", SDLK_HOME, 0 }, + { HOTKEY_ORDER_QUEUE, "order.queue", SDLK_LSHIFT, SDLK_RSHIFT }, { HOTKEY_CONTEXTORDER_NEXT, "contextorder.next", SDLK_RIGHTBRACKET, 0 }, { HOTKEY_CONTEXTORDER_PREVIOUS, "contextorder.previous", SDLK_LEFTBRACKET, 0 }, { HOTKEY_HIGHLIGHTALL, "highlightall", SDLK_o, 0 } }; -void loadHotkeys() +/* SDL-type ends */ + +/* GUI-type */ + +struct SHotkeyMappingGUI { - initKeyNameMap(); + CStr mapsTo; + std::vector requires; +}; - CParser multikeyParser; - multikeyParser.InputTaskType( "multikey", "<_$value_+_>_$value" ); +typedef std::vector GuiMapping; - for( int i = 0; i < HOTKEY_LAST; i++ ) - { - CStr hotkeyname( "hotkey." ); - hotkeyname += hotkeyInfo[i].name; +// A mapping of keycodes onto sets of hotkey name strings (e.g. '[hotkey.]camera.reset') +static GuiMapping hotkeyMapGUI[SDLK_LAST + 5]; - CConfigValueSet* binding = g_ConfigDB.GetValues( CFG_SYSTEM, hotkeyname ); +typedef std::vector GUIObjectList; // A list of GUI objects +typedef std::map GUIHotkeyMap; // A mapping of name strings to lists of GUI objects that they trigger - if( binding ) - { - int mapping; - - CConfigValueSet::iterator it; +static GUIHotkeyMap guiHotkeyMap; + +// Look up a key binding in the config file and set the mappings for +// all key combinations that trigger it. + +void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) +{ + CConfigValueSet* binding = g_ConfigDB.GetValues( CFG_SYSTEM, CStr( "hotkey." ) + hotkeyName ); + if( binding ) + { + int mapping; + + CConfigValueSet::iterator it; + CParser multikeyParser; + multikeyParser.InputTaskType( "multikey", "<_$value_+_>_$value" ); - // Iterate through the bindings for this event... + // Iterate through the bindings for this event... - for( it = binding->begin(); it != binding->end(); it++ ) + for( it = binding->begin(); it != binding->end(); it++ ) + { + std::string hotkey; + if( it->GetString( hotkey ) ) { - std::string hotkey; - if( it->GetString( hotkey ) ) - { - std::vector keyCombination; + std::vector keyCombination; - CParserLine multikeyIdentifier; - multikeyIdentifier.ParseString( multikeyParser, hotkey ); + CParserLine multikeyIdentifier; + multikeyIdentifier.ParseString( multikeyParser, hotkey ); - // Iterate through multiple-key bindings (e.g. Ctrl+I) + // Iterate through multiple-key bindings (e.g. Ctrl+I) - for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ ) + for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ ) + { + + if( multikeyIdentifier.GetArgString( (int)t, hotkey ) ) { - - if( multikeyIdentifier.GetArgString( (int)t, hotkey ) ) - { - mapping = getKeyCode( hotkey ); // Attempt decode as key name - if( !mapping ) - if( !it->GetInt( mapping ) ) // Attempt decode as key code - continue; - keyCombination.push_back( mapping ); - } + mapping = getKeyCode( hotkey ); // Attempt decode as key name + if( !mapping ) + if( !it->GetInt( mapping ) ) // Attempt decode as key code + continue; + keyCombination.push_back( mapping ); } - - std::vector::iterator itKey, itKey2; + } + + std::vector::iterator itKey, itKey2; - SHotkeyMapping bind; + SHotkeyMapping bindCode; + SHotkeyMappingGUI bindName; - for( itKey = keyCombination.begin(); itKey != keyCombination.end(); itKey++ ) + for( itKey = keyCombination.begin(); itKey != keyCombination.end(); itKey++ ) + { + bindName.mapsTo = hotkeyName; + bindName.requires.clear(); + if( integerMapping != -1 ) { - bind.mapsTo = i; - bind.requires.clear(); - for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); itKey2++ ) + bindCode.mapsTo = integerMapping; + bindCode.requires.clear(); + } + for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); itKey2++ ) + { + // Push any auxiliary keys. + if( itKey != itKey2 ) { - // Push any auxiliary keys. - if( itKey != itKey2 ) - bind.requires.push_back( *itKey2 ); + bindName.requires.push_back( *itKey2 ); + if( integerMapping != -1 ) + bindCode.requires.push_back( *itKey2 ); } - hotkeyMap[*itKey].push_back( bind ); } + hotkeyMapGUI[*itKey].push_back( bindName ); + if( integerMapping != -1 ) + hotkeyMap[*itKey].push_back( bindCode ); } } } - else - { - SHotkeyMapping bind[2]; - bind[0].mapsTo = i; - bind[1].mapsTo = i; - bind[0].requires.clear(); - bind[1].requires.clear(); - hotkeyMap[ hotkeyInfo[i].defaultmapping1 ].push_back( bind[0] ); - if( hotkeyInfo[i].defaultmapping2 ) - hotkeyMap[ hotkeyInfo[i].defaultmapping2 ].push_back( bind[1] ); - } + } + else if( integerMapping != -1 ) + { + SHotkeyMapping bind[2]; + bind[0].mapsTo = integerMapping; + bind[1].mapsTo = integerMapping; + bind[0].requires.clear(); + bind[1].requires.clear(); + hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping1 ].push_back( bind[0] ); + if( hotkeyInfo[integerMapping].defaultmapping2 ) + hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping2 ].push_back( bind[1] ); } } +void loadHotkeys() +{ + initKeyNameMap(); + + for( int i = 0; i < HOTKEY_LAST; i++ ) + setBindings( hotkeyInfo[i].name, i ); + +} + +void hotkeyRegisterGUIObject( const CStr& objName, const CStr& hotkeyName ) +{ + GUIObjectList& boundTo = guiHotkeyMap[hotkeyName]; + if( boundTo.empty() ) + { + // Load keybindings from the config file + setBindings( hotkeyName ); + } + boundTo.push_back( objName ); +} int hotkeyInputHandler( const SDL_Event* ev ) { int keycode; switch( ev->type ) { case SDL_KEYDOWN: case SDL_KEYUP: keycode = (int)ev->key.keysym.sym; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: keycode = SDLK_LAST + (int)ev->button.button; break; default: return( EV_PASS ); } + // Inhibit the dispatch of hotkey events caused by printable or control keys + // while the console is up. + + if( g_Console->IsActive() && ( + ( keycode == 8 ) || ( keycode == 9 ) || ( keycode == 13 ) || /* Editing */ + ( ( keycode >= 32 ) && ( keycode < 282 ) ) ) ) /* Printable (<128), 'World' (<256) */ + return( EV_PASS ); /* Numeric keypad (<273) and Navigation (<282) */ + std::vector::iterator it; + std::vector::iterator itGUI; + SDL_Event hotkeyNotification; if( ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN ) ) { + // SDL-events bit for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) { // Check to see if all auxiliary keys are down std::vector::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( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; } } if( accept ) { hotkeys[it->mapsTo] = true; hotkeyNotification.type = SDL_HOTKEYDOWN; hotkeyNotification.user.code = it->mapsTo; SDL_PushEvent( &hotkeyNotification ); } } + // GUI bit... could do with some optimization later. + for( itGUI = hotkeyMapGUI[keycode].begin(); itGUI != hotkeyMapGUI[keycode].end(); itGUI++ ) + { + // Check to see if all auxiliary keys are down + + std::vector::iterator itKey; + bool accept = true; + + for( itKey = itGUI->requires.begin(); itKey != itGUI->requires.end(); itKey++ ) + { + if( *itKey < SDLK_LAST ) + { + if( !keys[*itKey] ) accept = false; + } + else + { + if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; + } + } + + if( accept ) + { + // GUI-objects bit + // This fragment is an obvious candidate for rewriting when speed becomes an issue. + GUIHotkeyMap::iterator map_it; + GUIObjectList::iterator obj_it; + map_it = guiHotkeyMap.find( itGUI->mapsTo ); + 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 ); + } + } + } + } } else { for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) { // Check to see if all auxiliary keys are down std::vector::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( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; } } if( accept ) { hotkeys[it->mapsTo] = false; hotkeyNotification.type = SDL_HOTKEYUP; hotkeyNotification.user.code = it->mapsTo; SDL_PushEvent( &hotkeyNotification ); } } } return( EV_PASS ); } Index: ps/trunk/source/ps/ConfigDB.cpp =================================================================== --- ps/trunk/source/ps/ConfigDB.cpp (revision 803) +++ ps/trunk/source/ps/ConfigDB.cpp (revision 804) @@ -1,373 +1,373 @@ #include "precompiled.h" #include "Prometheus.h" #include "Parser.h" #include "ConfigDB.h" #include "CLogger.h" #include "res/vfs.h" #include "res/file.h" #include "scripting/ScriptingHost.h" #include "types.h" using namespace std; typedef map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; CStr CConfigDB::m_ConfigFile[CFG_LAST]; bool CConfigDB::m_UseVFS[CFG_LAST]; namespace ConfigNamespace_JS { JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; CStr propName = g_ScriptingHost.ValueToString(id); CConfigValue *val=g_ConfigDB.GetValue(cfgNs, propName); if (val) { JSString *js_str=JS_NewStringCopyN(cx, val->m_String.c_str(), val->m_String.size()); *vp = STRING_TO_JSVAL(js_str); } return JS_TRUE; } JSBool SetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; CStr propName = g_ScriptingHost.ValueToString(id); CConfigValue *val=g_ConfigDB.CreateValue(cfgNs, propName); char *str; if (JS_ConvertArguments(cx, 1, vp, "s", &str)) { val->m_String=str; return JS_TRUE; } else return JS_FALSE; } JSClass Class = { "ConfigNamespace", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, GetProperty, SetProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { if (argc != 0) return JS_FALSE; JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj); *rval=OBJECT_TO_JSVAL(newObj); return JS_TRUE; } void SetNamespace(JSContext *cx, JSObject *obj, EConfigNamespace cfgNs) { JS_SetPrivate(cx, obj, (void *)cfgNs); } JSBool WriteFile( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; if (argc != 2) return JS_FALSE; JSBool useVFS; char *path; if (JS_ConvertArguments(cx, 2, argv, "bs", &useVFS, &path)) { JSBool res=g_ConfigDB.WriteFile(cfgNs, useVFS?true:false, path); *rval = BOOLEAN_TO_JSVAL(res); return JS_TRUE; } else return JS_FALSE; } JSBool Reload( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { if (argc != 0) return JS_FALSE; EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; JSBool ret=g_ConfigDB.Reload(cfgNs); *rval = BOOLEAN_TO_JSVAL(ret); return JS_TRUE; } JSBool SetFile( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { if (argc != 0) return JS_FALSE; EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; JSBool useVFS; char *path; if (JS_ConvertArguments(cx, 2, argv, "bs", &useVFS, &path)) { g_ConfigDB.SetConfigFile(cfgNs, useVFS?true:false, path); return JS_TRUE; } else return JS_FALSE; } JSFunctionSpec Funcs[] = { { "WriteFile", WriteFile, 2, 0, 0}, { "Reload", Reload, 0, 0, 0}, { "SetFile", SetFile, 1, 0, 0}, {0} }; }; namespace ConfigDB_JS { JSClass Class = { "ConfigDB", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; JSPropertySpec Props[] = { {0} }; JSFunctionSpec Funcs[] = { {0} }; JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { if (argc != 0) return JS_FALSE; JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj); *rval=OBJECT_TO_JSVAL(newObj); int flags=JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT; #define cfg_ns(_propname, _enum) STMT (\ JSObject *nsobj=g_ScriptingHost.CreateCustomObject("ConfigNamespace"); \ assert(nsobj); \ ConfigNamespace_JS::SetNamespace(cx, nsobj, _enum); \ assert(JS_DefineProperty(cx, newObj, _propname, OBJECT_TO_JSVAL(nsobj), NULL, NULL, flags)); ) cfg_ns("system", CFG_SYSTEM); cfg_ns("user", CFG_USER); cfg_ns("mod", CFG_MOD); #undef cfg_ns return JS_TRUE; } }; CConfigDB::CConfigDB() { g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL); g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL); JSObject *js_ConfigDB=g_ScriptingHost.CreateCustomObject("ConfigDB"); g_ScriptingHost.SetGlobal("g_ConfigDB", OBJECT_TO_JSVAL(js_ConfigDB)); } CConfigValue *CConfigDB::GetValue(EConfigNamespace ns, CStr name) { /* assert(ns < CFG_LAST && ns >= 0); TConfigMap::iterator it=m_Map[ns].find(name); if (it == m_Map[ns].end()) return NULL; else return &(it->second[0] ); */ CConfigValueSet* values = GetValues( ns, name ); if( !values ) return( NULL ); return &( (*values)[0] ); } CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, CStr name ) { assert(ns < CFG_LAST && ns >= 0); TConfigMap::iterator it=m_Map[ns].find(name); if (it == m_Map[ns].end()) return NULL; else return &(it->second ); } CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, CStr name) { assert(ns < CFG_LAST && ns >= 0); CConfigValue *ret=GetValue(ns, name); if (ret) return ret; TConfigMap::iterator it=m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet( 1 ))); return &(it->second[0]); } void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, CStr path) { assert(ns < CFG_LAST && ns >= 0); m_ConfigFile[ns]=path; m_UseVFS[ns]=useVFS; } bool CConfigDB::Reload(EConfigNamespace ns) { // Set up CParser CParser parser; CParserLine parserLine; parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]"); parser.InputTaskType("CommentOrBlank", "_[;[$rest]]"); void *buffer; uint buflen; File f; Handle fh; if (m_UseVFS[ns]) { // Open file with VFS fh=vfs_load(m_ConfigFile[ns], buffer, buflen); if (fh <= 0) { LOG(ERROR, "vfs_load for \"%s\" failed: return was %lld", m_ConfigFile[ns].c_str(), fh); return false; } } else { if (file_open(m_ConfigFile[ns], 0, &f)!=0) { LOG(ERROR, "file_open for \"%s\" failed", m_ConfigFile[ns].c_str()); return false; } if (file_map(&f, buffer, buflen) != 0) { LOG(ERROR, "file_map for \"%s\" failed", m_ConfigFile[ns].c_str()); return false; } } TConfigMap newMap; char *filebuf=(char *)buffer; // Read file line by line char *next=filebuf-1; do { char *pos=next+1; next=strchr(pos, '\n'); if (!next) next=filebuf+buflen; char *lend=next; if (*(lend-1) == '\r') lend--; // Send line to parser bool parseOk=parserLine.ParseString(parser, std::string(pos, lend)); // Get name and value from parser string name; string value; if (parseOk && parserLine.GetArgCount()>=2 && parserLine.GetArgString(0, name) && parserLine.GetArgString(1, value)) { // Add name and value to the map size_t argCount = parserLine.GetArgCount(); newMap[name].clear(); for( size_t t = 0; t < argCount; t++ ) { - if( !parserLine.GetArgString( t + 1, value ) ) + if( !parserLine.GetArgString( (int)t + 1, value ) ) continue; CConfigValue argument; argument.m_String = value; newMap[name].push_back( argument ); LOG(NORMAL, "Loaded config string \"%s\" = \"%s\"", name.c_str(), value.c_str()); } } } while (next < filebuf+buflen); m_Map[ns].swap(newMap); // Close the correct file handle if (m_UseVFS[ns]) { vfs_close(fh); } else { file_unmap(&f); file_close(&f); } return true; } bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, CStr path) { assert(ns >= 0 && ns < CFG_LAST); char realpath[VFS_MAX_PATH]; char nativepath[VFS_MAX_PATH]; const char *filepath=path.c_str(); int err; FILE *fp; if (useVFS) { if ((err=vfs_realpath(filepath, realpath))!=0) { LOG(ERROR, "CConfigDB::WriteFile(): vfs_realpath for VFS path \"%s\" failed (error: %d)", filepath, err); return false; } filepath=realpath; } file_make_native_path(filepath, nativepath); if ((fp = fopen(nativepath, "w")) == NULL) { LOG(ERROR, "CConfigDB::WriteFile(): fopen for path \"%s\" failed (error: %d)", filepath, errno); return false; } uint offset=0; TConfigMap &map=m_Map[ns]; TConfigMap::const_iterator it=map.begin(); while (it != map.end()) { fprintf(fp, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str()); ++it; } fclose(fp); return true; } Index: ps/trunk/source/ps/Interact.h =================================================================== --- ps/trunk/source/ps/Interact.h (revision 803) +++ ps/trunk/source/ps/Interact.h (revision 804) @@ -1,122 +1,120 @@ // Interact.h // // Manages main-screen interaction (screen->worldspace mapping, unit selection, ordering, etc.) // and the hotkey message processor. // Does this belong in GUI? // Mark Thompson (mot20@cam.ac.uk / mark@wildfiregames.com) #include #include "Singleton.h" #include "Entity.h" #include "EntityManager.h" #include "EntityMessage.h" #include "Scheduler.h" #include "Camera.h" -// In it's current incarnation, inefficient but pretty -#define SELECTION_TERRAIN_CONFORMANCE - -#define SELECTION_CIRCLE_POINTS 25 -#define SELECTION_BOX_POINTS 10 - // CSelectedEntities: the singleton containing entities currently selected on the local machine. // (including group allocations on the local machine) struct CSelectedEntities : public Singleton { CSelectedEntities() { clearSelection(); m_group = 255; m_group_highlight = 255; m_contextOrder = -1; } std::vector m_selected; std::vector m_groups[10]; u8 m_group, m_group_highlight; int m_contextOrder; void addSelection( CEntity* entity ); void removeSelection( CEntity* entity ); void setSelection( CEntity* entity ); void clearSelection(); void removeAll( CEntity* entity ); bool isSelected( CEntity* entity ); CVector3D getSelectionPosition(); void saveGroup( u8 groupid ); void loadGroup( u8 groupid ); void addGroup( u8 groupid ); void changeGroup( CEntity* entity, u8 groupid ); void highlightGroup( u8 groupid ); void highlightNone(); int getGroupCount( u8 groupid ); CVector3D getGroupPosition( u8 groupid ); void update(); bool isContextValid( int contextOrder ); void contextOrder( bool pushQueue = false ); + void setContext( int contextOrder ); bool nextContext(); bool previousContext(); void renderSelectionOutlines(); void renderOverlays(); }; // CMouseoverEntities: the singleton containing entities the mouse is currently hovering over or bandboxing // ( for mouseover selection outlines ) struct SMouseoverFader { CEntity* entity; float fade; bool isActive; SMouseoverFader( CEntity* _entity, float _fade = 0.0f, bool _active = true ) : entity( _entity ), fade( _fade ), isActive( _active ) {} }; struct CMouseoverEntities : public Singleton { float m_fadeinrate; float m_fadeoutrate; float m_fademaximum; CEntity* m_target; bool m_bandbox, m_viewall; u16 m_x1, m_y1, m_x2, m_y2; CMouseoverEntities() { m_bandbox = false; m_viewall = false; m_fadeinrate = 1.0f; m_fadeoutrate = 2.0f; m_fademaximum = 0.5f; m_mouseover.clear(); } std::vector m_mouseover; void update( float timestep ); void addSelection(); void removeSelection(); void setSelection(); void expandAcrossScreen(); void expandAcrossWorld(); void renderSelectionOutlines(); void renderOverlays(); bool isBandbox() { return( m_bandbox ); } void startBandbox( u16 x, u16 y ); void stopBandbox(); }; bool isMouseoverType( CEntity* ev ); bool isOnScreen( CEntity* ev ); void pushCameraTarget( const CVector3D& target ); void setCameraTarget( const CVector3D& target ); void popCameraTarget(); int interactInputHandler( const SDL_Event* ev ); extern std::vector cameraTargets; +extern CVector3D cameraBookmarks[10]; +extern bool bookmarkInUse[10]; +extern u8 currentBookmark; extern CVector3D cameraDelta; #define g_Selection CSelectedEntities::GetSingleton() #define g_Mouseover CMouseoverEntities::GetSingleton() Index: ps/trunk/source/ps/Hotkey.h =================================================================== --- ps/trunk/source/ps/Hotkey.h (revision 803) +++ ps/trunk/source/ps/Hotkey.h (revision 804) @@ -1,60 +1,70 @@ // Hotkey.h // // Constant definitions and a couple of exports for the hotkey processor // // Mark Thompson (mot20@cam.ac.uk / mark@wildfiregames.com) - -// Adding a hotkey: +// +// Hotkeys can be mapped onto SDL events (for use internal to the engine), +// or used to trigger activation of GUI buttons. +// +// Adding a hotkey (SDL event type): // // - Define your constant in the enum, just below; // - Add an entry to hotkeyInfo[], in Hotkey.cpp // first column is this constant, second is the config string (minus 'hotkey.') it maps to // third and fourth are the default keys, used if the config file doesn't contain that string. // - Create an input handler for SDL_HOTKEYDOWN, SDL_HOTKEYUP, or poll the hotkeys[] array. // For SDL_HOTKEYDOWN, SDL_HOTKEYUP, the constant is passed in as ev->user.code. // - Add some bindings to the config file. #include "precompiled.h" #include "sdl.h" #include "CStr.h" const int SDL_HOTKEYDOWN = SDL_USEREVENT; const int SDL_HOTKEYUP = SDL_USEREVENT + 1; +const int SDL_GUIHOTKEYPRESS = SDL_USEREVENT + 2; enum { HOTKEY_EXIT, HOTKEY_SCREENSHOT, HOTKEY_WIREFRAME, HOTKEY_CAMERA_RESET, HOTKEY_CAMERA_ZOOM_IN, HOTKEY_CAMERA_ZOOM_OUT, HOTKEY_CAMERA_ZOOM_WHEEL_IN, HOTKEY_CAMERA_ZOOM_WHEEL_OUT, HOTKEY_CAMERA_ROTATE, + HOTKEY_CAMERA_ROTATE_ABOUT_TARGET, HOTKEY_CAMERA_PAN, HOTKEY_CAMERA_PAN_LEFT, HOTKEY_CAMERA_PAN_RIGHT, HOTKEY_CAMERA_PAN_FORWARD, HOTKEY_CAMERA_PAN_BACKWARD, + HOTKEY_CAMERA_BOOKMARK_MODIFIER, + HOTKEY_CAMERA_BOOKMARK_SAVE, + HOTKEY_CAMERA_BOOKMARK_SNAP, HOTKEY_CONSOLE_TOGGLE, HOTKEY_SELECTION_ADD, HOTKEY_SELECTION_REMOVE, HOTKEY_SELECTION_GROUP_ADD, HOTKEY_SELECTION_GROUP_SAVE, HOTKEY_SELECTION_GROUP_SNAP, HOTKEY_SELECTION_SNAP, + HOTKEY_ORDER_QUEUE, HOTKEY_CONTEXTORDER_NEXT, HOTKEY_CONTEXTORDER_PREVIOUS, HOTKEY_HIGHLIGHTALL, HOTKEY_LAST, }; void loadHotkeys(); int hotkeyInputHandler( const SDL_Event* ev ); +void hotkeyRegisterGUIObject( const CStr& objName, const CStr& hotkeyName ); void initKeyNameMap(); CStr getKeyName( int keycode ); int getKeyCode( CStr keyname ); extern bool hotkeys[HOTKEY_LAST]; \ No newline at end of file Index: ps/trunk/source/ps/Interact.cpp =================================================================== --- ps/trunk/source/ps/Interact.cpp (revision 803) +++ ps/trunk/source/ps/Interact.cpp (revision 804) @@ -1,835 +1,870 @@ #include "precompiled.h" #include "Interact.h" #include "Renderer.h" #include "input.h" #include "CConsole.h" #include "HFTracer.h" #include "Hotkey.h" #include "timer.h" extern CCamera g_Camera; extern CConsole* g_Console; extern int mouse_x, mouse_y; extern bool keys[SDLK_LAST]; static const float SELECT_DBLCLICK_RATE = 0.5f; static const int ORDER_DELAY = 5; void CSelectedEntities::addSelection( CEntity* entity ) { m_group = 255; assert( !isSelected( entity ) ); m_selected.push_back( entity ); entity->m_selected = true; } void CSelectedEntities::removeSelection( CEntity* entity ) { m_group = 255; assert( isSelected( entity ) ); entity->m_selected = false; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) { m_selected.erase( it ); break; } } } void CSelectedEntities::renderSelectionOutlines() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->renderSelectionOutline(); if( m_group_highlight != 255 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->renderSelectionOutline( 0.5f ); glDisable( GL_BLEND ); } } void CSelectedEntities::renderOverlays() { glPushMatrix(); glEnable( GL_TEXTURE_2D ); std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it)->m_grouped != 255 ) { assert( (*it)->m_bounds ); glLoadIdentity(); float x, y; CVector3D labelpos = (*it)->m_graphics_position - g_Camera.m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; labelpos.X += (*it)->m_bounds->m_offset.x; labelpos.Z += (*it)->m_bounds->m_offset.y; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = (*it)->getExactGroundLevel( labelpos.X, labelpos.Z ); #endif g_Camera.GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (*it)->m_grouped ); } } if( m_group_highlight != 255 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) { assert( (*it)->m_bounds ); glLoadIdentity(); float x, y; CVector3D labelpos = (*it)->m_graphics_position - g_Camera.m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; labelpos.X += (*it)->m_bounds->m_offset.x; labelpos.Z += (*it)->m_bounds->m_offset.y; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = (*it)->getExactGroundLevel( labelpos.X, labelpos.Z ); #endif g_Camera.GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (*it)->m_grouped ); } glDisable( GL_BLEND ); } glLoadIdentity(); glTranslatef( (float)( mouse_x + 16 ), (float)( g_Renderer.GetHeight() - mouse_y - 8 ), 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); switch( m_contextOrder ) { case CEntityOrder::ORDER_GOTO: glwprintf( L"Go to" ); break; case CEntityOrder::ORDER_PATROL: glwprintf( L"Patrol to" ); break; } glDisable( GL_TEXTURE_2D ); glPopMatrix(); } void CSelectedEntities::setSelection( CEntity* entity ) { m_group = 255; clearSelection(); m_selected.push_back( entity ); } void CSelectedEntities::clearSelection() { m_group = 255; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = false; m_selected.clear(); } void CSelectedEntities::removeAll( CEntity* entity ) { // Remove a reference to an entity from everywhere // (for use when said entity is being destroyed) std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) { m_selected.erase( it ); break; } } for( u8 group = 0; group < 10; group++ ) { for( it = m_groups[group].begin(); it < m_groups[group].end(); it++ ) { if( (*it) == entity ) { m_groups[group].erase( it ); break; } } } } CVector3D CSelectedEntities::getSelectionPosition() { CVector3D avg; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { avg += (*it)->m_graphics_position; avg.X += (*it)->m_bounds->m_offset.x; avg.Z += (*it)->m_bounds->m_offset.y; } return( avg * ( 1.0f / m_selected.size() ) ); } void CSelectedEntities::saveGroup( u8 groupid ) { std::vector::iterator it; // Clear all entities in the group... for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) { (*it)->m_grouped = 255; (*it)->m_grouped_mirror = 0; } m_groups[groupid].clear(); // Remove selected entities from each group they're in, and flag them as // members of the new group for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it)->m_grouped < 255 ) { std::vector& group = m_groups[(*it)->m_grouped]; std::vector::iterator it2; for( it2 = group.begin(); it2 < group.end(); it2++ ) { if( (*it2) == &(**it) ) { group.erase( it2 ); break; } } } (*it)->m_grouped = groupid; (*it)->m_grouped_mirror = ( groupid != 0 ) ? groupid : 10; } // Copy the group across m_groups[groupid] = m_selected; // Set the group selection memory m_group = groupid; } void CSelectedEntities::loadGroup( u8 groupid ) { clearSelection(); m_selected = m_groups[groupid]; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; m_group = groupid; } void CSelectedEntities::addGroup( u8 groupid ) { std::vector::iterator it; for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) { if( !isSelected( *it ) ) addSelection( *it ); } for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; } void CSelectedEntities::changeGroup( CEntity* entity, u8 groupid ) { // Remove from current group u8 current = entity->m_grouped; if( current != 255 ) { std::vector::iterator it; for( it = m_groups[current].begin(); it < m_groups[current].end(); it++ ) { if( (*it) == entity ) { m_groups[current].erase( it ); break; } } } if( groupid != 255 ) m_groups[groupid].push_back( entity ); entity->m_grouped = groupid; } bool CSelectedEntities::isSelected( CEntity* entity ) { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) return( true ); } return( false ); } void CSelectedEntities::highlightGroup( u8 groupid ) { if( m_group_highlight != 255 ) return; if( !getGroupCount( groupid ) ) return; m_group_highlight = groupid; pushCameraTarget( getGroupPosition( groupid ) ); } void CSelectedEntities::highlightNone() { if( m_group_highlight != 255 ) popCameraTarget(); m_group_highlight = 255; } int CSelectedEntities::getGroupCount( u8 groupid ) { return( (int)m_groups[groupid].size() ); } CVector3D CSelectedEntities::getGroupPosition( u8 groupid ) { CVector3D avg; std::vector::iterator it; for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) { avg += (*it)->m_graphics_position; avg.X += (*it)->m_bounds->m_offset.x; avg.Z += (*it)->m_bounds->m_offset.y; } return( avg * ( 1.0f / m_groups[groupid].size() ) ); } void CSelectedEntities::update() { if( !isContextValid( m_contextOrder ) ) { // This order isn't valid for the current selection and/or target. for( int t = 0; t < CEntityOrder::ORDER_LAST; t++ ) if( isContextValid( t ) ) { m_contextOrder = t; return; } m_contextOrder = -1; } if( ( m_group_highlight != 255 ) && getGroupCount( m_group_highlight ) ) setCameraTarget( getGroupPosition( m_group_highlight ) ); } +void CSelectedEntities::setContext( int contextOrder ) +{ + assert( isContextValid( contextOrder ) ); + m_contextOrder = contextOrder; +} + bool CSelectedEntities::nextContext() { // No valid orders? if( m_contextOrder == -1 ) return( false ); int t = m_contextOrder + 1; while( t != m_contextOrder ) { if( t == CEntityOrder::ORDER_LAST ) t = 0; if( isContextValid( t ) ) break; t++; } if( m_contextOrder == t ) return( false ); m_contextOrder = t; return( true ); - - } bool CSelectedEntities::previousContext() { // No valid orders? if( m_contextOrder == -1 ) return( false ); int t = m_contextOrder - 1; while( t != m_contextOrder ) { if( isContextValid( t ) ) break; if( t == 0 ) t = CEntityOrder::ORDER_LAST; t--; } if( m_contextOrder == t ) return( false ); m_contextOrder = t; return( true ); } bool CSelectedEntities::isContextValid( int contextOrder ) { if( contextOrder == -1 ) return( false ); // Check to see if any member of the selection supports this order type. std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) if( (*it)->acceptsOrder( contextOrder, g_Mouseover.m_target ) ) return( true ); return( false ); } void CSelectedEntities::contextOrder( bool pushQueue ) { std::vector::iterator it; CEntityOrder context; (int&)context.m_type = m_contextOrder; CVector3D origin, dir; g_Camera.BuildCameraRay( origin, dir ); switch( m_contextOrder ) { case CEntityOrder::ORDER_GOTO: case CEntityOrder::ORDER_PATROL: { CHFTracer maptracer( g_Terrain.GetHeightMap(), g_Terrain.GetVerticesPerSide(), (float)CELL_SIZE, HEIGHT_SCALE ); int x, y; CVector3D ipt; maptracer.RayIntersect( origin, dir, x, y, ipt ); context.m_data[0].location.x = ipt.X; context.m_data[0].location.y = ipt.Z; } break; default: break; } for( it = m_selected.begin(); it < m_selected.end(); it++ ) if( (*it)->acceptsOrder( m_contextOrder, g_Mouseover.m_target ) ) g_Scheduler.pushFrame( ORDER_DELAY, (*it)->me, new CMessageOrder( context, pushQueue ) ); } void CMouseoverEntities::update( float timestep ) { CVector3D origin, dir; g_Camera.BuildCameraRay( origin, dir ); CUnit* hit = g_UnitMan.PickUnit( origin, dir ); m_target = NULL; if( hit ) m_target = hit->GetEntity(); if( m_viewall ) { // 'O' key. Show selection outlines for all player units on screen // (as if bandboxing them all). // These aren't selectable; clicking when 'O' is pressed won't select // all units on screen. m_mouseover.clear(); std::vector* onscreen = g_EntityManager.matches( isOnScreen ); std::vector::iterator it; for( it = onscreen->begin(); it < onscreen->end(); it++ ) m_mouseover.push_back( SMouseoverFader( &(**it), m_fademaximum, false ) ); delete( onscreen ); } else if( m_bandbox ) { m_x2 = mouse_x; m_y2 = mouse_y; // Here's the fun bit: // Get the screen-space coordinates of all onscreen entities // then find the ones falling within the box. // // Fade in the ones in the box at (in+out) speed, then fade everything // out at (out) speed. std::vector* onscreen = g_EntityManager.matches( isOnScreen ); std::vector::iterator it; // Reset active flags on everything... std::vector::iterator it2; for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) it2->isActive = false; for( it = onscreen->begin(); it < onscreen->end(); it++ ) { CVector3D worldspace = (*it)->m_graphics_position; worldspace.X += (*it)->m_bounds->m_offset.x; worldspace.Z += (*it)->m_bounds->m_offset.y; float x, y; g_Camera.GetScreenCoordinates( worldspace, x, y ); bool inBox; if( m_x1 < m_x2 ) { inBox = ( x >= m_x1 ) && ( x < m_x2 ); } else { inBox = ( x >= m_x2 ) && ( x < m_x1 ); } if( m_y1 < m_y2 ) { inBox &= ( y >= m_y1 ) && ( y < m_y2 ); } else { inBox &= ( y >= m_y2 ) && ( y < m_y1 ); } if( inBox ) { bool found = false; for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) if( it2->entity == &(**it) ) { found = true; it2->fade += ( m_fadeinrate + m_fadeoutrate ) * timestep; it2->isActive = true; } if( !found ) m_mouseover.push_back( SMouseoverFader( &(**it), ( m_fadeinrate + m_fadeoutrate ) * timestep ) ); } } delete( onscreen ); for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); ) { it2->fade -= m_fadeoutrate * timestep; if( it2->fade > m_fademaximum ) it2->fade = m_fademaximum; if( it2->fade < 0.0f ) { it2 = m_mouseover.erase( it2 ); } else it2++; } } else { std::vector::iterator it; bool found = false; for( it = m_mouseover.begin(); it < m_mouseover.end(); ) { if( it->entity == m_target ) { found = true; it->fade += m_fadeinrate * timestep; if( it->fade > m_fademaximum ) it->fade = m_fademaximum; it->isActive = true; it++; continue; } else { it->fade -= m_fadeoutrate * timestep; if( it->fade <= 0.0f ) { it = m_mouseover.erase( it ); continue; } it++; continue; } } if( !found && m_target ) { float initial = m_fadeinrate * timestep; if( initial > m_fademaximum ) initial = m_fademaximum; m_mouseover.push_back( SMouseoverFader( m_target, initial ) ); } } } void CMouseoverEntities::addSelection() { std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) if( it->isActive && !g_Selection.isSelected( it->entity ) ) g_Selection.addSelection( it->entity ); } void CMouseoverEntities::removeSelection() { std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) if( it->isActive && g_Selection.isSelected( it->entity ) ) g_Selection.removeSelection( it->entity ); } void CMouseoverEntities::setSelection() { g_Selection.clearSelection(); addSelection(); } void CMouseoverEntities::expandAcrossScreen() { std::vector* activeset = g_EntityManager.matches( isMouseoverType, isOnScreen ); m_mouseover.clear(); std::vector::iterator it; for( it = activeset->begin(); it < activeset->end(); it++ ) { m_mouseover.push_back( SMouseoverFader( &(**it) ) ); } delete( activeset ); } void CMouseoverEntities::expandAcrossWorld() { std::vector* activeset = g_EntityManager.matches( isMouseoverType ); m_mouseover.clear(); std::vector::iterator it; for( it = activeset->begin(); it < activeset->end(); it++ ) { m_mouseover.push_back( SMouseoverFader( &(**it) ) ); } delete( activeset ); } void CMouseoverEntities::renderSelectionOutlines() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->renderSelectionOutline( it->fade ); glDisable( GL_BLEND ); } void CMouseoverEntities::renderOverlays() { glLoadIdentity(); glDisable( GL_TEXTURE_2D ); if( m_bandbox ) { //glPushMatrix(); glColor3f( 1.0f, 1.0f, 1.0f ); glBegin( GL_LINE_LOOP ); glVertex2i( m_x1, g_Renderer.GetHeight() - m_y1 ); glVertex2i( m_x2, g_Renderer.GetHeight() - m_y1 ); glVertex2i( m_x2, g_Renderer.GetHeight() - m_y2 ); glVertex2i( m_x1, g_Renderer.GetHeight() - m_y2 ); glEnd(); //glPopMatrix(); } glEnable( GL_TEXTURE_2D ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) { if( it->entity->m_grouped != 255 ) { assert( it->entity->m_bounds ); glPushMatrix(); glEnable( GL_TEXTURE_2D ); glLoadIdentity(); float x, y; CVector3D labelpos = it->entity->m_graphics_position - g_Camera.m_Orientation.GetLeft() * it->entity->m_bounds->m_radius; labelpos.X += it->entity->m_bounds->m_offset.x; labelpos.Z += it->entity->m_bounds->m_offset.y; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = it->entity->getExactGroundLevel( labelpos.X, labelpos.Z ); #endif g_Camera.GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, it->fade ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", it->entity->m_grouped ); glDisable( GL_TEXTURE_2D ); glPopMatrix(); } } } void CMouseoverEntities::startBandbox( u16 x, u16 y ) { m_bandbox = true; m_x1 = x; m_y1 = y; } void CMouseoverEntities::stopBandbox() { m_bandbox = false; } int interactInputHandler( const SDL_Event* ev ) { static float lastclicktime = 0.0f; static CEntity* lastclickobject = NULL; static u8 clicks = 0; static u16 button_down_x, button_down_y; static bool button_down = false; switch( ev->type ) { case SDL_KEYDOWN: if( ( ev->key.keysym.sym >= SDLK_0 ) && ( ev->key.keysym.sym <= SDLK_9 ) ) { - u8 groupid = ev->key.keysym.sym - SDLK_0; - if( hotkeys[HOTKEY_SELECTION_GROUP_ADD] ) - { - g_Selection.addGroup( groupid ); - } - else if( hotkeys[HOTKEY_SELECTION_GROUP_SAVE] ) + u8 id = ev->key.keysym.sym - SDLK_0; + // This competes with the camera bookmarks for the top-row number keys + // Find out which this is, and act accordingly + if( !hotkeys[HOTKEY_CAMERA_BOOKMARK_MODIFIER] ) { - g_Selection.saveGroup( groupid ); - } - else if( hotkeys[HOTKEY_SELECTION_GROUP_SNAP] ) - { - g_Selection.highlightGroup( groupid ); + if( hotkeys[HOTKEY_SELECTION_GROUP_ADD] ) + { + g_Selection.addGroup( id ); + } + else if( hotkeys[HOTKEY_SELECTION_GROUP_SAVE] ) + { + g_Selection.saveGroup( id ); + } + else if( hotkeys[HOTKEY_SELECTION_GROUP_SNAP] ) + { + g_Selection.highlightGroup( id ); + } + else + { + if( ( g_Selection.m_group == id ) && g_Selection.getGroupCount( id ) ) + { + setCameraTarget( g_Selection.getGroupPosition( id ) ); + } + else + g_Selection.loadGroup( id ); + } } else { - if( ( g_Selection.m_group == groupid ) && g_Selection.getGroupCount( groupid ) ) + if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SAVE] ) { - setCameraTarget( g_Selection.getGroupPosition( groupid ) ); + cameraBookmarks[id] = g_Camera.m_Orientation.GetTranslation() + g_Camera.m_Orientation.GetIn() * 160.0f; + bookmarkInUse[id] = true; + } + else if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SNAP] ) + { + if( bookmarkInUse[id] && ( currentBookmark == 255 ) ) + { + pushCameraTarget( cameraBookmarks[id] ); + currentBookmark = id; + } } else - g_Selection.loadGroup( groupid ); + { + if( bookmarkInUse[id] ) + setCameraTarget( cameraBookmarks[id] ); + } } } break; case SDL_HOTKEYDOWN: switch( ev->user.code ) { case HOTKEY_HIGHLIGHTALL: g_Mouseover.m_viewall = true; break; case HOTKEY_SELECTION_SNAP: if( g_Selection.m_selected.size() ) setCameraTarget( g_Selection.getSelectionPosition() ); break; case HOTKEY_CONTEXTORDER_NEXT: g_Selection.nextContext(); break; case HOTKEY_CONTEXTORDER_PREVIOUS: g_Selection.previousContext(); break; default: return( EV_PASS ); } return( EV_HANDLED ); case SDL_HOTKEYUP: switch( ev->user.code ) { case HOTKEY_SELECTION_GROUP_SNAP: if( g_Selection.m_group_highlight != 255 ) g_Selection.highlightNone(); break; + case HOTKEY_CAMERA_BOOKMARK_SNAP: + if( currentBookmark != 255 ) + popCameraTarget(); + currentBookmark = 255; + break; case HOTKEY_HIGHLIGHTALL: g_Mouseover.m_viewall = false; break; } return( EV_HANDLED ); case SDL_MOUSEBUTTONUP: switch( ev->button.button ) { case SDL_BUTTON_LEFT: if( g_Mouseover.m_viewall ) break; float time; time = (float)get_time(); // Reset clicks counter if too slow or if the cursor's // hovering over something else now. if( time - lastclicktime >= SELECT_DBLCLICK_RATE ) clicks = 0; if( g_Mouseover.m_target != lastclickobject ) clicks = 0; clicks++; if( clicks == 2 ) { // Double click g_Mouseover.expandAcrossScreen(); } else if( clicks == 3 ) { // Triple click g_Mouseover.expandAcrossWorld(); } lastclicktime = time; lastclickobject = g_Mouseover.m_target; button_down = false; g_Mouseover.stopBandbox(); if( hotkeys[HOTKEY_SELECTION_ADD] ) { g_Mouseover.addSelection(); } else if( hotkeys[HOTKEY_SELECTION_REMOVE] ) { g_Mouseover.removeSelection(); } else g_Mouseover.setSelection(); break; case SDL_BUTTON_RIGHT: - g_Selection.contextOrder( keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT] ); + g_Selection.contextOrder( hotkeys[HOTKEY_ORDER_QUEUE] ); break; } break; case SDL_MOUSEBUTTONDOWN: switch( ev->button.button ) { case SDL_BUTTON_LEFT: button_down = true; button_down_x = ev->button.x; button_down_y = ev->button.y; break; } break; case SDL_MOUSEMOTION: if( !g_Mouseover.isBandbox() && button_down ) { int deltax = ev->motion.x - button_down_x; int deltay = ev->motion.y - button_down_y; if( ABS( deltax ) > 2 || ABS( deltay ) > 2 ) g_Mouseover.startBandbox( button_down_x, button_down_y ); } break; } return( EV_PASS ); } bool isOnScreen( CEntity* ev ) { CFrustum frustum = g_Camera.GetFustum(); return( frustum.IsBoxVisible( CVector3D(), ev->m_actor->GetModel()->GetBounds() ) ); } bool isMouseoverType( CEntity* ev ) { std::vector::iterator it; for( it = g_Mouseover.m_mouseover.begin(); it < g_Mouseover.m_mouseover.end(); it++ ) { if( it->isActive && ( (CBaseEntity*)it->entity->m_base == (CBaseEntity*)ev->m_base ) ) return( true ); } return( false ); } void pushCameraTarget( const CVector3D& target ) { cameraTargets.push_back( g_Camera.m_Orientation.GetTranslation() ); setCameraTarget( target ); } void setCameraTarget( const CVector3D& target ) { CVector3D cameraForward = g_Camera.m_Orientation.GetIn(); cameraDelta = ( target - ( cameraForward * 160.0f ) ) - g_Camera.m_Orientation.GetTranslation(); } void popCameraTarget() { cameraDelta = cameraTargets.back() - g_Camera.m_Orientation.GetTranslation(); cameraTargets.pop_back(); } \ No newline at end of file Index: ps/trunk/source/simulation/EntityManager.cpp =================================================================== --- ps/trunk/source/simulation/EntityManager.cpp (revision 803) +++ ps/trunk/source/simulation/EntityManager.cpp (revision 804) @@ -1,112 +1,122 @@ #include "precompiled.h" #include "EntityManager.h" #include "BaseEntityCollection.h" +#include "ConfigDB.h" +int SELECTION_CIRCLE_POINTS; +int SELECTION_BOX_POINTS; +int SELECTION_SMOOTHNESS_UNIFIED; CEntityManager::CEntityManager() { m_nextalloc = 0; m_extant = true; + // Also load a couple of global entity settings + CConfigValue* cfg = g_ConfigDB.GetValue( CFG_SYSTEM, "selection.outline.quality" ); + if( cfg ) cfg->GetInt( SELECTION_SMOOTHNESS_UNIFIED ); + if( SELECTION_SMOOTHNESS_UNIFIED < 0 ) SELECTION_SMOOTHNESS_UNIFIED = 0; + SELECTION_CIRCLE_POINTS = 7 + 2 * SELECTION_SMOOTHNESS_UNIFIED; + SELECTION_BOX_POINTS = 1 + SELECTION_SMOOTHNESS_UNIFIED; } CEntityManager::~CEntityManager() { m_extant = false; for( int i = 0; i < MAX_HANDLES; i++ ) if( m_entities[i].m_refcount ) delete( m_entities[i].m_entity ); } HEntity CEntityManager::create( CBaseEntity* base, CVector3D position, float orientation ) { assert( base ); while( m_entities[m_nextalloc].m_refcount ) m_nextalloc++; m_entities[m_nextalloc].m_entity = new CEntity( base, position, orientation ); m_entities[m_nextalloc].m_entity->me = HEntity( m_nextalloc ); return( HEntity( m_nextalloc++ ) ); } HEntity CEntityManager::create( CStr templatename, CVector3D position, float orientation ) { CBaseEntity* templateobj = g_EntityTemplateCollection.getTemplate( templatename ); return( create( templateobj, position, orientation ) ); } HEntity* CEntityManager::getByHandle( u16 index ) { if( index >= MAX_HANDLES ) return( NULL ); if( !m_entities[index].m_refcount ) return( NULL ); return( new HEntity( index ) ); } std::vector* CEntityManager::matches( EntityPredicate predicate ) { std::vector* matchlist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) if( predicate( m_entities[i].m_entity ) ) matchlist->push_back( HEntity( i ) ); return( matchlist ); } std::vector* CEntityManager::matches( EntityPredicate predicate1, EntityPredicate predicate2 ) { std::vector* matchlist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) if( predicate1( m_entities[i].m_entity ) && predicate2( m_entities[i].m_entity ) ) matchlist->push_back( HEntity( i ) ); return( matchlist ); } std::vector* CEntityManager::getExtant() { std::vector* activelist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) 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::updateAll( float timestep ) { std::vector::iterator it; for( it = m_reaper.begin(); it < m_reaper.end(); it++ ) delete( *it ); m_reaper.clear(); 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->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 ) 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 ) m_entities[i].m_entity->render(); } void CEntityManager::destroy( u16 handle ) { m_reaper.push_back( m_entities[handle].m_entity ); m_entities[handle].m_entity->me.m_handle = INVALID_HANDLE; //delete( m_entities[handle].m_entity ); // PT: Don't delete here, because the reaper will kill it later } bool CEntityManager::m_extant = false; Index: ps/trunk/source/simulation/EntityStateProcessing.cpp =================================================================== --- ps/trunk/source/simulation/EntityStateProcessing.cpp (revision 803) +++ ps/trunk/source/simulation/EntityStateProcessing.cpp (revision 804) @@ -1,179 +1,181 @@ // Entity state-machine processing code. #include "precompiled.h" #include "Entity.h" #include "Model.h" #include "Collision.h" #include "PathfindEngine.h" bool CEntity::processGotoNoPathing( CEntityOrder* current, float timestep ) { CVector2D delta; delta.x = (float)current->m_data[0].location.x - m_position.X; delta.y = (float)current->m_data[0].location.y - m_position.Z; float len = delta.length(); // ... 'Are we there yet?' ... if( len < 0.1f ) { if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) { repath(); } else m_orderQueue.pop_front(); return( false ); } // Curve smoothing. // Here there be trig. if( current->m_type != CEntityOrder::ORDER_GOTO_SMOOTHED ) { // We can only really attempt to smooth paths the pathfinder // has flagged for us. If the turning-radius calculations are // applied to other types of waypoint, wierdness happens. // Things like an entity trying to walk to a point inside // his turning radius (which he can't do directly, so he'll // orbit the point indefinately), or just massive deviations // making the paths we calculate useless. // It's also painful trying to watch two entities resolve their // collision when they're both bound by turning constraints. m_ahead = delta / len; m_orientation = atan2( m_ahead.x, m_ahead.y ); } else { m_targetorientation = atan2( delta.x, delta.y ); float deltatheta = m_targetorientation - (float)m_orientation; while( deltatheta > PI ) deltatheta -= 2 * PI; while( deltatheta < -PI ) deltatheta += 2 * PI; if( fabs( deltatheta ) > 0.01f ) { float maxTurningSpeed = ( m_speed / m_turningRadius ) * timestep; if( deltatheta > 0 ) { m_orientation = m_orientation + MIN( deltatheta, maxTurningSpeed ); } else m_orientation = m_orientation + MAX( deltatheta, -maxTurningSpeed ); m_ahead.x = sin( m_orientation ); m_ahead.y = cos( m_orientation ); } else { m_ahead = delta / len; m_orientation = atan2( m_ahead.x, m_ahead.y ); } } if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) ((CBoundingBox*)m_bounds)->setOrientation( m_ahead ); - float scale = m_speed * timestep; if( scale > len ) scale = len; delta = m_ahead * scale; m_position.X += delta.x; m_position.Z += delta.y; m_bounds->setPosition( m_position.X, m_position.Z ); HEntity collide = getCollisionObject( this ); if( collide ) { // Hit something. Is it our destination? if( collide->m_bounds->contains( current->m_data[0].location ) ) { m_orderQueue.pop_front(); return( false ); } // No? Take a step back. m_position.X -= delta.x; m_position.Z -= delta.y; m_bounds->setPosition( m_position.X, m_position.Z ); // Are we still hitting it? if( collide->m_bounds->intersects( m_bounds ) ) { // Oh dear. Most likely explanation is that this unit was created // within the bounding area of another entity. // Try a little boost of speed, to help resolve the situation more quickly. + + // This really shouldn't happen in the current build. + m_position.X += delta.x * 2.0f; m_position.Z += delta.y * 2.0f; m_bounds->setPosition( m_position.X, m_position.Z ); return( false ); } // No? 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; 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 ); } 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 ); } return( false ); } bool CEntity::processGoto( CEntityOrder* current, float timestep ) { CVector2D pos( m_position.X, m_position.Z ); CVector2D path_to = current->m_data[0].location; m_orderQueue.pop_front(); if( ( path_to - pos ).length() < 0.1f ) return( false ); if( m_actor->GetModel()->GetAnimation() != m_actor->GetObject()->m_WalkAnim ) { m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim ); m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f ); } g_Pathfinder.requestPath( me, path_to ); return( true ); } bool CEntity::processPatrol( CEntityOrder* current, float timestep ) { CEntityOrder this_segment; CEntityOrder repeat_patrol; this_segment.m_type = CEntityOrder::ORDER_GOTO; this_segment.m_data[0] = current->m_data[0]; repeat_patrol.m_type = CEntityOrder::ORDER_PATROL; repeat_patrol.m_data[0] = current->m_data[0]; m_orderQueue.pop_front(); m_orderQueue.push_front( this_segment ); m_orderQueue.push_back( repeat_patrol ); return( true ); } Index: ps/trunk/source/simulation/Entity.h =================================================================== --- ps/trunk/source/simulation/Entity.h (revision 803) +++ ps/trunk/source/simulation/Entity.h (revision 804) @@ -1,123 +1,134 @@ // Entity.h // // Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Entity class. // // Usage: Do not attempt to instantiate this class directly. (See EntityManager.h) // Most of the members are trivially obvious; some highlights are: // // HEntity me: is a reference to this entity. Use instead of the address-of operator for // non-temporary references. See EntityHandles.h // // Destroying entities: An entity is destroyed when all references to it expire. // It is somewhat unfunny if this happens while a method from this // class is still executing. If you need to kill an entity, // use CEntity::kill(). If kill() releases the final handle, // the entity is placed in the reaper queue and is deleted immediately // prior to its next update cycle. // // CUnit* m_actor: is the visible representation of this entity. // std::hash_map m_properties: isn't yet used, is capable of storing properties defined by script. // // snapToGround(): Called every frame, this will ensure the entity never takes flight. // updateActorTransforms(): Must be called every time the position of this entity changes. // Also remember to update the collision object if you alter the position directly. // // Some notes: update() and dispatch() /can/ be called directly without ill effects, // but it's preferable to go through the Entity manager and the Scheduler, respectively. // #ifndef ENTITY_INCLUDED #define ENTITY_INCLUDED #include #include "EntityProperties.h" #include "BaseEntity.h" #include "Vector2D.h" #include "BoundingObjects.h" #include "Vector3D.h" #include "Unit.h" #include "UnitManager.h" #include "EntityOrders.h" #include "EntityHandles.h" #include "EntityMessage.h" class CEntityManager; class CEntity : public IPropertyOwner { friend class CEntityManager; public: // Intrinsic properties CProperty_CStr m_name; CProperty_float m_speed; CProperty_float m_turningRadius; CProperty_bool m_selected; bool m_extant; // Don't want JS to have direct write-access to these. (Things that should be done might not be) CProperty_bool m_extant_mirror; // plus this way limits the number of nasty semantics to work around. u8 m_grouped; CProperty_i32 m_grouped_mirror; //-- Interpolated property CVector3D m_position; CVector3D m_position_previous; CProperty_CVector3D m_graphics_position; CBoundingObject* m_bounds; float m_targetorientation; CVector2D m_ahead; //-- Interpolated property float m_orientation; float m_orientation_previous; CProperty_float m_graphics_orientation; CUnit* m_actor; std::deque m_orderQueue; private: CEntity( CBaseEntity* base, CVector3D position, float orientation ); bool processGotoNoPathing( CEntityOrder* current, float timestep ); bool processGoto( CEntityOrder* current, float timestep ); bool processPatrol( CEntityOrder* current, float timestep ); public: ~CEntity(); // Handle-to-self. HEntity me; void dispatch( const CMessage* msg ); void update( float timestep ); void kill(); void interpolate( float relativeoffset ); float getExactGroundLevel( float x, float y ); void snapToGround(); void updateActorTransforms(); void render(); void renderSelectionOutline( float alpha = 1.0f ); void repath(); void loadBase(); 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 checkGroup(); // Groups void checkExtant(); // Existance bool acceptsOrder( int orderType, CEntity* orderTarget ); void clearOrders(); void pushOrder( CEntityOrder& order ); }; +// General entity globals + + +// In it's current incarnation, inefficient but pretty +#define SELECTION_TERRAIN_CONFORMANCE + +extern int SELECTION_CIRCLE_POINTS; +extern int SELECTION_BOX_POINTS; +extern int SELECTION_SMOOTHNESS_UNIFIED; + + #endif Index: ps/trunk/source/simulation/scripting/JSInterface_Entity.cpp =================================================================== --- ps/trunk/source/simulation/scripting/JSInterface_Entity.cpp (revision 803) +++ ps/trunk/source/simulation/scripting/JSInterface_Entity.cpp (revision 804) @@ -1,147 +1,142 @@ #include "precompiled.h" #include "JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" #include "EntityHandles.h" #include "Entity.h" #include "EntityManager.h" #include "BaseEntityCollection.h" #include "CConsole.h" JSClass JSI_Entity::JSI_class = { "Entity", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JSI_Entity::getProperty, JSI_Entity::setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JSI_Entity::finalize, NULL, NULL, NULL, NULL }; JSPropertySpec JSI_Entity::JSI_props[] = { { 0 } }; JSFunctionSpec JSI_Entity::JSI_methods[] = { { "toString", JSI_Entity::toString, 0, 0, 0 }, { 0 } }; JSBool JSI_Entity::getProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { HEntity* e = (HEntity*)JS_GetPrivate( cx, obj ); if( !e ) { *vp = JSVAL_NULL; - JS_ReportError( cx, "[Entity] Invalid reference" ); return( JS_TRUE ); } CStr propName = g_ScriptingHost.ValueToString( id ); if( (*e)->m_properties.find( propName ) != (*e)->m_properties.end() ) { *vp = (*e)->m_properties[propName]->tojsval(); return( JS_TRUE ); } else JS_ReportError( cx, "No such property on %s: %s", (const char*)((*e)->m_name), (const char*)propName ); return( JS_TRUE ); } JSBool JSI_Entity::setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { HEntity* e = (HEntity*)JS_GetPrivate( cx, obj ); CStr propName = g_ScriptingHost.ValueToString( id ); if( (*e)->m_properties.find( propName ) != (*e)->m_properties.end() ) { *((*e)->m_properties[propName]) = *vp; (*e)->rebuild( propName ); return( JS_TRUE ); } else JS_ReportError( cx, "No such property on %s: %s", (const char*)((*e)->m_name), (const char*)propName ); return( JS_TRUE ); } JSBool JSI_Entity::construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { assert( argc >= 2 ); - CBaseEntity* baseEntity; + CBaseEntity* baseEntity = NULL; CVector3D position; float orientation = 0.0f; JSObject* jsBaseEntity = JSVAL_TO_OBJECT( argv[0] ); CStr templateName; - if( !JSVAL_IS_NULL( argv[0] ) && JSVAL_IS_OBJECT( argv[0] ) && ( JS_GetClass( jsBaseEntity ) == &JSI_BaseEntity::JSI_class ) ) - { - baseEntity = (CBaseEntity*)JS_GetPrivate( cx, jsBaseEntity ); - } - else + if( !JSVAL_IS_OBJECT( argv[0] ) || !( baseEntity = (CBaseEntity*)JS_GetInstancePrivate( cx, jsBaseEntity, &JSI_BaseEntity::JSI_class, NULL ) ) ) { try { templateName = g_ScriptingHost.ValueToString( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; JS_ReportError( cx, "Invalid template identifier" ); return( JS_TRUE ); } baseEntity = g_EntityTemplateCollection.getTemplate( templateName ); } if( !baseEntity ) { *rval = JSVAL_NULL; JS_ReportError( cx, "No such template: %s", (const char*)templateName ); return( JS_TRUE ); } - JSObject* jsVector3D = JSVAL_TO_OBJECT( argv[1] ); - if( !JSVAL_IS_NULL( argv[1] ) && JSVAL_IS_OBJECT( argv[1] ) && ( JS_GetClass( jsVector3D ) == &JSI_Vector3D::JSI_class ) ) + JSI_Vector3D::Vector3D_Info* jsVector3D = NULL; + if( JSVAL_IS_OBJECT( argv[1] ) && ( jsVector3D = (JSI_Vector3D::Vector3D_Info*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( argv[1] ), &JSI_Vector3D::JSI_class, NULL ) ) ) { - position = *( ( (JSI_Vector3D::Vector3D_Info*)JS_GetPrivate( cx, jsVector3D ) )->vector ); + position = *( jsVector3D->vector ); } if( argc >= 3 ) { try { orientation = (float)g_ScriptingHost.ValueToDouble( argv[2] ); } catch( PSERROR_Scripting_ConversionFailed ) { orientation = 0.0f; } } HEntity* handle = new HEntity( g_EntityManager.create( baseEntity, position, orientation ) ); CMessage message( CMessage::EMSG_INIT ); (*handle)->dispatch( &message ); JSObject* entity = JS_NewObject( cx, &JSI_Entity::JSI_class, NULL, NULL ); JS_SetPrivate( cx, entity, handle ); *rval = OBJECT_TO_JSVAL( entity ); return( JS_TRUE ); } void JSI_Entity::finalize( JSContext* cx, JSObject* obj ) { delete( (HEntity*)JS_GetPrivate( cx, obj ) ); } void JSI_Entity::init() { g_ScriptingHost.DefineCustomObjectType( &JSI_class, construct, 2, JSI_props, JSI_methods, NULL, NULL ); } JSBool JSI_Entity::toString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) { HEntity* e = (HEntity*)JS_GetPrivate( cx, obj ); char buffer[256]; snprintf( buffer, 256, "[object Entity: \"%s\" (%s)]", (const TCHAR*)(*e)->m_name, (const TCHAR*)(*e)->m_base->m_name ); buffer[255] = 0; *rval = STRING_TO_JSVAL( JS_NewStringCopyZ( cx, buffer ) ); return( JS_TRUE ); } Index: ps/trunk/source/simulation/EntityProperties.cpp =================================================================== --- ps/trunk/source/simulation/EntityProperties.cpp (revision 803) +++ ps/trunk/source/simulation/EntityProperties.cpp (revision 804) @@ -1,434 +1,436 @@ #include "precompiled.h" #include "EntityProperties.h" #include "BaseEntityCollection.h" #include "scripting/JSInterface_BaseEntity.h" #undef new // to avoid confusing warnings void CProperty::associate( IPropertyOwner* owner, const CStr& name ) { m_owner = owner; owner->m_properties[name] = this; m_updateFn = NULL; } void CProperty::associate( IPropertyOwner* owner, const CStr& name, void (IPropertyOwner::*updateFn)() ) { m_owner = owner; owner->m_properties[name] = this; m_updateFn = updateFn; } CProperty& CProperty::operator=( jsval value ) { set( value ); return( *this ); } CProperty_i32::CProperty_i32() { modifier = NULL; } CProperty_i32::~CProperty_i32() { if( modifier ) delete( modifier ); } CProperty_i32& CProperty_i32::operator =( i32 value ) { if( !modifier ) modifier = new SProperty_NumericModifier(); *modifier = (float)value; data = value; return( *this ); } void CProperty_i32::set( jsval value ) { if( !modifier ) modifier = new SProperty_NumericModifier(); try { *modifier = (float)g_ScriptingHost.ValueToInt( value ); } catch( PSERROR_Scripting_ConversionFailed ) { *modifier = 0; } } bool CProperty_i32::rebuild( CProperty* parent, bool triggerFn ) { CProperty_i32* _parent = (CProperty_i32*)parent; i32 newvalue = 0; if( _parent ) newvalue = *_parent; if( modifier ) { newvalue = (i32)(newvalue * modifier->multiplicative); newvalue += (i32)modifier->additive; } if( data == newvalue ) return( false ); // No change. data = newvalue; if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); return( true ); } inline CProperty_i32::operator i32() { return( data ); } jsval CProperty_i32::tojsval() { return( INT_TO_JSVAL( data ) ); } CProperty_bool::CProperty_bool() { modifier = NULL; } CProperty_bool::~CProperty_bool() { if( modifier ) delete( modifier ); } CProperty_bool& CProperty_bool::operator=( const bool value ) { if( !modifier ) modifier = new SProperty_BooleanModifier(); *modifier = value; data = value; return( *this ); } void CProperty_bool::set( jsval value ) { if( !modifier ) modifier = new SProperty_BooleanModifier(); try { *modifier = g_ScriptingHost.ValueToBool( value ); } catch( ... ) { *modifier = false; } } bool CProperty_bool::rebuild( CProperty* parent, bool triggerFn ) { CProperty_bool* _parent = (CProperty_bool*)parent; bool newvalue = false; if( _parent ) newvalue = *_parent; if( modifier ) newvalue = modifier->replacement; if( data == newvalue ) return( false ); // No change. data = newvalue; if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); return( true ); } inline CProperty_bool::operator bool() { return( data ); } jsval CProperty_bool::tojsval() { return( BOOLEAN_TO_JSVAL( data ) ); } CProperty_float::CProperty_float() { modifier = NULL; } CProperty_float::~CProperty_float() { if( modifier ) delete modifier; } CProperty_float& CProperty_float::operator =( const float& value ) { if( !modifier ) modifier = new SProperty_NumericModifier(); *modifier = value; data = value; return( *this ); } void CProperty_float::set( const jsval value ) { if( !modifier ) modifier = new SProperty_NumericModifier(); try { *modifier = (float)g_ScriptingHost.ValueToDouble( value ); } catch( PSERROR_Scripting_ConversionFailed ) { *modifier = 0.0f; } } bool CProperty_float::rebuild( CProperty* parent, bool triggerFn ) { CProperty_float* _parent = (CProperty_float*)parent; float newvalue = 0; if( _parent ) newvalue = *_parent; if( modifier ) { newvalue *= modifier->multiplicative; newvalue += modifier->additive; } if( data == newvalue ) return( false ); // No change. data = newvalue; if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); return( true ); } CProperty_float::operator float() { return( data ); } jsval CProperty_float::tojsval() { return( DOUBLE_TO_JSVAL( JS_NewDouble( g_ScriptingHost.getContext(), (jsdouble)data ) ) ); } CProperty_float::operator bool() { return( data != 0.0f); } float CProperty_float::operator+( float value ) { return( data + value ); } float CProperty_float::operator-( float value ) { return( data - value ); } float CProperty_float::operator*( float value ) { return( data * value ); } float CProperty_float::operator/( float value ) { return( data / value ); } bool CProperty_float::operator<( float value ) { return( data < value ); } bool CProperty_float::operator>( float value ) { return( data > value ); } bool CProperty_float::operator==( float value ) { return( data == value ); } CProperty_CStr::CProperty_CStr() { modifier = NULL; } CProperty_CStr::~CProperty_CStr() { if( modifier ) delete( modifier ); } CProperty_CStr& CProperty_CStr::operator=( const CStr& value ) { if( !modifier ) modifier = new SProperty_StringModifier(); *modifier = value; m_String = value; return( *this ); } void CProperty_CStr::set( jsval value ) { if( !modifier ) modifier = new SProperty_StringModifier(); try { *modifier = g_ScriptingHost.ValueToString( value ); } catch( PSERROR_Scripting_ConversionFailed ) { *modifier = CStr(); m_String.clear(); } } bool CProperty_CStr::rebuild( CProperty* parent, bool triggerFn ) { CProperty_CStr* _parent = (CProperty_CStr*)parent; CStr newvalue = ""; if( _parent ) newvalue = *_parent; if( modifier ) newvalue = modifier->replacement; if( *this == newvalue ) return( false ); // No change. m_String = newvalue; if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); return( true ); } jsval CProperty_CStr::tojsval() { return( STRING_TO_JSVAL( JS_NewStringCopyZ( g_ScriptingHost.getContext(), m_String.c_str() ) ) ); } CProperty_CVector3D& CProperty_CVector3D::operator =( const CVector3D& value ) { *( (CVector3D*)this ) = value; return( *this ); } void CProperty_CVector3D::set( jsval value ) { JSObject* vector3d = JSVAL_TO_OBJECT( value ); - if( JSVAL_IS_NULL( value ) || !JSVAL_IS_OBJECT( value ) || ( JS_GetClass( vector3d ) != &JSI_Vector3D::JSI_class ) ) + JSI_Vector3D::Vector3D_Info* v = NULL; + if( JSVAL_IS_OBJECT( value ) && ( v = (JSI_Vector3D::Vector3D_Info*)JS_GetInstancePrivate( g_ScriptingHost.getContext(), vector3d, &JSI_Vector3D::JSI_class, NULL ) ) ) { - X = 0.0f; Y = 0.0f; Z = 0.0f; - } - else - { - CVector3D* copy = ( (JSI_Vector3D::Vector3D_Info*)JS_GetPrivate( g_ScriptingHost.getContext(), vector3d ) )->vector; + CVector3D* copy = v->vector; X = copy->X; Y = copy->Y; Z = copy->Z; } + else + { + X = 0.0f; Y = 0.0f; Z = 0.0f; + } } bool CProperty_CVector3D::rebuild( CProperty* parent, bool triggerFn ) { if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); return( false ); // Vector properties aren't inheritable. } jsval CProperty_CVector3D::tojsval() { JSObject* vector3d = JS_NewObject( g_ScriptingHost.getContext(), &JSI_Vector3D::JSI_class, NULL, NULL ); JS_SetPrivate( g_ScriptingHost.getContext(), vector3d, new JSI_Vector3D::Vector3D_Info( this, m_owner, m_updateFn ) ); return( OBJECT_TO_JSVAL( vector3d ) ); } CProperty_CBaseEntityPtr& CProperty_CBaseEntityPtr::operator =( CBaseEntity* value ) { data = value; return( *this ); } void CProperty_CBaseEntityPtr::set( jsval value ) { JSObject* baseEntity = JSVAL_TO_OBJECT( value ); - if( !JSVAL_IS_NULL( value ) && JSVAL_IS_OBJECT( value ) && ( JS_GetClass( baseEntity ) == &JSI_BaseEntity::JSI_class ) ) + CBaseEntity* base = NULL; + if( JSVAL_IS_OBJECT( value ) && ( base = (CBaseEntity*)JS_GetInstancePrivate( g_ScriptingHost.getContext(), baseEntity, &JSI_BaseEntity::JSI_class, NULL ) ) ) { - data = (CBaseEntity*)JS_GetPrivate( g_ScriptingHost.getContext(), baseEntity ); + data = base; } else JS_ReportError( g_ScriptingHost.getContext(), "[BaseEntity] Invalid reference" ); } bool CProperty_CBaseEntityPtr::rebuild( CProperty* parent, bool triggerFn ) { if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); return( false ); // CBaseEntity* properties aren't inheritable. } jsval CProperty_CBaseEntityPtr::tojsval() { JSObject* baseEntity = JS_NewObject( g_ScriptingHost.getContext(), &JSI_BaseEntity::JSI_class, NULL, NULL ); JS_SetPrivate( g_ScriptingHost.getContext(), baseEntity, data ); return( OBJECT_TO_JSVAL( baseEntity ) ); } CProperty_CBaseEntityPtr::operator bool() { return( data != NULL ); } CProperty_CBaseEntityPtr::operator CBaseEntity*() { return( data ); } CBaseEntity& CProperty_CBaseEntityPtr::operator *() const { return( *data ); } CBaseEntity* CProperty_CBaseEntityPtr::operator ->() const { return( data ); } void IPropertyOwner::rebuild( CStr propertyName ) { CProperty* thisProperty = m_properties[propertyName]; CProperty* baseProperty = NULL; if( m_base ) { if( m_base->m_properties.find( propertyName ) != m_base->m_properties.end() ) baseProperty = m_base->m_properties[propertyName]; } if( thisProperty->rebuild( baseProperty ) ) { std::vector::iterator it; for( it = m_inheritors.begin(); it != m_inheritors.end(); it++ ) (*it)->rebuild( propertyName ); } } void IPropertyOwner::rebuild() { STL_HASH_MAP::iterator property; if( m_base ) { for( property = m_properties.begin(); property != m_properties.end(); property++ ) { CProperty* baseProperty = NULL; if( m_base->m_properties.find( property->first ) != m_base->m_properties.end() ) baseProperty = m_base->m_properties[property->first]; (property->second)->rebuild( baseProperty, false ); } } else { for( property = m_properties.begin(); property != m_properties.end(); property++ ) (property->second)->rebuild( NULL, false ); } std::vector::iterator it; for( it = m_inheritors.begin(); it != m_inheritors.end(); it++ ) (*it)->rebuild(); } Index: ps/trunk/source/simulation/Collision.cpp =================================================================== --- ps/trunk/source/simulation/Collision.cpp (revision 803) +++ ps/trunk/source/simulation/Collision.cpp (revision 804) @@ -1,99 +1,99 @@ #include "precompiled.h" #include "Collision.h" #include "EntityManager.h" CBoundingObject* getContainingObject( const CVector2D& point ) { std::vector* entities = g_EntityManager.getExtant(); std::vector::iterator it; for( it = entities->begin(); it != entities->end(); it++ ) { assert( (*it)->m_bounds ); if( (*it)->m_bounds->contains( point ) ) { CBoundingObject* bounds = (*it)->m_bounds; delete( entities ); return( bounds ); } } delete( entities ); return( NULL ); } HEntity getCollisionObject( CEntity* entity ) { assert( entity->m_bounds ); std::vector* entities = g_EntityManager.getExtant(); std::vector::iterator it; for( it = entities->begin(); it != entities->end(); it++ ) { assert( (*it)->m_bounds ); if( (*it)->m_bounds == entity->m_bounds ) continue; if( entity->m_bounds->intersects( (*it)->m_bounds ) ) { HEntity collisionObject = *it; delete( entities ); return( collisionObject ); } } delete( entities ); return HEntity(); } HEntity getCollisionObject( CEntity* entity, float x, float y ) { float _x = entity->m_bounds->m_pos.x; float _y = entity->m_bounds->m_pos.y; entity->m_bounds->setPosition( x, y ); HEntity _e = getCollisionObject( entity ); entity->m_bounds->setPosition( _x, _y ); return( _e ); } bool getRayIntersection( const CVector2D& source, const CVector2D& forward, const CVector2D& right, float length, float maxDistance, CBoundingObject* destinationCollisionObject, rayIntersectionResults* results ) { std::vector* entities = g_EntityManager.getExtant(); std::vector::iterator it; float closestApproach, dist; CVector2D delta; results->distance = length + maxDistance; results->boundingObject = NULL; for( it = entities->begin(); it != entities->end(); it++ ) { assert( (*it)->m_bounds ); if( (*it)->m_bounds == destinationCollisionObject ) continue; // HACK: - if( (*it)->m_bounds->m_type == CBoundingObject::BOUND_OABB ) continue; + // if( (*it)->m_bounds->m_type == CBoundingObject::BOUND_OABB ) continue; if( (*it)->m_speed ) continue; CBoundingObject* obj = (*it)->m_bounds; delta = obj->m_pos - source; closestApproach = delta.dot( right ); dist = delta.dot( forward ); float collisionRadius = maxDistance + obj->m_radius; if( ( fabs( closestApproach ) < collisionRadius ) && ( dist > collisionRadius * 0.0f ) && ( dist < length - collisionRadius * 0.0f ) ) { if( dist < results->distance ) { results->boundingObject = obj; results->closestApproach = closestApproach; results->distance = dist; results->hEntity = (*it); results->position = obj->m_pos; } } } delete( entities ); if( results->boundingObject ) return( true ); return( false ); } Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 803) +++ ps/trunk/source/gui/CGUI.cpp (revision 804) @@ -1,1639 +1,1671 @@ /* CGUI by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CText.h" #include "CCheckBox.h" #include "CRadioButton.h" #include "ps/Xeromyces.h" #include "Prometheus.h" #include "input.h" #include "OverlayText.h" // TODO Gee: Whatever include CRect/CPos/CSize #include "Overlay.h" #include "scripting/ScriptingHost.h" +#include "Hotkey.h" #include #include #include // namespaces used using namespace std; #include "ps/CLogger.h" #define XERO_TIME // Class for global JavaScript object JSClass GUIClass = { "GUIClass", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, }; extern int g_xres, g_yres; // TODO Gee: how to draw overlays? void render(COverlayText* overlaytext) { } //------------------------------------------------------------------- // called from main loop when (input) events are received. // event is passed to other handlers if false is returned. // trampoline: we don't want to make the implementation (in CGUI) static //------------------------------------------------------------------- int gui_handler(const SDL_Event* ev) { return g_GUI.HandleEvent(ev); } int CGUI::HandleEvent(const SDL_Event* ev) { + // MT: If something's gone wrong, check this block... (added for hotkey support) + + if( ev->type == SDL_GUIHOTKEYPRESS ) + { + const CStr& objectName = *( (CStr*)ev->user.code ); + IGUIObject* object = FindObjectByName( objectName ); + object->HandleMessage( SGUIMessage( GUIM_PRESSED ) ); + object->ScriptEvent( "press" ); + } + + // -- MT + if(ev->type == SDL_MOUSEMOTION) { m_MousePos = CPos(ev->motion.x, ev->motion.y); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_MOUSE_MOTION)); } // TODO Gee: temp-stuff // char buf[30]; // sprintf(buf, "type = %d", ev->type); //TEMPmessage = buf; // Update m_MouseButtons. (BUTTONUP is handled later.) if (ev->type == SDL_MOUSEBUTTONDOWN) { // (0,1,2) = (LMB,RMB,MMB) if (ev->button.button < 3) m_MouseButtons |= (1 << ev->button.button); } // JW: (pre|post)process omitted; what're they for? why would we need any special button_released handling? // Only one object can be hovered IGUIObject *pNearest = NULL; try { // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); if (ev->type == SDL_MOUSEMOTION && pNearest) pNearest->ScriptEvent("mousemove"); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly + GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); if (ev->type == SDL_MOUSEBUTTONDOWN) { switch (ev->button.button) { case SDL_BUTTON_LEFT: if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT)); pNearest->ScriptEvent("mouseleftpress"); } break; case SDL_BUTTON_WHEELDOWN: // wheel down if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN)); pNearest->ScriptEvent("mousewheeldown"); } break; case SDL_BUTTON_WHEELUP: // wheel up if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP)); pNearest->ScriptEvent("mousewheelup"); } break; default: break; } } else if (ev->type == SDL_MOUSEBUTTONUP) { if (ev->button.button == SDL_BUTTON_LEFT) { if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_LEFT)); pNearest->ScriptEvent("mouseleftrelease"); } } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // It will have reset the mouse over of the current hovered, so we'll // have to restore that if (pNearest) pNearest->m_MouseHovering = true; } } catch (PS_RESULT e) { UNUSED(e); // TODO Gee: Handle } // JW: what's the difference between mPress and mDown? what's the code below responsible for? /* // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } */ // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->type == SDL_MOUSEBUTTONUP) { // (0,1,2) = (LMB,RMB,MMB) if (ev->button.button < 3) m_MouseButtons &= ~(1 << ev->button.button); } return EV_PASS; } //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CGUI::CGUI() : m_InternalNameNumber(0), m_MouseButtons(0) { m_BaseObject = new CGUIDummyObject; m_BaseObject->SetGUI(this); // Construct the parent object for all GUI JavaScript things m_ScriptObject = (void*)JS_NewObject(g_ScriptingHost.getContext(), &GUIClass, NULL, NULL); assert(m_ScriptObject != NULL); // How should it handle errors? JS_AddRoot(g_ScriptingHost.getContext(), &m_ScriptObject); // This will make this invisible, not add //m_BaseObject->SetName(BASE_OBJECT_NAME); } CGUI::~CGUI() { if (m_BaseObject) delete m_BaseObject; if (m_ScriptObject) // Let it be garbage-collected JS_RemoveRoot(g_ScriptingHost.getContext(), &m_ScriptObject); } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- IGUIObject *CGUI::ConstructObject(const CStr& str) { if (m_ObjectTypes.count(str) > 0) return (*m_ObjectTypes[str])(); else { // TODO Gee: Report in log return NULL; } } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Prometheus though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); } void CGUI::Process() { /* // TODO Gee: check if m_pInput is valid, otherwise return /// assert(m_pInput); // Pre-process all objects try { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_PREPROCESS); } catch (PS_RESULT e) { return; } // Check mouse over try { // Only one object can be hovered // check which one it is, if any ! IGUIObject *pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); // If pressed if (m_pInput->mPress(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_PRESS_LEFT); } else // If released if (m_pInput->mRelease(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_RELEASE_LEFT); } // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } } catch (PS_RESULT e) { return; } // Post-process all objects try { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_POSTPROCESS); } catch (PS_RESULT e) { return; } */ } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); glPushMatrix(); glLoadIdentity(); // Adapt (origio) to being in top left corner and down // just like the mouse position glTranslatef(0.0f, (GLfloat)g_yres, -1000.0f); glScalef(1.0f, -1.f, 1.0f); try { // Recurse IGUIObject::Draw() with restriction: hidden // meaning all hidden objects won't call Draw (nor will it recurse its children) GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw); } catch (PS_RESULT e) { UNUSED(e); glPopMatrix(); // TODO Gee: Report error. return; } glPopMatrix(); } void CGUI::DrawSprite(const CStr& SpriteName, const float &Z, const CRect &Rect, const CRect &Clipping) { // This is not an error, it's just a choice not to draw any sprite. if (SpriteName == CStr()) return; bool DoClipping = (Clipping != CRect()); CGUISprite Sprite; // Fetch real sprite from name if (m_Sprites.count(SpriteName) == 0) { // TODO Gee: Report error return; } else Sprite = m_Sprites[SpriteName]; glPushMatrix(); glTranslatef(0.0f, 0.0f, Z); // Iterate all images and request them being drawn be the // CRenderer std::vector::const_iterator cit; for (cit=Sprite.m_Images.begin(); cit!=Sprite.m_Images.end(); ++cit) { if (cit->m_Texture) { // TODO: Handle the GL state in a nicer way glEnable(GL_TEXTURE_2D); glDisable(GL_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); int fmt; tex_info(cit->m_Texture, NULL, NULL, &fmt, NULL, NULL); if (fmt == GL_RGBA || fmt == GL_BGRA) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); } else { glDisable(GL_BLEND); } tex_bind(cit->m_Texture); CRect real = cit->m_Size.GetClientArea(Rect); // Get the screen position/size of a single tiling of the texture CRect TexSize = cit->m_TextureSize.GetClientArea(real); float TexLeft = (float)(TexSize.left - real.left) / (float)TexSize.GetWidth(); float TexRight = TexLeft + (float)real.GetWidth() / (float)TexSize.GetWidth(); // 'Bottom' is actually the top in screen-space (I think), // because the GUI puts (0,0) at the top-left float TexBottom = (float)(TexSize.bottom - real.bottom) / (float)TexSize.GetHeight(); float TexTop = TexBottom + (float)real.GetHeight() / (float)TexSize.GetHeight(); glBegin(GL_QUADS); glTexCoord2f(TexRight, TexBottom); glVertex3f((float)real.right, (float)real.bottom, cit->m_DeltaZ); glTexCoord2f(TexLeft, TexBottom); glVertex3f((float)real.left, (float)real.bottom, cit->m_DeltaZ); glTexCoord2f(TexLeft, TexTop); glVertex3f((float)real.left, (float)real.top, cit->m_DeltaZ); glTexCoord2f(TexRight, TexTop); glVertex3f((float)real.right, (float)real.top, cit->m_DeltaZ); glEnd(); glDisable(GL_TEXTURE_2D); } else { glColor3f(cit->m_BackColor.r, cit->m_BackColor.g, cit->m_BackColor.b); CRect real = cit->m_Size.GetClientArea(Rect); glBegin(GL_QUADS); glVertex3f((float)real.right, (float)real.bottom, cit->m_DeltaZ); glVertex3f((float)real.left, (float)real.bottom, cit->m_DeltaZ); glVertex3f((float)real.left, (float)real.top, cit->m_DeltaZ); glVertex3f((float)real.right, (float)real.top, cit->m_DeltaZ); glEnd(); } } glPopMatrix(); } void CGUI::Destroy() { // We can use the map to delete all // now we don't want to cancel all if one Destroy fails map_pObjects::iterator it; for (it = m_pAllObjects.begin(); it != m_pAllObjects.end(); ++it) { try { it->second->Destroy(); } catch (PS_RESULT e) { UNUSED(e); // TODO Gee: Handle } delete it->second; } // Clear all m_pAllObjects.clear(); m_Sprites.clear(); } void CGUI::UpdateResolution() { // Update ALL cached GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize ); } void CGUI::AddObject(IGUIObject* pObject) { try { // Add CGUI pointer GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this); // Add child to base object m_BaseObject->AddChild(pObject); // Cache tree GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); // Loaded GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_LOAD)); GUI::RecurseObject(0, pObject, &IGUIObject::ScriptEvent, "load"); } catch (PS_RESULT e) { throw e; } } void CGUI::UpdateObjects() { // We'll fill a temporary map until we know everything // succeeded map_pObjects AllObjects; try { // Fill freshly GUI< map_pObjects >::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects ); } catch (PS_RESULT e) { // Throw the same error throw e; } // Else actually update the real one m_pAllObjects = AllObjects; } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.count(Name) != 0; } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return NULL; else return it->second; } // private struct used only in GenerateText(...) struct SGenerateTextImage { int m_YFrom, // The images starting location in Y m_YTo, // The images end location in Y m_Indentation; // The image width in other words // Some help functions // TODO Gee: CRect => CPoint ? void SetupSpriteCall(const bool &Left, SGUIText::SSpriteCall &SpriteCall, const int &width, const int &y, const CSize &Size, const CStr& TextureName, const int &BufferZone) { // TODO Gee: Temp hardcoded values SpriteCall.m_Area.top = y+BufferZone; SpriteCall.m_Area.bottom = y+BufferZone + Size.cy; if (Left) { SpriteCall.m_Area.left = BufferZone; SpriteCall.m_Area.right = Size.cx+BufferZone; } else { SpriteCall.m_Area.left = width-BufferZone - Size.cx; SpriteCall.m_Area.right = width-BufferZone; } SpriteCall.m_TextureName = TextureName; m_YFrom = SpriteCall.m_Area.top-BufferZone; m_YTo = SpriteCall.m_Area.bottom+BufferZone; m_Indentation = Size.cx+BufferZone*2; } }; SGUIText CGUI::GenerateText(const CGUIString &string, /*const CColor &Color, */ const CStr& Font, const int &Width, const int &BufferZone) { SGUIText Text; // object we're generating if (string.m_Words.size() == 0) return Text; int x=BufferZone, y=BufferZone; // drawing pointer int from=0; bool done=false; // Images on the left or the right side. vector Images[2]; int pos_last_img=-1; // Position in the string where last img (either left or right) were encountered. // in order to avoid duplicate processing. // Easier to read. bool WordWrapping = (Width != 0); // Go through string word by word for (int i=0; i<(int)string.m_Words.size()-1 && !done; ++i) { // Pre-process each line one time, so we know which floating images // will be added for that line. // Generated stuff is stored in Feedback. CGUIString::SFeedback Feedback; // Preliminary line_height, used for word-wrapping with floating images. int prelim_line_height=0; // Width and height of all text calls generated. string.GenerateTextCall(Feedback, Font, /*CColor(),*/ string.m_Words[i], string.m_Words[i+1]); // Loop through our images queues, to see if images has been added. // Check if this has already been processed. // Also, floating images are only applicable if Word-Wrapping is on if (WordWrapping && i > pos_last_img) { // Loop left/right for (int j=0; j<2; ++j) { for (vector::const_iterator it = Feedback.m_Images[j].begin(); it != Feedback.m_Images[j].end(); ++it) { SGUIText::SSpriteCall SpriteCall; SGenerateTextImage Image; // Y is if no other floating images is above, y. Else it is placed // after the last image, like a stack downwards. int _y; if (Images[j].size() > 0) _y = max(y, Images[j].back().m_YTo); else _y = y; // TODO Gee: CSize temp CSize size; size.cx = 100; size.cy = 100; Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, CStr("white-border"), BufferZone); // Check if image is the lowest thing. Text.m_Size.cy = max(Text.m_Size.cy, Image.m_YTo); Images[j].push_back(Image); Text.m_SpriteCalls.push_back(SpriteCall); } } } pos_last_img = max(pos_last_img, i); x += Feedback.m_Size.cx; prelim_line_height = max(prelim_line_height, Feedback.m_Size.cy); // If Width is 0, then there's no word-wrapping, disable NewLine. if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == string.m_Words.size()-2) { // Change from to i, but first keep a copy of its value. int temp_from = from; from = i; static const int From=0, To=1; //int width_from=0, width_to=width; int width_range[2]; width_range[From] = BufferZone; width_range[To] = Width - BufferZone; // Floating images are only appicable if word-wrapping is enabled. if (WordWrapping) { // Decide width of the line. We need to iterate our floating images. // this won't be exact because we're assuming the line_height // will be as our preliminary calculation said. But that may change, // although we'd have to add a couple of more loops to try straightening // this problem out, and it is very unlikely to happen noticably if one // stuctures his text in a stylistically pure fashion. Even if not, it // is still quite unlikely it will happen. // Loop through left and right side, from and to. for (int j=0; j<2; ++j) { for (vector::const_iterator it = Images[j].begin(); it != Images[j].end(); ++it) { // We're working with two intervals here, the image's and the line height's. // let's find the union of these two. int union_from, union_to; union_from = max(y, it->m_YFrom); union_to = min(y+prelim_line_height, it->m_YTo); // The union is not ø if (union_to > union_from) { if (j == From) width_range[From] = max(width_range[From], it->m_Indentation); else width_range[To] = min(width_range[To], Width - it->m_Indentation); } } } } // Reset X for the next loop x = width_range[From]; // Now we'll do another loop to figure out the height of // the line (the height of the largest character). This // couldn't be determined in the first loop (main loop) // because it didn't regard images, so we don't know // if all characters processed, will actually be involved // in that line. int line_height=0; for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another. CGUIString::SFeedback Feedback2; string.GenerateTextCall(Feedback2, Font, /*CColor(),*/ string.m_Words[j], string.m_Words[j+1]); // Append X value. x += Feedback2.m_Size.cx; if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine) break; // Let line_height be the maximum m_Height we encounter. line_height = max(line_height, Feedback2.m_Size.cy); if (WordWrapping && Feedback2.m_NewLine) break; } // Reset x once more x = width_range[From]; // Move down, because font drawing starts from the baseline y += line_height; // Do the real processing now for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another. CGUIString::SFeedback Feedback2; // Defaults string.GenerateTextCall(Feedback2, Font, /*Color, */ string.m_Words[j], string.m_Words[j+1]); // Iterate all and set X/Y values // Since X values are not set, we need to make an internal // iteration with an increment that will append the internal // x, that is what x_pointer is for. int x_pointer=0; vector::iterator it; for (it = Feedback2.m_TextCalls.begin(); it != Feedback2.m_TextCalls.end(); ++it) { it->m_Pos = CPos(x + x_pointer, y + line_height - it->m_Size.cy); x_pointer += it->m_Size.cx; if (it->m_pSpriteCall) { it->m_pSpriteCall->m_Area = it->m_pSpriteCall->m_Area + it->m_Pos; } } // Append X value. x += Feedback2.m_Size.cx; Text.m_Size.cx = max(Text.m_Size.cx, x+BufferZone); // The first word overrides the width limit, that we // do in those cases, are just draw that word even // though it'll extend the object. if (WordWrapping) // only if word-wrapping is applicable { if (Feedback2.m_NewLine) { from = j+1; break; } else if (x > width_range[To] && j==temp_from) { from = j+1; // do not break, since we want it to be added to m_TextCalls } else if (x > width_range[To]) { from = j; break; } } // Add the whole Feedback2.m_TextCalls to our m_TextCalls. Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end()); Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); if (j == string.m_Words.size()-2) done = true; } // Reset X x = 0; // Update height of all Text.m_Size.cy = max(Text.m_Size.cy, y+BufferZone); // Now if we entered as from = i, then we want // i being one minus that, so that it will become // the same i in the next loop. The difference is that // we're on a new line now. i = from-1; } } return Text; } void CGUI::DrawText(const SGUIText &Text, const CColor &DefaultColor, const CPos &pos, const float &z) { glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); Handle font = 0; CStr LastFontName; for (vector::const_iterator it = Text.m_TextCalls.begin(); it != Text.m_TextCalls.end(); ++it) { if (it->m_pSpriteCall) continue; // Switch fonts when necessary, but remember the last one used if (it->m_Font != LastFontName) { if (font) unifont_unload(font); font = unifont_load(it->m_Font); unifont_bind(font); LastFontName = it->m_Font; } CColor color = it->m_UseCustomColor ? it->m_Color : DefaultColor; glPushMatrix(); glTranslatef((float)pos.x+it->m_Pos.x, (float)pos.y+it->m_Pos.y, (float)z); glColor4f(color.r, color.g, color.b, color.a); glwprintf(L"%hs", it->m_String.c_str()); glPopMatrix(); } if (font) unifont_unload(font); for (vector::const_iterator it=Text.m_SpriteCalls.begin(); it!=Text.m_SpriteCalls.end(); ++it) { DrawSprite(it->m_TextureName, z, it->m_Area + pos); } } void CGUI::ReportParseError(const CStr& str, ...) { // Print header if (m_Errors==0) { /// g_nemLog("*** GUI Tree Creation Errors"); } // Important, set ParseError to true ++m_Errors; /* TODO Gee: (MEGA) char buffer[512]; va_list args; // get arguments va_start(args, str); vsprintf(buffer, str.c_str(), args); va_end(args); */ /// g_nemLog(" %s", buffer); } /** * @callgraph */ void CGUI::LoadXMLFile(const string &Filename) { // Reset parse error // we can later check if this has increased m_Errors = 0; CXeromyces XeroFile; try { XeroFile.Load(Filename.c_str()); } catch (PSERROR_Xeromyces) { // Fail silently return; } XMBElement node = XeroFile.getRoot(); // Check root element's (node) name so we know what kind of // data we'll be expecting std::string root_name = XeroFile.getElementString(node.getNodeName()); try { if (root_name == "objects") { Xeromyces_ReadRootObjects(node, &XeroFile); // Re-cache all values so these gets cached too. //UpdateResolution(); } else if (root_name == "sprites") { Xeromyces_ReadRootSprites(node, &XeroFile); } else if (root_name == "styles") { Xeromyces_ReadRootStyles(node, &XeroFile); } else if (root_name == "setup") { Xeromyces_ReadRootSetup(node, &XeroFile); } else { // TODO Gee: Output in log } } catch (PSERROR_GUI) { LOG(ERROR, "Errors loading GUI file %s", Filename.c_str()); return; } // Now report if any other errors occured if (m_Errors > 0) { /// g_console.submit("echo GUI Tree Creation Reports %d errors", m_Errors); } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile) { int el_script = pFile->getElementID("script"); // Iterate main children // they should all be or