diff --git a/data/dfndata/items/lootlists.dfn b/data/dfndata/items/lootlists.dfn index 7a62a53fd..0638b0eee 100644 --- a/data/dfndata/items/lootlists.dfn +++ b/data/dfndata/items/lootlists.dfn @@ -962,4 +962,53 @@ 100|PaintedPlagueMask 100|PaintedDaemonMask 100|PaintedEvilClownMask +} + +// Treasure Map Level 0 +[LOOTLIST TreasureMapLvl0Loot] +{//approximately 1% +990|blank +10|treasuremaplvl0 +} + +// Treasure Map Level 1 +[LOOTLIST TreasureMapLvl1Loot] +{//approximately 1% +990|blank +10|treasuremaplvl1 +} + +// Treasure Map Level 2 +[LOOTLIST TreasureMapLvl2Loot] +{//approximately 1% +990|blank +10|treasuremaplvl2 +} + +// Treasure Map Level 3 +[LOOTLIST TreasureMapLvl3Loot] +{//approximately 1% +990|blank +10|treasuremaplvl3 +} + +// Treasure Map Level 4 +[LOOTLIST TreasureMapLvl4Loot] +{//approximately 1% +990|blank +10|treasuremaplvl4 +} + +// Treasure Map Level 5 +[LOOTLIST TreasureMapLvl5Loot] +{//approximately 1% +990|blank +10|treasuremaplvl5 +} + +// Treasure Map Level 6 +[LOOTLIST TreasureMapLvl6Loot] +{//approximately 1% +990|blank +10|treasuremaplvl6 } \ No newline at end of file diff --git a/data/dfndata/skills/skills.dfn b/data/dfndata/skills/skills.dfn index b28b32a15..28d129929 100644 --- a/data/dfndata/skills/skills.dfn +++ b/data/dfndata/skills/skills.dfn @@ -1206,10 +1206,10 @@ SKILLPOINT=990,5,0 SKILLPOINT=1000,0,0 } -// Imbuing +// Mysticism [SKILL 55] { -NAME=IMBUING +NAME=MYSTICISM STR=0 DEX=0 INT=0 @@ -1227,10 +1227,10 @@ SKILLPOINT=990,5,0 SKILLPOINT=1000,0,0 } -// Mysticism +// Imbuing [SKILL 56] { -NAME=MYSTICISM +NAME=IMBUING STR=0 DEX=0 INT=0 diff --git a/data/js/combat/leechstats.js b/data/js/combat/leechstats.js new file mode 100644 index 000000000..fc1086fdf --- /dev/null +++ b/data/js/combat/leechstats.js @@ -0,0 +1,56 @@ +function onEquip( pEquipper, iEquipped ) +{ + pEquipper.AddScriptTrigger( 7003 ); +} + +// Remove script trigger on unequip +function onUnequip( pUnequipper, iUnequipped ) +{ + pUnequipper.RemoveScriptTrigger( 7003 ); +} + +function onDamageDeal( attacker, damaged, damageValue, damageType ) +{ + // Fetch weapon in main hand or secondary hand + var iWeapon = attacker.FindItemLayer( 0x01 ); + if( !ValidateObject( iWeapon )) + { + iWeapon = attacker.FindItemLayer( 0x02 ); + } + + if( ValidateObject( iWeapon )) + { // Apply leech effects based on weapon properties + ApplyLeech( attacker, damaged, damageValue, iWeapon, 'healthLeech', 30 ); + ApplyLeech( attacker, damaged, damageValue, iWeapon, 'staminaLeech', 100 ); + ApplyLeech( attacker, damaged, damageValue, iWeapon, 'manaLeech', 40 ); + } + + return true; +} + +function ApplyLeech( attacker, damaged, damageValue, weapon, leechType, multiplier ) +{ + // Get the leech amount for the specified leech type from the weapon + var leechPercentVal = weapon[ leechType ]; + if( leechPercentVal > 0 ) + { + // Calculate the percent of health restored to the attacker + var leechAmt = Math.round( damageValue * ( leechPercentVal / 100 ) * ( multiplier/100 )); + + // Apply the leech effect based on the leech type + switch( leechType ) + { + case 'healthLeech': + attacker.Heal( leechAmt ); + break; + case 'staminaLeech': + attacker.stamina += leechAmt; + break; + case 'manaLeech': + attacker.mana += leechAmt; + break; + } + + attacker.SoundEffect( 0x44D, true ); + } +} \ No newline at end of file diff --git a/data/js/commands/targeting/get.js b/data/js/commands/targeting/get.js index 365d3a2b3..b07305bd5 100644 --- a/data/js/commands/targeting/get.js +++ b/data/js/commands/targeting/get.js @@ -294,30 +294,27 @@ function HandleGetItem( socket, ourItem, uKey ) case "DESC": socket.SysMessage( ourItem.desc ); break; - case "DEF": - socket.SysMessage( ourItem.Resist( 1 )); - break; case "DEF": case "RESISTARMOR": - socket.SysMessage( ourObj.Resist( 1 )); + socket.SysMessage( ourItem.Resist( 1 )); break; case "RESISTLIGHT": - socket.SysMessage( ourObj.Resist( 2 )); + socket.SysMessage( ourItem.Resist( 2 )); break; case "RESISTWATER": - socket.SysMessage( ourObj.Resist( 3 )); + socket.SysMessage( ourItem.Resist( 3 )); break; case "RESISTCOLD": - socket.SysMessage( ourObj.Resist( 4 )); + socket.SysMessage( ourItem.Resist( 4 )); break; case "RESISTFIRE": - socket.SysMessage( ourObj.Resist( 5 )); + socket.SysMessage( ourItem.Resist( 5 )); break; case "RESISTENERGY": - socket.SysMessage( ourObj.Resist( 6 )); + socket.SysMessage( ourItem.Resist( 6 )); break; case "RESISTPOISON": - socket.SysMessage( ourObj.Resist( 7 )); + socket.SysMessage( ourItem.Resist( 7 )); break; case "ARMORCLASS": case "ARMOURCLASS": @@ -598,8 +595,27 @@ function HandleGetChar( socket, ourChar, uKey ) break; case "ARMOUR": case "ARMOR": + case "RESISTARMOR": socket.SysMessage( ourChar.Resist( 1 )); break; + case "RESISTLIGHT": + socket.SysMessage( ourChar.Resist( 2 )); + break; + case "RESISTWATER": + socket.SysMessage( ourChar.Resist( 3 )); + break; + case "RESISTCOLD": + socket.SysMessage( ourChar.Resist( 4 )); + break; + case "RESISTFIRE": + socket.SysMessage( ourChar.Resist( 5 )); + break; + case "RESISTENERGY": + socket.SysMessage( ourChar.Resist( 6 )); + break; + case "RESISTPOISON": + socket.SysMessage( ourChar.Resist( 7 )); + break; case "MAXHP": socket.SysMessage( ourChar.maxhp ); break; diff --git a/data/js/item/holidays/addondeedgump.js b/data/js/item/holidays/addondeedgump.js index 36e94e3e3..1f0851f6e 100644 --- a/data/js/item/holidays/addondeedgump.js +++ b/data/js/item/holidays/addondeedgump.js @@ -181,7 +181,7 @@ function CheckForNearbyDoors( myTarget, itemToCheck, pSocket ) } } - if( myTarget.DistanceTo(itemToCheck) <= 2 ) + if( myTarget.DistanceTo( itemToCheck ) <= 2 ) { return true; } diff --git a/data/js/item/holidays/candy.js b/data/js/item/holidays/candy.js index cbe3cdae9..ed007641a 100644 --- a/data/js/item/holidays/candy.js +++ b/data/js/item/holidays/candy.js @@ -18,7 +18,6 @@ function onUseChecked(pUser, iUsed) } else { - socket.SysMessage("test 1"); if( Acidity <= 30 ) { pUser.SetTempTag( "Acidity", Acidity += 5 ); @@ -26,7 +25,6 @@ function onUseChecked(pUser, iUsed) if ( Toothach == 0) { - socket.SysMessage("test 2"); pUser.SetTempTag( "toothach", 1 ); pUser.StartTimer( 1000, 0, true ); } diff --git a/data/js/item/holidays/halloweenmasks.js b/data/js/item/holidays/halloweenmasks.js index ac1919dd4..f1f031843 100644 --- a/data/js/item/holidays/halloweenmasks.js +++ b/data/js/item/holidays/halloweenmasks.js @@ -3,7 +3,7 @@ function onCreateDFN( objMade, objType ) if( objType == 0 ) { var maskname = ""; - switch(objMade.GetTag( "paintedmask" )) + switch( objMade.GetTag( "paintedmask" )) { case 1: maskname = "A Evil Clown Mask"; break; case 2: maskname = "A Daemon Mask"; break; diff --git a/data/js/item/holidays/headonaspike.js b/data/js/item/holidays/headonaspike.js index 8da857603..44b8b6192 100644 --- a/data/js/item/holidays/headonaspike.js +++ b/data/js/item/holidays/headonaspike.js @@ -4,7 +4,7 @@ function onUseChecked( pUser, iUsed ) var headspike = new Gump; var head = 0; - switch (iUsed.sectionID) + switch( iUsed.sectionID ) { case "MrsTroubleMakersHeadOnASpike": head = 30522; break; case "BrutrinsHeadOnASpike": head = 30522; break; @@ -20,7 +20,7 @@ function onUseChecked( pUser, iUsed ) default: head = 30522; } - headspike.AddGump(100, 100, head); - headspike.Send(pUser); + headspike.AddGump( 100, 100, head ); + headspike.Send( pUser ); headspike.Free(); } \ No newline at end of file diff --git a/data/js/item/holidays/holidaypottedplant.js b/data/js/item/holidays/holidaypottedplant.js index 0cc81122f..df0948203 100644 --- a/data/js/item/holidays/holidaypottedplant.js +++ b/data/js/item/holidays/holidaypottedplant.js @@ -11,14 +11,14 @@ function onUseChecked( pUser, iUsed ) PottedPlantGump( pUser, iUsed ); } -function PottedPlantGump(pUser, iUsed) +function PottedPlantGump( pUser, iUsed ) { var socket = pUser.socket; socket.tempObj = iUsed; var pottedPlant = new Gump; pottedPlant.AddPage( 0 ); - pottedPlant.AddBackground(0, 0, 360, 195, 0xA28); + pottedPlant.AddBackground( 0, 0, 360, 195, 0xA28 ); pottedPlant.AddPage( 1 ); pottedPlant.AddText( 45, 15, 0, "Choose a Potted Plant:" ); @@ -35,7 +35,7 @@ function PottedPlantGump(pUser, iUsed) pottedPlant.Free(); } -function onGumpPress(pSock, pButton, gumpData) +function onGumpPress( pSock, pButton, gumpData ) { var pUser = pSock.currentChar; var iUsed = pSock.tempObj; @@ -48,7 +48,7 @@ function onGumpPress(pSock, pButton, gumpData) } var pottedplant = ""; - if ( pButton >= 1 && pButton <= 5 ) + if( pButton >= 1 && pButton <= 5 ) { var plantIds = [0x11C8, 0x11C9, 0x11CA, 0x11CB, 0x11CC]; pottedplant = "0x" + ( plantIds[pButton - 1] ).toString( 16 ); diff --git a/data/js/item/holidays/pumpkins.js b/data/js/item/holidays/pumpkins.js index e63df2678..68cb883a8 100644 --- a/data/js/item/holidays/pumpkins.js +++ b/data/js/item/holidays/pumpkins.js @@ -16,7 +16,7 @@ function onCreateDFN( objMade, objType ) if( objMade.id == 0x0C6A || objMade.id == 0x0C6B ) { - objMade.weight = Math.floor(Math.random() * ( 2500 - 1200 + 1 ) + 1200); + objMade.weight = Math.floor( Math.random() * ( 2500 - 1200 + 1 ) + 1200 ); } else if( objMade.id == 0x0C6C ) { @@ -72,22 +72,22 @@ function onUseChecked( pUser, iUsed ) // Randomize countdown length, if enabled if( randomizePumpkinCountdown ) { - iUsed.speed = RandomNumber(iUsed.speed - 1, iUsed.speed + 1); + iUsed.speed = RandomNumber( iUsed.speed - 1, iUsed.speed + 1 ); } // Item's speed forms the basis of the countdownTime var countdownTime = iUsed.speed * 1000; // Start the initial timer that shows the first number over the character/object's head - iUsed.StartTimer(200, 1, true); + iUsed.StartTimer( 200, 1, true ); // Start timers with IDs from 2, and count until we reach item's speed + 1 var iCount = 2; - for (iCount = 2; iCount < (iUsed.speed + 2); iCount++) + for( iCount = 2; iCount < ( iUsed.speed + 2 ); iCount++ ) { - iUsed.StartTimer((iCount - 1) * 1000, iCount, true); + iUsed.StartTimer(( iCount - 1) * 1000, iCount, true ); } - socket.CustomTarget(0, GetDictionaryEntry(1348, socket.language)); //Now would be a good time to throw it! + socket.CustomTarget( 0, GetDictionaryEntry( 1348, socket.language )); //Now would be a good time to throw it! } return false; } @@ -96,7 +96,7 @@ function onCallback0( socket, ourObj ) { var mChar = socket.currentChar; var iUsed = socket.tempObj; - if ( mChar && mChar.isChar && iUsed && iUsed.isItem ) + if( mChar && mChar.isChar && iUsed && iUsed.isItem ) { var StrangeByte = socket.GetWord( 1 ); if( StrangeByte == 0 && ourObj ) @@ -107,7 +107,7 @@ function onCallback0( socket, ourObj ) iUsed.container = null; iUsed.Teleport( ourObj ); } - else + else { socket.SysMessage( GetDictionaryEntry( 1646, socket.language )); // You cannot see that @@ -252,7 +252,7 @@ function ApplyExplosionDamage( timerObj, targetChar ) return; // Deal damage, and do criminal check for source character! - targetChar.Damage(RandomNumber( timerObj.lodamage, timerObj.hidamage ), 5, sourceChar, true ); + targetChar.Damage( RandomNumber( timerObj.lodamage, timerObj.hidamage ), 5, sourceChar, true ); // If target is an NPC, make them attack the person who threw the potion! if( targetChar.npc && targetChar.target == null && targetChar.atWar == false ) @@ -299,7 +299,7 @@ function onPickup( iPickedUp, pGrabber, containerObj ) else if( iPickedUp.GetTag( "Named" ) == 0 ) { iPickedUp.name = pGrabber.name + " Pumpkin"; - iPickedUp.SetTag("Named", 1); + iPickedUp.SetTag( "Named", 1 ); } } return true; diff --git a/data/js/item/holidays/snowpile.js b/data/js/item/holidays/snowpile.js index c2e92ecc9..58d289745 100644 --- a/data/js/item/holidays/snowpile.js +++ b/data/js/item/holidays/snowpile.js @@ -59,7 +59,7 @@ function onCallback0( socket, myTarget ) pUser.visible = 0; } - if (!socket.GetWord(1) && myTarget.isChar && myTarget.socket ) + if( !socket.GetWord( 1 ) && myTarget.isChar && myTarget.socket ) { pUser.DoAction( 0x9 ); pUser.SoundEffect( 0x145, true ); @@ -73,10 +73,10 @@ function onCallback0( socket, myTarget ) } } -function onTimer( pUser, timerID ) +function onTimer( pUser, timerID ) { var socket = pUser.socket; - if( pUser.visible == 1 || pUser.visible == 2 ) + if( pUser.visible == 1 || pUser.visible == 2 ) { pUser.visible = 0; } diff --git a/data/js/jse_fileassociations.scp b/data/js/jse_fileassociations.scp index 25a8d8f6e..4318369ce 100644 --- a/data/js/jse_fileassociations.scp +++ b/data/js/jse_fileassociations.scp @@ -335,6 +335,7 @@ // Combat Scripts [7000-7499] //------------------------------------------- 7000=combat/peacemake_effect.js +7003=combat/leechstats.js //------------------------------------------- // Misc Player Scripts [8000-8499] diff --git a/data/js/jse_objectassociations.scp b/data/js/jse_objectassociations.scp index 93f7c41d5..25cce9751 100644 --- a/data/js/jse_objectassociations.scp +++ b/data/js/jse_objectassociations.scp @@ -580,6 +580,8 @@ 0x10a4=15007 // Swords +0x0EC2=5009 //cleaver +0x0EC3=5009 //cleaver 0x0EC4=5009 //skinning knife 0x0EC5=5009 //skinning knife 0x0F60=5009 //longsword diff --git a/data/js/npc/ai/vendor_bdo_dispenser.js b/data/js/npc/ai/vendor_bdo_dispenser.js index 0b5739539..9d069ea22 100644 --- a/data/js/npc/ai/vendor_bdo_dispenser.js +++ b/data/js/npc/ai/vendor_bdo_dispenser.js @@ -302,7 +302,7 @@ function onSpeech( myString, pUser, myNPC ) { if( CheckBodTimers( pUser, myNPC.GetTag( "bodType" ) )) { - if( EraStringToNum( GetServerSetting( "CoreShardEra" )) <= EraStringToNum( "lbr" )) + if( EraStringToNum( GetServerSetting( "CoreShardEra" )) >= EraStringToNum( "lbr" )) { myNPC.SetTimer( Timer.MOVETIME, 1000 ); // Pause NPC in their tracks for a second myNPC.TurnToward( pUser ); diff --git a/data/js/server/data/weapontypes.js b/data/js/server/data/weapontypes.js index 42fe15a2b..0a6940e56 100644 --- a/data/js/server/data/weapontypes.js +++ b/data/js/server/data/weapontypes.js @@ -208,6 +208,10 @@ function GetWeaponType( mChar, itemID ) case 0x48B3: //gargish axe - SA weaponType = "TWOHND_AXES"; break; // Default Maces + case 0x0DF2: // Wand + case 0x0DF3: // Wand + case 0x0DF4: // Wand + case 0x0DF5: // Wand case 0x0FB4: //sledge hammer case 0x0FB5: //sledge hammer case 0x0F5C: //mace diff --git a/data/js/server/house/houseCommands.js b/data/js/server/house/houseCommands.js index e27bef044..2c7afd3f3 100644 --- a/data/js/server/house/houseCommands.js +++ b/data/js/server/house/houseCommands.js @@ -1522,8 +1522,17 @@ function DemolishHouse( pSocket, iMulti ) iMulti.RemoveTrashCont( itemInHouse ); itemInHouse.Delete(); } - else if( itemInHouse.movable == 2 ) // items placed as part of the house itself like forge/anvil in smithy + else if( itemInHouse.movable == 2 || itemInHouse.GetTag( "deed" )) // items placed as part of the house itself like forge/anvil in smithy or the addon deed { + var addonDeed = itemInHouse.GetTag( "deed" ); + if( addonDeed ) + { + var newDeed = CreateDFNItem( pSocket, pSocket.currentChar, addonDeed, 1, "ITEM", true ); + if( newDeed ) + { + pSocket.SysMessage( GetDictionaryEntry( 1970, pSocket.language )); // A deed for the house add-on has been placed in your backpack. + } + } itemInHouse.Delete(); } else if( itemInHouse.isLockedDown ) diff --git a/data/js/server/network/0xf1_freeshardServerPoll.js b/data/js/server/network/0xf1_freeshardServerPoll.js index 4b278c240..33e2f1434 100644 --- a/data/js/server/network/0xf1_freeshardServerPoll.js +++ b/data/js/server/network/0xf1_freeshardServerPoll.js @@ -1,6 +1,6 @@ // Handler for Freeshard Server Poll requests from // CUO Web (https://play.classicuo.org) -// Last Updated: 5. September, 2022 +// Last Updated: 3. Decemeber, 2024 function PacketRegistration() { @@ -27,7 +27,18 @@ function onPacketReceive( pSocket, packetNum, subCommand ) else { Console.Print( "Freeshard Server Poll Packet detected, responding..." ); - SendServerPollInfo( pSocket ); + SendServerPollInfoCompact( pSocket ); + } + break; + case 0xFF: + if( !GetServerSetting( "FREESHARDSERVERPOLL" )) + { + Console.Print( "Freeshard Server Poll Packet detected; response disabled via FREESHARDPOLL ini-setting.\n" ); + } + else + { + Console.Print( "Freeshard Server Poll Packet detected, responding..." ); + SendServerPollInfoExtended( pSocket ); } break; default: @@ -36,7 +47,7 @@ function onPacketReceive( pSocket, packetNum, subCommand ) return; } -function SendServerPollInfo( pSocket ) +function SendServerPollInfoCompact( pSocket ) { var uptime = Math.floor( GetCurrentClock() / 1000 ) - Math.floor( GetStartTime() / 1000 ); var totalAccounts = GetAccountCount(); @@ -55,8 +66,31 @@ function SendServerPollInfo( pSocket ) toSend.WriteLong( 15, uptime ); // Server Uptime toSend.WriteLong( 19, memoryHigh ); // Max memory usage - no need to send toSend.WriteLong( 23, memoryLow ); // Min memory usage - no need to send - pSocket.Send( toSend ); + pSocket.Send(toSend); toSend.Free(); Console.Print( "Done!\n" ); Console.Log( "Response sent to Freeshard Server Poll Packet (totalOnline: " + totalOnline + ", upTimeInSeconds: " + uptime ); } + +function SendServerPollInfoExtended( pSocket ) +{ + var protocolVersion = 2; + var shardName = GetServerSetting( "SERVERNAME" ); + var uptime = Math.floor( GetCurrentClock() / 1000 ) - Math.floor( GetStartTime() / 1000 ); + var totalOnline = GetPlayerCount(); + var totalItems = 0; + var totalChars = 0; + var totalMemory = 0; + var statsStr = "UOX3, Name=" + shardName + ", Age=" + Math.round( uptime / 60 / 60 ) + ", Clients=" + totalOnline + ", Items=" + totalItems + ", Chars=" + totalChars + ", Mem=" + totalMemory + "K, Ver=" + protocolVersion; + var toSend = new Packet; + + toSend.ReserveSize( statsStr.length + 2 ); + toSend.WriteByte( 0, 0x53 ); // PacketID + toSend.WriteShort( 1, statsStr.length ); // Length + toSend.WriteString( 3, statsStr, statsStr.length ); + toSend.WriteByte( statsStr.length + 1, 0x00 ); // null terminated + pSocket.Send( toSend ); + toSend.Free(); + Console.Print( "Done!\n" ); + Console.Log( "Response sent to Freeshard Server Poll Packet (totalOnline: " + totalOnline + ", upTimeInSeconds: " + uptime ); +} \ No newline at end of file diff --git a/docs/jsdocs.html b/docs/jsdocs.html index 8dbe347a8..8665ea36c 100644 --- a/docs/jsdocs.html +++ b/docs/jsdocs.html @@ -4018,7 +4018,7 @@

January 9th, 2022

Prototype

-

function onSpellTargetSelect( pCaster, myTarget, spellID )

+

function onSpellTargetSelect( myTarget, pCaster, spellID )

Purpose

@@ -4043,7 +4043,7 @@

January 9th, 2022

Example of usage

// Script attached to an NPC that's immune to magic, where event returns 2 to reject spell being cast
-function onSpellTargetSelect( pCaster, myTarget, spellID )
+function onSpellTargetSelect( myTarget, pCaster, spellID )
 {
 	var socket = pCaster.socket;
 	if( socket != null )
diff --git a/source/CPacketSend.cpp b/source/CPacketSend.cpp
index ba30f52d8..961323651 100644
--- a/source/CPacketSend.cpp
+++ b/source/CPacketSend.cpp
@@ -70,7 +70,7 @@ using namespace std::string_literals;
 //|						BYTE[2] unknown5 (0x0)
 //|						BYTE[4] unknown6 (0x0)
 //|
-//|						Note: Only send once after login. It’s mandatory to send it once.
+//|						Note: Only send once after login. It’s mandatory to send it once.
 //o------------------------------------------------------------------------------------------------o
 void CPCharLocBody::Log( std::ostream &outStream, bool fullHeader )
 {
@@ -1411,7 +1411,7 @@ CPPaperdoll &CPPaperdoll::operator = ( CChar &toCopy )
 //|
 //|					Packet Build
 //|						BYTE cmd
-//|						BYTE type (0x00 – “It starts to rain”, 0x01 – “A fierce storm approaches.”, 0x02 – “It begins to snow”, 0x03 - “A storm is brewing.”, 0xFF – None (turns off sound effects), 0xFE (no effect?? Set temperature?)
+//|						BYTE type (0x00 – “It starts to rain”, 0x01 – “A fierce storm approaches.”, 0x02 – “It begins to snow”, 0x03 - “A storm is brewing.”, 0xFF – None (turns off sound effects), 0xFE (no effect?? Set temperature?)
 //|						BYTE num (number of weather effects on screen)
 //|						BYTE temperature
 //|
@@ -1422,8 +1422,8 @@ CPPaperdoll &CPPaperdoll::operator = ( CChar &toCopy )
 //|					Note: Weather messages are only displayed when weather starts.
 //|					Note: Weather will end automatically after 6 minutes without any weather change packets.
 //|					Note: You can totally end weather (to display a new message) by teleporting.
-//|						I think it’s either the 0x78 or 0x20 messages that reset it, though I
-//|						haven’t checked to be sure (other possibilities, 0x4F or 0x4E)
+//|						I think it’s either the 0x78 or 0x20 messages that reset it, though I
+//|						haven’t checked to be sure (other possibilities, 0x4F or 0x4E)
 //o------------------------------------------------------------------------------------------------o
 void CPWeather::InternalReset( void )
 {
@@ -2043,7 +2043,7 @@ void CPOpenGump::Serial( SERIAL toSet )
 //|						BYTE unknown (0x00)
 //|						BYTE click zLoc
 //|						BYTE[2] model # (if a static tile, 0 if a map/landscape tile)
-//|							Note: the model # shouldn’t be trusted.
+//|							Note: the model # shouldn’t be trusted.
 //o------------------------------------------------------------------------------------------------o
 CPTargetCursor::CPTargetCursor()
 {
@@ -2601,7 +2601,7 @@ void CPStatWindow::TithingPoints( UI32 value )
 //|							0x07 idle
 //|							0x05 another character is online
 //|								"Another character from this account is currently online in this world.
-//|								You must either log in as that character or wait for it to time out.”
+//|								You must either log in as that character or wait for it to time out.”
 //o------------------------------------------------------------------------------------------------o
 void CPIdleWarning::InternalReset( void )
 {
@@ -3107,12 +3107,12 @@ CPMultiPlacementView::CPMultiPlacementView( SERIAL toSet )
 //|						0	neither T2A NOR LBR, equal to not sending it at all,
 //|						1	is T2A, chatbutton,
 //|						2	is LBR without chatbutton,
-//|						3	is LBR with chatbutton…
+//|						3	is LBR with chatbutton…
 //|						8013	LBR + chatbutton + AOS enabled
 //|
 //|						Note1: this message is send immediately after login.
-//|						Note2: on OSI  servers this controls features OSI enables/disables via “upgrade codes.”
-//|						Note3: a 3 doesn’t seem to “hurt” older (NON LBR) clients.
+//|						Note2: on OSI  servers this controls features OSI enables/disables via “upgrade codes.”
+//|						Note3: a 3 doesn’t seem to “hurt” older (NON LBR) clients.
 //o------------------------------------------------------------------------------------------------o
 CPEnableClientFeatures::CPEnableClientFeatures( CSocket *mSock )
 {
@@ -6118,7 +6118,7 @@ void CPObjectInfo::Objects( CItem& mItem, CChar& mChar )
 //|						BYTE[2] Font
 //|						BYTE[4] Language
 //|						BYTE[30] Name
-//|						BYTE[?][2] Msg – Null Terminated (blockSize - 48)
+//|						BYTE[?][2] Msg – Null Terminated (blockSize - 48)
 //|
 //|					The various types of text is as follows:
 //|						0x00 - Normal
@@ -6294,7 +6294,7 @@ void CPUnicodeSpeech::GhostIt( [[maybe_unused]] UI08 method )
 //|						BYTE[2] Font
 //|						BYTE[4] Language
 //|						BYTE[30] Name
-//|						BYTE[?][2] Msg – Null Terminated (blockSize - 48)
+//|						BYTE[?][2] Msg – Null Terminated (blockSize - 48)
 //|
 //|					The various types of text is as follows:
 //|						0x00 - Normal
@@ -6572,7 +6572,7 @@ void CPSecureTrading::Name( const std::string& nameFollowing )
 //o------------------------------------------------------------------------------------------------o
 //| Purpose		-	Handles outgoing packet with server response to all names request
 //o------------------------------------------------------------------------------------------------o
-//|	Notes		-	Packet: 0x98 (All-names “3D”)
+//|	Notes		-	Packet: 0x98 (All-names “3D”)
 //|					Size: Variable
 //|
 //|					Packet Build
@@ -6588,7 +6588,7 @@ void CPSecureTrading::Name( const std::string& nameFollowing )
 //|						Client asks for name of object with ID x.
 //|						Server has to reply with ID + name
 //|						Client automatically knows names of items.
-//|						Hence it only asks only for NPC/Player names nearby, but shows bars of items plus NPC’s.
+//|						Hence it only asks only for NPC/Player names nearby, but shows bars of items plus NPC’s.
 //|
 //|						Client request has 7 bytes, server-reply 37
 //|						Triggered by Crtl + Shift.
@@ -6641,7 +6641,7 @@ void CPAllNames3D::Object( CBaseObject& obj )
 //|								BYTE[var] null terminated line
 //|						Note:
 //|						server side: # of pages equals value given in 0x93/0xd4
-//|						EACH page # given. If empty: # lines: 0 + terminator (=3 0’s)
+//|						EACH page # given. If empty: # lines: 0 + terminator (=3 0’s)
 //|						client side:  # of pages always 1. if 2 pages changed, client generates 2 packets.
 //o------------------------------------------------------------------------------------------------o
 void CPBookPage::IncLength( UI08 amount )
@@ -7009,7 +7009,7 @@ bool CPNewSpellBook::ClientCanReceive( CSocket *mSock )
 //|							BYTE[4]	Serial
 //|							BYTE Damage // how much damage was done ?
 //|
-//|						Note: displays damage above the npc/player’s head.
+//|						Note: displays damage above the npc/player’s head.
 //o------------------------------------------------------------------------------------------------o
 void CPDisplayDamage::InternalReset( void )
 {
@@ -7473,6 +7473,28 @@ void CPToolTip::CopyItemData( CItem& cItem, size_t &totalStringLen, bool addAmou
 		tempEntry.ourText = oldstrutil::number( cItem.GetTempVar( CITV_MOREZ ));
 		FinalizeData( tempEntry, totalStringLen );
 	}
+
+	if( cItem.GetManaLeech() > 0 )
+	{
+		tempEntry.stringNum = 1060427; // hit mana leech ~1_val~%
+		tempEntry.ourText = oldstrutil::number( cItem.GetManaLeech() );
+		FinalizeData( tempEntry, totalStringLen );
+	}
+
+	if( cItem.GetStaminaLeech() > 0 )
+	{
+		tempEntry.stringNum = 1060430; // hit stamina leech ~1_val~%
+		tempEntry.ourText = oldstrutil::number( cItem.GetStaminaLeech() );
+		FinalizeData( tempEntry, totalStringLen );
+	}
+
+	if( cItem.GetHealthLeech() > 0 )
+	{
+		tempEntry.stringNum = 1060422; // hit life leech ~1_val~%
+		tempEntry.ourText = oldstrutil::number( cItem.GetHealthLeech() );
+		FinalizeData( tempEntry, totalStringLen );
+	}
+
 	if( cItem.GetType() == IT_SPELLCHANNELING )
 	{
 		tempEntry.stringNum = 1060482; // spell channeling
@@ -7665,6 +7687,41 @@ void CPToolTip::CopyItemData( CItem& cItem, size_t &totalStringLen, bool addAmou
 				FinalizeData( tempEntry, totalStringLen );
 			}
 
+			if( cItem.GetHitChance() > 0 )
+			{
+				tempEntry.stringNum = 1060415; // hit chance increase ~1_val~%
+				tempEntry.ourText = oldstrutil::number( cItem.GetHitChance() );
+				FinalizeData( tempEntry, totalStringLen );
+			}
+
+			if( cItem.GetDefenseChance() > 0 )
+			{
+				tempEntry.stringNum = 1060408; // defense chance increase ~1_val~%
+				tempEntry.ourText = oldstrutil::number( cItem.GetDefenseChance() );
+				FinalizeData( tempEntry, totalStringLen );
+			}
+
+			if( cItem.GetHealthBonus() > 0 )
+			{
+				tempEntry.stringNum = 1060431; // hit point increase ~1_val~
+				tempEntry.ourText = oldstrutil::number( cItem.GetHealthBonus() );
+				FinalizeData( tempEntry, totalStringLen );
+			}
+
+			if( cItem.GetStaminaBonus() > 0 )
+			{
+				tempEntry.stringNum = 1060484; // stamina increase ~1_val~
+				tempEntry.ourText = oldstrutil::number( cItem.GetStaminaBonus() );
+				FinalizeData( tempEntry, totalStringLen );
+			}
+
+			if( cItem.GetManaBonus() > 0 )
+			{
+				tempEntry.stringNum = 1060439; // mana increase ~1_val~
+				tempEntry.ourText = oldstrutil::number( cItem.GetManaBonus() );
+				FinalizeData( tempEntry, totalStringLen );
+			}
+
 			if( cItem.GetStrength() > 1 )
 			{
 				tempEntry.stringNum = 1061170; // strength requirement ~1_val~
@@ -8036,9 +8093,9 @@ bool CPSellList::CanSellItems( CChar &mChar, CChar &vendor )
 //|						 BYTE[2] len
 //|						 BYTE subcmd
 //|						 BYTE[ len - 4 ] submessage
-//|							Submessage 0 – Display Bulletin Board
+//|							Submessage 0 – Display Bulletin Board
 //|							BYTE[4] Board serial
-//|							BYTE[22] board name (default is “bulletin board”, the rest nulls)
+//|							BYTE[22] board name (default is “bulletin board”, the rest nulls)
 //|							BYTE[4] unknown/ID?
 //|							BYTE[4] zero (0)
 //o------------------------------------------------------------------------------------------------o
@@ -8118,7 +8175,7 @@ CPOpenMessageBoard::CPOpenMessageBoard( CSocket *mSock )
 //|							BYTE subjectLen
 //|							BYTE[subjectLen] subject (null terminated string)
 //|							BYTE timeLen
-//|							BYTE[timeLen] time (null terminated string with time of posting) (“Day 1 @ 11:28”)
+//|							BYTE[timeLen] time (null terminated string with time of posting) (“Day 1 @ 11:28”)
 //o------------------------------------------------------------------------------------------------o
 //|					Subcommand: 0x2 (Message Summary)
 //|					Size: Variable
@@ -8135,7 +8192,7 @@ CPOpenMessageBoard::CPOpenMessageBoard( CSocket *mSock )
 //|							BYTE subjectLen
 //|							BYTE[subjectLen] subject (null terminated string)
 //|							BYTE timeLen
-//|							BYTE[timeLen] time (null terminated string with time of posting) (“Day 1 @ 11:28”)
+//|							BYTE[timeLen] time (null terminated string with time of posting) (“Day 1 @ 11:28”)
 //|							BYTE[5] Unknown (01 90 03 F7 00)
 //|							BYTE numlines
 //|							For each line:
diff --git a/source/Changelog.txt b/source/Changelog.txt
index 28782fa9c..ef568652c 100644
--- a/source/Changelog.txt
+++ b/source/Changelog.txt
@@ -1,6 +1,53 @@
+21/01/2025 - Dragon Slayer
+	Fixed damage dealt in combat was incorrectly modified while applying defense modifiers. (combat.cpp)
+
+07/12/2024 - Dragon Slayer
+	Updated the packet 0xf1 to reflect the current CUO Web Ping while maintaining backward compatibility with older ping statuses. (0xf1_freeshardServerPoll.js) (Thanks Karasho and Xuri)
+
+12/08/2024 - Dragon Slayer
+	Fixed an issue where Imbuing and Mysticism skills were listed in wrong order in skills.dfn and enums.h
+
+4/07/2024 - Dragon Slayer
+	Fixed Cleaver ID missing in the jse_objectassociations.scp for carving corpses
+
+18/06/2024 - Dragon Slayer
+	Added three new DFN tags for Items:
+		HEALTHLEECH=# 	// It gives an attacker the ability to leech health from his opponent every time he successfully delivers a hit adds it to himself.
+		STAMINALEECH=# 	// It gives an attacker the ability to leech Stamina from his opponent every time he successfully delivers a hit adds it to himself.
+		MANALEECH=# 	// It gives an attacker the ability to leech Mana from his opponent every time he successfully delivers a hit adds it to himself.
+	These are also available as JS Engine object properties: .healthLeech, .staminaLeech and .manaLeech
+	Added leechstats.js file that controls the combat for the properties. (script 7003)
+	To add this script to a weapon only. add in SCRIPT=7003, HEALTHLEECH=# or STAMINALEECH=# or MANALEECH=# it can also be all three on the weapon.
+
+14/06/2024 - Dragon Slayer
+	Added two new DFN tags for Items:
+		HITCHANCE=# 		// Increases the player's chance to hit a target with wrestling, melee and ranged weapons.
+		DEFENSECHANCE=# 	// Increases the wearer's chance that his opponents' swings (or arrows/bolts) will miss.
+	These are also available as JS Engine object properties: .hitChance, .defenseChance
+
+13/06/2024 - Dragon Slayer
+	Added three More AOS Props
+	-HEALTHBONUS=#
+	-MANABONUS=#
+	-STAMINABONUS=#
+	Add this properties to any weapon/armor/jewlery will give the player more hp/mana/stam why its equiped. depending on number you add with it
+	These are also available as JS Engine object properties: .healthBonus, .staminaBonus, .manaBonus
+
+6/06/2024 - Dragon Slayer
+	Fixed Accepting bods, When the expansion is to to lbr or later.
+
+29/05/2024 - Dragon Slayer
+	House add-on deeds are now returned when an add-on is present in the house on demolish.
+
+27/05/2024 - Dragon Slayer
+	Added Missing Wand ID's to combat weapon type in core and in js.
+
 13/05/2024 - Dragon Slayer
 	Added New Shield Type 107 so shield ID's no longer have to be in hard code for shields to work properly
 
+11/05/2024 - Dragon Slayer
+	Updated 'get command (js/commands/targeting/get.js) to use correct object reference when getting resist values for items, and added support for getting resist values for chars
+
 09/05/2024 - Dragon Slayer
 	Added ArtifactRarity AOS Property for items
 	-ARTIFACTRARITY=#
diff --git a/source/UOXJSPropertyEnums.h b/source/UOXJSPropertyEnums.h
index 45517f28d..2c2eb0a80 100644
--- a/source/UOXJSPropertyEnums.h
+++ b/source/UOXJSPropertyEnums.h
@@ -339,6 +339,8 @@ enum CC_Properties
 	CCP_SPAWNSERIAL,
 	CCP_SPATTACK,
 	CCP_SPDELAY,
+	CCP_HITCHANCE,
+	CCP_DEFENSECHANCE,
 	CCP_AITYPE,
 	CCP_SPLIT,
 	CCP_SPLITCHANCE,
@@ -461,6 +463,9 @@ enum CI_Properties
 	CIP_DAMAGEPOISON,
 	CIP_DAMAGERAIN,
 	CIP_DAMAGESNOW,
+	CIP_HITCHANCE,
+	CIP_DEFENSECHANCE,
+
 	CIP_ARTIFACTRARITY,
 	CIP_NAME2,
 	CIP_ISITEM,
@@ -496,7 +501,9 @@ enum CI_Properties
 	CIP_SECTIONALIST,
 	CIP_MININTERVAL,
 	CIP_MAXINTERVAL,
-
+	CIP_HEALTHLEECH,
+	CIP_STAMINALEECH,
+	CIP_MANALEECH,
 	CIP_ISNEWBIE,
 	CIP_ISDISPELLABLE,
 	CIP_MADEWITH,
@@ -517,6 +524,9 @@ enum CI_Properties
 	CIP_ISCONTTYPE,
 	CIP_CARVESECTION,
 	CIP_SPEED,
+	CIP_HEALTHBONUS,
+	CIP_STAMINABONUS,
+	CIP_MANABONUS,
 	CIP_MULTI,
 	CIP_AMMOID,
 	CIP_AMMOHUE,
diff --git a/source/UOXJSPropertyFuncs.cpp b/source/UOXJSPropertyFuncs.cpp
index 5fc2a3a82..3418eec20 100644
--- a/source/UOXJSPropertyFuncs.cpp
+++ b/source/UOXJSPropertyFuncs.cpp
@@ -676,7 +676,16 @@ JSBool CItemProps_getProperty( JSContext *cx, JSObject *obj, jsval id, jsval *vp
 			case CIP_DAMAGERAIN:		*vp = BOOLEAN_TO_JSVAL( gPriv->GetWeatherDamage( RAIN ));	break;
 			case CIP_DAMAGESNOW:		*vp = BOOLEAN_TO_JSVAL( gPriv->GetWeatherDamage( SNOW ));	break;
 			case CIP_SPEED:			*vp = INT_TO_JSVAL( gPriv->GetSpeed() );			break;
+			case CIP_HEALTHLEECH:	*vp = INT_TO_JSVAL( gPriv->GetHealthLeech() );			break;
+			case CIP_STAMINALEECH:	*vp = INT_TO_JSVAL( gPriv->GetStaminaLeech() );			break;
+			case CIP_MANALEECH:		*vp = INT_TO_JSVAL( gPriv->GetManaLeech() );			break;
+			case CIP_HITCHANCE:	*vp = INT_TO_JSVAL( gPriv->GetHitChance() );			break;
+			case CIP_DEFENSECHANCE:	*vp = INT_TO_JSVAL( gPriv->GetDefenseChance() );			break;
+			case CIP_HEALTHBONUS:		*vp = INT_TO_JSVAL( gPriv->GetHealthBonus() );			break;
+			case CIP_STAMINABONUS:		*vp = INT_TO_JSVAL( gPriv->GetStaminaBonus() );			break;
+			case CIP_MANABONUS:		*vp = INT_TO_JSVAL( gPriv->GetManaBonus() );			break;
 			case CIP_ARTIFACTRARITY:		*vp = INT_TO_JSVAL( gPriv->GetArtifactRarity() );			break;
+
 			case CIP_NAME2:
 				tString = JS_NewStringCopyZ( cx, gPriv->GetName2().c_str() );
 				*vp = STRING_TO_JSVAL( tString );
@@ -1322,7 +1331,16 @@ JSBool CItemProps_setProperty( JSContext *cx, JSObject *obj, jsval id, jsval *vp
 			case CIP_DAMAGERAIN:	gPriv->SetWeatherDamage( RAIN, encaps.toBool() );			break;
 			case CIP_DAMAGESNOW:	gPriv->SetWeatherDamage( SNOW, encaps.toBool() );			break;
 			case CIP_SPEED:			gPriv->SetSpeed( static_cast( encaps.toInt() ));		break;
+			case CIP_HEALTHLEECH:	gPriv->SetHealthLeech( static_cast( encaps.toInt() ));	break;
+			case CIP_STAMINALEECH:	gPriv->SetStaminaLeech( static_cast( encaps.toInt() ));	break;
+			case CIP_MANALEECH:		gPriv->SetManaLeech( static_cast( encaps.toInt() ));	break;
+			case CIP_HITCHANCE:	gPriv->SetHitChance( static_cast( encaps.toInt() ));	break;
+			case CIP_DEFENSECHANCE:	gPriv->SetDefenseChance( static_cast( encaps.toInt() ));	break;
+			case CIP_HEALTHBONUS:		gPriv->SetHealthBonus( static_cast( encaps.toInt() ));	break;
+			case CIP_STAMINABONUS:		gPriv->SetStaminaBonus( static_cast( encaps.toInt() ));	break;
+			case CIP_MANABONUS:		gPriv->SetManaBonus( static_cast( encaps.toInt() ));	break;
 			case CIP_ARTIFACTRARITY:	gPriv->SetArtifactRarity( static_cast( encaps.toInt() ));	break;
+
 			case CIP_NAME2:			gPriv->SetName2( encaps.toString() );						break;
 			case CIP_RACE:			gPriv->SetRace( static_cast( encaps.toInt() ));		break;
 			case CIP_MAXHP:			gPriv->SetMaxHP( static_cast( encaps.toInt() ));		break;
@@ -1996,6 +2014,8 @@ JSBool CCharacterProps_getProperty( JSContext *cx, JSObject *obj, jsval id, jsva
 			case CCP_HOUSEICONS:	*vp = BOOLEAN_TO_JSVAL( gPriv->ViewHouseAsIcon() );			break;
 			case CCP_SPATTACK:		*vp = INT_TO_JSVAL( gPriv->GetSpAttack() );					break;
 			case CCP_SPDELAY:		*vp = INT_TO_JSVAL( gPriv->GetSpDelay() );					break;
+			case CCP_HITCHANCE:		*vp = INT_TO_JSVAL( gPriv->GetHitChance() );				break;
+			case CCP_DEFENSECHANCE:	*vp = INT_TO_JSVAL( gPriv->GetDefenseChance() );			break;
 			case CCP_AITYPE:		*vp = INT_TO_JSVAL( gPriv->GetNpcAiType() );				break;
 			case CCP_SPLIT:			*vp = INT_TO_JSVAL( gPriv->GetSplit() );					break;
 			case CCP_SPLITCHANCE:	*vp = INT_TO_JSVAL( gPriv->GetSplitChance() );				break;
@@ -2501,6 +2521,8 @@ JSBool CCharacterProps_setProperty( JSContext *cx, JSObject *obj, jsval id, jsva
 			case CCP_HOUSEICONS:	gPriv->SetViewHouseAsIcon( encaps.toBool() );				break;
 			case CCP_SPATTACK:		gPriv->SetSpAttack( static_cast( encaps.toInt() ));	break;
 			case CCP_SPDELAY:		gPriv->SetSpDelay( static_cast( encaps.toInt() ));	break;
+			case CCP_HITCHANCE:		gPriv->SetHitChance( static_cast( encaps.toInt() ));	break;
+			case CCP_DEFENSECHANCE:	gPriv->SetDefenseChance( static_cast( encaps.toInt() ));	break;
 			case CCP_AITYPE:		gPriv->SetNPCAiType( static_cast( encaps.toInt() ));	break;
 			case CCP_SPLIT:			gPriv->SetSplit( static_cast( encaps.toInt() ));		break;
 			case CCP_SPLITCHANCE:	gPriv->SetSplitChance( static_cast( encaps.toInt() ));break;
diff --git a/source/UOXJSPropertySpecs.h b/source/UOXJSPropertySpecs.h
index 8607ffab5..e9e341754 100644
--- a/source/UOXJSPropertySpecs.h
+++ b/source/UOXJSPropertySpecs.h
@@ -357,6 +357,8 @@ inline JSPropertySpec CCharacterProps[] =
 	{ "houseicons",		CCP_HOUSEICONS,		JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "spattack",		CCP_SPATTACK,		JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "spdelay",		CCP_SPDELAY,		JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "hitChance",		CCP_HITCHANCE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "defenseChance",	CCP_DEFENSECHANCE,	JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "aitype",			CCP_AITYPE,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "split",			CCP_SPLIT,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "splitchance",	CCP_SPLITCHANCE,	JSPROP_ENUMANDPERM, nullptr, nullptr },
@@ -535,7 +537,19 @@ inline JSPropertySpec CItemProps[] =
 	{ "ammoFXHue",		CIP_AMMOFXHUE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "ammoFXRender",	CIP_AMMOFXRENDER,	JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "speed",			CIP_SPEED,			JSPROP_ENUMANDPERM, nullptr, nullptr },
+
+	{ "healthLeech",	CIP_HEALTHLEECH,	JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "staminaLeech",	CIP_STAMINALEECH,	JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "manaLeech",		CIP_MANALEECH,		JSPROP_ENUMANDPERM, nullptr, nullptr },
+
+	{ "hitChance",		CIP_HITCHANCE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "defenseChance",	CIP_DEFENSECHANCE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
+
+	{ "healthBonus",	CIP_HEALTHBONUS,	JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "staminaBonus",	CIP_STAMINABONUS,	JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "manaBonus",		CIP_MANABONUS,		JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "artifactRarity",	CIP_ARTIFACTRARITY,	JSPROP_ENUMANDPERM, nullptr, nullptr },
+
 	{ "multi",			CIP_MULTI,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "maxRange",		CIP_MAXRANGE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "baseRange",		CIP_BASERANGE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
@@ -676,8 +690,8 @@ inline JSPropertySpec CSkillsProps[] =
 	{ "bushido",			BUSHIDO,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "ninjitsu",			NINJITSU,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "spellweaving",		SPELLWEAVING,		JSPROP_ENUMANDPERM, nullptr, nullptr },
-	{ "imbuing",			IMBUING,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "mysticism",			MYSTICISM,			JSPROP_ENUMANDPERM, nullptr, nullptr },
+	{ "imbuing",			IMBUING,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "throwing",			THROWING,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "allskills",			ALLSKILLS,			JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ nullptr,				static_cast(0),			static_cast(0), nullptr, nullptr }
diff --git a/source/cBaseObject.cpp b/source/cBaseObject.cpp
index 506c247ac..8ffa0e864 100644
--- a/source/cBaseObject.cpp
+++ b/source/cBaseObject.cpp
@@ -94,6 +94,16 @@ const SI16			DEFBASE_KILLS		= 0;
 const UI16			DEFBASE_RESIST 		= 0;
 const bool			DEFBASE_NAMEREQUESTACTIVE = 0;
 const ExpansionRuleset	DEFBASE_ORIGIN	= ER_UO;
+const SI16			DEFBASE_HEALTHLEECH = 0;
+const SI16			DEFBASE_STAMINALEECH = 0;
+const SI16			DEFBASE_MANALEECH = 0;
+
+const SI16			DEFBASE_HITCHANCE = 0;
+const SI16			DEFBASE_DEFENSECHANCE = 0;
+
+const SI16			DEFBASE_HEALTHBONUS = 0;
+const SI16			DEFBASE_STAMINABONOS = 0;
+const SI16			DEFBASE_MANABONUS = 0;
 
 //o------------------------------------------------------------------------------------------------o
 //|	Function	-	CBaseObject constructor
@@ -110,7 +120,9 @@ loDamage( DEFBASE_LODAMAGE ), weight( DEFBASE_WEIGHT ),
 mana( DEFBASE_MANA ), stamina( DEFBASE_STAMINA ), scriptTrig( DEFBASE_SCPTRIG ), st2( DEFBASE_STR2 ), dx2( DEFBASE_DEX2 ),
 in2( DEFBASE_INT2 ), FilePosition( DEFBASE_FP ),
 poisoned( DEFBASE_POISONED ), carve( DEFBASE_CARVE ), oldLocX( 0 ), oldLocY( 0 ), oldLocZ( 0 ), oldTargLocX( 0 ), oldTargLocY( 0 ),
-fame( DEFBASE_FAME ), karma( DEFBASE_KARMA ), kills( DEFBASE_KILLS ), subRegion( DEFBASE_SUBREGION ), nameRequestActive( DEFBASE_NAMEREQUESTACTIVE ), origin( DEFBASE_ORIGIN )
+fame( DEFBASE_FAME ), karma( DEFBASE_KARMA ), kills( DEFBASE_KILLS ), subRegion( DEFBASE_SUBREGION ), nameRequestActive( DEFBASE_NAMEREQUESTACTIVE ), origin( DEFBASE_ORIGIN ),
+healthBonus( DEFBASE_HEALTHBONUS ),staminaBonus( DEFBASE_STAMINABONOS ), manaBonus( DEFBASE_MANABONUS ), hitChance( DEFBASE_HITCHANCE ), defenseChance( DEFBASE_DEFENSECHANCE ),
+healthLeech( DEFBASE_HEALTHLEECH ), staminaLeech( DEFBASE_STAMINALEECH ), manaLeech( DEFBASE_MANALEECH )
 {
 	multis = nullptr;
 	tempMulti = INVALIDSERIAL;
@@ -1036,6 +1048,46 @@ void CBaseObject::IncHP( SI16 amtToChange )
 	SetHP( hitpoints + amtToChange );
 }
 
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::GetHitChance()
+//|					CBaseObject::SetHitChance()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the Defense Chance of the Item(s) Equiped or Character
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetHitChance( void ) const
+{
+	return hitChance;
+}
+void CBaseObject::SetHitChance( SI16 newValue )
+{
+	hitChance = newValue;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::GetDefenseChance()
+//|					CBaseObject::SetDefenseChance()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the Defense Chance of the Item(s) Equiped or Character
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetDefenseChance( void ) const
+{
+	return defenseChance;
+}
+void CBaseObject::SetDefenseChance( SI16 newValue )
+{
+	defenseChance = newValue;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
 //o------------------------------------------------------------------------------------------------o
 //|	Function	-	CBaseObject::GetDir()
 //|					CBaseObject::SetDir()
@@ -1631,6 +1683,126 @@ void CBaseObject::SetIntelligence2( SI16 nVal )
 	}
 }
 
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::GetHealthLeech()
+//|					CBaseObject::SetHealthLeech()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the Health Leech
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetHealthLeech( void ) const
+{
+	return healthLeech;
+}
+void CBaseObject::SetHealthLeech( SI16 nVal )
+{
+	healthLeech = nVal;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
+//|	Function	-	CBaseObject::GetHealthBonus()
+//|					CBaseObject::SetHealthBonus()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the health max var associated with the object. For chars, it's the
+//|					bonuses (via armour and such)
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetHealthBonus( void ) const
+{
+	return healthBonus;
+}
+void CBaseObject::SetHealthBonus( SI16 nVal )
+{
+	healthBonus = nVal;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::GetStaminaLeech()
+//|					CBaseObject::SetStaminaLeech()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the Stamina Leech
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetStaminaLeech( void ) const
+{
+	return staminaLeech;
+}
+void CBaseObject::SetStaminaLeech( SI16 nVal )
+{
+	staminaLeech = nVal;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
+//|	Function	-	CBaseObject::GetStaminaBonus()
+//|					CBaseObject::SetStaminaBonus()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the stamina max var associated with the object. For chars, it's the
+//|					bonuses (via armour and such)
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetStaminaBonus( void ) const
+{
+	return staminaBonus;
+}
+void CBaseObject::SetStaminaBonus( SI16 nVal )
+{
+	staminaBonus = nVal;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::GetManaLeech()
+//|					CBaseObject::SetManaLeech()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the Mana Leech
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetManaLeech( void ) const
+{
+	return manaLeech;
+}
+void CBaseObject::SetManaLeech( SI16 nVal )
+{
+	manaLeech = nVal;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
+//|	Function	-	CBaseObject::GetManaBonus()
+//|					CBaseObject::SetManaBonus()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Gets/Sets the Mana max var associated with the object. For chars, it's the
+//|					bonuses (via armour and such)
+//o------------------------------------------------------------------------------------------------o
+SI16 CBaseObject::GetManaBonus( void ) const
+{
+	return manaBonus;
+}
+void CBaseObject::SetManaBonus( SI16 nVal )
+{
+	manaBonus = nVal;
+
+	if( CanBeObjType( OT_ITEM ))
+	{
+		( static_cast( this ))->UpdateRegion();
+	}
+}
+
 //o------------------------------------------------------------------------------------------------o
 //|	Function	-	CBaseObject::IncStrength()
 //o------------------------------------------------------------------------------------------------o
@@ -1661,6 +1833,55 @@ void CBaseObject::IncIntelligence( SI16 toInc )
 	SetIntelligence( intelligence + toInc );
 }
 
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::IncHealthLeech()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Increments the object's Health Leech Points value
+//o------------------------------------------------------------------------------------------------o
+void CBaseObject::IncHealthLeech( SI16 toInc )
+{
+	SetHealthLeech( healthLeech + toInc );
+}
+
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::IncStaminaLeech()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Increments the object's Stamina Leech Points value
+//o------------------------------------------------------------------------------------------------o
+void CBaseObject::IncStaminaLeech( SI16 toInc )
+{
+	SetStaminaLeech( staminaLeech + toInc );
+}
+
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::IncManaLeech()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Increments the object's Mana Leech Points value
+//o------------------------------------------------------------------------------------------------o
+void CBaseObject::IncManaLeech( SI16 toInc )
+{
+	SetManaLeech( manaLeech + toInc );
+}
+
+//|	Function	-	CBaseObject::IncHitChance()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Increments the object's Hit Chance value
+//o------------------------------------------------------------------------------------------------o
+void CBaseObject::IncHitChance( SI16 toInc )
+{
+	SetHitChance( hitChance + toInc );
+}
+
+//o------------------------------------------------------------------------------------------------o
+//|	Function	-	CBaseObject::IncDefenseChance()
+//o------------------------------------------------------------------------------------------------o
+//|	Purpose		-	Increments the object's Hit Chance value
+//o------------------------------------------------------------------------------------------------o
+void CBaseObject::IncDefenseChance( SI16 toInc )
+{
+	SetDefenseChance( defenseChance + toInc );
+}
+
 //o------------------------------------------------------------------------------------------------o
 //|	Function	-	CBaseObject::DumpFooter()
 //o------------------------------------------------------------------------------------------------o
diff --git a/source/cBaseObject.h b/source/cBaseObject.h
index 638e826f7..d19902995 100644
--- a/source/cBaseObject.h
+++ b/source/cBaseObject.h
@@ -69,16 +69,24 @@ class CBaseObject
 	SI16				dexterity;
 	SI16				intelligence;
 	SI16				hitpoints;
+	SI16				hitChance;
+	SI16				defenseChance;
 	VisibleTypes		visible;
 	SI16				hiDamage;
 	SI16				loDamage;
 	SI32				weight;
 	SI16				mana;
 	SI16				stamina;
+	SI16				healthLeech;
+	SI16				staminaLeech;
+	SI16				manaLeech;
 	UI16				scriptTrig;
 	SI16				st2;
 	SI16				dx2;
 	SI16				in2;
+	SI16				healthBonus;
+	SI16				staminaBonus;
+	SI16				manaBonus;
 	mutable SI32		FilePosition;
 	SERIAL				tempMulti;
 	std::string			name;
@@ -223,6 +231,12 @@ class CBaseObject
 	virtual void			SetHP( SI16 newValue );
 	void					IncHP( SI16 amtToChange );
 
+	virtual SI16			GetHitChance( void ) const;
+	virtual void			SetHitChance( SI16 newValue );
+
+	virtual SI16			GetDefenseChance( void ) const;
+	virtual void			SetDefenseChance( SI16 newValue );
+
 	void					SetDir( UI08 newDir, bool sendUpdate = true );
 	UI08					GetDir( void ) const;
 
@@ -256,6 +270,32 @@ class CBaseObject
 	void					IncDexterity( SI16 toInc = 1 );
 	void					IncIntelligence( SI16 toInc = 1 );
 
+
+	SI16					GetHealthLeech( void ) const;
+	virtual void			SetHealthLeech( SI16 nVal );
+
+	SI16					GetStaminaLeech( void ) const;
+	virtual void			SetStaminaLeech( SI16 nVal );
+
+	SI16					GetManaLeech( void ) const;
+	virtual void			SetManaLeech( SI16 nVal );
+
+	void					IncHealthLeech( SI16 toInc = 1 );
+	void					IncStaminaLeech( SI16 toInc = 1 );
+	void					IncManaLeech( SI16 toInc = 1 );
+
+	void					IncHitChance( SI16 toInc = 1 );
+	void					IncDefenseChance( SI16 toInc = 1 );
+
+	SI16					GetHealthBonus( void ) const;
+	virtual void			SetHealthBonus( SI16 nVal );
+
+	SI16					GetStaminaBonus( void ) const;
+	virtual void			SetStaminaBonus( SI16 nVal );
+
+	SI16					GetManaBonus( void ) const;
+	virtual void			SetManaBonus( SI16 nVal );
+
 	virtual void			PostLoadProcessing( void );
 	virtual bool			LoadRemnants( void ) = 0;
 
diff --git a/source/cChar.cpp b/source/cChar.cpp
index 7aa03f6fa..acd8011f4 100644
--- a/source/cChar.cpp
+++ b/source/cChar.cpp
@@ -2411,6 +2411,8 @@ void CChar::CopyData( CChar *target )
 	target->SetNextAct( nextAct );
 	target->SetSquelched( GetSquelched() );
 	target->SetMeditating( IsMeditating() );
+	target->SetHitChance( GetHitChance() );
+	target->SetDefenseChance( GetDefenseChance() );
 	target->SetStealth( stealth );
 	target->SetRunning( running );
 	target->SetRace( GetRace() );
@@ -2920,6 +2922,17 @@ bool CChar::WearItem( CItem *toWear )
 			IncDexterity2( itemLayers[tLayer]->GetDexterity2() );
 			IncIntelligence2( itemLayers[tLayer]->GetIntelligence2() );
 
+			IncHealthLeech( itemLayers[tLayer]->GetHealthLeech() );
+			IncStaminaLeech( itemLayers[tLayer]->GetStaminaLeech() );
+			IncManaLeech( itemLayers[tLayer]->GetManaLeech() );
+
+      IncHitChance( itemLayers[tLayer]->GetHitChance() );
+			IncDefenseChance( itemLayers[tLayer]->GetDefenseChance() );
+
+			IncHealthBonus( itemLayers[tLayer]->GetHealthBonus() );
+			IncStaminaBonus( itemLayers[tLayer]->GetStaminaBonus() );
+			IncManaBonus( itemLayers[tLayer]->GetManaBonus() );
+
 			if( toWear->IsPostLoaded() )
 			{
 				if( itemLayers[tLayer]->GetPoisoned() )
@@ -2979,6 +2992,18 @@ bool CChar::TakeOffItem( ItemLayers Layer )
 		IncStrength2( -itemLayers[Layer]->GetStrength2() );
 		IncDexterity2( -itemLayers[Layer]->GetDexterity2() );
 		IncIntelligence2( -itemLayers[Layer]->GetIntelligence2() );
+
+		IncHealthLeech( -itemLayers[Layer]->GetHealthLeech() );
+		IncStaminaLeech( -itemLayers[Layer]->GetStaminaLeech() );
+		IncManaLeech( -itemLayers[Layer]->GetManaLeech() );
+
+		IncHitChance( -itemLayers[Layer]->GetHitChance() );
+		IncDefenseChance( -itemLayers[Layer]->GetDefenseChance() );
+
+		IncHealthBonus( -itemLayers[Layer]->GetHealthBonus() );
+		IncStaminaBonus( -itemLayers[Layer]->GetStaminaBonus() );
+		IncManaBonus( -itemLayers[Layer]->GetManaBonus() );
+
 		if( itemLayers[Layer]->GetPoisoned() )
 		{
 			if( itemLayers[Layer]->GetPoisoned() > GetPoisoned() )
@@ -3134,6 +3159,8 @@ bool CChar::DumpBody( std::ostream &outStream ) const
 	//-------------------------------------------------------------------------------------------
 	outStream << "CanRun=" + std::to_string((( CanRun() && IsNpc() ) ? 1 : 0 )) + newLine;
 	outStream << "CanAttack=" + std::to_string(( GetCanAttack() ? 1 : 0 )) + newLine;
+	outStream << "HitChance=" + std::to_string( GetHitChance() ) + newLine;
+	outStream << "DefChance=" + std::to_string( GetDefenseChance() ) + newLine;
 	outStream << "AllMove=" + std::to_string(( AllMove() ? 1 : 0 )) + newLine;
 	outStream << "IsNpc=" + std::to_string(( IsNpc() ? 1 : 0 )) + newLine;
 	outStream << "IsShop=" + std::to_string(( IsShop() ? 1 : 0 )) + newLine;
@@ -3787,7 +3814,7 @@ UI16 CChar::GetMaxHP( void )
 		oldRace			= GetRace();
 
 	}
-	return maxHP;
+	return maxHP + GetHealthBonus();
 }
 void CChar::SetMaxHP( UI16 newmaxhp, UI16 newoldstr, RACEID newoldrace )
 {
@@ -3830,7 +3857,7 @@ void CChar::SetFixedMaxHP( SI16 newmaxhp )
 SI16 CChar::GetMaxMana( void )
 {
 	if(( maxMana_oldint != GetIntelligence() || oldRace != GetRace() ) && !GetMaxManaFixed() )
-		//if int/race changed since last calculation, recalculate maxHp
+		//if int/race changed since last calculation, recalculate maxMana
 	{
 		CRace *pRace = Races->Race( GetRace() );
 
@@ -3847,7 +3874,7 @@ SI16 CChar::GetMaxMana( void )
 		oldRace			= GetRace();
 
 	}
-	return maxMana;
+	return maxMana + GetManaBonus();
 }
 void CChar::SetMaxMana( SI16 newmaxmana, UI16 newoldint, RACEID newoldrace )
 {
@@ -3889,7 +3916,7 @@ void CChar::SetFixedMaxMana( SI16 newmaxmana )
 //o------------------------------------------------------------------------------------------------o
 SI16 CChar::GetMaxStam( void )
 {
-	// If dex/race changed since last calculation, recalculate maxHp
+	// If dex/race changed since last calculation, recalculate maxStam
 	if(( maxStam_olddex != GetDexterity() || oldRace != GetRace() ) && !GetMaxStamFixed() )
 	{
 		CRace *pRace = Races->Race( GetRace() );
@@ -3907,7 +3934,7 @@ SI16 CChar::GetMaxStam( void )
 		oldRace			= GetRace();
 
 	}
-	return maxStam;
+	return maxStam + GetStaminaBonus();
 }
 void CChar::SetMaxStam( SI16 newmaxstam, UI16 newolddex, RACEID newoldrace )
 {
@@ -4372,6 +4399,11 @@ bool CChar::HandleLine( std::string &UTag, std::string &data )
 					SetDead(( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 ));
 					rValue = true;
 				}
+				else if( UTag == "DEFCHANCE" )
+				{
+					SetDefenseChance( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
 				break;
 			case 'E':
 				if( UTag == "EMOTION" )
@@ -4460,7 +4492,12 @@ bool CChar::HandleLine( std::string &UTag, std::string &data )
 				}
 				break;
 			case 'H':
-				if( UTag == "HUNGER" )
+				if( UTag == "HITCHANCE" )
+				{
+					SetHitChance( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
+				else if( UTag == "HUNGER" )
 				{
 					SetHunger( static_cast( std::stoi( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
 					rValue = true;
@@ -5593,6 +5630,74 @@ void CChar::SetIntelligence2( SI16 nVal )
 	UpdateRegion();
 }
 
+//o------------------------------------------------------------------------------------------------o
+//| Function	-	CChar::SetHealthBonus()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose		-	Sets bonus Hits stat for character
+//o------------------------------------------------------------------------------------------------o
+void CChar::SetHealthBonus( SI16 nVal )
+{
+	CBaseObject::SetHealthBonus( nVal );
+	Dirty( UT_HITPOINTS );
+	UpdateRegion();
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function	-	CChar::SetStaminaBonus()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose		-	Sets bonus Stam stat for character
+//o------------------------------------------------------------------------------------------------o
+void CChar::SetStaminaBonus( SI16 nVal )
+{
+	CBaseObject::SetStaminaBonus( nVal );
+	Dirty( UT_STAMINA );
+	UpdateRegion();
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function	-	CChar::SetManaBonus()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose		-	Sets bonus Mana stat for character
+//o------------------------------------------------------------------------------------------------o
+void CChar::SetManaBonus( SI16 nVal )
+{
+	CBaseObject::SetManaBonus( nVal );
+	Dirty( UT_MANA );
+	UpdateRegion();
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function	-	CChar::IncHealthBonus()
+//o------------------------------------------------------------------------------------------------o
+//| Purpose		-	Increments GetHealthBonus (modifications) by toAdd
+//o------------------------------------------------------------------------------------------------o
+void CChar::IncHealthBonus( SI16 toAdd )
+{
+	SetHealthBonus( static_cast( GetHealthBonus() + toAdd ));
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function	-	CChar::IncStaminaBonus()
+//| Date		-	26 May 2024
+//o------------------------------------------------------------------------------------------------o
+//| Purpose		-	Increments GetBonusStam (modifications) by toAdd
+//o------------------------------------------------------------------------------------------------o
+void CChar::IncStaminaBonus( SI16 toAdd )
+{
+	SetStaminaBonus( static_cast( GetStaminaBonus() + toAdd ));
+}
+
+//o------------------------------------------------------------------------------------------------o
+//| Function	-	CChar::IncManaBonus()
+//| Date		-	26 May 2024
+//o------------------------------------------------------------------------------------------------o
+//| Purpose		-	Increments GetBonusMana (modifications) by toAdd
+//o------------------------------------------------------------------------------------------------o
+void CChar::IncManaBonus( SI16 toAdd )
+{
+	SetManaBonus( static_cast( GetManaBonus() + toAdd ));
+}
+
 //o------------------------------------------------------------------------------------------------o
 //| Function	-	CChar::IncStamina()
 //o------------------------------------------------------------------------------------------------o
diff --git a/source/cChar.h b/source/cChar.h
index ed4cc90ba..ae387a58c 100644
--- a/source/cChar.h
+++ b/source/cChar.h
@@ -628,6 +628,15 @@ class CChar : public CBaseObject
 	virtual void	SetStrength2( SI16 newValue ) override;
 	virtual void	SetDexterity2( SI16 newValue ) override;
 	virtual void	SetIntelligence2( SI16 newValue ) override;
+
+	virtual void	SetHealthBonus( SI16 newValue ) override;
+	virtual void	SetStaminaBonus( SI16 newValue ) override;
+	virtual void	SetManaBonus( SI16 newValue ) override;
+
+	void			IncHealthBonus( SI16 toAdd = 1 );
+	void			IncStaminaBonus( SI16 toAdd = 1 );
+	void			IncManaBonus( SI16 toAdd = 1 );
+
 	void			IncStamina( SI16 toInc );
 	void			IncMana( SI16 toInc );
 	void			SetMaxLoyalty( UI16 newMaxLoyalty );
diff --git a/source/cItem.cpp b/source/cItem.cpp
index 3a2acd106..d692eaede 100644
--- a/source/cItem.cpp
+++ b/source/cItem.cpp
@@ -1654,13 +1654,20 @@ auto CItem::CopyData( CItem *target ) -> void
 	target->SetRndValueRate( GetRndValueRate() );
 	target->SetSpawn( GetSpawn() );
 	target->SetSpeed( GetSpeed() );
+	target->SetHitChance( GetHitChance() );
+	target->SetDefenseChance( GetDefenseChance() );
+
 	target->SetArtifactRarity( GetArtifactRarity() );
+
 	target->SetSpell( 0, GetSpell( 0 ));
 	target->SetSpell( 1, GetSpell( 1 ));
 	target->SetSpell( 2, GetSpell( 2 ));
 	target->SetStamina( GetStamina() );
 	target->SetStrength( GetStrength() );
 	target->SetStrength2( GetStrength2() );
+	target->SetHealthLeech( GetHealthLeech() );
+	target->SetStaminaLeech( GetStaminaLeech() );
+	target->SetManaLeech( GetManaLeech() );
 	target->SetTitle( GetTitle() );
 	target->SetType( GetType() );
 	target->SetBuyValue( GetBuyValue() );
@@ -1671,6 +1678,9 @@ auto CItem::CopyData( CItem *target ) -> void
 	target->SetWeightMax( GetWeightMax() );
 	target->SetBaseWeight( GetBaseWeight() );
 	target->SetMaxItems( GetMaxItems() );
+	target->SetHealthBonus( GetHealthBonus() );
+	target->SetStaminaBonus( GetStaminaBonus() );
+	target->SetManaBonus( GetManaBonus() );
 	//target->SetWipeable( IsWipeable() );
 	target->SetPriv( GetPriv() );
 	target->SetBaseRange( GetBaseRange() );
@@ -1753,10 +1763,14 @@ bool CItem::DumpBody( std::ostream &outStream ) const
 	outStream << "BaseWeight=" + std::to_string( GetBaseWeight() ) + newLine;
 	outStream << "MaxItems=" + std::to_string( GetMaxItems() ) + newLine;
 	outStream << "MaxHP=" + std::to_string( GetMaxHP() ) + newLine;
+	outStream << "HitChance=" + std::to_string( GetHitChance() ) + newLine;
+	outStream << "DefenseChance=" + std::to_string( GetDefenseChance() ) + newLine;
 	outStream << "Speed=" + std::to_string( GetSpeed() ) + newLine;
+	outStream << "BonusStats=" + std::to_string( GetHealthBonus() ) + "," + std::to_string( GetStaminaBonus() ) + "," + std::to_string( GetManaBonus() ) + newLine;
 	outStream << "ArtifactRarity=" + std::to_string( GetArtifactRarity() ) + newLine;
 	outStream << "Movable=" + std::to_string( GetMovable() ) + newLine;
 	outStream << "Priv=" + std::to_string( GetPriv() ) + newLine;
+	outStream << "LeechStats=" + std::to_string( GetHealthLeech() ) + "," + std::to_string( GetStaminaLeech() ) + "," + std::to_string( GetManaLeech() ) + newLine;
 	outStream << "Value=" + std::to_string( GetBuyValue() ) + "," + std::to_string( GetSellValue() ) + "," + std::to_string( GetVendorPrice() ) + newLine;
 	outStream << "Restock=" + std::to_string( GetRestock() ) + newLine;
 	outStream << "AC=" + std::to_string( GetArmourClass() ) + newLine;
@@ -1843,6 +1857,13 @@ bool CItem::HandleLine( std::string &UTag, std::string &data )
 					bools = static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ));
 					rValue = true;
 				}
+				else if( UTag == "BONUSSTATS" )
+				{
+				    SetHealthBonus( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nullptr, 0 )));
+				    SetStaminaBonus( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 )));
+				    SetManaBonus( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[2], "//" )), nullptr, 0 )));
+				    rValue = true;
+				}
 				break;
 			case 'C':
 				if( UTag == "CONT" )
@@ -1882,6 +1903,11 @@ bool CItem::HandleLine( std::string &UTag, std::string &data )
 					SetDye( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 );
 					rValue = true;
 				}
+				else if( UTag == "DEFENSECHANCE" )
+				{
+					SetDefenseChance( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
 				break;
 			case 'E':
 				if( UTag == "ENTRYMADEFROM" )
@@ -1928,6 +1954,11 @@ bool CItem::HandleLine( std::string &UTag, std::string &data )
 					SetWeatherDamage( HEAT, static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 );
 					rValue = true;
 				}
+				else if( UTag == "HITCHANCE" )
+				{
+					SetHitChance( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
 				break;
 			case 'L':
 				if( UTag == "LAYER" )
@@ -1945,6 +1976,13 @@ bool CItem::HandleLine( std::string &UTag, std::string &data )
 					SetWeatherDamage( LIGHTNING, static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 );
 					rValue = true;
 				}
+				else if( UTag == "LEECHSTATS" )
+				{
+					SetHealthLeech( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nullptr, 0 )));
+				    SetStaminaLeech( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 )));
+				    SetManaLeech( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[2], "//" )), nullptr, 0 )));
+				    rValue = true;
+				}
 				break;
 			case 'M':
 				if( UTag == "MAXITEMS" )
diff --git a/source/combat.cpp b/source/combat.cpp
index 8817d7b3d..4d8bb1842 100644
--- a/source/combat.cpp
+++ b/source/combat.cpp
@@ -801,6 +801,10 @@ UI08 CHandleCombat::GetWeaponType( CItem *i )
 		case 0x48B3: //gargish axe - SA
 			return TWOHND_AXES;
 		// Default Maces
+		case 0x0DF2: // Wand
+		case 0x0DF3: // Wand
+		case 0x0DF4: // Wand
+		case 0x0DF5: // Wand
 		case 0x13E3: //smith's hammer
 		case 0x13E4: //smith's hammer
 		case 0x13B3: //club
@@ -2645,7 +2649,7 @@ SI16 CHandleCombat::ApplyDefenseModifiers( WeatherType damageType, CChar *mChar,
 
 	if( getDef > 0 )
 	{
-		damage -= static_cast(( static_cast( getDef ) * static_cast( attSkill )) / 750 );
+		damage -= static_cast( getDef );
 	}
 
 	return static_cast( std::round( damage ));
@@ -2877,18 +2881,24 @@ bool CHandleCombat::HandleCombat( CSocket *mSock, CChar& mChar, CChar *ourTarg )
 				R32 attHitChanceBonus = 0;
 				R32 defDefenseChanceBonus = 0;
 				R32 maxAttHitChanceBonus = 45;
+
 				if( cwmWorldState->ServerData()->ExpansionCombatHitChance() >= ER_SA && mChar.GetBodyType() == BT_GARGOYLE )
 				{
 					// If attacker is a Gargoyle player, and ExpansionCombatHitChance is ER_SA or higher, use 50 as hitchance bonus cap instead of 45
 					maxAttHitChanceBonus = 50;
 				}
 				
-				// Fetch bonuses to hitChance/defenseChance from AoS item properties, when implemented
-				//attHitChanceBonus = GetAttackerHitChanceBonus();
-				//defDefenseChanceBonus = GetDefenderDefenseChanceBonus();
+				// Fetch bonuses to hitChance/defenseChance from AoS item properties or characters
+				attHitChanceBonus = mChar.GetHitChance();
+				defDefenseChanceBonus = mChar.GetDefenseChance();
+
+				// Clamp to ensure valid bonus ranges (e.g., no multiplier below 1)
+				R32 effectiveAttHitChanceBonus = std::max(-99.0f, std::min( attHitChanceBonus, maxAttHitChanceBonus )); // Cap at -99 and maxAttHitChanceBonus
+				R32 effectiveDefDefenseChanceBonus = std::max(-99.0f, std::min( defDefenseChanceBonus, 45.0f )); // Cap at -99 and 45
 
-				R32 attackerHitChance = ( static_cast( attackSkill / 10 ) + 20 ) * ( 100 + std::min( attHitChanceBonus, static_cast( maxAttHitChanceBonus )));
-				R32 defenderDefenseChance = ( static_cast( defendSkill / 10 ) + 20 ) * ( 100 + std::min( defDefenseChanceBonus, static_cast( 45 )));
+				// Calculate the attacker's hit chance and defender's defense chance
+				R32 attackerHitChance = ( static_cast( attackSkill / 10 ) + 20 ) * ( 100 + effectiveAttHitChanceBonus );
+				R32 defenderDefenseChance = ( static_cast( defendSkill / 10 ) + 20 ) * ( 100 + effectiveDefDefenseChanceBonus );
 				hitChance = ( attackerHitChance / ( defenderDefenseChance * 2 )) * 100;
 
 				// Always leave at least 2% chance to hit
diff --git a/source/enums.h b/source/enums.h
index 1383bbd10..16db01f95 100644
--- a/source/enums.h
+++ b/source/enums.h
@@ -373,8 +373,8 @@ enum Skills
     BUSHIDO,
     NINJITSU,
     SPELLWEAVING,
-    IMBUING,
     MYSTICISM,
+    IMBUING,
     THROWING,
 
     ALLSKILLS, // #skills+1
diff --git a/source/items.cpp b/source/items.cpp
index a95e80f70..9bb1e737e 100644
--- a/source/items.cpp
+++ b/source/items.cpp
@@ -139,7 +139,13 @@ auto ApplyItemSection( CItem *applyTo, CScriptSection *toApply, std::string sect
 				}
 				break;
 			case DFNTAG_AC:				applyTo->SetArmourClass( static_cast( ndata ));	break;
+			case DFNTAG_HEALTHLEECH:	applyTo->SetHealthLeech( static_cast( ndata ));		break;
+			case DFNTAG_STAMINALEECH:	applyTo->SetStaminaLeech( static_cast( ndata ));		break;
+			case DFNTAG_MANALEECH:		applyTo->SetManaLeech( static_cast( ndata ));		break;
 			case DFNTAG_BASERANGE:		applyTo->SetBaseRange( static_cast( ndata ));		break;
+			case DFNTAG_HEALTHBONUS:		applyTo->SetHealthBonus( static_cast( ndata ));		break;
+			case DFNTAG_STAMINABONUS:		applyTo->SetStaminaBonus( static_cast( ndata ));		break;
+			case DFNTAG_MANABONUS:		applyTo->SetManaBonus( static_cast( ndata ));		break;
 			case DFNTAG_CREATOR:		applyTo->SetCreator( ndata );							break;
 			case DFNTAG_COLOUR:			applyTo->SetColour( static_cast( ndata ));		break;
 			case DFNTAG_COLOURLIST:		applyTo->SetColour( AddRandomColor( cdata ));			break;
@@ -230,6 +236,7 @@ auto ApplyItemSection( CItem *applyTo, CScriptSection *toApply, std::string sect
 			case DFNTAG_DISPELLABLE:	applyTo->SetDispellable( true );			break;
 			case DFNTAG_DISABLED:		applyTo->SetDisabled( ndata != 0 );			break;
 			case DFNTAG_DOORFLAG:		break;
+			case DFNTAG_DEFENSECHANCE:	applyTo->SetDefenseChance( static_cast( ndata ));		break;
 			case DFNTAG_GOOD:			applyTo->SetGood( static_cast( ndata ));			break;
 			case DFNTAG_GLOW:			applyTo->SetGlow( ndata );								break;
 			case DFNTAG_GLOWBC:			applyTo->SetGlowColour( static_cast( ndata ));	break;
@@ -339,6 +346,7 @@ auto ApplyItemSection( CItem *applyTo, CScriptSection *toApply, std::string sect
 				}
 				break;
 			case DFNTAG_HIDAMAGE:		applyTo->SetHiDamage( static_cast( ndata ));		break;
+			case DFNTAG_HITCHANCE:	applyTo->SetHitChance( static_cast( ndata ));		break;
 			case DFNTAG_HEAT:			applyTo->SetWeatherDamage( HEAT, ndata != 0 );			break;
 			case DFNTAG_ID:				// applyTo->SetId( static_cast( ndata ));				break;
 				if( ssecs.size() == 1 )
diff --git a/source/npcs.cpp b/source/npcs.cpp
index 36005b420..a749b95aa 100644
--- a/source/npcs.cpp
+++ b/source/npcs.cpp
@@ -1541,6 +1541,8 @@ auto CCharStuff::ApplyNpcSection( CChar *applyTo, CScriptSection *NpcCreation, s
 			case DFNTAG_NAMELIST:			SetRandomName( applyTo, cdata );		break;
 			case DFNTAG_NECROMANCY:			skillToSet = NECROMANCY;				break;
 			case DFNTAG_NINJITSU:			skillToSet = NINJITSU;					break;
+			case DFNTAG_HITCHANCE:			applyTo->SetHitChance( static_cast( ndata ));		break;
+			case DFNTAG_DEFENSECHANCE:		applyTo->SetDefenseChance( static_cast( ndata ));		break;
 			case DFNTAG_NPCWANDER:
 				if( !isGate )
 				{
diff --git a/source/ssection.cpp b/source/ssection.cpp
index fae41e7a1..2c1285d37 100644
--- a/source/ssection.cpp
+++ b/source/ssection.cpp
@@ -67,6 +67,7 @@ const UI08 dfnDataTypes[DFNTAG_COUNTOFTAGS] =
 	DFN_NUMERIC,		//	DFNTAG_DOORFLAG,
 	DFN_NUMERIC,		//	DFNTAG_DYE,
 	DFN_NUMERIC,		//	DFNTAG_DYEBEARD,
+	DFN_NUMERIC,		//	DFNTAG_DEFENSECHANCE,
 	DFN_NUMERIC,		//	DFNTAG_DYEHAIR,
 	DFN_STRING,			//	DFNTAG_ELEMENTRESIST,
 	DFN_STRING,			//	DFNTAG_ERBONUS,
@@ -112,10 +113,12 @@ const UI08 dfnDataTypes[DFNTAG_COUNTOFTAGS] =
 	DFN_NUMERIC,		//	DFNTAG_HEAT,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_HERDING,
 	DFN_NUMERIC,		//	DFNTAG_HIDAMAGE,
+	DFN_NUMERIC,		//	DFNTAG_HITCHANCE,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_HIDING,
 	DFN_NODATA,			//	DFNTAG_HIRELING,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_HP,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_HPMAX,
+	DFN_NUMERIC,		//  DFNTAG_HEALTHLEECH,
 	DFN_UPPERSTRING,	//	DFNTAG_ID,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_IMBUING,
 	DFN_NUMERIC,		//	DFNTAG_INTADD,
@@ -141,6 +144,7 @@ const UI08 dfnDataTypes[DFNTAG_COUNTOFTAGS] =
 	DFN_DOUBLENUMERIC,	//	DFNTAG_MAGICRESISTANCE,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_MANA,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_MANAMAX,
+	DFN_NUMERIC,		//  DFNTAG_MANALEECH,
 	DFN_NUMERIC,		//	DFNTAG_MAXHP,
 	DFN_NUMERIC,		//	DFNTAG_MAXITEMS,
 	DFN_NUMERIC,		//	DFNTAG_MAXLOYALTY,
@@ -216,6 +220,9 @@ const UI08 dfnDataTypes[DFNTAG_COUNTOFTAGS] =
 	DFN_STRING,			//	DFNTAG_SPAWNOBJ,
 	DFN_STRING,			//	DFNTAG_SPAWNOBJLIST,
 	DFN_NUMERIC,		//	DFNTAG_SPD,
+	DFN_NUMERIC,		//  DFNTAG_HEALTHBONUS,
+	DFN_NUMERIC,		//  DFNTAG_STAMINABONUS,
+	DFN_NUMERIC,		//  DFNTAG_MANABONUS,
 	DFN_STRING,			//	DFNTAG_SPELLS,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_SPELLWEAVING,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_SPIRITSPEAK,
@@ -223,6 +230,7 @@ const UI08 dfnDataTypes[DFNTAG_COUNTOFTAGS] =
 	DFN_NUMERIC,		//	DFNTAG_SPLITCHANCE,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_STAMINA,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_STAMINAMAX,
+	DFN_NUMERIC,		//  DFNTAG_STAMINALEECH,
 	DFN_DOUBLENUMERIC,	//	DFNTAG_STRENGTH,
 	DFN_NUMERIC,		//	DFNTAG_STRADD,
 	DFN_NUMERIC,		//	DFNTAG_STEALABLE,
@@ -285,6 +293,9 @@ const std::map strToDFNTag
 	{"BLACKSMITHING"s,		DFNTAG_BLACKSMITHING},
 	{"BOWCRAFT"s,			DFNTAG_BOWCRAFT},
 	{"BUSHIDO"s,			DFNTAG_BUSHIDO},
+	{"HEALTHBONUS"s,		DFNTAG_HEALTHBONUS},
+	{"STAMINABONUS"s,		DFNTAG_STAMINABONUS},
+	{"MANABONUS"s,			DFNTAG_MANABONUS},
 	{"CAMPING"s,			DFNTAG_CAMPING},
 	{"CARPENTRY"s,			DFNTAG_CARPENTRY},
 	{"CARTOGRAPHY"s,		DFNTAG_CARTOGRAPHY},
@@ -323,6 +334,7 @@ const std::map strToDFNTag
 	{"DYEABLE"s,			DFNTAG_DYE},
 	{"DYEHAIR"s,			DFNTAG_DYEHAIR},
 	{"DYEBEARD"s,			DFNTAG_DYEBEARD},
+	{"DEFENSECHANCE"s,		DFNTAG_DEFENSECHANCE},
 	{"ELEMENTRESIST"s,		DFNTAG_ELEMENTRESIST},
 	{"ERBONUS"s,			DFNTAG_ERBONUS},	
 	{"EMOTECOLOR"s,			DFNTAG_EMOTECOLOUR},
@@ -368,10 +380,12 @@ const std::map strToDFNTag
 	{"HEAT"s,				DFNTAG_HEAT},
 	{"HERDING"s,			DFNTAG_HERDING},
 	{"HIDAMAGE"s,			DFNTAG_HIDAMAGE},
+	{"HITCHANCE"s,			DFNTAG_HITCHANCE},
 	{"HIDING"s,				DFNTAG_HIDING},
 	{"HIRELING"s,			DFNTAG_HIRELING},
 	{"HP"s,					DFNTAG_HP},
 	{"HPMAX"s,				DFNTAG_HPMAX},
+	{"HEALTHLEECH"s,		DFNTAG_HEALTHLEECH},
 	{"ID"s,					DFNTAG_ID},
 	{"IMBUING"s,			DFNTAG_IMBUING},
 	{"IN"s,					DFNTAG_INTELLIGENCE},
@@ -401,6 +415,7 @@ const std::map strToDFNTag
 	{"MAGICRESISTANCE"s,	DFNTAG_MAGICRESISTANCE},
 	{"MANA"s,				DFNTAG_MANA},
 	{"MANAMAX"s,			DFNTAG_MANAMAX},
+	{"MANALEECH"s,			DFNTAG_MANALEECH},
 	{"MAXHP"s,				DFNTAG_MAXHP},
 	{"MAXITEMS"s,			DFNTAG_MAXITEMS},
 	{"MAXLOYALTY"s,			DFNTAG_MAXLOYALTY},
@@ -485,6 +500,7 @@ const std::map strToDFNTag
 	{"ST"s,					DFNTAG_STRENGTH},
 	{"STAMINA"s,			DFNTAG_STAMINA},
 	{"STAMINAMAX"s,			DFNTAG_STAMINAMAX},
+	{"STAMINALEECH"s,		DFNTAG_STAMINALEECH},
 	{"STR"s,				DFNTAG_STRENGTH},
 	{"STRENGTH"s,			DFNTAG_STRENGTH},
 	{"ST2"s,				DFNTAG_STRADD},
diff --git a/source/ssection.h b/source/ssection.h
index c652b9d3b..dc5ab2f45 100644
--- a/source/ssection.h
+++ b/source/ssection.h
@@ -72,6 +72,7 @@ enum DFNTAGS
 	DFNTAG_DISPELLABLE,
 	DFNTAG_DISABLED,
 	DFNTAG_DOORFLAG,
+	DFNTAG_DEFENSECHANCE,
 	DFNTAG_DYE,
 	DFNTAG_DYEBEARD,
 	DFNTAG_DYEHAIR,
@@ -119,10 +120,12 @@ enum DFNTAGS
 	DFNTAG_HEAT,
 	DFNTAG_HERDING,
 	DFNTAG_HIDAMAGE,
+	DFNTAG_HITCHANCE,
 	DFNTAG_HIDING,
 	DFNTAG_HIRELING,
 	DFNTAG_HP,
 	DFNTAG_HPMAX,
+	DFNTAG_HEALTHLEECH,
 	DFNTAG_ID,
 	DFNTAG_IMBUING,
 	DFNTAG_INTADD,
@@ -148,6 +151,7 @@ enum DFNTAGS
 	DFNTAG_MAGICRESISTANCE,
 	DFNTAG_MANA,
 	DFNTAG_MANAMAX,
+	DFNTAG_MANALEECH,
 	DFNTAG_MAXHP,
 	DFNTAG_MAXITEMS,
 	DFNTAG_MAXLOYALTY,
@@ -223,6 +227,9 @@ enum DFNTAGS
 	DFNTAG_SPAWNOBJ,
 	DFNTAG_SPAWNOBJLIST,
 	DFNTAG_SPD,
+	DFNTAG_HEALTHBONUS,
+	DFNTAG_STAMINABONUS,
+	DFNTAG_MANABONUS,
 	DFNTAG_SPELLS,
 	DFNTAG_SPELLWEAVING,
 	DFNTAG_SPIRITSPEAK,
@@ -230,6 +237,7 @@ enum DFNTAGS
 	DFNTAG_SPLITCHANCE,
 	DFNTAG_STAMINA,
 	DFNTAG_STAMINAMAX,
+	DFNTAG_STAMINALEECH,
 	DFNTAG_STRENGTH,
 	DFNTAG_STRADD,
 	DFNTAG_STEALABLE,