Ejemplo n.º 1
0
/**
 * @brief Persists Lua data.
 *
 *    @param writer XML Writer to use to persist stuff.
 *    @param type Type of the data to save.
 *    @param name Name of the data to save.
 *    @param value Value of the data to save.
 *    @return 0 on success.
 */
static int nxml_saveData( xmlTextWriterPtr writer,
      const char *type, const char *name, const char *value,
      int keynum )
{
   xmlw_startElem(writer,"data");

   xmlw_attr(writer,"type","%s",type);
   xmlw_attr(writer,"name","%s",name);
   if (keynum)
      xmlw_attr(writer,"keynum","1");
   xmlw_str(writer,"%s",value);

   xmlw_endElem(writer); /* "data" */

   return 0;
}
Ejemplo n.º 2
0
/**
 * @brief Saves player's standings with the factions.
 *
 *    @param writer The xml writer to use.
 *    @return 0 on success.
 */
int pfaction_save( xmlTextWriterPtr writer )
{
   int i;

   xmlw_startElem(writer,"factions");

   for (i=1; i<faction_nstack; i++) { /* player is faction 0 */
      /* Must not be static. */
      if (faction_isFlag( &faction_stack[i], FACTION_STATIC ))
         continue;

      xmlw_startElem(writer,"faction");

      xmlw_attr(writer,"name","%s",faction_stack[i].name);
      xmlw_elem(writer, "standing", "%f", faction_stack[i].player);

      if (faction_isKnown_(&faction_stack[i]))
         xmlw_elemEmpty(writer, "known");

      xmlw_endElem(writer); /* "faction" */
   }

   xmlw_endElem(writer); /* "factions" */

   return 0;
}
Ejemplo n.º 3
0
/**
 * @brief Saves player's standings with the factions.
 *
 *    @param writer The xml writer to use.
 *    @return 0 on success.
 */
int pfaction_save( xmlTextWriterPtr writer )
{
   int i;

   xmlw_startElem(writer,"factions");

   for (i=1; i<faction_nstack; i++) { /* player is faction 0 */
      xmlw_startElem(writer,"faction");

      xmlw_attr(writer,"name","%s",faction_stack[i].name);
      xmlw_str(writer, "%f", faction_stack[i].player);

      xmlw_endElem(writer); /* "faction" */
   }

   xmlw_endElem(writer); /* "factions" */

   return 0;
}
Ejemplo n.º 4
0
/**
 * @brief Saves a star system.
 *
 *    @param writer Write to use for saving the star system.
 *    @param sys Star system to save.
 *    @return 0 on success.
 */
int dsys_saveSystem( StarSystem *sys )
{
   int i;
   xmlDocPtr doc;
   xmlTextWriterPtr writer;
   const Planet **sorted_planets;
   const JumpPoint **sorted_jumps, *jp;
   char file[PATH_MAX], *cleanName;

   /* Reconstruct jumps so jump pos are updated. */
   system_reconstructJumps(sys);

   /* Create the writer. */
   writer = xmlNewTextWriterDoc(&doc, 0);
   if (writer == NULL) {
      WARN("testXmlwriterDoc: Error creating the xml writer");
      return -1;
   }

   /* Set the writer parameters. */
   xmlw_setParams( writer );

   /* Start writer. */
   xmlw_start(writer);
   xmlw_startElem( writer, "ssys" );

   /* Attributes. */
   xmlw_attr( writer, "name", "%s", sys->name );

   /* General. */
   xmlw_startElem( writer, "general" );
   if (sys->background != NULL)
      xmlw_elem( writer, "background", "%s", sys->background );
   xmlw_elem( writer, "radius", "%f", sys->radius );
   xmlw_elem( writer, "stars", "%d", sys->stars );
   xmlw_elem( writer, "interference", "%f", sys->interference );
   xmlw_startElem( writer, "nebula" );
   xmlw_attr( writer, "volatility", "%f", sys->nebu_volatility );
   xmlw_str( writer, "%f", sys->nebu_density );
   xmlw_endElem( writer ); /* "nebula" */
   xmlw_endElem( writer ); /* "general" */

   /* Position. */
   xmlw_startElem( writer, "pos" );
   xmlw_elem( writer, "x", "%f", sys->pos.x );
   xmlw_elem( writer, "y", "%f", sys->pos.y );
   xmlw_endElem( writer ); /* "pos" */

   /* Planets. */
   sorted_planets = malloc( sizeof(Planet*) * sys->nplanets);
   memcpy( sorted_planets, sys->planets, sizeof(Planet*) * sys->nplanets );
   qsort( sorted_planets, sys->nplanets, sizeof(Planet*), dsys_compPlanet );
   xmlw_startElem( writer, "assets" );
   for (i=0; i<sys->nplanets; i++)
      xmlw_elem( writer, "asset", "%s", sorted_planets[i]->name );
   xmlw_endElem( writer ); /* "assets" */
   free(sorted_planets);

   /* Jumps. */
   sorted_jumps = malloc( sizeof(JumpPoint*) * sys->njumps );
   for (i=0; i<sys->njumps; i++)
      sorted_jumps[i] = &sys->jumps[i];
   qsort( sorted_jumps, sys->njumps, sizeof(JumpPoint*), dsys_compJump );
   xmlw_startElem( writer, "jumps" );
   for (i=0; i<sys->njumps; i++) {
      jp = sorted_jumps[i];
      xmlw_startElem( writer, "jump" );
      xmlw_attr( writer, "target", "%s", jp->target->name );
      /* Position. */
      if (!jp_isFlag( jp, JP_AUTOPOS )) {
         xmlw_startElem( writer, "pos" );
         xmlw_attr( writer, "x", "%f", jp->pos.x );
         xmlw_attr( writer, "y", "%f", jp->pos.y );
         xmlw_endElem( writer ); /* "pos" */
      }
      else
         xmlw_elemEmpty( writer, "autopos" );
      /* Radius and misc properties. */
      if (jp->radius != 200.)
         xmlw_elem( writer, "radius", "%f", jp->radius );
      /* More flags. */
      if (jp_isFlag( jp, JP_HIDDEN ))
         xmlw_elemEmpty( writer, "hidden" );
      if (jp_isFlag( jp, JP_EXITONLY ))
         xmlw_elemEmpty( writer, "exitonly" );
      xmlw_elem( writer, "hide", "%f", sqrt(jp->hide) );
      xmlw_endElem( writer ); /* "jump" */
   }
   xmlw_endElem( writer ); /* "jumps" */
   free(sorted_jumps);

   xmlw_endElem( writer ); /** "ssys" */
   xmlw_done(writer);

   /* No need for writer anymore. */
   xmlFreeTextWriter(writer);

   /* Write data. */
   cleanName = uniedit_nameFilter( sys->name );
   nsnprintf( file, sizeof(file), "%s/%s.xml", conf.dev_save_sys, cleanName );
   xmlSaveFileEnc( file, doc, "UTF-8" );

   /* Clean up. */
   xmlFreeDoc(doc);
   free(cleanName);

   return 0;
}
Ejemplo n.º 5
0
/**
 * @brief Saves selected systems as a map outfit file.
 *
 *    @return 0 on success.
 */
int dsys_saveMap (StarSystem **uniedit_sys, int uniedit_nsys)
{
   int i, j, k;
   xmlDocPtr doc;
   xmlTextWriterPtr writer;
   StarSystem *s;
   char file[PATH_MAX], *cleanName;

   /* Create the writer. */
   writer = xmlNewTextWriterDoc(&doc, 0);
   if (writer == NULL) {
      WARN("testXmlwriterDoc: Error creating the xml writer");
      return -1;
   }

   /* Set the writer parameters. */
   xmlw_setParams( writer );

   /* Start writer. */
   xmlw_start(writer);
   xmlw_startElem( writer, "outfit" );

   /* Attributes. */
   xmlw_attr( writer, "name", "Editor-generated Map" );

   /* General. */
   xmlw_startElem( writer, "general" );
   xmlw_elem( writer, "mass", "%d", 0 );
   xmlw_elem( writer, "price", "%d", 1000 );
   xmlw_elem( writer, "description", "%s", "This map has been created by the universe editor." );
   xmlw_elem( writer, "gfx_store", "%s", "map" );
   xmlw_endElem( writer ); /* "general" */

   xmlw_startElem( writer, "specific" );
   xmlw_attr( writer, "type", "map" );

   /* Iterate over all selected systems. Save said systems and any NORMAL jumps they might share. */
   for (i = 0; i < uniedit_nsys; i++) {
      s = uniedit_sys[i];
      xmlw_startElem( writer, "sys" );
      xmlw_attr( writer, "name", "%s", s->name );

      /* Iterate jumps and see if they lead to any other systems in our array. */
      for (j = 0; j < s->njumps; j++) {
         /* Ignore hidden and exit-only jumps. */
         if (jp_isFlag(&s->jumps[j], JP_EXITONLY ))
            continue;
         if (jp_isFlag(&s->jumps[j], JP_HIDDEN))
            continue;
         /* This is a normal jump. */
         for (k = 0; k < uniedit_nsys; k++) {
            if (s->jumps[j].target == uniedit_sys[k]) {
               xmlw_elem( writer, "jump", "%s", uniedit_sys[k]->name );
               break;
            }
         }
      }

      /* Iterate assets and add them */
      for (j = 0; j < s->nplanets; j++) {
         if (s->planets[j]->real)
            xmlw_elem( writer, "asset", "%s", s->planets[j]->name );
      }

      xmlw_endElem( writer ); /* "sys" */
   }

   xmlw_endElem( writer ); /* "specific" */
   xmlw_endElem( writer ); /* "outfit" */
   xmlw_done(writer);

   /* No need for writer anymore. */
   xmlFreeTextWriter(writer);

   /* Write data. */
   cleanName = uniedit_nameFilter( "saved map" );
   nsnprintf( file, sizeof(file), "%s/%s.xml", conf.dev_save_map, cleanName );
   xmlSaveFileEnc( file, doc, "UTF-8" );

   /* Clean up. */
   xmlFreeDoc(doc);
   free(cleanName);

   return 0;
}
Ejemplo n.º 6
0
/**
 * @brief Saves the player's active missions.
 *
 *    @param writer XML Write to use to save missions.
 *    @return 0 on success.
 */
int missions_saveActive( xmlTextWriterPtr writer )
{
   int i,j,n;
   int nitems;
   char **items;

   xmlw_startElem(writer,"missions");

   for (i=0; i<MISSION_MAX; i++) {
      if (player_missions[i]->id != 0) {
         xmlw_startElem(writer,"mission");

         /* data and id are attributes because they must be loaded first */
         xmlw_attr(writer,"data","%s",player_missions[i]->data->name);
         xmlw_attr(writer,"id","%u",player_missions[i]->id);

         xmlw_elem(writer,"title","%s",player_missions[i]->title);
         xmlw_elem(writer,"desc","%s",player_missions[i]->desc);
         xmlw_elem(writer,"reward","%s",player_missions[i]->reward);

         /* Markers. */
         xmlw_startElem( writer, "markers" );
         if (player_missions[i]->markers != NULL) {
            n = array_size( player_missions[i]->markers );
            for (j=0; j<n; j++) {
               xmlw_startElem(writer,"marker");
               xmlw_attr(writer,"id","%d",player_missions[i]->markers[j].id);
               xmlw_attr(writer,"type","%d",player_missions[i]->markers[j].type);
               xmlw_str(writer,"%s", system_getIndex(player_missions[i]->markers[j].sys)->name);
               xmlw_endElem(writer); /* "marker" */
            }
         }
         xmlw_endElem( writer ); /* "markers" */

         /* Cargo */
         xmlw_startElem(writer,"cargos");
         for (j=0; j<player_missions[i]->ncargo; j++)
            xmlw_elem(writer,"cargo","%u", player_missions[i]->cargo[j]);
         xmlw_endElem(writer); /* "cargos" */

         /* OSD. */
         if (player_missions[i]->osd > 0) {
            xmlw_startElem(writer,"osd");

            /* Save attributes. */
            items = osd_getItems(player_missions[i]->osd, &nitems);
            xmlw_attr(writer,"title","%s",osd_getTitle(player_missions[i]->osd));
            xmlw_attr(writer,"nitems","%d",nitems);
            xmlw_attr(writer,"active","%d",osd_getActive(player_missions[i]->osd));

            /* Save messages. */
            for (j=0; j<nitems; j++)
               xmlw_elem(writer,"msg","%s",items[j]);

            xmlw_endElem(writer); /* "osd" */
         }

         /* Claims. */
         xmlw_startElem(writer,"claims");
         claim_xmlSave( writer, player_missions[i]->claims );
         xmlw_endElem(writer); /* "claims" */

         /* Write Lua magic */
         xmlw_startElem(writer,"lua");
         nxml_persistLua( player_missions[i]->L, writer );
         xmlw_endElem(writer); /* "lua" */

         xmlw_endElem(writer); /* "mission" */
      }
   }

   xmlw_endElem(writer); /* "missions" */

   return 0;
}
Ejemplo n.º 7
0
/**
 * @brief Saves a planet.
 *
 *    @param writer Write to use for saving the star planet.
 *    @param p Planet to save.
 *    @return 0 on success.
 */
static int dpl_savePlanet( xmlTextWriterPtr writer, const Planet *p )
{
    int i;

    xmlw_startElem( writer, "asset" );

    /* Attributes. */
    xmlw_attr( writer, "name", "%s", p->name );

    /* Explicit virtualness. */
    if (p->real == ASSET_VIRTUAL)
        xmlw_elemEmpty( writer, "virtual" );

    /* Position. */
    if (p->real == ASSET_REAL) {
        xmlw_startElem( writer, "pos" );
        xmlw_elem( writer, "x", "%f", p->pos.x );
        xmlw_elem( writer, "y", "%f", p->pos.y );
        xmlw_endElem( writer ); /* "pos" */
    }

    /* GFX. */
    if (p->real == ASSET_REAL) {
        xmlw_startElem( writer, "GFX" );
        xmlw_elem( writer, "space", "%s", p->gfx_spacePath );
        xmlw_elem( writer, "exterior", "%s", p->gfx_exteriorPath );
        xmlw_endElem( writer ); /* "GFX" */
    }

    /* Presence. */
    if (p->faction >= 0) {
        xmlw_startElem( writer, "presence" );
        xmlw_elem( writer, "faction", "%s", faction_name( p->faction ) );
        xmlw_elem( writer, "value", "%f", p->presenceAmount );
        xmlw_elem( writer, "range", "%d", p->presenceRange );
        xmlw_endElem( writer );
    }

    /* General. */
    if (p->real == ASSET_REAL) {
        xmlw_startElem( writer, "general" );
        xmlw_elem( writer, "class", "%c", planet_getClass( p ) );
        xmlw_elem( writer, "population", "%"PRIu64, p->population );
        xmlw_startElem( writer, "services" );
        if (planet_hasService( p, PLANET_SERVICE_LAND )) {
            if (p->land_func == NULL)
                xmlw_elemEmpty( writer, "land" );
            else
                xmlw_elem( writer, "land", "%s", p->land_func );
        }
        if (planet_hasService( p, PLANET_SERVICE_REFUEL ))
            xmlw_elemEmpty( writer, "refuel" );
        if (planet_hasService( p, PLANET_SERVICE_BAR ))
            xmlw_elemEmpty( writer, "bar" );
        if (planet_hasService( p, PLANET_SERVICE_MISSIONS ))
            xmlw_elemEmpty( writer, "missions" );
        if (planet_hasService( p, PLANET_SERVICE_COMMODITY ))
            xmlw_elemEmpty( writer, "commodity" );
        if (planet_hasService( p, PLANET_SERVICE_OUTFITS ))
            xmlw_elemEmpty( writer, "outfits" );
        if (planet_hasService( p, PLANET_SERVICE_SHIPYARD ))
            xmlw_elemEmpty( writer, "shipyard" );
        xmlw_endElem( writer ); /* "services" */
        if (planet_hasService( p, PLANET_SERVICE_LAND )) {
            xmlw_startElem( writer, "commodities" );
            for (i=0; i<p->ncommodities; i++)
                xmlw_elem( writer, "commodity", "%s", p->commodities[i]->name );
            xmlw_endElem( writer ); /* "commodities" */
            xmlw_elem( writer, "description", "%s", p->description );
            if (planet_hasService( p, PLANET_SERVICE_BAR ))
                xmlw_elem( writer, "bar", "%s", p->bar_description );
        }
        xmlw_endElem( writer ); /* "general" */
    }

    /* Tech. */
    if (planet_hasService( p, PLANET_SERVICE_LAND ))
        tech_groupWrite( writer, p->tech );

    xmlw_endElem( writer ); /** "planet" */

    return 0;
}
Ejemplo n.º 8
0
/**
 * @brief Persists the node on the top of the stack and pops it.
 *
 *    @param L Lua state with node to persist on top of the stack.
 *    @param writer XML Writer to use.
 *    @param Are we parsing a node in a table?  Avoids checking for extra __save.
 *    @return 0 on success.
 */
static int nxml_persistDataNode( lua_State *L, xmlTextWriterPtr writer, int intable )
{
   int ret, b;
   LuaPlanet *p;
   LuaSystem *s;
   LuaFaction *f;
   LuaShip *sh;
   LuaTime *lt;
   Planet *pnt;
   StarSystem *ss;
   char buf[PATH_MAX];
   const char *name, *str;
   int keynum;

   /* Default values. */
   ret   = 0;

   /* key, value */
   /* Handle different types of keys. */
   switch (lua_type(L, -2)) {
      case LUA_TSTRING:
         /* Can just tostring directly. */
         name     = lua_tostring(L,-2);
         /* Isn't a number key. */
         keynum   = 0;
         break;
      case LUA_TNUMBER:
         /* Can't tostring directly. */
         lua_pushvalue(L,-2);
         name     = lua_tostring(L,-1);
         lua_pop(L,1);
         /* Is a number key. */
         keynum   = 1;
         break;

      /* We only handle string or number keys, so ignore the rest. */
      default:
         lua_pop(L,1);
         /* key */
         return 0;
   }
   /* key, value */

   /* Now handle the value. */
   switch (lua_type(L, -1)) {
      /* Recursive for tables. */
      case LUA_TTABLE:
         /* Check if should save -- only if not in table.. */
         if (!intable) {
            lua_getfield(L, -1, "__save");
            b = lua_toboolean(L,-1);
            lua_pop(L,1);
            if (!b) /* No need to save. */
               break;
         }
         /* Start the table. */
         xmlw_startElem(writer,"data");
         xmlw_attr(writer,"type","table");
         xmlw_attr(writer,"name","%s",name);
         if (keynum)
            xmlw_attr(writer,"keynum","1");
         lua_pushnil(L); /* key, value, nil */
         /* key, value, nil */
         while (lua_next(L, -2) != 0) {
            /* key, value, key, value */
            ret = nxml_persistDataNode( L, writer, 1 );
            /* key, value, key */
         }
         /* key, value */
         xmlw_endElem(writer); /* "table" */
         break;

      /* Normal number. */
      case LUA_TNUMBER:
         nxml_saveData( writer, "number",
               name, lua_tostring(L,-1), keynum );
         /* key, value */
         break;

      /* Boolean is either 1 or 0. */
      case LUA_TBOOLEAN:
         /* lua_tostring doesn't work on booleans. */
         if (lua_toboolean(L,-1)) buf[0] = '1';
         else buf[0] = '0';
         buf[1] = '\0';
         nxml_saveData( writer, "bool",
               name, buf, keynum );
         /* key, value */
         break;

      /* String is saved normally. */
      case LUA_TSTRING:
         nxml_saveData( writer, "string",
               name, lua_tostring(L,-1), keynum );
         /* key, value */
         break;

      /* User data must be handled here. */
      case LUA_TUSERDATA:
         if (lua_isplanet(L,-1)) {
            p = lua_toplanet(L,-1);
            pnt = planet_getIndex( p->id );
            if (pnt != NULL)
               nxml_saveData( writer, "planet",
                     name, pnt->name, keynum );
            else
               WARN("Failed to save invalid planet.");
            /* key, value */
            break;
         }
         else if (lua_issystem(L,-1)) {
            s  = lua_tosystem(L,-1);
            ss = system_getIndex( s->id );
            if (ss != NULL)
               nxml_saveData( writer, "system",
                     name, ss->name, keynum );
            else
               WARN("Failed to save invalid system.");
            /* key, value */
            break;
         }
         else if (lua_isfaction(L,-1)) {
            f = lua_tofaction(L,-1);
            str = faction_name( f->f );
            if (str == NULL)
               break;
            nxml_saveData( writer, "faction",
                  name, str, keynum );
            /* key, value */
            break;
         }
         else if (lua_isship(L,-1)) {
            sh = lua_toship(L,-1);
            str = sh->ship->name;
            if (str == NULL)
               break;
            nxml_saveData( writer, "ship",
                  name, str, keynum );
            /* key, value */
            break;
         }
         else if (lua_istime(L,-1)) {
            lt = lua_totime(L,-1);
            snprintf( buf, sizeof(buf), "%"PRId64, lt->t );
            nxml_saveData( writer, "time",
                  name, buf, keynum );
            break;
         }

      /* Rest gets ignored, like functions, etc... */
      default:
         /* key, value */
         break;
   }
   lua_pop(L,1);
   /* key */

   return ret;
}
Ejemplo n.º 9
0
/**
 * @brief Saves a planet.
 *
 *    @param writer Write to use for saving the star planet.
 *    @param p Planet to save.
 *    @return 0 on success.
 */
int dpl_savePlanet( const Planet *p )
{
   xmlDocPtr doc;
   xmlTextWriterPtr writer;
   char file[PATH_MAX], *cleanName;
   int i;

   /* Create the writer. */
   writer = xmlNewTextWriterDoc(&doc, 0);
   if (writer == NULL) {
      WARN("testXmlwriterDoc: Error creating the xml writer");
      return -1;
   }

   /* Set the writer parameters. */
   xmlw_setParams( writer );

   /* Start writer. */
   xmlw_start(writer);
   xmlw_startElem( writer, "asset" );

   /* Attributes. */
   xmlw_attr( writer, "name", "%s", p->name );

   /* Explicit virtualness. */
   if (p->real == ASSET_VIRTUAL)
      xmlw_elemEmpty( writer, "virtual" );

   /* Position. */
   if (p->real == ASSET_REAL) {
      xmlw_startElem( writer, "pos" );
      xmlw_elem( writer, "x", "%f", p->pos.x );
      xmlw_elem( writer, "y", "%f", p->pos.y );
      xmlw_endElem( writer ); /* "pos" */
   }

   /* GFX. */
   if (p->real == ASSET_REAL) {
      xmlw_startElem( writer, "GFX" );
      xmlw_elem( writer, "space", "%s", p->gfx_spacePath );
      xmlw_elem( writer, "exterior", "%s", p->gfx_exteriorPath );
      xmlw_endElem( writer ); /* "GFX" */
   }

   /* Presence. */
   if (p->faction >= 0) {
      xmlw_startElem( writer, "presence" );
      xmlw_elem( writer, "faction", "%s", faction_name( p->faction ) );
      xmlw_elem( writer, "value", "%f", p->presenceAmount );
      xmlw_elem( writer, "range", "%d", p->presenceRange );
      xmlw_endElem( writer );
   }

   /* General. */
   if (p->real == ASSET_REAL) {
      xmlw_startElem( writer, "general" );
      xmlw_elem( writer, "class", "%s", p->class );
      xmlw_elem( writer, "population", "%"PRIu64, p->population );
      xmlw_elem( writer, "hide", "%f", sqrt(p->hide) );
      xmlw_startElem( writer, "services" );
      if (planet_hasService( p, PLANET_SERVICE_LAND )) {
         if (p->land_func == NULL)
            xmlw_elemEmpty( writer, "land" );
         else
            xmlw_elem( writer, "land", "%s", p->land_func );
      }
      if (planet_hasService( p, PLANET_SERVICE_REFUEL ))
         xmlw_elemEmpty( writer, "refuel" );
      if (planet_hasService( p, PLANET_SERVICE_BAR ))
         xmlw_elemEmpty( writer, "bar" );
      if (planet_hasService( p, PLANET_SERVICE_MISSIONS ))
         xmlw_elemEmpty( writer, "missions" );
      if (planet_hasService( p, PLANET_SERVICE_COMMODITY ))
         xmlw_elemEmpty( writer, "commodity" );
      if (planet_hasService( p, PLANET_SERVICE_OUTFITS ))
         xmlw_elemEmpty( writer, "outfits" );
      if (planet_hasService( p, PLANET_SERVICE_SHIPYARD ))
         xmlw_elemEmpty( writer, "shipyard" );
      xmlw_endElem( writer ); /* "services" */
      if (planet_hasService( p, PLANET_SERVICE_LAND )) {
         xmlw_startElem( writer, "commodities" );
         for (i=0; i<p->ncommodities; i++)
            xmlw_elem( writer, "commodity", "%s", p->commodities[i]->name );
         xmlw_endElem( writer ); /* "commodities" */

         if (planet_isBlackMarket(p))
            xmlw_elemEmpty( writer, "blackmarket" );

         xmlw_elem( writer, "description", "%s", p->description );
         if (planet_hasService( p, PLANET_SERVICE_BAR ))
            xmlw_elem( writer, "bar", "%s", p->bar_description );
      }
      xmlw_endElem( writer ); /* "general" */
   }