예제 #1
0
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() );
}