help with character rotation

Here’s mine, without fitDestAngle2Src, but by limiting the lookAt resulting hpr to the target with clampScalar, to all 3 axis.

from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *
from direct.showbase.DirectObject import DirectObject
from random import choice
from copy import copy
import direct.directbase.DirectStart
import os

base.cTrav = CollisionTraverser('')
base.cTrav.setRespectPrevTransform(1)
CHPusher = CollisionHandlerFluidPusher()
CHEvent = CollisionHandlerEvent()
CHEvent.addInPattern('%fn-hit-%in')

GHOST = BitMask32.allOff()
SHIP2SHIP = BitMask32.bit(0)
PROJ2SHIP = BitMask32.bit(1)

class Proj(DirectObject):
  lastID = -1
  def __init__(self,target):
      self.target = target
      self.targetExist = True
      self.maxSpeed = self.speed = 70.
      self.turn = self.speed*3
      self.p = loader.loadModel('misc/Spotlight')
      self.p.reparentTo(render)
      self.p.setP(180)
      self.p.setY(2)
      self.p.setScale(.1,.25,.1)
      self.p.flattenLight()
      maxb = self.p.getTightBounds()[1]
      Proj.lastID+=1
      collName = 'proj%s'%Proj.lastID
      self.collNP = self.p.attachCollisionSphere(collName, 0,maxb[1]*.5,0, maxb[1]*.2, PROJ2SHIP, GHOST)
      base.cTrav.addCollider(self.collNP,CHEvent)
      self.p.setPos(player,0,player.getChild(0).getTightBounds()[1][1],0)
      self.p.setHpr(player,0,0,0)
      self.p.setColor(Vec4(1,1,0,1))
      self.p.setTextureOff(1)
      self.acceptOnce('%s-hit-ship'%collName,self.hit)
      self.updateTask = taskMgr.add(self.update,'update-'+collName)
      self.probe = self.p.attachNewNode('')

  def update(self,task):
      dt = globalClock.getDt()
      if self.targetExist and self.target.getErrorType()==NodePath.ETRemoved:
         self.targetExist = False
         taskMgr.doMethodLater(3,self.cleanup,'cleanup',extraArgs=[])
      if self.targetExist:
         self.probe.lookAt(self.target)
         degThreshold = self.turn*dt
         origHpr = self.probe.getHpr()
         d = copy(origHpr)
         for i in range(3):
             d[i] = clampScalar(-degThreshold,degThreshold,d[i])
         self.p.setHpr(self.p,d)
         dist = self.p.getDistance(self.target)
         # within this range, and not immediately after launched, and not head on,
         # slow down smoothly, otherwise go full speed
         if dist<30 and task.time>.5 and (d-origHpr).lengthSquared()>500:
            self.speed = max(10,self.maxSpeed * dist/30.)
         elif self.speed<self.maxSpeed:
            self.speed+=15*dt
      # after the target is destroyed, accelerate to maxSpeed
      elif self.speed<self.maxSpeed:
         self.speed+=15*dt
      speed = self.speed*dt
      self.p.setFluidY(self.p,speed)
      return task.cont

  def cleanup(self):
      taskMgr.remove(self.updateTask)
      base.cTrav.removeCollider(self.collNP)
      print 'CLEANUP',self.collNP.getName()
      self.p.removeNode()
      self.ignoreAll()

  def hit(self,ce):
      into = ce.getIntoNodePath()
      if into.hasNetPythonTag('hit'):
         into.getNetPythonTag('hit')()
      self.cleanup()


class Ship:
  def __init__(self):
      global s
      if choice([0,1]):
         self.s = loader.loadModel('smiley')
         self.s.reparentTo(shipsParent)
         self.s.getChild(0).setScale(3)
         csNP = self.s.attachCollisionSphere('ship', 0,0,0, 3, SHIP2SHIP,PROJ2SHIP)
      else:
         self.s = loader.loadModel('misc/rgbCube')
         self.s.reparentTo(shipsParent)
         self.s.getChild(0).setScale(7,3,3)
         b = self.s.getTightBounds()
         dim = b[1]-b[0]
         coll = CollisionNode('ship')
         box = CollisionBox(*b)
         coll.addSolid(box)
         coll.setFromCollideMask(SHIP2SHIP)
         coll.setIntoCollideMask(PROJ2SHIP)
         boxNP = self.s.attachNewNode(coll)
         csNP = self.s.attachCollisionSphere('bounds', 0,0,0, dim.length()*.5, SHIP2SHIP,GHOST)
      base.cTrav.addCollider(csNP,CHPusher)
      CHPusher.addCollider(csNP,self.s)
      self.s.setY(25)
      self.s.setPythonTag('hit',self.hit)
      self.health = 1
      s=self

  def hit(self):
      self.health -= .25
      self.s.setColor(1,self.health,self.health,1)
      if self.health<=0:
         self.destroyed()

  def destroyed(self):
      global s
      if self.s.hasPythonTag('hit'):
         self.s.clearPythonTag('hit')
         base.cTrav.removeCollider(self.s.find('+CollisionNode'))
         self.s.setTransparency(1)
         Sequence(
           Parallel(
             self.s.scaleInterval(.2,Vec3(2)),
             self.s.colorScaleInterval(.2,Vec4(1,1,1,0))
           ),
           Func(self.s.removeNode),
           Wait(1),
           Func(spawnShip)
         ).start()
         s=None



def spawnShip():
    global s
    s = Ship()

def fire():
    if s is None: return
    Proj(s.s)

def updatePlayer(task):
    dt = globalClock.getDt()
    move = playerMove[0]-playerMove[1]
    rotate = playerMove[2]-playerMove[3]
    if move:
       player.setY(player,move*20*dt)
       if move<0:
          rotate*=-1
    if rotate:
       player.setH(player,rotate*120*dt)
    return task.cont

def toggleShipMovement():
    if shipsParentIval.isPlaying():
       shipsParentIval.pause()
    else:
       shipsParentIval.resume()

shipsParent = render.attachNewNode('')
shipsParentIval = shipsParent.hprInterval(4,Vec3(360,0,0))
shipsParentIval.loop()
spawnShip()

player = loader.loadModel('misc/Dirlight')
player.reparentTo(render)
player.setTextureOff(1)
coll = CollisionNode('player')
box = CollisionBox(*player.getTightBounds())
coll.addSolid(box)
coll.setFromCollideMask(SHIP2SHIP)
coll.setIntoCollideMask(SHIP2SHIP)
playerColl = player.attachNewNode(coll)
CHPusher.addCollider(playerColl,player)
base.cTrav.addCollider(playerColl,CHPusher)

playerMove = Vec4(0)
base.accept('f',fire)
base.accept('arrow_up',playerMove.setX,[1])
base.accept('arrow_up-up',playerMove.setX,[0])
base.accept('arrow_down',playerMove.setY,[1])
base.accept('arrow_down-up',playerMove.setY,[0])
base.accept('arrow_left',playerMove.setZ,[1])
base.accept('arrow_left-up',playerMove.setZ,[0])
base.accept('arrow_right',playerMove.setW,[1])
base.accept('arrow_right-up',playerMove.setW,[0])
base.accept('space',toggleShipMovement)
base.accept('escape',os._exit,[0])
taskMgr.add(updatePlayer,'updatePlayer')

camera.setPos(-1.09, -52.53, 115.76)
camera.setHpr(0.53, -65.61, 3.09)
mat4=Mat4(camera.getMat())
mat4.invertInPlace()
base.mouseInterfaceNode.setMat(mat4)
run()

ARROWS to move, F to shoot.

EDIT:
added speed damping when close to the target and not head on.