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( 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; @@ -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( 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 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( 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() @@ -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( 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" ) @@ -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( 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; 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( 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" ) @@ -1939,6 +1950,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" ) 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( 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/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( 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; @@ -342,6 +343,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 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 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 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,