message: Display D2 subclass nodes
diff: @@ -336,6 +336,15 @@ export function D2ItemFactory(
  console.error(`Error building stats for ${createdItem.name}`, item, itemDef, e);
  }
  + try {
  + createdItem.talentGrid = buildTalentGrid(item, itemComponents.talentGrids.data, defs.TalentGrid);
  + if (createdItem.talentGrid) {
  + console.log(createdItem.name, createdItem.talentGrid);
  + }
  + } catch (e) {
  + console.error(`Error building talent grid for ${createdItem.name}`, item, itemDef, e);
  + }
  +
  try {
  createdItem.objectives = buildObjectives(item, itemComponents.objectives.data, defs.Objective);
  } catch (e) {
  @@ -551,6 +560,80 @@ export function D2ItemFactory(
  });
  }
  + function buildTalentGrid(item, talentsMap, talentDefs) {
  + const talentGrid = talentsMap[item.itemInstanceId];
  + if (!talentGrid) {
  + return null;
  + }
  +
  + const talentGridDef = talentDefs.get(talentGrid.talentGridHash);
  + if (!talentGridDef || !talentGridDef.nodes || !talentGridDef.nodes.length) {
  + return undefined;
  + }
  +
  + let gridNodes = talentGridDef.nodes.map((node) => {
  + const talentNodeGroup = node;
  + const talentNodeSelected = node.steps[0];
  +
  + console.log(talentNodeGroup, talentNodeSelected);
  +
  + if (!talentNodeSelected) {
  + return undefined;
  + }
  +
  + const nodeName = talentNodeSelected.displayProperties.name;
  +
  + // Filter out some weird bogus nodes
  + if (!nodeName || nodeName.length === 0 || talentNodeGroup.column < 0) {
  + return undefined;
  + }
  +
  + // Only one node in this column can be selected (scopes, etc)
  + const exclusiveInColumn = Boolean(talentNodeGroup.exlusiveWithNodeHashes &&
  + talentNodeGroup.exlusiveWithNodeHashes.length > 0);
  +
  + const activatedAtGridLevel = talentNodeSelected.activationRequirement.gridLevel;
  +
  + // There's a lot more here, but we're taking just what we need
  + return {
  + name: nodeName,
  + hash: talentNodeSelected.nodeStepHash,
  + description: talentNodeSelected.displayProperties.description,
  + icon: talentNodeSelected.displayProperties.icon,
  + // Position in the grid
  + column: talentNodeGroup.column / 8,
  + row: talentNodeGroup.row / 8,
  + // Is the node selected (lit up in the grid)
  + activated: true,
  + // The item level at which this node can be unlocked
  + activatedAtGridLevel: activatedAtGridLevel,
  + // Only one node in this column can be selected (scopes, etc)
  + exclusiveInColumn: exclusiveInColumn,
  + // Whether or not the material cost has been paid for the node
  + unlocked: true,
  + // Some nodes don't show up in the grid, like purchased ascend nodes
  + hidden: false
  + };
  + });
  +
  + // We need to unique-ify because Ornament nodes show up twice!
  + gridNodes = _.uniq(_.compact(gridNodes), false, 'hash');
  +
  + if (!gridNodes.length) {
  + return undefined;
  + }
  +
  + // Fix for stuff that has nothing in early columns
  + const minColumn = _.min(_.reject(gridNodes, 'hidden'), 'column').column;
  + if (minColumn > 0) {
  + gridNodes.forEach((node) => { node.column -= minColumn; });
  + }
  +
  + return {
  + nodes: _.sortBy(gridNodes, (node) => { return node.column + (0.1 * node.row); }),
  + complete: _.all(gridNodes, (n) => n.unlocked)
  + };
  + }
  function buildSockets(item, socketsMap, defs, itemDef) {
  if (!itemDef.sockets || !itemDef.sockets.socketEntries.length) {
  