Esempio n. 1
0
/**
 * \brief Updates this state.
 */
void Hero::StairsState::update() {

    State::update();

    if (is_suspended()) {
        return;
    }

    // first time: we play the sound and initialize
    if (phase == 0) {
        stairs->play_sound(way);
        next_phase_date = System::now() + 450;
        phase++;
    }

    // update the carried item if any
    if (carried_item != nullptr) {
        carried_item->update();
    }

    Hero& hero = get_entity();
    if (stairs->is_inside_floor()) {

        // inside a single floor: return to normal state as soon as the movement is finished
        if (hero.get_movement()->is_finished()) {

            if (way == Stairs::REVERSE_WAY) {
                get_entities().set_entity_layer(hero, stairs->get_layer());
            }
            hero.clear_movement();
            if (carried_item == nullptr) {
                hero.set_state(new FreeState(hero));
            }
            else {
                hero.set_state(new CarryingState(hero, carried_item));
            }
        }
    }
    else {
        // stairs between two different floors: more complicated

        HeroSprites& sprites = get_sprites();
        if (hero.get_movement()->is_finished()) {
            hero.clear_movement();

            if (carried_item == nullptr) {
                hero.set_state(new FreeState(hero));
            }
            else {
                hero.set_state(new CarryingState(hero, carried_item));
            }

            if (way == Stairs::NORMAL_WAY) {
                // we are on the old floor:
                // there must be a teletransporter associated with these stairs,
                // otherwise the hero would get stuck into the walls
                Teletransporter* teletransporter = hero.get_delayed_teletransporter();
                Debug::check_assertion(teletransporter != nullptr, "Teletransporter expected with the stairs");
                teletransporter->transport_hero(hero);
            }
            else {
                // we are on the new floor: everything is finished
                sprites.set_clipping_rectangle();
            }
        }
        else { // movement not finished yet

            uint32_t now = System::now();
            if (now >= next_phase_date) {
                phase++;
                next_phase_date += 350;

                // main movement direction corresponding to each animation direction while taking stairs
                static constexpr int movement_directions[] = { 0, 0, 2, 4, 4, 4, 6, 0 };

                int animation_direction = stairs->get_animation_direction(way);
                if (phase == 2) { // the first phase of the movement is finished
                    if (animation_direction % 2 != 0) {
                        // if the stairs are spiral, take a diagonal direction of animation
                        sprites.set_animation_walking_diagonal(animation_direction);
                    }
                    else {
                        // otherwise, take a usual direction
                        sprites.set_animation_direction(animation_direction / 2);
                        sprites.set_animation_walking_normal();
                    }
                }
                else if (phase == 3) { // the second phase of the movement (possibly diagonal) is finished
                    sprites.set_animation_walking_normal();

                    if (way == Stairs::NORMAL_WAY) {
                        // on the old floor, take a direction towards the next floor
                        sprites.set_animation_direction(movement_directions[animation_direction] / 2);
                    }
                    else {
                        // on the new floor, take the opposite direction from the stairs
                        sprites.set_animation_direction((stairs->get_direction() + 2) % 4);
                    }
                }
            }
        }
    }
}
Esempio n. 2
0
/**
 * \brief Throws the item that is being lifted.
 *
 * This function is called when this state is interrupted by a new state,
 * e.g. when the hero is hurt while lifting an item.
 */
void Hero::LiftingState::throw_item() {

  lifted_item->throw_item(get_sprites().get_animation_direction());
  get_entities().add_entity(lifted_item);
  lifted_item = NULL;
}
Esempio n. 3
0
/**
 * \brief This function is called repeatedly.
 */
void CarriedItem::update() {

  // update the sprite and the position
  Entity::update();

  if (is_suspended()) {
    return;
  }

  // when the hero finishes lifting the item, start carrying it
  if (is_lifting && get_movement()->is_finished()) {
    is_lifting = false;

    // make the item follow the hero
    clear_movement();
    set_movement(std::make_shared<RelativeMovement>(
        std::static_pointer_cast<Hero>(hero.shared_from_this()),
        0,
        -18,
        true
    ));
  }

  // when the item has finished flying, destroy it
  else if (can_explode() && !is_breaking) {

    uint32_t now = System::now();

    if (now >= explosion_date) {
      break_item();
    }
    else if (will_explode_soon()) {

      std::string animation = get_sprite().get_current_animation();
      if (animation == "stopped") {
        get_sprite().set_current_animation("stopped_explosion_soon");
      }
      else if (animation == "walking") {
        get_sprite().set_current_animation("walking_explosion_soon");
      }
    }
  }

  if (is_throwing) {
    shadow_sprite->update();

    if (is_broken()) {
      remove_from_map();
    }
    else if (break_one_layer_above) {
      break_item();
      Layer layer = get_layer();
      if (layer != LAYER_HIGH) {
        get_entities().set_entity_layer(*this, Layer(layer + 1));
      }
      break_one_layer_above = false;
    }
    else if (get_movement()->is_stopped() || y_increment >= 7) {
      // Interrupt the movement.
      break_item_on_ground();
    }
    else {
      uint32_t now = System::now();
      while (now >= next_down_date) {
        next_down_date += 40;
        item_height -= y_increment;
        y_increment++;
      }
    }
  }
}
Esempio n. 4
0
/**
 * \brief Throws the item carried.
 *
 * This function is called when the player presses the action key
 * or when another state becomes the current state.
 */
void Hero::CarryingState::throw_item() {

  carried_item->throw_item(get_sprites().get_animation_direction());
  get_entities().add_entity(carried_item);
  carried_item = NULL;
}
Esempio n. 5
0
/**
 * \brief Changes the order of an entity in its layer.
 * \param src_index The current index of the entity to change.
 * \param dst_order The new order to set.
 * It must be valid: in particular, tiles must remain before dynamic entities.
 */
void MapData::set_entity_order(const EntityIndex& src_index, int dst_order) {

  Layer layer = src_index.layer;
  int src_order = src_index.order;

  if (dst_order == src_order) {
    // No change.
    return;
  }

  EntityData entity = get_entity(src_index);  // Make a copy.
  bool dynamic = entity.is_dynamic();
  int min_order = dynamic ? get_num_tiles(layer) : 0;
  int max_order = dynamic ? (get_num_entities(layer) - 1) : (get_num_tiles(layer) - 1);

  Debug::check_assertion(dst_order >= min_order, "Entity order out of range (lower than min)");
  Debug::check_assertion(dst_order <= max_order, "Entity order out of range (higher than max)");

  std::deque<EntityData>& entities = get_entities(layer);

  // Update entities and named_entities.
  auto src_it = entities.begin() + src_order;

  if (entity.has_name()) {
    named_entities[entity.get_name()] = { layer, dst_order };
  }

  entities.erase(src_it);
  auto dst_it = entities.begin() + dst_order;
  entities.insert(dst_it, entity);

  if (dst_order < src_order) {
    // Moving downwards.
    // Indexes between dst_order exclusive and src_order inclusive get incremented.
    for (int i = dst_order + 1;
        i <= src_order;
        ++i
    ) {
      const EntityData& current_entity = get_entity({ layer, i });
      const std::string& name = current_entity.get_name();
      if (!name.empty() && name != entity.get_name()) {
        EntityIndex& index = named_entities[name];
        ++index.order;
      }
    }
  }
  else {
    // Moving upwards.
    // Indexes between src_order inclusive and dst_order exclusive get decremented.
    for (int i = src_order;
        i < dst_order;
        ++i
    ) {
      const EntityData& current_entity = get_entity({ layer, i });
      const std::string& name = current_entity.get_name();
      if (!name.empty() && name != entity.get_name()) {
        EntityIndex& index = named_entities[name];
        --index.order;
      }
    }
  }
}
Esempio n. 6
0
/**
 * @brief This function is called repeatedly.
 */
void CarriedItem::update() {

  // update the sprite and the position
  MapEntity::update();

  if (suspended) {
    return;
  }

  // when the hero finishes lifting the item, start carrying it
  if (is_lifting && get_movement()->is_finished()) {
    is_lifting = false;

    // make the item follow the hero
    clear_movement();
    set_movement(new FollowMovement(&hero, 0, -18, true));
  }

  // when the item has finished flying, destroy it
  else if (can_explode() && !is_breaking) {
    
    uint32_t now = System::now();
    
    if (now >= explosion_date) {
      break_item();
    }
    else if (will_explode_soon()) {

      std::string animation = get_sprite().get_current_animation();
      if (animation == "stopped") {
        get_sprite().set_current_animation("stopped_explosion_soon");
      }
      else if (animation == "walking") {
        get_sprite().set_current_animation("walking_explosion_soon");
      }
    }
  }

  if (is_throwing) {
    shadow_sprite->update();

    if (is_broken()) {
      remove_from_map();
    }
    else if (break_on_intermediate_layer) {
      break_item();
      get_entities().set_entity_layer(*this, LAYER_INTERMEDIATE);
      break_on_intermediate_layer = false;
    }
    else if (get_movement()->is_stopped() || y_increment >= 7) {
      break_item();
    }
    else {
      uint32_t now = System::now();
      while (now >= next_down_date) {
        next_down_date += 40;
        item_height -= y_increment;
        y_increment++;
      }
    }
  }
}
Esempio n. 7
0
/**
 * \brief Makes the bomb explode.
 */
void Bomb::explode() {

  get_entities().add_entity(new Explosion("", get_layer(), get_center_point(), true));
  Sound::play("explosion");
  remove_from_map();
}
Esempio n. 8
0
int
main( int argc, char *argv[] )
{
     int             ret;
     int             fd;
     struct stat     stat;
     void           *ptr  = MAP_FAILED;
     Entity::vector  faces;
     DGIFFHeader     header = {
          magic: { 'D', 'G', 'I', 'F', 'F' },
          major: 0,
          minor: 0,
          flags: DGIFF_FLAG_LITTLE_ENDIAN,
          num_faces: 0
     };

     direct_initialize();

     direct_debug_config_domain( "mkdgiff", true );

     direct_config->debug    = true;
     direct_config->debugmem = true;

     /* Parse the command line. */
     if (!parse_command_line( argc, argv ))
          return -1;


     /* Open the file. */
     fd = open( filename, O_RDONLY );
     if (fd < 0) {
          ret = errno2result( errno );
          D_PERROR( "Font/DGIFF: Failure during open() of '%s'!\n", filename );
          return ret;
     }

     /* Query file size etc. */
     if (fstat( fd, &stat ) < 0) {
          ret = errno2result( errno );
          D_PERROR( "Font/DGIFF: Failure during fstat() of '%s'!\n", filename );
          goto out;
     }

     /* Memory map the file. */
     ptr = mmap( NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0 );
     if (ptr == MAP_FAILED) {
          ret = errno2result( errno );
          D_PERROR( "Font/DGIFF: Failure during mmap() of '%s'!\n", filename );
          goto out;
     }


     get_entities( (const char*) ptr, stat.st_size, faces );

     header.num_faces = faces.size();



     fwrite( &header, sizeof(header), 1, stdout );

     for (Entity::vector::const_iterator iter = faces.begin(); iter != faces.end(); iter++) {
          const Face *face = dynamic_cast<const Face*>( *iter );

          face->Dump();

          ret = do_face( face );
          if (ret)
               goto out;
     }


out:
     if (ptr != MAP_FAILED)
          munmap( ptr, stat.st_size );

     close( fd );

     direct_print_memleaks();

     direct_shutdown();

     return ret;
}
StringDictionary EntityBrowser<Assembly>::get_entities(const string& type) const
{
    return get_entities(m_assembly, type);
}
Esempio n. 10
0
DESCRIBE("Entity System")

    IT("adds entities that have correct components on world update",
    {
        // Arrange
        auto world = std::make_shared<World>();
        auto system = std::make_shared<SystemThatWantsComponent1>();
        world->attach(system);
    
        // Act
        auto e = world->create_entity();
        world->add_component<Component1>(e);
        world->update();
    
        // Assert
        S_ASSERT(1 == (int)system->get_entities().size(), "system did not store entity");
    
        // Teardown
    });

    IT("does'nt add entities with incorrect components on world update",
    {
        // Arrange
        auto world = std::make_shared<World>();
        auto system = std::make_shared<SystemThatWantsComponent1>();
        world->attach(system);
    
        // Act
        world->create_entity();
        world->update();
    
Esempio n. 11
0
void ib::tulip_fabric_t::populate(const bool populateFields)
{
  tlp::StringProperty * viewLabel = 0;
  ///Using string for GUID since integer is 32bits (on x86)
  tlp::StringProperty * ibGuid = 0;
  tlp::IntegerProperty * ibPortNum = 0;
  tlp::IntegerProperty * ibLid = 0;
  tlp::IntegerProperty * ibHca = 0;
  tlp::StringProperty * ibWidth = 0;
  tlp::StringProperty * ibSpeed = 0;
  tlp::StringProperty * ibName = 0;
  tlp::StringProperty * ibLeaf = 0;
  tlp::StringProperty * ibSpine = 0;

  if(populateFields)
  {
    viewLabel = graph->getProperty<tlp::StringProperty>("viewLabel");
    ibGuid = graph->getProperty<tlp::StringProperty>("ibGuid");
    ibWidth = graph->getProperty<tlp::StringProperty>("ibWidth");
    ibSpeed = graph->getProperty<tlp::StringProperty>("ibSpeed");
    ibName = graph->getProperty<tlp::StringProperty>("ibName");
    ibLeaf = graph->getProperty<tlp::StringProperty>("ibLeaf");
    ibSpine = graph->getProperty<tlp::StringProperty>("ibSpine");
    ibPortNum = graph->getProperty<tlp::IntegerProperty >("ibPortNum");
    ibLid = graph->getProperty<tlp::IntegerProperty >("ibLid");
    ibHca = graph->getProperty<tlp::IntegerProperty >("ibHca");
  }
  
  /**
   * Create the tulip graph by having the following
   * 1 node = 1 entity
   * 2 edges = 1 cable (1 edge in each direction)
   */

  /**
   * reserve 2 edges per cable
   */
  graph->reserveEdges(get_portmap().size() * 2);
  
  /**
   * Walk every entity and create every node
   */
  for(
    ib::fabric_t::entities_t::const_iterator 
      itr = get_entities().begin(),
      eitr = get_entities().end();
    itr != eitr;
    ++itr
  )
  {
    const ib::entity_t &entity = itr->second;
    
    if(entity_nodes.find(const_cast<ib::entity_t*>(&entity)) == entity_nodes.end())
    {
      /**
       * Create node and insert it into map
       */
      tlp::node node = graph->addNode();
      assert(node.isValid());
      assert(graph->getRoot()->isElement(node));
      
      std::pair<ib::tulip_fabric_t::entity_nodes_t::const_iterator, bool> result = entity_nodes.insert(std::make_pair(const_cast<ib::entity_t*>(&entity), node));
      assert(result.second); ///should never fail!
      assert(result.first->second == node);
      
      if(populateFields)
      {
        typedef ib::entity_t l;
        viewLabel->setNodeValue(node, entity.label(l::LABEL_ENTITY_ONLY));
        ibName->setNodeValue(node, entity.label(l::LABEL_NAME_ONLY));
        ibLeaf->setNodeValue(node, entity.label(l::LABEL_LEAF_ONLY));
        ibSpine->setNodeValue(node, entity.label(l::LABEL_SPINE_ONLY));
        ibPortNum->setNodeValue(node, entity.ports.size()); ///define list of known port count on this entity
        ibGuid->setNodeValue(node, regex::string_cast_uint(entity.guid));
        ibLid->setNodeValue(node, entity.lid());
        ibHca->setNodeValue(node, entity.hca());
      }
    }
  }
    
  /**
    * Walk every port and create every edge
    */
  for(
    ib::fabric_t::portmap_guidport_t::const_iterator
      itr = get_portmap().begin(),
      eitr = get_portmap().end();
    itr != eitr;
    ++itr
  )
  {
    ib::port_t const * const port = itr->second;
    assert(port);
    
    if(port->connection)
    {
      tlp::node n1 = get_entity_node(port->guid);
      tlp::node n2 = get_entity_node(port->connection->guid);
      assert(n1.isValid()); assert(n2.isValid());
      
      tlp::edge edge = graph->addEdge(n1, n2);
      assert(edge.isValid());
      
      std::pair<ib::tulip_fabric_t::port_edges_t::iterator, bool> result = port_edges.insert(std::make_pair(const_cast<ib::port_t*>(port), edge));
      assert(result.second); ///should never fail!
      assert(result.first->second == edge);
     
      if(populateFields)
      {
        typedef ib::port_t l;
        ibName->setEdgeValue(edge, port->label(l::LABEL_FULL));
        ///Dump full label for edges with both ports
        const std::string label = port->label() + " <--> " + port->connection->label();
        viewLabel->setEdgeValue(edge, label);
        
        ibGuid->setEdgeValue(edge, regex::string_cast_uint(port->guid));
        ibWidth->setEdgeValue(edge, port->width);
        ibSpeed->setEdgeValue(edge, port->speed);
        ibLeaf->setEdgeValue(edge, regex::string_cast_uint(port->leaf));
        ibSpine->setEdgeValue(edge, regex::string_cast_uint(port->spine));
        ibPortNum->setEdgeValue(edge, port->port);
        ibLid->setEdgeValue(edge, port->lid);
        ibHca->setEdgeValue(edge, port->hca);
      }
    }
  }

}
Esempio n. 12
0
/**
 * \brief Creates an explosion on the item.
 */
void Destructible::explode() {
  get_entities().add_entity(new Explosion("", get_layer(), get_xy(), true));
  Sound::play("explosion");
}
Esempio n. 13
0
/**
 * \brief Adds to the map the pickable treasure (if any) hidden under this destructible item.
 */
void Destructible::create_pickable() {

  get_entities().add_entity(Pickable::create(get_game(),
      "", get_layer(), get_x(), get_y(),
      treasure, FALLING_MEDIUM, false));
}
Esempio n. 14
0
/**
 * \brief Reacts to the ground of the pickable.
 *
 * It is removed it is on water, lava or a hole.
 * It goes to the lower layer if the ground is empty.
 */
void Pickable::check_bad_ground() {

  if (is_being_removed()) {
    // Be silent if the pickable was already removed by a script.
    return;
  }

  if (get_entity_followed() != nullptr) {
    // We are attached to a hookshot or boomerang: don't fall.
    return;
  }

  if (get_y() < shadow_xy.y) {
    // The pickable is above the ground for now, let it fall first.
    return;
  }

  if (get_movement() != nullptr && !get_movement()->is_finished()) {
    // The falling movement is not finished yet.
    return;
  }

  if (System::now() <= appear_date + 200) {
    // The pickable appeared very recently, let the user see it for
    // a short time at least.
    return;
  }

  Ground ground = get_ground_below();
  switch (ground) {

    case Ground::EMPTY:
    {
      // Fall to a lower layer.
      int layer = get_layer();
      if (layer > 0) {
        --layer;
        get_entities().set_entity_layer(*this, layer);
      }
    }
    break;

    case Ground::HOLE:
    {
      Sound::play("jump");
      remove_from_map();
    }
    break;

    case Ground::DEEP_WATER:
    case Ground::LAVA:
    {
      Sound::play("splash");
      remove_from_map();
    }
    break;

    default:
      break;
  }
}
Esempio n. 15
0
/**
 * \brief Returns the number of entities on a layer of this map.
 * \param layer A layer.
 * \return The number of entities on that layer.
 */
int MapData::get_num_entities(Layer layer) const {
  return get_entities(layer).size();
}
Esempio n. 16
0
/**
 * \brief Updates the enemy.
 */
void Enemy::update() {

  MapEntity::update();

  if (is_suspended() || !is_enabled()) {
    return;
  }

  uint32_t now = System::now();

  if (being_hurt) {

    // see if we should stop the animation "hurt"
    if (now >= stop_hurt_date) {
      being_hurt = false;
      set_movement_events_enabled(true);

      if (life <= 0) {
        kill();
      }
      else if (is_immobilized()) {
        clear_movement();
        set_animation("immobilized");
        notify_immobilized();
      }
      else {
        clear_movement();
        restart();
      }
    }
  }

  if (life > 0 && invulnerable && now >= vulnerable_again_date && !being_hurt) {
    invulnerable = false;
  }

  if (life > 0 && !can_attack && !is_immobilized()
      && can_attack_again_date != 0 && now >= can_attack_again_date) {
    can_attack = true;
  }

  if (is_immobilized() && !is_killed() && now >= end_shaking_date &&
      get_sprite().get_current_animation() == "shaking") {

    restart();
  }

  if (is_immobilized() && !is_killed() && !is_being_hurt() && now >= start_shaking_date &&
      get_sprite().get_current_animation() != "shaking") {

    end_shaking_date = now + 2000;
    set_animation("shaking");
  }

  if (exploding) {
    uint32_t now = System::now();
    if (now >= next_explosion_date) {

      // create an explosion
      Rectangle xy;
      xy.set_x(get_top_left_x() + Random::get_number(get_width()));
      xy.set_y(get_top_left_y() + Random::get_number(get_height()));
      get_entities().add_entity(new Explosion("", LAYER_HIGH, xy, false));
      Sound::play("explosion");

      next_explosion_date = now + 200;
      nb_explosions++;

      if (nb_explosions >= 15) {
        exploding = false;
      }
    }
  }

  if (is_killed() && is_dying_animation_finished()) {

    // Create the pickable treasure if any.
    get_entities().add_entity(Pickable::create(get_game(),
        "", get_layer(), get_x(), get_y(),
        treasure, FALLING_HIGH, false));

    // Remove the enemy.
    remove_from_map();

    // Notify Lua that this enemy is dead.
    // We need to do this after remove_from_map() so that this enemy is
    // considered dead in functions like map:has_entities(prefix).
    notify_dead();
  }

  get_lua_context().enemy_on_update(*this);
}
Esempio n. 17
0
static int
do_face( const Face *face )
{
     int              i, ret;
     int              align        = DFB_PIXELFORMAT_ALIGNMENT( m_format );
     int              num_glyphs   = 0;
     int              num_rows     = 1;
     int              row_index    = 0;
     int              row_offset   = 0;
     int              next_face    = sizeof(DGIFFFaceHeader);
     int              total_height = 0;

     Entity::vector   glyph_vector;
     unsigned int     glyph_count = 0;

     DGIFFFaceHeader  header;
     DGIFFGlyphInfo  *glyphs;
     DGIFFGlyphRow   *rows;
     void           **row_data;

     DFBSurfaceDescription *descs;

     D_DEBUG_AT( mkdgiff, "%s( %p )\n", __FUNCTION__, face );

     get_entities( face->buf, face->length, glyph_vector );

     glyph_count = glyph_vector.size();


     /* Clear to not leak any data into file. */
     memset( &header, 0, sizeof(header) );


     /* Allocate glyph info array. */
     glyphs   = (DGIFFGlyphInfo*)        D_CALLOC( glyph_count, sizeof(DGIFFGlyphInfo) );
     rows     = (DGIFFGlyphRow*)         D_CALLOC( glyph_count, sizeof(DGIFFGlyphRow) );            /* WORST case :) */
     row_data = (void**)                 D_CALLOC( glyph_count, sizeof(void*) );                    /* WORST case :) */
     descs    = (DFBSurfaceDescription*) D_CALLOC( glyph_count, sizeof(DFBSurfaceDescription) );    /* WORST case :) */

     for (Entity::vector::const_iterator iter = glyph_vector.begin(); iter != glyph_vector.end(); iter++) {
          const Glyph *glyph = dynamic_cast<const Glyph*>( *iter );

          glyph->Dump();


          DGIFFGlyphInfo *info = &glyphs[num_glyphs];
          DGIFFGlyphRow  *row  = &rows[num_rows - 1];

          D_DEBUG_AT( mkdgiff, "  -> code %3u\n", glyph->unicode );

          ret = load_image( glyph->file.c_str(), &descs[num_glyphs] );
          if (ret)
               continue;

          info->unicode = glyph->unicode;

          info->width   = descs[num_glyphs].width;
          info->height  = descs[num_glyphs].height;

          info->left    = glyph->left;
          info->top     = glyph->top;
          info->advance = glyph->advance;

          num_glyphs++;

          if (row->width > 0 && row->width + info->width > MAX_ROW_WIDTH) {
               num_rows++;
               row++;
          }

          row->width += (info->width + align) & ~align;

          if (row->height < info->height)
               row->height = info->height;
     }

     for (i=0; i<num_rows; i++) {
          DGIFFGlyphRow *row = &rows[i];

          D_DEBUG_AT( mkdgiff, "  ->   row %d, width %d, height %d\n", i, row->width, row->height );

          total_height += row->height;

          row->pitch = (DFB_BYTES_PER_LINE( m_format, row->width ) + 7) & ~7;

          row_data[i] = D_CALLOC( row->height, row->pitch );

          next_face += row->height * row->pitch;
     }

     D_DEBUG_AT( mkdgiff, "  -> %d glyphs, %d rows, total height %d\n", num_glyphs, num_rows, total_height );

     next_face += num_glyphs * sizeof(DGIFFGlyphInfo);
     next_face += num_rows * sizeof(DGIFFGlyphRow);

     for (i=0; i<num_glyphs; i++) {
          DGIFFGlyphInfo *glyph = &glyphs[i];

          D_DEBUG_AT( mkdgiff, "  -> writing character 0x%x (%d)\n", glyph->unicode, i );

          if (row_offset > 0 && row_offset + glyph->width > MAX_ROW_WIDTH) {
               row_index++;
               row_offset = 0;
          }


          D_DEBUG_AT( mkdgiff, "  -> row offset %d\n", row_offset );

          write_glyph( glyph, descs[i],
                       (char*) row_data[row_index] + DFB_BYTES_PER_LINE( m_format, row_offset ),
                       rows[row_index].pitch );

          glyph->row    = row_index;
          glyph->offset = row_offset;

          row_offset += (glyph->width + align) & ~align;
     }

     D_ASSERT( row_index == num_rows - 1 );

     header.next_face   = next_face;
     header.size        = face->size;

     header.ascender    = face->ascender;
     header.descender   = face->descender;
     header.height      = face->height;

     header.max_advance = face->maxadvance;

     header.pixelformat = m_format;

     header.num_glyphs  = num_glyphs;
     header.num_rows    = num_rows;

     header.blittingflags = face->blittingflags;

     D_DEBUG_AT( mkdgiff, "  -> ascender %d, descender %d\n", header.ascender, header.descender );
     D_DEBUG_AT( mkdgiff, "  -> height %d, max advance %d\n", header.height, header.max_advance );

     fwrite( &header, sizeof(header), 1, stdout );

     fwrite( glyphs, sizeof(*glyphs), num_glyphs, stdout );

     for (i=0; i<num_rows; i++) {
          DGIFFGlyphRow *row = &rows[i];

          fwrite( row, sizeof(*row), 1, stdout );

          fwrite( row_data[i], row->pitch, row->height, stdout );
     }

     for (i=0; i<num_rows; i++) {
          if (row_data[i])
               D_FREE( row_data[i] );
     }

     D_FREE( row_data );
     D_FREE( rows );
     D_FREE( glyphs );

     return 0;
}