/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 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 */ // // Drive.c // // Routines for player driving units about the map. // #define DEFINE_DRIVE_INLINE #include #include // FIXME Direct iVis implementation include! #include "lib/ivis_common/rendmode.h" #include "lib/framework/frame.h" #include "objects.h" #include "move.h" #include "findpath.h" #include "visibility.h" #include "map.h" #include "fpath.h" #include "loop.h" #include "lib/gamelib/gtime.h" #include "lib/sound/audio.h" #include "audio_id.h" #include "geometry.h" #include "lib/gamelib/animobj.h" #include "anim_id.h" #include "formationdef.h" #include "formation.h" #include "action.h" #include "order.h" #include "astar.h" #include "combat.h" #include "mapgrid.h" #include "display.h" // needed for widgetsOn flag. #include "effects.h" #include "lib/framework/fractions.h" #include "hci.h" #include "warcam.h" #include "radar.h" #include "display3ddef.h" #include "display3d.h" #include "console.h" #include "text.h" #include "intdisplay.h" // all the bollox needed for script callbacks #include "lib/script/interp.h" // needed to define types in scripttabs.h #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 DEBUG_DRIVE_SPEED #define DRIVEFIXED #define DRIVENOISE // Enable driving noises. #define ENGINEVOL (0xfff) #define MINPITCH (768) #define PITCHCHANGE (512) #include "multiplay.h" #include "target.h" #include "drive.h" 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 int 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, }; UWORD ControlMode = CONTROLMODE_DRIVE; 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; } } UWORD controlModeGet(void) { return ControlMode; } void controlModeSet(UWORD Mode) { ControlMode = Mode; } void setDrivingStatus( BOOL val ) { bDriveMode = val; } BOOL getDrivingStatus( void ) { return(bDriveMode); } // Start droid driving mode. // BOOL StartDriverMode(DROID *psOldDroid) { DROID *psDroid; DROID *psLastDriven; // DBPRINTF(("StartDriveMode\n")); 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" ); } else if(psDroid != psDrivenDroid) { // All the others become followers of the driven droid. // psDroid->sMove.Status = MOVEDRIVEFOLLOW; } } } // 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 = psDrivenDroid->direction % 360; driveSpeed = 0; driveBumpTime = gameTime; setDrivingStatus(TRUE); if(DriveInterfaceEnabled) { debug( LOG_NEVER, "Interface enabled1 ! Disabling drive control\n" ); DriveControlEnabled = FALSE; } else { DriveControlEnabled = TRUE; } if(psLastDriven != psDrivenDroid) { debug( LOG_NEVER, "camAllignWithTarget\n" ); camAllignWithTarget((BASE_OBJECT*)psDrivenDroid); } return TRUE; } else { } return FALSE; } void StopEngineNoise(void) { DrivingAudioTrack=-1; } 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; } } } // #ifdef PSX // MouseMovement(TRUE); // #endif // setDrivingStatus(FALSE); // DriveControlEnabled = FALSE; } // Stop droid driving mode. // void StopDriverMode(void) { DROID *psDroid; if(psDrivenDroid != NULL) { debug( LOG_NEVER, "Drive mode canceled\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; } // Returns TRUE if we have a driven droid. // //INLINED BOOL driveHasDriven(void) //INLINED { //INLINED return (DirectControl) && (psDrivenDroid != NULL) ? TRUE : FALSE; //INLINED } // Returns TRUE if drive mode is active. // //INLINED BOOL driveModeActive(void) //INLINED { //INLINED //DBPRINTF(("%d\n",DirectControl); //INLINED #ifdef DRIVEFIXED //INLINED return DirectControl; //INLINED #else //INLINED return psDrivenDroid != NULL ? TRUE : FALSE; //INLINED #endif //INLINED } // Return TRUE if the specified droid is the driven droid. // //INLINED BOOL driveIsDriven(DROID *psDroid) //INLINED { //INLINED #ifdef DRIVEFIXED //INLINED return (DirectControl) && (psDrivenDroid != NULL) && (psDroid == psDrivenDroid) ? TRUE : FALSE; //INLINED #else //INLINED return (psDrivenDroid != NULL) && (psDroid == psDrivenDroid) ? TRUE : FALSE; //INLINED #endif //INLINED } //INLINED BOOL driveIsFollower(DROID *psDroid) //INLINED { //INLINED #ifdef DRIVEFIXED //INLINED return (DirectControl) && (psDrivenDroid != NULL) && (psDroid != psDrivenDroid) && psDroid->selected ? TRUE : FALSE; //INLINED #else //INLINED return (psDrivenDroid != NULL) && (psDroid != psDrivenDroid) && psDroid->selected ? TRUE : FALSE; //INLINED #endif //INLINED } //INLINED DROID *driveGetDriven(void) //INLINED { //INLINED return psDrivenDroid; //INLINED } // 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) { // DROID *NewDroid; if(driveModeActive()) { if(psDroid == psDrivenDroid) { ChangeDriver(); // StopDriverMode(); psDrivenDroid = NULL; // psDroid->selected = FALSE; DeSelectDroid(psDroid); if(!StartDriverMode(psDroid)) { // NewDroid = intGotoNextDroidType(NULL,DROID_ANY,TRUE); // // DBPRINTF(("Droid Killed %p new %p\n",psDroid,NewDroid)); // // if(NewDroid == psDroid) { // NewDroid = intGotoNextDroidType(NULL,DROID_ANY,TRUE); // DBPRINTF(("Droid Killed %p new %p\n",psDroid,NewDroid)); // } // // if((!StartDriverMode()) || (NewDroid == psDroid)) { //#ifdef PSX // // Failed to find a droid to track! // DBPRINTF(("No droid to drive!\n")); // // DBPRINTF(("no droids, find a structure\n"); // if(StartObjectOrbit((BASE_OBJECT*)intFindAStructure())) { // } else { // DBPRINTF(("no structures or droids, should be game over!\n"); // } //#endif return FALSE; // } } } } return TRUE; } BOOL driveDroidIsBusy(DROID *psDroid) { if( DroidIsBuilding(psDroid) || DroidGoingToBuild(psDroid) || DroidIsDemolishing(psDroid) ) { return TRUE; } return FALSE; } // 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. // void driveNextDriver(void) { DROID *psDroid; // BOOL Found = FALSE; //DBPRINTF(("driveNextDriver %p\n",psDrivenDroid); // 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; } } // if(!Found) { // Not found so start at the begining. 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... } // PC Version... 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->x-psDrivenDroid->x) < FOLLOW_STOP_RANGE) && (abs(psDroid->y-psDrivenDroid->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->x,psDrivenDroid->y); } else { // otherwise we just adjust its move-to location moveDroidTo(psDroid,psDrivenDroid->x,psDrivenDroid->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(bMultiPlayer && (driveBumpTime < gameTime)) // send latest info about driven droid. { SendDroidInfo(psDrivenDroid,DORDER_MOVE,psDrivenDroid->x,psDrivenDroid->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 = 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 != LIFT) || (psDroid->droidType == DROID_CYBORG)) ) { ((psPropStats->propulsionType != 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 { #ifdef DRIVEFIXED // Start driving //DBPRINTF(("StartDriveMode\n"); if(StartDriverMode(NULL) == FALSE) { //DBPRINTF(("StartObjectOrbit\n"); // // No droids to drive so start orbiting a structure. // if(!OrbitIsValid()) { // if(StartObjectOrbit((BASE_OBJECT*)intFindAStructure())) { // MouseMovement(FALSE); // setDrivingStatus(TRUE); // DriveControlEnabled = TRUE; // } else { // DBPRINTF(("** Failed to start drive mode **\n"); // } // } } #endif } } } SDWORD driveGetMoveSpeed(void) { return driveSpeed; } SDWORD driveGetMoveDir(void) { return driveDir; } void driveSetDroidMove(DROID *psDroid) { // psDroid->sMove.speed = MAKEFRACT(driveSpeed); // psDroid->sMove.dir = driveDir; psDroid->direction = (UWORD)driveDir; } // Dissable user control of droid, ie when interface is up. void driveDisableControl(void) { DriveControlEnabled = FALSE; } // Enable user control of droid, ie when interface is down. // void driveEnableControl(void) { DriveControlEnabled = TRUE; } // 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; } // Get rid of the reticule. // void driveDisableInterface2(void) { 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 = targetAquireNext(TARGET_TYPE_ANY); // psObj = targetAquireNearestObj(targetGetCrosshair(),TARGET_TYPE_ANY); psObj = targetAquireNearestObjView((BASE_OBJECT*)psDrivenDroid,TARGET_TYPE_ANY); } } // Start structure placement for drive mode. // void driveStartBuild(void) { intRemoveReticule(); DriveInterfaceEnabled = FALSE; // driveDisableInterface(); driveEnableControl(); } void driveStartDemolish(void) { intRemoveReticule(); DriveInterfaceEnabled = FALSE; driveEnableControl(); } // Stop structure placement for drive mode. // void driveStopBuild(void) { } // Return TRUE if all the conditions for allowing user control of the droid are met. // BOOL driveAllowControl(void) { if( TacticalActive || DriveInterfaceEnabled || (!DriveControlEnabled) ) { // if( TacticalActive ) DBPRINTF(("TacticalActive\n"); // if( DriveInterfaceEnabled ) DBPRINTF(("DriveInterfaceEnabled\n"); // if( DeliveryReposValid() ) DBPRINTF(("DeliveryReposValid\n"); // if( (camGetMode() == CAMMODE_ORBIT) ) DBPRINTF(("CAMMODE_ORBIT\n"); // if( (!DriveControlEnabled) ) DBPRINTF(("!DriveControlEnabled\n"); return FALSE; } return TRUE; } // Enable Tactical order mode. // void driveEnableTactical(void) { // MouseMovement(TRUE); // SetMouseRange(0,RADTLX,RADTLY,RADTLX+RADWIDTH,RADTLY+RADHEIGHT); // SetMousePos(0,RADTLX+RADWIDTH/2,RADTLY+RADHEIGHT/2); StartTacticalScroll(TRUE); TacticalActive = TRUE; debug( LOG_NEVER, "Tactical Mode Activated\n" ); } // Disable Tactical order mode. // void driveDisableTactical(void) { if(driveModeActive() && TacticalActive) { CancelTacticalScroll(); // MouseMovement(FALSE); // SetMouseRange(0,16,16,639-16,479-16); // SetMousePos(0,320,160); TacticalActive = FALSE; debug( LOG_NEVER, "Tactical Mode Canceled\n" ); } } // 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) { SDWORD PosX,PosY; // when drive mode is active, clicking on the radar orders all selected droids // to move to this position. CalcRadarPosition(x,y,(UDWORD *)&PosX,(UDWORD *)&PosY); orderSelectedLoc(selectedPlayer, PosX*TILE_UNITS,PosY*TILE_UNITS); }