/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2009 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file drive.c * Routines for player driving units about the map. */ #define DEFINE_DRIVE_INLINE #include "lib/framework/frame.h" #include "lib/framework/strres.h" #include "lib/framework/input.h" #include "lib/ivis_common/rendmode.h" #include "lib/gamelib/gtime.h" #include "lib/gamelib/animobj.h" #include "lib/sound/audio.h" #include "drive.h" #include "objects.h" #include "move.h" #include "visibility.h" #include "map.h" #include "loop.h" #include "geometry.h" #include "anim_id.h" #include "formationdef.h" #include "formation.h" #include "action.h" #include "order.h" #include "combat.h" #include "mapgrid.h" #include "display.h" #include "effects.h" #include "hci.h" #include "warcam.h" #include "radar.h" #include "display3ddef.h" #include "display3d.h" #include "console.h" #include "intdisplay.h" #include "multiplay.h" #include "target.h" // all the bollox needed for script callbacks #include "lib/script/parse.h" // needed to define types in scripttabs.h (Arse!) #include "scripttabs.h" // needed to define the callback #include "scriptextern.h" // needed to include the GLOBAL for checking bInTutorial #include "group.h" #define DRIVENOISE // Enable driving noises. #define ENGINEVOL (0xfff) #define MINPITCH (768) #define PITCHCHANGE (512) static SWORD DrivingAudioTrack=-1; // Which hardware channel are we using for the car driving noise extern UDWORD selectedPlayer; extern BOOL DirectControl; // Driving characteristics. #define DRIVE_TURNSPEED (4) #define DRIVE_ACCELERATE (16) #define DRIVE_BRAKE (32) #define DRIVE_DECELERATE (16) #define FOLLOW_STOP_RANGE (256) // How close followers need to get to the driver before they can stop. #define DRIVE_TURNDAMP (32) // Damping value for analogue turning. #define MAX_IDLE (GAME_TICKS_PER_SEC*60) // Start to orbit if idle for 60 seconds. DROID *psDrivenDroid = NULL; // The droid that's being driven. static BOOL bDriveMode = false; static SDWORD driveDir; // Driven droid's direction. static SDWORD driveSpeed; // Driven droid's speed. static UDWORD driveBumpTime; // Time that followers get a kick up the ass. static BOOL DoFollowRangeCheck = true; static BOOL AllInRange = true; static BOOL ClearFollowRangeCheck = false; static BOOL DriveControlEnabled = false; static BOOL DriveInterfaceEnabled = false; static UDWORD IdleTime; static BOOL TacticalActive = false; static BOOL WasDriving = false; enum { CONTROLMODE_POINTNCLICK, CONTROLMODE_DRIVE, }; static UWORD ControlMode = CONTROLMODE_DRIVE; static BOOL TargetFeatures = false; // Intialise drive statics, call with true if coming from frontend, false if // coming from a mission. // void driveInitVars(BOOL Restart) { if(WasDriving && !Restart) { debug( LOG_NEVER, "driveInitVars: WasDriving\n" ); DrivingAudioTrack=-1; psDrivenDroid = NULL; DoFollowRangeCheck = true; ClearFollowRangeCheck = false; bDriveMode = false; DriveControlEnabled = true; //false; DriveInterfaceEnabled = false; //true; TacticalActive = false; ControlMode = CONTROLMODE_DRIVE; TargetFeatures = false; } else { debug( LOG_NEVER, "driveInitVars: Driving\n" ); DrivingAudioTrack=-1; psDrivenDroid = NULL; DoFollowRangeCheck = true; ClearFollowRangeCheck = false; bDriveMode = false; DriveControlEnabled = true; //false; DriveInterfaceEnabled = false; TacticalActive = false; ControlMode = CONTROLMODE_DRIVE; TargetFeatures = false; WasDriving = false; } } void setDrivingStatus( BOOL val ) { bDriveMode = val; } BOOL getDrivingStatus( void ) { return(bDriveMode); } // Start droid driving mode. // BOOL StartDriverMode(DROID *psOldDroid) { DROID *psDroid; DROID *psLastDriven; IdleTime = gameTime; psLastDriven = psDrivenDroid; psDrivenDroid = NULL; // Find a selected droid and make that the driven one. for(psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext) { if(psDroid->selected) { if((psDrivenDroid == NULL) && (psDroid != psOldDroid)) { // The first droid found becomes the driven droid. if(!(DroidIsBuilding(psDroid) || DroidGoingToBuild(psDroid))) { // psDroid->sMove.Status = MOVEDRIVE; } psDrivenDroid = psDroid; debug( LOG_NEVER, "New driven droid\n" ); } } } // If that failed then find any droid and make it the driven one. if(psDrivenDroid == NULL) { psLastDriven = NULL; psDrivenDroid = intGotoNextDroidType(NULL,DROID_ANY,true); // If it's the same droid then try again if(psDrivenDroid == psOldDroid) { psDrivenDroid = intGotoNextDroidType(NULL,DROID_ANY,true); } if(psDrivenDroid == psOldDroid) { psDrivenDroid = NULL; } // If it failed then try for a transporter. if(psDrivenDroid == NULL) { psDrivenDroid = intGotoNextDroidType(NULL,DROID_TRANSPORTER,true); } // DBPRINTF(("Selected a new driven droid : %p\n",psDrivenDroid)); } if(psDrivenDroid) { driveDir = (int)psDrivenDroid->direction % 360; driveSpeed = 0; driveBumpTime = gameTime; setDrivingStatus(true); if(DriveInterfaceEnabled) { debug( LOG_NEVER, "Interface enabled1 ! Disabling drive control\n" ); DriveControlEnabled = false; DirectControl = false; } else { DriveControlEnabled = true; DirectControl = true; // we are taking over the unit. } if(psLastDriven != psDrivenDroid) { debug( LOG_NEVER, "camAllignWithTarget\n" ); camAllignWithTarget((BASE_OBJECT*)psDrivenDroid); } return true; } else { } return false; } static void ChangeDriver(void) { DROID *psDroid; if(psDrivenDroid != NULL) { debug( LOG_NEVER, "Driver Changed\n" ); // audio_StopObjTrack(psDrivenDroid,ID_SOUND_SMALL_DROID_RUN); // psDrivenDroid = NULL; for(psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext) { if( (psDroid->sMove.Status == MOVEDRIVE) ) { ASSERT( (psDroid->droidType != DROID_TRANSPORTER),"Tried to control a transporter" ); secondarySetState(psDroid, DSO_HALTTYPE, DSS_HALT_GUARD); psDroid->sMove.Status = MOVEINACTIVE; } } } // setDrivingStatus(false); // DriveControlEnabled = false; } // Stop droid driving mode. // void StopDriverMode(void) { DROID *psDroid; if(psDrivenDroid != NULL) { debug( LOG_NEVER, "Drive mode canceled\n" ); addConsoleMessage("Driver mode canceled.", LEFT_JUSTIFY,SYSTEM_MESSAGE); // audio_StopObjTrack(psDrivenDroid,ID_SOUND_SMALL_DROID_RUN); psDrivenDroid = NULL; for(psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext) { if( (psDroid->sMove.Status == MOVEDRIVE) ) { ASSERT( (psDroid->droidType != DROID_TRANSPORTER),"Tried to control a transporter" ); secondarySetState(psDroid, DSO_HALTTYPE, DSS_HALT_GUARD); psDroid->sMove.Status = MOVEINACTIVE; } } } setDrivingStatus(false); driveInitVars(false); // reset everything again DriveControlEnabled = false; DirectControl = false; } // Call this whenever a droid gets killed or removed. // returns true if ok, returns false if resulted in driving mode being stopped, ie could'nt find // a selected droid to drive. // BOOL driveDroidKilled(DROID *psDroid) { if(driveModeActive()) { if(psDroid == psDrivenDroid) { ChangeDriver(); // StopDriverMode(); psDrivenDroid = NULL; DeSelectDroid(psDroid); if(!StartDriverMode(psDroid)) { return false; } } } return true; } // Call whenever the selections change to re initialise drive mode. // void driveSelectionChanged(void) { if(driveModeActive()) { if(psDrivenDroid) { // StopDriverMode(); ChangeDriver(); StartDriverMode(NULL); driveTacticalSelectionChanged(); } } } // Cycle to next droid in group and make it the driver. // static void driveNextDriver(void) { DROID *psDroid; // Start from the current driven droid. for(psDroid = psDrivenDroid; psDroid; psDroid = psDroid->psNext) { if( (psDroid->selected) && (psDroid != psDrivenDroid) ) { // Found one so make it the driven droid. // psDrivenDroid->sMove.Status = MOVEINACTIVE; // psDroid->sMove.Status = MOVEDRIVE; psDrivenDroid = psDroid; camAllignWithTarget((BASE_OBJECT*)psDroid); driveTacticalSelectionChanged(); return; } } for(psDroid = apsDroidLists[selectedPlayer]; psDroid && (psDroid != psDrivenDroid); psDroid = psDroid->psNext) { if(psDroid->selected) { // Found one so make it the driven droid. // psDrivenDroid->sMove.Status = MOVEINACTIVE; // psDroid->sMove.Status = MOVEDRIVE; psDrivenDroid = psDroid; camAllignWithTarget((BASE_OBJECT*)psDroid); driveTacticalSelectionChanged(); return; } } // No other selected droids found. Oh well... } static BOOL driveControl(DROID *psDroid) { BOOL Input = false; SDWORD MaxSpeed = moveCalcDroidSpeed(psDroid); if(!DriveControlEnabled) { return false; } if(keyPressed(KEY_N)) { driveNextDriver(); } if(keyDown(KEY_LEFTARROW)) { driveDir += DRIVE_TURNSPEED; Input = true; } else if(keyDown(KEY_RIGHTARROW)) { driveDir -= DRIVE_TURNSPEED; if(driveDir < 0) { driveDir += 360; } Input = true; } driveDir = driveDir % 360; if(keyDown(KEY_UPARROW)) { if(driveSpeed >= 0) { driveSpeed += DRIVE_ACCELERATE; if(driveSpeed > MaxSpeed) { driveSpeed = MaxSpeed; } } else { driveSpeed += DRIVE_BRAKE; if(driveSpeed > 0) { driveSpeed = 0; } } Input = true; } else if(keyDown(KEY_DOWNARROW)) { if(driveSpeed <= 0) { driveSpeed -= DRIVE_ACCELERATE; if(driveSpeed < -MaxSpeed) { driveSpeed = -MaxSpeed; } } else { driveSpeed -= DRIVE_BRAKE; if(driveSpeed < 0) { driveSpeed = 0; } } Input = true; } else { if(driveSpeed > 0) { driveSpeed -= DRIVE_DECELERATE; if(driveSpeed < 0) { driveSpeed = 0; } } else { driveSpeed += DRIVE_DECELERATE; if(driveSpeed > 0) { driveSpeed = 0; } } } return Input; } static BOOL driveInDriverRange(DROID *psDroid) { if( (abs(psDroid->pos.x-psDrivenDroid->pos.x) < FOLLOW_STOP_RANGE) && (abs(psDroid->pos.y-psDrivenDroid->pos.y) < FOLLOW_STOP_RANGE) ) { return true; } return false; } static void driveMoveFollower(DROID *psDroid) { if(driveBumpTime < gameTime) { // Update the driven droid's followers. if(!driveInDriverRange(psDroid)) { //psDroid->secondaryOrder&=~DSS_MOVEHOLD_SET; // Remove secondary order ... this stops the droid from jumping back to GUARD mode ... see order.c #111 - tjc secondarySetState(psDroid, DSO_HALTTYPE, DSS_HALT_GUARD); // if the droid is currently guarding we need to change the order to a move if (psDroid->order==DORDER_GUARD) { orderDroidLoc(psDroid,DORDER_MOVE, psDrivenDroid->pos.x,psDrivenDroid->pos.y); } else { // otherwise we just adjust its move-to location moveDroidTo(psDroid,psDrivenDroid->pos.x,psDrivenDroid->pos.y); } } } // Stop it when it gets within range of the driver. if(DoFollowRangeCheck) { if(driveInDriverRange(psDroid)) { psDroid->sMove.Status = MOVEINACTIVE; } else { AllInRange = false; } } } static void driveMoveCommandFollowers(DROID *psDroid) { DROID *psCurr; DROID_GROUP *psGroup = psDroid->psGroup; for (psCurr = psGroup->psList; psCurr!=NULL; psCurr=psCurr->psGrpNext) { driveMoveFollower(psCurr); } } // Call once per frame. // void driveUpdate(void) { DROID *psDroid; PROPULSION_STATS *psPropStats; AllInRange = true; if(DirectControl) { if(psDrivenDroid != NULL) { if (driveBumpTime < gameTime) // send latest info about driven droid. { SendDroidInfo(psDrivenDroid,DORDER_MOVE,psDrivenDroid->pos.x,psDrivenDroid->pos.y, NULL); } //TO BE DONE: //clear the order on taking over the droid, to stop attacks.. //send some sort of message when droids stopo and get inrange. // Check the driven droid is still selected if(psDrivenDroid->selected == false) { // if it's not then reset the driving system. driveSelectionChanged(); return; } // Update the driven droid. if(driveControl(psDrivenDroid)) { // If control did something then force the droid's move status. if(psDrivenDroid->sMove.Status != MOVEDRIVE) { psDrivenDroid->sMove.Status = MOVEDRIVE; ASSERT( (psDrivenDroid->droidType != DROID_TRANSPORTER),"Tried to control a transporter" ); driveDir = (int)psDrivenDroid->direction % 360; } DoFollowRangeCheck = true; } // Is the driven droid under user control? if(psDrivenDroid->sMove.Status == MOVEDRIVE) { // Is it a command droid if( (psDrivenDroid->droidType == DROID_COMMAND) && (psDrivenDroid->psGroup != NULL) ) { driveMoveCommandFollowers(psDrivenDroid); } for(psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext) { psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat; if( (psDroid->selected) && (psDroid != psDrivenDroid) && (psDroid->droidType != DROID_TRANSPORTER) && //((psPropStats->propulsionType != PROPULSION_TYPE_LIFT) || (psDroid->droidType == DROID_CYBORG)) ) { ((psPropStats->propulsionType != PROPULSION_TYPE_LIFT) || cyborgDroid(psDroid)) ) { // Send new orders to it's followers. driveMoveFollower(psDroid); } } } if(AllInRange) { DoFollowRangeCheck = false; } if(driveBumpTime < gameTime) { // Send next order in 1 second. driveBumpTime = gameTime+GAME_TICKS_PER_SEC; } } else { if(StartDriverMode(NULL) == false) { // nothing } } } } SDWORD driveGetMoveSpeed(void) { return driveSpeed; } SDWORD driveGetMoveDir(void) { return driveDir; } void driveSetDroidMove(DROID *psDroid) { // psDroid->sMove.speed = (float)driveSpeed; // psDroid->sMove.dir = driveDir; psDroid->direction = driveDir; } // Dissable user control of droid, ie when interface is up. void driveDisableControl(void) { DriveControlEnabled = false; DirectControl = false; DriveInterfaceEnabled = true; } // Enable user control of droid, ie when interface is down. // void driveEnableControl(void) { DriveControlEnabled = true; DirectControl = true; DriveInterfaceEnabled = false; } // Return true if drive control is enabled. // BOOL driveControlEnabled(void) { return DriveControlEnabled; } // Bring up the reticule. // void driveEnableInterface(BOOL AddReticule) { if(AddReticule) { intAddReticule(); } DriveInterfaceEnabled = true; } // Get rid of the reticule. // void driveDisableInterface(void) { intResetScreen(false); intRemoveReticule(); DriveInterfaceEnabled = false; } // Return true if the reticule is up. // BOOL driveInterfaceEnabled(void) { return DriveInterfaceEnabled; } // Check for and process a user request for a new target. // void driveProcessAquireButton(void) { if(mouseReleased(MOUSE_RMB) || keyPressed(KEY_S)) { BASE_OBJECT *psObj; psObj = targetAquireNearestObjView((BASE_OBJECT*)psDrivenDroid); // driveMarkTarget(); // pie_SetMouse(CURSOR_ATTACK, true); } } // Start structure placement for drive mode. // void driveStartBuild(void) { intRemoveReticule(); DriveInterfaceEnabled = false; // driveDisableInterface(); driveEnableControl(); } // Return true if all the conditions for allowing user control of the droid are met. // BOOL driveAllowControl(void) { if (TacticalActive || DriveInterfaceEnabled || !DriveControlEnabled) { return false; } return true; } // Disable Tactical order mode. // void driveDisableTactical(void) { if(driveModeActive() && TacticalActive) { CancelTacticalScroll(); TacticalActive = false; } } // Return true if Tactical order mode is active. // BOOL driveTacticalActive(void) { return TacticalActive; } void driveTacticalSelectionChanged(void) { if(TacticalActive && psDrivenDroid) { StartTacticalScrollObj(true,(BASE_OBJECT *)psDrivenDroid); debug( LOG_NEVER, "driveTacticalSelectionChanged\n" ); } } // Player clicked in the radar window. // void driveProcessRadarInput(int x,int y) { int PosX, PosY; // when drive mode is active, clicking on the radar orders all selected droids // to move to this position. CalcRadarPosition(x, y, &PosX, &PosY); orderSelectedLoc(selectedPlayer, PosX*TILE_UNITS,PosY*TILE_UNITS); } /* void driveMarkTarget(void) { BASE_OBJECT *psObj = targetGetCurrent(); if(psObj != NULL) { if(driveAllowControl()) { // MouseMovement(false); targetMarkCurrent(); SetMousePos(0,psObj->sDisplay.screenX,psObj->sDisplay.screenY); // pie_DrawMouse(psObj->sDisplay.screenX,psObj->sDisplay.screenY); } } } */