void psNPCLoader::WriteStats()
{
    csRef<iDocumentNode> statsNode = npcRoot->CreateNodeBefore(CS_NODE_ELEMENT);

    statsNode->SetValue("stats");

    MathEnvironment baseSkillVal;
    npc->GetSkillBaseValues(&baseSkillVal);

    statsNode->SetAttributeAsInt("agi", baseSkillVal.Lookup("AGI")->GetRoundValue());
    statsNode->SetAttributeAsInt("cha", baseSkillVal.Lookup("CHA")->GetRoundValue());
    statsNode->SetAttributeAsInt("end", baseSkillVal.Lookup("END")->GetRoundValue());
    statsNode->SetAttributeAsInt("int", baseSkillVal.Lookup("INT")->GetRoundValue());
    statsNode->SetAttributeAsInt("str", baseSkillVal.Lookup("STR")->GetRoundValue());
    statsNode->SetAttributeAsInt("wil", baseSkillVal.Lookup("WIL")->GetRoundValue());
}
Example #2
0
int EntityManager::CalculateFamiliarAffinity( psCharacter * chardata, size_t type, size_t lifecycle, size_t attacktool, size_t attacktype )
{
    static MathScript *msAffinity;
    int affinityValue = 0;

    if (!msAffinity)
    {
        msAffinity = psserver->GetMathScriptEngine()->FindScript("CalculateFamiliarAffinity");
        CS_ASSERT(msAffinity != NULL);
    }

    // Determine Familiar Type using Affinity Values
    if ( msAffinity )
    {
        MathEnvironment env;
        env.Define("Actor",      chardata);
        env.Define("Type",       type);
        env.Define("Lifecycle",  lifecycle);
        env.Define("AttackTool", attacktool);
        env.Define("AttackType", attacktype);
        msAffinity->Evaluate(&env);
        MathVar *affinity = env.Lookup("Affinity");
        affinityValue = affinity->GetRoundValue();
    }

    return affinityValue;

}
TEST(MathScriptTest, StringTest2)
{
    Foo foo;
    MathScript *script = MathScript::Create("StringTest2", "\
        HasWeapon = 0;\
        SkillName = if(HasWeapon, 'Sword', \"Lah'ar\");\
        Rank = Quux:GetSkillRank(SkillName);\
    ");
    ASSERT_NE(script, NULL);
    MathEnvironment env;
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    script->Evaluate(&env);
    MathVar *rank = env.Lookup("Rank");
    EXPECT_EQ(77, rank->GetValue());
};
void CombatManager::DebugOutput(psCombatGameEvent *event, const MathEnvironment & env)
{
    MathVar *missed   = env.Lookup("Missed");      // Missed  = Attack missed the enemy
    MathVar *dodged   = env.Lookup("Dodged");      // Dodged  = Attack dodged  by enemy
    MathVar *blocked  = env.Lookup("Blocked");     // Blocked = Attack blocked by enemy
    MathVar *damage   = env.Lookup("FinalDamage"); // Actual damage done, if any

    psItem* item = event->GetAttackerData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot() );

    csString debug;
    debug.Append( "-----Debug Combat Summary--------\n");
    debug.AppendFmt( "%s attacks %s with slot %d , weapon %s, quality %1.2f, basedmg %1.2f/%1.2f/%1.2f\n",
      event->attacker->GetName(),event->target->GetName(), event->GetWeaponSlot(),item->GetName(),item->GetItemQuality(),
      item->GetDamage(PSITEMSTATS_DAMAGETYPE_SLASH),item->GetDamage(PSITEMSTATS_DAMAGETYPE_BLUNT),item->GetDamage(PSITEMSTATS_DAMAGETYPE_PIERCE));
    debug.AppendFmt( "Missed: %1.6f Dodged: %1.6f Blocked: %1.6f", missed->GetValue(), dodged->GetValue(), blocked->GetValue());
    debug.AppendFmt( "Damage: %1.1f\n", damage->GetValue());
    Debug2(LOG_COMBAT, event->attacker->GetClientID(), "%s", debug.GetData());
}
TEST(MathScriptTest, BasicExpression)
{
    MathExpression *exp = MathExpression::Create("X < 7");
    ASSERT_NE(exp, NULL);
    MathEnvironment env;
    env.Define("X", 5);
    ASSERT_NE(env.Lookup("X"), NULL);
    EXPECT_NE(exp->Evaluate(&env), 0.0);
}
TEST(MathScriptTest, PropertyAndFunction)
{
    Foo foo;
    MathExpression *exp = MathExpression::Create("Quux:TheAnswer = Quux:Multiply(6,7)");
    MathEnvironment env;
    ASSERT_NE(exp, NULL);
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    EXPECT_NE(0.0, exp->Evaluate(&env));
}
TEST(MathScriptTest, NestedCalls)
{
    Foo foo;
    MathExpression *exp = MathExpression::Create("Quux:Multiply(Quux:Multiply(2,3),7)");
    MathEnvironment env;
    ASSERT_NE(exp, NULL);
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    EXPECT_EQ(42, exp->Evaluate(&env));
};
TEST(MathScriptTest, MultipleOr)
{
    Foo foo;
    MathExpression *exp = MathExpression::Create("Quux:Multiply(0,8) | Quux:Multiply(2,3) | Quux:Multiply(17,0)");
    ASSERT_NE(exp, NULL);
    MathEnvironment env;
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    EXPECT_NE(exp->Evaluate(&env), 0.0);
}
TEST(MathScriptTest, StringTest)
{
    Foo foo;
    MathExpression *exp = MathExpression::Create("Quux:GetSkillRank('Lah\\'ar')");
    MathEnvironment env;
    ASSERT_NE(exp, NULL);
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    EXPECT_EQ(77, exp->Evaluate(&env));
};
TEST(MathScriptTest, BasicScript)
{
    // Basic conversion from Fahrenheit to Celsius and Kelvin.
    MathScript *script = MathScript::Create("TemperatureConv", "C = (F - 32) * 5/9; K = C + 273.15");
    ASSERT_NE(script, NULL);
    MathEnvironment env;
    env.Define("F", 212.0);
    script->Evaluate(&env);
    MathVar *F = env.Lookup("F");
    MathVar *C = env.Lookup("C");
    MathVar *K = env.Lookup("K");
    EXPECT_NE(F, NULL);
    EXPECT_NE(C, NULL);
    EXPECT_NE(K, NULL);
    EXPECT_EQ(212.0,  F->GetValue());
    EXPECT_EQ(100.0,  C->GetValue());
    EXPECT_EQ(373.15, K->GetValue());
    EXPECT_STREQ("373.15", K->ToString());
}
TEST(MathScriptTest, MethodNoArgs)
{
    Foo foo;
    MathExpression *exp = MathExpression::Create("Quux:GetSeven()");
    MathEnvironment env;
    ASSERT_NE(exp, NULL);
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    EXPECT_EQ(7, exp->Evaluate(&env));
}
TEST(MathScriptTest, BasicProperty)
{
    Foo foo;
    MathExpression *exp = MathExpression::Create("Quux:TheAnswer");
    MathEnvironment env;
    ASSERT_NE(exp, NULL);
    env.Define("Quux", &foo);
    ASSERT_NE(env.Lookup("Quux"), NULL);
    EXPECT_EQ(42, exp->Evaluate(&env));
}
TEST(MathScriptTest, StringAssignment)
{
    Foo foo;
    MathScript *script = MathScript::Create("StringAssignment", "Skill = 'Sword'; Rank = Quux:GetSkillRank(Skill);");
    MathEnvironment env;
    ASSERT_NE(script, NULL);
    env.Define("Quux", &foo);
    script->Evaluate(&env);
    MathVar *rank = env.Lookup("Rank");
    EXPECT_EQ(33, rank->GetValue());
};
TEST(MathScriptTest, StringAssignment2)
{
    MathScript *script = MathScript::Create("StringAssignment2", "Skill = 'Sword';");
    ASSERT_NE(script, NULL);

    MathEnvironment env;
    script->Evaluate(&env);

    MathVar *rank = env.Lookup("Skill");
    ASSERT_NE(rank, NULL);
    EXPECT_STREQ("Sword", rank->ToString());
};
Example #15
0
float LootRandomizer::CalcModifierCostCap(psCharacter* chr)
{
    float result = 1000.0;

    if(modifierCostCalc)
    {
        // Use the mob's attributes to calculate modifier cost cap
        MathEnvironment env;
        env.Define("Actor",   chr);
        env.Define("MaxHP",   chr->GetMaxHP().Current());
        env.Define("MaxMana", chr->GetMaxMana().Current());

        (void) modifierCostCalc->Evaluate(&env);
        MathVar* modcap = env.Lookup("ModCap");
        result = modcap->GetValue();
    }
    Debug2(LOG_LOOT,0,"DEBUG: Calculated cost cap %f\n", result);
    return result;
}
void randomgentest(int limit)
{
   csString scriptstr;
    if (limit<0)
    {
       scriptstr = "Roll = rnd();";
       limit=1;
    }
    else    
    {
       scriptstr.Format("Roll = rnd(%d);",limit);
    }
    MathScript *script = MathScript::Create("randomgen test", scriptstr.GetData());
    MathEnvironment env;
    ASSERT_NE(script, NULL) << scriptstr.GetData() << " did not create script";
    bool above1=false, abovehalflimit=false;
    for (int i=0; i<100; i++) //try 100 times, since this is random
    {
       script->Evaluate(&env);
       MathVar *roll = env.Lookup("Roll");
       EXPECT_GE(roll->GetValue(), 0); 
       EXPECT_LE(roll->GetValue(), limit);
       if (roll->GetValue()>1)
       {
          above1 = true;
       }
       if (2*roll->GetValue()>limit)
       {
          abovehalflimit = true;
       }
    }
    if (limit>1)
    {
       EXPECT_TRUE(above1) << scriptstr << "never exceeds 1"; 
    }
    if (limit>0)
    {
       EXPECT_TRUE(abovehalflimit) << scriptstr << "never exceeds half of limit"; 
    }
    delete script;
}
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 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;
        }
    }
}
Example #19
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);
    }
}
/**
 * This is the meat and potatoes of the combat engine here.
 */
int CombatManager::CalculateAttack(psCombatGameEvent *event, psItem* subWeapon)
{
    INVENTORY_SLOT_NUMBER otherHand = event->GetWeaponSlot() == PSCHARACTER_SLOT_LEFTHAND ? PSCHARACTER_SLOT_RIGHTHAND : PSCHARACTER_SLOT_LEFTHAND;
    event->AttackLocation = (INVENTORY_SLOT_NUMBER) targetLocations[randomgen->Get((int) targetLocations.GetSize())];

    gemObject *attacker = event->GetAttacker();
    gemObject *target = event->GetTarget();

    // calculate difference between target and attacker location - to be used for angle validation
    csVector3 diff(0); // initialize to some big value that shows an error
    
    {
        csVector3 attackPos, targetPos;
        iSector *attackSector, *targetSector;

        attacker->GetPosition(attackPos, attackSector);
        target->GetPosition(targetPos, targetSector);

        if((attacker->GetInstance() != target->GetInstance() &&
            attacker->GetInstance() != INSTANCE_ALL && target->GetInstance() != INSTANCE_ALL) ||
            !(entityManager->GetWorld()->WarpSpace(targetSector, attackSector, targetPos)))
        {
            return ATTACK_OUTOFRANGE;
        }

        diff = targetPos - attackPos;
    }

    MathEnvironment env;
    env.Define("Attacker",              attacker);
    env.Define("Target",                target);
    env.Define("AttackWeapon",          event->GetAttackerData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot()));
    env.Define("AttackWeaponSecondary", subWeapon);
    env.Define("TargetWeapon",          event->GetTargetData()->Inventory().GetEffectiveWeaponInSlot(event->GetWeaponSlot(), true));
    env.Define("TargetWeaponSecondary", event->GetTargetData()->Inventory().GetEffectiveWeaponInSlot(otherHand,true));
    env.Define("AttackLocationItem",    event->GetTargetData()->Inventory().GetEffectiveArmorInSlot(event->AttackLocation));
    env.Define("DiffX",                 diff.x ? diff.x : 0.00001F); // force minimal value
    env.Define("DiffY",                 diff.y ? diff.y : 0.00001F); // force minimal value
    env.Define("DiffZ",                 diff.z ? diff.z : 0.00001F); // force minimal value

    calc_damage->Evaluate(&env);

    if (DoLogDebug2(LOG_COMBAT, event->GetAttackerData()->GetPID().Unbox()))
    {
        CPrintf(CON_DEBUG, "Variables for Calculate Damage:\n");
        env.DumpAllVars();
    }

    MathVar *badrange = env.Lookup("BadRange");    // BadRange = Target is too far away
    MathVar *badangle = env.Lookup("BadAngle");    // BadAngle = Attacker doesn't aim at enemy
    MathVar *missed   = env.Lookup("Missed");      // Missed   = Attack missed the enemy
    MathVar *dodged   = env.Lookup("Dodged");      // Dodged   = Attack dodged  by enemy
    MathVar *blocked  = env.Lookup("Blocked");     // Blocked  = Attack blocked by enemy
    MathVar *damage   = env.Lookup("FinalDamage"); // Actual damage done, if any

    if (badrange && badrange->GetValue() < 0.0)
        return ATTACK_OUTOFRANGE;
    else if (badangle && badangle->GetValue() < 0.0)
        return ATTACK_BADANGLE;
    else if (missed && missed->GetValue() < 0.0)
        return ATTACK_MISSED;
    else if (dodged && dodged->GetValue() < 0.0)
        return ATTACK_DODGED;
    else if (blocked && blocked->GetValue() < 0.0)
       return ATTACK_BLOCKED;

    event->FinalDamage = damage->GetValue();

    DebugOutput(event, env);

    return ATTACK_DAMAGE;
}