ソースを参照

- Fix #528 - Support for DoF Advancement Window!
* Restrictions based on rule R_Player, TraitTieringSelection = 1, limits to 1 trait selection per group, 0 allows 'free' selection much like newer EQ2
* Racial Traditions and Personal Traits available, Training and Enemy Tactics need content
- Fix #472 - Spell damage now has resist added to reduce damage. Resistability support, info struct now has float "max_spell_reduction" and int8 "max_spell_reduction_override". Use override to set manually the resistability. Otherwise uses table for max resistability based on level
- Fix #456 - Parital support for NPC Knockback, challenge is the updates are not using delta properly, but they do fly!
- Fixed /follow for DoF client
- Fixed multiple crashes in adding a trait
- Spell book should no longer have type 4 spells (not shown spells)
- Spell book issue addressed where innate abilities would wipe out the spell book
- Fixed situations on login where HP/Power would not regen since you were not added to the damaged spawns
- Implemented a workaround to get all items to appear in inventory for DoF client upon zoning *does not resolve evac/escape
- Fixed Spawn info struct, we were not passing spell_effects properly for DoF client
**Be sure to check the db file may2024_classictraits_personalandracial.sql and note it has a static max item id 10204351 that may need to be changed in the future if you insert it later

Emagi 6 ヶ月 前
コミット
69612e08b6

+ 237 - 0
DB/updates/may2024_classictraits_personalandracial.sql

@@ -0,0 +1,237 @@
+alter table spell_traits add column item_id int(10) unsigned not null default 0;
+alter table spell_traits add column is_active tinyint(1) unsigned not null default 1;
+alter table spell_traits add column insert_id int(10) not null default 0;
+update spell_traits set is_active=0;
+
+insert into spell_traits set level=8,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=0,isFocusEffect=1,spell_id=1000196;
+insert into spell_traits set level=8,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=0,isFocusEffect=1,spell_id=1002091;
+insert into spell_traits set level=8,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=0,isFocusEffect=1,spell_id=1000520;
+insert into spell_traits set level=8,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=0,isFocusEffect=1,spell_id=1001879;
+insert into spell_traits set level=8,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=0,isFocusEffect=1,spell_id=1000288;
+insert into spell_traits set level=14,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=1,isFocusEffect=1,spell_id=1001768;
+insert into spell_traits set level=14,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=1,isFocusEffect=1,spell_id=1000300;
+insert into spell_traits set level=14,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=1,isFocusEffect=1,spell_id=1002936;
+insert into spell_traits set level=14,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=1,isFocusEffect=1,spell_id=1001841;
+insert into spell_traits set level=14,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=1,isFocusEffect=1,spell_id=1001890;
+insert into spell_traits set level=22,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=2,isFocusEffect=1,spell_id=1002730;
+insert into spell_traits set level=22,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=2,isFocusEffect=1,spell_id=1001678;
+insert into spell_traits set level=22,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=2,isFocusEffect=1,spell_id=1001794;
+insert into spell_traits set level=22,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=2,isFocusEffect=1,spell_id=1000240;
+insert into spell_traits set level=28,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=3,isFocusEffect=1,spell_id=1002067;
+insert into spell_traits set level=28,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=3,isFocusEffect=1,spell_id=1002656;
+insert into spell_traits set level=28,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=3,isFocusEffect=1,spell_id=1000585;
+insert into spell_traits set level=28,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=3,isFocusEffect=1,spell_id=1001268;
+insert into spell_traits set level=28,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=3,isFocusEffect=1,spell_id=1002458;
+insert into spell_traits set level=36,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=4,isFocusEffect=1,spell_id=1002383;
+insert into spell_traits set level=36,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=4,isFocusEffect=1,spell_id=1001412;
+insert into spell_traits set level=36,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=4,isFocusEffect=1,spell_id=1001706;
+insert into spell_traits set level=36,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=4,isFocusEffect=1,spell_id=1002278;
+insert into spell_traits set level=36,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=4,isFocusEffect=1,spell_id=1002962;
+insert into spell_traits set level=42,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=5,isFocusEffect=1,spell_id=1002689;
+insert into spell_traits set level=42,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=5,isFocusEffect=1,spell_id=1000254;
+insert into spell_traits set level=42,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=5,isFocusEffect=1,spell_id=1002660;
+insert into spell_traits set level=42,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=5,isFocusEffect=1,spell_id=1001962;
+insert into spell_traits set level=46,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=6,isFocusEffect=1,spell_id=1002807;
+insert into spell_traits set level=46,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=6,isFocusEffect=1,spell_id=1001422;
+insert into spell_traits set level=46,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=6,isFocusEffect=1,spell_id=1002379;
+insert into spell_traits set level=46,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=6,isFocusEffect=1,spell_id=1002363;
+insert into spell_traits set level=46,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=6,isFocusEffect=1,spell_id=1000454;
+insert into spell_traits set level=48,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=7,isFocusEffect=1,spell_id=1002221;
+insert into spell_traits set level=48,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=7,isFocusEffect=1,spell_id=1000418;
+insert into spell_traits set level=48,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=7,isFocusEffect=1,spell_id=1002384;
+insert into spell_traits set level=48,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=7,isFocusEffect=1,spell_id=1002923;
+insert into spell_traits set level=48,class_req=255,race_req=255,isTrait=1,isInate=1,tier=1,`group`=7,isFocusEffect=1,spell_id=1000203;
+
+
+#barbarian racials
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550454;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550455;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550456;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550457;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550458;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550459;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550460;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1000202;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001806;
+insert into spell_traits set level=0,class_req=255,race_req=0,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550461;
+
+
+#dark elf racials
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550109;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550118;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550464;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550114;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550117;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550115;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550113;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550111;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550112;
+insert into spell_traits set level=0,class_req=255,race_req=1,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550116;
+
+
+#dwarf racials
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550281;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550475;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550280;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550287;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550283;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550282;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550286;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550284;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550476;
+insert into spell_traits set level=0,class_req=255,race_req=2,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550285;
+
+#erudite racials
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550311;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550295;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550301;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550299;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550309;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550298;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550296;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550294;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550300;
+insert into spell_traits set level=0,class_req=255,race_req=3,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550293;
+
+
+#froglok is race_req 4 we got nothing!
+
+#gnome racials
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1000031;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001418;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550496;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001697;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1000268;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550497;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550498;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550499;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550500;
+insert into spell_traits set level=0,class_req=255,race_req=5,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550501;
+
+#half elf racials
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550109;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=1002991;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=1002992;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550278;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002779;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1000120;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001731;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550463;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550464;
+insert into spell_traits set level=0,class_req=255,race_req=6,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550465;
+
+#halfling racials
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550467;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550468;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002088;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550469;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550470;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550471;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002137;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550472;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550473;
+insert into spell_traits set level=0,class_req=255,race_req=7,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001907;
+
+#high elf racials
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550109;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001734;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550248;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550262;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550246;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002779;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550261;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550251;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001730;
+insert into spell_traits set level=0,class_req=255,race_req=8,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550253;
+
+#human racials
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550483;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550484;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550485;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550486;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1000293;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=1002991;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=80025;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550487;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550463;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550213;
+insert into spell_traits set level=0,class_req=255,race_req=9,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550488;
+
+#iksar racials
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550502;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550489;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=1002280;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002950;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1000378;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550503;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550504;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001942;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550505;
+insert into spell_traits set level=0,class_req=255,race_req=10,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550506;
+
+#kerra racials
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550129;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550139;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550466;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550133;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550128;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001733;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002797;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002797;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550127;
+insert into spell_traits set level=0,class_req=255,race_req=11,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550143;
+
+
+#ogre racials
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550474;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550079;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550086;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550084;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550082;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550078;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550088;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550081;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550087;
+insert into spell_traits set level=0,class_req=255,race_req=12,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550085;
+
+#ratonga racials
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002754;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550477;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550478;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550479;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002201;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001884;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550480;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550336;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550481;
+insert into spell_traits set level=0,class_req=255,race_req=13,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550482;
+
+#troll racials
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550489;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550490;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002835;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002010;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550491;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550492;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550493;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001985;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550494;
+insert into spell_traits set level=0,class_req=255,race_req=14,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550495;
+
+#wood elf racials
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550109;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550277;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550462;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1001731;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550273;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550507;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550508;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=2550275;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=0,tier=1,`group`=0,spell_id=2550274;
+insert into spell_traits set level=0,class_req=255,race_req=15,isTrait=0,isInate=1,tier=1,`group`=0,spell_id=1002926;
+
+update spell_traits set insert_id = id;
+
+insert into items ( skill_id_req, id, bPvpDesc, name, item_type, icon, developer_notes, count, tier, description, show_name ) select distinct(st.spell_id), st.insert_id+10204351, 1, s.name, 'Scroll', 75, 'test trait', 0, 1, trim(replace(s.description, '\n', '')), 1 from spell_traits as st inner join spells as s on s.id = st.spell_id;
+
+update spell_traits set item_id = insert_id + 10204351;

+ 35 - 3
EQ2/source/WorldServer/Combat.cpp

@@ -762,6 +762,7 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 		if(lua_spell->spell->GetSpellData()->resistibility > 0)
 			bonus -= (1.0f - lua_spell->spell->GetSpellData()->resistibility)*100.0f;
 	
+		LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellResist: resistibility %f, bonus %f", lua_spell->spell->GetSpellData()->resistibility, bonus);
 		// Here we take into account Subjugation, Disruption and Ordination (debuffs)
 		if(lua_spell->spell->GetSpellData()->mastery_skill) {
 			int32 master_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MasterSkillReduceSpellResist)->GetInt32();
@@ -781,6 +782,8 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 					item_stat_bonus = GetStat(item_stat);
 				}
 				bonus += (master_skill->current_val + item_stat_bonus) / master_skill_reduce;
+				LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellResistMasterySkill: skill %u, stat bonus %f, mastery skill reduce %u, bonus %f", master_skill->current_val, item_stat_bonus, master_skill_reduce, bonus);
+
 			}
 		}
 		
@@ -788,13 +791,17 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 	
 	if (victim->IsEntity())
 		bonus -= ((Entity*)victim)->GetDamageTypeResistPercentage(damage_type);
+	
+	LogWrite(COMBAT__DEBUG, 9, "Combat", "DamageResistPercent: type %u, bonus %f", damage_type, bonus);
 
 
 	Entity* entity_victim = (Entity*)victim;
 	float chance = 80 + bonus; //80% base chance that the victim will get hit (plus bonus)
 	sint16 roll_chance = 100;
 	if(skill)
-		roll_chance -= skill->current_val / 25;
+		roll_chance -= skill->current_val / 10;
+
+	LogWrite(COMBAT__DEBUG, 9, "Combat", "Chance: fchance %f, roll_chance %i", chance, roll_chance);
 
 	if(!is_caster_spell){ // melee or range attack		
 		skill = GetSkillByName("Offense", true); //add this skill for NPCs
@@ -875,7 +882,9 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 	else{
 		skill = entity_victim->GetSkillByName("Spell Avoidance", true);
 		if(skill)
-			chance -= skill->current_val / 25;
+			chance -= skill->current_val / 10;
+
+		LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellAvoidChance: fchance %f", chance);
 		if(rand()%roll_chance >= chance) {
 			return DAMAGE_PACKET_RESULT_RESIST; //successfully resisted	
 		}
@@ -1821,8 +1830,21 @@ sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_typ
 		damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_DAMAGE);
 	}
 
-	if(base_type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE)
+	if(base_type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE) {
 		damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE);
+		
+		if(target && target->IsEntity() && damage > 0) {
+			int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+			float resistBase = ((Entity*)target)->GetDamageTypeResistPercentage(damage_type);
+			if(resistBase > 1.0f) {
+				float dmgReduction = ((Entity*)target)->CalculateSpellDamageReduction((float)damage, resistBase - 1.0f, effective_level_attacker);
+				float newDamage = static_cast<float>(damage) - dmgReduction;
+				if(newDamage < 0.0f)
+					newDamage = 0.0f;
+				damage = static_cast<sint32>(std::floor(newDamage));
+			}
+		}
+	}
 
 	// combat abilities only bonus
 	if(damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE)
@@ -1868,4 +1890,14 @@ int32 Entity::CalculateFormulaByStat(int32 value, int16 stat) {
 
 int32 Entity::CalculateFormulaBonus(int32 value, float percent_bonus) {
 	return (int32)((float)value * ((percent_bonus / 100.0f) + 1.0f));
+}
+
+float Entity::CalculateSpellDamageReduction(float spellDamage, float resistancePercentage, int16 attackerLevel) {
+	int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+	float levelDifference = std::abs(effective_level - attackerLevel);
+	float logFactor = 1.0f / (1.0f + 0.1f * levelDifference); // Adjust the factor for smoother reduction
+
+	// Calculate the actual damage reduction based on resistance percentage, logarithmic factor, and maximum reduction
+	float reducedDamage = spellDamage * (1.0f - resistancePercentage) * logFactor * GetInfoStruct()->get_max_spell_reduction();
+	return reducedDamage;
 }

+ 50 - 7
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -5743,27 +5743,57 @@ void Commands::Command_AcceptAdvancement(Client* client, Seperator* sep)
 {
 	 Player *player = client->GetPlayer();
 	 if (sep && sep->IsSet(0)) {
-		 TraitData* trait = master_trait_list.GetTrait(atoul(sep->arg[0]));
+		 int32 trait_id = atoul(sep->arg[0]);
+		 TraitData* trait = nullptr;
+		 if(client->GetVersion() <= 547) {
+			 trait = master_trait_list.GetTraitByItemID(trait_id);
+		 }
+		 else {
+			trait = master_trait_list.GetTrait(trait_id);
+		 }
 
+		 if(!trait) {
+			LogWrite(COMMAND__ERROR, 0, "Command", "Invalid accept advancement of trait %u, no trait found.", trait_id);
+			 return; // not valid lets not crash!
+		 }
+		 
+		 if(!master_trait_list.IsPlayerAllowedTrait(client, trait)) {
+			 client->SimpleMessage(CHANNEL_COLOR_RED, "Not enough trait points to accept trait.");
+			 return;
+		 }
 		 // Check to see if this is a trait or grandmaster training (traits are always new spells, training is always upgrades)
 		 if (!player->HasSpell(trait->spellID, 0, true))
 		 {
 			 Spell* spell = master_spell_list.GetSpell(trait->spellID, trait->tier);
-			 player->AddSpellBookEntry(trait->spellID, trait->tier, player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
+			 if(spell) {
+				player->AddSpellBookEntry(trait->spellID, trait->tier, player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
+			 }
+			 else {
+				client->Message(CHANNEL_COLOR_RED, "ERROR! Trait is not a valid spell id %u may be disabled.", trait->spellID);
+			 }
 		 }
 		 else
 		 {
 			 Spell* spell = master_spell_list.GetSpell(trait->spellID, trait->tier);
-			 int8 old_slot = player->GetSpellSlot(spell->GetSpellID());
-			 player->RemoveSpellBookEntry(spell->GetSpellID());
-			 player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
-			 player->UnlockSpell(spell);
-			 client->SendSpellUpdate(spell);
+			 if(spell) {
+				 int8 old_slot = player->GetSpellSlot(spell->GetSpellID());
+				 player->RemoveSpellBookEntry(spell->GetSpellID());
+				 player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
+				 player->UnlockSpell(spell);
+				 client->SendSpellUpdate(spell);
+			 }
+			 else {
+				client->Message(CHANNEL_COLOR_RED, "ERROR! Trait is not a valid spell id %u may be disabled.", trait->spellID);
+			 }
 		 }
 
 		 // Spell book update
 		 client->QueuePacket(player->GetSpellBookUpdatePacket(client->GetVersion()));
 		 client->QueuePacket(master_trait_list.GetTraitListPacket(client));
+		 
+		 if(client->GetVersion() <= 547) {
+			master_trait_list.ChooseNextTrait(client);
+		 }
 	 }
 }
 
@@ -10705,6 +10735,19 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 			client->GetCurrentZone()->SendHealPacket(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(),
 													 atoul(sep->arg[1]), atoul(sep->arg[2]), "TestSpell");
 		}
+		else if(atoi(sep->arg[0]) == 34 && sep->IsNumber(1) && sep->IsNumber(2)) {
+			PacketStruct* packet = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion());
+			packet->setSubstructDataByName("reward_data", "unknown1", atoi(sep->arg[1]));
+			Item* item = master_item_list.GetItem(atoul(sep->arg[2]));
+			if(item) {
+				packet->setSubstructArrayLengthByName("reward_data", "num_select_rewards", 1);
+				packet->setArrayDataByName("select_reward_id", item->details.item_id, 0);
+				packet->setItemArrayDataByName("select_item", item, client->GetPlayer(), 0, 0, -1);
+			}
+
+			client->QueuePacket(packet->serialize());
+			safe_delete(packet);
+		}
 	}
 	else {
 			PacketStruct* packet2 = configReader.getStruct("WS_ExamineSpellInfo", client->GetVersion());

+ 31 - 0
EQ2/source/WorldServer/Entity.cpp

@@ -371,6 +371,9 @@ void Entity::MapInfoStruct()
 	
 	get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct);
 	get_string_funcs["combat_action_state"] = l::bind(&InfoStruct::get_combat_action_state, &info_struct);
+	
+	get_float_funcs["max_spell_reduction"] = l::bind(&InfoStruct::get_max_spell_reduction, &info_struct);
+	get_int8_funcs["max_spell_reduction_override"] = l::bind(&InfoStruct::get_max_spell_reduction_override, &info_struct);
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -575,6 +578,8 @@ void Entity::MapInfoStruct()
 	set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1);
 	set_string_funcs["combat_action_state"] = l::bind(&InfoStruct::set_combat_action_state, &info_struct, l::_1);
 
+	set_float_funcs["max_spell_reduction"] = l::bind(&InfoStruct::set_max_spell_reduction, &info_struct, l::_1);
+	set_int8_funcs["max_spell_reduction_override"] = l::bind(&InfoStruct::set_max_spell_reduction_override, &info_struct, l::_1);
 }
 
 bool Entity::HasMoved(bool include_heading){
@@ -1404,6 +1409,8 @@ void Entity::CalculateBonuses(){
 
 	int16 effective_level = info->get_effective_level() != 0 ? info->get_effective_level() : GetLevel();
 
+	CalculateMaxReduction();
+	
 	info->set_block(info->get_block_base());
 	
 	info->set_cur_attack(info->get_attack_base());
@@ -3947,4 +3954,28 @@ void Entity::TerminateTrade() {
 		tmpTradePtr->CancelTrade(this);
 		safe_delete(tmpTradePtr);
 	}
+}
+
+void Entity::CalculateMaxReduction() {
+	if(GetInfoStruct()->get_max_spell_reduction_override()) {
+		return; // override enabled, don't touch the max reduction
+	}
+	int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+	// Define thresholds and corresponding maximum reduction factors
+	const int thresholds[] = {1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120}; // Thresholds for level differences
+	const float maxReductions[] = {0.2f, 0.2f, 0.15f, 0.15f, 0.1f,0.1f,0.1f,0.1f,0.1f,0.075f,0.075f,0.075f,0.05f,0.05f,0.05f,0.05f,0.05f,0.05f,0.045f,0.045f,0.045f,0.045f,0.045f,0.045f,0.045f}; // Maximum reduction factors for each threshold
+	int numThresholds = sizeof(thresholds) / sizeof(thresholds[0]);
+
+	// Find the appropriate maximum reduction factor based on level difference
+	float maxReduction = .1f; // Default maximum reduction factor
+	for (int i = 0; i < numThresholds; ++i) {
+		if (effective_level >= thresholds[i]) {
+			maxReduction = maxReductions[i];
+		}
+		else {
+			break; // No need to check further
+		}
+	}
+
+	GetInfoStruct()->set_max_spell_reduction(maxReduction);
 }

+ 31 - 1
EQ2/source/WorldServer/Entity.h

@@ -291,6 +291,10 @@ struct InfoStruct{
 		assist_auto_attack_ = 0;
 		
 		action_state_ = std::string("");
+		combat_action_state_ = std::string("");
+		
+		max_spell_reduction_ = .1f;
+		max_spell_reduction_override_ = 0;
 	}
 
 
@@ -483,7 +487,19 @@ struct InfoStruct{
 		
 		action_state_ = oldStruct->get_action_state();
 		combat_action_state_ = oldStruct->get_combat_action_state();
-
+		
+		group_loot_method_ = oldStruct->get_group_loot_method();
+		group_loot_items_rarity_ = oldStruct->get_group_loot_items_rarity();
+		group_auto_split_ = oldStruct->get_group_auto_split();
+		group_default_yell_ = oldStruct->get_group_default_yell();
+		group_autolock_ = oldStruct->get_group_autolock();
+		group_lock_method_ = oldStruct->get_group_lock_method();
+		group_solo_autolock_ = oldStruct->get_group_solo_autolock();
+		group_auto_loot_method_ = oldStruct->get_group_auto_loot_method();
+		assist_auto_attack_ = oldStruct->get_assist_auto_attack();
+		
+		max_spell_reduction_ = oldStruct->get_max_spell_reduction();
+		max_spell_reduction_override_ = oldStruct->get_max_spell_reduction_override();
 	}
 	//mutable std::shared_mutex mutex_;
     std::string get_name() { std::lock_guard<std::mutex> lk(classMutex); return name_; }
@@ -706,6 +722,9 @@ struct InfoStruct{
 	
 	std::string get_combat_action_state() { std::lock_guard<std::mutex> lk(classMutex); return combat_action_state_; }
 	
+	float	get_max_spell_reduction() { std::lock_guard<std::mutex> lk(classMutex); return max_spell_reduction_; }
+	int8	get_max_spell_reduction_override() { std::lock_guard<std::mutex> lk(classMutex); return max_spell_reduction_override_; }
+	
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
 	
 	void	set_deity(std::string value) { std::lock_guard<std::mutex> lk(classMutex); deity_ = value; }
@@ -1014,6 +1033,10 @@ struct InfoStruct{
 	
 	void	set_combat_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); combat_action_state_ = value; }
 	
+	void	set_max_spell_reduction(float value) { std::lock_guard<std::mutex> lk(classMutex); max_spell_reduction_ = value; }
+	
+	void	set_max_spell_reduction_override(int8 value) { std::lock_guard<std::mutex> lk(classMutex); max_spell_reduction_override_ = value; }
+	
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -1237,6 +1260,9 @@ private:
 	std::string		action_state_;
 	std::string		combat_action_state_;
 	
+	float			max_spell_reduction_;
+	int8			max_spell_reduction_override_;
+	
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };
@@ -1927,6 +1953,7 @@ public:
 	bool SetInfoStructSInt(std::string field, sint64 value);
 	bool SetInfoStructFloat(std::string field, float value);
 
+	float CalculateSpellDamageReduction(float spellDamage, int16 competitorLevel);
 	sint32 CalculateHateAmount(Spawn* target, sint32 amt);
 	sint32 CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod = false);
 	sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell);
@@ -1934,6 +1961,7 @@ public:
 	sint32 CalculateFormulaByStat(sint32 value, int16 stat);
 	int32 CalculateFormulaByStat(int32 value, int16 stat);
 	int32 CalculateFormulaBonus(int32 value, float percent_bonus);
+	float CalculateSpellDamageReduction(float spellDamage, float resistancePercentage, int16 attackerLevel);
 	
 	float GetStat(int32 item_stat) {
 		float item_chance_or_skill = 0.0f;
@@ -2013,6 +2041,8 @@ public:
 	}
 	
 	void TerminateTrade();
+	
+	void CalculateMaxReduction();
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		MEquipment;
 	std::mutex		MStats;

+ 16 - 2
EQ2/source/WorldServer/Items/Items.cpp

@@ -3528,6 +3528,10 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
 }
 
 EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
+	bool firstRun = false;
+	if(!packet_count) {
+		firstRun = true;
+	}
 	EQ2Packet* app = 0;
 	PacketStruct* packet = configReader.getStruct("WS_UpdateInventory",version);
 	Item* item = 0;
@@ -3535,8 +3539,12 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
  	if(packet && indexed_items.size() > 0){
 		int8 packet_size = 0;
 		int16 size = indexed_items.size();
-		if (overflowItems.size() > 0)
+		if (!firstRun && overflowItems.size() > 0)
 			size++;
+		
+		if(size > 20 && firstRun) {
+			size = 20;
+		}
 		PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version);
 		packet_size = packet2->GetTotalPacketSize();
 		safe_delete(packet2);
@@ -3567,11 +3575,17 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
 			if(item && item->details.new_item)
 				new_index++;
 			
+			if(firstRun && i > 19) {
+				item->details.new_item = true;
+				continue;
+			}
+			
 			if (item && item->details.item_id > 0)
 				AddItemToPacket(packet, player, item, i, false, new_index);
+			
 		}
 
-		if (overflowItems.size() > 0) {
+		if (!firstRun && overflowItems.size() > 0) {
 			// We have overflow items, lets get the first one
 			item = overflowItems.at(0);
 			// Lets make sure the item is valid

+ 19 - 0
EQ2/source/WorldServer/Items/Items.h

@@ -750,6 +750,25 @@ struct ItemAppearance{
 	int8					highlight_green;
 	int8					highlight_blue;
 };
+
+enum AddItemType {
+	NOT_SET = 0,
+	BUY_FROM_BROKER = 1,
+	GM_COMMAND = 2
+};
+
+struct QuestRewardData {
+	int32 quest_id;
+	bool is_temporary;
+	std::string description;
+	bool is_collection;
+	bool has_displayed;
+	int64 tmp_coin;
+	int32 tmp_status;
+	bool db_saved;
+	int32 db_index;
+};
+
 class PlayerItemList;
 class Item{
 public:

+ 19 - 14
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -8611,21 +8611,26 @@ int EQ2Emu_lua_Knockback(lua_State* state) {
 		return 0;
 	}
 
-	if (spawn->IsPlayer() && (vertical != 0 || horizontal != 0)) {
-		Client* client = ((Player*)spawn)->GetClient();
-		PacketStruct* packet = configReader.getStruct("WS_PlayerKnockback", client->GetVersion());
-		if (packet) {
-			packet->setDataByName("target_x", target_spawn->GetX());
-			packet->setDataByName("target_y", target_spawn->GetY());
-			packet->setDataByName("target_z", target_spawn->GetZ());
-			packet->setDataByName("vertical_movement", vertical);
-			packet->setDataByName("horizontal_movement", horizontal);
-			if (use_heading)
-				packet->setDataByName("use_player_heading", 1);
-
-			client->QueuePacket(packet->serialize());
+	if ((vertical != 0 || horizontal != 0)) {
+		if(spawn->IsPlayer()) {
+			Client* client = ((Player*)spawn)->GetClient();
+			PacketStruct* packet = configReader.getStruct("WS_PlayerKnockback", client->GetVersion());
+			if (packet) {
+				packet->setDataByName("target_x", target_spawn->GetX());
+				packet->setDataByName("target_y", target_spawn->GetY());
+				packet->setDataByName("target_z", target_spawn->GetZ());
+				packet->setDataByName("vertical_movement", vertical);
+				packet->setDataByName("horizontal_movement", horizontal);
+				if (use_heading)
+					packet->setDataByName("use_player_heading", 1);
+
+				client->QueuePacket(packet->serialize());
+			}
+			safe_delete(packet);
+		}
+		else {
+			spawn->SetKnockback(target_spawn, duration, vertical, horizontal);
 		}
-		safe_delete(packet);
 	}
 
 	return 0;

+ 16 - 4
EQ2/source/WorldServer/Player.cpp

@@ -122,6 +122,13 @@ Player::Player(){
 	all_spells_locked = false;
 	current_language_id = 0;
 	active_reward = false;
+	
+	SortedTraitList = new map <int8, map <int8, vector<TraitData*> > >;
+	ClassTraining = new map <int8, vector<TraitData*> >;
+	RaceTraits = new map <int8, vector<TraitData*> >;
+	InnateRaceTraits = new map <int8, vector<TraitData*> >;
+	FocusEffects = new map <int8, vector<TraitData*> >;
+	need_trait_update = true;
 }
 Player::~Player(){
 	SetSaveSpellEffects(true);
@@ -204,6 +211,11 @@ Player::~Player(){
 	ClearPendingItemRewards();
 	ClearEverything();
 	
+	safe_delete(SortedTraitList);
+	safe_delete(ClassTraining);
+	safe_delete(RaceTraits);
+	safe_delete(InnateRaceTraits);
+	safe_delete(FocusEffects);
 	// leak fix on Language* pointer from Player::AddLanguage
 	player_languages_list.Clear();
 }
@@ -2509,7 +2521,7 @@ int16 Player::GetSpellSlotMappingCount(){
 	MSpellsBook.lock();
 	for(int32 i=0;i<spells.size();i++){
 		SpellBookEntry* spell = (SpellBookEntry*)spells[i];
-		if(spell->slot >= 0 && spell->spell_id > 0)
+		if(spell->slot >= 0 && spell->spell_id > 0 && spell->type != SPELL_BOOK_TYPE_NOT_SHOWN)
 			ret++;
 	}
 	MSpellsBook.unlock();
@@ -2535,7 +2547,7 @@ int16 Player::GetSpellPacketCount(){
 	MSpellsBook.lock();
 	for(int32 i=0;i<spells.size();i++){
 		SpellBookEntry* spell = (SpellBookEntry*)spells[i];
-		if(spell->spell_id > 0)
+		if(spell->spell_id > 0 && spell->type != SPELL_BOOK_TYPE_NOT_SHOWN)
 			ret++;
 	}
 	MSpellsBook.unlock();
@@ -2852,7 +2864,7 @@ EQ2Packet* Player::GetSpellSlotMappingPacket(int16 version){
 			MSpellsBook.lock();
 			for(int32 i=0;i<spells.size();i++){
 				SpellBookEntry* spell = (SpellBookEntry*)spells[i];
-				if(spell->slot < 0 || spell->spell_id == 0)
+				if(spell->type == SPELL_BOOK_TYPE_NOT_SHOWN || spell->slot < 0 || spell->spell_id == 0)
 					continue;
 				packet->setArrayDataByName("spell_id", spell->spell_id, ptr);
 				packet->setArrayDataByName("slot_id", (int16)spell->slot, ptr);
@@ -2903,7 +2915,7 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) {
 			MSpellsBook.lock();
 			for (int32 i = 0; i < spells.size(); i++) {
 				spell_entry = (SpellBookEntry*)spells[i];
-				if (spell_entry->spell_id == 0)
+				if (spell_entry->spell_id == 0 || spell_entry->type == SPELL_BOOK_TYPE_NOT_SHOWN)
 					continue;
 				spell = master_spell_list.GetSpell(spell_entry->spell_id, spell_entry->tier);
 				if (spell) {			

+ 9 - 18
EQ2/source/WorldServer/Player.h

@@ -32,6 +32,7 @@
 #include "Titles.h"
 #include "Languages.h"
 #include "Achievements/Achievements.h"
+#include "Traits/Traits.h"
 #include <algorithm>
 #include <set>
 
@@ -244,18 +245,6 @@ struct SpawnQueueState {
 	int16 index_id;
 };
 
-struct QuestRewardData {
-	int32 quest_id;
-	bool is_temporary;
-	std::string description;
-	bool is_collection;
-	bool has_displayed;
-	int64 tmp_coin;
-	int32 tmp_status;
-	bool db_saved;
-	int32 db_index;
-};
-
 class PlayerLoginAppearance {
 public:
 	PlayerLoginAppearance() { appearanceList = new map<int8, LoginAppearances>; }
@@ -435,12 +424,6 @@ private:
 	Mutex MFlagChanges;
 };
 
-enum AddItemType {
-	NOT_SET = 0,
-	BUY_FROM_BROKER = 1,
-	GM_COMMAND = 2
-};
-
 class Player : public Entity{
 public:
 	Player();
@@ -1093,6 +1076,14 @@ public:
 	void	ProcessSpawnRangeUpdates();
 	Mutex MPlayerQuests;
 	float   pos_packet_speed;
+	
+	map <int8, map <int8, vector<TraitData*> > >* SortedTraitList;
+	map <int8, vector<TraitData*> >* ClassTraining;
+	map <int8, vector<TraitData*> >* RaceTraits;
+	map <int8, vector<TraitData*> >* InnateRaceTraits;
+	map <int8, vector<TraitData*> >* FocusEffects;
+	mutable std::shared_mutex trait_mutex;
+	std::atomic<bool> need_trait_update;
 private:
 	bool reset_mentorship;
 	bool range_attack;

+ 6 - 0
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -227,6 +227,12 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, CoinWeightPerStone, "40.0"); // coin weight per stone, 40.0 = 40 coins per 1 stone (per DoF client hover over)
 	RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off
 	RULE_INIT(R_Player, LevelMasterySkillMultiplier, "5"); // multiplier for adventure level / recommended level when applying mastery damage to determine if they are in mastery range
+	RULE_INIT(R_Player, TraitTieringSelection, "1"); // when set to true limited to single trait per group, otherwise you can freely select from any group
+	RULE_INIT(R_Player, ClassicTraitLevelTable, "1"); // uses built in table based on Prima Guide, see Traits.cpp for more, otherwise  uses the levels below
+	RULE_INIT(R_Player, TraitFocusSelectLevel, "9"); // x levels to receive new trait of focus, eg level/rule, level 10, rule value 5, 10/5 = 2 focus traits available at level 10
+	RULE_INIT(R_Player, TraitTrainingSelectLevel, "10"); // x levels to receive new trait of focus
+	RULE_INIT(R_Player, TraitRaceSelectLevel, "10"); // x levels to receive new trait of focus
+	RULE_INIT(R_Player, TraitCharacterSelectLevel, "10"); // x levels to receive new trait of focus
 	
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");

+ 6 - 0
EQ2/source/WorldServer/Rules/Rules.h

@@ -87,6 +87,12 @@ enum RuleType {
 	CoinWeightPerStone,
 	WeightInflictsSpeed,
 	LevelMasterySkillMultiplier,
+	TraitTieringSelection,
+	ClassicTraitLevelTable,
+	TraitFocusSelectLevel,
+	TraitTrainingSelectLevel,
+	TraitRaceSelectLevel,
+	TraitCharacterSelectLevel,
 
 	/* PVP */
 	AllowPVP,

+ 141 - 7
EQ2/source/WorldServer/Spawn.cpp

@@ -148,6 +148,7 @@ Spawn::Spawn(){
 	is_loot_complete = false;
 	is_loot_dispensed = false;
 	reset_movement = false;
+	ResetKnockedBack();
 }
 
 Spawn::~Spawn(){
@@ -522,7 +523,6 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
 
 	pos_struct->ResetData();
 	InitializePosPacketData(player, pos_struct);
-
 	if (version <= 283) {
 		if (offset == 777) {
 			info_struct->setDataByName("name", "This is a really long name\n");
@@ -2091,7 +2091,13 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 		m_GridMutex.releasewritelock(__FUNCTION__, __LINE__);
 	}
 	
-	packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : GetLocation());
+	if(IsKnockedBack()) {
+		packet->setDataByName("pos_grid_id", 0);
+	}
+	else {
+		packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : GetLocation());
+	}
+	
 	bool include_heading = true;
 	if (IsWidget() && ((Widget*)this)->GetIncludeHeading() == false)
 		include_heading = false;
@@ -2274,7 +2280,6 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 	}
 	
 	
-
 	if (IsNPC() || IsPlayer()) {
 		packet->setDataByName("pos_move_type", 25);
 	}
@@ -2284,7 +2289,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 	else if(IsGroundSpawn()) {
 		packet->setDataByName("pos_move_type", 16);
 	}
-
+	
 	if (!IsPlayer()) // has to be 2 or NPC's warp around when moving
 		packet->setDataByName("pos_movement_mode", 2);
 	
@@ -2437,7 +2442,6 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	packet->setDataByName("mood_state", appearance.mood_state);
 	packet->setDataByName("gender", appearance.gender);
 	packet->setDataByName("race", appearance.race);
-	packet->setDataByName("gender", appearance.gender);
 	if (IsEntity()) {
 		Entity* entity = ((Entity*)this);
 		packet->setDataByName("combat_voice", entity->GetCombatVoice());
@@ -2740,7 +2744,10 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	*/
 	if (IsPlayer()) {
 		if (((Player*)this)->GetFollowTarget())
-			packet->setDataByName("follow_target", ((((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget()) * -1) - 1));
+			packet->setDataByName("follow_target", version <= 547 ? (((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget())) : ((((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget()) * -1) - 1));
+		else if(version <= 547) {
+			packet->setDataByName("follow_target", 0xFFFFFFFF);
+		}
 		//else
 		//	packet->setDataByName("follow_target", 0xFFFFFFFF);
 	}
@@ -2872,6 +2879,12 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 		}*/
 		return;
 	}
+	
+	if(IsKnockedBack()) {
+		if(CalculateSpawnProjectilePosition(GetX(), GetY(), GetZ()))
+			return; // being launched!
+	}
+	
 	if(reset_movement) {
 		ResetMovement();
 		reset_movement = false;
@@ -4170,7 +4183,7 @@ void Spawn::FixZ(bool forceUpdate) {
 	uint32 WidgetID = 0;
 	float new_z = GetY();
 	if(GetMap() != nullptr) {
-		float new_z = FindBestZ(current_loc, nullptr, &GridID, &WidgetID);
+		new_z = FindBestZ(current_loc, nullptr, &GridID, &WidgetID);
 
 		if ((IsTransportSpawn() || !IsFlyingCreature()) && GridID != 0 && GridID != GetLocation()) {
 			LogWrite(PLAYER__DEBUG, 0, "Player", "%s left grid %u and entered grid %u", appearance.name, GetLocation(), GridID);
@@ -5056,4 +5069,125 @@ void Spawn::DistributeGroupLoot_RoundRobin(std::vector<int32>* item_list, bool r
 			}
 		}
 	}
+}
+
+const double g = 9.81; // acceleration due to gravity (m/s^2)
+
+void Spawn::CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration) {
+    float vx = distanceHorizontal / duration;
+    float vy = (distanceVertical + 0.5 * g * duration * duration) / duration;
+    float vz = distanceDepth / duration;
+
+    // Convert heading angle to radians
+    knocked_velocity.x = vx * cos(heading);
+    knocked_velocity.y = vy;
+    knocked_velocity.z = vz * sin(heading);
+}
+
+// Function to calculate the projectile position at a given time
+glm::vec3 Spawn::CalculateProjectilePosition(glm::vec3 initialVelocity, float time) {
+    glm::vec3 position;
+	
+    position.x = knocked_back_start_x + initialVelocity.x * time;
+    position.y = knocked_back_start_y + initialVelocity.y * time - 0.5 * g * time * time;
+    position.z = knocked_back_start_z + initialVelocity.z * time;
+	
+	auto loc = glm::vec3(position.x, position.z, position.y);
+	float new_z = FindBestZ(loc, nullptr);
+	if(new_z > position.y)
+		position.y = new_z;
+	
+    return position;
+}
+
+bool Spawn::CalculateSpawnProjectilePosition(float x, float y, float z) {
+	float currentTimeOffset = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though
+	float stepAheadOne = currentTimeOffset+currentTimeOffset;
+	
+	knocked_back_time_step += currentTimeOffset;
+	if(Timer::GetCurrentTime2() >= knocked_back_end_time) {
+		ResetKnockedBack();
+		FixZ(true);
+		return false;
+	}
+	glm::vec3 position = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step);
+	glm::vec3 position_two = position;
+	
+	if(Timer::GetCurrentTime2() <= knocked_back_end_time+stepAheadOne) {
+		position_two = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step+stepAheadOne);
+	}
+	
+	if(GetMap()) {
+		glm::vec3 loc(GetX(), GetZ(), GetY() + .5f);
+		glm::vec3 dest_loc(position_two.x, position_two.z, position_two.y);
+		MIgnoredWidgets.lock_shared();
+		glm::vec3 outNorm;
+		float dist = 0.0f;
+		bool collide_ = GetMap()->DoCollisionCheck(loc, dest_loc, &ignored_widgets, outNorm, dist);
+		if(collide_) {
+			LogWrite(SPAWN__ERROR, 0, "Spawn", "Collision Hit: cur loc x,y,z: %f %f %f.  to loc %f %f %f.  TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position_two.x,position_two.y,position_two.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration);
+			MIgnoredWidgets.unlock_shared();
+			ResetKnockedBack();
+			FixZ(true);
+			return false;
+		}
+		MIgnoredWidgets.unlock_shared();
+	}
+	
+	LogWrite(SPAWN__ERROR, 0, "Spawn", "x,y,z: %f %f %f.  Final %f %f %f.  TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position.x,position.y,position.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration);
+	
+	SetX(position.x, false);
+	SetZ(position.z, false);
+	SetY(position.y, false, true);
+	
+	SetPos(&appearance.pos.X2, position_two.x, false);
+	SetPos(&appearance.pos.Z2, position_two.z, false);
+	SetPos(&appearance.pos.Y2, position_two.y, false);
+	SetPos(&appearance.pos.X3, position_two.x, false);
+	SetPos(&appearance.pos.Z3, position_two.z, false);
+	SetPos(&appearance.pos.Y3, position_two.y, false);
+	
+	position_changed = true;
+	changed = true;
+	GetZone()->AddChangedSpawn(this);
+	return true;
+}
+
+void Spawn::SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal) {
+	if(knocked_back) {
+		return; // already being knocked back
+	}
+	
+	    // Calculate the direction vector from source to destination
+    glm::vec3 direction = {GetX() - target->GetX(), GetZ() - target->GetZ(), GetY() - target->GetY()};
+
+    // Calculate the heading angle in radians
+    double headingRad = atan2(direction.y, sqrt(direction.x * direction.x + direction.z * direction.z));
+
+	knocked_angle = headingRad;
+	knocked_back_start_x = GetX();
+	knocked_back_start_y = GetY();
+	knocked_back_start_z = GetZ();
+	knocked_back_h_distance = horizontal / 10.0f;
+	knocked_back_v_distance = vertical / 10.0f;
+	knocked_back_duration = static_cast<float>(duration) / 1000.0f;
+	knocked_back_end_time = Timer::GetCurrentTime2() + duration;
+	CalculateInitialVelocity(knocked_angle, knocked_back_h_distance, knocked_back_h_distance, knocked_back_h_distance, knocked_back_duration);
+	knocked_back = true;
+}
+
+void Spawn::ResetKnockedBack() {
+	knocked_back = false;
+	knocked_back_time_step = 0.0f;
+	knocked_back_h_distance = 0.0f;
+	knocked_back_v_distance = 0.0f;
+	knocked_back_duration = 0.0f;
+	knocked_back_end_time = 0;
+	knocked_back_start_x = 0.0f;
+	knocked_back_start_y = 0.0f;
+	knocked_back_start_z = 0.0f;
+	knocked_angle = 0.0f;
+	knocked_velocity.x = 0.0f;
+	knocked_velocity.y = 0.0f;
+	knocked_velocity.z = 0.0f;
 }

+ 19 - 0
EQ2/source/WorldServer/Spawn.h

@@ -1383,6 +1383,12 @@ public:
 	bool IsItemInLootTier(Item* item);
 	void DistributeGroupLoot_RoundRobin(std::vector<int32>* item_list, bool roundRobinTrashLoot = false); // trash loot is what falls under the item tier requirement by group options
 	
+	void 		CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration);
+	glm::vec3 	CalculateProjectilePosition(glm::vec3 initialVelocity, float time);
+	bool 		CalculateSpawnProjectilePosition(float x, float y, float z);
+	void 		SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal);
+	void 		ResetKnockedBack();
+
 	mutable std::shared_mutex MIgnoredWidgets;
 	std::map<int32, bool> ignored_widgets;
 	
@@ -1429,6 +1435,7 @@ protected:
 	void CheckProximities();
 	Timer pause_timer;
 	
+	bool IsKnockedBack() { return knocked_back; }
 private:
 	int32			loot_group_id;
 	GroupLootMethod loot_method;
@@ -1517,6 +1524,18 @@ private:
 	bool is_collector;
 	bool scared_by_strong_players;
 	std::atomic<bool> is_alive;
+	std::atomic<bool> knocked_back;
+	float knocked_back_time_step;
+	float knocked_back_h_distance;
+	float knocked_back_v_distance;
+	float knocked_back_duration;
+	float knocked_back_start_x;
+	float knocked_back_start_y;
+	float knocked_back_start_z;
+	int32 knocked_back_end_time;
+	float knocked_angle;
+	glm::vec3 knocked_velocity;
+	
 };
 
 #endif

+ 1 - 1
EQ2/source/WorldServer/SpellProcess.cpp

@@ -980,7 +980,7 @@ bool SpellProcess::CheckSpellQueue(Spell* spell, Entity* caster){
 }
 
 void SpellProcess::SendSpellBookUpdate(Client* client){
-	if(client){
+	if(client && client->IsReadyForSpawns()){
 		EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion());
 		if(app)
 			client->QueuePacket(app);

+ 470 - 42
EQ2/source/WorldServer/Traits/Traits.cpp

@@ -23,11 +23,18 @@ along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 #include "../../common/Log.h"
 #include "../Spells.h"
 #include "../WorldDatabase.h"
+#include "../client.h"
+#include "../Rules/Rules.h"
+#include <boost/assign.hpp>
+
 #include <map>
 
 extern ConfigReader configReader;
 extern MasterSpellList master_spell_list;
 extern WorldDatabase database;
+extern RuleManager rule_manager;
+
+using namespace boost::assign;
 
 MasterTraitList::MasterTraitList(){
 	MMasterTraitList.SetName("MasterTraitList::TraitList");
@@ -47,46 +54,46 @@ int MasterTraitList::Size(){
 	return TraitList.size();
 }
 
-EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
+bool MasterTraitList::GenerateTraitLists(Client* client, map <int8, map <int8, vector<TraitData*> > >* sortedTraitList, map <int8, vector<TraitData*> >* classTraining,
+										map <int8, vector<TraitData*> >* raceTraits, map <int8, vector<TraitData*> >* innateRaceTraits, map <int8, vector<TraitData*> >* focusEffects, int16 max_level, int8 trait_group)
 {
 	if (!client) {
 		LogWrite(SPELL__ERROR, 0, "Traits", "GetTraitListPacket called with an invalid client");
-		return 0;
+		return false;
 	}
 	// Sort the Data
-	if (Size() == 0)
-		return NULL;
-
-	map <int8, map <int8, vector<TraitData*> > > SortedTraitList;
+	if (Size() == 0 || !sortedTraitList || !classTraining || !raceTraits || !innateRaceTraits || !focusEffects)
+		return false;
+	
 	map <int8, map <int8, vector<TraitData*> > >::iterator itr;
 	map <int8, vector<TraitData*> >::iterator itr2;
 	vector<TraitData*>::iterator itr3;
 
-	map <int8, vector<TraitData*> > ClassTraining;
-	map <int8, vector<TraitData*> > RaceTraits;
-	map <int8, vector<TraitData*> > InnateRaceTraits;
-	map <int8, vector<TraitData*> > FocusEffects;
-
 	MMasterTraitList.readlock(__FUNCTION__, __LINE__);
 	
 	for (int i=0; i < Size(); i++) {
-
+		if(max_level > 0 && TraitList[i]->level > max_level) {
+			continue; // skip per max level requirement
+		}
+		if(trait_group != 255 && trait_group != TraitList[i]->group) {
+			continue;
+		}
+		
 		// Sort Character Traits
 		if (TraitList[i]->classReq == 255 && TraitList[i]->raceReq == 255 && !TraitList[i]->isFocusEffect && TraitList[i]->isTrait) {
-
-			itr = SortedTraitList.lower_bound(TraitList[i]->group);
-			if (itr != SortedTraitList.end() && !(SortedTraitList.key_comp()(TraitList[i]->group, itr->first))) {
+			itr = sortedTraitList->lower_bound(TraitList[i]->group);
+			if (itr != sortedTraitList->end() && !(sortedTraitList->key_comp()(TraitList[i]->group, itr->first))) {
 
 				itr2 = (itr->second).lower_bound(TraitList[i]->level);
 				if (itr2 != (itr->second).end() && !((itr->second).key_comp()(TraitList[i]->level, itr2->first))) {
 					((itr->second)[itr2->first]).push_back(TraitList[i]);
-					//LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
+					LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
 				}
 				else {
 					vector<TraitData*> tempVec;
 					tempVec.push_back(TraitList[i]);
 					(itr->second).insert(make_pair(TraitList[i]->level, tempVec));
-					//LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
+					LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
 				}
 			}
 			else {
@@ -94,51 +101,55 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 				vector <TraitData*> tempVec;
 				tempVec.push_back(TraitList[i]);
 				tempMap.insert(make_pair(TraitList[i]->level, tempVec));
-				SortedTraitList.insert(make_pair(TraitList[i]->group, tempMap));
-				//LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
+				sortedTraitList->insert(make_pair(TraitList[i]->group, tempMap));
+				LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
 			}
 		}
+		
 		// Sort Class Training
 		if (TraitList[i]->classReq == client->GetPlayer()->GetAdventureClass() && TraitList[i]->isTraining) {
-			itr2 = ClassTraining.lower_bound(TraitList[i]->level);
-			if (itr2 != ClassTraining.end() && !(ClassTraining.key_comp()(TraitList[i]->level, itr2->first))) {
+			itr2 = classTraining->lower_bound(TraitList[i]->level);
+			if (itr2 != classTraining->end() && !(classTraining->key_comp()(TraitList[i]->level, itr2->first))) {
 				(itr2->second).push_back(TraitList[i]);
 			}
 			else {
 				vector<TraitData*> tempVec;
 				tempVec.push_back(TraitList[i]);
-				ClassTraining.insert(make_pair(TraitList[i]->level, tempVec));
+				classTraining->insert(make_pair(TraitList[i]->level, tempVec));
 			}
 		}
+		
 		// Sort Racial Abilities
 		if (TraitList[i]->raceReq == client->GetPlayer()->GetRace() && !TraitList[i]->isInate && !TraitList[i]->isTraining) {
-			itr2 = RaceTraits.lower_bound(TraitList[i]->group);
-			if (itr2 != RaceTraits.end() && !(RaceTraits.key_comp()(TraitList[i]->group, itr2->first))) {
+			itr2 = raceTraits->lower_bound(TraitList[i]->group);
+			if (itr2 != raceTraits->end() && !(raceTraits->key_comp()(TraitList[i]->group, itr2->first))) {
 				(itr2->second).push_back(TraitList[i]);
 			}
 			else {
 				vector<TraitData*> tempVec;
 				tempVec.push_back(TraitList[i]);
-				RaceTraits.insert(make_pair(TraitList[i]->group, tempVec));
+				raceTraits->insert(make_pair(TraitList[i]->group, tempVec));
 			}
 		}
+		
 		// Sort Innate Racial Abilities
 		if (TraitList[i]->raceReq == client->GetPlayer()->GetRace() && TraitList[i]->isInate) {
-			itr2 = InnateRaceTraits.lower_bound(TraitList[i]->group);
-			if (itr2 != InnateRaceTraits.end() && !(InnateRaceTraits.key_comp()(TraitList[i]->group, itr2->first))) {
+			itr2 = innateRaceTraits->lower_bound(TraitList[i]->group);
+			if (itr2 != innateRaceTraits->end() && !(innateRaceTraits->key_comp()(TraitList[i]->group, itr2->first))) {
 				(itr2->second).push_back(TraitList[i]);
 			}
 			else {
 				vector<TraitData*> tempVec;
 				tempVec.push_back(TraitList[i]);
-				InnateRaceTraits.insert(make_pair(TraitList[i]->group, tempVec));
+				innateRaceTraits->insert(make_pair(TraitList[i]->group, tempVec));
 			}
 		}
+		
 		// Sort Focus Effects
 		if ((TraitList[i]->classReq == client->GetPlayer()->GetAdventureClass() || TraitList[i]->classReq == 255) && TraitList[i]->isFocusEffect) {
 			int8 j = 0;
-			itr2 = FocusEffects.lower_bound(TraitList[i]->group);
-			if (itr2 != FocusEffects.end() && !(FocusEffects.key_comp()(TraitList[i]->group, itr2->first))) {
+			itr2 = focusEffects->lower_bound(TraitList[i]->group);
+			if (itr2 != focusEffects->end() && !(focusEffects->key_comp()(TraitList[i]->group, itr2->first))) {
 				
 				(itr2->second).push_back(TraitList[i]);
 				//LogWrite(SPELL__INFO, 0, "Traits", "Added Focus Effect: %u Tier: %i", TraitList[i]->spellID, TraitList[i]->tier);
@@ -147,14 +158,413 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 			else {
 				vector<TraitData*> tempVec;
 				tempVec.push_back(TraitList[i]);
-				FocusEffects.insert(make_pair(TraitList[i]->group, tempVec));
+				focusEffects->insert(make_pair(TraitList[i]->group, tempVec));
 				//LogWrite(SPELL__INFO, 0, "Traits", "Added Focus Effect: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier);
 			}
 		}
 	}
 
 	MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__);
+	
+	return true;
+}
+
+bool MasterTraitList::IdentifyNextTrait(Client* client, map <int8, vector<TraitData*> >* traitList, vector<TraitData*>* collectTraits, vector<TraitData*>* tieredTraits, std::map<int32, int8>* previousMatchedSpells, bool omitFoundMatches) {
+	bool found_spell_match = false;
+	bool match = false;
+	bool tiered_selection = rule_manager.GetGlobalRule(R_Player, TraitTieringSelection)->GetBool();
+	int8 group_to_apply = 255;
+	int8 count = 0;
+	map <int8, vector<TraitData*> >::iterator itr2;
+	vector<TraitData*>::iterator itr3;
+	
+				for (itr2 = traitList->begin(); itr2 != traitList->end(); itr2++) {
+					//LogWrite(SPELL__INFO, 0, "AA", "Character Traits Size...%i ", traits_size);
+					for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) {
+						if(tiered_selection) {
+							if(found_spell_match && (*itr3)->group == group_to_apply) {
+								continue; // skip!
+							}
+							else if((*itr3)->group != group_to_apply) {
+								if(group_to_apply != 255 && !found_spell_match) {
+									// found match
+									LogWrite(SPELL__INFO, 0, "Traits", "Found match to group id %u", group_to_apply);
+									match = true;
+									break;
+								}
+								else {
+									LogWrite(SPELL__INFO, 0, "Traits", "Try match to group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group);
+									found_spell_match = false;
+									group_to_apply = (*itr3)->group;
+									count = 0;
+									if(!omitFoundMatches)
+										tieredTraits->clear();
+								}
+							}
+						}
+					count++;
+					
+					std::map<int32,int8>::iterator spell_itr = previousMatchedSpells->find((*itr3)->spellID);
+					
+					if(spell_itr != previousMatchedSpells->end() && (*itr3)->group > spell_itr->second) {
+						continue;
+					}
+					if(!IsPlayerAllowedTrait(client, (*itr3))) {
+						LogWrite(SPELL__INFO, 0, "Traits", "We are not allowed any more spells from this type/group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group);
+						found_spell_match = true;
+					}
+					else if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) {
+						LogWrite(SPELL__INFO, 0, "Traits", "Found existing spell match to group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group);
+						if(!omitFoundMatches)
+							found_spell_match = true;
+						previousMatchedSpells->insert(std::make_pair((*itr3)->spellID,(*itr3)->group));
+					}
+					else {
+						tieredTraits->push_back((*itr3));
+						collectTraits->push_back((*itr3));
+					}
+				}
+				
+				if(match)
+					break;
+			}
+			
+			
+		if(!match && group_to_apply != 255 && !found_spell_match) {
+			// found match
+			match = true;
+		}
+		else if(!tiered_selection && collectTraits->size() > 0) {
+			match = true;
+		}
+	return match;
+}
+
+bool MasterTraitList::ChooseNextTrait(Client* client) {
+	map <int8, map <int8, vector<TraitData*> > >* SortedTraitList = new map <int8, map <int8, vector<TraitData*> > >;
+	map <int8, map <int8, vector<TraitData*> > >::iterator itr;
+	bool tiered_selection = rule_manager.GetGlobalRule(R_Player, TraitTieringSelection)->GetBool();
+	vector<TraitData*>::iterator itr3;
+
+	map <int8, vector<TraitData*> >* ClassTraining = new map <int8, vector<TraitData*> >;
+	map <int8, vector<TraitData*> >* RaceTraits = new map <int8, vector<TraitData*> >;
+	map <int8, vector<TraitData*> >* InnateRaceTraits = new map <int8, vector<TraitData*> >;
+	map <int8, vector<TraitData*> >* FocusEffects = new map <int8, vector<TraitData*> >;
+		vector<TraitData*>* collectTraits = new vector<TraitData*>;
+		vector<TraitData*>* tieredTraits = new vector<TraitData*>;
+		std::map<int32, int8>* previousMatchedSpells = new std::map<int32, int8>;
+	bool match = false;
+	if(GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects, client->GetPlayer()->GetLevel())) {
+		
+		vector<TraitData*>* endTraits;
+		if(!match || !tiered_selection) {
+			match = IdentifyNextTrait(client, ClassTraining, collectTraits, tieredTraits, previousMatchedSpells);
+		}
+		if(!match || !tiered_selection) {
+			match = IdentifyNextTrait(client, RaceTraits, collectTraits, tieredTraits, previousMatchedSpells, true);
+			
+			bool overrideMatch = IdentifyNextTrait(client, InnateRaceTraits, collectTraits, tieredTraits, previousMatchedSpells, true);
+			
+			if(!match && overrideMatch)
+				match = true;
+		}
+		if(!match || !tiered_selection) {
+			match = IdentifyNextTrait(client, FocusEffects, collectTraits, tieredTraits, previousMatchedSpells);
+		}
+		
+		if(!tiered_selection && collectTraits->size() > 0) {
+			endTraits = collectTraits;
+		}
+		else if (match) {
+			endTraits = tieredTraits;
+		}
+		if(match) {
+			PacketStruct* packet = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion());
+			// 0=enemy mastery, 1=specialized training,2=character trait, 3=racial tradition
+			int8 packet_type = 0;
+			int8 item_count = 0;
+			packet->setSubstructArrayLengthByName("reward_data", "num_select_rewards", endTraits->size());
+			for (itr3 = endTraits->begin(); itr3 != endTraits->end(); itr3++) {
+					
+					if((*itr3)->item_id) {
+						//LogWrite(SPELL__INFO, 0, "Traits", "Item %u to be sent", (*itr3)->item_id);
+						Item* item = master_item_list.GetItem((*itr3)->item_id);
+						if(item) {
+							//LogWrite(SPELL__INFO, 0, "Traits", "Item found %s to be sent", item->name.c_str());
+							packet->setArrayDataByName("select_reward_id", (*itr3)->item_id, item_count);
+							packet->setItemArrayDataByName("select_item", item, client->GetPlayer(), item_count, 0, -1);
+							item_count++;
+						}
+					}
+					
+								
+					// Sort Character Traits
+					if ((*itr3)->classReq == 255 && (*itr3)->raceReq == 255 && (*itr3)->isTrait) {
+						packet_type = 2;
+					}
+					// Sort Class Training
+					else if ((*itr3)->classReq == client->GetPlayer()->GetAdventureClass() && (*itr3)->isTraining) {
+						packet_type = 1;
+					}
+					// Sort Racial Abilities
+					else if ((*itr3)->raceReq == client->GetPlayer()->GetRace() && !(*itr3)->isInate && !(*itr3)->isTraining) {
+						packet_type = 3;
+					}
+					// Sort Innate Racial Abilities
+					else if ((*itr3)->raceReq == client->GetPlayer()->GetRace() && (*itr3)->isInate) {
+						packet_type = 3;
+					}
+
+					//LogWrite(SPELL__INFO, 0, "Traits", "Sending trait %u", (*itr3)->spellID);
+			}
+			packet->setSubstructDataByName("reward_data", "unknown1", packet_type);
+			client->QueuePacket(packet->serialize());
+			safe_delete(packet);
+		}
+	}
+	
+	safe_delete(SortedTraitList);
+	safe_delete(ClassTraining);
+	safe_delete(RaceTraits);
+	safe_delete(InnateRaceTraits);
+	safe_delete(FocusEffects);
+	
+	safe_delete(collectTraits);
+	safe_delete(tieredTraits);
+	safe_delete(previousMatchedSpells);
+	return match;
+}
+
+int16 MasterTraitList::GetSpellCount(Client* client, map <int8, vector<TraitData*> >* traits, bool onlyCharTraits) {
+	if(!traits)
+		return 0;
+	
+	int16 count = 0;
+	map <int8, vector<TraitData*> >::iterator itr2;
+	vector<TraitData*>::iterator itr3;
+	for (itr2 = traits->begin(); itr2 != traits->end(); itr2++) {
+		for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) {
+			if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier))  {
+				if(!onlyCharTraits || (onlyCharTraits && (*itr3)->classReq == 255 && (*itr3)->raceReq == 255 && (*itr3)->isTrait)) {
+					count++;
+				}
+			}
+		}
+	}
+	
+	return count;
+}
+
+vector<int16> PersonalTraitLevelLimits = boost::assign::list_of(0)(8)(14)(22)(28)(36)(42)(46)(48);
+vector<int16> TrainingTraitLevelLimits = boost::assign::list_of(0)(10)(20)(30)(40)(50);
+vector<int16> RacialTraitLevelLimits = boost::assign::list_of(0)(18)(26)(34)(44);
+vector<int16> CharacterTraitLevelLimits = boost::assign::list_of(0)(12)(16)(24)(32)(38);
+
+/***
+Every other level, beginning at Level 8,
+you gain an additional advantage — a
+Personal Trait, an Enemy Tactic, a Racial
+Tradition or a Training ability. Each time
+you reach an even-numbered level, you
+can select another advantage from the
+appropriate list. You don’t have to select
+in order — you may take any of the avail-
+able choices.
+Level Advantage
+8 Personal Trait (1st)
+10 Training (1st)
+12 Enemy Tactic (1st)
+14 Personal Trait (2nd)
+16 Enemy Tactic (2nd)
+18 Racial Tradition (1st)
+20 Training (2nd)
+22 Personal Trait (3rd)
+24 Enemy Tactic (3rd)
+26 Racial Tradition (2nd)
+28 Personal Trait (4th)
+30 Training (3rd)
+32 Enemy Tactic (4th)
+34 Racial Tradition (3rd)
+36 Personal Trait (5th)
+38 Enemy Tactic (5th)
+40 Training (4th)
+42 Personal Trait (6th)
+44 Racial Tradition (4th)
+46 Personal Trait (7th)
+48 Personal Trait (8th)
+50 Training (5th)
+***/
+
+bool MasterTraitList::IsPlayerAllowedTrait(Client* client, TraitData* trait) {
+	std::unique_lock(client->GetPlayer()->trait_mutex);
+	map <int8, map <int8, vector<TraitData*> > >* SortedTraitList = client->GetPlayer()->SortedTraitList;
+	map <int8, map <int8, vector<TraitData*> > >::iterator itr;
+	map <int8, vector<TraitData*> >::iterator itr2;
+	vector<TraitData*>::iterator itr3;
+	bool use_classic_table = rule_manager.GetGlobalRule(R_Player, ClassicTraitLevelTable)->GetBool();
+
+	map <int8, vector<TraitData*> >* ClassTraining = client->GetPlayer()->ClassTraining;
+	map <int8, vector<TraitData*> >* RaceTraits = client->GetPlayer()->RaceTraits;
+	map <int8, vector<TraitData*> >* InnateRaceTraits = client->GetPlayer()->InnateRaceTraits;
+	map <int8, vector<TraitData*> >* FocusEffects = client->GetPlayer()->FocusEffects;
+	if(client->GetPlayer()->need_trait_update) {
+		SortedTraitList->clear();
+		ClassTraining->clear();
+		RaceTraits->clear();
+		InnateRaceTraits->clear();
+		FocusEffects->clear();
+	}
+	bool allowed_trait = false;
+	if(!client->GetPlayer()->need_trait_update || GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects, 0)) {
+		client->GetPlayer()->need_trait_update = false;
+		if(trait->isFocusEffect) {
+			
+			int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitFocusSelectLevel)->GetInt32();
+			int16 num_available_selections = 0;
+			if(trait_level > 0) {
+				num_available_selections = client->GetPlayer()->GetLevel() / trait_level;
+			}
+			int16 total_used = GetSpellCount(client, FocusEffects);
+			
+			int16 classic_avail = 0;
+			
+			if(use_classic_table && PersonalTraitLevelLimits.size() > total_used+1) {
+				int16 classic_level_req = PersonalTraitLevelLimits.at(total_used+1);
+				if(client->GetPlayer()->GetLevel() >= classic_level_req)
+					classic_avail = num_available_selections = total_used+1;
+				else
+					num_available_selections = 0;
+			}
+			else if(use_classic_table)
+				num_available_selections = 0;
+			
+			LogWrite(SPELL__INFO, 9, "Traits", "%s FocusEffects used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail);
+			if(total_used < num_available_selections) {
+				allowed_trait = true;
+			}
+		}
+		else if(trait->isTraining) {
+			int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitTrainingSelectLevel)->GetInt32();
+			int16 num_available_selections = 0;
+			if(trait_level > 0) {
+				num_available_selections = client->GetPlayer()->GetLevel() / trait_level;
+			}
+			int16 total_used = GetSpellCount(client, ClassTraining);
+			
+			int16 classic_avail = 0;
+			
+			if(use_classic_table && TrainingTraitLevelLimits.size() > total_used+1) {
+				int16 classic_level_req = TrainingTraitLevelLimits.at(total_used+1);
+				if(client->GetPlayer()->GetLevel() >= classic_level_req)
+					classic_avail = num_available_selections = total_used+1;
+				else
+					num_available_selections = 0;
+			}
+			else if(use_classic_table)
+				num_available_selections = 0;
+			
+			LogWrite(SPELL__INFO, 9, "Traits", "%s ClassTraining used %u, available %u,  classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail);
+			if(total_used < num_available_selections) {
+				allowed_trait = true;
+			}
+		}
+		else {
+				if(trait->raceReq == client->GetPlayer()->GetRace()) {
+					int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitRaceSelectLevel)->GetInt32();
+					int16 num_available_selections = 0;
+					if(trait_level > 0) {
+						num_available_selections = client->GetPlayer()->GetLevel() / trait_level;
+					}
+					int16 total_used = GetSpellCount(client, RaceTraits);
+					int16 total_used2 = GetSpellCount(client, InnateRaceTraits);
+					
+					int16 classic_avail = 0;
+					
+					if(use_classic_table && RacialTraitLevelLimits.size() > total_used+total_used2+1) {
+						int16 classic_level_req = RacialTraitLevelLimits.at(total_used+total_used2+1);
+						if(client->GetPlayer()->GetLevel() >= classic_level_req)
+							classic_avail = num_available_selections = total_used+total_used2+1;
+						else
+							num_available_selections = 0;
+					}
+					else if(use_classic_table)
+						num_available_selections = 0;
+			
+					LogWrite(SPELL__INFO, 9, "Traits", "%s RaceTraits used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used+total_used2, num_available_selections, classic_avail);
+					if(total_used+total_used2 < num_available_selections) {
+						allowed_trait = true;
+					}
+				}
+				else { // character trait?
+					int16 num_available_selections = 0;
+					int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitCharacterSelectLevel)->GetInt32();
+					if(trait_level > 0) {
+						num_available_selections = client->GetPlayer()->GetLevel() / trait_level;
+					}
+					int16 total_used = 0;
+					for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) {
+						total_used += GetSpellCount(client, &itr->second, true);
+					}
+					
+					int16 classic_avail = 0;
+					
+					if(use_classic_table && CharacterTraitLevelLimits.size() > total_used+1) {
+						int16 classic_level_req = CharacterTraitLevelLimits.at(total_used+1);
+						if(client->GetPlayer()->GetLevel() >= classic_level_req)
+							classic_avail = num_available_selections = total_used+1;
+						else
+							num_available_selections = 0;
+					}
+					else if(use_classic_table)
+						num_available_selections = 0;
+					
+					LogWrite(SPELL__INFO, 9, "Traits", "%s CharacterTraits used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail);
+					if(total_used < num_available_selections) {
+						allowed_trait = true;
+					}
+				}
+			}
+		}
+	
+	return allowed_trait;
+}
+
+EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
+{
+	std::unique_lock(client->GetPlayer()->trait_mutex);
+	
+	if (!client) {
+		LogWrite(SPELL__ERROR, 0, "Traits", "GetTraitListPacket called with an invalid client");
+		return 0;
+	}
+	// Sort the Data
+	if (Size() == 0)
+		return NULL;
 
+	map <int8, map <int8, vector<TraitData*> > >* SortedTraitList = client->GetPlayer()->SortedTraitList;
+	map <int8, map <int8, vector<TraitData*> > >::iterator itr;
+	map <int8, vector<TraitData*> >::iterator itr2;
+	vector<TraitData*>::iterator itr3;
+
+	map <int8, vector<TraitData*> >* ClassTraining = client->GetPlayer()->ClassTraining;
+	map <int8, vector<TraitData*> >* RaceTraits = client->GetPlayer()->RaceTraits;
+	map <int8, vector<TraitData*> >* InnateRaceTraits = client->GetPlayer()->InnateRaceTraits;
+	map <int8, vector<TraitData*> >* FocusEffects = client->GetPlayer()->FocusEffects;
+	
+	if(client->GetPlayer()->need_trait_update) {
+		SortedTraitList->clear();
+		ClassTraining->clear();
+		RaceTraits->clear();
+		InnateRaceTraits->clear();
+		FocusEffects->clear();
+	}
+	
+	if(client->GetPlayer()->need_trait_update && !GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects)) {
+		return NULL;
+	}
+	
+	client->GetPlayer()->need_trait_update = false;
+	
 	int16 version = 1;
 	int8 count = 0;
 	int8 index = 0;
@@ -167,18 +577,19 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 	version = client->GetVersion();
 
 	// Jabantiz: Get the value for num_traits in the struct (num_traits refers to the number of rows not the total number of traits)
-	for (itr = SortedTraitList.begin(); itr != SortedTraitList.end(); itr++) {
+	for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) {
 		num_traits += (itr->second).size();
 	}
 
 	PacketStruct* packet = configReader.getStruct("WS_TraitsList", version);
 
-	if (packet == NULL)
+	if (packet == NULL) {
 		return NULL;
+	}
 
 	packet->setArrayLengthByName("num_traits", num_traits);
 
-	for (itr = SortedTraitList.begin(); itr != SortedTraitList.end(); itr++) {
+	for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) {
 
 		for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++, index++) {
 			traits_size += (itr2->second).size();
@@ -260,9 +671,9 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 	}
 	
 	// Class Training portion of the packet
-	packet->setArrayLengthByName("num_trainings", ClassTraining.size());
+	packet->setArrayLengthByName("num_trainings", ClassTraining->size());
 	index = 0;
-	for (itr2 = ClassTraining.begin(); itr2 != ClassTraining.end(); itr2++, index++) {
+	for (itr2 = ClassTraining->begin(); itr2 != ClassTraining->end(); itr2++, index++) {
 		count = 0;
 		Spell* tmp_spell = 0;
 		packet->setArrayDataByName("training_level", itr2->first, index);
@@ -345,11 +756,11 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 		}
 	}
 	// Racial Traits
-	packet->setArrayLengthByName("num_sections", RaceTraits.size());
+	packet->setArrayLengthByName("num_sections", RaceTraits->size());
 	index = 0;
 	string tempStr;
 	int8 num_selections = 0;
-	for (itr2 = RaceTraits.begin(); itr2 != RaceTraits.end(); itr2++, index++) {
+	for (itr2 = RaceTraits->begin(); itr2 != RaceTraits->end(); itr2++, index++) {
 		count = 0;
 		Spell* tmp_spell = 0;
 		switch (itr2->first)
@@ -410,11 +821,11 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 
 	// total number of Innate traits
 	num_traits = 0;
-	for (itr2 = InnateRaceTraits.begin(); itr2 != InnateRaceTraits.end(); itr2++) {
+	for (itr2 = InnateRaceTraits->begin(); itr2 != InnateRaceTraits->end(); itr2++) {
 		num_traits += (itr2->second).size();
 	}
 	packet->setArrayLengthByName("num_abilities", num_traits);
-	for (itr2 = InnateRaceTraits.begin(); itr2 != InnateRaceTraits.end(); itr2++) {
+	for (itr2 = InnateRaceTraits->begin(); itr2 != InnateRaceTraits->end(); itr2++) {
 		for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) {
 			Spell* innate_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier);
 			if (innate_spell) {
@@ -433,11 +844,11 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 		num_selections = 0;
 		num_focuseffects = 0;
 		index = 0;
-		for (itr2 = FocusEffects.begin(); itr2 != FocusEffects.end(); itr2++) {
+		for (itr2 = FocusEffects->begin(); itr2 != FocusEffects->end(); itr2++) {
 			num_focuseffects += (itr2->second).size();
 		}
 		packet->setArrayLengthByName("num_focuseffects", num_focuseffects);
-		for (itr2 = FocusEffects.begin(); itr2 != FocusEffects.end(); itr2++) {
+		for (itr2 = FocusEffects->begin(); itr2 != FocusEffects->end(); itr2++) {
 			for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) {
 				Spell* spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier);
 				if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) {
@@ -474,6 +885,7 @@ EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client)
 	//DumpPacket(outapp);
 	safe_delete(packet);
 	safe_delete(data);
+	
 	return outapp;
 }
 
@@ -494,6 +906,22 @@ TraitData* MasterTraitList::GetTrait(int32 spellID) {
 	return data;
 }
 
+TraitData* MasterTraitList::GetTraitByItemID(int32 itemID) {
+	vector<TraitData*>::iterator itr;
+	TraitData* data = NULL;
+
+	MMasterTraitList.readlock(__FUNCTION__, __LINE__);
+	for (itr = TraitList.begin(); itr != TraitList.end(); itr++) {
+		if ((*itr)->item_id == itemID) {
+			data = (*itr);
+			break;
+		}
+	}
+	MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__);
+
+	return data;
+}
+
 void MasterTraitList::DestroyTraits(){
 	MMasterTraitList.writelock(__FUNCTION__, __LINE__);
 	vector<TraitData*>::iterator itr;

+ 18 - 3
EQ2/source/WorldServer/Traits/Traits.h

@@ -22,13 +22,16 @@ along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 #define __Traits__
 
 #include <vector>
+#include <map>
+#include "../../common/Mutex.h"
 #include "../../common/types.h"
 #include "../../common/EQPacket.h"
-#include "../client.h"
+
+class Client;
 
 struct TraitData
 {
-	int32 spellID ;
+	int32 spellID;
 	int8 level;
 	int8 classReq;
 	int8 raceReq;
@@ -38,6 +41,7 @@ struct TraitData
 	bool isTraining;
 	int8 tier;
 	int8 group;
+	int32 item_id;
 };
 
 #define TRAITS_ATTRIBUTES	0
@@ -52,7 +56,14 @@ class MasterTraitList
 public:
 	MasterTraitList();
 	~MasterTraitList();
-
+	
+	bool IdentifyNextTrait(Client* client, map <int8, vector<TraitData*> >* traitList, vector<TraitData*>* collectTraits, vector<TraitData*>* tieredTraits, std::map<int32, int8>* previousMatchedSpells, bool omitFoundMatches = false);
+	bool ChooseNextTrait(Client* client);
+	int16 GetSpellCount(Client* client, map <int8, vector<TraitData*> >* traits, bool onlyCharTraits = false);
+	bool IsPlayerAllowedTrait(Client* client, TraitData* trait);
+	bool GenerateTraitLists(Client* client, map <int8, map <int8, vector<TraitData*> > >* sortedTraitList, map <int8, vector<TraitData*> >* classTraining,
+										map <int8, vector<TraitData*> >* raceTraits, map <int8, vector<TraitData*> >* innateRaceTraits, map <int8, vector<TraitData*> >* focusEffects, int16 max_level = 0, int8 trait_group = 255);
+	
 	/// <summary>Sorts the traits for the given client and creats and sends the trait packet.</summary>
 	/// <param name='client'>The Client calling this function</param>
 	/// <returns>EQ2Packet*</returns>
@@ -68,6 +79,10 @@ public:
 	/// <summary>Get the trait data for the given spell.</summary>
 	/// <param name='spellID'>Spell ID to get trait data for.</param>
 	TraitData* GetTrait(int32 spellID);
+	
+	/// <summary>Get the trait data for the given item.</summary>
+	/// <param name='itemID'>Item ID to map to the trait data.</param>
+	TraitData* GetTraitByItemID(int32 itemID);
 
 	/// <summary>Empties the master trait list</summary>
 	void DestroyTraits();

+ 2 - 1
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -4735,7 +4735,7 @@ void WorldDatabase::LoadTraits(){
 	MYSQL_ROW row;
 	TraitData* trait;
 
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`, `level`, `class_req`, `race_req`, `isTrait`,`isInate`, `isFocusEffect`, `isTraining`,`tier`, `group` FROM spell_traits");
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`, `level`, `class_req`, `race_req`, `isTrait`,`isInate`, `isFocusEffect`, `isTraining`,`tier`, `group`, `item_id` FROM spell_traits where is_active = 1");
 	while (result && (row = mysql_fetch_row(result))){
 		trait = new TraitData;
 		int8 i = 0;
@@ -4749,6 +4749,7 @@ void WorldDatabase::LoadTraits(){
 		trait->isTraining = (atoi(row[(++i)]) == 0) ? false : true;
 		trait->tier = atoi(row[(++i)]);
 		trait->group = atoi(row[(++i)]);
+		trait->item_id = atoul(row[(++i)]);
 
 		master_trait_list.AddTrait(trait);
 	}

+ 26 - 4
EQ2/source/WorldServer/client.cpp

@@ -1526,9 +1526,17 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		break;
 	}
 	case OP_DoneLoadingUIResourcesMsg: {
-		if(GetVersion() < 546) {
+		if(GetVersion() <= 547) {
 			ClientPacketFunctions::SendUpdateSpellBook(this);
 		}
+			// need to quickly flash the DoF client the rest of their inventory
+		if(GetVersion() <= 547) {
+			EQ2Packet* item_app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion());
+			if (item_app) {
+				QueuePacket(item_app);
+			}
+		}
+
 		EQ2Packet* app = new EQ2Packet(OP_DoneLoadingUIResourcesMsg, 0, 0);
 		QueuePacket(app);
 		if(!player_loading_complete)
@@ -1556,6 +1564,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 			SetReadyForSpawns(true);
 		player->CalculateApplyWeight();
 		SendCharInfo();
+		GetPlayer()->GetZone()->GetSpellProcess()->SendSpellBookUpdate(this);
 		pos_update.Start();
 		quest_pos_timer.Start();
 		break;
@@ -1869,6 +1878,12 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 						player_pos_changed = true;
 						GetPlayer()->AddChangedZoneSpawn();
 						ProcessZoneIgnoreWidgets();
+						if (version <= 547) {
+							master_trait_list.ChooseNextTrait(this);
+						}
+						
+						if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower())
+							GetCurrentZone()->AddDamagedSpawn(GetPlayer());
 					}
 					else {
 						LogWrite(CCLIENT__WARNING, 0, "Client", "Player %s reported SysClient/SignalMsg state %s.", GetPlayer()->GetName(), str.data.c_str());
@@ -5157,10 +5172,14 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 	// Also need to force the char sheet update or else there can be a large delay from when you level
 	// to when you are actually able to select traits.
 	QueuePacket(GetPlayer()->GetPlayerInfo()->serialize(GetVersion()));
-	QueuePacket(master_trait_list.GetTraitListPacket(this));
 
-	if (version > 546)
+	GetPlayer()->need_trait_update = true;
+	if (version > 547) {
+		QueuePacket(master_trait_list.GetTraitListPacket(this));
 		master_aa_list.DisplayAA(this, 0, 0);
+	}
+	else
+		master_trait_list.ChooseNextTrait(this);
 
 	if (GetPlayer()->SpawnedBots.size() > 0) {
 		map<int32, int32>::iterator itr;
@@ -5170,6 +5189,9 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 				((Bot*)bot)->ChangeLevel(old_level, new_level);
 		}
 	}
+	
+	if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower())
+		GetPlayer()->GetZone()->AddDamagedSpawn(GetPlayer());
 }
 
 void Client::ChangeTSLevel(int16 old_level, int16 new_level) {
@@ -9763,7 +9785,7 @@ void Client::SetReadyForUpdates() {
 
 	ready_for_updates = true;
 	
-	if(GetVersion() <= 546) {
+	if(GetVersion() <= 547) {
 		SendRecipeList();
 	}
 }

+ 2 - 2
server/SpawnStructs.xml

@@ -443,9 +443,9 @@
 <Struct Name="Substruct_SpawnInfoStruct" ClientVersion="546" >
 <Data ElementName="hp_remaining" Type="int32" Size="1" /> <!-- 0 -->
 <Data ElementName="power_percent" Type="int32" Size="1" /> <!-- 4 -->
-<Data ElementName="spells" Substruct="Substruct_TargetSpellEffects" Size="30" /> <!-- 8 -->
-<Data ElementName="follow_target" Type="int32" Size="1" /> <!-- 278 -->
+<Data ElementName="spell_effects" Substruct="Substruct_TargetSpellEffects" Size="30" /> <!-- 8 -->
 <Data ElementName="target_id" Type="int32" Size="1" /> <!-- 282 -->
+<Data ElementName="follow_target" Type="int32" Size="1" /> <!-- 278 -->
 <Data ElementName="unknown5" Type="int8" Size="8" /> <!-- 286 -->
 <Data ElementName="corpse" Type="int8" Size="1" /> <!-- 295 -->
 <Data ElementName="class" Type="int8" Size="1" /> <!-- 296 -->