void psAttack::Affect(psCombatAttackGameEvent* event) { gemActor* attacker = event->GetAttacker(); gemActor* target = event->GetTarget(); if(!attacker || !target) { Debug2(LOG_COMBAT,event->GetAttackerID(),"Attacker ID: %d. Combat stopped as one participant logged off.",event->GetAttackerID()); if(attacker) { attacker->EndAttack(); psserver->GetCombatManager()->StopAttack(attacker); } return; } attacker->EndAttack(); // If the attacker is no longer in attack mode, abort. if(attacker->GetMode() != PSCHARACTER_MODE_COMBAT) { Debug2(LOG_COMBAT,event->GetAttackerID(),"Combat stopped as attacker %d left combat mode.",event->GetAttackerID()); return; } // If target is dead, abort. if(!target->IsAlive()) { Debug2(LOG_COMBAT,event->GetAttackerID(),"Combat stopped as target %d is dead.",event->GetTargetID()); psserver->GetCombatManager()->StopAttack(attacker); return; } // If the slot is no longer attackable, abort psCharacter* attacker_data = attacker->GetCharacterData(); INVENTORY_SLOT_NUMBER slot = event->GetWeaponSlot(); if(!attacker_data->Inventory().CanItemAttack(slot)) { Debug2(LOG_COMBAT,event->GetAttackerID(),"Combat stopped as attacker no longer has an attacking item equipped. Attacker ID: %d",event->GetAttackerID()); psserver->GetCombatManager()->StopAttack(attacker); return; } psItem* weapon = attacker_data->Inventory().GetEffectiveWeaponInSlot(slot); // weapon became unwieldable csString response; if(weapon && !weapon->CheckRequirements(attacker_data, response)) { Debug2(LOG_COMBAT, event->GetAttackerID(), "%s has lost use of weapon", attacker->GetName()); psserver->GetCombatManager()->StopAttack(attacker); psserver->SendSystemError(event->GetAttackerID(), "You can't use your %s any more.", weapon->GetName()); return; } Client* attacker_client = attacker->GetClient(); if(attacker_client) { // Input the stamina data MathEnvironment env; env.Define("Actor", event->GetAttacker()); env.Define("Weapon", weapon); (void) psserver->GetCacheManager()->GetStaminaCombat()->Evaluate(&env); MathVar* PhyDrain = env.Lookup("PhyDrain"); MathVar* MntDrain = env.Lookup("MntDrain"); // stop the attack if the attacker has no stamina left if((attacker_data->GetStamina(true) < PhyDrain->GetValue()) || (attacker_data->GetStamina(false) < MntDrain->GetValue())) { psserver->GetCombatManager()->StopAttack(attacker); psserver->SendSystemError(event->GetAttackerID(), "You are too tired to attack."); return; } // If the target has become impervious, abort and give up attacking csString msg; if(!attacker->IsAllowedToAttack(target, msg)) { psserver->GetCombatManager()->StopAttack(attacker); return; } //I will be seeing how this affects things and make changes accordingly // If the target has changed, abort (assume another combat event has started since we are still in attack mode) if(target != attacker_client->GetTargetObject()) { Debug2(LOG_COMBAT,event->GetAttackerID(),"Skipping attack, Target changed for attacker ID: %d.",event->GetAttackerID()); return; } } else { // Check if the npc's target has changed. // If it has, then assume another combat event has started. gemNPC* npcAttacker = attacker->GetNPCPtr(); if(npcAttacker && npcAttacker->GetTarget() != target) { Debug2(LOG_COMBAT,event->GetAttackerID(),"Skipping attack, Target changed for attacker ID: %d.",event->GetAttackerID()); return; } } // If the weapon in the slot has been changed, // skip a turn (latency for this slot may also have changed). if(event->GetWeapon() != weapon) { Debug2(LOG_COMBAT, attacker->GetClientID(), "Skipping attack because %s has changed weapons mid battle", attacker->GetName()); } else if(attacker->IsSpellCasting()) { psserver->SendSystemInfo(event->GetAttackerID(), "You can't attack while casting spells."); } else { int attack_result; if(weapon->GetIsRangeWeapon() && weapon->GetUsesAmmo()) { INVENTORY_SLOT_NUMBER otherHand = event->GetWeaponSlot() == PSCHARACTER_SLOT_RIGHTHAND ? PSCHARACTER_SLOT_LEFTHAND: PSCHARACTER_SLOT_RIGHTHAND; psItem* otherItem = attacker_data->Inventory().GetInventoryItem(otherHand); uint32_t item_id = 0; if(weapon->GetAmmoType() == PSITEMSTATS_AMMOTYPE_ROCKS) { // Rocks are their own ammo. attack_result = ATTACK_NOTCALCULATED; item_id = weapon->GetUID(); } else 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 attack_result = ATTACK_OUTOFAMMO; for(size_t i=1; i<attacker_data->Inventory().GetInventoryIndexCount(); i++) { psItem* currItem = attacker_data->Inventory().GetInventoryIndexItem(i); if(currItem && currItem->GetContainerID() == item_id && weapon->UsesAmmoType(currItem->GetBaseStats()->GetUID())) { otherItem = currItem; attack_result = ATTACK_NOTCALCULATED; item_id = otherItem->GetUID(); break; } } } else if(!weapon->UsesAmmoType(otherItem->GetBaseStats()->GetUID())) { attack_result = ATTACK_OUTOFAMMO; } else { attack_result = ATTACK_NOTCALCULATED; item_id = otherItem->GetUID(); } if(attack_result != ATTACK_OUTOFAMMO) { psItem* usedAmmo = attacker_data->Inventory().RemoveItemID(item_id, 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); } int affectedCount = 1; AffectTarget(target, event, attack_result); // Handle AOE (Area of Effect) if attack caused some damage. float radius = aoeRadius ? aoeRadius->Evaluate(&event->env) : 0.0; if(radius >= 0.01f && attack_result == ATTACK_DAMAGE) { csVector3 attackerPos; csVector3 targetPos; iSector* attackerSector, *targetSector; InstanceID targetInstance = target->GetInstance(); attacker->GetPosition(attackerPos, attackerSector); target->GetPosition(targetPos, targetSector); // directional vector for a line from attacker to original target csVector3 attackerToTarget; attackerToTarget = targetPos - attackerPos; float angle = aoeAngle ? aoeAngle->Evaluate(&event->env) : 0.0; if(angle <= SMALL_EPSILON || angle > 360) angle = 360; angle = (angle/2)*(PI/180); // convert degrees to radians MathEnvironment& env(event->env); env.Define("AOE_Radius", radius); env.Define("AOE_Angle", angle); csArray<gemObject*> nearby = psserver->entitymanager->GetGEM()->FindNearbyEntities(targetSector, targetPos, targetInstance, radius); for(size_t i = 0; i < nearby.GetSize(); i++) { gemActor* nearby_target = nearby[i]->GetActorPtr(); if(nearby_target == attacker || nearby_target == target) continue; csString msg; if(!attacker->IsAllowedToAttack(nearby_target, msg)) continue; if(angle < PI) { csVector3 attackerToAffected = nearby_target->GetPosition(); // obtain a directional line for the vector from attacker to affacted target // note that this line does not originate at the original target because the // cone that shall include the hittable area shall originate at the attacker attackerToAffected -= attackerPos; // Angle between the line original target->attacker and original target->affected target float cosATAngle = (attackerToAffected * attackerToTarget) / (attackerToAffected.Norm() * attackerToTarget.Norm()); // cap the value to meaningful ones to account for rounding issues if(cosATAngle > 1.0f || CS::IsNaN(cosATAngle)) cosATAngle = 1.0f; if(cosATAngle < -1.0f) cosATAngle = -1.0f; // use the absolute value of the angle here to account // for both sides equally - see above if(fabs(acosf(cosATAngle)) >= angle) continue; } // XXX recompute attack_result? AffectTarget(nearby_target, event, attack_result); affectedCount++; } psserver->SendSystemInfo(attacker->GetClientID(), "%s affected %d %s.", name.GetData(), affectedCount, (affectedCount == 1) ? "target" : "targets"); } // Get the next special attack to execute. psAttack* attack = attacker_data->GetAttackQueue()->First(); if(!attack || !attack->CanAttack(attacker_client)) { // Use the default attack type. attack = psserver->GetCacheManager()->GetAttackByID(1); } // Schedule the next attack. attack->Attack(attacker, target, slot); } }
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() ); }