View Single Post
  #7  
Old 07-16-2009, 11:26 AM
Shendare
Dragon
 
Join Date: Apr 2009
Location: California
Posts: 814
Default

New version of entity.cpp (Line 579) - EntityList::FindNearbyObject()
Code:
Object* EntityList::FindNearbyObject(float x, float y, float z, float radius)
{
  LinkedListIterator<Object*> iterator(object_list);
  iterator.Reset();

  float ox;
  float oy;
  float oz;

  while(iterator.MoreElements())
  {
    Object* object=iterator.GetData();
		
    object->GetLocation(&ox, &oy, &oz);

    ox = (x < ox) ? (ox - x) : (x - ox);
    oy = (y < oy) ? (oy - y) : (y - oy);
    oz = (z < oz) ? (oz - z) : (z - oz);

    if ((ox <= radius) && (oy <= radius) && (oz <= radius))
    {
      return object;
    }
    iterator.Advance();
  }

  return NULL;
}
Out of several options available for fixing the gcc ambiguity problem, I simply removed the abs() calls altogether in favor of conditional assignments. Probably more efficient anyway.

New version of command.cpp (Line 13344) - command_object()
Code:
void command_object(Client *c, const Seperator *sep)
{
  if (!c)
  {
    return; // Crash Suppressant: No client. How did we get here?
  }

  // Save it here. We sometimes have need to refer to it in multiple places.
  char* usage_string = "Usage: #object List|Add|Edit|Move|Rotate|Save|Copy|Delete|Undo";
  
  if ((!sep) || (sep->argnum == 0))
  {
    // Crash Suppressant: Shouldn't be able to get here, either, but fail gracefully if we do.
    c->Message(0, usage_string);

    return;
  }

  char errbuf[MYSQL_ERRMSG_SIZE];
	char query[512];
  char line[256];
  int32 col;
  int32 lastid;
  MYSQL_RES *result;
  MYSQL_ROW row;
  int iObjectsFound = 0;
  int len;

  Object* o = NULL;
  Object_Struct od;
  Door door;
  Doors* doors;
  Door_Struct* ds;
  int32 id = 0;
  int32 itemid = 0;
  int32 icon = 0;
  int32 instance = 0;
  int32 newid = 0;
  int16 radius;
  EQApplicationPacket* app;

  bool bNewObject = false;

  errbuf[0] = '\0';

  float x2;
  float y2;

  // Temporary object type for static objects to allow manipulation
  // NOTE: Zone::LoadZoneObjects() currently loads this as an int8, so max value is 255!
  static const int32 TempStaticType = 255;

  // Case insensitive commands (List == list == LIST)
  strlwr(sep->arg[1]);

  // Protip: We only really care about the first letter. You can abbreviate Delete to just D if desired.
  switch (sep->arg[1][0])
  {
    case 'l': // List Objects
      // Insufficient or invalid args
      if ((sep->argnum < 2) || (sep->arg[2][0] < '0') || ((sep->arg[2][0] > '9') && ((sep->arg[2][0] & 0xDF) != 'A')))
      {
        c->Message(0, "Usage: #object List All|(radius)");

        return;
      }
      
      if ((sep->arg[2][0] & 0xDF) == 'A')
      {
        radius = 0; // List All
      }
      else if ((radius = atoi(sep->arg[2])) <= 0)
      {
        radius = 500; // Invalid radius. Default to 500 units.
      }
      
      if (radius == 0)
      {
        c->Message(0, "Objects within this zone:");
      }
      else
      {
        c->Message(0, "Objects within %u units of your current location:", radius);
      }
      
      if (radius)
      {
        len = snprintf(query, sizeof(query),
          "SELECT id, xpos, ypos, zpos, heading, itemid, objectname, type, icon, unknown08, unknown10, unknown20"
          " FROM object"
          " WHERE (zoneid=%u)"
          " AND (version=%u)"
          " AND (xpos BETWEEN %.1f AND %.1f)"
          " AND (ypos BETWEEN %.1f AND %.1f)"
          " AND (zpos BETWEEN %.1f AND %.1f)"
          " ORDER BY id",
          zone->GetZoneID(),
          zone->GetInstanceVersion(),
          c->GetX() - radius,         // Yes, we're actually using a bounding box instead of a radius.
          c->GetX() + radius,         // Much less processing power used this way.
          c->GetY() - radius,
          c->GetY() + radius,
          c->GetZ() - radius,
          c->GetZ() + radius);
      }
      else
      {
        len = snprintf(query, sizeof(query),
          "SELECT id, xpos, ypos, zpos, heading, itemid, objectname, type, icon, unknown08, unknown10, unknown20"
          " FROM object"
          " WHERE (zoneid=%u)"
          " AND (version=%u)"
          " ORDER BY id",
          zone->GetZoneID(),
          zone->GetInstanceVersion());
      }

      if (database.RunQuery(query, len, errbuf, &result))
      {
  			while ((row = mysql_fetch_row(result)))
        {
          col = 0;
          id = atoi(row[col++]);
          od.x = atof(row[col++]);
          od.y = atof(row[col++]);
          od.z = atof(row[col++]);
          od.heading = atof(row[col++]);
          itemid = atoi(row[col++]);
          strncpy(od.object_name, row[col++], sizeof(od.object_name));
          od.object_name[sizeof(od.object_name) - 1] = '\0'; // Required if strlen(row[col++]) exactly == sizeof(object_name)

          od.object_type = atoi(row[col++]);
          icon = atoi(row[col++]);
          od.unknown008[0] = atoi(row[col++]);
          od.unknown008[1] = atoi(row[col++]);
          od.unknown020 = atoi(row[col++]);

          switch (od.object_type)
          {
            case 0: // Static Object
            case TempStaticType: // Static Object unlocked for changes
              if (od.unknown008[0] == 0) // Unknown08 field is optional Size parameter for static objects
              {
                od.unknown008[0] = 100;  // Static object default Size is 100%
              }

              c->Message(0,
                "- STATIC Object (%s): id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, size %u, solidtype %u, incline %u",
                (od.object_type == 0) ? "locked" : "unlocked", id, od.x, od.y, od.z, od.heading, od.object_name, od.unknown008[0], od.unknown008[1], od.unknown020);
              break;
            case OT_DROPPEDITEM: // Ground Spawn
              c->Message(0,
                "- TEMPORARY Object: id %u, x %.1f, y %.1f, z %.1f, h %.1f, itemid %u, model %s, icon %u",
                id, od.x, od.y, od.z, od.heading, itemid, od.object_name, icon);
              break;
            default: // All others == Tradeskill Objects
              c->Message(0,
                "- TRADESKILL Object: id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, type %u, icon %u",
                id, od.x, od.y, od.z, od.heading, od.object_name, od.object_type, icon);
              break;
          }

          iObjectsFound++;
        }

        mysql_free_result(result);
      }

      c->Message(0, "%u object%s found", iObjectsFound, (iObjectsFound == 1) ? "" : "s");
      break;
    case 'a': // Add Object
      // Insufficient or invalid arguments
      if ((sep->argnum < 3) || ((sep->arg[3][0] == '\0') && (sep->arg[4][0] < '0') && (sep->arg[4][0] > '9')))
      {
        c->Message(0, "Usage: (Static Object): #object Add [ObjectID] 0 Model [SizePercent] [SolidType] [Incline]");
        c->Message(0, "Usage: (Tradeskill Object): #object Add [ObjectID] TypeNum Model Icon");
        c->Message(0, "- Notes: Model must start with a letter, max length 16. SolidTypes = 0 (Solid), 1 (Sometimes Non-Solid)");

        return;
      }

      if (sep->argnum > 3)
      {
        // Model name in arg3?
        if ((sep->arg[3][0] <= '9') && (sep->arg[3][0] >= '0'))
        {
          // Nope, user must have specified ObjectID. Extract it.
          id = atoi(sep->arg[2]);

          col = 1; // Bump all other arguments one to the right. Model is in arg4.
        }
        else
        {
          // Yep, arg3 is non-numeric, ObjectID must be omitted and model must be arg3
          id = 0;
          col = 0;
        }
      }
      else
      {
        // Nope, only 3 args. Object ID must be omitted and arg3 must be model.
        id = 0;
        col = 0;
      }
      
      memset(&od, 0, sizeof(od));
      
      od.object_type = atoi(sep->arg[2 + col]);

      switch (od.object_type)
      {
        case 0: // Static Object
          if ((sep->argnum - col) > 3)
          {
            od.unknown008[0] = atoi(sep->arg[4 + col]); // Size specified

            if ((sep->argnum - col) > 4)
            {
              od.unknown008[1] = atoi(sep->arg[5 + col]); // SolidType specified

              if ((sep->argnum - col) > 5)
              {
                od.unknown020 = atoi(sep->arg[6 + col]); // Incline specified
              }
            }
          }
          break;
        case 1: // Ground Spawn
          c->Message(0, "ERROR: Object Type 1 is used for temporarily spawned ground spawns and dropped items, which are not supported with #object. See the 'ground_spawns' table in the database.");

          return;
          break;
        default: // Everything else == Tradeskill Object
          icon = ((sep->argnum - col) > 3) ? atoi(sep->arg[4 + col]) : 0;

          if (icon == 0)
          {
            c->Message(0, "ERROR: Required property 'Icon' not specified for Tradeskill Object");

            return;
          }
          break;
      }
      
      od.x = c->GetX();
      od.y = c->GetY();
      od.z = c->GetZ() - (c->GetSize() * 0.625f);
      od.heading = c->GetHeading() * 2.0f; // GetHeading() is half of actual. Compensate by doubling.

      if (id)
      {
        // ID specified. Verify that it doesn't already exist.
        
        len = snprintf(query, sizeof(query), "SELECT COUNT(*) FROM object WHERE ID=%u", id);

        // Already in database?
        if (database.RunQuery(query, len, errbuf, &result))
        {
          if ((row = mysql_fetch_row(result)) != NULL)
          {
            if (atoi(row[0]) > 0)
            {
              // Yep, in database already.

              id = 0;
            }
          }

          mysql_free_result(result);
        }

        if (id)
        {
          // Not in database. Already spawned, just not saved?
          if (entity_list.FindObject(id))
          {
            // Yep, already spawned.
            
            id = 0;
          }
        }

        if (id == 0)
        {
          c->Message(0, "ERROR: An object already exists with the id %u", id);
          
          return;
        }
      }

      // Verify no other objects already in this spot (accidental double-click of Hotkey?)
      len = snprintf(query, sizeof(query),
        "SELECT COUNT(*) FROM object "
        "WHERE (zoneid=%u) "
        "AND (version=%u) "
        "AND (posx BETWEEN %.1f AND %.1f) "
        "AND (posy BETWEEN %.1f AND %.1f) "
        "AND (posz BETWEEN %.1f AND %.1f)",
        zone->GetZoneID(),
        zone->GetInstanceVersion(),
        od.x - 0.2f, od.x + 0.2f,     // Yes, we're actually using a bounding box instead of a radius.
        od.y - 0.2f, od.y + 0.2f,     // Much less processing power used this way.
        od.z - 0.2f, od.z + 0.2f);    // It's pretty forgiving, though, allowing for close-proximity objects

      iObjectsFound = 0;
      if (database.RunQuery(query, len, errbuf, &result))
      {
        if ((row = mysql_fetch_row(result)) != NULL)
        {
          iObjectsFound = atoi(row[0]); // Number of nearby objects from database
        }

        mysql_free_result(result);
      }

      if (iObjectsFound == 0)
      {
        // No objects found in database too close. How about spawned but not yet saved?
        if (entity_list.FindNearbyObject(od.x, od.y, od.z, 0.2f))
        {
          iObjectsFound++;
        }
      }

      if (iObjectsFound)
      {
        c->Message(0, "ERROR: Object already at this location.");

        return;
      }

      // Strip any single quotes from objectname (SQL injection FTL!)
      strncpy(od.object_name, sep->arg[3 + col], sizeof(od.object_name));
      od.object_name[sizeof(od.object_name) - 1] = '\0'; // Required if strlen(arg) exactly == sizeof(object_name)

      len = strlen(od.object_name);
      for (col = 0; col < (int32)len; col++)
      {
        if (od.object_name[col] == '\'')
        {
          // Uh oh, 1337 h4x0r monkeying around! Strip that apostrophe!
          memcpy(&od.object_name[col], &od.object_name[col + 1], len - col);
          
          len--;
          col--;
        }
      }
      
      strupr(od.object_name);  // Model names are always upper-case.

      if ((od.object_name[0] < 'A') || (od.object_name[0] > 'Z'))
      {
        c->Message(0, "ERROR: Model name must start with a letter.");

        return;
      }

      if (id == 0)
      {
        // No ID specified. Get a best-guess next number from the database
        
        // If there's a problem retrieving an ID from the database, it'll end up being object # 1. No biggie.

        strncpy(query, "SELECT MAX(id) FROM object", sizeof(query));

        if (database.RunQuery(query, strlen(query), errbuf, &result))
        {
          if (row = mysql_fetch_row(result))
          {
            id = atoi(row[0]);
          }

          mysql_free_result(result);
        }

        id++;
      }

      // Make sure not to overwrite already-spawned objects that haven't been saved yet.
      while (o = entity_list.FindObject(id))
      {
        id++;
      }

      if (od.object_type == 0) // Static object
      {
        od.object_type = TempStaticType; // Temporary. We'll make it 0 when we Save
      }

      od.zone_id = zone->GetZoneID();
      od.zone_instance = zone->GetInstanceVersion();

      o = new Object(id, od.object_type, icon, od, NULL);

      // Add to our zone entity list and spawn immediately for all clients
      entity_list.AddObject(o, true);

      // Bump player back to avoid getting stuck inside new object

      // GetHeading() returns half of the actual heading, for some reason, so we'll double it here for computation
      x2 = 10.0f * sin(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
      y2 = 10.0f * cos(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
      c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading() * 2);

      c->Message(0, "Spawning object with tentative id %u at location (%.1f, %.1f, %.1f heading %.1f). Use '#object Save' to save to database when satisfied with placement.", id, od.x, od.y, od.z, od.heading);
      
      if (od.object_type == TempStaticType) // Temporary Static Object
      {
        c->Message(0, "- Note: Static Object will act like a tradeskill container and will not reflect size, solidtype, or incline values until you commit with '#object Save', after which it will be unchangeable until you use '#object Edit' and zone back in.");
      }
      break;
    case 'e': // Edit
      // Insufficient or invalid arguments
      if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) < 1))
      {
        c->Message(0, "Usage: #object Edit (ObjectID) [PropertyName] [NewValue]");
        c->Message(0, "- Static Object (Type 0) Properties: model, type, size, solidtype, incline");
        c->Message(0, "- Tradeskill Object (Type 2+) Properties: model, type, icon");

        return;
      }

      o = entity_list.FindObject(id);

      // Object already available in-zone?
      if (o)
      {
        // Yep, looks like we can make real-time changes.
        if (sep->argnum < 4)
        {
          // Or not. '#object Edit (ObjectID)' called without PropertyName and NewValue

          c->Message(0, "Note: Object %u already unlocked and ready for changes", id);
          
          return;
        }
      }
      else
      {
        // Object not found in-zone in a modifiable form. Check for valid matching circumstances.
        
        len = snprintf(query, sizeof(query), "SELECT zoneid, version, type FROM object WHERE id=%u", id);

        iObjectsFound = 0;
        if (database.RunQuery(query, len, errbuf, &result))
        {
          if (row = mysql_fetch_row(result))
          {
            od.zone_id = atoi(row[0]);
            od.zone_instance = atoi(row[1]);
            od.object_type = atoi(row[2]);
            
            iObjectsFound++;
          }

          mysql_free_result(result);
        }

        // Object ID not found?
        if (iObjectsFound == 0)
        {
          c->Message(0, "ERROR: Object %u not found", id);

          return;
        }
        
        // Object not in this zone?
        if (od.zone_id != zone->GetZoneID())
        {
          c->Message(0, "ERROR: Object %u not in this zone.", id);

          return;
        }

        // Object not in this instance?
        if (od.zone_instance != zone->GetInstanceVersion())
        {
          c->Message(0, "ERROR: Object %u not part of this instance version.", id);

          return;
        }

        switch (od.object_type)
        {
          case 0: // Static object needing unlocking
            // Convert to tradeskill object temporarily for changes

            len = snprintf(query, sizeof(query), "UPDATE object SET type=%u WHERE id=%u", TempStaticType, id);

            database.RunQuery(query, len);

            c->Message(0, "Static Object %u unlocked for editing. You must zone out and back in to make your changes, then commit them with '#object Save'.", id);

            if (sep->argnum >= 4)
            {
              c->Message(0, "NOTE: The change you specified has not been applied, since the static object had not been unlocked for editing yet.");
            }

            return;
            break;
          case OT_DROPPEDITEM:
            c->Message(0, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, which cannot be manipulated with #object. See the 'ground_spawns' table in the database.", id);

            return;
            break;
          case TempStaticType:
            c->Message(0, "ERROR: Object %u has been unlocked for editing, but you must zone out and back in for your client to refresh its object table before you can make changes to it.", id);

            return;
            break;
          default:
            // Unknown error preventing us from seeing the object in the zone.

            c->Message(0, "ERROR: Unknown problem attempting to manipulate object %u", id);

            return;
            break;
        }
      }

      // If we're here, we have a manipulable object ready for changes. 

      strlwr(sep->arg[3]); // Case insensitive PropertyName
      strupr(sep->arg[4]); // In case it's model name, which should always be upper-case
      
      // Read current object info for reference
      icon = o->GetIcon();
      o->GetObjectData(&od);

      // We'll be a little more picky with property names, to prevent errors. Check against the whole word.
      switch (sep->arg[3][0])
      {
        case 'm':
          if (strcmp(sep->arg[3], "model") == 0)
          {
            if ((sep->arg[4][0] < 'A') || (sep->arg[4][0] > 'Z'))
            {
              c->Message(0, "ERROR: Model names must begin with a letter.");

              return;
            }

            strncpy(od.object_name, sep->arg[4], sizeof(od.object_name));
            od.object_name[sizeof(od.object_name) - 1] = '\0'; // Required if strlen(arg) exactly == sizeof(object_name)

            o->SetObjectData(&od);

            c->Message(0, "Object %u now being rendered with model '%s'", id, od.object_name);
          }
          else
          {
            id = 0; // Setting ID to 0 will signify invalid input
          }
          break;
        case 't':
          if (strcmp(sep->arg[3], "type") == 0)
          {
            if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9'))
            {
              c->Message(0, "ERROR: Invalid type number");

              return;
            }

            od.object_type = atoi(sep->arg[4]);

            switch (od.object_type)
            {
              case 0:
                // Convert Static Object to temporary changeable type
                od.object_type = TempStaticType;
                c->Message(0, "Note: Static Object will still act like tradeskill object and will not reflect size, solidtype, or incline settings until committed to the database with '#object Save', after which it will be unchangeable until it is unlocked again with '#object Edit'.");
                break;
              case OT_DROPPEDITEM:
                c->Message(0, "ERROR: Object Type 1 is used for temporarily spawned ground spawns and dropped items, which are not supported with #object. See the 'ground_spawns' table in the database.");

                return;
                break;
              default:
                c->Message(0, "Object %u changed to Tradeskill Object Type %u", id, od.object_type);
                break;
            }

            o->SetType(od.object_type);
          }
          else
          {
            id = 0; // Setting ID to 0 will signify invalid input
          }
          break;
        case 's':
          if (strcmp(sep->arg[3], "size") == 0)
          {
            if (od.object_type != TempStaticType)
            {
              c->Message(0, "ERROR: Object %u is not a Static Object and does not support the Size property", id);

              return;
            }

            if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9'))
            {
              c->Message(0, "ERROR: Invalid size specified. Please enter a number.");

              return;
            }

            od.unknown008[0] = atoi(sep->arg[4]);
            o->SetObjectData(&od);

            if (od.unknown008[0] == 0) // 0 == unspecified == 100%
            {
              od.unknown008[0] = 100;
            }

            c->Message(0, "Static Object %u set to %u%% size. Size will take effect when you commit to the database with '#object Save', after which the object will be unchangeable until you unlock it again with '#object Edit' and zone out and back in.", id, od.unknown008[0]);
          }
          else if (strcmp(sep->arg[3], "solidtype") == 0)
          {
            if (od.object_type != TempStaticType)
            {
              c->Message(0, "ERROR: Object %u is not a Static Object and does not support the SolidType property", id);

              return;
            }

            if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9'))
            {
              c->Message(0, "ERROR: Invalid solidtype specified. Please enter a number.");

              return;
            }

            od.unknown008[1] = atoi(sep->arg[4]);
            o->SetObjectData(&od);

            c->Message(0, "Static Object %u set to SolidType %u. Change will take effect when you comit to the database with '#object Save'. Support for this property is on a per-model basis, mostly seen in smaller objects such as chests and tables.", id, od.unknown008[1]);
          }
          else
          {
            id = 0; // Setting ID to 0 will signify invalid input
          }
          break;
        case 'i':
          if (strcmp(sep->arg[3], "icon") == 0)
          {
            if ((od.object_type < 2) || (od.object_type == TempStaticType))
            {
              c->Message(0, "ERROR: Object %u is not a Tradeskill Object and does not support the Icon property", id);

              return;
            }

            if ((icon = atoi(sep->arg[4])) == 0)
            {
              c->Message(0, "ERROR: Invalid Icon specified. Please enter an icon number.");

              return;
            }

            o->SetIcon(icon);

            c->Message(0, "Tradeskill Object %u icon set to %u", id, icon);
          }
          else if (strcmp(sep->arg[3], "incline") == 0)
          {
            if (od.object_type != TempStaticType)
            {
              c->Message(0, "ERROR: Object %u is not a Static Object and does not support the Incline property", id);

              return;
            }

            if ((sep->arg[4][0] < '0') || (sep->arg[4][0] > '9'))
            {
              c->Message(0, "ERROR: Invalid Incline specified. Please enter a number. Normal range is 0-512.");

              return;
            }

            od.unknown020 = atoi(sep->arg[4]);
            o->SetObjectData(&od);

            c->Message(0, "Static Object %u set to %u incline. Incline will take effect when you commit to the database with '#object Save', after which the object will be unchangeable until you unlock it again with '#object Edit' and zone out and back in.", id, od.unknown020);
          }
          else
          {
            id = 0; // Setting ID to 0 will signify invalid input
          }
          break;
        default:
          id = 0; // Setting ID to 0 will signify invalid input
          break;
      }

      if (id == 0)
      {
        c->Message(0, "ERROR: Unrecognized property name: %s", sep->arg[3]);

        return;
      }

      // Repop object to have it reflect the change.
      app = new EQApplicationPacket();
      o->CreateDeSpawnPacket(app);
      entity_list.QueueClients(0, app);
      safe_delete(app);

      app = new EQApplicationPacket();
      o->CreateSpawnPacket(app);
      entity_list.QueueClients(0, app);
      safe_delete(app);
      break;
    case 'm': // Move
      if ((sep->argnum < 2) || // Not enough arguments
          ((id = atoi(sep->arg[2])) == 0) || // ID not specified
          (((sep->arg[3][0] < '0') || (sep->arg[3][0] > '9')) &&
           ((sep->arg[3][0] & 0xDF) != 'T') &&
           (sep->arg[3][0] != '-') && (sep->arg[3][0] != '.'))) // Location argument not specified correctly
      {
        c->Message(0, "Usage: #object Move (ObjectID) ToMe|(x y z [h])");
        
        return;
      }

      if (!(o = entity_list.FindObject(id)))
      {
        len = snprintf(query, sizeof(query), "SELECT zoneid, version, type FROM object WHERE id=%u", id);

        if ((!database.RunQuery(query, len, errbuf, &result)) || ((row = mysql_fetch_row(result)) == 0))
        {
          if (result)
          {
            mysql_free_result(result);
          }

          c->Message(0, "ERROR: Object %u not found", id);

          return;
        }

        od.zone_id = atoi(row[0]);
        od.zone_instance = atoi(row[1]);
        od.object_type = atoi(row[2]);

        mysql_free_result(result);

        if (od.zone_id != zone->GetZoneID())
        {
          c->Message(0, "ERROR: Object %u is not in this zone", id);

          return;
        }

        if (od.zone_instance != zone->GetInstanceVersion())
        {
          c->Message(0, "ERROR: Object %u is not in this instance version", id);

          return;
        }

        switch (od.object_type)
        {
          case 0:
            c->Message(0, "ERROR: Object %u is not yet unlocked for editing. Use '#object Edit' then zone out and back in to move it.", id);

            return;
            break;
          case TempStaticType:
            c->Message(0, "ERROR: Object %u has been unlocked for editing, but you must zone out and back in before your client sees the change and will allow you to move it.", id);

            return;
            break;
          case 1:
            c->Message(0, "ERROR: Object %u is a temporary spawned object and cannot be manipulated with #object. See the 'ground_spawns' table in the database.", id);

            return;
            break;
          default:
            c->Message(0, "ERROR: Object %u not located in zone.", id);

            return;
            break;
        }
      }

      if ((sep->arg[3][0] & 0xDF) == 'T') // Move To Me
      {
        od.x = c->GetX();
        od.y = c->GetY();
        od.z = c->GetZ() - (c->GetSize() * 0.625f); // Compensate for #loc bumping up Z coordinate by 62.5% of character's size.
        
        o->SetHeading(c->GetHeading() * 2.0f); // Compensate for GetHeading() returning half of actual

        // Bump player back to avoid getting stuck inside object

        // GetHeading() returns half of the actual heading, for some reason
        x2 = 10.0f * sin(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
        y2 = 10.0f * cos(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
        c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading() * 2.0f);
      }
      else // Move to x, y, z [h]
      {
        od.x = atof(sep->arg[3]);
        if (sep->argnum > 3)
        {
          od.y = atof(sep->arg[4]);
        }
        else
        {
          o->GetLocation(NULL, &od.y, NULL);
        }

        if (sep->argnum > 4)
        {
          od.z = atof(sep->arg[5]);
        }
        else
        {
          o->GetLocation(NULL, NULL, &od.z);
        }

        if (sep->argnum > 5)
        {
          o->SetHeading(atof(sep->arg[6]));
        }
      }

      o->SetLocation(od.x, od.y, od.z);

      // Despawn and respawn object to reflect change
      app = new EQApplicationPacket();
      o->CreateDeSpawnPacket(app);
      entity_list.QueueClients(0, app);
      safe_delete(app);

      app = new EQApplicationPacket();
      o->CreateSpawnPacket(app);
      entity_list.QueueClients(0, app);
      safe_delete(app);
      break;
    case 'r': // Rotate
      // Insufficient or invalid arguments
      if ((sep->argnum < 3) || ((id = atoi(sep->arg[2])) == 0))
      {
        c->Message(0, "Usage: #object Rotate (ObjectID) (Heading, 0-512)");

        return;
      }

      if ((o = entity_list.FindObject(id)) == NULL)
      {
        c->Message(0, "ERROR: Object %u not found in zone, or is a static object not yet unlocked with '#object Edit' for editing.", id);

        return;
      }

      o->SetHeading(atof(sep->arg[3]));

      // Despawn and respawn object to reflect change
      app = new EQApplicationPacket();
      o->CreateDeSpawnPacket(app);
      entity_list.QueueClients(0, app);
      safe_delete(app);

      app = new EQApplicationPacket();
      o->CreateSpawnPacket(app);
      entity_list.QueueClients(0, app);
      safe_delete(app);
      break;
    case 's': // Save
      // Insufficient or invalid arguments
      if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) == 0))
      {
        c->Message(0, "Usage: #object Save (ObjectID)");

        return;
      }

      o = entity_list.FindObject(id);

      sprintf(query, "SELECT zoneid, version, type FROM object WHERE id=%u", id);

      od.zone_id = 0;
      od.zone_instance = 0;
      od.object_type = 0;
      
      // If this ID isn't in the database yet, it's a new object
      bNewObject = true;
      if (database.RunQuery(query, strlen(query), errbuf, &result))
      {
        if (row = mysql_fetch_row(result))
        {
          od.zone_id = atoi(row[0]);
          od.zone_instance = atoi(row[1]);
          od.object_type = atoi(row[2]);

          // ID already in database. Not a new object.
          bNewObject = false;
        }

        mysql_free_result(result);
      }

      if (!o)
      {
        // Object not found in zone. Can't save an object we can't see.

        if (bNewObject)
        {
          c->Message(0, "ERROR: Object %u not found", id);

          return;
        }
        
        if (od.zone_id != zone->GetZoneID())
        {
          c->Message(0, "ERROR: Wrong Object ID. %u is not part of this zone.", id);

          return;
        }

        if (od.zone_instance != zone->GetInstanceVersion())
        {
          c->Message(0, "ERROR: Wrong Object ID. %u is not part of this instance version.", id);

          return;
        }

        if (od.object_type == 0)
        {
          c->Message(0, "ERROR: Static Object %u has already been committed. Use '#object Edit %u' and zone out and back in to make changes.", id, id);

          return;
        }

        if (od.object_type == 1)
        {
          c->Message(0, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, which is not supported with #object. See the 'ground_spawns' table in the database.", id);

          return;
        }

        c->Message(0, "ERROR: Object %u not found.", id);
        
        return;
      }

      if ((od.zone_id > 0) && (od.zone_id != zone->GetZoneID()))
      {
        // Oops! Another GM already saved an object with our id from another zone.
        // We'll have to get a new one.
        
        id = 0;
      }

      if ((id > 0) && (od.zone_instance != zone->GetInstanceVersion()))
      {
        // Oops! Another GM already saved an object with our id from another instance.
        // We'll have to get a new one.
        
        id = 0;
      }

      // If we're asking for a new ID, it's a new object.
      bNewObject |= (id == 0);

      o->GetObjectData(&od);
      od.object_type = o->GetType();
      icon = o->GetIcon();

      // We're committing to the database now. Return temporary object type to actual.
      if (od.object_type == TempStaticType)
      {
        od.object_type = 0;
      }
      
      if (bNewObject)
      {
        if (id == 0)
        {
          len = snprintf(query, sizeof(query),
            "INSERT INTO object (zoneid, version, xpos, ypos, zpos, heading, objectname, type, icon, unknown08, unknown10, unknown20)"
            " VALUES (%u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)",
            zone->GetZoneID(), zone->GetInstanceVersion(),
            od.x, od.y, od.z, od.heading,
            od.object_name, od.object_type, icon,
            od.unknown008[0], od.unknown008[1], od.unknown020);
        }
        else
        {
          len = snprintf(query, sizeof(query),
            "INSERT INTO object (id, zoneid, version, xpos, ypos, zpos, heading, objectname, type, icon, unknown08, unknown10, unknown20)"
            " VALUES (%u, %u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)",
            id, zone->GetZoneID(), zone->GetInstanceVersion(),
            od.x, od.y, od.z, od.heading,
            od.object_name, od.object_type, icon,
            od.unknown008[0], od.unknown008[1], od.unknown020);
        }
      }
      else
      {
        len = snprintf(query, sizeof(query),
          "UPDATE object SET "
          " zoneid=%u, version=%u,"
          " xpos=%.1f, ypos=%.1f, zpos=%.1f, heading=%.1f,"
          " objectname='%s', type=%u, icon=%u,"
          " unknown08=%u, unknown10=%u, unknown20=%u"
          " WHERE ID=%u",
          zone->GetZoneID(), zone->GetInstanceVersion(),
          od.x, od.y, od.z, od.heading,
          od.object_name, od.object_type, icon,
          od.unknown008[0], od.unknown008[1], od.unknown020,
          id);
      }

      if (!database.RunQuery(query, len, errbuf, 0, &col, &newid))
      {
        col = 0;
      }

      if (col == 0)
      {
        if (errbuf[0] == '\0')
        {
          // No change made, but no error message given
          c->Message(0, "Database Error: Could not save change to Object %u", id);
        }
        else
        {
          c->Message(0, "Database Error: %s", errbuf);
        }

        return;
      }
      else
      {
        if (bNewObject)
        {
          if (newid == id)
          {
            c->Message(0, "Saved new Object %u to database", id);
          }
          else
          {
            c->Message(0, "Saved Object. NOTE: Database returned a new ID number for object: %u", newid);
            id = newid;
          }
        }
        else
        {
          c->Message(0, "Saved changes to Object %u", id);

          newid = id;
        }
      }

      if (od.object_type == 0)
      {
        // Static Object - Respawn as nonfunctional door
        
        app = new EQApplicationPacket();
        o->CreateDeSpawnPacket(app);
        entity_list.QueueClients(0, app);
        safe_delete(app);

        entity_list.RemoveObject(o->GetID());

        memset(&door, 0, sizeof(door));

        strncpy(door.zone_name, zone->GetShortName(), sizeof(door.zone_name));
        door.zone_name[sizeof(door.zone_name) - 1] = '\0'; // Required if strlen(shortname) exactly == sizeof(door.zone_name)

        door.db_id = 1000000000 + id; // Out of range of normal use for doors.id
        door.door_id = -1; // Client doesn't care if these are all the same door_id
        door.pos_x = od.x; // xpos
        door.pos_y = od.y; // ypos
        door.pos_z = od.z; // zpos
        door.heading = od.heading; // heading

        strncpy(door.door_name, od.object_name, sizeof(door.door_name)); // objectname
        door.door_name[sizeof(door.door_name) - 1] = '\0'; // Required if strlen(object_name) exactly == sizeof(door.door_name)

        // Strip trailing "_ACTORDEF" if present. Client won't accept it for doors.
        len = strlen(door.door_name);
        if ((len > 9) && (memcmp(&door.door_name[len - 9], "_ACTORDEF", 10) == 0))
        {
          door.door_name[len - 9] = '\0';
        }
        
        memcpy(door.dest_zone, "NONE", 5);
        
        if ((door.size = od.unknown008[0]) == 0) // unknown08 = optional size percentage
        {
          door.size = 100;
        }

        switch (door.opentype = od.unknown008[1]) // unknown10 = optional request_nonsolid (0 or 1 or experimental number)
        {
          case 0:
            door.opentype = 31;
            break;
          case 1:
            door.opentype = 9;
            break;
        }

        door.incline = od.unknown020; // unknown20 = optional incline value

        doors = new Doors(&door);
        entity_list.AddDoor(doors);

        app = new EQApplicationPacket(OP_SpawnDoor, sizeof(Door_Struct));
        ds = (Door_Struct*)app->pBuffer;

        memset(ds, 0, sizeof(Door_Struct));
		    memcpy(ds->name, door.door_name, 32);
		    ds->xPos = door.pos_x;
		    ds->yPos = door.pos_y;
		    ds->zPos = door.pos_z;
		    ds->heading = door.heading;
		    ds->incline = door.incline;
		    ds->size = door.size;
		    ds->doorId = door.door_id;
        ds->opentype = door.opentype;
        ds->unknown0052[9] = 1; // *ptr-1 and *ptr-3 from EntityList::MakeDoorSpawnPacket()
        ds->unknown0052[11] = 1;

        entity_list.QueueClients(0, app);
        safe_delete(app);

        c->Message(0, "NOTE: Object %u is now a static object, and is unchangeable. To make future changes, use '#object Edit' to convert it to a changeable form, then zone out and back in.", id);
      }
      break;
    case 'c': // Copy
      // Insufficient or invalid arguments
      if ((sep->argnum < 3) || (((sep->arg[2][0] & 0xDF) != 'A') && ((sep->arg[2][0] < '0') || (sep->arg[2][0] > '9'))))
      {
        c->Message(0, "Usage: #object Copy All|(ObjectID) (InstanceVersion)");
        c->Message(0, "- Note: Only objects saved in the database can be copied to another instance.");

        return;
      }

      od.zone_instance = atoi(sep->arg[3]);

      if (od.zone_instance == zone->GetInstanceVersion())
      {
        c->Message(0, "ERROR: Source and destination instance versions are the same.");

        return;
      }

      if ((sep->arg[2][0] & 0xDF) == 'A')
      {
        // Copy All

        len = snprintf(query, sizeof(query),
          "INSERT INTO object (zoneid, version, xpos, ypos, zpos, heading, itemid, objectname, type, icon, unknown08, unknown10, unknown20)"
          " SELECT zoneid, %u, xpos, ypos, zpos, heading, itemid, objectname, type, icon, unknown08, unknown10, unknown20"
          " FROM object"
          " WHERE (zoneid=%u) AND (version=%u)",
          od.zone_instance, zone->GetZoneID(), zone->GetInstanceVersion());

        if (database.RunQuery(query, len, errbuf, 0, &col))
        {
          c->Message(0, "Copied %u object%s into instance version %u", col, (col == 1) ? "" : "s", od.zone_instance);
        }
        else
        {
          if (errbuf[0] == '\0')
          {
            c->Message(0, "Database Error: No objects were copied into instance version %u", od.zone_instance);
          }
          else
          {
            c->Message(0, "Database Error: %s", errbuf);
          }
        }
      }
      else
      {
        // Copy ObjectID
        id = atoi(sep->arg[2]);
        
        len = snprintf(query, sizeof(query),
          "INSERT INTO object (zoneid, version, xpos, ypos, zpos, heading, itemid, objectname, type, icon, unknown08, unknown10, unknown20)"
          " SELECT zoneid, %u, xpos, ypos, zpos, heading, itemid, objectname, type, icon, unknown08, unknown10, unknown20"
          " FROM object"
          " WHERE (id=%u) AND (zoneid=%u) AND (version=%u)",
          od.zone_instance, id, zone->GetZoneID(), zone->GetInstanceVersion());

        if ((database.RunQuery(query, len, errbuf, 0, &col)) && (col > 0))
        {
          c->Message(0, "Copied Object %u into instance version %u", id, od.zone_instance);
        }
        else
        {
          // Couldn't copy the object.
          
          if (errbuf[0] == '\0')
          {
            // No database error returned. See if we can figure out why.

            len = snprintf(query, sizeof(query), "SELECT zoneid, version FROM object WHERE id=%u", id);

            if (database.RunQuery(query, len, errbuf, &result))
            {
              if (row = mysql_fetch_row(result))
              {
                // Wrong ZoneID?
                if (atoi(row[0]) != zone->GetZoneID())
                {
                  mysql_free_result(result);

                  c->Message(0, "ERROR: Object %u is not part of this zone.", id);
                  
                  return;
                }

                // Wrong Instance Version?
                if (atoi(row[1]) != zone->GetInstanceVersion())
                {
                  mysql_free_result(result);

                  c->Message(0, "ERROR: Object %u is not part of this instance version.", id);
                  
                  return;
                }
                
                // Well, NO clue at this point. Just let 'em know something screwed up.
                mysql_free_result(result);

                c->Message(0, "ERROR: Unknown database error copying Object %u to instance version %u", id, od.zone_instance);
                  
                return;
              }
              
              mysql_free_result(result);
            }

            // Typo?
            c->Message(0, "ERROR: Object %u not found", id);
          }
          else
          {
            c->Message(0, "Database Error: %s", errbuf);
          }
        }
      }
      break;
    case 'd': // Delete
      if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) <= 0))
      {
        c->Message(0, "Usage: #object Delete (ObjectID) -- NOTE: Object deletions are permanent and cannot be undone!");

        return;
      }

      o = entity_list.FindObject(id);

      if (o)
      {
        // Object found in zone.

        app = new EQApplicationPacket();
        o->CreateDeSpawnPacket(app);
        entity_list.QueueClients(NULL, app);
        
        entity_list.RemoveObject(o->GetID());

        // Verifying ZoneID and Version in case someone else ended up adding an object with our ID
        // from a different zone/version. Don't want to delete someone else's work.
        sprintf(query, "DELETE FROM object WHERE (id=%u) AND (zoneid=%u) AND (version=%u) LIMIT 1", id, zone->GetZoneID(), zone->GetInstanceVersion());
        database.RunQuery(query, strlen(query));
        
        c->Message(0, "Object %u deleted", id);
      }
      else
      {
        // Object not found in zone.

        sprintf(query, "SELECT type FROM object WHERE (id=%u) AND (zoneid=%u) AND (version=%u) LIMIT 1", id, zone->GetZoneID(), zone->GetInstanceVersion());

        if (database.RunQuery(query, strlen(query), errbuf, &result))
        {
          if (row = mysql_fetch_row(result))
          {
            switch (atoi(row[0]))
            {
              case 0: // Static Object
                mysql_free_result(result);

                sprintf(query, "DELETE FROM object WHERE (id=%u) AND (zoneid=%u) AND (version=%u) LIMIT 1", id, zone->GetZoneID(), zone->GetInstanceVersion());
                database.RunQuery(query, strlen(query));

                c->Message(0, "Object %u deleted. NOTE: This static object will remain for anyone currently in the zone until they next zone out and in.", id);

                mysql_free_result(result);

                return;
                break;
              case 1: // Temporary Spawn
                c->Message(0, "ERROR: Object %u is a temporarily spawned ground spawn or dropped item, which is not supported with #object. See the 'ground_spawns' table in the database.", id);

                mysql_free_result(result);

                return;
                break;
            }
          }

          mysql_free_result(result);
        }
        
        c->Message(0, "ERROR: Object %u not found in this zone or instance!", id);
      }
      break;
    case 'u': // Undo - Reload object from database to undo changes
      // Insufficient or invalid arguments
      if ((sep->argnum < 2) || ((id = atoi(sep->arg[2])) == 0))
      {
        c->Message(0, "Usage: #object Undo (ObjectID) -- Reload object from database, undoing any changes you have made");

        return;
      }

      o = entity_list.FindObject(id);

      if (!o)
      {
        c->Message(0, "ERROR: Object %u not found in zone in a manipulable form. No changes to undo.", id);

        return;
      }

      if (o->GetType() == OT_DROPPEDITEM)
      {
        c->Message(0, "ERROR: Object %u is a temporary spawned item and cannot be manipulated with #object. See the 'ground_spawns' table in the database.", id);

        return;
      }

      // Despawn current item for reloading from database
      app = new EQApplicationPacket();
      o->CreateDeSpawnPacket(app);
      entity_list.QueueClients(0, app);
      entity_list.RemoveObject(o->GetID());
      safe_delete(app);

      len = snprintf(query, sizeof(query),
        "SELECT xpos, ypos, zpos, heading, objectname, type, icon, unknown08, unknown10, unknown20"
        " FROM object WHERE id=%u", id);

      if ((!database.RunQuery(query, len, errbuf, &result)) || ((row = mysql_fetch_row(result)) == 0))
      {
        if (result)
        {
          mysql_free_result(result);
        }

        if (errbuf[0] == '\0')
        {
          c->Message(0, "Database Error: Could not retrieve Object %u from object table.", id);

          return;
        }

        c->Message(0, "Database Error: %s", errbuf);

        return;
      }

      memset(&od, 0, sizeof(od));

      col = 0;
      od.x = atof(row[col++]);
      od.y = atof(row[col++]);
      od.z = atof(row[col++]);
      od.heading = atof(row[col++]);
      strncpy(od.object_name, row[col++], sizeof(od.object_name));
      od.object_name[sizeof(od.object_name) - 1] = '\0'; // Required if strlen(row[col++]) exactly == sizeof(object_name)
      od.object_type = atoi(row[col++]);
      icon = atoi(row[col++]);
      od.unknown008[0] = atoi(row[col++]);
      od.unknown008[1] = atoi(row[col++]);
      od.unknown020 = atoi(row[col++]);

      if (od.object_type == 0)
      {
        od.object_type = TempStaticType;
      }

      o = new Object(id, od.object_type, icon, od, NULL);
      entity_list.AddObject(o, true);

      c->Message(0, "Object %u reloaded from database.", id);
      break;
    default: // Unrecognized command
      c->Message(0, usage_string);
      break;
  }
}
In addition to changing the above-noted MS-specific function calls to the POSIX portable versions, I took advantage of the snprintf() function returning the assembled string's length to eliminate several unnecessary strlen() calls.

Heh, VC++ gave me "deprecated function" warnings after switching to the POSIX function usage. *shakefist @ MS*

Let me know if there's anything else gcc doesn't like.
Reply With Quote