diff --git a/Changelog.txt b/Changelog.txt index d6bd993e5..a72642778 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -3915,8 +3915,57 @@ Added: 'H' shortcut for variables to get the value as hexadecimal. 13-10-2024, Jhobean - Added: @PetRelease trigger work like @petdesert and return 1 to prevent the pet from being released. -26-10-2024, canerksk -- Added: Added new local variable in WOPSYSTEM LOCAL.WOPTalkMode and ini settings WOPTalkMode=10 +27-11-2024, canerksk +- Added: All skills that have a distance query have been adjusted to take the RANGE values of that skill. + All skills to be used in RANGE; + BLACKSMITHING + MINING // It was added before. + FISHING // It was added before. + LUMBERJACKING // It was added before. + PEACEMAKING + ENTICEMENT + PROVOCATION + COOKING + TAMING + THROWING + SNOOPING + STEALING + Also added BREATH.MAXDIST to Breath. If this property is not given, the default is 14 distance. +- Added: Added NOREJOIN tag for corpses, If the NOREJOIN tag is one, the player cannot come back to life on that corpse. That is, the player comes back to life but not on the corpse. + This tag does not prevent the player from coming back to life, it just prevents them from respawning on that corpse. The player always comes back to life, but not on that corpse. +- Fixed: If you are around an entity that you own and you are trying to open a bank next to a bank, the "bank" command is perceived as a pet command and the bank does not open. There is no problem when you type "bank" after mounting the mount, but "bank" does not work when you dismount. (Issue #1331) + +27-11-2024, Gladie +- Fixed: Redeeding t_multi_addon outside of housing system. Before it was only looking for owner of the multi, but in our case we need to know uid of the redeeding char. + +2-12-2024, canerksk +- Fixed: Memory OnTick link char if not, the skill will not fail. + If the memory owner is not present, skills do not fail when taking damage, but if the memory owner is alive, skills fail when taking damage. +- Fixed: The entity was jumping frames in flooded pet commands (come, guard). +- Fixed: ITEMMEMORYEQUIP is not triggered in many default memories. + Previously, many memories did not have morex information, but now for some reason many memories have morex information and for this reason the trigger was not triggered in almost most of the memories, the morex query was removed. Also, the slang did not represent the memory item. + +02-12-2024, raydie +- Added: Add LAYER_SPELL_Explosion for use delayed explosion spell. Now if set Duration to spell explosion, these duration is the delay to execute the explosion (From UOGuide, explosion spell: 2 second delay between targetting and explosion). + +3-12-2024, canerksk +- Added: New trigger @HitReactive Reactive Armor plays a key role in the action mechanism and is currently under very fixed rules. It has been added as a new trigger with some variables to make it a bit more flexible. + @HitReactive + Local.Sound (r/w) =Sound ID, If it is blank or zero, no sound is produced. (Default: 01F1) + Local.EffectID (r/w) = Effect ID, If it is empty or zero, no effect will occur. (Default: 0374a) + Local.Damage (r/w) = This is the more1l, or PolyStr, value coming from the reactive armor spell to i_rune_reactive_armor. + LOCAL.ReflectDamage (r/w) = The amount of damage that will be reflected to this other party + LOCAL.ReduceDamage (r/w) = Amount to be deducted from damage received + LOCAL.DamageType (r/w) = Type of damage received (Default: DAMAGE_FIXED andDAMAGE_REACTIVE) + NOTE; + 1. If no ReflectDamage or ReduceDamage values are entered, the system defaults to the Reactive Armor Effect value. + 2. No damage amount can be less than 1. + +4-12-2024, canerksk +- Fixed: Sphere crash troubleshooting if a player has no CHATNAME value and remove a channel. + +6-12-2024, canerksk +- Added: Added LOCAL.WOPTalkMode in @SpellCast trigger and ini setting WOPTalkMode. In some clients, different operations can be performed in return for this, for example, if this talkmode went from sphere as TALKMODE SPELL, the operation is performed according to this incoming talk mode according to the client versions. Here, the possibility of changing the talkmode according to the client version used is given. By default, TALKMODE_SPELL Default talk mode: TALKMODE_SPELL = 10 Sphere.ini; @@ -3935,4 +3984,4 @@ Added: 'H' shortcut for variables to get the value as hexadecimal. TALKMODE_YELL = 9 // Can be heard 2 screens away. (client shortcut: !+space) TALKMODE_SPELL = 10 // Used by spells TALKMODE_GUILD = 13 // Used by guild chat (client shortcut: \) - TALKMODE_ALLIANCE = 14 // Used by alliance chat (client shortcut: shift+\) \ No newline at end of file + TALKMODE_ALLIANCE = 14 // Used by alliance chat (client shortcut: shift+\) diff --git a/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake b/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake index 699b50881..efc752811 100644 --- a/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake +++ b/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake @@ -17,6 +17,8 @@ function(toolchain_exe_stuff_common) HINT "/usr/local/opt/mariadb-connector-c/lib/mariadb" "/opt/homebrew/var/mariadb-connector-c/lib/mariadb" + "/opt/homebrew/opt/mariadb-connector-c/lib/mariadb" + "/opt/homebrew/opt/mariadb-connector-c/lib" "/opt/homebrew/lib/mariadb" ) message(STATUS "Library ${lib_name}: ${lib_${lib_name}_with_path}") diff --git a/src/game/CObjBase.h b/src/game/CObjBase.h index e34a42979..857db7cdd 100644 --- a/src/game/CObjBase.h +++ b/src/game/CObjBase.h @@ -1088,6 +1088,7 @@ enum CTRIG_TYPE : short CTRIG_HitIgnore, // I should ignore this target, just giving a record to scripts. CTRIG_HitMiss, // I just missed. CTRIG_HitParry, // I succesfully parried an hit. + CTRIG_HitReactive, // Reactive damage trigger CTRIG_HitTry, // I am trying to hit someone. starting swing. CTRIG_HouseDesignBegin, // Starting to customize. CTRIG_HouseDesignCommit, // I committed a new house design diff --git a/src/game/chars/CChar.cpp b/src/game/chars/CChar.cpp index c1b95127a..2504cbed8 100644 --- a/src/game/chars/CChar.cpp +++ b/src/game/chars/CChar.cpp @@ -87,6 +87,7 @@ lpctstr const CChar::sm_szTrigName[CTRIG_QTY+1] = // static "@HitIgnore", // I'm going to avoid a target (attacker.n.ignore=1) , should I un-ignore him?. "@HitMiss", // I just missed. "@HitParry", // I succesfully parried an hit. + "@HitReactive", // Reactive damage trigger "@HitTry", // I am trying to hit someone. starting swing. "@HouseDesignBegin", // Starting to customize. "@HouseDesignCommit", // I committed a new house design. @@ -2510,7 +2511,7 @@ bool CChar::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc, bo } case CHC_BREATH: { - if( !strnicmp(ptcKey, "BREATH.DAM", 10) ) + if (!strnicmp(ptcKey, "BREATH.MAXDIST", 14) || !strnicmp(ptcKey, "BREATH.DAM", 10)) { CVarDefCont * pVar = GetDefKey(ptcKey, true); sVal.FormatLLVal(pVar ? pVar->GetValNum() : 0); @@ -3808,7 +3809,7 @@ bool CChar::r_LoadVal( CScript & s ) break; case CHC_BREATH: { - if ( !strnicmp(ptcKey, "BREATH.DAM", 10) || !strnicmp(ptcKey, "BREATH.HUE", 10) || !strnicmp(ptcKey, "BREATH.ANIM", 11) || !strnicmp(ptcKey, "BREATH.TYPE", 11) || !strnicmp(ptcKey, "BREATH.DAMTYPE", 14)) + if ( !strnicmp(ptcKey, "BREATH.MAXDIST", 14) || !strnicmp(ptcKey, "BREATH.DAM", 10) || !strnicmp(ptcKey, "BREATH.HUE", 10) || !strnicmp(ptcKey, "BREATH.ANIM", 11) || !strnicmp(ptcKey, "BREATH.TYPE", 11) || !strnicmp(ptcKey, "BREATH.DAMTYPE", 14)) { SetDefNum(s.GetKey(), s.GetArgLLVal()); return true; diff --git a/src/game/chars/CCharAct.cpp b/src/game/chars/CCharAct.cpp index b4c427f1a..d6bf5d4e1 100644 --- a/src/game/chars/CCharAct.cpp +++ b/src/game/chars/CCharAct.cpp @@ -280,11 +280,14 @@ void CChar::LayerAdd( CItem * pItem, LAYER_TYPE layer ) return; } - if (!pItem->IsTypeSpellable() && !pItem->m_itSpell.m_spell && !pItem->IsType(IT_WAND)) // can this item have a spell effect ? If so we do not send - { + //if (!pItem->IsTypeSpellable() && !pItem->m_itSpell.m_spell && !pItem->IsType(IT_WAND)) // can this item have a spell effect ? If so we do not send + // Since most of the memories came with a morex information by default, almost no memory was triggered. + if (!(pItem->IsTypeSpellable() || pItem->IsType(IT_WAND))) + { if ((IsTrigUsed(TRIGGER_MEMORYEQUIP)) || (IsTrigUsed(TRIGGER_ITEMMEMORYEQUIP))) { - CScriptTriggerArgs pArgs; + //CScriptTriggerArgs pArgs; + CScriptTriggerArgs pArgs(pItem); // added "argo" argument pArgs.m_iN1 = layer; if (pItem->OnTrigger(ITRIG_MemoryEquip, this, &pArgs) == TRIGRET_RET_TRUE) { diff --git a/src/game/chars/CCharFight.cpp b/src/game/chars/CCharFight.cpp index 3ce5c26ba..182d96b14 100644 --- a/src/game/chars/CCharFight.cpp +++ b/src/game/chars/CCharFight.cpp @@ -943,19 +943,59 @@ int CChar::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uiType, int iDmgPhy { CItem* pReactive = LayerFind(LAYER_SPELL_Reactive); - if (pReactive) - { - int iReactiveDamage = (iDmg * pReactive->m_itSpell.m_PolyStr) / 100; - if (iReactiveDamage < 1) - { - iReactiveDamage = 1; - } - - iDmg -= iReactiveDamage; - pSrc->OnTakeDamage(iReactiveDamage, this, (DAMAGE_TYPE)(DAMAGE_FIXED | DAMAGE_REACTIVE), iDmgPhysical, iDmgFire, iDmgCold, iDmgPoison, iDmgEnergy,(SPELL_TYPE)pReactive->m_itSpell.m_spell); - pSrc->Sound(0x1F1); - pSrc->Effect(EFFECT_OBJ, ITEMID_FX_CURSE_EFFECT, this, 10, 16); - } + if (pReactive) + { + int iReactiveDamage = (iDmg * pReactive->m_itSpell.m_PolyStr) / 100; + int iReactiveRefDam = iReactiveDamage; + int iReactiveRedDam = iReactiveDamage; + SOUND_TYPE ReactiveSnd = 0x1F1; + ITEMID_TYPE ReactiveEffectID = ITEMID_FX_CURSE_EFFECT; + DAMAGE_TYPE ReactiveDamType = (DAMAGE_FIXED | DAMAGE_REACTIVE); + + if (IsTrigUsed(TRIGGER_HITREACTIVE)) + { + CScriptTriggerArgs HitReactiveArgs; + HitReactiveArgs.m_VarsLocal.SetNum("Sound", ReactiveSnd); // SOUND + HitReactiveArgs.m_VarsLocal.SetNum("EffectID", ReactiveEffectID); // EFFECTID + HitReactiveArgs.m_VarsLocal.SetNum("Damage", iReactiveDamage); // DAMAGE VALUE + HitReactiveArgs.m_VarsLocal.SetNum("ReflectDamage", iReactiveRefDam); // REFLECTED DAM + HitReactiveArgs.m_VarsLocal.SetNum("ReduceDamage", iReactiveRedDam); // REDUCED DAM + HitReactiveArgs.m_VarsLocal.SetNum("DamageType", ReactiveDamType); // DAMAGE TYPE + OnTrigger(CTRIG_HitReactive, pSrc, &HitReactiveArgs); + + ReactiveSnd = (SOUND_TYPE)HitReactiveArgs.m_VarsLocal.GetKeyNum("Sound"); // SOUND + ReactiveEffectID = (ITEMID_TYPE)HitReactiveArgs.m_VarsLocal.GetKeyNum("EffectID"); // EFFECTID + iReactiveDamage = (int)HitReactiveArgs.m_VarsLocal.GetKeyNum("Damage"); // DAMAGE VALUE + iReactiveRefDam = (int)HitReactiveArgs.m_VarsLocal.GetKeyNum("ReflectDamage"); // REFLECTED DAMAGE VALUE + iReactiveRedDam = (int)HitReactiveArgs.m_VarsLocal.GetKeyNum("ReduceDamage"); // REDUCED DAMAGE VALUE + ReactiveDamType = (DAMAGE_TYPE)HitReactiveArgs.m_VarsLocal.GetKeyNum("DamageType"); // DAMAGE TYPE + // should it be zero ? + //if (iReactiveDamage < 1) + // iReactiveDamage = 1; + + //if (iReactiveRedDam < 1) + // iReactiveRedDam = 1; + + //if (iReactiveRefDam < 1) + // iReactiveRefDam = 1; + } + + // reduce + if (iReactiveRedDam > 0 || iReactiveDamage > 0) + iDmg -= iReactiveRedDam ? iReactiveRedDam : iReactiveDamage; + + // reflect + if (iReactiveRefDam > 0 || iReactiveDamage > 0) + pSrc->OnTakeDamage(iReactiveRefDam ? iReactiveRefDam : iReactiveDamage, this, ReactiveDamType, iDmgPhysical, iDmgFire, iDmgCold, + iDmgPoison, iDmgEnergy, (SPELL_TYPE)pReactive->m_itSpell.m_spell); + + if (ReactiveSnd) + pSrc->Sound(ReactiveSnd); + + if (ReactiveEffectID) + pSrc->Effect(EFFECT_OBJ, ReactiveEffectID, this, 10, 16); + + } } } // Check if REFLECTPHYSICALDAM will reflect some damage back. diff --git a/src/game/chars/CCharNPCPet.cpp b/src/game/chars/CCharNPCPet.cpp index 91e65ff6a..0b9ef724c 100644 --- a/src/game/chars/CCharNPCPet.cpp +++ b/src/game/chars/CCharNPCPet.cpp @@ -173,14 +173,20 @@ bool CChar::NPC_OnHearPetCmd( lpctstr pszCmd, CChar *pSrc, bool fAllPets ) break; case PC_GUARD_ME: - m_Act_UID = pSrc->GetUID(); - Skill_Start(NPCACT_GUARD_TARG); + if ((m_Act_UID != pSrc->GetUID()) || (Skill_GetActive() != NPCACT_GUARD_TARG)) // When you do all guard flood, the mount jumps one frame. + { + m_Act_UID = pSrc->GetUID(); + Skill_Start(NPCACT_GUARD_TARG); + } break; case PC_COME: case PC_FOLLOW_ME: - m_Act_UID = pSrc->GetUID(); - Skill_Start(NPCACT_FOLLOW_TARG); + if ((m_Act_UID != pSrc->GetUID()) || (Skill_GetActive() != NPCACT_FOLLOW_TARG)) // When you do all come flood, the mount jumps one frame. + { + m_Act_UID = pSrc->GetUID(); + Skill_Start(NPCACT_FOLLOW_TARG); + } break; case PC_FOLLOW: diff --git a/src/game/chars/CCharSkill.cpp b/src/game/chars/CCharSkill.cpp index fe11e036f..4a344e817 100644 --- a/src/game/chars/CCharSkill.cpp +++ b/src/game/chars/CCharSkill.cpp @@ -1078,10 +1078,21 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) return true; } - if ( pItemTarg != nullptr && pItemTarg->IsTopLevel() && pItemTarg->IsType( IT_FORGE )) - m_Act_p = pItemTarg->GetTopPoint(); - else - m_Act_p = CWorldMap::FindItemTypeNearby( GetTopPoint(), IT_FORGE, 3, false ); + if (pItemTarg != nullptr && pItemTarg->IsTopLevel() && pItemTarg->IsType(IT_FORGE)) + { + m_Act_p = pItemTarg->GetTopPoint(); + } + else + { + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_BLACKSMITHING); + int iMaxRange = pSkillDef->m_Range; + if (!iMaxRange) + { + g_Log.EventError("Blacksmith skill doesn't have a value for RANGE, defaulting to 3\n"); + iMaxRange = 3; + } + m_Act_p = CWorldMap::FindItemTypeNearby(GetTopPoint(), IT_FORGE, iMaxRange, false); + } if ( !m_Act_p.IsValidPoint() || !CanTouch(m_Act_p)) { @@ -1405,7 +1416,7 @@ int CChar::Skill_Mining( SKTRIG_TYPE stage ) const CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_MINING); const int iTargRange = GetTopPoint().GetDist(m_Act_p); int iMaxRange = pSkillDef->m_Range; - if ( !iMaxRange ) + if (!iMaxRange) { g_Log.EventError("Mining skill doesn't have a value for RANGE, defaulting to 2\n"); iMaxRange = 2; @@ -1501,7 +1512,7 @@ int CChar::Skill_Fishing( SKTRIG_TYPE stage ) CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_FISHING); int iTargRange = GetTopPoint().GetDist(m_Act_p); int iMaxRange = pSkillDef->m_Range; - if ( !iMaxRange ) + if (!iMaxRange) { g_Log.EventError("Fishing skill doesn't have a value for RANGE, defaulting to 4\n"); iMaxRange = 4; @@ -1603,7 +1614,7 @@ int CChar::Skill_Lumberjack( SKTRIG_TYPE stage ) CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_LUMBERJACKING); int iTargRange = GetTopPoint().GetDist(m_Act_p); int iMaxRange = pSkillDef->m_Range; - if ( !pSkillDef->m_Range ) + if (!iMaxRange) { g_Log.EventError("Lumberjacking skill doesn't have a value for RANGE, defaulting to 2\n"); iMaxRange = 2; @@ -1825,7 +1836,15 @@ int CChar::Skill_Peacemaking( SKTRIG_TYPE stage ) { int peace = Skill_GetAdjusted(SKILL_PEACEMAKING); int iRadius = ( peace / 100 ) + 2; // 2..12 - auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), iRadius); + + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_PEACEMAKING); + int iMaxRadius = pSkillDef->m_Range; + if (!iMaxRadius) + { + //g_Log.EventError("Peacemaking skill doesn't have a value for RANGE, defaulting to (Peacemaking skill level / 100 + 2) \n"); + iMaxRadius = iRadius; + } + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), iMaxRadius); for (;;) { CChar *pChar = Area->GetChar(); @@ -1951,9 +1970,15 @@ int CChar::Skill_Enticement( SKTRIG_TYPE stage ) SysMessagef("%s %s.", pChar->GetName(), g_Cfg.GetDefaultMsg(DEFMSG_ENTICEMENT_BATTLE)); return -SKTRIG_ABORT; } - + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_ENTICEMENT); + int iMaxRange = pSkillDef->m_Range; + if (!iMaxRange) + { + //g_Log.EventError("Enticement skill doesn't have a value for RANGE, defaulting 3\n"); + iMaxRange = 3; + } pChar->m_Act_p = GetTopPoint(); - pChar->NPC_WalkToPoint( ( pChar->m_Act_p.GetDist(pChar->GetTopPoint()) > 3) ); + pChar->NPC_WalkToPoint((pChar->m_Act_p.GetDist(pChar->GetTopPoint()) > iMaxRange)); return 0; } @@ -1969,6 +1994,9 @@ int CChar::Skill_Provocation(SKTRIG_TYPE stage) // m_Act_Prv_UID = provoke this person // m_Act_UID = against this person. + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_PROVOCATION); + int iMaxRange = pSkillDef->m_Range; + if ( stage == SKTRIG_ABORT ) return -SKTRIG_ABORT; @@ -2070,8 +2098,14 @@ int CChar::Skill_Provocation(SKTRIG_TYPE stage) pCharProv->Memory_AddObjTypes(this, MEMORY_AGGREIVED|MEMORY_IRRITATEDBY); + if (!iMaxRange) + { + //g_Log.EventError("Provocation skill doesn't have a value for RANGE, defaulting to UO_MAP_VIEW_SIGHT(14) \n"); + iMaxRange = UO_MAP_VIEW_SIGHT; + } + // If out of range we might get attacked ourself. - if ( (pCharProv->GetTopDist3D(pCharTarg) > UO_MAP_VIEW_SIGHT) || (pCharProv->GetTopDist3D(this) > UO_MAP_VIEW_SIGHT) ) + if ((pCharProv->GetTopDist3D(pCharTarg) > iMaxRange) || (pCharProv->GetTopDist3D(this) > iMaxRange)) { // Check that only "evil" monsters attack provoker back if ( pCharProv->Noto_IsEvil() ) @@ -2183,7 +2217,13 @@ int CChar::Skill_Cooking( SKTRIG_TYPE stage ) // m_Act_p = the heat source // m_Act_UID = the skill tool - int iMaxDist = 3; + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_COOKING); + int iMaxDist = pSkillDef->m_Range; + if (!iMaxDist) + { + g_Log.EventError("Cooking skill doesn't have a value for RANGE, defaulting to 3\n"); + iMaxDist = 3; + } if ( stage == SKTRIG_START ) { @@ -2239,10 +2279,13 @@ int CChar::Skill_Taming( SKTRIG_TYPE stage ) } CSkillDef* pSkillDef = g_Cfg.GetSkillDef(SKILL_TAMING); - if (pSkillDef->m_Range <= 0) - pSkillDef->m_Range = 10; - - if ( GetTopDist3D(pChar) > pSkillDef->m_Range) + int iMaxRange = pSkillDef->m_Range; + if (!iMaxRange) + { + g_Log.EventError("Taming skill doesn't have a value for RANGE, defaulting to 10\n"); + iMaxRange = 10; + } + if (GetTopDist3D(pChar) > iMaxRange) { SysMessageDefault( DEFMSG_TAMING_REACH ); return -SKTRIG_QTY; @@ -3237,8 +3280,16 @@ int CChar::Skill_Act_Breath( SKTRIG_TYPE stage ) return -SKTRIG_QTY; const CPointMap& pntMe = GetTopPoint(); - if ( pntMe.GetDist( m_Act_p ) > UO_MAP_VIEW_SIGHT ) - m_Act_p.StepLinePath( pntMe, UO_MAP_VIEW_SIGHT ); + + int iMaxDist = (int)(GetDefNum("BREATH.MAXDIST", true)); + if (!iMaxDist) + { + //g_Log.EventError("Breath skill doesn't have a value for RANGE, defaulting to UO_MAP_VIEW_SIGHT(14) \n"); + iMaxDist = UO_MAP_VIEW_SIGHT; + } + + if (pntMe.GetDist(m_Act_p) > iMaxDist) + m_Act_p.StepLinePath(pntMe, iMaxDist); int iDamage = (int)(GetDefNum("BREATH.DAM", true)); @@ -3319,8 +3370,17 @@ int CChar::Skill_Act_Throwing( SKTRIG_TYPE stage ) return -SKTRIG_QTY; const CPointMap pntMe(GetTopPoint()); - if ( pntMe.GetDist( m_Act_p ) > UO_MAP_VIEW_SIGHT ) - m_Act_p.StepLinePath( pntMe, UO_MAP_VIEW_SIGHT ); + + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_THROWING); + int iMaxRange = pSkillDef->m_Range; + if (!iMaxRange) + { + //g_Log.EventError("Throwing skill doesn't have a value for RANGE, defaulting to UO_MAP_VIEW_SIGHT(14) \n"); + iMaxRange = UO_MAP_VIEW_SIGHT; + } + + if (pntMe.GetDist(m_Act_p) > iMaxRange) + m_Act_p.StepLinePath(pntMe, iMaxRange); SoundChar( CRESND_GETHIT ); @@ -4055,7 +4115,15 @@ int CChar::Skill_Snooping(SKTRIG_TYPE stage) if (!IsTakeCrime(pCont, &pCharMark) || pCharMark == nullptr) return 0; // Not a crime really. - if (GetTopDist3D(pCharMark) > 1) + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_SNOOPING); + int iMaxRange = pSkillDef->m_Range; + if (!iMaxRange) + { + g_Log.EventError("Snooping skill doesn't have a value for RANGE, defaulting to 1\n"); + iMaxRange = 1; + } + + if (GetTopDist3D(pCharMark) > iMaxRange) { SysMessageDefault(DEFMSG_SNOOPING_REACH); return (-SKTRIG_QTY); @@ -4207,7 +4275,14 @@ int CChar::Skill_Stealing(SKTRIG_TYPE stage) bool fGround = false; if (pCharMark != nullptr) { - if (GetTopDist3D(pCharMark) > 2) + CSkillDef *pSkillDef = g_Cfg.GetSkillDef(SKILL_STEALING); + int iMaxRange = pSkillDef->m_Range; + if (!iMaxRange) + { + g_Log.EventError("Stealing skill doesn't have a value for RANGE, defaulting to 2\n"); + iMaxRange = 2; + } + if (GetTopDist3D(pCharMark) > iMaxRange) { SysMessageDefault(DEFMSG_STEALING_MARK); return -SKTRIG_QTY; diff --git a/src/game/chars/CCharSpell.cpp b/src/game/chars/CCharSpell.cpp index 0d58a8b16..1a487493d 100644 --- a/src/game/chars/CCharSpell.cpp +++ b/src/game/chars/CCharSpell.cpp @@ -1894,6 +1894,14 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) break; } + case SPELL_Explosion: + { + iEffect = iLevel; + iDmgType = DAMAGE_MAGIC | DAMAGE_FIRE; + Effect(EFFECT_OBJ, ITEMID_FX_EXPLODE_3, pItem->m_uidLink.CharFind(), 10, 16); + } + break; + case SPELL_Strangle: { /* @@ -1982,13 +1990,23 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("DamageType"))); if (iDmgType > 0 && iEffect > 0) // This is necessary if we have a spell that is harmful but does no damage periodically. { - OnTakeDamage(iEffect, pItem->m_uidLink.CharFind(), iDmgType, - (iDmgType & (DAMAGE_HIT_BLUNT | DAMAGE_HIT_PIERCE | DAMAGE_HIT_SLASH)) ? 100 : 0, - (iDmgType & DAMAGE_FIRE) ? 100 : 0, - (iDmgType & DAMAGE_COLD) ? 100 : 0, - (iDmgType & DAMAGE_POISON) ? 100 : 0, - (iDmgType & DAMAGE_ENERGY) ? 100 : 0, - spell); + // + CChar *pLinkedChar = pItem->m_uidLink.CharFind(); + if (pLinkedChar == nullptr) + { + // pLinkedChar = this; // If it is not alive or deleted, should it be like it is self-damaging? Note: Under the OnTakeDamage() + Skill_Fail(); // If the memory does not belong to a creature, or if the creature is dead or deleted, skills do not take effect on damage taken. + } + + OnTakeDamage(iEffect, + pLinkedChar, + iDmgType, + (iDmgType & (DAMAGE_HIT_BLUNT | DAMAGE_HIT_PIERCE | DAMAGE_HIT_SLASH)) ? 100 : 0, + (iDmgType & DAMAGE_FIRE) ? 100 : 0, + (iDmgType & DAMAGE_COLD) ? 100 : 0, + (iDmgType & DAMAGE_POISON) ? 100 : 0, + (iDmgType & DAMAGE_ENERGY) ? 100 : 0, + spell); } } else if (pSpellDef->IsSpellType(SPELLFLAG_HEAL)) @@ -2037,6 +2055,10 @@ CItem * CChar::Spell_Effect_Create( SPELL_TYPE spell, LAYER_TYPE layer, int iEff if ( layer == LAYER_SPELL_STATS && spell != pSpellPrev->m_itSpell.m_spell && IsSetMagicFlags(MAGICF_STACKSTATS) ) continue; + // If spell is explosion and there's already an explosion timer, dont remove it + if ( spell == SPELL_Explosion && layer == LAYER_SPELL_Explosion ) + continue; + pSpellPrev->Delete(); break; } @@ -2045,15 +2067,16 @@ CItem * CChar::Spell_Effect_Create( SPELL_TYPE spell, LAYER_TYPE layer, int iEff CItem *pSpell = CItem::CreateBase(pSpellDef ? pSpellDef->m_idSpell : ITEMID_RHAND_POINT_NW); ASSERT(pSpell); - switch ( layer ) - { - case LAYER_FLAG_Criminal: pSpell->SetName("Criminal Timer"); break; - case LAYER_FLAG_PotionUsed: pSpell->SetName("Potion Cooldown"); break; - case LAYER_FLAG_Drunk: pSpell->SetName("Drunk Effect"); break; - case LAYER_FLAG_Hallucination: pSpell->SetName("Hallucination Effect"); break; - case LAYER_FLAG_Murders: pSpell->SetName("Murder Decay"); break; - default: break; - } + switch ( layer ) + { + case LAYER_FLAG_Criminal: pSpell->SetName("Criminal Timer"); break; + case LAYER_FLAG_PotionUsed: pSpell->SetName("Potion Cooldown"); break; + case LAYER_FLAG_Drunk: pSpell->SetName("Drunk Effect"); break; + case LAYER_FLAG_Hallucination: pSpell->SetName("Hallucination Effect"); break; + case LAYER_FLAG_Murders: pSpell->SetName("Murder Decay"); break; + case LAYER_SPELL_Explosion: pSpell->SetName("Explosion Timer"); break; + default: break; + } g_World.m_uidNew = pSpell->GetUID(); pSpell->SetAttr(pSpellDef ? ATTR_NEWBIE|ATTR_MAGIC : ATTR_NEWBIE); @@ -3909,6 +3932,12 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, } break; + case SPELL_Explosion: + // if not a potion and have duration, create effect + if (!fPotion && iDuration > 0) + Spell_Effect_Create( SPELL_Explosion, LAYER_SPELL_Explosion, iEffect, iDuration, pCharSrc ); + break; + case SPELL_Invis: Spell_Effect_Create( spell, fPotion ? LAYER_FLAG_Potion : LAYER_SPELL_Invis, iEffect, iDuration, pCharSrc ); break; diff --git a/src/game/chars/CCharStatus.cpp b/src/game/chars/CCharStatus.cpp index 32c20165e..f6cff4e63 100644 --- a/src/game/chars/CCharStatus.cpp +++ b/src/game/chars/CCharStatus.cpp @@ -1996,7 +1996,7 @@ bool CChar::CanStandAt(CPointMap *ptDest, const CRegion* pArea, uint64 uiMyMovem if (!fPathfinding && (g_Cfg.m_iDebugFlags & DEBUGF_WALK)) g_Log.EventWarn("GetHeightMount() %hhu, block.m_Top.m_z %hhd, ptDest.m_z %hhd.\n", uiMyHeight, blockingState->m_Top.m_z, ptDest->m_z); - if ((uiMyHeight + ptDest->m_z >= blockingState->m_Top.m_z) && g_Cfg.m_iMountHeight && !IsPriv(PRIV_GM) && !IsPriv(PRIV_ALLMOVE)) + if (IsStatFlag(STATF_ONHORSE) && (uiMyHeight + ptDest->m_z >= blockingState->m_Top.m_z) && g_Cfg.m_iMountHeight && !IsPriv(PRIV_GM) && !IsPriv(PRIV_ALLMOVE)) { if (!fPathfinding) SysMessageDefault(DEFMSG_MSG_MOUNT_CEILING); diff --git a/src/game/clients/CChatChanMember.cpp b/src/game/clients/CChatChanMember.cpp index edc41a909..0904d5e95 100644 --- a/src/game/clients/CChatChanMember.cpp +++ b/src/game/clients/CChatChanMember.cpp @@ -197,7 +197,7 @@ lpctstr CChatChanMember::GetChatName() const ADDTOCALLSTACK("CChatChanMember::GetChatName"); const CClient *pClient = GetClientActive(); - if (pClient) + if (pClient && pClient->GetAccount() && !pClient->GetAccount()->m_sChatName.IsEmpty()) return(pClient->GetAccount()->m_sChatName); return ""; } diff --git a/src/game/clients/CClientEvent.cpp b/src/game/clients/CClientEvent.cpp index c4ecc42ff..491ec1962 100644 --- a/src/game/clients/CClientEvent.cpp +++ b/src/game/clients/CClientEvent.cpp @@ -1988,6 +1988,15 @@ void CClient::Event_Talk_Common(lpctstr pszText) // PC speech break; } */ + // NPC's with special key words ? + if (pChar->m_pNPC) + { + if (pChar->m_pNPC->m_Brain == NPCBRAIN_BANKER) + { + if (FindStrWord(pszText, "BANK") > 0) + break; + } + } } if ( !pChar ) diff --git a/src/game/clients/CClientUse.cpp b/src/game/clients/CClientUse.cpp index 2b13591ab..2e536abee 100644 --- a/src/game/clients/CClientUse.cpp +++ b/src/game/clients/CClientUse.cpp @@ -158,6 +158,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) case IT_SHAFT: case IT_FEATHER: + case IT_FLETCHING: return Skill_Menu(SKILL_BOWCRAFT, "sm_bolts", pItem->GetID()); case IT_FISH_POLE: // Just be near water ? @@ -185,7 +186,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) } else // I have the key but i need to use it to unlock the container. { - SysMessageDefault(DEFMSG_LOCK_HAS_KEY); + SysMessageDefault(DEFMSG_LOCK_HAS_KEY); return false; } break; @@ -194,7 +195,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) SysMessageDefault(DEFMSG_ITEMUSE_LOCKED); if ( !m_pChar->GetPackSafe()->ContentFindKeyFor(pItem) ) // I don't have the hold key { - + SysMessageDefault(DEFMSG_LOCK_HOLD_NO_KEY); if ( !IsPriv(PRIV_GM) ) return false; @@ -225,7 +226,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) if ( m_pChar->CheckCorpseCrime(pCorpseItem, true, true) ) SysMessageDefault(DEFMSG_LOOT_CRIMINAL_ACT); } - + return true; } @@ -849,7 +850,7 @@ int CClient::Cmd_Skill_Menu_Build( const CResourceID& rid, int iSelect, CMenuIte { if ( (iSelect < -1) && (iShowCount >= 1) ) // just a test. so we are done. return 1; - + CMenuItem miTest; if ( !miTest.ParseLine(s.GetArgRaw(), nullptr, m_pChar) ) { @@ -1150,7 +1151,7 @@ bool CClient::Cmd_Skill_Tracking( uint track_sel, bool fExec ) When the Tracking skill starts and the Effect property is defined on the Tracking skill use it instead of the hardcoded formula for the maximum distance. */ - + if (m_pChar->m_Act_Effect >= 0) m_pChar->m_atTracking.m_dwDistMax = (dword)m_pChar->m_Act_Effect; else //This is default Sphere maximum tracking distance. diff --git a/src/game/items/CItemCorpse.cpp b/src/game/items/CItemCorpse.cpp index 75c5b57c0..95b837923 100644 --- a/src/game/items/CItemCorpse.cpp +++ b/src/game/items/CItemCorpse.cpp @@ -150,6 +150,8 @@ CItemCorpse *CChar::FindMyCorpse( bool ignoreLOS, int iRadius ) const break; if ( !pItem->IsType(IT_CORPSE) ) continue; + if (pItem->m_TagDefs.GetKeyNum("NOREJOIN")) // The owner should not rejoin this body even if resurrected on top of it. + continue; CItemCorpse *pCorpse = dynamic_cast(pItem); if ( !pCorpse || (pCorpse->m_uidLink != GetUID()) ) continue; diff --git a/src/game/items/CItemMulti.cpp b/src/game/items/CItemMulti.cpp index 01cf07eca..64753406c 100644 --- a/src/game/items/CItemMulti.cpp +++ b/src/game/items/CItemMulti.cpp @@ -1174,7 +1174,7 @@ int16 CItemMulti::GetMultiCount() const return _iMultiCount; } -void CItemMulti::Redeed(bool fDisplayMsg, bool fMoveToBank, CUID uidChar) +void CItemMulti::Redeed(bool fDisplayMsg, bool fMoveToBank, CUID uidRedeedingChar) { ADDTOCALLSTACK("CItemMulti::Redeed"); if (GetKeyNum("REMOVED") > 0) // Just don't pass from here again, to avoid duplicated deeds. @@ -1209,7 +1209,7 @@ void CItemMulti::Redeed(bool fDisplayMsg, bool fMoveToBank, CUID uidChar) args.m_iN3 = fMoveToBank; // Transfer the Moving Crate to the owner's bank. if (IsTrigUsed(TRIGGER_REDEED)) { - tRet = OnTrigger(ITRIG_Redeed, uidChar.CharFind(), &args); + tRet = OnTrigger(ITRIG_Redeed, uidRedeedingChar.CharFind(), &args); if (args.m_iN2 == 0) { fMoveToBank = false; @@ -1230,7 +1230,8 @@ void CItemMulti::Redeed(bool fDisplayMsg, bool fMoveToBank, CUID uidChar) } CChar* pOwner = GetOwner().CharFind(); - if (!pOwner || !pOwner->m_pPlayer) + CChar* pChar = uidRedeedingChar.CharFind(); + if ((!pChar || !pChar->m_pPlayer) && (!pOwner || !pOwner->m_pPlayer)) { return; } @@ -1256,11 +1257,26 @@ void CItemMulti::Redeed(bool fDisplayMsg, bool fMoveToBank, CUID uidChar) } if (fMoveToBank) { - pOwner->GetBank(LAYER_BANKBOX)->ContentAdd(pDeed); + if (pOwner) + { + pOwner->GetBank(LAYER_BANKBOX)->ContentAdd(pDeed); + } + else + { + pChar->GetBank(LAYER_BANKBOX)->ContentAdd(pDeed); + } + } else { - pOwner->ItemBounce(pDeed, fDisplayMsg); + if (pOwner) + { + pOwner->ItemBounce(pDeed, fDisplayMsg); + } + else + { + pChar->ItemBounce(pDeed, fDisplayMsg); + } } } SetKeyNum("REMOVED", 1); diff --git a/src/game/items/item_types.h b/src/game/items/item_types.h index 64000b465..438e0569c 100644 --- a/src/game/items/item_types.h +++ b/src/game/items/item_types.h @@ -173,7 +173,7 @@ enum IT_TYPE : int32_t // double click type action. IT_TRAIN_DUMMY, // 156 IT_TRAIN_PICKPOCKET, // 157 IT_BEDROLL, // 158 - IT_UNUSED_159, // 159 + IT_FLETCHING, // 159 IT_HIDE, // 160 = hides are cured to make leather. IT_CLOTH_BOLT, // 161 = must be cut up to make cloth squares. IT_BOARD, // 162 = logs are plained into decent lumber @@ -214,7 +214,7 @@ enum IT_TYPE : int32_t // double click type action. IT_PILOT, // 197 = ship's pilot (PacketWheelMove) IT_ROPE, // 198 = t_rope (working like t_ship_plank but without id changes) IT_WEAPON_WHIP, // 199 - + // New SphereX hardcoded types starting from 300 IT_SPAWN_CHAMPION = 300,// 300 = t_spawn_champion IT_MULTI_ADDON, // 301 = t_multi_addon diff --git a/src/game/uo_files/uofiles_enums.h b/src/game/uo_files/uofiles_enums.h index 2653cd8d6..fe233958c 100644 --- a/src/game/uo_files/uofiles_enums.h +++ b/src/game/uo_files/uofiles_enums.h @@ -654,9 +654,10 @@ enum LAYER_TYPE : unsigned char // defined by UO. Only one item can be in a slot //Individual Spell Layers LAYER_SPELL_Mana_Drain, + LAYER_SPELL_Explosion, - LAYER_STORAGE, //80 New Storage layer, can equip t_container and t_container_locked. - LAYER_STABLE, //81 New stable layer, now stabled pets will be stored in this layer of the player instead of npc's itself. + LAYER_STORAGE, // New Storage layer, can equip t_container and t_container_locked. + LAYER_STABLE, // New stable layer, now stabled pets will be stored in this layer of the player instead of npc's itself. LAYER_QTY }; diff --git a/src/tables/triggers.tbl b/src/tables/triggers.tbl index a200f613a..53e82c442 100644 --- a/src/tables/triggers.tbl +++ b/src/tables/triggers.tbl @@ -79,6 +79,7 @@ ADD(HITCHECK) ADD(HITIGNORE) ADD(HITMISS) ADD(HITPARRY) +ADD(HITREACTIVE) ADD(HITTRY) ADD(HOUSEDESIGNBEGIN) ADD(HOUSEDESIGNCOMMIT)