• Hello everyone! If you would like to become an official Kush member in our discord and get some awesome perks or just wish to join our monthly raffles head on over to our Patreon see link below. Signing up helps us out a lot, also like to say welcome to the site and poke around! -KushGames

Karana (x3 XP, Bots, Speed) Server.

Oh, also noticed that the Beastlord Epic 1.0 is the only one that still has a recommended level on it (46). So while every other class gets full stats on theirs at lvl 1.. the poor poor Bst is stuck with 1... poor thing.... hehe.
Could be wrong as I didnt check out every single one.. but if its not its REALLY close :)


Staff member
I just checked the spells file from the OP post my pc shows it in english. Wish more ppl would chime in so we can see what's up better.

On a side note what client are you using for eq?
RoF2.. and thats really odd.. I downloaded it and its definitely NOT English lmao
I will try it again.. My PC could just be spazzing.. who knows
Ok, so in the spirit of not giving my PC a chance to F it up, I didnt download it again, I just opened it and copy & pasted it to my file.. hahah
And yes, that was definitely English
Aannnd... its still doing it, but only on 1 buff now. Ah well.. as long as it still gives me the buff.. at this point I will just roll with it.. haha.
Not sure what other things the spell file changes so I will keep the one from here and hope for the best :)
Well, had that one already, but I reinstalled them just in case. Haha, I dunno what issue mine is having. Just no clue.. Thanks for trying to help me sort it.


Staff member
Added new quest api to lua and perl to allow quests to enable/disable mob processing in empty zones.

This topic in general deserves some discussion. In empty zones, mob processing typically does not take place, but quest timers do fire. Over the past year I have added multiple enhancements to this, and this is the latest of those enhancements.
Calling the quest::processmobswhilezoneempty(1/0) or lua counterpart eq.process_mobs_while_zone_empty(true/false) from a quest file enables or disables normal processing of mobs (movement, aggro, etc) in a zone, even when that zone is empty. Without these calls, a quest that say, happens once an hour and involves mobs spawning, moving and attacking (each other) would operate correctly in an empty zone.
In the past few months I added these other enhancements along the same line:
A rule that allows mob processing to continue for a set # of seconds after the zone goes empty. This allows mobs that chased someone to a zone line for example, to move back rather than just standing at the zone line.
RULE_INT(Zone, SecondsBeforeIdle, 60, "Seconds before IDLE_WHEN_EMPTY define kicks in")
Allowing grid types 4 and 6 to always process, even when zone is empty.
This change allows mobs like Fippy that spawn and run a fixed grid and despawn to complete their run on time, rather than always running the second someone enters a zone that was empty. This could be used for boats on servers, if they are placed on one of these types of grids.
This is the current logic that dictates mob processing. So mob processing occurs if the flag process_mobs_while_empty is set (this is the new quest api I just added), there are clients in the zone, the mob is on a grid that despawns at the end, or the zone is settling after all clients have left (my rule I added above).
if (zone->process_mobs_while_empty || numclients > 0 ||mob->GetWanderType() == 4 || mob->GetWanderType() == 6 ||mob_settle_timer->Enabled())
Virtual Zone Points (ZoneLines)
This PR adds support for virtual zone points
Because the zone points that are sent to the client use an internal index lookup, it does not really give us power over determining or creating zone points in some cases. While this works in most of the data we've collected, there are scenarios where we'd like to create zone points entirely server side

We've added 3 new fields to the zone_points table is_virtual width height
When a zone point is virtual it follows different rules, a box is created using the x, y, z with the width and the height into consideration
New command #showzonepoints will list all of the zone points in a zone whether they are client side driven or virtual. It will also show a physical representation of the zone points in game

Enhanced DevTools Menu
DevTools menu now displays whenever a GM enters a zone, it also has many more tools available

Loot Drop Filtering Adjustments
  • Add new logging category "Loot"
  • Adds fields npc_min_level and npc_max_level to lootdrop_entries which runs checks against the current NPC as long as the field values are non-zero
  • Fields minlevel and maxlevel are renamed to trivial_min_level and trivial_max_level respectively for lootdrop_entries for item drops to be filtered based on the player level
  • Before, trivial_max_level defaulted to 127 in the database, this became an issue on some servers and some scenarios, the default has been adjusted to simply be 0 and only check if the value is non-zero
This PR / changelog covers many things; it would be preferred to not have a PR this large ever but considering the sweeping changes, it needed very careful testing, coordination and rollout
Expansions Support / Content Filtering

Multi Tenancy

Repository Pattern (Developer Tool)


This PR has been running on PEQ for over 3 months stable
PEQ Editor Accompanying Branch (JJ)
  • Add Merchants and ZonePoints logging categories
  • Add fix for object display issues on RoF2 where objects would float, we are using GroundZ to correct these. Previous clients auto corrected objects and snapped them to the ground level, newer clients do not do this
  • Added WorldContentService which has the responsibility of setting / getting content context for the server, expansions, flags etc.
  • Added an optional second database connection to the config "content_database" that will use a separate database connection for queries that target content tables. If none is specified it falls back to the default connection pointer
  • Added connection labels to MySQL connect messages
  • Added magical global helper methods ZoneID(string short_name) ZoneName(zone_id) ZoneLongName(zone_id) which are all pre-loaded in memory to be used at the zone or world level for common needs
  • Added official single source of truth in database_schema.h which defines different table types player, content, server, state, queryserv, login, version
  • Fix issues with world CLI interface returning exit code 1 (error) versus exit code 0 (success)
  • Saylinks are added to #fi command for summoning items
Performance Improvements
  • Task goallists now bulk load
  • Spawns prefer bulk loading
  • Improvements to grid loading logic (bulk load)
New Commands
  • Added in-command #gearup - which takes in tooling data to automatically gear a GM test toon with expansion specific best-in-class gear
  • Added in-game command #copycharacter [source_char_name] [dest_char_name] [dest_account_name]
  • Added console CLI command via world character:copy-character
Connection Label Messages
[ZoneServer] [Info] [MySQL] Connection [default] database [peq] at [mariadb]:[3306][ZoneServer] [Info] [MySQL] Connection [content] database [peq_content] at [content-cdn.projecteq.net]:[16033]
New methods available to support expansion & content filtering
quest::is_content_flag_enabled(string flag_name)quest::set_content_flag(string flag_name, bool enabled)
eq.is_content_flag_enabled(string flag_name)eq.set_content_flag(string flag_name, bool enabled)
  • Heavily reducing the mental overhead of having to interact with the database at the programmatic level
  • It creates an object representation of our tables (persistence layer) with a 1:1 mapping with what is in the source by having row values stored in a struct (Data Transfer Object)
  • We reduce manual overhead by having code generation generate our database table representations
  • Ease of maintenance; whenever we make schema changes, rerunning the repository updates fields and subsequent methods keeping code changes minimal
Methods Implemented
  • Single insertion covered by InsertOne
  • Single update covered by UpdateOne
  • Single delete covered by DeleteOne
  • Single select covered by FindOne
  • Bulk selection method via filtered GetWhere(std::string where_filter)
  • Bulk deletion method via filtered DeleteWhere(std::string where_filter)
  • Bulk insertion methods handled automatically via InsertMany
  • Select all covered by All
Code Generation
This is a result of the repository generator located at
# Helpperl ~/code/utils/scripts/generators/repository-generator.pl [server-location] [table_name|all]
# Exampleperl ~/code/utils/scripts/generators/repository-generator.pl ~/server/ all
Example Use (How do I use this?)
Starting at line 358 in the following Gist is an example CLI command that uses all of the above methods as examples; above line 358 is an example of what the a repository can look like
Add hot reload saylinks.
Completely overhauled cross zone and world wide methods in quest API.

quest::crosszonecastspellbycharid(character_id, spell_id);quest::crosszonecastspellbygroupid(group_id, spell_id);quest::crosszonecastspellbyraidid(raid_id, spell_id);quest::crosszonecastspellbyguildid(guild_id, spell_id);quest::crosszonedisabletaskbycharid(character_id, task_id);quest::crosszonedisabletaskbygroupid(group_id, task_id);quest::crosszonedisabletaskbyraidid(raid_id, task_id);quest::crosszonedisabletaskbyguildid(guild_id, task_id);quest::crosszoneenabletaskbycharid(character_id, task_id);quest::crosszoneenabletaskbygroupid(group_id, task_id);quest::crosszoneenabletaskbyraidid(raid_id, task_id);quest::crosszoneenabletaskbyguildid(guild_id, task_id);quest::crosszonefailtaskbycharid(character_id, task_id);quest::crosszonefailtaskbygroupid(group_id, task_id);quest::crosszonefailtaskbyraidid(raid_id, task_id);quest::crosszonefailtaskbyguildid(guild_id, task_id);quest::crosszonemoveinstancebycharid(character_id, instance_id);quest::crosszonemoveinstancebygroupid(group_id, instance_id);quest::crosszonemoveinstancebyraidid(raid_id, instance_id);quest::crosszonemoveinstancebyguildid(guild_id, instance_id);quest::crosszoneremovespellbycharid(character_id, spell_id);quest::crosszoneremovespellbygroupid(group_id, spell_id);quest::crosszoneremovespellbyraidid(raid_id, spell_id);quest::crosszoneremovespellbyguildid(guild_id, spell_id);quest::crosszoneresetactivitybycharid(character_id, task_id, activity_id);quest::crosszoneresetactivitybygroupid(group_id, task_id, activity_id);quest::crosszoneresetactivitybyraidid(raid_id, task_id, activity_id);quest::crosszoneresetactivitybyguildid(guild_id, task_id, activity_id);quest::crosszoneupdateactivitybycharid(character_id, task_id, activity_id, activity_count);quest::crosszoneupdateactivitybygroupid(group_id, task_id, activity_id, activity_count);quest::crosszoneupdateactivitybyraidid(raid_id, task_id, activity_id, activity_count);quest::crosszoneupdateactivitybyguildid(guild_id, task_id, activity_id, activity_count);quest::worldwideassigntask(task_id, enforce_level_requirement, min_status, max_status);quest::worldwidecastspell(spell_id, min_status, max_status);quest::worldwidedisabletask(task_id, min_status, max_status);quest::worldwideenabletask(task_id, min_status, max_status);quest::worldwidefailtask(task_id, min_status, max_status);quest::worldwidemessage(type, message, min_status, max_status);quest::worldwidemove(zone_short_name, min_status, max_status);quest::worldwidemoveinstance(instance_id, min_status, max_status);quest::worldwideremovespell(spell_id, min_status, max_status);quest::worldwideremovetask(task_id, min_status, max_status);quest::worldwideresetactivity(task_id, activity_id, min_status, max_status);quest::worldwidesetentityvariableclient(variable_name, variable_value, min_status, max_status);quest::worldwidesetentityvariablenpc(variable_name, variable_value);quest::worldwidesignalclient(signal, min_status, max_status);quest::worldwidesignalnpc(signal);quest::worldwideupdateactivity(task_id, activity_id, activity_count, min_status, max_status);
eq.cross_zone_cast_spell_by_char_id(character_id, spell_id);eq.cross_zone_cast_spell_by_group_id(group_id, spell_id);eq.cross_zone_cast_spell_by_raid_id(raid_id, spell_id);eq.cross_zone_cast_spell_by_guild_id(guild_id, spell_id);eq.cross_zone_disable_task_by_char_id(character_id, task_id);eq.cross_zone_disable_task_by_group_id(group_id, task_id);eq.cross_zone_disable_task_by_raid_id(raid_id, task_id);eq.cross_zone_disable_task_by_guild_id(guild_id, task_id);eq.cross_zone_enable_task_by_char_id(character_id, task_id);eq.cross_zone_enable_task_by_group_id(group_id, task_id);eq.cross_zone_enable_task_by_raid_id(raid_id, task_id);eq.cross_zone_enable_task_by_guild_id(guild_id, task_id);eq.cross_zone_fail_task_by_char_id(character_id, task_id);eq.cross_zone_fail_task_by_group_id(group_id, task_id);eq.cross_zone_fail_task_by_raid_id(raid_id, task_id);eq.cross_zone_fail_task_by_guild_id(guild_id, task_id);eq.cross_zone_move_instance_by_char_id(character_id, instance_id);eq.cross_zone_move_instance_by_group_id(group_id, instance_id);eq.cross_zone_move_instance_by_raid_id(raid_id, instance_id);eq.cross_zone_move_instance_by_guild_id(guild_id, instance_id);eq.cross_zone_remove_spell_by_char_id(character_id, spell_id);eq.cross_zone_remove_spell_by_group_id(group_id, spell_id);eq.cross_zone_remove_spell_by_raid_id(raid_id, spell_id);eq.cross_zone_remove_spell_by_guild_id(guild_id, spell_id);eq.cross_zone_reset_activity_by_char_id(character_id, task_id, activity_id);eq.cross_zone_reset_activity_by_group_id(group_id, task_id, activity_id);eq.cross_zone_reset_activity_by_raid_id(raid_id, task_id, activity_id);eq.cross_zone_reset_activity_by_guild_id(guild_id, task_id, activity_id);eq.cross_zone_update_activity_by_char_id(character_id, task_id, activity_id, activity_count);eq.cross_zone_update_activity_by_group_id(group_id, task_id, activity_id, activity_count);eq.cross_zone_update_activity_by_raid_id(raid_id, task_id, activity_id, activity_count);eq.cross_zone_update_activity_by_guild_id(guild_id, task_id, activity_id, activity_count);eq.world_wide_assign_task(task_id, enforce_level_requirement, min_status, max_status);eq.world_wide_cast_spell(spell_id, min_status, max_status);eq.world_wide_disable_task(task_id, min_status, max_status);eq.world_wide_enable_task(task_id, min_status, max_status);eq.world_wide_fail_task(task_id, min_status, max_status);eq.world_wide_message(type, message, min_status, max_status);eq.world_wide_move(zone_short_name, min_status, max_status);eq.world_wide_move_instance(instance_id, min_status, max_status);eq.world_wide_remove_spell(spell_id, min_status, max_status);eq.world_wide_remove_task(task_id, min_status, max_status);eq.world_wide_reset_activity(task_id, activity_id, min_status, max_status);eq.world_wide_set_entity_variable_client(variable_name, variable_value, min_status, max_status);eq.world_wide_set_entity_variable_npc(variable_name, variable_value);eq.world_wide_signal_client(signal, min_status, max_status);eq.world_wide_signal_npc(signal);eq.world_wide_update_activity(task_id, activity_id, activity_count, min_status, max_status);
Add GetDisplayAC() to Perl/Lua.

Add DyeArmorBySlot() to Perl/Lua.
$client->DyeArmorBySlot(slot, red, green, blue, [use_tint = 0x00]);
client:DyeArmorBySlot(slot, red, green, blue, [use_tint = 0x00]);
Add MoveZoneInstance methods to Perl/Lua.
Add cross-zone player move utilities.
quest::crosszonemoveplayerbycharid(character_id, zone_short_name);quest::crosszonemoveplayerbygroupid(group_id, zone_short_name);quest::crosszonemoveplayerbyraidid(raid_id, zone_short_name);quest::crosszonemoveplayerbyguildid(guild_id, zone_short_name);
eq.crosszonemoveplayerbycharid(character_id, zone_short_name);eq.crosszonemoveplayerbygroupid(group_id, zone_short_name);eq.crosszonemoveplayerbyraidid(raid_id, zone_short_name);eq.crosszonemoveplayerbyguildid(guild_id, zone_short_name);
  • Fixed an issue where when a client first enters a zone; an NPC may not be aware of their presence immediately
  • Fixed a UCS issue where a crash or another UCS connection would delete connection pointers; this should keep UCS connected more reliably
  • Lowered the rate in which animation packets are throttled to 500ms from 1000ms; this should keep faster animations from being gated from being sent
  • Fix $npc->RecalculateSkills() in Perl.
Added Quest API MoveZone Methods
Added Quest API CrossZone Task Methods
quest::crosszoneassigntaskbycharid(character_id, task_id, enforce_level_requirement);quest::crosszoneassigntaskbygroupid(group_id, task_id, enforce_level_requirement);quest::crosszoneassigntaskbyraidid(raid_id, task_id, enforce_level_requirement);quest::crosszoneassigntaskbyguildid(guild_id, task_id, enforce_level_requirement);
eq.cross_zone_assign_task_by_char_id(character_id, task_id, enforce_level_requirement);eq.cross_zone_assign_task_by_group_id(group_id, task_id, enforce_level_requirement);eq.cross_zone_assign_task_by_raid_id(raid_id, task_id, enforce_level_requirement);eq.cross_zone_assign_task_by_guild_id(guild_id, task_id, enforce_level_requirement);
Added Quest API Discipline Task Methods
Updated #grid show command to use alphabetic names so names display properly (numerics were getting stripped) and stacked the names of nodes that are in the same #loc.
Added code to make sure that mobs that call for help, complete that activity before starting to move toward target. As written (previously) a mob could be out of earshot of his neighbors because movement started immediately. It was a race condition that nearly always resulted in mobs within the attacked mobs assist radius not hearing the call.
Added command #findrace [name|id].

Added zone ID to #findzone.

Added eq.zone(short_name) to Lua.
Added eq.zone_group(short_name) to Lua.
Added eq.zone_raid(short_name) to Lua.
Added quest::zonegroup(short_name) to Perl.
Added quest::zoneraid(short_name) to Perl.
SQL to load correct mods into faction_list_mod for Drakkin and Guktan races as well as Agnostic deity mods. Only to be used on databases converted to using the client faction ids.SQL found in optional SQL area as: utils/sql/git/optional/drakkin_guktan_faction_data.sql

Added GetNPCBySpawnID() to Perl/Lua.
Add several cross zone methods to Perl/Lua.
quest::crosszonemessageplayerbygroupid(type, group_id, message);quest::crosszonemessageplayerbyraidid(type, raid_id, message);quest::crosszonemessageplayerbyguildid(type, guild_id, message);quest::crosszonesignalclientbygroupid(group_id, value);quest::crosszonesignalclientbyraidid(raid_id, value);quest::crosszonesignalclientbyguildid(guild_id, value);quest::crosszonesetentityvariablebygroupid(group_id, value);quest::crosszonesetentityvariablebyraidid(raid_id, value);quest::crosszonesetentityvariablebyguildid(guild_id, value);
eq.cross_zone_message_player_by_group_id(type, group_id, message);eq.cross_zone_message_player_by_raid_id(type, raid_id, message);eq.cross_zone_message_player_by_guild_id(type, guild_id, message);eq.cross_zone_signal_client_by_group_id(group_id, value);eq.cross_zone_signal_client_by_raid_id(group_id, value);eq.cross_zone_signal_client_by_guild_id(guild_id, value);eq.cross_zone_set_entity_variable_by_group_id(group_id, value);eq.cross_zone_set_entity_variable_by_raid_id(raid_id, value);eq.cross_zone_set_entity_variable_by_guild_id(guild_id, value);
UCS / Raid / Zone Fixes
On PEQ we noticed via zone with high CPU footprint spending tons of time in in function Raid::GetRaidByClient where we excessively loop through all raids in zone, all members in the raid to return back a simple raid pointer. Now, we cache the pointer lookup instead of running the entire loop constantly. For perspective, with 100 toons in a zone all in a raid, you will easily see 4k/s calls to this function which adds up on a CPU profile (nuts)
CPU Profile https://gist.github.com/Akkadius/a5c55654d4b729697964f938c824030f
Call Measurement Example
eqemu@ce0931611614:~$ tail -f ~/server/logs/**/*.log | grep "returning cached raid" | pv -lr 2>&1 >/dev/null[4.04k/s]
We also noticed UCS sucking up a lot of CPU with 1,500 players online
CPU Profile https://gist.github.com/Akkadius/14efe565caf468cc892293fd95cc0377
  • Converted to a less aggressive loop frequency 31.25 FPS and converted to using UV library
  • No longer load characters into UCS context that are soft deleted
  • Fixed UCS bug where it would not reconnect properly after a crash / improper shutdown
  • Fixed UCS bug where it was not registering a new connection properly, and sending a "Not Available" to all zones right after it is in fact "Available"
  • Added Logging in zone that clearly says whether or not UCS is online or not
  • Properly removing UCS connected clients even if the /q /ex connections resolving by sending keepalive packets #735 this prevents UCS from looping over ghosted clients making unnecessary database calls and sending improper connected client counts
  • Removed tons of verbose LogInfo logging and replaced the calls for LogDebug as it was writing to disk excessively for mostly debugging related information
  • We are now properly shutting down zone processes by killing the UV loop instead of doing a hard process exit, allowing the zone to shut everything else down gracefully
Implement SendToGuildHall and Improve Instance ID Cycling
Instead of purging immediately expired instances every 7.5 minutes, we will purge expired instances from the database table after they've been completely expired for an entire day every 7.5 minutes. The reason for this is that the code will still determine that an instance is expired when it needs to; but in order to not re-use the same instance ID not long after it had been freed while a zone is still online with the same instance ID, we're simply going to wait a grace period of 1 day to purge those entries as it will not harm anything
Client::SendToGuildHall() will send a client to a Guild Hall instance for 90 days based on a configurable rule. This will send the player to a Guild Hall instance regardless of whether they are in a Guild or not and it is up to the discretion of the server operator to check for guild id before making the call. If you are not guilded, you share an instance with other non-guilded players
We pull instance remaining time out of the database once on zone bootup because the timer class is returning the value in milliseconds which causes the values to overflow and improperly show remaining instance time for instances that are open for long period of time
Created new rule category Instances
RULE_INT(Instances, ReservedInstances, 30, "Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running")RULE_BOOL(Instances, RecycleInstanceIds, true, "Will recycle free instance ids instead of gradually running out at 32k")RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires")
This exports Lua / Perl method client->SendToGuildHall
  • Added getcharidbyname(name) to Perl/Lua.
  • Added getcharnamebyid(char_id) to Perl/Lua.