Xudojnik 904 Report post Posted September 9, 2014 (edited) Главный вопрос: почему у игроков можно блочить школу магии, а у мобов и петов - нет? Edited September 19, 2014 by Xudojnik 0 Share this post Link to post Share on other sites
Motorbreath 923 Report post Posted September 9, 2014 некому прописать в скрипте юзания абилок проверку на наличие соответствующих аур 0 Share this post Link to post Share on other sites
Xudojnik 904 Report post Posted September 9, 2014 некому прописать в скрипте юзания абилок проверку на наличие соответствующих аурЧерез час курения мангоса я пришел к выводу, что таких аур нет, а игрокам просто накидывают кулдауны.Ни в одном методе каста нет явной проверки интерраптов. 0 Share this post Link to post Share on other sites
the Exile 245 Report post Posted September 9, 2014 Уже не раз писалось, что для того, чтобы реализовать данную фичу надо переписать всю систему применения магии в ядре, что трудоёмко, не всем под силу, и, возможно, не так приоритетно. 0 Share this post Link to post Share on other sites
Xudojnik 904 Report post Posted September 10, 2014 (edited) Еще через час трассировки мангоса я нашел место, где обрабатывается запрет на школу магии. Тут меня ждал сюрприз. void Spell::EffectInterruptCast(SpellEffectIndex /*eff_idx*/){ if (!unitTarget) { return; } if (!unitTarget->IsAlive()) { return; } // TODO: not all spells that used this effect apply cooldown at school spells // also exist case: apply cooldown to interrupted cast only and to all spells for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; ++i) { if (Spell* spell = unitTarget->GetCurrentSpell(CurrentSpellTypes(i))) { SpellEntry const* curSpellInfo = spell->m_spellInfo; // check if we can interrupt spell if ((curSpellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_INTERRUPT) && curSpellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE) { unitTarget->ProhibitSpellSchool(GetSpellSchoolMask(curSpellInfo), GetSpellDuration(m_spellInfo)); unitTarget->InterruptSpell(CurrentSpellTypes(i), false); } } }} Вызывает метод virtual void ProhibitSpellSchool(SpellSchoolMask /*idSchoolMask*/, uint32 /*unTimeMs*/) { } С одной стороны все ок. Виртуальный метод может переопределиться в наследнике. Однако этот метод переопределен только у игроков. Мобы используют именно этот пустой метод. Так что я считаю, что на механику блока школы просто положили болт. Edited September 10, 2014 by Xudojnik 0 Share this post Link to post Share on other sites
Motorbreath 923 Report post Posted September 11, 2014 некому прописать в скрипте юзания абилок проверку на наличие соответствующих аур 1 Share this post Link to post Share on other sites
Xudojnik 904 Report post Posted September 11, 2014 Еще примерно час ушел на формулировку задачи и еще два часа на реализацию и тестирование.Тестировал на1) каком то мобе в Монастыре, который постоянно спамил шадоуболты2) втором персонаже.3) на втором персонаже с импом-пулеметчиком.Для прерывания кастов юзал маговский кс.В первом случае в дебаге наблюдал как все попытки моба фейлились в течение положенных 10 секунд после успешного прерывания спелла (т.е. задача выполнена)Во втором случае в дебаге наблюдал как вызовы ProhibitSpellSchool корректно уходили по своему обычному пути в Player.cpp (т.е. ничего другого не сломано)В третьем случае при успешном прерывании каста импа, он на 10 секунд прекращал пулять фаирболы и переходил в рукопашную. По истечении 10 секунд снова начинал пулять фаирболы. Вот только у варлока не было видно того, что имп получил кулдаун на школу.Всего добавлено примерно 30-40 строк кода. Никакого коренного переписывания спеллсистемы не надо.Нужно банально:* организовать хранение кулдаунов на школу у юнитов* переопредилить ProhibitSpellSchool для Creature* добавить проверку на кулдаун школы при попытке кастовать спеллCreature.cpp Unit* Creature::SelectAttackingTarget(AttackingTarget target, uint32 position, SpellEntry const* pSpellInfo /*= NULL*/, uint32 selectFlags/*= 0*/) const{ if (!CanHaveThreatList()) { return NULL; } // ThreatList m_threatlist; ThreatList const& threatlist = GetThreatManager().getThreatList(); ThreatList::const_iterator itr = threatlist.begin(); ThreatList::const_reverse_iterator ritr = threatlist.rbegin(); if (position >= threatlist.size() || !threatlist.size()) { return NULL; } switch (target) { case ATTACKING_TARGET_RANDOM: { std::vector<Unit*> suitableUnits; suitableUnits.reserve(threatlist.size() - position); advance(itr, position); for (; itr != threatlist.end(); ++itr) if (Unit* pTarget = GetMap()->GetUnit((*itr)->getUnitGuid())) if (!selectFlags || MeetsSelectAttackingRequirement(pTarget, pSpellInfo, selectFlags)) { suitableUnits.push_back(pTarget); } if (!suitableUnits.empty()) { return suitableUnits[urand(0, suitableUnits.size() - 1)]; } break; } case ATTACKING_TARGET_TOPAGGRO: { advance(itr, position); for (; itr != threatlist.end(); ++itr) if (Unit* pTarget = GetMap()->GetUnit((*itr)->getUnitGuid())) if (!selectFlags || MeetsSelectAttackingRequirement(pTarget, pSpellInfo, selectFlags)) { return pTarget; } break; } case ATTACKING_TARGET_BOTTOMAGGRO: { advance(ritr, position); for (; ritr != threatlist.rend(); ++ritr) if (Unit* pTarget = GetMap()->GetUnit((*itr)->getUnitGuid())) if (!selectFlags || MeetsSelectAttackingRequirement(pTarget, pSpellInfo, selectFlags)) { return pTarget; } break; } } return NULL;}//<Добавлено>void Creature::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs){ time_t curTime = time(NULL); for(SchoolCooldowns::const_iterator itr = m_schoolCooldowns.begin(); itr != m_schoolCooldowns.end(); ++itr) { if (idSchoolMask && GetSchoolCooldownDelay(idSchoolMask) < unTimeMs) { AddSchoolCooldown(idSchoolMask, curTime + unTimeMs / IN_MILLISECONDS); } }}//</Добавлено>void Creature::_AddCreatureSpellCooldown(uint32 spell_id, time_t end_time){ m_CreatureSpellCooldowns[spell_id] = end_time;}void Creature::_AddCreatureCategoryCooldown(uint32 category, time_t apply_time){ m_CreatureCategoryCooldowns[category] = apply_time;}void Creature::AddCreatureSpellCooldown(uint32 spellid){ SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid); if (!spellInfo) { return; } uint32 cooldown = GetSpellRecoveryTime(spellInfo); if (cooldown) { _AddCreatureSpellCooldown(spellid, time(NULL) + cooldown / IN_MILLISECONDS); } if (spellInfo->Category) { _AddCreatureCategoryCooldown(spellInfo->Category, time(NULL)); }} Creature.h SpellSchoolMask GetMeleeDamageSchoolMask() const override { return m_meleeDamageSchoolMask; } void SetMeleeDamageSchool(SpellSchools school) { m_meleeDamageSchoolMask = GetSchoolMask(school); }//<Добавлено> void ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) override;//</Добавлено> void _AddCreatureSpellCooldown(uint32 spell_id, time_t end_time); void _AddCreatureCategoryCooldown(uint32 category, time_t apply_time); void AddCreatureSpellCooldown(uint32 spellid); bool HasSpellCooldown(uint32 spell_id) const; bool HasCategoryCooldown(uint32 spell_id) const; uint32 GetCreatureSpellCooldownDelay(uint32 spellId) const; Unit.cpp ////////////////////////////////////////////////////////////// Methods of class UnitUnit::Unit() : movespline(new Movement::MoveSpline()), m_charmInfo(NULL), i_motionMaster(this), m_ThreatManager(this), m_HostileRefManager(this){ m_objectType |= TYPEMASK_UNIT; m_objectTypeId = TYPEID_UNIT; m_updateFlag = (UPDATEFLAG_ALL | UPDATEFLAG_LIVING | UPDATEFLAG_HAS_POSITION); m_attackTimer[BASE_ATTACK] = 0; m_attackTimer[OFF_ATTACK] = 0; m_attackTimer[RANGED_ATTACK] = 0; m_modAttackSpeedPct[BASE_ATTACK] = 1.0f; m_modAttackSpeedPct[OFF_ATTACK] = 1.0f; m_modAttackSpeedPct[RANGED_ATTACK] = 1.0f;//<Добавлено> m_schoolCooldowns[SPELL_SCHOOL_MASK_HOLY] = time(NULL); m_schoolCooldowns[SPELL_SCHOOL_MASK_FIRE] = time(NULL); m_schoolCooldowns[SPELL_SCHOOL_MASK_NATURE] = time(NULL); m_schoolCooldowns[SPELL_SCHOOL_MASK_FROST] = time(NULL); m_schoolCooldowns[SPELL_SCHOOL_MASK_SHADOW] = time(NULL); m_schoolCooldowns[SPELL_SCHOOL_MASK_ARCANE] = time(NULL);//</Добавлено> m_extraAttacks = 0; m_state = 0; m_deathState = ALIVE; for (uint32 i = 0; i < CURRENT_MAX_SPELL; ++i) { m_currentSpells[i] = NULL; } m_castCounter = 0; // m_Aura = NULL; // m_AurasCheck = 2000; // m_removeAuraTimer = 4; m_spellAuraHoldersUpdateIterator = m_spellAuraHolders.end(); m_AuraFlags = 0; m_Visibility = VISIBILITY_ON; m_AINotifyScheduled = false; m_detectInvisibilityMask = 0; m_invisibilityMask = 0; m_transform = 0; m_canModifyStats = false; for (int i = 0; i < MAX_SPELL_IMMUNITY; ++i) { m_spellImmune[i].clear(); } for (int i = 0; i < UNIT_MOD_END; ++i) { m_auraModifiersGroup[i][BASE_VALUE] = 0.0f; m_auraModifiersGroup[i][BASE_PCT] = 1.0f; m_auraModifiersGroup[i][TOTAL_VALUE] = 0.0f; m_auraModifiersGroup[i][TOTAL_PCT] = 1.0f; } // implement 50% base damage from offhand m_auraModifiersGroup[UNIT_MOD_DAMAGE_OFFHAND][TOTAL_PCT] = 0.5f; for (int i = 0; i < MAX_ATTACK; ++i) { m_weaponDamage[i][MINDAMAGE] = BASE_MINDAMAGE; m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE; } for (int i = 0; i < MAX_STATS; ++i) { m_createStats[i] = 0.0f; } m_attacking = NULL; m_modMeleeHitChance = 0.0f; m_modRangedHitChance = 0.0f; m_modSpellHitChance = 0.0f; m_baseSpellCritChance = 5; m_CombatTimer = 0; m_lastManaUseTimer = 0; // m_victimThreat = 0.0f; for (int i = 0; i < MAX_SPELL_SCHOOL; ++i) { m_threatModifier[i] = 1.0f; } m_isSorted = true; for (int i = 0; i < MAX_MOVE_TYPE; ++i) { m_speed_rate[i] = 1.0f; } // remove aurastates allowing special moves for (int i = 0; i < MAX_REACTIVE; ++i) { m_reactiveTimer[i] = 0; } m_isCreatureLinkingTrigger = false; m_isSpawningLinked = false; m_dummyCombatState = false;}...void Unit::_UpdateAutoRepeatSpell(){ // check "realtime" interrupts if ((GetTypeId() == TYPEID_PLAYER && ((Player*)this)->isMoving()) || IsNonMeleeSpellCasted(false, false, true)) { // cancel wand shoot if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Category == 351) { InterruptSpell(CURRENT_AUTOREPEAT_SPELL); } m_AutoRepeatFirstCast = true; return; } // apply delay if (m_AutoRepeatFirstCast && getAttackTimer(RANGED_ATTACK) < 500) { setAttackTimer(RANGED_ATTACK, 500); } m_AutoRepeatFirstCast = false; // castroutine if (isAttackReady(RANGED_ATTACK)) { // Check if able to cast if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true) != SPELL_CAST_OK) { InterruptSpell(CURRENT_AUTOREPEAT_SPELL); return; } // we want to shoot Spell* spell = new Spell(this, m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo, true); spell->prepare(&(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets)); // all went good, reset attack resetAttackTimer(RANGED_ATTACK); }}//<Добавлено>void Unit::AddSchoolCooldown(uint32 school_id, time_t end_time){ m_schoolCooldowns[school_id] = end_time;}void Unit::RemoveSchoolCooldown(uint32 school_id){ m_schoolCooldowns.erase(school_id);}void Unit::RemoveAllSchoolCooldowns(){ if (!m_schoolCooldowns.empty()) { m_schoolCooldowns.clear(); }}//</Добавлено>void Unit::SetCurrentCastedSpell(Spell* pSpell){ MANGOS_ASSERT(pSpell); // NULL may be never passed here, use InterruptSpell or InterruptNonMeleeSpells CurrentSpellTypes CSpellType = pSpell->GetCurrentContainer(); if (pSpell == m_currentSpells[CSpellType]) { return; } // avoid breaking self // break same type spell if it is not delayed InterruptSpell(CSpellType, false); // special breakage effects: switch (CSpellType) { case CURRENT_GENERIC_SPELL: { // generic spells always break channeled not delayed spells InterruptSpell(CURRENT_CHANNELED_SPELL, false); // autorepeat breaking if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) { // break autorepeat if not Auto Shot if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Category == 351) { InterruptSpell(CURRENT_AUTOREPEAT_SPELL); } m_AutoRepeatFirstCast = true; } } break; Unit.h enum SpellInterruptFlags{ SPELL_INTERRUPT_FLAG_MOVEMENT = 0x01, SPELL_INTERRUPT_FLAG_DAMAGE = 0x02, SPELL_INTERRUPT_FLAG_INTERRUPT = 0x04, SPELL_INTERRUPT_FLAG_AUTOATTACK = 0x08, SPELL_INTERRUPT_FLAG_ABORT_ON_DMG = 0x10 // _complete_ interrupt on direct damage // SPELL_INTERRUPT_UNK = 0x20 // unk, 564 of 727 spells having this spell start with "Glyph"};//<Добавлено>typedef std::map<uint32, time_t> SchoolCooldowns;//</Добавлено>enum SpellChannelInterruptFlags{ CHANNEL_FLAG_DAMAGE = 0x0002, CHANNEL_FLAG_MOVEMENT = 0x0008, CHANNEL_FLAG_TURNING = 0x0010, CHANNEL_FLAG_DAMAGE2 = 0x0080, CHANNEL_FLAG_DELAY = 0x4000};... void SetCreateStat(Stats stat, float val) { m_createStats[stat] = val; } void SetCreateHealth(uint32 val) { SetUInt32Value(UNIT_FIELD_BASE_HEALTH, val); } uint32 GetCreateHealth() const { return GetUInt32Value(UNIT_FIELD_BASE_HEALTH); } void SetCreateMana(uint32 val) { SetUInt32Value(UNIT_FIELD_BASE_MANA, val); } uint32 GetCreateMana() const { return GetUInt32Value(UNIT_FIELD_BASE_MANA); } uint32 GetCreatePowers(Powers power) const; float GetCreateStat(Stats stat) const { return m_createStats[stat]; }//<Добавлено> ///School Cooldowns SchoolCooldowns const& GetSchoolCooldownMap() const { return m_schoolCooldowns; } SchoolCooldowns m_schoolCooldowns; bool HasSchoolCooldown(uint32 school_id) const { SchoolCooldowns::const_iterator itr = m_schoolCooldowns.find(school_id); return itr != m_schoolCooldowns.end() && itr->second > time(NULL); } time_t GetSchoolCooldownDelay(uint32 school_id) const { SchoolCooldowns::const_iterator itr = m_schoolCooldowns.find(school_id); time_t t = time(NULL); return itr != m_schoolCooldowns.end() && itr->second > t ? itr->second - t : 0; } void AddSchoolCooldown(uint32 school_id, time_t end_time); virtual void ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) { }; void RemoveSchoolCooldown(uint32 school_id); void RemoveAllSchoolCooldowns();//</Добавлено> void SetCurrentCastedSpell(Spell* pSpell); //virtual void ProhibitSpellSchool(SpellSchoolMask /*idSchoolMask*/, uint32 /*unTimeMs*/) { } void InterruptSpell(CurrentSpellTypes spellType, bool withDelayed = true); void FinishSpell(CurrentSpellTypes spellType, bool ok = true); Spell.cpp Unit* Spell::GetPrefilledUnitTargetOrUnitTarget(SpellEffectIndex effIndex) const{for (TargetList::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr)if (itr->effectMask & (1 << effIndex)){ return m_caster->GetMap()->GetUnit(itr->targetGUID); }return m_targets.getUnitTarget();}SpellCastResult Spell::CheckCast(bool strict){//<Добавлено>//Check school cooldowns for NPC's. Players are handled in other wayif (m_caster->GetTypeId() == TYPEID_UNIT){if (((Unit*)m_caster)->HasSchoolCooldown(GetSpellSchoolMask(m_spellInfo))){return SPELL_FAILED_DONT_REPORT;}}//</Добавлено>// check cooldowns to prevent cheating (ignore passive spells, that client side visual only)if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR_PASSIVE) &&((Player*)m_caster)->HasSpellCooldown(m_spellInfo->Id)){if (m_triggeredByAuraSpell){ return SPELL_FAILED_DONT_REPORT; }else{ return SPELL_FAILED_NOT_READY; }}// check global cooldownif (strict && !m_IsTriggeredSpell && HasGlobalCooldown()){ return SPELL_FAILED_NOT_READY; }// only allow triggered spells if at an ended battlegroundif (!m_IsTriggeredSpell && m_caster->GetTypeId() == TYPEID_PLAYER)if (BattleGround* bg = ((Player*)m_caster)->GetBattleGround())if (bg->GetStatus() == STATUS_WAIT_LEAVE){ return SPELL_FAILED_DONT_REPORT; } 14 Share this post Link to post Share on other sites
Sidsukana 1749 Report post Posted September 11, 2014 Спасибо. Вы уверены что багов в снятии кулдауна не наблюдается? При наложении на цель нескольких подобных эффектов (от разных игроков например, они блокируются?). Все работает по аналогии с игроком? 0 Share this post Link to post Share on other sites
Xudojnik 904 Report post Posted September 11, 2014 Там не "кулдаун" на самом деле, а время до которого нельзя юзать школу.Отличия от игроков в том, что у игроков просматриваются выученные спеллы и накидываются им кд. Но не все мобы, которые умеют кастить, "знают" то что они кастят (привет скриптовка на с++). Поэтому сделал дополнительную мапу только для школ.А проверку поместил в самый корень функции каста вместе с другими проверками кулдаунов.Возможно некоторые мобы (у которых скриптовка на с++ и прямой вызов каста без всяких проверок) все же будут класть болт на блок школы. Но тут уже половые трудности этих самых мобов. 0 Share this post Link to post Share on other sites
Zoldjen 362 Report post Posted September 14, 2014 Надо, чтоб эта механика на боссов не распространялась , а то слишком жирно будет. 2 Share this post Link to post Share on other sites
Xudojnik 904 Report post Posted September 14, 2014 Это изначально предусмотрено в мангосе. Насколько я помню, мобам можно выставить свойство "иммунить интеррапты". Если боссу будет выставлено такое свойство, то он просто заиммунит спелл (и эффект блока школы не наложится). 0 Share this post Link to post Share on other sites
nogur 69 Report post Posted December 8, 2014 Я както помню что говорили что это не хотят реализовывать, как и скрипт мобов - потому что сервак лагает от такого количества обработки инфы) 0 Share this post Link to post Share on other sites