From 6ebfe2f5803cb95bc8f2710892548a60ebe6f63d Mon Sep 17 00:00:00 2001 From: thenecromancer Date: Wed, 20 May 2009 22:39:52 +0200 Subject: Correct behavior of Add Extra Attact spelleffect. Correctly send attack gain in spell log. Attack right after processing effect Ignore effect if target not in melee range/angle --HG-- branch : trunk --- src/game/Spell.cpp | 6 +++++- src/game/SpellEffects.cpp | 13 +++++++++++++ src/game/Unit.cpp | 24 ------------------------ 3 files changed, 18 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 49b957c05bf..9c4681d2f1c 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -2728,6 +2728,10 @@ void Spell::finish(bool ok) if (m_caster->GetTypeId() == TYPEID_PLAYER) ((Player*)m_caster)->RemoveSpellMods(this); + // Okay to remove extra attacks + if(IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_ADD_EXTRA_ATTACKS)) + m_caster->m_extraAttacks = 0; + // Heal caster for all health leech from all targets if (m_healthLeech) { @@ -2989,7 +2993,7 @@ void Spell::SendLogExecute() data.append(unit->GetPackGUID()); else data << uint8(0); - data << uint32(0); // count? + data << uint32(m_caster->m_extraAttacks); break; case SPELL_EFFECT_INTERRUPT_CAST: if(Unit *unit = m_targets.getUnitTarget()) diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp index a1cf876fc6c..89b92ccdc61 100644 --- a/src/game/SpellEffects.cpp +++ b/src/game/SpellEffects.cpp @@ -5441,7 +5441,20 @@ void Spell::EffectAddExtraAttacks(uint32 /*i*/) if( unitTarget->m_extraAttacks ) return; + Unit *victim = unitTarget->getVictim(); + + // attack prevented + // fixme, some attacks may not target current victim, this is right now not handled + if (!victim || !unitTarget->IsWithinMeleeRange(victim) || !unitTarget->HasInArc( 2*M_PI/3, victim )) + return; + + // Only for proc/log informations unitTarget->m_extraAttacks = damage; + // Need to send log before attack is made + SendLogExecute(); + m_needSpellLog = false; + + unitTarget->AttackerStateUpdate(victim, BASE_ATTACK, true); } void Spell::EffectParry(uint32 /*i*/) diff --git a/src/game/Unit.cpp b/src/game/Unit.cpp index ceade1af235..38e2519fe47 100644 --- a/src/game/Unit.cpp +++ b/src/game/Unit.cpp @@ -2378,24 +2378,10 @@ void Unit::AttackerStateUpdate (Unit *pVictim, WeaponAttackType attType, bool ex else return; // ignore ranged case - uint32 extraAttacks = m_extraAttacks; - // melee attack spell casted at main hand attack only if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL]) { m_currentSpells[CURRENT_MELEE_SPELL]->cast(); - - // not recent extra attack only at any non extra attack (melee spell case) - if(!extra && extraAttacks) - { - while(m_extraAttacks) - { - AttackerStateUpdate(pVictim, BASE_ATTACK, true); - if(m_extraAttacks > 0) - --m_extraAttacks; - } - } - return; } @@ -2413,16 +2399,6 @@ void Unit::AttackerStateUpdate (Unit *pVictim, WeaponAttackType attType, bool ex DEBUG_LOG("AttackerStateUpdate: (NPC) %u attacked %u (TypeId: %u) for %u dmg, absorbed %u, blocked %u, resisted %u.", GetGUIDLow(), pVictim->GetGUIDLow(), pVictim->GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist); - // extra attack only at any non extra attack (normal case) - if(!extra && extraAttacks) - { - while(m_extraAttacks) - { - AttackerStateUpdate(pVictim, BASE_ATTACK, true); - if(m_extraAttacks > 0) - --m_extraAttacks; - } - } } /* -- cgit v1.2.3 From d7740fcd178c0125d8917009034d42c8c5ae2517 Mon Sep 17 00:00:00 2001 From: thenecromancer Date: Wed, 20 May 2009 22:59:22 +0200 Subject: Correctly interpret SPELL_AURA_MOD_COMBAT_CHANCE DBC values as % modificators instead of flat values. --HG-- branch : trunk --- src/game/Unit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/game/Unit.cpp b/src/game/Unit.cpp index 38e2519fe47..37cc737f81c 100644 --- a/src/game/Unit.cpp +++ b/src/game/Unit.cpp @@ -2534,7 +2534,7 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst (const Unit *pVictim, WeaponAttack dodge_chance -= int32(((Player*)this)->GetExpertiseDodgeOrParryReduction(attType)*100); // Modify dodge chance by attacker SPELL_AURA_MOD_COMBAT_RESULT_CHANCE - dodge_chance+= GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE); + dodge_chance+= GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE)*100; tmp = dodge_chance; if ( (tmp > 0) // check if unit _can_ dodge @@ -2838,7 +2838,7 @@ SpellMissInfo Unit::MeleeSpellHitResult(Unit *pVictim, SpellEntry const *spell) // Roll dodge int32 dodgeChance = int32(pVictim->GetUnitDodgeChance()*100.0f) - skillDiff * 4; // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE - dodgeChance+= GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE); + dodgeChance+= GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE)*100; // Reduce dodge chance by attacker expertise rating if (GetTypeId() == TYPEID_PLAYER) -- cgit v1.2.3 From 2f27bde026d3b514d696b17fd415c9218f5496d7 Mon Sep 17 00:00:00 2001 From: tvaroh Date: Mon, 18 May 2009 00:11:39 +0200 Subject: Clear afk before joining bg --HG-- branch : trunk --- src/game/BattleGroundHandler.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/game/BattleGroundHandler.cpp b/src/game/BattleGroundHandler.cpp index b1522c939f7..1cb0084a1d1 100644 --- a/src/game/BattleGroundHandler.cpp +++ b/src/game/BattleGroundHandler.cpp @@ -454,6 +454,9 @@ void WorldSession::HandleBattleGroundPlayerPortOpcode( WorldPacket &recv_data ) _player->SetBattleGroundId(bg->GetInstanceID()); // set the destination team _player->SetBGTeam(team); + // clear AFK + if(_player->isAFK()) + _player->ToggleAFK(); // bg->HandleBeforeTeleportToBattleGround(_player); sBattleGroundMgr.SendToBattleGround(_player, instanceId); // add only in HandleMoveWorldPortAck() -- cgit v1.2.3 From b0a48f60c92291c80ff40379316787e77f46321d Mon Sep 17 00:00:00 2001 From: raczman Date: Thu, 21 May 2009 23:17:52 +0200 Subject: Added new config option: vmap.petLOS Enabling it makes pets do LOS check before attacking its target. --HG-- branch : trunk --- src/game/PetHandler.cpp | 9 +++++++-- src/game/World.cpp | 2 +- src/game/World.h | 2 +- src/trinitycore/trinitycore.conf.dist | 8 +++++++- 4 files changed, 16 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/game/PetHandler.cpp b/src/game/PetHandler.cpp index a3644371f47..350c390d86c 100644 --- a/src/game/PetHandler.cpp +++ b/src/game/PetHandler.cpp @@ -115,9 +115,14 @@ void WorldSession::HandlePetAction( WorldPacket & recv_data ) return; // Not let attack through obstructions - //if(!pet->IsWithinLOSInMap(TargetUnit)) - // return; + if(sWorld.getConfig(CONFIG_PET_LOS)) + { + + if(!pet->IsWithinLOSInMap(TargetUnit)) + return; + } + pet->clearUnitState(UNIT_STAT_FOLLOW); if(pet->GetTypeId() != TYPEID_PLAYER && ((Creature*)pet)->IsAIEnabled) diff --git a/src/game/World.cpp b/src/game/World.cpp index d1f5490ba55..c0f815e8888 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -1009,7 +1009,7 @@ void World::LoadConfigSettings(bool reload) m_configs[CONFIG_MAX_WHO] = sConfig.GetIntDefault("MaxWhoListReturns", 49); - + m_configs[CONFIG_PET_LOS] = sConfig.GetBoolDefault("vmap.petLOS", false); m_configs[CONFIG_PREMATURE_BG_REWARD] = sConfig.GetBoolDefault("Battleground.PrematureReward", true); m_configs[CONFIG_BG_START_MUSIC] = sConfig.GetBoolDefault("MusicInBattleground", false); m_configs[CONFIG_START_ALL_SPELLS] = sConfig.GetBoolDefault("PlayerStart.AllSpells", false); diff --git a/src/game/World.h b/src/game/World.h index 513d8894640..32af277c8f5 100644 --- a/src/game/World.h +++ b/src/game/World.h @@ -207,7 +207,7 @@ enum WorldConfigs CONFIG_MIN_LOG_UPDATE, CONFIG_ENABLE_SINFO_LOGIN, CONFIG_PREMATURE_BG_REWARD, - + CONFIG_PET_LOS, CONFIG_VALUE_COUNT }; diff --git a/src/trinitycore/trinitycore.conf.dist b/src/trinitycore/trinitycore.conf.dist index d7b892320d9..ce290c796b9 100644 --- a/src/trinitycore/trinitycore.conf.dist +++ b/src/trinitycore/trinitycore.conf.dist @@ -146,7 +146,12 @@ EAIErrorLevel = 2 # vmap.ignoreSpellIds # These spells are ignored for LoS calculation # List of ids with delimiter ',' -# +# +# vmap.petLOS +# Check LOS for pets, to avoid them going through walls etc. +# Default: 0 (disable, less CPU usage) +# 1 (enable, each pet attack command will check for LOS) +# # DetectPosCollision # Check final move position, summon position, etc for visible collision with other objects or # wall (wall only if vmaps are enabled) @@ -193,6 +198,7 @@ vmap.enableLOS = 0 vmap.enableHeight = 0 vmap.ignoreMapIds = "369" vmap.ignoreSpellIds = "7720" +vmap.petLOS = 0 DetectPosCollision = 1 TargetPosRecalculateRange = 1.5 UpdateUptimeInterval = 10 -- cgit v1.2.3 From f1ea5714f24ea0c3e51a6c37e0ce0bec577804b6 Mon Sep 17 00:00:00 2001 From: raczman Date: Fri, 22 May 2009 13:23:07 +0200 Subject: Implemented carrier debuffs in WSG battleground After 10 and 15 minutes players carrying flags will receive debuffs, if both flags are kept. In case of dropping flag buff is removed. When Flag is picked up by another player, he gets debuff. Please test and report bugs. --HG-- branch : trunk --- src/game/BattleGroundWS.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++ src/game/BattleGroundWS.h | 10 ++++++- 2 files changed, 74 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/game/BattleGroundWS.cpp b/src/game/BattleGroundWS.cpp index 3a8c5668a7a..5462a3de57a 100644 --- a/src/game/BattleGroundWS.cpp +++ b/src/game/BattleGroundWS.cpp @@ -152,6 +152,7 @@ void BattleGroundWS::Update(time_t diff) { m_FlagsDropTimer[BG_TEAM_ALLIANCE] = 0; RespawnFlagAfterDrop(ALLIANCE); + m_BothFlagsKept = false; } } if(m_FlagState[BG_TEAM_HORDE] == BG_WS_FLAG_STATE_WAIT_RESPAWN) @@ -172,8 +173,40 @@ void BattleGroundWS::Update(time_t diff) { m_FlagsDropTimer[BG_TEAM_HORDE] = 0; RespawnFlagAfterDrop(HORDE); + m_BothFlagsKept = false; } } + if(m_BothFlagsKept) + { + m_FlagSpellForceTimer += diff; + if(m_FlagDebuffState == 0 && m_FlagSpellForceTimer >= 600000) //10 minutes + { + if(Player * plr = objmgr.GetPlayer(m_FlagKeepers[0])) + plr->CastSpell(plr,WS_SPELL_FOCUSED_ASSAULT,true); + if(Player * plr = objmgr.GetPlayer(m_FlagKeepers[1])) + plr->CastSpell(plr,WS_SPELL_FOCUSED_ASSAULT,true); + m_FlagDebuffState = 1; + } + else if(m_FlagDebuffState == 1 && m_FlagSpellForceTimer >= 900000) //15 minutes + { + if(Player * plr = objmgr.GetPlayer(m_FlagKeepers[0])) + { + plr->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + plr->CastSpell(plr,WS_SPELL_BRUTAL_ASSAULT,true); + } + if(Player * plr = objmgr.GetPlayer(m_FlagKeepers[1])) + { + plr->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + plr->CastSpell(plr,WS_SPELL_BRUTAL_ASSAULT,true); + } + m_FlagDebuffState = 2; + } + } + else + { + m_FlagSpellForceTimer = 0; //reset timer. + m_FlagDebuffState = 0; + } } } @@ -207,6 +240,7 @@ void BattleGroundWS::RespawnFlag(uint32 Team, bool captured) SendMessageToAll(GetTrinityString(LANG_BG_WS_F_PLACED)); PlaySoundToAll(BG_WS_SOUND_FLAGS_RESPAWNED); // flag respawned sound... } + m_BothFlagsKept = false; } void BattleGroundWS::RespawnFlagAfterDrop(uint32 team) @@ -235,6 +269,7 @@ void BattleGroundWS::RespawnFlagAfterDrop(uint32 team) sLog.outError("unknown droped flag bg, guid: %u",GUID_LOPART(GetDroppedFlagGUID(team))); SetDroppedFlagGUID(0,team); + m_BothFlagsKept = false; } void BattleGroundWS::EventPlayerCapturedFlag(Player *Source) @@ -258,6 +293,10 @@ void BattleGroundWS::EventPlayerCapturedFlag(Player *Source) m_FlagState[BG_TEAM_HORDE] = BG_WS_FLAG_STATE_WAIT_RESPAWN; // Drop Horde Flag from Player Source->RemoveAurasDueToSpell(BG_WS_SPELL_WARSONG_FLAG); + if(m_FlagDebuffState == 1) + Source->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + if(m_FlagDebuffState == 2) + Source->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); message = GetTrinityString(LANG_BG_WS_CAPTURED_HF); type = CHAT_MSG_BG_SYSTEM_ALLIANCE; if(GetTeamScore(ALLIANCE) < BG_WS_MAX_TEAM_SCORE) @@ -275,6 +314,10 @@ void BattleGroundWS::EventPlayerCapturedFlag(Player *Source) m_FlagState[BG_TEAM_ALLIANCE] = BG_WS_FLAG_STATE_WAIT_RESPAWN; // Drop Alliance Flag from Player Source->RemoveAurasDueToSpell(BG_WS_SPELL_SILVERWING_FLAG); + if(m_FlagDebuffState == 1) + Source->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + if(m_FlagDebuffState == 2) + Source->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); message = GetTrinityString(LANG_BG_WS_CAPTURED_AF); type = CHAT_MSG_BG_SYSTEM_HORDE; if(GetTeamScore(HORDE) < BG_WS_MAX_TEAM_SCORE) @@ -359,6 +402,10 @@ void BattleGroundWS::EventPlayerDroppedFlag(Player *Source) { SetHordeFlagPicker(0); Source->RemoveAurasDueToSpell(BG_WS_SPELL_WARSONG_FLAG); + if(m_FlagDebuffState == 1) + Source->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + if(m_FlagDebuffState == 2) + Source->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); m_FlagState[BG_TEAM_HORDE] = BG_WS_FLAG_STATE_ON_GROUND; message = GetTrinityString(LANG_BG_WS_DROPPED_HF); type = CHAT_MSG_BG_SYSTEM_HORDE; @@ -374,6 +421,10 @@ void BattleGroundWS::EventPlayerDroppedFlag(Player *Source) { SetAllianceFlagPicker(0); Source->RemoveAurasDueToSpell(BG_WS_SPELL_SILVERWING_FLAG); + if(m_FlagDebuffState == 1) + Source->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + if(m_FlagDebuffState == 2) + Source->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); m_FlagState[BG_TEAM_ALLIANCE] = BG_WS_FLAG_STATE_ON_GROUND; message = GetTrinityString(LANG_BG_WS_DROPPED_AF); type = CHAT_MSG_BG_SYSTEM_ALLIANCE; @@ -422,6 +473,8 @@ void BattleGroundWS::EventPlayerClickedOnFlag(Player *Source, GameObject* target UpdateFlagState(HORDE, BG_WS_FLAG_STATE_ON_PLAYER); UpdateWorldState(BG_WS_FLAG_UNK_ALLIANCE, 1); Source->CastSpell(Source, BG_WS_SPELL_SILVERWING_FLAG, true); + if(m_FlagState[1] == BG_WS_FLAG_STATE_ON_PLAYER) + m_BothFlagsKept = true; } //horde flag picked up from base @@ -438,6 +491,8 @@ void BattleGroundWS::EventPlayerClickedOnFlag(Player *Source, GameObject* target UpdateFlagState(ALLIANCE, BG_WS_FLAG_STATE_ON_PLAYER); UpdateWorldState(BG_WS_FLAG_UNK_HORDE, 1); Source->CastSpell(Source, BG_WS_SPELL_WARSONG_FLAG, true); + if(m_FlagState[0] == BG_WS_FLAG_STATE_ON_PLAYER) + m_BothFlagsKept = true; } //Alliance flag on ground(not in base) (returned or picked up again from ground!) @@ -452,6 +507,7 @@ void BattleGroundWS::EventPlayerClickedOnFlag(Player *Source, GameObject* target SpawnBGObject(BG_WS_OBJECT_A_FLAG, RESPAWN_IMMEDIATELY); PlaySoundToAll(BG_WS_SOUND_FLAG_RETURNED); UpdatePlayerScore(Source, SCORE_FLAG_RETURNS, 1); + m_BothFlagsKept = false; } else { @@ -463,6 +519,10 @@ void BattleGroundWS::EventPlayerClickedOnFlag(Player *Source, GameObject* target Source->CastSpell(Source, BG_WS_SPELL_SILVERWING_FLAG, true); m_FlagState[BG_TEAM_ALLIANCE] = BG_WS_FLAG_STATE_ON_PLAYER; UpdateFlagState(HORDE, BG_WS_FLAG_STATE_ON_PLAYER); + if(m_FlagDebuffState == 1) + Source->CastSpell(Source,WS_SPELL_FOCUSED_ASSAULT,true); + if(m_FlagDebuffState == 2) + Source->CastSpell(Source,WS_SPELL_BRUTAL_ASSAULT,true); UpdateWorldState(BG_WS_FLAG_UNK_ALLIANCE, 1); } //called in HandleGameObjectUseOpcode: @@ -481,6 +541,7 @@ void BattleGroundWS::EventPlayerClickedOnFlag(Player *Source, GameObject* target SpawnBGObject(BG_WS_OBJECT_H_FLAG, RESPAWN_IMMEDIATELY); PlaySoundToAll(BG_WS_SOUND_FLAG_RETURNED); UpdatePlayerScore(Source, SCORE_FLAG_RETURNS, 1); + m_BothFlagsKept = false; } else { @@ -492,6 +553,10 @@ void BattleGroundWS::EventPlayerClickedOnFlag(Player *Source, GameObject* target Source->CastSpell(Source, BG_WS_SPELL_WARSONG_FLAG, true); m_FlagState[BG_TEAM_HORDE] = BG_WS_FLAG_STATE_ON_PLAYER; UpdateFlagState(ALLIANCE, BG_WS_FLAG_STATE_ON_PLAYER); + if(m_FlagDebuffState == 1) + Source->CastSpell(Source,WS_SPELL_FOCUSED_ASSAULT,true); + if(m_FlagDebuffState == 2) + Source->CastSpell(Source,WS_SPELL_BRUTAL_ASSAULT,true); UpdateWorldState(BG_WS_FLAG_UNK_HORDE, 1); } //called in HandleGameObjectUseOpcode: diff --git a/src/game/BattleGroundWS.h b/src/game/BattleGroundWS.h index 4e57826cd30..837414fe6d2 100644 --- a/src/game/BattleGroundWS.h +++ b/src/game/BattleGroundWS.h @@ -128,6 +128,12 @@ enum BG_WS_CreatureTypes BG_CREATURES_MAX_WS = 2 }; +enum BG_WS_CarrierDebuffs +{ + WS_SPELL_FOCUSED_ASSAULT = 46392, + WS_SPELL_BRUTAL_ASSAULT = 46393 +}; + class BattleGroundWGScore : public BattleGroundScore { public: @@ -196,9 +202,11 @@ class BattleGroundWS : public BattleGround uint32 m_TeamScores[2]; int32 m_FlagsTimer[2]; int32 m_FlagsDropTimer[2]; - + int32 m_FlagSpellForceTimer; int32 m_FlagSpellBrutalTimer; + bool m_BothFlagsKept; + uint8 m_FlagDebuffState; // 0 - no debuffs, 1 - focused assault, 2 - brutal assault }; #endif -- cgit v1.2.3 From fcbcf595113bb9b2c256fd5652adafa79ba89b78 Mon Sep 17 00:00:00 2001 From: Anubisss Date: Fri, 22 May 2009 20:58:07 +0200 Subject: *Fix a typo. This fix the bug that FireBarrier dont open after kill Felmyst by hunteee. Thank you. --HG-- branch : trunk --- .../scripts/scripts/zone/sunwell_plateau/instance_sunwell_plateau.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/bindings/scripts/scripts/zone/sunwell_plateau/instance_sunwell_plateau.cpp b/src/bindings/scripts/scripts/zone/sunwell_plateau/instance_sunwell_plateau.cpp index 283f5e5954a..ae58ccfd2c1 100644 --- a/src/bindings/scripts/scripts/zone/sunwell_plateau/instance_sunwell_plateau.cpp +++ b/src/bindings/scripts/scripts/zone/sunwell_plateau/instance_sunwell_plateau.cpp @@ -216,7 +216,7 @@ struct TRINITY_DLL_DECL instance_sunwell_plateau : public ScriptedInstance case DATA_BRUTALLUS_EVENT: Encounters[1] = data; break; case DATA_FELMYST_EVENT: if(data == DONE) - HandleGameObject(FireBarrier, 1); + HandleGameObject(FireBarrier, OPEN); Encounters[2] = data; break; case DATA_EREDAR_TWINS_EVENT: Encounters[3] = data; break; case DATA_MURU_EVENT: -- cgit v1.2.3