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 @@
Prototype
-function onSpellTargetSelect( pCaster, myTarget, spellID )
+function onSpellTargetSelect( myTarget, pCaster, spellID )
Purpose
@@ -4043,7 +4043,7 @@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,