Game Development Community

dev|Pro Game Development Curriculum

MMORPG Tutorial article 3 part 1 of 2 RPG Style Animals

by Dreamer · 04/13/2005 (8:38 am) · 7 comments

I got tired of chasing Kork around Stronghold and trying to shoot him, so I decided to work a little magic and turn him into a Fish!

For this to work you have to have successfully completed my other MMORPG tutorials.

Basically this is just a slight change to the mission file, and the creation of a new AIPlayer class called fish body here goes.

Edit your server/scripts/game.cs startgame function so it looks like this...
function startGame()
{
   if ($Game::Running) {
      error("startGame: End the game first!");
      return;
   }

   // Inform the client we're starting up
   for( %clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++ ) {
      %cl = ClientGroup.getObject( %clientIndex );
      commandToClient(%cl, 'GameStart');

      // Other client specific setup..
      %cl.score = 0;
   }

   // Start the game timer
   if ($Game::Duration)
      $Game::Schedule = schedule($Game::Duration * 10000, 0, "onGameDurationEnd" );
   $Game::Running = true;
   
   // Start the AIManager
   new ScriptObject(AIManager) {};
   MissionCleanup.add(AIManager);
   //AIManager.think();
   for(%x=0; %x <=6; %x++){
        schedule(1000 * (%x * 10),0,"MakeFish");
   }
 }

function MakeFish(){
   AIManager.Spawn("FishBody");
}

Now update your AIplayer.cs file with this
datablock PlayerData(FishBody)
{
   renderFirstPerson = false;
   emap = true;
   
   className = Armor;
   shapeFile = "~/data/shapes/fish/fish.dts";
   cameraMaxDist = 3;
   computeCRC = true;
  
   canObserve = true;
   cmdCategory = "Clients";

   cameraDefaultFov = 90.0;
   cameraMinFov = 5.0;
   cameraMaxFov = 120.0;
   
   debrisShapeName = "~/data/shapes/player/debris_player.dts";
   debris = playerDebris;

   aiAvoidThis = true;

   minLookAngle = -1.4;
   maxLookAngle = 1.4;
   maxFreelookAngle = 3.0;

   mass = 20;
   drag = 0.1;
   maxdrag = 0.4;
   density = 6;
   maxDamage = 100;
   maxEnergy =  60;
   repairRate = 0.33;
   energyPerDamagePoint = 75.0;

   rechargeRate = 0.256;

   runForce = 100 * 90;
   runEnergyDrain = 0;
   minRunEnergy = 0;
   maxForwardSpeed = 20;
   maxBackwardSpeed = 13;
   maxSideSpeed = 13;

   maxUnderwaterForwardSpeed = 20;
   maxUnderwaterBackwardSpeed = 7.8;
   maxUnderwaterSideSpeed = 17;

   jumpForce = 8.3 * 90;
   jumpEnergyDrain = 0;
   minJumpEnergy = 0;
   jumpDelay = 15;

   recoverDelay = 9;
   recoverRunForceScale = 1.2;

   minImpactSpeed = 45;
   speedDamageScale = 0.4;

   boundingBox = "5 5 5";
   //boundingBox = "";
   pickupRadius = 2;
   
   // Damage location details
   boxNormalHeadPercentage       = 0.83;
   boxNormalTorsoPercentage      = 0.49;
   boxHeadLeftPercentage         = 0;
   boxHeadRightPercentage        = 1;
   boxHeadBackPercentage         = 0;
   boxHeadFrontPercentage        = 1;

   // Foot Prints
   decalData   = PlayerFootprint;
   decalOffset = 0.25;
   
   footPuffEmitter = LightPuffEmitter;
   footPuffNumParts = 10;
   footPuffRadius = 0.25;

   dustEmitter = LiftoffDustEmitter;

   splash = PlayerSplash;
   splashVelocity = 4.0;
   splashAngle = 67.0;
   splashFreqMod = 300.0;
   splashVelEpsilon = 0.60;
   bubbleEmitTime = 0.4;
   splashEmitter[0] = PlayerFoamDropletsEmitter;
   splashEmitter[1] = PlayerFoamEmitter;
   splashEmitter[2] = PlayerBubbleEmitter;
   mediumSplashSoundVelocity = 10.0;   
   hardSplashSoundVelocity = 20.0;   
   exitSplashSoundVelocity = 5.0;

   // Controls over slope of runnable/jumpable surfaces
   runSurfaceAngle  = 70;
   jumpSurfaceAngle = 80;

   minJumpSpeed = 20;
   maxJumpSpeed = 30;

   horizMaxSpeed = 68;
   horizResistSpeed = 33;
   horizResistFactor = 0.35;

   upMaxSpeed = 80;
   upResistSpeed = 25;
   upResistFactor = 0.3;
   
   footstepSplashHeight = 0.35;

   groundImpactMinSpeed    = 50.0;
   groundImpactShakeFreq   = "4.0 4.0 4.0";
   groundImpactShakeAmp    = "1.0 1.0 1.0";
   groundImpactShakeDuration = 0.8;
   groundImpactShakeFalloff = 10.0;
   
   observeParameters = "0.5 4.5 4.5";

 };

function FishBody::onReachDestination(%this,%obj)
{
   // Moves to the next node on the path.
   // Override for all player. Normally we'd override this for only
   // a specific player datablock or class of players.
   if (%obj.path !$= "") {
      if (%obj.currentNode == %obj.targetNode)
         %this.onEndOfPath(%obj,%obj.path);
      else
         %obj.moveToNextNode();
   }
}

function FishBody::onEndOfPath(%this,%obj,%path)
{
   %obj.nextTask();
}

function FishBody::onEndSequence(%this,%obj,%slot)
{
   echo("Sequence Done!");
   %obj.stopThread(%slot);
   %obj.nextTask();
}


//-----------------------------------------------------------------------------
// AIPlayer static functions
//-----------------------------------------------------------------------------

function AIPlayer::spawn(%name,%spawnPoint)
{
   // Create the demo player object
   %player = new AiPlayer() {
      dataBlock = FishBody;
      path = "";
   };
   MissionCleanup.add(%player);
   %player.setShapeName(%name);
   %player.setTransform(%spawnPoint);
   return %player;
}

function AIPlayer::spawnOnPath(%name,%path,%x)
{
   // Spawn a player and place him on the first node of the path
   if (!isObject(%path))
      return;
   %node = %path.getObject(0);
   %player = AIPlayer::spawn(%name,pickSpawnPoint());
  
   return %player;
}


//-----------------------------------------------------------------------------
// AIPlayer methods 
//-----------------------------------------------------------------------------

function AIPlayer::followPath(%this,%path,%node)
{
   // Start the player following a path
   %this.stopThread(0);
   if (!isObject(%path)) {
      %this.path = "";
      return;
   }
   if (%node > %path.getCount() - 1)
      %this.targetNode = %path.getCount() - 1;
   else
      %this.targetNode = %node;
   if (%this.path $= %path)
      %this.moveToNode(%this.currentNode);
   else {
      %this.path = %path;
      %this.moveToNode(0);
   }
}

function AIPlayer::moveToNextNode(%this)
{
   if (%this.targetNode < 0 || %this.currentNode < %this.targetNode) {
      if (%this.currentNode < %this.path.getCount() - 1)
         %this.moveToNode(%this.currentNode + 1);
      else
         %this.moveToNode(0);
   }
   else
      if (%this.currentNode == 0)
         %this.moveToNode(%this.path.getCount() - 1);
      else
         %this.moveToNode(%this.currentNode - 1);
}

function AIPlayer::moveToNode(%this,%index)
{
   // Move to the given path node index
   %this.currentNode = %index;
   %node = %this.path.getObject(%index);
   %this.setMoveDestination(%node.getTransform(), %index == %this.targetNode);
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------

function AIPlayer::pushTask(%this,%method)
{
   if (%this.taskIndex $= "") {
      %this.taskIndex = 0;
      %this.taskCurrent = -1;
   }
   %this.task[%this.taskIndex] = %method; 
   %this.taskIndex++;
   if (%this.taskCurrent == -1)
      %this.executeTask(%this.taskIndex - 1);
}

function AIPlayer::clearTasks(%this)
{
   %this.taskIndex = 0;
   %this.taskCurrent = -1;
}

function AIPlayer::nextTask(%this)
{
   if (%this.taskCurrent != -1)
      if (%this.taskCurrent < %this.taskIndex - 1)
         %this.executeTask(%this.taskCurrent++);
      else
         %this.taskCurrent = -1;
}

function AIPlayer::executeTask(%this,%index)
{
   %this.taskCurrent = %index;
   eval(%this.getId() @ "." @ %this.task[%index] @ ";");
}


//-----------------------------------------------------------------------------

function AIPlayer::singleShot(%this)
{
   // The shooting delay is used to pulse the trigger
   %this.setImageTrigger(0,true);
   %this.setImageTrigger(0,false);
   %this.trigger = %this.schedule(%this.shootingDelay,singleShot);
}


//-----------------------------------------------------------------------------

function AIPlayer::wait(%this,%time)
{
   %this.schedule(%time * 1000,"nextTask");
}

function AIPlayer::done(%this,%time)
{
   %this.schedule(0,"delete");
}

function AIPlayer::fire(%this,%bool)
{
   if (%bool) {
      cancel(%this.trigger);
      %this.singleShot();
   }
   else
      cancel(%this.trigger);
   %this.nextTask();
}

function AIPlayer::aimAt(%this,%object)
{
   echo("Aim: " @ %object);
   %this.setAimObject(%object);
   %this.nextTask();
}

function AIPlayer::animate(%this,%seq)
{
   //%this.stopThread(0);
   //%this.playThread(0,%seq);
   //%this.setActionThread(%seq);
}


//-----------------------------------------------------------------------------

function AIManager::think(%this)
{
   // We could hook into the player's onDestroyed state instead of
   // having to "think", but thinking allows us to consider other
   // things...
   if (!isObject(%this.player))
      %this.player = %this.spawn();
   %this.schedule(500,think);
}

function AIManager::spawn(%this)
{
    %player = AIPlayer::spawnOnPath("Fish","MissionGroup/Paths/Path1");
   %player.followPath("MissionGroup/Paths/Path1", - 1);
  return %player;
}

The above code assumes you have a Fish model, if you don't have one either use blender and make one really quickly or use the player.dts from player.cs (If you do this Kork will behave like a fish, but still look like an orc pretty funny really)

Edit your stronghold.mis file with this information
new SimGroup(PlayerDropPoints) {

      new SpawnSphere() {
         position = "209.506 98.2568 300";
         rotation = "0 0 -1 8.2031";
         scale = "1 1 1";
         dataBlock = "SpawnSphereMarker";
         radius = "10";
         sphereWeight = "100";
         indoorWeight = "100";
         outdoorWeight = "100";
            locked = "false";
            lockCount = "0";
            homingCount = "0";
      };
     new SpawnSphere() {
         position = "513.799 297.531 300";
         rotation = "0 0 -1 87.6625";
         scale = "1 1 1";
         dataBlock = "SpawnSphereMarker";
         radius = "1";
         sphereWeight = "100";
         indoorWeight = "100";
         outdoorWeight = "100";
      };
      new SpawnSphere() {
         position = "441.327 -95.9903 300";
         rotation = "0 0 -1 48.7014";
         scale = "1 1 1";
         dataBlock = "SpawnSphereMarker";
         radius = "2";
         sphereWeight = "100";
         indoorWeight = "100";
         outdoorWeight = "100";
      };
      new SpawnSphere() {
         position = "210.272 339.355 300";
         rotation = "0 0 1 91.6733";
         scale = "1 1 1";
         dataBlock = "SpawnSphereMarker";
         radius = "";
         sphereWeight = "100";
         indoorWeight = "100";
         outdoorWeight = "100";
      };
      new SpawnSphere() {
         position = "348.483 201.943 300";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "SpawnSphereMarker";
         radius = "1";
         sphereWeight = "100";
         indoorWeight = "100";
         outdoorWeight = "100";
      };
   };
   new SimGroup(FishingPoles) {

      new Item() {
         position = "333.898 340.747 219.188";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "Bait";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "334.393 340.13 219.328";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "Bait";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "332.512 342.316 219.431";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "FishPole";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "206.766 122.86 224.522";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "Bait";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "207.157 122.414 224.662";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "Bait";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "210.577 124.064 224.112";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "FishPole";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "334.321 178.98 204.11";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "FishPole";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "330.901 177.33 204.66";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "Bait";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "330.51 177.776 204.52";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "Bait";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
   };
   new SimGroup(HealthKits) {

      new Item() {
         position = "291.437 198.805 203.017";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthKit";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "405.29 330.084 220.015";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthKit";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "382.735 -48.6771 217.837";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthKit";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "391.006 290.664 219.427";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthPatch";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "389.943 289.402 219.42";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthPatch";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "328.822 302.457 219.212";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthPatch";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
      new Item() {
         position = "328.253 303.822 219.226";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         dataBlock = "HealthPatch";
         collideable = "0";
         static = "1";
         rotate = "1";
      };
   };
 
   new SimGroup(Paths) {

      new Path(Path1) {
         isLooping = "1";

         new Marker() {
            position = "330.818 166.563 200.411";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "1";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };

         new Marker() {
            position = "212.812 7.5407 189.404";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "2";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "289.216 91.1337 193.016";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "3";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "298.708 178.853 197.794";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "4";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "251.577 16.1326 206.6";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "5";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "166.976 -307.918 199.35";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "6";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "133.829 -189.667 200";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "7";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "193.303 -161.098 200";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "8";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
         new Marker() {
            position = "157.643 -46.1415 200";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "9";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
	 new Marker() {
            position = "257.553 -2.0712 186.217";
            rotation = "1 0 0 0";
           scale = "1 1 1";
            seqNum = "10";
            type = "Normal";
            msToNext = "1000";
            smoothingType = "Spline";
         };
      };

Don't worry about the fact that we don't have fishing poles, bait etc yet, we will create them in the next tutorial.

And thats everything for this tutorial!

#2
04/13/2005 (5:28 pm)
Nice, I put a horse mesh on the default BlueGuy's animations! That was very strange to see it go from all fours to standing in the Root pose....very strange. Looked like a guy in a pantomine horse costume....:).
#3
04/13/2005 (10:05 pm)
hahahahahaha
www.visionbali.com/handycratfs/images/010-standing-horse.jpg
Put em up! Put em up!
#4
05/04/2005 (10:51 am)
time to start something else /sigh did all this and get

Object 'Bait' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'Bait' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'FishPole' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'Bait' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'Bait' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'FishPole' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'FishPole' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'Bait' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.
Object 'Bait' is not a member of the 'GameBaseData' data block class
starter.fps/data/missions/stronghold.mis (0): Register object failed for object (null) of class Item.

in my consol.log
#5
05/04/2005 (10:54 am)
I had to break this tutorial up into 2 parts, just complete it and move onto the next one, the issue is resolved there.
#6
05/04/2005 (10:56 am)
k thought i was crazy :P
#7
05/04/2005 (10:59 am)
Sorry should have mentioned it earlier, there is a naming convention I am using...
Any time you see Part Foo of Bar, that means it won't work out of the box and you will need to complete all the little broken up parts, for it to work.