//----------------------------------------------------------------------------- // V12 Engine // // Copyright (c) 2001 GarageGames.Com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include "loki_utils.h" #include "sdl_utils.h" #include "engine/platform/platformInput.h" #include "engine/platformLinux/platformLinux.h" #include "engine/platform/platform.h" #include "engine/platformWIN32/platformGL.h" #include "engine/platformWIN32/platformAL.h" #include "engine/platform/platformVideo.h" #include "engine/platformLinux/linuxOGLVideo.h" #include "engine/platformLinux/linuxConsole.h" #include "engine/platform/event.h" #include "engine/console/console.h" #include "engine/math/mPoint.h" #include "engine/platform/gameInterface.h" #include "game/src/t2Version.h" #include "engine/core/fileio.h" #include "engine/math/mRandom.h" // random-ness static MRandomLCG sgPlatRandom; // platform-specific state LinuxPlatformState linuxState; // windowing state static bool windowActive = false; static bool windowLocked = false; static Point2I windowSize; // timing static U32 lastTimeTick = 0; // uber-cheesy modifier key var static U32 modifierKeys = 0; #ifdef DEDICATED static bool shouldQuit = false; #endif void Platform::AlertOK( const char* title, const char* message ) { Con::warnf( ConsoleLogEntry::General, message ); } bool Platform::AlertOKCancel( const char* title, const char* message ) { #ifdef DEDICATED char response[32]; fprintf( stderr, "tribes2: %s\n", message ); while( 1 ) { fputs( "OK? > ", stderr ); fgets( response, 32, stdin ); if( strncasecmp( response, "yes", 3 ) == 0 ) { return true; } else if( strncasecmp( response, "no", 2 ) == 0 ) { return false; } else { fputs( "Please answer \"yes\" or \"no\"\n", stderr ); } } #else Con::warnf( ConsoleLogEntry::General, message ); return false; #endif } bool Platform::AlertRetry( const char* title, const char* message ) { #ifdef DEDICATED char response[32]; fprintf( stderr, "tribes2: %s\n", message ); while( 1 ) { fputs( "Retry? > ", stderr ); fgets( response, 32, stdin ); if( strncasecmp( response, "yes", 3 ) == 0 ) { return true; } else if( strncasecmp( response, "no", 2 ) == 0 ) { return false; } else { fputs( "Please answer \"yes\" or \"no\"\n", stderr ); } } #else Con::warnf( ConsoleLogEntry::General, message ); return false; #endif } static void setMouseClipping( void ) { #ifndef DEDICATED if( windowActive ) { SDL_ShowCursor( 0 ); if( windowLocked ) { SDL_WM_GrabInput( SDL_GRAB_ON ); } else { SDL_WM_GrabInput( SDL_GRAB_OFF ); } } else { SDL_ShowCursor( 1 ); } #endif } static void InitInput( void ) { windowActive = true; setMouseClipping( ); } //-------------------------------------- void Platform::enableKeyboardTranslation(void) { #ifndef DEDICATED SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); SDL_EnableUNICODE( 1 ); #endif } //-------------------------------------- void Platform::disableKeyboardTranslation(void) { #ifndef DEDICATED SDL_EnableKeyRepeat(0, 0); SDL_EnableUNICODE( 0 ); #endif } void Platform::setWindowLocked( bool locked ) { windowLocked = locked; setMouseClipping( ); } void Platform::minimizeWindow() { #ifndef DEDICATED SDL_WM_IconifyWindow( ); #endif } #ifdef DEDICATED void linuxPostQuitMessage( void ) { shouldQuit = true; } #endif static U32 translateMods( SDLMod mods ) { if( mods == KMOD_NONE ) { return 0; } S32 out = 0; if( mods & KMOD_LSHIFT ) { out |= SI_LSHIFT; } if( mods & KMOD_RSHIFT ) { out |= SI_RSHIFT; } if( mods & KMOD_LCTRL ) { out |= SI_LCTRL; } if( mods & KMOD_RCTRL ) { out |= SI_RCTRL; } if( mods & KMOD_LALT ) { out |= SI_LALT; } if( mods & KMOD_RALT ) { out |= SI_RALT; } return out; } static void processKeyMessage( SDL_keysym* keysym, bool down ) { InputEvent event; S32 key = translateSDLToKeyCode( keysym->sym ); #if (defined DEBUG) && !(defined DEDICATED) if( key == KEY_NULL ) { fprintf( stderr, "Keyboard weirdness: %d / %d / %d / %d / %s\n", keysym->scancode, keysym->sym, keysym->mod, keysym->unicode, SDL_GetKeyName( keysym->sym ) ); } #endif U32 mods = translateMods( keysym->mod ); modifierKeys = mods; event.deviceInst = 0; event.deviceType = KeyboardDeviceType; event.objType = SI_KEY; event.objInst = key; event.action = down ? SI_MAKE : SI_BREAK; event.modifier = mods; event.ascii = keysym->unicode; event.fValue = down ? 1.0 : 0.0; Game->postEvent( event ); } static void processDeltaMessage( int x, int y ) { InputEvent event; float xp = static_cast( x ); float yp = static_cast( y ); event.deviceType = MouseDeviceType; // always 0 for mouse, only one mouse event.deviceInst = 0; // always 0 for mouse, only one axis of given type event.objInst = 0; event.modifier = modifierKeys; event.action = SI_MOVE; event.objType = SI_XAXIS; event.fValue = xp; Game->postEvent( event ); event.objType = SI_YAXIS; event.fValue = yp; Game->postEvent( event ); } KeyCodes translateMouseButton( int button ) { KeyCodes key; switch( button ) { case SDL_BUTTON_LEFT: key = KEY_BUTTON0; break; case SDL_BUTTON_MIDDLE: key = KEY_BUTTON2; break; case SDL_BUTTON_RIGHT: key = KEY_BUTTON1; break; case 4: case 5: /* This should never happen - Buttons 4 and 5 are interpreted as mouse wheel */ default: key = KEY_BUTTON0 + button - 1; } return key; } static void processMouseMessage( int button, int state ) { InputEvent event; event.deviceInst = 0; event.deviceType = MouseDeviceType; event.objType = SI_BUTTON; event.objInst = translateMouseButton( button ); event.action = state ? SI_MAKE : SI_BREAK; event.modifier = modifierKeys; event.ascii = 0; event.fValue = ( event.action == SI_MAKE ) ? 1.0 : 0.0; Game->postEvent( event ); } static void processWheelMessage( int button, int state ) { if( !state ) { // we don't want button "down" // events for the wheel return; } InputEvent event; F32 delta = 0.0f; if( button == 4 ) { delta = 5.0f; } else { delta = -5.0f; } event.deviceInst = 0; event.deviceType = MouseDeviceType; event.objType = SI_ZAXIS; event.objInst = 0; event.action = SI_MOVE; event.modifier = modifierKeys; event.ascii = 0; event.fValue = delta; Game->postEvent( event ); } static JoystickCodes translatePOV( U8 hat ) { JoystickCodes array[] = { SI_XPOV, SI_YPOV, SI_UPOV, SI_DPOV, SI_LPOV, SI_RPOV }; hat = ( hat < 6 ) ? hat : 5; return array[hat]; } static bool findHatDelta( U8 oldHat, U8 newHat, JoystickCodes* hat, U8* action ) { bool wasUp = ( oldHat & SDL_HAT_UP ); bool isUp = ( newHat & SDL_HAT_UP ); bool wasDown = ( oldHat & SDL_HAT_DOWN ); bool isDown = ( newHat & SDL_HAT_DOWN ); bool wasLeft = ( oldHat & SDL_HAT_LEFT ); bool isLeft = ( newHat & SDL_HAT_LEFT ); bool wasRight = ( oldHat & SDL_HAT_RIGHT ); bool isRight = ( newHat & SDL_HAT_RIGHT ); if( hat == 0 || action == 0 ) { return false; } *action = SI_BREAK; if( wasUp != isUp ) { *hat = SI_UPOV; if( isUp ) { *action = SI_MAKE; } return true; } if( wasDown != isDown ) { *hat = SI_DPOV; if( isDown ) { *action = SI_MAKE; } return true; } if( wasLeft != isLeft ) { *hat = SI_LPOV; if( isLeft ) { *action = SI_MAKE; } return true; } if( wasRight != isRight ) { *hat = SI_RPOV; if( isRight ) { *action = SI_MAKE; } return true; } return false; } static void processJoyHatMessage( U8 hat, U8 newValue ) { InputEvent event; U8 oldValue = getHatState( hat ); JoystickCodes pov; U8 action; // this is a little more complicated than I'd like // because SDL doesn't report hat events very well. // it just says "hey, the hat changed", and you have // to manage the on/off-ness of it. // also, I'm assuming I'll only see one delta per // packet, which Sam says is an okay assumption // for now. event.deviceInst = 0; event.deviceType = JoystickDeviceType; // which hat event.objInst = hat; if( findHatDelta( oldValue, newValue, &pov, &action ) == false ) { return; } setHatState( hat, newValue ); // set to SI_RPOV, SI_LPOV, etc. event.objType = pov; // set to SI_MAKE, SI_BREAK event.action = action; event.modifier = modifierKeys; event.ascii = 0; event.fValue = ( event.action == SI_MAKE ) ? 1.0 : 0.0; Game->postEvent( event ); } static KeyCodes translateJoyButton( int button ) { if( button < 32 ) { return static_cast( KEY_BUTTON0 + button ); } else { return KEY_NULL; } } static void processJoyButtonMessage( int button, int state ) { InputEvent event; event.deviceInst = 0; event.deviceType = JoystickDeviceType; event.objType = SI_BUTTON; event.objInst = translateJoyButton( button ); event.action = state ? SI_MAKE : SI_BREAK; event.modifier = modifierKeys; event.ascii = 0; event.fValue = ( event.action == SI_MAKE ) ? 1.0 : 0.0; Game->postEvent( event ); } static bool processMessages( void ) { #ifdef DEDICATED return ( shouldQuit == false ); #else SDL_Event event; bool cont = true; while( SDL_PollEvent( &event ) ) { switch( event.type ) { case SDL_ACTIVEEVENT: if ( event.active.state & SDL_APPACTIVE ) { windowActive = event.active.gain; if( windowActive ) { Video::reactivate( ); Con::evaluate( "resetCanvas();" ); Input::activate( ); } else { Input::deactivate( ); Video::deactivate( ); } setMouseClipping( ); } break; case SDL_VIDEOEXPOSE: Con::evaluate( "resetCanvas();" ); break; case SDL_KEYDOWN: processKeyMessage( &event.key.keysym, true ); break; case SDL_KEYUP: processKeyMessage( &event.key.keysym, false ); break; case SDL_MOUSEMOTION: if( windowLocked ) { processDeltaMessage( event.motion.xrel, event.motion.yrel ); } else { MouseMoveEvent move; move.xPos = event.motion.x; move.yPos = event.motion.y; move.modifier = modifierKeys; Game->postEvent( move ); } break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if( event.button.button == 4 || event.button.button == 5 ) { processWheelMessage( event.button.button, event.button.state ); } else { processMouseMessage( event.button.button, event.button.state ); } break; case SDL_JOYAXISMOTION: // we ignore this because it needs to be reported // continusously, but this is only discrete // see Input::process() break; case SDL_JOYHATMOTION: processJoyHatMessage( event.jhat.hat, event.jhat.value ); break; case SDL_JOYBUTTONDOWN: processJoyButtonMessage( event.jbutton.button, true ); break; case SDL_JOYBUTTONUP: processJoyButtonMessage( event.jbutton.button, false ); break; case SDL_JOYBALLMOTION: processDeltaMessage( event.jball.xrel, event.jball.yrel ); break; case SDL_QUIT: cont = false; break; } } return cont; #endif } void Platform::process( void ) { linuxConsole->process( ); if( !processMessages( ) ) { Event quitEvent; quitEvent.type = QuitEventType; Game->postEvent( quitEvent ); } #ifdef DEDICATED usleep( 1000 ); #else Input::process( ); #endif } // FIXME: I'm not sure exactly what this is supposed to do. // Does the CD have to be in the drive at all times? Just for the server? // The Win32 code looks like it's looking for a stub executable of some kind. // eh? :) For now, return true... bool Platform::doCDCheck() { return true; } void GetDesktopState( void ) { #ifndef DEDICATED if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { Con::warnf( ConsoleLogEntry::General, "SDL video init failed..." ); return; } const SDL_VideoInfo* info = SDL_GetVideoInfo( ); if( info == 0 ) { return; } SDL_PixelFormat* format = info->vfmt; int bpp = format->BitsPerPixel; int width = 0; int height = 0; if( sdl_GetScreenSize( &width, &height ) == 0 ) { return; } linuxState.bpp = bpp; linuxState.width = width; linuxState.height = height; #endif } const Point2I& Platform::getWindowSize( void ) { return windowSize; } void Platform::setWindowSize( U32 w, U32 h ) { windowSize.set( w, h ); } static void InitWindow( const Point2I& initialSize ) { windowSize = initialSize; } static void InitOpenGL( void ) { #ifdef DEDICATED Con::printf( "Ignoring OpenGL initialization." ); #else DisplayDevice::init( ); int width = 640; int height = 480; int bpp = 16; bool fullScreen = true; const char* resolution = Con::getVariable( "$pref::Video::resolution" ); if( resolution[0] != '\0' ) { sscanf( resolution, "%d %d %d", &width, &height, &bpp ); } // default to fullscreen fullScreen = Con::getBoolVariable( "$pref::Video::fullScreen", true ); // we don't have a separate voodoo 2 driver if( !Video::setDevice( "OpenGL", width, height, bpp, fullScreen ) ) { Con::errorf( ConsoleLogEntry::General, "Couldn't activate OpenGL, exiting..." ); Platform::forceShutdown( 1 ); return; } // hack from Win32 stuff glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); #endif } static char cBuffer[512]; static const char* cGetDesktopResolution( SimObject*, S32, const char** ) { dSprintf( cBuffer, sizeof( cBuffer ), "%d %d %d", linuxState.width, linuxState.height, linuxState.bpp ); char* returnString = Con::getReturnBuffer( dStrlen( cBuffer ) + 1 ); dStrcpy( returnString, cBuffer ); return returnString; } void Platform::init( void ) { // Set the platform variable for the scripts Con::setVariable( "$platform", "linux" ); LinuxConsole::create( ); #ifndef DEDICATED GetDesktopState( ); installRedBookDevices( ); Con::printf( "Video Init:" ); Video::init( ); if( Video::installDevice( OpenGLDevice::create( ) ) ) { Con::printf( " OpenGL display device created." ); } else { Con::printf( " OpenGL display device *not* created." ); } Input::init( ); InitInput( ); Con::addCommand( "getDesktopResolution", cGetDesktopResolution, "getDesktopResolution();", 1, 1 ); #endif } void Platform::shutdown( void ) { LinuxConsole::destroy( ); #ifndef DEDICATED setWindowLocked( false ); Input::destroy( ); Video::destroy( ); SDL_Quit(); #endif } S32 run( S32 argc, const char** argv ) { #ifdef DEDICATED // start the timers linuxInitTicks( ); lastTimeTick = Platform::getRealMilliseconds( ); return Game->main( argc, argv ); #else // start the timers linuxInitTicks( ); lastTimeTick = Platform::getRealMilliseconds( ); createFontInit( ); windowSize.set( 0, 0 ); S32 ret = Game->main( argc, argv ); createFontShutdown( ); return ret; #endif } void Platform::initWindow( const Point2I& initialSize, const char* name ) { InitWindow( initialSize ); InitOpenGL( ); #ifndef DEDICATED SDL_WM_SetCaption( name, "tribes2" ); SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"), NULL); #endif } static void setupFPU( void ) { // The correct combination to catch NaNs is: IM, ZM, OM bool im = getenv( "TRIBES2_FPU_IM" ); bool dm = getenv( "TRIBES2_FPU_DM" ); bool zm = getenv( "TRIBES2_FPU_ZM" ); bool om = getenv( "TRIBES2_FPU_OM" ); bool um = getenv( "TRIBES2_FPU_UM" ); fpu_control_t cw; _FPU_GETCW( cw ); // triggers problem in NVIDIA drivers if( im ) { cw &= ~_FPU_MASK_IM; } else { cw |= _FPU_MASK_IM; } // triggers problems everywhere :) if( dm ) { cw &= ~_FPU_MASK_DM; } else { cw |= _FPU_MASK_DM; } // div0 if( zm ) { cw &= ~_FPU_MASK_ZM; } else { cw |= _FPU_MASK_ZM; } // *numeric* overflow if( om ) { cw &= ~_FPU_MASK_OM; } else { cw |= _FPU_MASK_OM; } // *numeric* underflow if( um ) { cw &= ~_FPU_MASK_UM; } else { cw |= _FPU_MASK_UM; } _FPU_SETCW( cw ); if ( getenv("TRIBES2_FPU_WINDOWS") ) { cw = 0x0f7f; _FPU_SETCW( cw ); } } int main( int argc, char* argv[] ) { int dedicatedArgc = 0; int missionArgc = 0; char version[32]; sprintf( version, "#%d", getTribes2VersionNumber( ) ); loki_setgamename( "tribes2", version, "Tribes 2 for Linux" ); loki_isdemo( 0 ); #ifndef DEDICATED loki_signalcleanup(SDL_Quit); #endif loki_registeroption( "help", 'h', "Display this help message" ); loki_registeroption( "version", 'v', "Display the game version" ); #ifndef DEDICATED loki_registeroption( "fullscreen", 'f', "Run the game fullscreen" ); loki_registeroption( "windowed", 'w', "Run the game in a window" ); loki_registeroption( "nosound", 's', "Do not access the soundcard" ); #endif loki_registeroption( "update", 'u', "Run the Loki auto-update tool" ); loki_registeroption( "qagent", 'q', "Run the Loki QAgent support tool" ); #ifndef DEDICATED loki_registeroption( "gllibrary", 'g', "Select 3D rendering library" ); #endif loki_initialize_noparse( argc, argv ); // HACK: path creation at startup Platform::createPath( "base/prefs/" ); for( int i = 1; i < argc; i++ ) { #define IS_ARG(x,l,s) ( !strcasecmp( x, l ) || !strcasecmp( x, s ) ) if( IS_ARG( argv[i], "--help", "-h" ) ) { loki_printusage( argv[0], 0 ); exit( 0 ); } else if( IS_ARG( argv[i], "--version", "-v" ) ) { printf( "%s\nBuilt with glibc-%d.%d on %s\n", loki_getgamedescription( ), __GLIBC__, __GLIBC_MINOR__, loki_getarch( ) ); exit( 0 ); } else if( IS_ARG( argv[i], "--update", "-u" ) ) { loki_runupdate( argc, argv ); } else if( IS_ARG( argv[i], "--qagent", "-q" ) ) { loki_runqagent( 0 ); } else { // do our last check, and pass to engine if( strcasecmp( argv[i], "-dedicated" ) == 0 ) { dedicatedArgc = i; } else if( strcasecmp( argv[i], "-mission" ) == 0 ) { missionArgc = i; } } #undef IS_ARG } chdir( loki_getdatapath( ) ); #if defined(DEDICATED) && !defined(BUILD_TOOLS) // make sure we have -dedicated if( dedicatedArgc ) { // if you specify mission, you must specify args, // but you don't need to specify it if( missionArgc ) { if( missionArgc + 2 >= argc ) { // uh oh, need two args loki_printusage( argv[0], "\nFATAL: \"-mission\" requires " " and \"" ); exit( 1 ); } } } else { loki_printusage( argv[0], "\nFATAL: The dedicated server " "*must* be run with \"-dedicated\"." ); exit( 1 ); } printf( "Dedicated server by:\n\tLoki Software, Inc.\n\thttp://www.lokigames.com\n" ); #else // make sure we don't have -dedicated if( dedicatedArgc ) { loki_printusage( argv[0], "\nFATAL: The client " "*may not* be run with \"-dedicated\"." ); exit( 1 ); } #endif setupFPU( ); int retval = run( argc, const_cast( argv ) ); return retval; } void TimeManager::process( void ) { U32 currentTime = Platform::getRealMilliseconds( ); TimeEvent event; event.elapsedTime = currentTime - lastTimeTick; lastTimeTick = currentTime; Game->postEvent( event ); } F32 Platform::getRandom( void ) { return sgPlatRandom.randF( ); } bool Platform::openWebBrowser( const char* webAddress ) { if ( Video::isFullScreen() ) { minimizeWindow( ); } loki_launchURL( webAddress ); } const char* Platform::getLoginPassword() { File file; file.open( "password", File::Read ); if( file.getStatus( ) != File::Ok ) { return ""; } U32 size = file.getSize( ); if( !size ) { return ""; } static char buffer[256]; file.read( size, buffer ); if( file.getStatus( ) != File::Ok ) { file.close( ); return ""; } file.close( ); return buffer; } bool Platform::setLoginPassword( const char* password ) { File file; file.open( "password", File::Write ); if( file.getStatus( ) != File::Ok ) { return false; } file.write( dStrlen( password ), password ); if( file.getStatus( ) != File::Ok ) { return false; } file.close( ); return true; } bool Platform::excludeOtherInstances( const char* mutexName ) { // foo on that return true; } //-------------------------------------- // Dedicated server launcher: //-------------------------------------- ConsoleFunction( launchDedicatedServer, bool, 4, 4, "launchDedicatedServer( map, missionType, botCount )" ) { // run "tribes2 -dedicated -mission argv[1] argv[2] -bot dAtoi( argv[3] )" // Tell what we are doing char cmdLine[512]; dSprintf( cmdLine, sizeof( cmdLine ), "tribes2d -dedicated -mission %s %s -bot %d", argv[1], argv[2], dAtoi( argv[3] ) ); Con::errorf( "** launching dedicated server - command line = \"%s\" **", cmdLine ); // Fork and run the server pid_t child = fork(); if ( child == 0 ) { // The child runs the dedicated server int i; char *args[12]; i = 0; // Always pop up an xterm so we can have console input args[i++] = "xterm"; args[i++] = "-e"; // The rest of the dedicated server command line goes here args[i++] = "./tribes2d"; args[i++] = "-dedicated"; args[i++] = "-mission"; args[i++] = argv[1]; args[i++] = argv[2]; args[i++] = "-bot"; args[i++] = argv[3]; args[i++] = NULL; // Execute it! execvp(args[0], args); // Fall through only if exec() fails... perror(args[0]); _exit(255); } return( child != -1 ); }