/* * Copyright (C) 2006 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program 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 (version 2 of the License) * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include "globals.h" #include #include #include #include #include #include "net/msghandler.h" #include "psmovement.h" #include "pscharcontrol.h" #include "pscamera.h" #include "effects/pseffectmanager.h" //#define MOVE_DEBUG #define RUNTO_EPSILON 0.75f #define USE_EXPERIMENTAL_AUTORUN 0 //---------------------------------------------------------------------------------------------------- // Some additional csVector3 operators to simplify things inline csVector3& operator *= (csVector3& u, const csVector3& v) { u.x *= v.x; u.y *= v.y; u.z *= v.z; return u; } inline csVector3& operator /= (csVector3& u, const csVector3& v) { u.x /= v.x; u.y /= v.y; u.z /= v.z; return u; } // psVelocity operator definitions inline psVelocity& psVelocity::operator = (const psVelocity& v) { move = v.move; rotate = v.rotate; return *this; } inline psVelocity& psVelocity::operator += (const psVelocity& v) { move += v.move; rotate += v.rotate; return *this; } inline psVelocity& psVelocity::operator -= (const psVelocity& v) { move -= v.move; rotate -= v.rotate; return *this; } inline psVelocity& psVelocity::operator *= (const psVelocity& v) { move *= v.move; rotate *= v.rotate; return *this; } inline psVelocity& psVelocity::operator /= (const psVelocity& v) { move /= v.move; rotate /= v.rotate; return *this; } //---------------------------------------------------------------------------------------------------- psMovementManager::psMovementManager(iEventNameRegistry* eventname_reg, psControlManager* controls) { CS_ASSERT(eventname_reg); event_frame = csevFrame(eventname_reg); event_mousemove = csevMouseMove(eventname_reg,0); actor = NULL; this->controls = controls; ready = false; psengine->GetMsgHandler()->Subscribe(this,MSGTYPE_MOVEINFO); psengine->GetMsgHandler()->Subscribe(this,MSGTYPE_MOVELOCK); psengine->GetMsgHandler()->Subscribe(this,MSGTYPE_MOVEMOD); defaultmode = NULL; actormode = NULL; onGround = true; locked = false; activeMoves = 0; autoRun = false; mouseLook = false; mouseZoom = false; mouseRun = false; runToMarkerID = 0; lastDist = 0.0f; runToDiff = csVector3(0.0f); sensY = 1.0f; sensX = 1.0f; invertedMouse = true; activeModType = psMoveModMsg::NONE; forward = NULL; run = NULL; } psMovementManager::~psMovementManager() { ready = false; psengine->GetMsgHandler()->Unsubscribe(this,MSGTYPE_MOVEINFO); psengine->GetMsgHandler()->Unsubscribe(this,MSGTYPE_MOVELOCK); psengine->GetMsgHandler()->Unsubscribe(this,MSGTYPE_MOVEMOD); } void psMovementManager::SetActor(GEMClientActor* a) { actor = a ? a : psengine->GetCelClient()->GetMainPlayer() ; CS_ASSERT(actor); linearMove = actor->linmove; CS_ASSERT(linearMove); SetupControls(); } const psCharMode* psMovementManager::FindCharMode(const char* name) const { for (size_t i=0; iname == name) return modes[i]; return NULL; } const psMovement* psMovementManager::FindMovement(const char* name) const { for (size_t i=0; iname == name) return moves[i]; return NULL; } void psMovementManager::LockMoves(bool v) { if (v) StopAllMovement(); locked = v; } void psMovementManager::HandleMessage(MsgEntry* me) { switch ( me->GetType() ) { case MSGTYPE_MOVEINFO: // Initial info about modes and moves { if (ready) { Bug1("Received second set of movement info!"); return; } psMovementInfoMessage movemsg(me); SetupMovements(movemsg); return; } case MSGTYPE_MOVELOCK: // Movement lockout started { // The server will override any attempts to move during a lockout. // The client should lockout controls too, until the server says otherwise. psMoveLockMessage lockmsg(me); LockMoves(lockmsg.locked); return; } case MSGTYPE_MOVEMOD: // Movement modifier { psMoveModMsg modmsg(me); HandleMod(modmsg); return; } } } bool psMovementManager::HandleEvent( iEvent &event ) { if (!actor) // Not fully loaded yet return false; if (event.Name == event_frame) { // If we we've returned to the ground, update allowed velocity if (!onGround && linearMove->IsOnGround()) UpdateVelocity(); if (mouseRun) UpdateRunTo(); } else if (event.Name == event_mousemove) { if (mouseLook) MouseLook(event); else if (mouseZoom) MouseZoom(event); } return false; } void psMovementManager::HandleMod(psMoveModMsg& msg) { csVector3 rotMod = csVector3(0,msg.rotationMod,0); if (msg.type == psMoveModMsg::PUSH) { psVelocity push(msg.movementMod,rotMod); // Execute once move_total += push; UpdateVelocity(); move_total -= push; } else // Persistant mod { activeModType = msg.type; activeMod.move = msg.movementMod; activeMod.rotate = rotMod; UpdateVelocity(); } } inline void psMovementManager::ApplyMod(psVelocity& vel) { switch (activeModType) { default: case psMoveModMsg::NONE: case psMoveModMsg::PUSH: return; // Add to any existing movement case psMoveModMsg::ADDITION: { size_t i; for (i=0; i<3; i++) { if (vel.move[i] > SMALL_EPSILON) vel.move[i] += activeMod.move[i]; else if (vel.move[i] < -SMALL_EPSILON) vel.move[i] -= activeMod.move[i]; } for (i=0; i<3; i++) { if (vel.rotate[i] > SMALL_EPSILON) vel.rotate[i] += activeMod.rotate[i]; else if (vel.rotate[i] < -SMALL_EPSILON) vel.rotate[i] -= activeMod.rotate[i]; } return; } // Multiply case psMoveModMsg::MULTIPLIER: { vel *= activeMod; return; } // Add case psMoveModMsg::CONSTANT: { vel += activeMod; return; } } } void psMovementManager::SetupMovements(psMovementInfoMessage& movemsg) { #ifdef MOVE_DEBUG printf("\nReceived character modes:\n"); #endif for (size_t i=0; iid = id; newmode->name = name; newmode->modifier.move = move_mod; newmode->modifier.rotate = rotate_mod; newmode->idle_anim = idle_anim; modes.Put(id,newmode); if (defaultmode == NULL) actormode = defaultmode = newmode; // Use first mode as default } if (movemsg.modes == 0) { Bug1("Received no character modes!"); } #ifdef MOVE_DEBUG printf("\nReceived movement types:\n"); #endif for (size_t i=0; iid = id; newmove->name = name; newmove->motion.move = base_move; newmove->motion.rotate = base_rotate; CS_ASSERT(id < sizeof(activeMoves)*8); moves.Put(id,newmove); } if (movemsg.moves == 0) { Bug1("Received no movement types!"); } forward = FindMovement("forward"); run = FindCharMode("run"); ready = true; } void psMovementManager::SetupControls() { for (size_t i=0; iSetTriggerData(modes[i]->name,modes[i]) ) { #ifdef MOVE_DEBUG printf("Set control for mode %s\n", modes[i]->name.GetData() ); #endif } } for (size_t i=0; iSetTriggerData(moves[i]->name,moves[i]) ) { #ifdef MOVE_DEBUG printf("Set control for moves %s\n", moves[i]->name.GetData() ); #endif } } } void psMovementManager::SetActorMode(const psCharMode* mode) { actormode = mode; actor->SetIdleAnimation(actormode->idle_anim); } void psMovementManager::Start(const psCharMode* mode) { if (locked) return; #ifdef MOVE_DEBUG printf("Starting mode %s\n", mode->name.GetData() ); #endif SetActorMode(mode); UpdateVelocity(); } void psMovementManager::Stop(const psCharMode* mode) { if (locked) return; #ifdef MOVE_DEBUG printf("Stopping mode %s\n", mode->name.GetData() ); #endif SetActorMode(defaultmode); UpdateVelocity(); } void psMovementManager::Start(const psMovement* move) { #ifdef MOVE_DEBUG printf("Starting move %s\n", move->name.GetData() ); #endif // Cancel autorun if starting to move forward on own if (move == forward) autoRun = false; uint bit = (1 << move->id); if (activeMoves & bit) return; // Already active activeMoves |= bit; move_total += move->motion; UpdateVelocity(); } void psMovementManager::Stop(const psMovement* move) { #ifdef MOVE_DEBUG printf("Stopping move %s\n", move->name.GetData() ); #endif uint bit = (1 << move->id); if (!(activeMoves & bit)) return; // Not active activeMoves &= ~bit; move_total -= move->motion; UpdateVelocity(); } void psMovementManager::Push(const psMovement* move) { #ifdef MOVE_DEBUG printf("Pushing move %s\n", move->name.GetData() ); #endif uint bit = (1 << move->id); if (activeMoves & bit) return; // Already active move_total += move->motion; UpdateVelocity(); move_total -= move->motion; } void psMovementManager::StopAllMovement() { if (!ready || !actor) // Not fully loaded yet return; // Cancel all active activeMoves = 0; move_total = psVelocity(0.0f,0.0f); SetActorMode(defaultmode); // Halt actor linearMove->SetVelocity(0); linearMove->SetAngularVelocity(0); actor->SetAnimationVelocity(0); // Remove run-to effect if (runToMarkerID != 0) { psengine->GetEffectManager()->DeleteEffect(runToMarkerID); runToMarkerID = 0; } autoRun = false; } void psMovementManager::StopControlledMovement() { if (!ready || !actor) // Not fully loaded yet return; if (runToMarkerID == 0) // Only stop if no run-to { // Cancel all active activeMoves = 0; move_total = psVelocity(0.0f,0.0f); if (autoRun) // Don't stop autorun { Start(forward); autoRun = true; } else { SetActorMode(defaultmode); } UpdateVelocity(); } } void psMovementManager::UpdateVelocity() { /// While the client is locked out the server will also reject any attempts to move. if (locked) return; // Start with the total of all applied movements psVelocity vel = move_total; // Apply special modifier, if we have one ApplyMod(vel); /** When falling or otherwise not on the ground we restrict movement. * Some control is required to prevent collision detection issues, * and to give the limited ability to turn or glide mid-flight. */ onGround = linearMove->IsOnGround(); if (onGround) // Normal { // Apply mode's modifier vel *= actormode->modifier; #ifdef MOVE_DEBUG printf("Changing velocity to (%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)\n", vel.move.x,vel.move.y,vel.move.z, vel.rotate.x,vel.rotate.y,vel.rotate.z); #endif // Set vertical linearMove->ClearWorldVelocity(); linearMove->AddVelocity( csVector3(0,vel.move.y,0) ); // Set horizontal linearMove->SetVelocity( csVector3(vel.move.x,0,vel.move.z) ); // Set rotation linearMove->SetAngularVelocity( vel.rotate ); // Update animating speed actor->SetAnimationVelocity( vel.move ); } else // Airborne { // restriction vel *= psVelocity(0.2f,0.5f); #ifdef MOVE_DEBUG printf("Adding velocity (%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)\n", vel.move.x,vel.move.y,vel.move.z, vel.rotate.x,vel.rotate.y,vel.rotate.z); #endif // Add to existing velocity linearMove->AddVelocity( csVector3(vel.move.x,0,vel.move.z) ); // Set rotation linearMove->SetAngularVelocity( vel.rotate ); } } void psMovementManager::LoadMouseSettings() { bool inv; int v1, v2; psengine->GetMouseBinds()->GetOnOff("InvertMouse", inv ); psengine->GetMouseBinds()->GetInt("VertSensitivity", v1); psengine->GetMouseBinds()->GetInt("HorzSensitivity", v2); SetInvertedMouse( inv ); SetMouseSensY( v1 ); SetMouseSensX( v2 ); } void psMovementManager::MouseLook(iEvent& ev) { psCamera* camera = psengine->GetPSCamera(); iGraphics2D* g2d = psengine->GetG2D(); int mouseX = csMouseEventHelper::GetX(&ev); int mouseY = csMouseEventHelper::GetY(&ev); int centerX = g2d->GetWidth() / 2; int centerY = g2d->GetHeight() / 2; float deltaX = float(mouseX - centerX); float deltaY = float(mouseY - centerY); // Recenter mouse so we don't lose focus g2d->SetMousePosition(centerX, centerY); float deltaPitch = deltaY * (sensY/25000.0f) * (invertedMouse ? 1.0f : -1.0f); float deltaYaw; if ( camera->RotateCameraWithPlayer() ) { deltaYaw = 0.0f; if (!locked) { float spin = -1.0f * deltaX * (sensX/200.0f); if (spin > 5) spin = 5.0f; if (spin < -5) spin = -5.0f; linearMove->SetAngularVelocity( csVector3(0,spin,0) ); } } else { deltaYaw = deltaX * (sensX/25000.0f); } camera->MovePitch(deltaPitch); camera->MoveYaw(deltaYaw); } void psMovementManager::MouseZoom(iEvent& ev) { int mouseY = csMouseEventHelper::GetY(&ev); // Recenter mouse so we don't lose focus iGraphics2D* g2d = psengine->GetG2D(); int centerX = g2d->GetWidth() / 2; int centerY = g2d->GetHeight() / 2; g2d->SetMousePosition(centerX, centerY); psengine->GetPSCamera()->MoveDistance( float(mouseY - centerY) * (sensY/2500.0f) ); } void psMovementManager::SetRunToPos(psPoint& mouse) { csVector3 tmp, tmpDiff; iMeshWrapper *mesh = psengine->GetPSCamera()->Get3DPointFrom2D(mouse.x, mouse.y, &tmp, &tmpDiff); if (mesh) { // Stop and remove run-to marker, if one exists StopAllMovement(); iSector* sector = linearMove->GetSector(); iMeshWrapper* actormesh = actor->pcmesh->GetMesh(); runToMarkerID = psengine->GetEffectManager()->RenderEffect("marker", sector, tmp, actormesh); runToDiff = tmpDiff; lastDist = tmpDiff.SquaredNorm() + 1.0f; // Apply movements Start(run); Start(forward); } else { Error1("Failed to find mesh for SetRunToPos"); } } void psMovementManager::CancelRunTo() { if (runToMarkerID != 0) StopAllMovement(); } void psMovementManager::UpdateRunTo() { if (runToMarkerID != 0 && !locked) { csVector3 currPos; float yRot; iSector* sector; linearMove->GetLastPosition(currPos, yRot, sector); csVector3 diff = runToDiff - currPos; // only move the char if the target location isn't within range, and we are not stuck if (((lastDist - diff.SquaredNorm()) > 0.05f) && (diff.SquaredNorm() > RUNTO_EPSILON*RUNTO_EPSILON)) { float targetYRot = atan2(-diff.x,-diff.z); lastDist = diff.SquaredNorm(); #if USE_EXPERIMENTAL_AUTORUN // Try to avoid big ugly stuff in our path iSector* sector = movePropClass->GetSector(); if ( sector ) { csVector3 isect,start,end,dummy,box,legs; iPcCollisionDetection* pcDummy; csRef cdsys = CS_QUERY_REGISTRY (psengine->GetObjectRegistry (), iCollideSystem); // Construct the feeling broom // Calculate the start and end poses start= currPos; linearMove->GetCDDimensions(box,legs,dummy,pcDummy); // We can walk over some stuff start += csVector3(0,0.6f,0); end = start + csVector3(sin(targetYRot), 0, cos(targetYRot)) * -0.5; // Feel csIntersectingTriangle closest_tri; iMeshWrapper* sel = 0; float dist = csColliderHelper::TraceBeam (cdsys, sector, start, end, true, closest_tri, isect, &sel); if(dist > 0) { const float begin = (PI/6); // The lowest turning constant const float delta = (PI/8); const float length = 0.5; float left,right,turn; for(int i = 1; i <= 3;i++) { csVector3 broomS[2],broomE[2]; // Left and right left = targetYRot - (begin * float(i)); right = targetYRot + (begin * float(i)); // Construct the testing brooms broomE[0] = broomS[0] = end; broomS[0] = currPos + csVector3(sin(left+delta),0,cos(left+delta)) * -length; broomE[0] = currPos + csVector3(sin(left-delta),0,cos(left-delta)) * -length; broomE[1] = broomS[1] = end; broomS[1] = currPos + csVector3(sin(right+delta),0,cos(right+delta)) * -length; broomE[1] = currPos + csVector3(sin(right-delta),0,cos(right-delta)) * -length; // The broom is already 0.6 over the ground, so we need to cut that out broomS[0].y += legs.y + box.y - 0.6; broomE[1].y += legs.y + box.y - 0.6; // Check if we can get the broom through where we want to go float dist = csColliderHelper::TraceBeam (cdsys, sector, broomS[0], broomE[0], true, closest_tri, isect, &sel); if(dist < 0) { turn = left; break; } // Do again for the other side dist = csColliderHelper::TraceBeam (cdsys, sector, broomS[1], broomE[1], true, closest_tri, isect, &sel); if(dist < 0) { turn = right; break; } } // Apply turn targetYRot = turn; } } #endif targetYRot = yRot-targetYRot; if (targetYRot > PI ) targetYRot -= TWO_PI; if (targetYRot > -1.0f && targetYRot < 1.0f) targetYRot *= 3; // Turn towards target linearMove->SetAngularVelocity( csVector3(0,targetYRot,0) ); } else { CancelRunTo(); } } } void psMovementManager::AutoRun(bool value) { if (run && forward && !locked) { StopAllMovement(); if (value) { Start(run); Start(forward); } autoRun = value; } }