Skip to content

2019

12/25/2019

Akkadius

World CLI Interface Overhaul

  • We've added a CLI interface to the world process which can be handy for various things, such as showing build version, database schema, database version or other utility things like setting account status. This feature is built in such a way that makes it easier for the development team to also add other commands in the future
  • Added an internal single source of truth for where we maintain our database schema for different types of tables. These lists will be significant for things like export/import commands, future plans for multi-tenancy etc.
  • Player Tables
  • Content Tables
  • State Tables
  • Server Tables
  • Login Tables
  • CLI Interface Changes
  • Commands that used option flags are arguments are now proper arguments as shown in examples. This is reflected in the Loginserver CLI as well and the documentation has been changed to reflect
    • Old
    • ./world database:set-account-status --name=* --status=*
    • New
    • ./world database:set-account-status {name} {status}
    • ./world database:set-account-status myaccount 255
eqemu@9044748fdbe2:~/server$ ./bin/world --help

> EQEmulator [WorldServer] CLI Menu

database
  database:schema              Displays server database schema
  database:set-account-status  Sets account status by account name
  database:version             Shows database version
world
  world:version                Shows server version

12/10/2019

Uleat

Fixes and New Bot Command

  • Fix for pets breaking mez during combat
  • Fix for bot classes having skills they should not
  • Added bot command itemuse
  • This command will cause spawned bots to report if they can use the item which you currently hold on your cursor
  • To use this command, simply pick up an item and type ^itemuse
  • All spawned bots will check their inventories based on the items 'Slots' property and report that it is usable by them (no report if not usable)
  • Each bot will give a report for each eligible slot
  • To give the item to any reporting bot, simply click the text link with their name in it
  • This will invoke the inventorygive command using ^inventorygive byname botname
  • All rules governing use of the bot command inventorygive apply
  • You may also use the optional argument ^itemuse empty to have bots only report empty inventory slots that can use it

12/8/2019

Uleat

New Bot Command Option ^follow chain

  • This command will affect a chain follow effect, starting with the bot owner
  • All spawned bots will be included in this follow order unless they have previously been issued a follow command
  • The chain processing will skip all bots with the manual_follow flag set
  • This flag may be removed by issuing a ^follow reset on the affected bot or bots
  • The flag is temporary and is reset upon camping or zoning

12/3/2019

Uleat

New Bot Commands and Owner Option

  • Added bot command applypoison
  • This will apply item type 42 poisons (rogue-crafted) to an owner's targeted rogue bot's weapon
  • Class and level restrictions are observed
  • The application chance is the same as that for rogue players attempting to apply poison
  • To apply, simply place the poison on your cursor and type ^applypoison
  • You will receive a success/fail message and the item will be consumed in both cases
  • This effect remains active during the time in zone - just like a player's poison application
  • Added rule Bots:AllowApplyPoisonCommand to allow the command to be enabled or disabled per server admin's needs (default is true)
  • Added bot command applypotion
  • This will apply item type 21 potions to any bot targeted by its owner
  • Class, race and level restrictions are observed
  • Potion-type rogue poisons must be applied using this command
  • Casting time requirements are currently bypassed - expect a change in behavior at some point
  • To apply, simply place the potion on your cursor and type ^applypotion
  • You will receive a success/fail message and the item will be consumed in both cases
  • This effect is retained as a 'casted buff' and will remain until expired or removed
  • Added rule Bots:AllowApplyPotionCommand to allow the command to be enabled or disabled per server admin's needs (default is true)
  • Added rule Bots:RestrictApplyPotionToRogue to allow a restriction of applypotion to rogue-only potions (i.e., poisons) (default is true)
  • Added bot owner option buffcounter
  • When enabled, any bot buff with a counter will display a marquee message indicating how many 'hits' remain

image

Note: Bot owner option deathmarquee messages have been changed to red in color.

Notes:

In order to get potion effects to work properly after zoning or camping, I had to borrow code from the client connection handler.

It's unknown whether this code helps in the case of bots recasting buffs upon zoning and spawning..but, I did notice the lack of rebuffing in many cases - will watch!

Uleat

Changed Bots:AAExpansion to Bots:BotExpansionSettings

  • Changed this rule property from an enumerated value to a bitmask
  • Renamed rule to coincide with the property change
  • Works the same as World:ExpansionSettings in regards to active expansions (defaults to 16383 - same as the world rule)
  • Added code to bot aa assignment to honor the per-expansion restrictions
  • This rule may be expanded in the future to other bot-related systems

11/25/2019

Uleat

New Command #nudge

  • Added command nudge to facilitate npc placement and orientation
  • Command accepts up to 4 arguments (x,y,z,h) and may use any combination, in any order
  • Acceptable values are float-type for all parameters
  • Values are interpreted as offsets and not absolute values
  • Adjustments are 'set' by applying the command #fixnpc
  • This command is ideally suited for macro (hotkey) use
  • Real-time updates are broadcast to clients..but, there may be 'visible' glitches if you try to use the command too fast (limited by packet broadcast timer)
  • Examples of use:
  • #nudge x=1.00 y=-1.00 z=3.45 h=-11.38
  • #nudge x=2.33 z=-1.01
  • #nudge h=133.00 z=-12.34

11/18/2019

Uleat

EVENT_COMBINE_VALIDATE

  • Added the new event EVENT_COMBINE_VALIDATE
  • This event is processed as the very last step in a tradeskill combine process just before the actual combine action
  • Its use is automatic and criteria may be added to the global_player file for whichever scripting api that you are using
  • The initial candidate for special validation is tradeskill recipe #10344 - "Clockwork Scout Module"
function event_combine_validate(e)
    if (e.recipe_id == 10344) then
        if (e.validate_type:find("check_zone")) then
            if (e.zone_id ~= 289 and e.zone_id ~= 290) then
                return 1;
            end
        end
    end

    return 0;
end
  • For this recipe, the combine is only allowed in two zones, tipt and vxed
  • The active recipe id will always be passed by integer as the variable recipe_id
  • In this case, the active validation_type of check_zone, the current zone id will be passed by integer as the variable zone_id
  • From there, a simple check against valid zones can be made
  • Invalid zones will return a non-zero value resulting in a failure to validate the combine..hence, cancelling it
  • Valid zones will return a zero value and allow the combine to occur normally
  • The validation types can be expanded
  • This initial implementation is to alleviate a current bug

Note: This change requires a quest update to capture the new global player files, or the changes may be added manually.

Noudess

Fix for client positions upon entering or exiting a boat

10/5/2019

Uleat

Bot AI and Command Changes

  • Added two new 'Owner Option' features: *
  • altcombat - enabled, bots aggro based on owner's attack state; disabled, pet-like behavior
  • autodefend - enabled, bots defend owner on aggro; disabled, no action taken
  • Renamed bot command ^hold to ^suspend (same action and counter to ^release)
  • Repurposed bot command ^hold to override all attack actions
  • Reworked ^guard to allow all 'normal' actions and for bots to return to their guard spots when idle
  • Bot command ^attack should now work as intended - with noted exceptions above
  • Revamped bot command ^pull: *
  • Pulling bot will travel to entity, gain aggro, then return to home position
  • Aggro is attained through the use of the low-damage spell 'Throw Stone'
  • Pulling candidacy is currently determined by a 4-tier ranking system

9/30/2019

Akkadius

Login Server Changes

  • Auto convert insecure world server admin passwords during the world authentication process
  • Add CLI support for updating world admin account world-admin:update --username=* --password=*

9/19/2019

Kinglykrab

Fixed Maximum Status value in entity_list.QueueClientsStatus.

  • Maximum Status of 250 was causing status levels 251 to 254 to be excluded from the queue.

9/17/2019

Akkadius

Login Server Overhaul

Info

There's a few weeks of solid work wrapped in this update, you can review the changes in this pull request if you wish

Login Server Highlights

  • Database schema includes all new tables, to migrate see migration reference below
  • All new config file JSON format login.json found in ./loginserver/login_util/login.json
  • Now has an embedded Web API on port 6000 by default, details can be found at Loginserver API
  • Now has a rich embedded CLI interface, details found at Login CLI Interface
  • Now uses Scrypt for encryption which you can learn more about here
  • Now automatically updates weak encryption methods during client login to the latest encryption method (Scrypt) in use on the fly
  • Now properly supports string escaping in all database calls
  • Now no longer requires you to specify a Local LAN subnet in the config to detect LAN world servers and LAN player connections. The server now can intelligently detect RFC1918 local LAN subnets in the requests to properly mark both World Server connections and Player connections appropriately (Plug-N-Play)
  • A production and stage/testing server of the same long name / short name pair can now live together as long as one world is registered
  • Code is heavily cleaned up, many parts not perfect but far far improved
  • Many many bug fixes, stability improvements
  • Improvements made to world -> loginserver and loginserver -> world reconnects through keep-alive. If a world is disconnected it will properly picked up by world and vise versa. Which also allows the chance for the process to reconnect sooner because it knows it had a connection die instead of waiting for it to be told
  • Improvements made to world -> zone and zone -> world disconnects through keep-alive for example on Linux if world had been killed, it would not gracefully reconnect to all zone processes and vise versa

Migrating a Legacy Login Server

Info

To migrate your Legacy login server, follow this guide Migrating your Legacy Loginserver

Log System Enhancements

  • The popular C++ library FMT is now powering most of our logging FMT Library
  • Migrated thousands of log calls to use now simplified log aliases, the most recent up to date list can be found here

https://github.com/EQEmu/Server/blob/master/common/eqemu_logsys_log_aliases.h

The main reason for these changes is that it simplifies the use of logging by developers, it also makes it far easier to create logging statements because we no longer need to look up printf codes to match variable types going into logging calls as our FMT library that we are using will detect the types at runtime

Using the category log aliases also heavily simplifies the amount of verbosity that goes into simply typing the log statements to begin with

We have Detail level aliases, but we are trying to keep from using detail in categorized logging, if you are using detail you probably either need to use another specialized category. The only time detail should ever really be used is if it is incredibly spammy to the rest of the logging in the same category

Log(Logs::General, Logs::ZoneServer, "Connecting to MySQL... ");
Log(Logs::General, Logs::Error, "Failed to connect to database: Error: %s", errbuf);
Log(Logs::Detail, Logs::Spells, "Bard song %d started: slot %d, target id %d", bardsong, bardsong_slot, bardsong_target_id);
Log(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", hit.damage_done);
LogInfo("Connecting to MySQL... ");
LogError("Failed to connect to database: Error: {}", errbuf);
LogSpells("Bard song [{}] started: slot [{}], target id [{}]", bardsong, (int) bardsong_slot, bardsong_target_id);
LogCombat("Final damage after all reductions: [{}]", hit.damage_done);
LogEmergency
LogAlert
LogCritical
LogError
LogWarning
LogNotice
LogInfo
LogDebug
LogAA
LogAADetail
LogAI
LogAIDetail
LogAggro
LogAggroDetail
LogAttack
LogAttackDetail
LogPacketClientServer
LogPacketClientServerDetail
LogCombat
LogCombatDetail
LogCommands
LogCommandsDetail
LogCrash
LogCrashDetail
LogDoors
LogDoorsDetail
LogGuilds
LogGuildsDetail
LogInventory
LogInventoryDetail
LogLauncher
LogLauncherDetail
LogNetcode
LogNetcodeDetail
LogNormal
LogNormalDetail
LogObject
LogObjectDetail
LogPathing
LogPathingDetail
LogQSServer
LogQSServerDetail
LogQuests
LogQuestsDetail
LogRules
LogRulesDetail
LogSkills
LogSkillsDetail
LogSpawns
LogSpawnsDetail
LogSpells
LogSpellsDetail
LogTCPConnection
LogTCPConnectionDetail
LogTasks
LogTasksDetail
LogTradeskills
LogTradeskillsDetail
LogTrading
LogTradingDetail
LogTribute
LogTributeDetail
LogMySQLError
LogMySQLErrorDetail
LogMySQLQuery
LogMySQLQueryDetail
LogMercenaries
LogMercenariesDetail
LogQuestDebug
LogQuestDebugDetail
LogLoginserver
LogLoginserverDetail
LogClientLogin
LogClientLoginDetail
LogHeadlessClient
LogHeadlessClientDetail
LogHPUpdate
LogHPUpdateDetail
LogFixZ
LogFixZDetail
LogFood
LogFoodDetail
LogTraps
LogTrapsDetail
LogNPCRoamBox
LogNPCRoamBoxDetail
LogNPCScaling
LogNPCScalingDetail
LogMobAppearance
LogMobAppearanceDetail
LogStatus
LogStatusDetail

RFC5424 Log Categories

RFC5424 Log Categories have been introduced, they are meant to simplify the use of common logging scenarios that don't require specific categories

LogEmergency
LogAlert
LogCritical
LogError
LogWarning
LogNotice
LogInfo
LogDebug

When using any of these macros, logging output will append the process name before the message itself, thus deprecating process specific categories

Example

[WorldServer] [Info] Purging expired data buckets
[WorldServer] [Info] Loading zones
[WorldServer] [Info] Clearing groups
[WorldServer] [Info] Clearing raids
[WorldServer] [Info] Clearing inventory snapshots
[WorldServer] [Info] Loading items
[WorldServer] [Info] Loading skill caps
[WorldServer] [Info] Loading guilds
[LoginServer] [Warning] Remote address was null, defaulting to stream address 127.0.0.1

9/4/2019

Uleat

Added code to restore rule notes to their original values

9/2/2019

Uleat

Added code to inject new rules into the 'default' ruleset and remove orphaned rules from all rulesets

  • New rules are only added using the 'default' ruleset - Other rulesets will need to be added manually or through in-game updates
  • Rule notes are now loaded into the system's hard-coded entries and will now propagate properly into database updates
  • Defunct rules will have their orphaned entries removed from the 'rule_values' table for the all rulesets

8/30/2019

Uleat

Added code to inject new commands and remove orphaned commands from both command systems

  • New commands are added with their status ('access') set to the server default value - no aliases are defined
  • Defunct commands will have their orhpaned entries removed from the command settings table for each system

8/16/2019

Akkadius

Simplified Roambox Usage and Improve Roambox AI

Improved roam-box AI

New Roambox GM Commands

  • Implemented command #roambox set [move_delay]
  • Implemented command #roambox remove

New Roambox Quest API Calls

$npc->SetSimpleRoamBox(box_size, [move_distance], [move_delay]);
NPC:SetSimpleRoamBox(box_size, [move_distance], [move_delay]);

New GM Commands!

  • Spawngroup data now hot reloads on #repop
  • Command #npceditmass now lists column options when one isn't properly specified
  • Implemented command #spawneditmass [search] [option] with options [respawn_time] currently implemented

8/11/2019

Akkadius

Bulk Editing GM Commands! #npceditmass

Added bulk edit command #npceditmass [column-to-search] [column-search-value] [change-column] [change-value]

#findzone Enhancements!

  • Modified #findzone to include clickable saylinks to both regular zone (if able) and private gmzone instances
  • Added command #findzone [expansion-number] expansion to show zones via expansion

8/6/2019

Akkadius Optimizations to movement updates to eliminate ghosting possibilities in larger zones

7/22/2019

Uleat

Windows Build Work

Added script \vcxproj_dependencies.py a script to help determine conflicting project dependencies (alpha-stage)

7/10/2019

Akkadius

NPC Flymode Changes - Commands and DB Changes

  • Add #npcedit flymode [0 = ground, 1 = flying, 2 = levitate, 3 = water, 4 = floating]
  • Added "flymode" to npc_types database table

7/3/2019

Akkadius/KLS

Packet Compress, CPU, Position Update Optimizations

  • Optimizations to packet updates introduced back into the source post network code overhaul
  • Optimizations made to position update packets by sending updates far less frequently when not in line with zone:max_movement_update_range
  • Optimizations made to position updates in respect to the much higher resolution of navmesh path finding that we were using. We have cut down on the resolution of path finding / updating so that we reduce the CPU overhead of path*finding and subsequent client update packets that get generated this action
  • Optimization made by adjusting ZLIB compression rate that was accidentally set to a compression level of 4 a long time ago

Added #netstats Command

  • Added #netstats admin command to troubleshoot connection issues in detail

Zone Process now Has an Embedded Websocket Server

Websocket server is now available in zone and is bound to the same UDP port that the zoneserver listens on

  • Currently implemented websocket API calls at the zone level
  • get_packet_statistics
  • get_opcode_list
  • get_npc_list_detail
  • get_door_list_detail
  • get_corpse_list_detail
  • get_object_list_detail
  • get_mob_list_detail
  • get_client_list_detail
  • get_zone_attributes
  • get_logsys_categories
  • set_logging_level

EQEmu Server Build Changes - Using Submodules

6/24/2019

Uleat

Reworked BotDatabase Into a Functional Add-On for ZoneDatabase

  • Eliminated the database connection associated with class BotDatabase
  • All behaviors remain the same with the exception of the calling object
  • To invoke a BotDatabase function, use database.botdb rather than botdb

3/1/2019

Noudess

Major Faction Conversion to Use Real Client Data

Pull request #802 New min/max personal faction per faction. Use of actual client mods for race/class/deity.

This PR involves major changes to your database and server operators quests

The clients recently exposed raw data included

  • Min/Max personal faction for each faction
  • Actual faction id the client uses for each faction
  • Actual mods that come into play when a PC cons an opponent that determine your overall con to that faction

Approach

The approach I took resulted in minimal change to the code base. I did alter the code to enforce the new validated min/max from the client. This min/max applies to personally earned faction. So if a faction has a min of 0 and a max of 2000, that means your personally earned value can never go below 0 or over 2000. The actual con, will, however often do so because of class/race/deity modifications. I also changed the con ranges, per Mackal's data that was proven to be accurate

Faction Value
Ally 1100+
Warmly 750 to 1099
Kindly 500 to 749
Amiable 100 to 499
Indifferent 0 to 99
Apprehensive -1 to -100
Dubious -101 to -500
Threateningly -501 to -750
Ready to Attack -751

The above means that dubious is a much smaller range now. For that reason the scripts modify any custom faction base values to put them in the same range, hopefully as the creators of the custom factions intended.

Also to be noted as characters that have a faction between -501 and -700 wont be dubious anymore, they will be threateningly. This is expected with the new ranges, but might take players by surprise as the old ranges we used were more liberal but were incorrect.

The database is changed extensively, but really only content. We're translating faction_list to use the clients ids. As such every place a faction_is is used, namely (see below) are being converted

  • faction_list
  • faction_list_mod
  • npc_faction (primary_faction field only)
  • npc_faction_entries (faction_id field only)
  • faction_values

Quests will also automatically be adjusted. This MUST be done after the PR sql and before starting the server. This is automated by eqemu_server.pl (or starting world)

Be assured, custom factions that you may have created, or obsolete or duplicate factions in our original faction_list, that you may have used, will be preserved. Anything that does not map directly is being moved to the 5000 range in faction_list and any references are corrected to point there.

A great example of this is Ebon Mask and Hall of the Ebon Mask. Many PEQ DB style servers have both of these. Some have used one, some the other. We map Ebon Mask to the clients Ebon mask and the Hall of the Ebon Mask gets moved to the 5000 range, and all its references are preserved. However, if you would like to make proper use of client mobs to Ebon mask, or other factions that have duplicitous entries, I recommend you manually move to using the correct one. In that way all of the new raw data mapped in from the client into faction_list_mod will get used instead of what your db had before these values were known.

In my experience converting 4 different server's data, there are only about 20 factions moved into the 5000 range.

This PR has only 1 new, permanent table faction_base_data, which is taken right from the client. The base field is left in case you want to mod your server, but we are very sure that the client doesn't use a base. It uses global mods to race or class for this as you'll see in the new faction_list_mod

The PR makes many backup tables, and two mapping tables that are used during the conversion process to fix quests. This table was hand created by analysis. This table serves no purpose after conversion except an audit trail if we see any issues.

I will release a new PR that will clean up all these backups and temporary tables in about a month.

2/7/2019

Uleat

Merc and Bots Now Use the Same Stance Standard

  • Both classes will now use the same stance standard
  • Pushed stance types up to EQEmu::constants

2/4/2019

Uleat

Added GM-Command "profanity"

  • This is a server-based tool for redacting any language that an admin deems as profanity (socially unacceptable within their community)
  • Five options are available under this command
  • list __ shows the current list of banned words
  • clears the current list of banned words
  • add adds to the banned word list
  • del deletes from the banned word list
  • reload forces a reload of the banned word list
  • All actions are immediate and a world broadcast refreshes other active zones
  • The system is in stand-by when the list is empty..just add a word to the list to begin censorship
  • Redaction only occurs on genuine occurrences of any banned word
  • Banned words are replaced with a series of '' characters
  • Compounded words are ignored to avoid issues with allowed words containing a banned substring
  • If 'test' is banned, 'testing' will not be banned .. it must be added separately
  • Extreme care should be exercised when adding words to the banned list
  • Quest failures and limited social interactions may alienate players if they become inhibiting
  • System commands are allowed to be processed before redaction occurs in the 'say' channel
  • A longer list requires more clock cycles to process - so, try to keep them to the most offensible occurrences
  • Fix for bots ceasing combat when their 'follow me' mob dies
  • Bots will revert to their client leash owner (bot owner or client group leader) when their FollowID() mob is no longer valid
  • Combat will no longer be interrupted in these cases
  • Does not apply to bot owner death

1/26/2019

Uleat

Fix for class Bot not honoring NPCType data reference

  • Fixes bots not moving on spawn/grouping issue
  • Report any issues with non movement-related behavior

1/24/2019

Uleat

Extended Server SpellBook Entries to RoF2 Standard...

...and added per-client restriction of spell id max

  • Bumped server spellbook entry capacity to 720 spells
  • Server keeps all 'learned' spells as found
  • Access is limited by the clients' limitations of spellbook capacities and max spell ids
  • This is done to avoid losing spells by switching from newer clients to older ones
  • Existing behavior is kept in place for illegal access conditions
  • Each client is still restricted to its spellbook capacity (400, 480, 480, 720, 720, 720 - respectively)
  • Each client is restricted to its max supported spell id (9999, 15999, 23000, 28000, 45000, 45000 respectively)
  • Please report any abnormal behavior so it may be addressed
  • Removed server-side checksum of player profile..wasted calculation since it's performed again in all translators

1/20/2019

Uleat

Added "spells" Entry to EQDictionary

Akkadius

Extended GM-Command #goto, New #who, #gmzone Commands

  • [Command] Extended #goto via #goto [playername]
  • This will work cross zone, cross instance, in zone etc.
  • It works on top of the original #goto (target) and #goto x y z
  • [Command] Implemented server side #who
  • Searches can be filtered by
    • Account Name
    • Base Class Name
    • Guild Name
    • IP
    • Player Name
    • Race Name
    • Zone Short Name
  • Features a clickable (Goto) saylink that will bring you directly to a player regardless of

    whether or not they are in an instance * [Command] Implemented: #gmzone [zone_short_name] [zone_version=0] [identifier=gmzone] * Zones to a private GM instance

Fixes to NPC's Ghosting at Zone Entrances

Fix issue where NPC's clip into the world and the client interprets them at coordinates _**_0,0,0

This issue would show itself when NPC's would bunch up by a zone-in

1/15/2019

Uleat

Activated Per-Expansion Support for Active Inventory Slot Addressing

  • Server honors expansions that alter bank size and power source, general9 and general10 slots
  • Server honors gm flag behaviors for the active inventory slots of each client

1/11/2019

Uleat

Modified Rules System to Ignore All Runtime Modifications...

of 'World:ExpansionSettings' and 'World:UseClientBasedExpansionSettings' fields.

  • These fields are no longer allowed to be changed during server runtime through the command system
  • Major synchronization issues between server and clients result when these fields are altered in-game
  • It is not recommended to update these fields via sql queries while the server is in operation
  • Failure to observe these warnings will result in abhorant behavior and loss of items
  • Modify these fields during server operation at your own risk!

¼/2019

Akkadius

Global base scaling data has been updated in new database binary revision

1/1/2019

Akkadius

New DevTool Commands, Categories, Logging and Scaling

  • Logging: Added new logging category MobAppearance
  • DevTools Proximity show of NPC now shows a "Path finding" circle around the proximity nodes to more clearly display
  • Scaling Global base scaling data now refreshes from the database on #repop
  • Commands Implemented command #killallnpcs [npc_name] for testing, leave blank for all attackable NPC's

NPC and Player Textures

  • Textures that have been changed with #wearchange / #wc or any wearchange quest script call will now stick for new clients entering a zone
  • Weapon models for NPCs changed using wearchange will stick as well during combat and when new clients enter the zone
  • The above changes allow for customization of a zone and NPC's without needing static data configured on npc_types table data and allows for much more customization options

Implemented Ornamentation Quest API Calls

$client->SetPrimaryWeaponOrnamentation(uint32 model_id);
$client->SetSecondaryWeaponOrnamentation(uint32 model_id);
client:SetPrimaryWeaponOrnamentation(uint32 model_id);
client:SetSecondaryWeaponOrnamentation(uint32 model_id);

Both of these API calls persist an ornamentation to the weapon in the inventory table and will load both in character select and cross zone