diff --git a/source/CPacketSend.cpp b/source/CPacketSend.cpp
index e74cc6f15..5f266e059 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 )
 {
@@ -7665,6 +7665,20 @@ 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~
@@ -8057,9 +8071,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
@@ -8139,7 +8153,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
@@ -8156,7 +8170,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 349c05eb2..0e0caac46 100644
--- a/source/Changelog.txt
+++ b/source/Changelog.txt
@@ -1,3 +1,9 @@
+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=#
diff --git a/source/UOXJSPropertyEnums.h b/source/UOXJSPropertyEnums.h
index d785c5137..6bda6ca60 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,
diff --git a/source/UOXJSPropertyFuncs.cpp b/source/UOXJSPropertyFuncs.cpp
index 906a73910..d755e91c7 100644
--- a/source/UOXJSPropertyFuncs.cpp
+++ b/source/UOXJSPropertyFuncs.cpp
@@ -676,10 +676,13 @@ 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_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 );
@@ -1325,10 +1328,13 @@ 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<UI08>( encaps.toInt() ));		break;
+			case CIP_HITCHANCE:	gPriv->SetHitChance( static_cast<SI16>( encaps.toInt() ));	break;
+			case CIP_DEFENSECHANCE:	gPriv->SetDefenseChance( static_cast<SI16>( encaps.toInt() ));	break;
 			case CIP_HEALTHBONUS:		gPriv->SetHealthBonus( static_cast<SI16>( encaps.toInt() ));	break;
 			case CIP_STAMINABONUS:		gPriv->SetStaminaBonus( static_cast<SI16>( encaps.toInt() ));	break;
 			case CIP_MANABONUS:		gPriv->SetManaBonus( static_cast<SI16>( encaps.toInt() ));	break;
 			case CIP_ARTIFACTRARITY:	gPriv->SetArtifactRarity( static_cast<SI16>( encaps.toInt() ));	break;
+
 			case CIP_NAME2:			gPriv->SetName2( encaps.toString() );						break;
 			case CIP_RACE:			gPriv->SetRace( static_cast<RACEID>( encaps.toInt() ));		break;
 			case CIP_MAXHP:			gPriv->SetMaxHP( static_cast<SI16>( encaps.toInt() ));		break;
@@ -2002,6 +2008,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;
@@ -2507,6 +2515,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<SI16>( encaps.toInt() ));	break;
 			case CCP_SPDELAY:		gPriv->SetSpDelay( static_cast<SI08>( encaps.toInt() ));	break;
+			case CCP_HITCHANCE:		gPriv->SetHitChance( static_cast<SI16>( encaps.toInt() ));	break;
+			case CCP_DEFENSECHANCE:	gPriv->SetDefenseChance( static_cast<SI16>( encaps.toInt() ));	break;
 			case CCP_AITYPE:		gPriv->SetNPCAiType( static_cast<SI16>( encaps.toInt() ));	break;
 			case CCP_SPLIT:			gPriv->SetSplit( static_cast<UI08>( encaps.toInt() ));		break;
 			case CCP_SPLITCHANCE:	gPriv->SetSplitChance( static_cast<UI08>( encaps.toInt() ));break;
diff --git a/source/UOXJSPropertySpecs.h b/source/UOXJSPropertySpecs.h
index c81b1f58d..3217cf8e9 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,10 +537,15 @@ inline JSPropertySpec CItemProps[] =
 	{ "ammoFXHue",		CIP_AMMOFXHUE,		JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "ammoFXRender",	CIP_AMMOFXRENDER,	JSPROP_ENUMANDPERM, nullptr, nullptr },
 	{ "speed",			CIP_SPEED,			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 },
diff --git a/source/cBaseObject.cpp b/source/cBaseObject.cpp
index 131f0417b..b236df496 100644
--- a/source/cBaseObject.cpp
+++ b/source/cBaseObject.cpp
@@ -94,6 +94,10 @@ const SI16			DEFBASE_KILLS		= 0;
 const UI16			DEFBASE_RESIST 		= 0;
 const bool			DEFBASE_NAMEREQUESTACTIVE = 0;
 const ExpansionRuleset	DEFBASE_ORIGIN	= ER_UO;
+
+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;
@@ -114,7 +118,7 @@ mana( DEFBASE_MANA ), stamina( DEFBASE_STAMINA ), scriptTrig( DEFBASE_SCPTRIG ),
 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 ),
-healthBonus( DEFBASE_HEALTHBONUS ),staminaBonus( DEFBASE_STAMINABONOS ), manaBonus( DEFBASE_MANABONUS )
+healthBonus( DEFBASE_HEALTHBONUS ),staminaBonus( DEFBASE_STAMINABONOS ), manaBonus( DEFBASE_MANABONUS ), hitChance( DEFBASE_HITCHANCE ), defenseChance( DEFBASE_DEFENSECHANCE )
 {
 	multis = nullptr;
 	tempMulti = INVALIDSERIAL;
@@ -1040,6 +1044,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<CItem *>( 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<CItem *>( this ))->UpdateRegion();
+	}
+}
+
 //o------------------------------------------------------------------------------------------------o
 //|	Function	-	CBaseObject::GetDir()
 //|					CBaseObject::SetDir()
@@ -1728,6 +1772,26 @@ void CBaseObject::IncIntelligence( SI16 toInc )
 	SetIntelligence( intelligence + toInc );
 }
 
+//o------------------------------------------------------------------------------------------------o
+//|	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 b3c7e98f9..d014ce397 100644
--- a/source/cBaseObject.h
+++ b/source/cBaseObject.h
@@ -69,6 +69,8 @@ class CBaseObject
 	SI16				dexterity;
 	SI16				intelligence;
 	SI16				hitpoints;
+	SI16				hitChance;
+	SI16				defenseChance;
 	VisibleTypes		visible;
 	SI16				hiDamage;
 	SI16				loDamage;
@@ -226,6 +228,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;
 
@@ -259,6 +267,10 @@ class CBaseObject
 	void					IncDexterity( SI16 toInc = 1 );
 	void					IncIntelligence( SI16 toInc = 1 );
 
+
+	void					IncHitChance( SI16 toInc = 1 );
+	void					IncDefenseChance( SI16 toInc = 1 );
+
 	SI16					GetHealthBonus( void ) const;
 	virtual void			SetHealthBonus( SI16 nVal );
 
@@ -268,6 +280,7 @@ class CBaseObject
 	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 bcd9e90f6..9e9f82727 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,9 @@ bool CChar::WearItem( CItem *toWear )
 			IncDexterity2( itemLayers[tLayer]->GetDexterity2() );
 			IncIntelligence2( itemLayers[tLayer]->GetIntelligence2() );
 
+			IncHitChance( itemLayers[tLayer]->GetHitChance() );
+			IncDefenseChance( itemLayers[tLayer]->GetDefenseChance() );
+
 			IncHealthBonus( itemLayers[tLayer]->GetHealthBonus() );
 			IncStaminaBonus( itemLayers[tLayer]->GetStaminaBonus() );
 			IncManaBonus( itemLayers[tLayer]->GetManaBonus() );
@@ -2984,6 +2989,9 @@ bool CChar::TakeOffItem( ItemLayers Layer )
 		IncDexterity2( -itemLayers[Layer]->GetDexterity2() );
 		IncIntelligence2( -itemLayers[Layer]->GetIntelligence2() );
 
+		IncHitChance( -itemLayers[Layer]->GetHitChance() );
+		IncDefenseChance( -itemLayers[Layer]->GetDefenseChance() );
+
 		IncHealthBonus( -itemLayers[Layer]->GetHealthBonus() );
 		IncStaminaBonus( -itemLayers[Layer]->GetStaminaBonus() );
 		IncManaBonus( -itemLayers[Layer]->GetManaBonus() );
@@ -3143,6 +3151,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;
@@ -4381,6 +4391,11 @@ bool CChar::HandleLine( std::string &UTag, std::string &data )
 					SetDead(( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 ));
 					rValue = true;
 				}
+				else if( UTag == "DEFCHANCE" )
+				{
+					SetDefenseChance( static_cast<SI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
 				break;
 			case 'E':
 				if( UTag == "EMOTION" )
@@ -4469,7 +4484,12 @@ bool CChar::HandleLine( std::string &UTag, std::string &data )
 				}
 				break;
 			case 'H':
-				if( UTag == "HUNGER" )
+				if( UTag == "HITCHANCE" )
+				{
+					SetHitChance( static_cast<SI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
+				else if( UTag == "HUNGER" )
 				{
 					SetHunger( static_cast<SI16>( std::stoi( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
 					rValue = true;
diff --git a/source/cItem.cpp b/source/cItem.cpp
index ed6672205..c994486c5 100644
--- a/source/cItem.cpp
+++ b/source/cItem.cpp
@@ -1654,7 +1654,11 @@ 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 ));
@@ -1756,6 +1760,8 @@ 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;
@@ -1893,6 +1899,11 @@ bool CItem::HandleLine( std::string &UTag, std::string &data )
 					SetDye( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 );
 					rValue = true;
 				}
+				else if( UTag == "DEFENSECHANCE" )
+				{
+					SetDefenseChance( static_cast<SI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
 				break;
 			case 'E':
 				if( UTag == "ENTRYMADEFROM" )
@@ -1939,6 +1950,11 @@ bool CItem::HandleLine( std::string &UTag, std::string &data )
 					SetWeatherDamage( HEAT, static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 1 );
 					rValue = true;
 				}
+				else if( UTag == "HITCHANCE" )
+				{
+					SetHitChance( static_cast<SI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
+					rValue = true;
+				}
 				break;
 			case 'L':
 				if( UTag == "LAYER" )
diff --git a/source/combat.cpp b/source/combat.cpp
index b38953b35..181c7503c 100644
--- a/source/combat.cpp
+++ b/source/combat.cpp
@@ -2881,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<R32>( attackSkill / 10 ) + 20 ) * ( 100 + std::min( attHitChanceBonus, static_cast<R32>( maxAttHitChanceBonus )));
-				R32 defenderDefenseChance = ( static_cast<R32>( defendSkill / 10 ) + 20 ) * ( 100 + std::min( defDefenseChanceBonus, static_cast<R32>( 45 )));
+				// Calculate the attacker's hit chance and defender's defense chance
+				R32 attackerHitChance = ( static_cast<R32>( attackSkill / 10 ) + 20 ) * ( 100 + effectiveAttHitChanceBonus );
+				R32 defenderDefenseChance = ( static_cast<R32>( defendSkill / 10 ) + 20 ) * ( 100 + effectiveDefDefenseChanceBonus );
 				hitChance = ( attackerHitChance / ( defenderDefenseChance * 2 )) * 100;
 
 				// Always leave at least 2% chance to hit
diff --git a/source/items.cpp b/source/items.cpp
index 101f7338c..04fa7e80d 100644
--- a/source/items.cpp
+++ b/source/items.cpp
@@ -233,6 +233,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<SI16>( ndata ));		break;
 			case DFNTAG_GOOD:			applyTo->SetGood( static_cast<SI16>( ndata ));			break;
 			case DFNTAG_GLOW:			applyTo->SetGlow( ndata );								break;
 			case DFNTAG_GLOWBC:			applyTo->SetGlowColour( static_cast<UI16>( ndata ));	break;
@@ -342,6 +343,7 @@ auto ApplyItemSection( CItem *applyTo, CScriptSection *toApply, std::string sect
 				}
 				break;
 			case DFNTAG_HIDAMAGE:		applyTo->SetHiDamage( static_cast<SI16>( ndata ));		break;
+			case DFNTAG_HITCHANCE:	applyTo->SetHitChance( static_cast<SI16>( ndata ));		break;
 			case DFNTAG_HEAT:			applyTo->SetWeatherDamage( HEAT, ndata != 0 );			break;
 			case DFNTAG_ID:				// applyTo->SetId( static_cast<UI16>( 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<SI16>( ndata ));		break;
+			case DFNTAG_DEFENSECHANCE:		applyTo->SetDefenseChance( static_cast<SI16>( ndata ));		break;
 			case DFNTAG_NPCWANDER:
 				if( !isGate )
 				{
diff --git a/source/ssection.cpp b/source/ssection.cpp
index 8b37b854b..dcd593c59 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,6 +113,7 @@ 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,
@@ -329,6 +331,7 @@ const std::map<std::string, DFNTAGS> 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},
@@ -374,6 +377,7 @@ const std::map<std::string, DFNTAGS> 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},
diff --git a/source/ssection.h b/source/ssection.h
index f7c34b2dd..937a9af32 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,6 +120,7 @@ enum DFNTAGS
 	DFNTAG_HEAT,
 	DFNTAG_HERDING,
 	DFNTAG_HIDAMAGE,
+	DFNTAG_HITCHANCE,
 	DFNTAG_HIDING,
 	DFNTAG_HIRELING,
 	DFNTAG_HP,