Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

Reply
 
Thread Tools Display Modes
  #1  
Old 07-16-2009, 04:11 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

Commented that while out for now to try to get around it and now getting these compile errors:

Code:
command.cpp:13397: error: ‘_strlwr’ was not declared in this scope
command.cpp:13447: error: ‘sprintf_s’ was not declared in this scope
command.cpp:13458: error: ‘sprintf_s’ was not declared in this scope
command.cpp:13472: error: ‘_TRUNCATE’ was not declared in this scope
command.cpp:13472: error: ‘strncpy_s’ was not declared in this scope
command.cpp:13595: error: ‘sprintf_s’ was not declared in this scope
command.cpp:13600: warning: suggest parentheses around assignment used as truth value
command.cpp:13644: error: ‘sprintf_s’ was not declared in this scope
command.cpp:13649: warning: suggest parentheses around assignment used as truth value
command.cpp:13674: error: ‘_TRUNCATE’ was not declared in this scope
command.cpp:13674: error: ‘strncpy_s’ was not declared in this scope
command.cpp:13676: warning: comparison between signed and unsigned integer expressions
command.cpp:13688: error: ‘_strupr’ was not declared in this scope
command.cpp:13703: error: ‘strcpy_s’ was not declared in this scope
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #2  
Old 07-16-2009, 06:03 AM
Kobaz
Hill Giant
 
Join Date: Nov 2008
Location: Gold Coast, Oz
Posts: 119
Default

I've hit this in my "trying to learn C++ adventure". The *_s functions are Microsoft only. For sprintf_s you might try the POSIX snprintf:

Code:
int snprintf(char *str, size_t size, const char *format, ...);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       snprintf(), vsnprintf(): _BSD_SOURCE || _XOPEN_SOURCE >= 500 ||
       _ISOC99_SOURCE; or cc -std=c99

 The  functions  snprintf()  and  vsnprintf()  write  at most size bytes
       (including the trailing null byte ('\0')) to str.
This is from the man pages with glibc version 2.2
Reply With Quote
  #3  
Old 07-16-2009, 09:47 AM
Shendare
Dragon
 
Join Date: Apr 2009
Location: California
Posts: 814
Default

Gah, portability issues from Microsoft, how utterly shocking.

No biggie, let me find the more portable versions of all of the functions that appear to be MS-specific and I'll post a quick diff here.
Reply With Quote
  #4  
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
  #5  
Old 07-16-2009, 12:57 PM
Secrets's Avatar
Secrets
Demi-God
 
Join Date: May 2007
Location: b
Posts: 1,449
Default

I'll give this a try today. Nice work!
Reply With Quote
  #6  
Old 07-16-2009, 01:23 PM
Shendare
Dragon
 
Join Date: Apr 2009
Location: California
Posts: 814
Default

On a read-through pass, I noticed a cosmetic glitch.

Line 13628 in command.cpp sends an error message that tries to refer to 'id' after it has been reset to 0.

The fix:

Code:
...
        if (id == 0)
        {
-           c->Message(0, "ERROR: An object already exists with the id %u", id);
+           c->Message(0, "ERROR: An object already exists with the id %u", atoi(sep->arg[2]));

          return;
        }
...
Reply With Quote
  #7  
Old 07-16-2009, 01:35 PM
Shendare
Dragon
 
Join Date: Apr 2009
Location: California
Posts: 814
Default

Minor typo on Line 13980:

Code:
            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]);
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 11:08 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3