With roles, users and forum structure in place, we can now look at migrating threads from the vBulletin database threads table with something like the following in the vbulletin_forum.inc file

class vBulletinThreadMigration extends Migration {
  public function __construct() {
    parent::__construct();
    $this->description = t('Migrate vBulletin forum threads');

    // Set the source table's primary key
    $this->map = new MigrateSQLMap(
      $this->machineName,
      array(
        'threadid' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'description' => 'vBulletin forum threads',
          'alias' => 't',
        ),
      ),
      MigrateDestinationNode::getKeySchema(),
      'for_forum_migration'
    );

    // Set the query to get the source object
    $query = Database::getConnection('default', 'for_forum_migration')
      ->select('thread', 't')
      ->fields('t', array(
        'threadid',
        'title',
        'firstpostid',
        'lastpost',
        'forumid',
        'pollid',
        'open',
        'replycount',
        'hiddencount',
        'postusername',
        'postuserid',
        'lastposter',
        'dateline',
        'views',
        'iconid',
        'notes',
        'visible',
        'sticky',
        'votenum',
        'votetotal',
        'attach',
        'similar',
        'deletedcount',
        'lastpostid',
        'description',
        'prefixid',
        'taglist',
        'vbseo_linkbacks_no',
        ));

    // vBulletin thread doesn't include the first post text, so get it by joining with the post table, matching firstpostid and postid

    $query->join('post', 'p', "t.firstpostid = p.postid");
    $query->addField('p', 'pagetext');

    $this->source = new MigrateSourceSQL($query, array(), NULL, array('map_joinable' => FALSE));

    // Define the destination as the D7 forum node

    $this->destination = new MigrateDestinationNode('forum', array('text_format' => 'bbcode'));

    // Map fields from vBulletin threads to D7 forum nodes

    $this->addFieldMapping('uid', 'postuserid')
         ->sourceMigration('vBulletinUser');

    $this->addFieldMapping('created', 'dateline');

    $this->addFieldMapping('changed', 'lastpost');

    $this->addFieldMapping('status', 'visible');

    $this->addFieldMapping('promote')
         ->defaultValue(0);

    $this->addFieldMapping('sticky', 'sticky');

    $this->addFieldMapping('title', 'title');

    $this->addFieldMapping('field_tags', 'taglist')
         ->separator(',');

    $this->addFieldMapping('comment', 'open');

    // vBulletin thread forumid needs to map to tid in the D7 forum table 

    $this->addFieldMapping('taxonomy_forums', 'forumid')
         ->sourceMigration('vBulletinForum');

    $this->addFieldMapping('body', 'pagetext');

    $this->addFieldMapping('pathauto')
         ->defaultValue(0);

    $this->addFieldMapping('field_forum_icon', 'iconid');

    // Flag up any issues to address

    $this->addFieldMapping(NULL, 'deletedcount')
         ->description('Find out what deletedcount is for')
         ->issueGroup(t('Owner Issues'))
         ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
         ->issueNumber(0012);

    $this->addFieldMapping(NULL, 'votenum')
         ->description('Decide to drop or implement thread votes')
         ->issueGroup(t('Owner Issues'))
         ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
         ->issueNumber(0012);

    $this->addFieldMapping(NULL, 'votetotal')
         ->description('How does D7 calculate total and average votes')
         ->issueGroup(t('Owner Issues'))
         ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
         ->issueNumber(0012);

    // Unmapped source fields

    $this->addFieldMapping(NULL, 'pollid') ->description(t('Used only once in vBulletin and not needed in D7'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'postusername') ->description(t('Username generated by postuserid and sourceMigration from vBulletinUser'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'lastposter') ->description(t('Used in vBulletin, but no need to migrate as D7 identifies last poster from post date'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'views') ->description(t('Used in vBulletin, but not obvious and probably not needed in D7'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'notes') ->description(t('Never used in vBulletin and not needed in D7'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'attach') ->description(t('Not used in vBulletin and not needed in D7'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'similar') ->description(t('Use D7 or a contrib to find similar content if this featured is needed'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'lastpostid') ->description(t('D7 creates this automatically from post created dates'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'description') ->description(t('Barely used in vBulletin and not needed in D7'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'prefixid') ->description(t('Not used in vBulletin and not needed in D7'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping(NULL, 'vbseo_linkbacks_no') ->description(t('Always 0 in vBulletin and not needed in D7'))
           ->issueGroup(t('DNM'));

    // Unmapped destination fields

    $this->addFieldMapping('revision') ->description(t('vBulletin does not use node revisions so nothing to import'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping('log') ->description(t('vBulletin did not use a thread log so nothing to import'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping('language') ->description(t('English language only set at site level'))
           ->issueGroup(t('DNM'));
    $this->addFieldMapping('tnid') ->description(t('No translation so tnid not required'))
           ->issueGroup(t('DNM'));

    }

    public function prepare($node, stdClass $row) {
      if ($row->open == '1') {
        $node->comment = '2';
    }
  }
}

When you start a thread in vBulletin, the text content of the thread is stored in the vBulletin database 'post' table in a 'pagetext' field, and linked back the thread with a 'firstpostid' reference. However, in D7 the first post is stored as 'body' text in the thread node. As a result, we need to join the vBulletin 'thread' source table with the 'post' table, matching the 'firstpostid' and 'postid' fields respectively, using...

    $query->join('post', 'p', "t.firstpostid = p.postid");
    $query->addField('p', 'pagetext');

The addFieldMApping then sources the pagetext field data so we can map it to the relevant D7 forum thread node's body field using...

    $this->addFieldMapping('body', 'pagetext');