void MType::MinorAttack(std::list<PlayerBullet*>& pl_bulletlist) { if (!CPlayState::Instance()->mpInterface->SetMana(-1)) { StopAttack(); return; } if (!mpSlash->IsDone()) return; if (wpn_timer.GetTicks() > minor_speed || wpn_timer.IsPaused()) { wpn_timer.Start(); mShotAnimClip = 0; mShotAnimTimer.Start(); SFX::PlaySoundResource("attack"); pl_bulletlist.push_back(new PlayerBullet(wpn_pos.x, wpn_pos.y, 180+mov, rot_divs)); for (auto it = totem_list.begin(); it != totem_list.end(); it++) { if (!(*it)->IsDisabled()) { int x = (*it)->GetMiddle(); int y = (*it)->GetVertical(); for (int i =4; i<10; i+= 2) { pl_bulletlist.push_back(new PlayerBullet(x+(i*2), y+(i*2), 180+(i*2)+mov,rot_divs)); pl_bulletlist.push_back(new PlayerBullet(x-(i*2), y+(i*2), 180-(i*2)+mov,rot_divs)); } } } } }
void AWeapon::UnEquip(AMutagenCharacter* entity) { DetachMeshFromPawn(); SetEquiped(true); StopAttack(); if (bPendingEquip) { StopWeaponAnimation(equipAnimation); bPendingEquip = false; //GetWorldTimerManager().ClearTimer(this, &AWeapon::OnEquipFinished); GetWorldTimerManager().ClearTimer(equipTimerHandle); } DetermineWeaponState(); }
void CombatManager::HandleCombatEvent(psCombatGameEvent *event) { psCharacter *attacker_data,*target_data; int attack_result; bool skipThisRound = false; if (!event->GetAttacker() || !event->GetTarget()) // disconnected and deleted { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Combat stopped as one participant logged of."); #endif return; } gemActor *gemAttacker = dynamic_cast<gemActor*> ((gemObject *) event->attacker); gemActor *gemTarget = dynamic_cast<gemActor*> ((gemObject *) event->target); attacker_data=event->GetAttackerData(); target_data=event->GetTargetData(); // If the attacker is no longer in attack mode abort. if (gemAttacker->GetMode() != PSCHARACTER_MODE_COMBAT) { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Combat stopped as you left combat mode."); #endif return; } // If target is dead, abort. if (!gemTarget->IsAlive() ) { #ifdef COMBAT_DEBUG psserver->SendSystemResult(event->AttackerCID, "Combat stopped as one participant logged of."); #endif return; } // If the slot is no longer attackable, abort if (!attacker_data->Inventory().CanItemAttack(event->GetWeaponSlot())) { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Combat stopped as you have no longer an attackable item equipped."); #endif return; } if (attacker_data->Inventory().GetEquipmentObject(event->GetWeaponSlot()).eventId != event->id) { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Ignored combat event as newer is in."); #endif return; } psItem* weapon = attacker_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot()); // weapon became unwieldable csString response; if(weapon!=NULL && !weapon->CheckRequirements(attacker_data,response)) { Debug2(LOG_COMBAT, gemAttacker->GetClientID(),"%s has lost use of weapon", gemAttacker->GetName() ); psserver->SendSystemError(event->AttackerCID, "You can't use your %s any more.", weapon->GetName() ); return; } // If the weapon in the slot has been changed, skip a turn (latency for this slot may also have changed) if (event->WeaponID != weapon->GetUID()) { Debug2(LOG_COMBAT, gemAttacker->GetClientID(),"%s has changed weapons mid battle", gemAttacker->GetName() ); #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Weapon changed. Skipping"); #endif skipThisRound = true; } Client * attacker_client = psserver->GetNetManager()->GetClient(event->AttackerCID); if (attacker_client) { // Input the stamina data MathEnvironment env; env.Define("Actor", event->GetAttacker()); env.Define("Weapon", weapon); staminacombat->Evaluate(&env); MathVar *PhyDrain = env.Lookup("PhyDrain"); MathVar *MntDrain = env.Lookup("MntDrain"); if ( (attacker_client->GetCharacterData()->GetStamina(true) < PhyDrain->GetValue()) || (attacker_client->GetCharacterData()->GetStamina(false) < MntDrain->GetValue()) ) { StopAttack(attacker_data->GetActor()); psserver->SendSystemError(event->AttackerCID, "You are too tired to attack."); return; } // If the target has become impervious, abort and give up attacking if (!attacker_client->IsAllowedToAttack(gemTarget)) { StopAttack(attacker_data->GetActor()); return; } // If the target has changed, abort (assume another combat event has started since we are still in attack mode) if (gemTarget != attacker_client->GetTargetObject()) { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Target changed."); #endif return; } } else { // Check if the npc's target has changed (if it has, then assume another combat event has started.) gemNPC* npcAttacker = dynamic_cast<gemNPC*>(gemAttacker); if (npcAttacker && npcAttacker->GetTarget() != gemTarget) { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "NPC's target changed."); #endif return; } } if (gemAttacker->IsSpellCasting()) { psserver->SendSystemInfo(event->AttackerCID, "You can't attack while casting spells."); skipThisRound = true; } if (!skipThisRound) { if (weapon->GetIsRangeWeapon() && weapon->GetUsesAmmo()) { INVENTORY_SLOT_NUMBER otherHand = event->GetWeaponSlot() == PSCHARACTER_SLOT_RIGHTHAND ? PSCHARACTER_SLOT_LEFTHAND: PSCHARACTER_SLOT_RIGHTHAND; attack_result = ATTACK_NOTCALCULATED; psItem* otherItem = attacker_data->Inventory().GetInventoryItem(otherHand); if (otherItem == NULL) { attack_result = ATTACK_OUTOFAMMO; } else if (otherItem->GetIsContainer()) // Is it a quiver? { // Pick the first ammo we can shoot from the container // And set it as the active ammo bool bFound = false; for (size_t i=1; i<attacker_data->Inventory().GetInventoryIndexCount() && !bFound; i++) { psItem* currItem = attacker_data->Inventory().GetInventoryIndexItem(i); if (currItem && currItem->GetContainerID() == otherItem->GetUID() && weapon->GetAmmoTypeID().In(currItem->GetBaseStats()->GetUID())) { otherItem = currItem; bFound = true; } } if (!bFound) attack_result = ATTACK_OUTOFAMMO; } else if (!weapon->GetAmmoTypeID().In(otherItem->GetBaseStats()->GetUID())) { attack_result = ATTACK_OUTOFAMMO; } if (attack_result != ATTACK_OUTOFAMMO) { psItem* usedAmmo = attacker_data->Inventory().RemoveItemID(otherItem->GetUID(), 1); if (usedAmmo) { attack_result=CalculateAttack(event, usedAmmo); usedAmmo->Destroy(); psserver->GetCharManager()->UpdateItemViews(attacker_client->GetClientNum()); } else attack_result=CalculateAttack(event); } } else { attack_result=CalculateAttack(event); } event->AttackResult=attack_result; ApplyCombatEvent(event, attack_result); } // Queue next event to continue combat if this is an auto attack slot if (attacker_data->Inventory().IsItemAutoAttack(event->GetWeaponSlot())) { // CPrintf(CON_DEBUG, "Queueing Slot %d for %s's next combat event.\n",event->GetWeaponSlot(), event->attacker->GetName() ); QueueNextEvent(event); } else { #ifdef COMBAT_DEBUG psserver->SendSystemError(event->AttackerCID, "Item %s is not a auto attack item.",attacker_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot())->GetName()); #endif } // else // CPrintf(CON_DEBUG, "Slot %d for %s not an auto-attack slot.\n",event->GetWeaponSlot(), event->attacker->GetName() ); }
void PetAI::UpdateAI(uint32 diff) { if (!me->IsAlive() || !me->GetCharmInfo()) return; Unit* owner = me->GetCharmerOrOwner(); if (_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else _updateAlliesTimer -= diff; if (me->GetVictim() && me->EnsureVictim()->IsAlive()) { // is only necessary to stop casting, the pet must not exit combat if (!me->GetCurrentSpell(CURRENT_CHANNELED_SPELL) && // ignore channeled spells (Pin, Seduction) me->EnsureVictim()->HasBreakableByDamageCrowdControlAura(me)) { me->InterruptNonMeleeSpells(false); return; } if (NeedToStop()) { TC_LOG_TRACE("scripts.ai.petai", "PetAI::UpdateAI: AI stopped attacking %s", me->GetGUID().ToString().c_str()); StopAttack(); return; } // Check before attacking to prevent pets from leaving stay position if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY)) { if (me->GetCharmInfo()->IsCommandAttack() || (me->GetCharmInfo()->IsAtStay() && me->IsWithinMeleeRange(me->GetVictim()))) DoMeleeAttackIfReady(); } else DoMeleeAttackIfReady(); } else { if (me->HasReactState(REACT_AGGRESSIVE) || me->GetCharmInfo()->IsAtStay()) { // Every update we need to check targets only in certain cases // Aggressive - Allow auto select if owner or pet don't have a target // Stay - Only pick from pet or owner targets / attackers so targets won't run by // while chasing our owner. Don't do auto select. // All other cases (ie: defensive) - Targets are assigned by DamageTaken(), OwnerAttackedBy(), OwnerAttacked(), etc. Unit* nextTarget = SelectNextTarget(me->HasReactState(REACT_AGGRESSIVE)); if (nextTarget) AttackStart(nextTarget); else HandleReturnMovement(); } else HandleReturnMovement(); } // Autocast (cast only in combat or persistent spells in any state) if (!me->HasUnitState(UNIT_STATE_CASTING)) { TargetSpellList targetSpellStore; for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i) { uint32 spellID = me->GetPetAutoSpellOnPos(i); if (!spellID) continue; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID); if (!spellInfo) continue; if (me->GetSpellHistory()->HasGlobalCooldown(spellInfo)) continue; // check spell cooldown if (!me->GetSpellHistory()->IsReady(spellInfo)) continue; if (spellInfo->IsPositive()) { if (spellInfo->CanBeUsedInCombat()) { // Check if we're in combat or commanded to attack if (!me->IsInCombat() && !me->GetCharmInfo()->IsCommandAttack()) continue; } Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE); bool spellUsed = false; // Some spells can target enemy or friendly (DK Ghoul's Leap) // Check for enemy first (pet then owner) Unit* target = me->getAttackerForHelper(); if (!target && owner) target = owner->getAttackerForHelper(); if (target) { if (CanAttack(target) && spell->CanAutoCast(target)) { targetSpellStore.push_back(std::make_pair(target, spell)); spellUsed = true; } } if (spellInfo->HasEffect(SPELL_EFFECT_JUMP_DEST)) { if (!spellUsed) delete spell; continue; // Pets must only jump to target } // No enemy, check friendly if (!spellUsed) { for (GuidSet::const_iterator tar = _allySet.begin(); tar != _allySet.end(); ++tar) { Unit* ally = ObjectAccessor::GetUnit(*me, *tar); //only buff targets that are in combat, unless the spell can only be cast while out of combat if (!ally) continue; if (spell->CanAutoCast(ally)) { targetSpellStore.push_back(std::make_pair(ally, spell)); spellUsed = true; break; } } } // No valid targets at all if (!spellUsed) delete spell; } else if (me->GetVictim() && CanAttack(me->GetVictim()) && spellInfo->CanBeUsedInCombat()) { Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE); if (spell->CanAutoCast(me->GetVictim())) targetSpellStore.push_back(std::make_pair(me->GetVictim(), spell)); else delete spell; } } // found units to cast on to if (!targetSpellStore.empty()) { TargetSpellList::iterator it = targetSpellStore.begin(); std::advance(it, urand(0, targetSpellStore.size() - 1)); Spell* spell = (*it).second; Unit* target = (*it).first; targetSpellStore.erase(it); SpellCastTargets targets; targets.SetUnitTarget(target); spell->prepare(targets); } // deleted cached Spell objects for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } // Update speed as needed to prevent dropping too far behind and despawning me->UpdateSpeed(MOVE_RUN); me->UpdateSpeed(MOVE_WALK); me->UpdateSpeed(MOVE_FLIGHT); }
void CombatManager::ApplyCombatEvent(psCombatGameEvent *event, int attack_result) { psCharacter *attacker_data = event->GetAttackerData(); psCharacter *target_data=event->GetTargetData(); MathVar *weaponDecay = NULL; MathVar *blockDecay = NULL; MathVar *armorDecay = NULL; MathEnvironment env; psItem *weapon = attacker_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot()); psItem *blockingWeapon = target_data->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot(),true); psItem *struckArmor = target_data->Inventory().GetEffectiveArmorInSlot(event->AttackLocation); // there may only be a decay if you actually hit your target by some means if(attack_result == ATTACK_DAMAGE || attack_result == ATTACK_BLOCKED) { // we are guaranteed some armor is present - real one, race one or base one CS_ASSERT(struckArmor); float ArmorVsWeapon = weapon->GetArmorVSWeaponResistance(struckArmor->GetBaseStats()); // clamp value between 0 and 1 ArmorVsWeapon = ArmorVsWeapon > 1.0F ? 1.0F : ArmorVsWeapon < 0.0F ? 0.0F : ArmorVsWeapon; env.Define("Weapon", weapon); // weapon that was used to attack env.Define("BlockingWeapon", blockingWeapon); // weapon that blocked the attack env.Define("Armor", struckArmor); // armor hit env.Define("ArmorVsWeapon", ArmorVsWeapon); // armor vs weapon effectiveness env.Define("Damage", event->FinalDamage); // actual damage dealt env.Define("Blocked", (attack_result == ATTACK_BLOCKED)); // identifies whether this attack was blocked calc_decay->Evaluate(&env); weaponDecay = env.Lookup("WeaponDecay"); blockDecay = env.Lookup("BlockingDecay"); armorDecay = env.Lookup("ArmorDecay"); } gemActor *gemAttacker = dynamic_cast<gemActor*> ((gemObject *) event->attacker); gemActor *gemTarget = dynamic_cast<gemActor*> ((gemObject *) event->target); switch (attack_result) { case ATTACK_DAMAGE: { bool isNearlyDead = false; if (target_data->GetMaxHP().Current() > 0.0 && target_data->GetHP()/target_data->GetMaxHP().Current() > 0.2) { if ((target_data->GetHP() - event->FinalDamage) / target_data->GetMaxHP().Current() <= 0.2) isNearlyDead = true; } psCombatEventMessage ev(event->AttackerCID, isNearlyDead ? psCombatEventMessage::COMBAT_DAMAGE_NEARLY_DEAD : psCombatEventMessage::COMBAT_DAMAGE, gemAttacker->GetEID(), gemTarget->GetEID(), event->AttackLocation, event->FinalDamage, weapon->GetAttackAnimID(gemAttacker->GetCharacterData()), gemTarget->FindAnimIndex("hit")); ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE); // Apply final damage if (target_data!=NULL) { gemTarget->DoDamage(gemAttacker,event->FinalDamage); if (gemAttacker) { gemAttacker->InvokeAttackScripts(gemTarget, weapon); } if (gemTarget) { gemTarget->InvokeDefenseScripts(gemAttacker, weapon); if(isNearlyDead) { gemTarget->InvokeNearlyDeadScripts(gemAttacker, weapon); } } } // If the target wasn't in combat, it is now... // Note that other modes shouldn't be interrupted automatically if (gemTarget->GetMode() == PSCHARACTER_MODE_PEACE || gemTarget->GetMode() == PSCHARACTER_MODE_WORK) { if (gemTarget->GetClient()) // Set reciprocal target gemTarget->GetClient()->SetTargetObject(gemAttacker,true); // The default stance is 'Fully Defensive'. Stance initialStance = GetStance(cacheManager, "FullyDefensive"); AttackSomeone(gemTarget,gemAttacker,initialStance); } if (weapon) { weapon->AddDecay(weaponDecay->GetValue()); } if (struckArmor) { struckArmor->AddDecay(armorDecay->GetValue()); } NotifyTarget(gemAttacker,gemTarget); break; } case ATTACK_DODGED: { psCombatEventMessage ev(event->AttackerCID, psCombatEventMessage::COMBAT_DODGE, gemAttacker->GetEID(), gemTarget->GetEID(), event->AttackLocation, 0, // no dmg on a dodge weapon->GetAttackAnimID(gemAttacker->GetCharacterData()), (unsigned int)-1); // no defense anims yet ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE); NotifyTarget(gemAttacker,gemTarget); break; } case ATTACK_BLOCKED: { psCombatEventMessage ev(event->AttackerCID, psCombatEventMessage::COMBAT_BLOCK, gemAttacker->GetEID(), gemTarget->GetEID(), event->AttackLocation, 0, // no dmg on a block weapon->GetAttackAnimID( gemAttacker->GetCharacterData() ), (unsigned int)-1); // no defense anims yet ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE); if (weapon) { weapon->AddDecay(weaponDecay->GetValue()); } if (blockingWeapon) { blockingWeapon->AddDecay(blockDecay->GetValue()); } NotifyTarget(gemAttacker,gemTarget); break; } case ATTACK_MISSED: { psCombatEventMessage ev(event->AttackerCID, psCombatEventMessage::COMBAT_MISS, gemAttacker->GetEID(), gemTarget->GetEID(), event->AttackLocation, 0, // no dmg on a miss weapon->GetAttackAnimID( gemAttacker->GetCharacterData() ), (unsigned int)-1); // no defense anims yet ev.Multicast(gemTarget->GetMulticastClients(),0,MAX_COMBAT_EVENT_RANGE); NotifyTarget(gemAttacker,gemTarget); break; } case ATTACK_OUTOFRANGE: { if (event->AttackerCID) { psserver->SendSystemError(event->AttackerCID,"You are too far away to attack!"); // Auto-stop attack is commented out below, when out of range to prevent npc kiting by jumping in and out of range //if (event->attacker && event->attacker.IsValid()) // StopAttack(dynamic_cast<gemActor*>((gemObject *) event->attacker)); // if you run away, you exit attack mode } break; } case ATTACK_BADANGLE: { if (event->AttackerCID) // if human player { psserver->SendSystemError(event->AttackerCID,"You must face the enemy to attack!"); // Auto-stop attack is commented out below, when out of range to prevent npc kiting by jumping in and out of range //if (event->attacker && event->attacker.IsValid()) // StopAttack(dynamic_cast<gemActor*>((gemObject *) event->attacker)); // if you run away, you exit attack mode } break; } case ATTACK_OUTOFAMMO: { psserver->SendSystemError(event->AttackerCID, "You are out of ammo!"); if (event->attacker && event->attacker.IsValid()) StopAttack(dynamic_cast<gemActor*>((gemObject *) event->attacker)); // if you run out of ammo, you exit attack mode break; } } }