diff --git a/Changelog.txt b/Changelog.txt index 4aba05b98..9e74e3d91 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -3352,6 +3352,28 @@ Additionally, the problem of zig-zag issue following in the South direction has 26-09-2023, Drk84 - Fixed: Ship plank disappearing when closed before its timer expires or after a save is performed. +02-10-2023, xwerswoodx +- Fixed: Re-Coded @KarmaChange and @FameChange triggers not setting variables correctly. (Issue #1118) + ARGN1 now return as the amount of Karma/Fame being added. + ARGN2 is now return oldKarma. (ARGN2 + ARGN1 == New Karma) + LOCAL.NEW removed, while ARGN1 is writeable, it's impossible to track it inside the trigger. + ARGO now return the source of karma/fame income if exists. +- Fixed: f_onchar_create_init is not called. (Issue: #1117) +- Fixed: IF and QVAL statements cannot evaluate correctly 32bit+ bitwise conditions. (Issue: #1078) +- Added: @PartyAdd trigger. (Feature Request: #1054) + I: The player who is joining to the party. + SRC: The player who is inviting. + Return 1: Cancels the action. +- Added: LOCAL.SOUND and LOCAL.ANIM under @Start (Skill) and @SkillStart triggers to override sound and animations before start crafting or gathering. +- Fixed: FACTION property not saved and load correctly under [CHARDEF] definition. (Issue: #1059 and #1083) +- Added: Added COMBAT_PARALYZE_CANSWING (080000) to combat flags to let characters continue attack while paralyzed like old behaviour. (Issue: #1124) +- Fixed: ATTACK command causes the player getting criminal without checking AttackingIsACrime value in sphere.ini (Issue: #1123) +- Added: Added CAN_I_EQUIPONCAST (08000000) flag for items to let scripters customize allowed items while EquippedCast disabled. +- Fixed: Spells can trigger @Success triggers and play animations if they paralyzed after selected skill. +- Fixed: Motivation < 0 causes STATF_WAR flag stay infinitely on characters. (Issue: #517) +- Fixed: The issue where characters can spawn out of the rooms. (Issue: #1084) +- Fixed: More1 and More2 values readn as integer instead of dword (unsigned integer) (Issue: #784) + 07-10-2023, Nolok - Added: FUNC keyword to item templates and template-triggers with special parsing (@Create, @CreateLoot, @NPCRestock). It allows to call a function with arguments on the last created ITEM. Default object: the item. SRC: the character holding it. diff --git a/src/common/CScriptObj.cpp b/src/common/CScriptObj.cpp index 231d3dd79..3b34067f5 100644 --- a/src/common/CScriptObj.cpp +++ b/src/common/CScriptObj.cpp @@ -1527,7 +1527,7 @@ bool CScriptObj::_Evaluate_Conditional_EvalSingle(SubexprData& sdata, CTextConso // If an expression is enclosed by parentheses, ParseScriptText needs to read both the open and the closed one, we cannot // pass the string starting with the character after the '('. ParseScriptText(ptcSubexpr, pSrc, 0, pArgs); - fVal = bool(Exp_GetVal(ptcSubexpr)); + fVal = bool(Exp_GetLLVal(ptcSubexpr)); } -- pContext->_iEvaluate_Conditional_Reentrant; @@ -1708,7 +1708,7 @@ bool CScriptObj::Evaluate_QvalConditional(lpctstr ptcKey, CSString& sVal, CTextC tchar* ptcTemp = Str_GetTemp(); Str_CopyLimitNull(ptcTemp, ppCmds[0], STR_TEMPLENGTH); ParseScriptText(ptcTemp, pSrc, 0, pArgs, pContext); - const bool fCondition = Exp_GetVal(ptcTemp); + const bool fCondition = Exp_GetLLVal(ptcTemp); // Get the retval we want // (we might as well work on the transformed original string, since at this point we don't care if we corrupt other arguments) diff --git a/src/game/CBase.cpp b/src/game/CBase.cpp index b721eec32..5185428ba 100644 --- a/src/game/CBase.cpp +++ b/src/game/CBase.cpp @@ -56,7 +56,7 @@ void CBaseBaseDef::DelInstance() --_dwInstances; } -CFactionDef CBaseBaseDef::GetFaction() +CCFaction CBaseBaseDef::GetFaction() { return _pFaction; } @@ -147,7 +147,7 @@ bool CBaseBaseDef::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * p case OBC_FACTION: case OBC_SLAYER: - sVal.FormatHex((dword)GetFaction().GetFactionID()); + sVal.FormatULLHex(_pFaction.GetFactionID()); break; case OBC_ARMOR: @@ -329,7 +329,7 @@ bool CBaseBaseDef::r_LoadVal( CScript & s ) break; case OBC_FACTION: case OBC_SLAYER: - GetFaction().SetFactionID( (NPC_FACTION)s.GetArgVal() ); + _pFaction.SetFactionID( static_cast(s.GetArgULLVal()) ); return true; //Set as number only case OBC_EXPANSION: diff --git a/src/game/CBase.h b/src/game/CBase.h index a3ceb8223..64dcf8c8f 100644 --- a/src/game/CBase.h +++ b/src/game/CBase.h @@ -55,11 +55,11 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps dword m_Can; // Base attribute flags. CAN_C_GHOST, etc RESDISPLAY_VERSION _iEraLimitProps; // Don't allow to have properties newer than the given era. - CFactionDef _pFaction; + CCFaction _pFaction; public: - CFactionDef GetFaction(); + CCFaction GetFaction(); /** * @brief Gets definition string. @@ -153,6 +153,7 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps #define CAN_I_FORCEDC 0x1000000 // Can force DClick skipping other checks (LOS,Distance, Cont...). #define CAN_I_DAMAGEABLE 0x2000000 // Display item health bar on HS clients >= 7.0.30.0 (MORE1L = cur hitpoints / MORE1H = max hitpoints) #define CAN_I_BLOCKLOS_HEIGHT 0x4000000 // blocks LOS without blocking walkchecks, but only if the item is too high for the viewer. +#define CAN_I_EQUIPONCAST 0x8000000 // Allow items to stay equipped while EquippedCast disabled in sphere.ini. // (CItemBase) CanEquip specific defs. #define CAN_U_ALL 0x000 // Can be used by everyone. diff --git a/src/game/CObjBase.h b/src/game/CObjBase.h index f0713b96b..5c49ed41b 100644 --- a/src/game/CObjBase.h +++ b/src/game/CObjBase.h @@ -1118,7 +1118,7 @@ enum CTRIG_TYPE : short CTRIG_ExpChange, // EXP is going to change CTRIG_ExpLevelChange, // Experience LEVEL is going to change CTRIG_Falling, // CHAR IS FALLING - CTRIG_FameChange, // Fame chaged + CTRIG_FameChange, // Fame is changing CTRIG_FollowersUpdate, // Adding or removing CurFollowers. CTRIG_GetHit, // I just got hit. @@ -1174,7 +1174,7 @@ enum CTRIG_TYPE : short CTRIG_itemUNEQUIP, // i have unequipped (or try to unequip) an item. CTRIG_Jailed, // I'm up to be send to jail, or to be forgiven. - CTRIG_KarmaChange, // Karma chaged + CTRIG_KarmaChange, // Karma is changing CTRIG_Kill, // I have just killed someone. CTRIG_LogIn, // Client logs in. CTRIG_LogOut, // Client logs out (21). @@ -1200,6 +1200,7 @@ enum CTRIG_TYPE : short CTRIG_NPCSeeWantItem, // (NPC only) i see something good. CTRIG_NPCSpecialAction, // (NPC only) performing some special actions (spyder's web, dragon's breath...). + CTRIG_PartyAdd, // Player joined to the party. CTRIG_PartyDisband, // I just disbanded my party. CTRIG_PartyInvite, // SRC invited me to join a party, so I may chose. CTRIG_PartyLeave, // I'm leaving this party. diff --git a/src/game/CServerConfig.h b/src/game/CServerConfig.h index b2ffb9ddf..0552ac3dd 100644 --- a/src/game/CServerConfig.h +++ b/src/game/CServerConfig.h @@ -119,7 +119,8 @@ enum COMBATFLAGS_TYPE COMBAT_ANIM_HIT_SMOOTH = 0x10000, // The hit animation has the same duration as the swing delay, instead of having a fixed fast duration and being idle until the delay has expired. // WARNING: doesn't work with Gargoyles due to the new animation packet not accepting a custom animation duration! COMBAT_FIRSTHIT_INSTANT = 0x20000, // The first hit in a fight doesn't wait for the recoil time (OSI like) - COMBAT_NPC_BONUSDAMAGE = 0x40000 // NPC will get full bonus damage from various sources. + COMBAT_NPC_BONUSDAMAGE = 0x40000, // NPC will get full bonus damage from various sources. + COMBAT_PARALYZE_CANSWING = 0x80000 // Characters can continue attacking while paralyzed. (Old sphere behaviour) }; /** diff --git a/src/game/CWorldImport.cpp b/src/game/CWorldImport.cpp index 4ca21821d..828f64bbe 100644 --- a/src/game/CWorldImport.cpp +++ b/src/game/CWorldImport.cpp @@ -471,12 +471,12 @@ bool CImportFile::ImportWSC( CScript & s, word wModeFlags ) } else if ( s.IsKey("MORE" )) { - pItem->m_itNormal.m_more1 = atoi(pArg); + pItem->m_itNormal.m_more1 = Str_ToUI(pArg); continue; } else if ( s.IsKey("MORE2" )) { - pItem->m_itNormal.m_more2 = atoi(pArg); + pItem->m_itNormal.m_more2 = Str_ToUI(pArg); continue; } else if ( s.IsKey("DYEABLE" )) diff --git a/src/game/chars/CChar.cpp b/src/game/chars/CChar.cpp index bb6f8973d..e770944f1 100644 --- a/src/game/chars/CChar.cpp +++ b/src/game/chars/CChar.cpp @@ -73,7 +73,7 @@ lpctstr const CChar::sm_szTrigName[CTRIG_QTY+1] = // static "@ExpChange", // EXP is going to change "@ExpLevelChange", // Experience LEVEL is going to change "@Falling", //char is falling from height >= 10 - "@FameChange", // Fame changed + "@FameChange", // Fame is changing "@FollowersUpdate", "@GetHit", // I just got hit. @@ -129,8 +129,8 @@ lpctstr const CChar::sm_szTrigName[CTRIG_QTY+1] = // static "@itemUNEQUIP", // i have unequipped (or try to unequip) an item "@Jailed", - "@KarmaChange", // Karma chaged - "@Kill", //+I have just killed someone + "@KarmaChange", // Karma is changing + "@Kill", // +I have just killed someone "@LogIn", // Client logs in "@LogOut", // Client logs out (21) "@Mount", // I'm trying to mount my horse (or whatever) @@ -155,6 +155,7 @@ lpctstr const CChar::sm_szTrigName[CTRIG_QTY+1] = // static "@NPCSeeWantItem", // (NPC only) i see something good. "@NPCSpecialAction", // Idle + "@PartyAdd", // Player joined the party. "@PartyDisband", //I just disbanded my party "@PartyInvite", //SRC invited me to join a party, so I may chose "@PartyLeave", @@ -319,9 +320,7 @@ CChar::CChar( CREID_TYPE baseID ) : // SubscribeComponent Prop Components TrySubscribeComponentProps(); TrySubscribeComponentProps(); - - // SubscribeComponent regular Components - SubscribeComponent(new CCFaction()); + SubscribeComponent(new CCFaction(pCharDef->GetFaction())); ASSERT(IsDisconnected()); } diff --git a/src/game/chars/CChar.h b/src/game/chars/CChar.h index 8238ed6a5..b3c85975c 100644 --- a/src/game/chars/CChar.h +++ b/src/game/chars/CChar.h @@ -443,9 +443,9 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; SKILLLOCK_TYPE Stat_GetLock(STAT_TYPE stat); void Stat_SetLock(STAT_TYPE stat, SKILLLOCK_TYPE state); short GetKarma() const; - void SetKarma(short iNewKarma); + void SetKarma(short iNewKarma, CChar* pNPC = nullptr); ushort GetFame() const; - void SetFame(ushort uiNewFame); + void SetFame(ushort uiNewFame, CChar* pNPC = nullptr); void Stat_StrCheckEquip(); @@ -606,7 +606,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * @param iBottom is the lower value you can have for this execution. * @param bMessage show message to the char or not. */ - void Noto_Karma( int iKarmaChange, int iBottom = INT32_MIN, bool fMessage = false ); + void Noto_Karma( int iKarmaChange, int iBottom = INT32_MIN, bool fMessage = false, CChar* pNPC = nullptr ); /** * @brief Update Fame with the given value. @@ -615,7 +615,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * Can't never exceed g_Cfg.m_iMaxFame and can't never be lower than 0. * @param iFameChange is the amount of fame to change over the current one. */ - void Noto_Fame( int iFameChange ); + void Noto_Fame( int iFameChange, CChar* pNPC = nullptr ); /** * @brief I have a new notoriety Level? check it and show a message if so. @@ -1256,7 +1256,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; void NPC_Act_Fight(); void NPC_Act_Idle(); void NPC_Act_Looting(); - void NPC_Act_Flee(); + bool NPC_Act_Flee(); void NPC_Act_Goto(int iDist = 30); void NPC_Act_Runto(int iDist = 30); bool NPC_Act_Food(); diff --git a/src/game/chars/CCharBase.cpp b/src/game/chars/CCharBase.cpp index 6f328bf61..df7c83839 100644 --- a/src/game/chars/CCharBase.cpp +++ b/src/game/chars/CCharBase.cpp @@ -88,7 +88,6 @@ void CCharBase::CopyBasic( const CCharBase * pCharDef ) _uiRange = pCharDef->_uiRange; m_BaseResources = pCharDef->m_BaseResources; - _pFaction = pCharDef->_pFaction; CBaseBaseDef::CopyBasic( pCharDef ); // This will overwrite the CResourceLink!! } diff --git a/src/game/chars/CCharFight.cpp b/src/game/chars/CCharFight.cpp index 41e4e9da5..9515810fe 100644 --- a/src/game/chars/CCharFight.cpp +++ b/src/game/chars/CCharFight.cpp @@ -345,14 +345,20 @@ bool CChar::OnAttackedBy(CChar * pCharSrc, bool fCommandPet, bool fShouldReveal) Memory_AddObjTypes(pCharSrc, wMemTypes); Attacker_Add(pCharSrc); - // Are they a criminal for it ? Is attacking me a crime ? - if ((Noto_GetFlag(pCharSrc) == NOTO_GOOD) && fAggreived) + if ((Noto_GetFlag(pCharSrc) == NOTO_GOOD) && fAggreived ) { if (IsClientActive()) // I decide if this is a crime. - OnNoticeCrime(pCharSrc, this); + { + if (!fCommandPet || g_Cfg.m_fAttackingIsACrime) + { + OnNoticeCrime(pCharSrc, this); + CChar* pCharMark = pCharSrc->IsStatFlag(STATF_PET) ? pCharSrc->NPC_PetGetOwner() : pCharSrc; + if (pCharMark != pCharSrc) + OnNoticeCrime(pCharMark, this); + } + } else { - // If it is a pet then this a crime others can report. CChar * pCharMark = IsStatFlag(STATF_PET) ? NPC_PetGetOwner() : this; pCharSrc->CheckCrimeSeen(Skill_GetActive(), pCharMark, nullptr, nullptr); } @@ -1567,7 +1573,7 @@ WAR_SWING_TYPE CChar::Fight_CanHit(CChar * pCharSrc, bool fSwingNoRange) // We can't hit them right now. Because we can't see them or reach them (invis/hidden). // Why the target is freeze we are change the attack type to swinging? Player can still attack paralyzed or sleeping characters. // We make sure that the target is freeze or sleeping must wait ready for attack! - else if ( (pCharSrc->IsStatFlag(STATF_HIDDEN | STATF_INVISIBLE | STATF_SLEEPING)) || (IsStatFlag(STATF_FREEZE | STATF_SLEEPING)) ) // STATF_FREEZE | STATF_SLEEPING + else if ( (pCharSrc->IsStatFlag(STATF_HIDDEN | STATF_INVISIBLE | STATF_SLEEPING)) || (IsStatFlag(STATF_FREEZE) && (!IsSetCombatFlags(COMBAT_PARALYZE_CANSWING))) || (IsStatFlag(STATF_SLEEPING)) ) // STATF_FREEZE | STATF_SLEEPING { return WAR_SWING_SWINGING; } diff --git a/src/game/chars/CCharNPCAct.cpp b/src/game/chars/CCharNPCAct.cpp index aa38d6be7..0098dcc18 100644 --- a/src/game/chars/CCharNPCAct.cpp +++ b/src/game/chars/CCharNPCAct.cpp @@ -1571,7 +1571,7 @@ void CChar::NPC_Act_Looting() ItemBounce(pItem, false); } -void CChar::NPC_Act_Flee() +bool CChar::NPC_Act_Flee() { ADDTOCALLSTACK("CChar::NPC_Act_Flee"); ASSERT(m_pNPC); @@ -1581,13 +1581,14 @@ void CChar::NPC_Act_Flee() if ( ++ m_atFlee.m_iStepsCurrent >= m_atFlee.m_iStepsMax ) { Skill_Start( SKILL_NONE ); - return; + return false; } if ( ! NPC_Act_Follow( true, m_atFlee.m_iStepsMax )) { Skill_Start( SKILL_NONE ); - return; + return false; } + return true; } void CChar::NPC_Act_Runto(int iDist) diff --git a/src/game/chars/CCharNPCAct_Fight.cpp b/src/game/chars/CCharNPCAct_Fight.cpp index 52260950e..ad6224335 100644 --- a/src/game/chars/CCharNPCAct_Fight.cpp +++ b/src/game/chars/CCharNPCAct_Fight.cpp @@ -246,7 +246,18 @@ void CChar::NPC_Act_Fight() { m_atFlee.m_iStepsMax = 20; // how long should it take to get there. m_atFlee.m_iStepsCurrent = 0; // how long has it taken ? - Skill_Start(NPCACT_FLEE); // Run away! + if (NPC_Act_Flee()) + { + Skill_Start(NPCACT_FLEE); // Run away! + } + else + { + //We have to clean STATF_WAR if npc motivation below zero but can't see the target or flee target is invalid. + Skill_Start(SKILL_NONE); + StatFlag_Clear(STATF_WAR); + Attacker_Delete(pChar, true, ATTACKER_CLEAR_DISTANCE); + m_Fight_Targ_UID.InitUID(); + } return; } } diff --git a/src/game/chars/CCharNotoriety.cpp b/src/game/chars/CCharNotoriety.cpp index dc57a7f91..0c8364c51 100644 --- a/src/game/chars/CCharNotoriety.cpp +++ b/src/game/chars/CCharNotoriety.cpp @@ -470,7 +470,7 @@ void CChar::Noto_ChangeNewMsg( int iPrvLevel ) } } -void CChar::Noto_Fame( int iFameChange ) +void CChar::Noto_Fame( int iFameChange, CChar* pNPC ) { ADDTOCALLSTACK("CChar::Noto_Fame"); @@ -503,11 +503,11 @@ void CChar::Noto_Fame( int iFameChange ) //if ( ! iFameChange ) // return; - SetFame((ushort)(iFame + iFameChange)); + SetFame((ushort)(iFame + iFameChange), pNPC); Noto_ChangeDeltaMsg( (int)GetFame() - iFame, g_Cfg.GetDefaultMsg( DEFMSG_NOTO_FAME ) ); } -void CChar::Noto_Karma( int iKarmaChange, int iBottom, bool fMessage ) +void CChar::Noto_Karma( int iKarmaChange, int iBottom, bool fMessage, CChar* pNPC ) { ADDTOCALLSTACK("CChar::Noto_Karma"); @@ -541,7 +541,7 @@ void CChar::Noto_Karma( int iKarmaChange, int iBottom, bool fMessage ) //if ( ! iKarmaChange ) // return; - SetKarma((short)(iKarma + iKarmaChange)); + SetKarma((short)(iKarma + iKarmaChange), pNPC); Noto_ChangeDeltaMsg( (int)GetKarma() - iKarma, g_Cfg.GetDefaultMsg( DEFMSG_NOTO_KARMA ) ); NotoSave_Update(); if ( fMessage == true ) @@ -612,8 +612,9 @@ void CChar::Noto_Kill(CChar * pKill, int iTotalKillers) return; int iPrvLevel = Noto_GetLevel(); // store title before fame/karma changes to check if it got changed - Noto_Fame(g_Cfg.Calc_FameKill(pKill) / iTotalKillers); - Noto_Karma(g_Cfg.Calc_KarmaKill(pKill, NotoThem) / iTotalKillers); + + Noto_Fame(g_Cfg.Calc_FameKill(pKill) / iTotalKillers, pKill); + Noto_Karma(g_Cfg.Calc_KarmaKill(pKill, NotoThem) / iTotalKillers, INT32_MIN, false, pKill); if ( g_Cfg.m_bExperienceSystem && (g_Cfg.m_iExperienceMode & EXP_MODE_RAISE_COMBAT) ) { diff --git a/src/game/chars/CCharSkill.cpp b/src/game/chars/CCharSkill.cpp index f30e50da5..4fce933bb 100644 --- a/src/game/chars/CCharSkill.cpp +++ b/src/game/chars/CCharSkill.cpp @@ -4235,8 +4235,21 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) // (like ACTARG1/2 for magery, they are respectively m_atMagery.m_iSpell and m_atMagery.m_iSummonID and are set in // CClient::OnTarg_Skill_Magery, which then calls Skill_Start). // Skill_Cleanup(); + SOUND_TYPE sound = SOUND_NONE; + ANIM_TYPE anim = ANIM_WALK_UNARM; + if (!g_Cfg.IsSkillFlag(skill, SKF_NOSFX)) + { + sound = Skill_GetSound(skill); + } + if (!g_Cfg.IsSkillFlag(skill, SKF_NOANIM)) + { + anim = Skill_GetAnim(skill); + } + CScriptTriggerArgs pArgs; pArgs.m_iN1 = skill; + pArgs.m_VarsLocal.SetNumNew("Sound", sound); + pArgs.m_VarsLocal.SetNumNew("Anim", anim); // Some skill can start right away. Need no targetting. // 0-100 scale of Difficulty @@ -4306,7 +4319,6 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) pArgs.m_VarsLocal.SetNum("GatherStrokeCnt", m_atResource.m_dwStrokeCount); } - if ( IsTrigUsed(TRIGGER_SKILLSTART) ) { //If we are using a combat skill and m_Act_Difficult(actdiff) is < 0 combat will be blocked. @@ -4315,6 +4327,8 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) Skill_Cleanup(); return false; } + sound = (SOUND_TYPE)(pArgs.m_VarsLocal.GetKeyNum("Sound")); + anim = static_cast(pArgs.m_VarsLocal.GetKeyNum("Anim")); } if ( IsTrigUsed(TRIGGER_START) ) @@ -4325,6 +4339,8 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) Skill_Cleanup(); return false; } + sound = (SOUND_TYPE)(pArgs.m_VarsLocal.GetKeyNum("Sound")); + anim = static_cast(pArgs.m_VarsLocal.GetKeyNum("Anim")); } iWaitTime = (int)pArgs.m_iN2; if (IsSkillBase(skill) && iWaitTime > 0) @@ -4353,10 +4369,10 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) skActive = Skill_GetActive(); if ( !g_Cfg.IsSkillFlag(skActive, SKF_NOSFX) ) - Sound(Skill_GetSound(skActive)); + Sound(sound); if ( !g_Cfg.IsSkillFlag(skActive, SKF_NOANIM) ) - UpdateAnimate(Skill_GetAnim(skActive)); + UpdateAnimate(anim); } diff --git a/src/game/chars/CCharSpell.cpp b/src/game/chars/CCharSpell.cpp index 89e118435..1695997c4 100644 --- a/src/game/chars/CCharSpell.cpp +++ b/src/game/chars/CCharSpell.cpp @@ -2746,11 +2746,17 @@ bool CChar::Spell_Unequip( LAYER_TYPE layer ) return false; } //Allow to cast a spell when wielding a spellbook or wand (but not an item with the spellchanneling property) when MAGICF_CASTPARALYZED is enabled. - else if (IsSetMagicFlags(MAGICF_CASTPARALYZED) && ( pItemPrev->IsTypeSpellbook() || pItemPrev->IsType(IT_WAND) )) + else if (IsSetMagicFlags(MAGICF_CASTPARALYZED) && (pItemPrev->IsTypeSpellbook() || pItemPrev->IsType(IT_WAND) || pItemPrev->Can(CAN_I_EQUIPONCAST))) return true; + else if (IsSetMagicFlags(MAGICF_CASTPARALYZED) && IsStatFlag(STATF_FREEZE)) + { + //We need to inform player that he couldn't cast spell because of his hands frozen. + SysMessageDefault(DEFMSG_SPELL_TRY_FROZENHANDS); + return false; + } else if ( !CanMove( pItemPrev ) ) //If we are unable to do any action because of certain conditions(dead, paralyzed, stoned and so on) and wielding some item while MAGICF_CASTPARALYZED is disabled interrupt the cast. return false; - else if ( !pItemPrev->IsTypeSpellbook() && !pItemPrev->IsType(IT_WAND) && !pItemPrev->GetPropNum(COMP_PROPS_ITEMEQUIPPABLE, PROPIEQUIP_SPELLCHANNELING, true) && !ItemBounce( pItemPrev )) + else if ( !pItemPrev->IsTypeSpellbook() && !pItemPrev->IsType(IT_WAND) && !pItemPrev->Can(CAN_I_EQUIPONCAST) && !pItemPrev->GetPropNum(COMP_PROPS_ITEMEQUIPPABLE, PROPIEQUIP_SPELLCHANNELING, true) && !ItemBounce(pItemPrev)) { SysMessageDefault(DEFMSG_SPELL_TRY_BUSYHANDS); return false; @@ -3306,6 +3312,26 @@ int CChar::Spell_CastStart() if ( !pSpellDef ) return -1; + //We have to check player status and flags again before casting the spells. + //Otherwise, player On=@SpellSuccess/@Success triggered even spell failed. + //Even if preCast active, still need to check if player can successfully use spell. + if (pSpellDef->IsSpellType(SPELLFLAG_DISABLED)) //This one should never happen + return -1; + + if (m_pPlayer && !IsPriv(PRIV_GM)) + { + if (IsStatFlag(STATF_DEAD | STATF_SLEEPING | STATF_STONE) || Can(CAN_C_STATUE)) + { + SysMessageDefault(DEFMSG_SPELL_TRY_DEAD); + return -1; + } + else if (IsStatFlag(STATF_FREEZE) && !IsSetMagicFlags(MAGICF_CASTPARALYZED)) + { + SysMessageDefault(DEFMSG_SPELL_TRY_FROZENHANDS); + return -1; + } + } + if ( IsClientActive() && IsSetMagicFlags(MAGICF_PRECAST) && !pSpellDef->IsSpellType(SPELLFLAG_NOPRECAST) ) { m_Act_p = GetTopPoint(); @@ -3342,6 +3368,10 @@ int CChar::Spell_CastStart() fWOP = false; iDifficulty = 1; } + else if (pItem->Can(CAN_I_EQUIPONCAST)) //If the item has CAN_I_EQUIPONCAST flag don't need to unequip it. + { + fAllowEquip = true; + } else { // Scroll diff --git a/src/game/chars/CCharStat.cpp b/src/game/chars/CCharStat.cpp index 783c2de6d..5f0fee323 100644 --- a/src/game/chars/CCharStat.cpp +++ b/src/game/chars/CCharStat.cpp @@ -608,27 +608,36 @@ short CChar::GetKarma() const return (short)(maximum(g_Cfg.m_iMinKarma, minimum(g_Cfg.m_iMaxKarma, m_iKarma))); } -void CChar::SetKarma(short iNewKarma) +void CChar::SetKarma(short iNewKarma, CChar* pNPC) { + /* + Issue: 1118 + https://github.com/Sphereserver/Source-X/issues/1118 + + Wrong calculations breaks all the variables while saving. But the issues of this trigger are LOCAL.OLD and LOCAL.NEW + As LOCAL.NEW updating after KarmaChange triggered because ARGN1 is writeable, it's impossible to update it inside the trigger. + Best way to track that values is adding another trigger, that triggers after Karma value Changed. + + xwerswoodx + */ + + const short iOldKarma = GetKarma(); + short iKarmaChange = iNewKarma - iOldKarma; + if (IsTrigUsed(TRIGGER_KARMACHANGE)) { - const int iOldKarma = GetKarma(); - - CScriptTriggerArgs Args; - Args.m_iN1 = iNewKarma; - Args.m_VarsLocal.SetNum("Old", iOldKarma); - Args.m_VarsLocal.SetNum("New", iNewKarma + iOldKarma); - TRIGRET_TYPE retType = OnTrigger(CTRIG_KarmaChange, this, &Args); - + CScriptTriggerArgs Args(iKarmaChange, iOldKarma); + Args.m_pO1 = pNPC; + TRIGRET_TYPE retType = OnTrigger(CTRIG_KarmaChange, this, &Args); if (retType == TRIGRET_RET_TRUE) return; - - iNewKarma = (ushort)(minimum(g_Cfg.m_iMaxKarma, Args.m_iN1)); + iKarmaChange = (short)Args.m_iN1; + iNewKarma = (short)(maximum(g_Cfg.m_iMinKarma, minimum(g_Cfg.m_iMaxKarma, iOldKarma + iKarmaChange))); } m_iKarma = (short)(maximum(g_Cfg.m_iMinKarma, minimum(g_Cfg.m_iMaxKarma, iNewKarma))); - + if ( !g_Serv.IsLoading() ) NotoSave_Update(); } @@ -638,26 +647,34 @@ ushort CChar::GetFame() const return (ushort)(minimum(g_Cfg.m_iMaxFame, m_uiFame)); } -void CChar::SetFame(ushort uiNewFame) +void CChar::SetFame(ushort uiNewFame, CChar* pNPC) { - + /* + Issue: 1118 + https://github.com/Sphereserver/Source-X/issues/1118 + + Wrong calculations breaks all the variables while saving. But the issues of this trigger are LOCAL.OLD and LOCAL.NEW. + As LOCAL.NEW updating after FameChange triggered because of ARGN1 is writeable, it's impossible to update it inside the trigger. + Best way to track that values is adding another trigger, that triggers after Fame value Changed. + + xwerswoodx + */ + + const short iOldFame = GetFame(); + short iFameChange = uiNewFame - iOldFame; + if (IsTrigUsed(TRIGGER_FAMECHANGE)) { - const int iOldFame = GetFame(); - - CScriptTriggerArgs Args; - Args.m_iN1 = uiNewFame; - Args.m_VarsLocal.SetNum("Old", iOldFame); - Args.m_VarsLocal.SetNum("New", uiNewFame + iOldFame); - TRIGRET_TYPE retType = OnTrigger(CTRIG_FameChange, this, &Args); - - if ( retType == TRIGRET_RET_TRUE ) + CScriptTriggerArgs Args(iFameChange, iOldFame); + Args.m_pO1 = pNPC; + TRIGRET_TYPE retType = OnTrigger(CTRIG_FameChange, this, &Args); + if (retType == TRIGRET_RET_TRUE) return; - - uiNewFame = (ushort)(minimum(g_Cfg.m_iMaxFame, Args.m_iN1)); + iFameChange = (short)Args.m_iN1; + uiNewFame = (short)(maximum(0, minimum(g_Cfg.m_iMaxFame, iOldFame + iFameChange))); } - m_uiFame = (ushort)(minimum(g_Cfg.m_iMaxFame, uiNewFame)); + m_uiFame = (short)(maximum(0, minimum(g_Cfg.m_iMaxFame, uiNewFame))); } bool CChar::Stat_Decrease(STAT_TYPE stat, SKILL_TYPE skill) @@ -738,4 +755,4 @@ void CChar::Stat_StrCheckEquip() ItemBounce(pItem, false); } } -} \ No newline at end of file +} diff --git a/src/game/clients/CParty.cpp b/src/game/clients/CParty.cpp index 82747dd4e..259548964 100644 --- a/src/game/clients/CParty.cpp +++ b/src/game/clients/CParty.cpp @@ -453,6 +453,13 @@ bool CPartyDef::AcceptEvent( CChar *pCharAccept, CUID uidInviter, bool bForced, else return false; } + + if (IsTrigUsed(TRIGGER_PARTYADD)) + { + CScriptTriggerArgs Args; + if ( pCharAccept->OnTrigger(CTRIG_PartyAdd, pCharInviter, &Args) == TRIGRET_RET_TRUE ) + return false; + } tchar *pszMsg = Str_GetTemp(); snprintf(pszMsg, STR_TEMPLENGTH, g_Cfg.GetDefaultMsg(DEFMSG_PARTY_JOINED), pCharAccept->GetName()); diff --git a/src/game/components/CCFaction.cpp b/src/game/components/CCFaction.cpp index f1b25eaef..324d04875 100644 --- a/src/game/components/CCFaction.cpp +++ b/src/game/components/CCFaction.cpp @@ -245,7 +245,7 @@ void CCFaction::r_Write(CScript & s) { ADDTOCALLSTACK("CCFaction::r_Write"); if (GetFactionID() != FACTION_NONE){ - s.WriteKeyHex("FACTION", (llong)GetFactionID()); // Same value stored with different names for CChars and CItems. + s.WriteKeyHex("FACTION", GetFactionID()); // Same value stored with different names for CChars and CItems. } } diff --git a/src/game/components/CCSpawn.cpp b/src/game/components/CCSpawn.cpp index 5f61e8cec..89addc103 100644 --- a/src/game/components/CCSpawn.cpp +++ b/src/game/components/CCSpawn.cpp @@ -418,7 +418,7 @@ CChar* CCSpawn::GenerateChar(CResourceIDBase rid) if (pChar->GetTopPoint().IsValidPoint() == false)// Try to place it only if the @Spawn trigger didn't set it a valid P. { ushort iPlacingTries = 0; - while (!pChar->MoveNear(pt, _iMaxDist ? (word)(Calc_GetRandVal(_iMaxDist) + 1) : 1) || pChar->IsStuck(false)) + while (!pChar->MoveNear(pt, _iMaxDist ? (word)(Calc_GetRandVal(_iMaxDist) + 1) : 1) || pChar->IsStuck(false) || !pChar->CanSeeLOS(pt)) //Character shouldn't spawn where can't see it's spawn point. { ++iPlacingTries; if (iPlacingTries <= 3) diff --git a/src/game/items/CItem.cpp b/src/game/items/CItem.cpp index a8502419d..32100e392 100644 --- a/src/game/items/CItem.cpp +++ b/src/game/items/CItem.cpp @@ -166,7 +166,7 @@ CItem::CItem( ITEMID_TYPE id, CItemBase * pItemDef ) : } if (CCFaction::CanSubscribe(this)) { - SubscribeComponent(new CCFaction()); // Adding it only to equippable items + SubscribeComponent(new CCFaction(pItemDef->GetFaction())); // Adding it only to equippable items } TrySubscribeComponentProps(); @@ -2247,12 +2247,6 @@ void CItem::r_WriteMore1(CSString & sVal) ADDTOCALLSTACK("CItem::r_WriteMore1"); // do special processing to represent this. - if (IsTypeSpellbook()) - { - sVal.FormatHex(m_itNormal.m_more1); - return; - } - switch (GetType()) { case IT_TREE: @@ -2304,12 +2298,6 @@ void CItem::r_WriteMore2( CSString & sVal ) ADDTOCALLSTACK_INTENSIVE("CItem::r_WriteMore2"); // do special processing to represent this. - if (IsTypeSpellbook()) - { - sVal.FormatHex(m_itNormal.m_more2); - return; - } - switch ( GetType()) { case IT_FRUIT: diff --git a/src/network/receive.cpp b/src/network/receive.cpp index 41f53f681..0dc85b3de 100644 --- a/src/network/receive.cpp +++ b/src/network/receive.cpp @@ -203,7 +203,7 @@ bool PacketCreate::doCreate(CNetState* net, lpctstr charname, bool fFemale, RACE createArgs.m_s1 = account->GetName(); createArgs.m_pO1 = client; - client->r_Call("f_onchar_create_init", nullptr, &createArgs, nullptr, &tr); + client->r_Call("f_onchar_create_init", &g_Serv, &createArgs, nullptr, &tr); if (tr == TRIGRET_RET_TRUE) goto block_creation; diff --git a/src/sphere.ini b/src/sphere.ini index 0c0ddca60..cd2ddad15 100644 --- a/src/sphere.ini +++ b/src/sphere.ini @@ -373,18 +373,19 @@ SuppressCapitals=0 // COMBAT_ELEMENTAL_ENGINE 00008 // Use DAM*/RES* to split damage/resist into Physical/Fire/Cold/Poison/Energy (AOS) instead use old AR (pre-AOS) // COMBAT_DCLICKSELF_UNMOUNTS 00020 // Unmount horse when dclicking self while in warmode // COMBAT_ALLOWHITFROMSHIP 00040 // Allow attacking opponents from ships -// COMBAT_NOPETDESERT 00080 // Allow pet owner attack own pet without make it desert its owner +// COMBAT_NOPETDESERT 00080 // Allow pet owner attack own pet without make it desert its owner // COMBAT_ARCHERYCANMOVE 00100 // Allow firing bow while moving // COMBAT_STAYINRANGE 00200 // Abort attack swing when out of range instead of waiting to come back in range // COMBAT_STACKARMOR 01000 // If a region is covered by more than one armor part, all AR will count // COMBAT_NOPOISONHIT 02000 // Disables old (55i like) poisoning style (0~100% chance based on Poisoning skill for monsters, or 50% chance for poisoned weapons) -// COMBAT_SLAYER 04000 // Enables Slayer damage PVM combat -// COMBAT_SWING_NORANGE 08000 // The hit can be started at any distance and regardless of the Line of Sight, then the damage will be dealt when in range and LoS -// // WARNING: This flag is ignored if PREHIT is on! -// COMBAT_ANIM_HIT_SMOOTH 010000 // The hit animation has the same duration as the swing delay, instead of having a fixed fast duration and being idle until the delay has expired. -// // WARNING: doesn't work with Gargoyles due to the new animation packet not accepting a custom animation duration! -// COMBAT_FIRSTHIT_INSTANT 020000 // The first hit in a fight doesn't wait for the recoil time (OSI like) -// COMBAT_NPC_BONUSDAMAGE 040000 // Npc get bonus damage from various sources such as CombatBonus, CombatBonusPercent, IncreaseDam, Anatomy, Tactics etc. See revision from 12-11-2019 for more details. +// COMBAT_SLAYER 04000 // Enables Slayer damage PVM combat +// COMBAT_SWING_NORANGE 08000 // The hit can be started at any distance and regardless of the Line of Sight, then the damage will be dealt when in range and LoS +// // WARNING: This flag is ignored if PREHIT is on! +// COMBAT_ANIM_HIT_SMOOTH 010000 // The hit animation has the same duration as the swing delay, instead of having a fixed fast duration and being idle until the delay has expired. +// // WARNING: doesn't work with Gargoyles due to the new animation packet not accepting a custom animation duration! +// COMBAT_FIRSTHIT_INSTANT 020000 // The first hit in a fight doesn't wait for the recoil time (OSI like) +// COMBAT_NPC_BONUSDAMAGE 040000 // Npc get bonus damage from various sources such as CombatBonus, CombatBonusPercent, IncreaseDam, Anatomy, Tactics etc. See revision from 12-11-2019 for more details. +// COMBAT_PARALYZE_CANSWING 080000 // Characters can continue attacking while paralyzed. (Old sphere behaviour) //CombatFlags=0 // If COMBAT_ARCHERYCANMOVE is not enabled, wait this much tenth of seconds (minimum=0) after the player diff --git a/src/sphereCrypt.ini b/src/sphereCrypt.ini index c859a7791..4d214b1f1 100644 --- a/src/sphereCrypt.ini +++ b/src/sphereCrypt.ini @@ -24,6 +24,7 @@ ENC_LOGIN 4 // Rotation cipher used for the Login Server crypt by every client, [SPHERECRYPT] //Classic Clients +7009900 03A7731CD 0A9CE5E7F ENC_TFISH // 7.0.99 7009800 03AA8ABDD 0A9AB227F ENC_TFISH // 7.0.98 7009700 03AE221ED 0A9F47E7F ENC_TFISH // 7.0.97 7009600 03ADBA3FD 0A9E1527F ENC_TFISH // 7.0.96 diff --git a/src/tables/triggers.tbl b/src/tables/triggers.tbl index 9292a1653..9d266e0b9 100644 --- a/src/tables/triggers.tbl +++ b/src/tables/triggers.tbl @@ -140,6 +140,7 @@ ADD(NPCRESTOCK) ADD(NPCSEENEWPLAYER) ADD(NPCSEEWANTITEM) ADD(NPCSPECIALACTION) +ADD(PARTYADD) ADD(PARTYDISBAND) ADD(PARTYINVITE) ADD(PARTYLEAVE)