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());
}
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());
    MathScript::Destroy(script);
};
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, 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());
}
Exemple #5
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;
}
Exemple #7
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, InterpolateTest)
{
    csString msg("Xordan hits you for ${Elite} damage...");
    MathEnvironment env;
    env.Define("Elite", 1337);
    env.Define("E", 3.1337);
    env.InterpolateString(msg);
    EXPECT_STREQ("Xordan hits you for 1337 damage...", msg.GetData());

    msg = "${E} times, because he's just that ${Elite}";
    env.InterpolateString(msg);
    EXPECT_STREQ("3.13 times, because he's just that 1337", msg.GetData());

    msg = "${Elite}";
    env.InterpolateString(msg);
    EXPECT_STREQ("1337", msg.GetData());

    msg = "${} ${Elite} ${}";
    env.InterpolateString(msg);
    EXPECT_STREQ("${} 1337 ${}", msg.GetData());
}
void psNPCLoader::ReadStats()
{
    csRef<iDocumentNode> xmlnode = npcRoot->GetNode("stats");

    if(!xmlnode)
    {
        CPrintf(CON_WARNING, "Warning: no <stats> tag found\n");
        return;
    }

    MathEnvironment env;
    env.Define("Actor", npc);
    env.Define("STR", xmlnode->GetAttributeValueAsFloat("agi"));
    env.Define("AGI", xmlnode->GetAttributeValueAsFloat("cha"));
    env.Define("END", xmlnode->GetAttributeValueAsFloat("end"));
    env.Define("INT", xmlnode->GetAttributeValueAsFloat("int"));
    env.Define("WILL", xmlnode->GetAttributeValueAsFloat("str"));
    env.Define("CHA", xmlnode->GetAttributeValueAsFloat("wil"));

    psserver->GetCacheManager()->GetSetBaseSkillsScript()->Evaluate(&env);
}
void ProgressionManager::AllocateKillDamage(gemActor *deadActor, int exp)
{
    csArray<gemActor*> attackers;

    // Last timestamp, used for breaking the loop when > 10 secs had gone
    unsigned int lastTimestamp = 0;
    float        totalDamage   = 0; // The denominator for the percentages

    int i;
    // First build list of attackers and determine how far to go back and what total dmg was.
    for (i = (int) deadActor->GetDamageHistoryCount(); i > 0; i--)
    {
        AttackerHistory* history = deadActor->GetDamageHistory(i-1);

        // 15 secs passed
        if (lastTimestamp - history->TimeOfAttack() > 15000 && lastTimestamp != 0)
        {
            Debug1(LOG_COMBAT, 0, "15 secs passed between hits, breaking loop\n");
            break;
        }
        lastTimestamp = history->TimeOfAttack();

        totalDamage += history->Damage();

        bool found = false;

        gemActor* attacker = history->Attacker();
        if (!attacker)
            continue;  // This attacker has disconnected since he did this damage.

        // Have we already added that player?
        for (size_t j = 0; j < attackers.GetSize(); j++)
        {
            if (attackers[j] == attacker)
            {
                found = true;
                break;
            }
        }

        // New player, add to list
        if (!found)
        {
            attackers.Push(attacker);
        }
    }
    int lastHistory = i;

    for(size_t i = 0; i < attackers.GetSize(); i++)
    {
        gemActor* attacker = attackers[i];

        float dmgMade = 0;
        float mod = 0;

        for (int j = (int) deadActor->GetDamageHistoryCount(); j > lastHistory; j--)
        {
            AttackerHistory* history = deadActor->GetDamageHistory(j-1);
            if (history->Attacker() == attacker)
            {
                dmgMade += history->Damage();
            }
        }

        if (!totalDamage)
        {
            Error2("%s was found to have zero totalDamage in damagehistory!", deadActor->GetName());
            continue;
        }
        // Use the latest HP (needs to be redesigned when NPCs can cast heal spells on eachoter)
        mod = dmgMade / totalDamage; // Get a 0.something value or 1 if we did all dmg
        if (mod > 1.0)
            mod = 1.0;

        int final;
        if(exp <= 0) //use automatically generated experience if exp doesn't have a valid value
        {
            MathEnvironment env;
            env.Define("Killer",    attacker); 	 
            env.Define("DeadActor", deadActor);
            calc_dynamic_experience->Evaluate(&env);
            final = env.Lookup("Exp")->GetValue();
        }
Exemple #11
0
void SpellManager::HandleAssembler(MsgEntry* me, Client* client)
{
    psGlyphAssembleMessage mesg;
    mesg.FromClient(me);

    csArray<psItemStats*> assembler;
    for(size_t i = 0; i < GLYPH_ASSEMBLER_SLOTS; i++)
    {
        if(mesg.glyphs[i] != 0)
        {
            psItemStats* stats = cacheManager->GetBasicItemStatsByID(mesg.glyphs[i]);
            if(stats)
                assembler.Push(stats);
        }
    }

    if(assembler.GetSize() == 0)
    {
        psserver->SendSystemError(client->GetClientNum(), "There are no glyphs in the research slots.");
        return;
    }

    if(!client->GetCharacterData()->Inventory().HasPurifiedGlyphs(assembler))
    {
        Error2("Client %i tried to research spell with glyphs he actually doesn't have", client->GetClientNum());
        SendGlyphs(NULL,client);
        return;
    }

    // Is the Glyph Sequence a Valid one?
    psSpell* spell = FindSpell(client, assembler);

    csString description("Your research didn't result in a valid spell.");
    if(spell)
    {
        // Is this spell already in our spellbook?
        psSpell* knownSpell = client->GetCharacterData()->GetSpellByName(spell->GetName());
        if(knownSpell)
        {
            if(mesg.info)
            {
                psGlyphAssembleMessage newmsginfo(client->GetClientNum(), knownSpell->GetName(), knownSpell->GetImage(), knownSpell->GetDescription());
                newmsginfo.SendMessage();
            }
            else
            {
                psserver->SendSystemInfo(client->GetClientNum(), "You know this spell already.");
            }
            return;
        }
        description = "A spell materializes for a second, but you then lose focus. Try again or increase your magic knowledge.";
    }
    if(mesg.info)
        return;

    csString name(" ");
    csString image(" ");

    const bool success = spell && psserver->GetRandom() * 100.0 < spell->ChanceOfResearchSuccess(client->GetCharacterData());
    ProgressionScript* outcome = psserver->GetProgressionManager()->FindScript(success ? "ResearchSpellSuccess" : "ResearchSpellFailure");
    if(outcome)
    {
        MathEnvironment env;
        env.Define("Actor", client->GetActor());
        outcome->Run(&env);
    }

    if(success)
    {
        description = spell->GetDescription();
        name = spell->GetName();
        image = spell->GetImage();
        SaveSpell(client, name);
    }

    // Clear the description, if this is not valid glyph sequence for our player:
    psGlyphAssembleMessage newmsg(client->GetClientNum(), name, image, description);
    newmsg.SendMessage();
}
Exemple #12
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);
    }
}
Exemple #13
0
void CharCreationManager::HandleUploadMessage(MsgEntry* me, Client* client)
{
    Debug1(LOG_NEWCHAR, me->clientnum,"New Character is being created");

    psCharUploadMessage upload(me);

    if(!upload.valid)
    {
        Debug2(LOG_NET,me->clientnum,"Received unparsable psUploadMessage from client %u.",me->clientnum);
        return;
    }

    AccountID acctID = client->GetAccountID();
    if(!acctID.IsValid())
    {
        Error2("Player tried to upload a character to unknown account %s.", ShowID(acctID));

        psCharRejectedMessage reject(me->clientnum);

        psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET);
        psserver->RemovePlayer(me->clientnum,"Could not find your account.");
        return;
    }

    // Check to see if the player already has 4 accounts;
    csString query;
    query.Format("SELECT id FROM characters WHERE account_id=%d", acctID.Unbox());
    Result result(db->Select(query));
    if(result.IsValid() && result.Count() >= CHARACTERS_ALLOWED)
    {
        psserver->RemovePlayer(me->clientnum,"At your character limit.");
        return;
    }

    csString playerName =  upload.name;
    csString lastName =  upload.lastname;

    playerName = NormalizeCharacterName(playerName);
    lastName = NormalizeCharacterName(lastName);

    // Check banned names
    if(psserver->GetCharManager()->IsBanned(playerName))
    {
        csString error;
        error.Format("The name %s is banned", playerName.GetData());
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::RESERVED_NAME,
                                     (char*)error.GetData());
        reject.SendMessage();
        return;
    }

    if(psserver->GetCharManager()->IsBanned(lastName))
    {
        csString error;
        error.Format("The lastname %s is banned", lastName.GetData());
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::RESERVED_NAME,
                                     (char*)error.GetData());
        reject.SendMessage();
        return;
    }

    Debug3(LOG_NEWCHAR, me->clientnum,"Got player firstname (%s) and lastname (%s)\n",playerName.GetData(), lastName.GetData());

    ///////////////////////////////////////////////////////////////
    //  Check to see if the player name is valid
    ///////////////////////////////////////////////////////////////
    if(playerName.Length() == 0 || !FilterName(playerName))
    {
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::NON_LEGAL_NAME,
                                     "The name you specifed is not a legal player name.");

        psserver->GetEventManager()->SendMessage(reject.msg);
        return;
    }

    if(lastName.Length() != 0 && !FilterName(lastName))
    {
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::NON_LEGAL_NAME,
                                     "The name you specifed is not a legal lastname.");

        psserver->GetEventManager()->SendMessage(reject.msg);
        return;
    }

    Debug2(LOG_NEWCHAR, me->clientnum,"Checking player firstname '%s'..\n",playerName.GetData());
    ///////////////////////////////////////////////////////////////
    //  Check to see if the character name is unique in 'characters'.
    ///////////////////////////////////////////////////////////////
    if(!IsUnique(playerName))
    {
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::NON_UNIQUE_NAME,
                                     "The firstname you specifed is not unique.");

        psserver->GetEventManager()->SendMessage(reject.msg);
        return;
    }

    if(lastName.Length())
    {
        Debug2(LOG_NEWCHAR, me->clientnum,"Checking player lastname '%s'..\n",lastName.GetData());
        if(!IsLastNameAvailable(lastName, acctID))
        {
            psCharRejectedMessage reject(me->clientnum,
                                         psCharRejectedMessage::NON_UNIQUE_NAME,
                                         "The lastname you specifed is not unique.");

            psserver->GetEventManager()->SendMessage(reject.msg);
            return;
        }
    }
    ///////////////////////////////////////////////////////////////
    //  Check to see if the character name is on the reserve list.
    ///////////////////////////////////////////////////////////////
    int reservedName = IsReserved(playerName, acctID);
    if(reservedName == NAME_RESERVED)
    {
        csString error;
        error.Format("The name %s is reserved", playerName.GetData());
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::RESERVED_NAME,
                                     (char*)error.GetData());

        psserver->GetEventManager()->SendMessage(reject.msg);
        return;
    }


    csString error;
    if(!psserver->charCreationManager->Validate(upload, error))
    {
        error.Append(", your creation choices are invalid.");
        psCharRejectedMessage reject(me->clientnum,
                                     psCharRejectedMessage::INVALID_CREATION,
                                     (char*)error.GetData());

        reject.SendMessage();
        return;
    }

    ///////////////////////////////////////////////////////////////
    //  Create the psCharacter structure for the player.
    ///////////////////////////////////////////////////////////////
    psCharacter* chardata=new psCharacter();
    chardata->SetCharType(PSCHARACTER_TYPE_PLAYER);
    chardata->SetFullName(playerName,lastName);
    chardata->SetCreationInfo(upload.bio);

    psRaceInfo* raceinfo=psserver->GetCacheManager()->GetRaceInfoByNameGender(upload.race, (PSCHARACTER_GENDER)upload.gender);
    if(raceinfo==NULL)
    {
        Error3("Invalid race/gender combination on character creation:  Race='%d' Gender='%d'", upload.race, upload.gender);
        psCharRejectedMessage reject(me->clientnum);
        psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET);
        psserver->RemovePlayer(me->clientnum,"Player tried to create an invalid race/gender.");
        delete chardata;
        return;
    }
    chardata->SetRaceInfo(raceinfo);
    chardata->SetHitPoints(50.0);
    chardata->GetMaxHP().SetBase(0.0);
    chardata->GetMaxMana().SetBase(0.0);

    //range is unused here
    float x,y,z,yrot,range;
    const char* sectorname;
    InstanceID newinstance = DEFAULT_INSTANCE;

    //get the option entries for tutorial from the server options. Note it's tutorial:variousdata
    optionEntry* tutorialEntry = psserver->GetCacheManager()->getOptionSafe("tutorial","");
    sectorname = tutorialEntry->getOptionSafe("sectorname", "tutorial")->getValue();

    psSectorInfo* sectorinfo = psserver->GetCacheManager()->GetSectorInfoByName(sectorname);

    if(!sectorinfo || PlayerHasFinishedTutorial(acctID, sectorinfo->uid))
    {
        raceinfo->GetStartingLocation(x,y,z,yrot,range,sectorname);
        sectorinfo = psserver->GetCacheManager()->GetSectorInfoByName(sectorname);

        //As we aren't going in the tutorial disable the tutorial help messages disable them
        for(int i = 0; i < TutorialManager::TUTOREVENTTYPE_COUNT; i++)
            chardata->CompleteHelpEvent(i);
    }
    else
    {
        // Try tutorial level first.
        x = tutorialEntry->getOptionSafe("sectorx", "-225.37")->getValueAsDouble();
        y = tutorialEntry->getOptionSafe("sectory", "-21.32")->getValueAsDouble();
        z = tutorialEntry->getOptionSafe("sectorz", "26.79")->getValueAsDouble();
        yrot = tutorialEntry->getOptionSafe("sectoryrot", "-2.04")->getValueAsDouble();
    }

    bool sectorFound = true;

    if(sectorinfo && EntityManager::GetSingleton().FindSector(sectorinfo->name) == NULL)
    {
        Error2("Sector='%s' found but no map file was detected for it. Using NPCroom1", sectorname);
        sectorinfo = psserver->GetCacheManager()->GetSectorInfoByName("NPCroom1");
        if(sectorinfo && EntityManager::GetSingleton().FindSector(sectorinfo->name) == NULL)
        {
            Error1("NPCroom1 failed - Critical");
            sectorFound = false;
        }
        else if(sectorinfo && EntityManager::GetSingleton().FindSector(sectorinfo->name))
        {
            sectorFound = true;
        }
        else
        {
            sectorFound = false;
        }
    }
    else if(sectorinfo && EntityManager::GetSingleton().FindSector(sectorinfo->name))
    {
        sectorFound = true;
    }
    else
    {
        sectorFound = false;
    }


    if(!sectorFound)
    {
        Error2("Unresolvable starting sector='%s'", sectorname);
        psCharRejectedMessage reject(me->clientnum);
        psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET);
        psserver->RemovePlayer(me->clientnum,"No starting Sector.");
        delete chardata;
        return;
    }

    chardata->SetLocationInWorld(newinstance, sectorinfo, x, y, z, yrot);

    psTrait* trait;
//    CPrintf(CON_DEBUG, "Trait: %d\n", upload.selectedFace );
    trait = psserver->GetCacheManager()->GetTraitByID(upload.selectedFace);
    if(trait)
        chardata->SetTraitForLocation(trait->location, trait);

    trait = psserver->GetCacheManager()->GetTraitByID(upload.selectedHairStyle);
    if(trait)
        chardata->SetTraitForLocation(trait->location, trait);

    trait = psserver->GetCacheManager()->GetTraitByID(upload.selectedBeardStyle);
    if(trait)
        chardata->SetTraitForLocation(trait->location, trait);

    trait = psserver->GetCacheManager()->GetTraitByID(upload.selectedHairColour);
    if(trait)
        chardata->SetTraitForLocation(trait->location, trait);

    trait = psserver->GetCacheManager()->GetTraitByID(upload.selectedSkinColour);
    if(trait)
        chardata->SetTraitForLocation(trait->location, trait);

    gemActor* actor = new gemActor(gemSupervisor, cacheManager, entityManager, chardata,
                                   raceinfo->mesh_name,
                                   newinstance,
                                   EntityManager::GetSingleton().FindSector(sectorinfo->name),
                                   csVector3(x,y,z),yrot,
                                   client->GetClientNum());

    actor->SetupCharData();

    if(!upload.verify)
    {
        if(!psServer::CharacterLoader.NewCharacterData(acctID,chardata))
        {
            Error1("Character could not be created.");
            psCharRejectedMessage reject(me->clientnum);
            psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET);
            psserver->RemovePlayer(me->clientnum,"Your character could not be created in the database.");
            delete chardata;
            return;
        }
    }

    // Check to see if a path name was set. If so we will use that to generate
    // the character starting stats and skills.
    if(upload.path != "None")
    {
        // Progression Event name is PATH_PathName
        csString name("PATH_");
        name.Append(upload.path);
        ProgressionScript* script = psserver->GetProgressionManager()->FindScript(name.GetData());
        if(script)
        {
            // The script uses the race base character points to calculate starting stats.
            MathEnvironment env;
            env.Define("CharPoints", raceinfo->initialCP);
            env.Define("Actor", actor);
            script->Run(&env);
        }
    }
    else
    {
        //int cpUsage = psserver->charCreationManager->CalculateCPChoice( upload.choices ) +
        //              psserver->charCreationManager->CalculateCPLife(upload.lifeEvents );
        for(size_t ci = 0; ci < upload.choices.GetSize(); ci++)
        {
            CharCreationManager::CreationChoice* choice = psserver->charCreationManager->FindChoice(upload.choices[ci]);
            if(choice)
            {
                csString name(psserver->charCreationManager->FindChoice(upload.choices[ci])->name.GetData());
                Debug3(LOG_NEWCHAR, me->clientnum,"Choice: %s Creation Script: %s", name.GetData(), choice->eventScript.GetData());

                MathEnvironment env;
                env.Define("Actor", actor);
                if(choice->choiceArea == FATHER_JOB || choice->choiceArea == MOTHER_JOB)
                {
                    int modifier = (choice->choiceArea == FATHER_JOB) ? upload.fatherMod : upload.motherMod;
                    if(modifier > 3 || modifier < 1)
                        modifier = 1;

                    env.Define("ParentStatus", modifier);
                }
                ProgressionScript* script = psserver->GetProgressionManager()->FindScript(choice->eventScript);
                if(script)
                    script->Run(&env);
            }
            else
            {
                Debug2(LOG_NEWCHAR, me->clientnum,"Character Choice %d not found\n", upload.choices[ci]);
            }
        }
        for(size_t li = 0; li < upload.lifeEvents.GetSize(); li++)
        {
            MathEnvironment env;
            env.Define("Actor", actor);
            LifeEventChoiceServer* lifeEvent = psserver->charCreationManager->FindLifeEvent(upload.lifeEvents[li]);
            if(!lifeEvent)
            {
                Error2("No LifeEvent Script found: %d", upload.lifeEvents[li]);
                continue;
            }

            csString scriptName(lifeEvent->eventScript.GetData());
            Debug2(LOG_NEWCHAR, me->clientnum, "LifeEvent Script: %s", scriptName.GetDataSafe());

            ProgressionScript* script = psserver->GetProgressionManager()->FindScript(scriptName);
            if(script)
                script->Run(&env);
        }
    }

    if(!upload.verify)
    {

        if(reservedName == NAME_RESERVED_FOR_YOU)
        {
            AssignScript(chardata);
        }
        // This function recalculates the Max HP, Mana and Stamina of the new character
        chardata->RecalculateStats();

        // Make sure the new player have HP, Mana and Samina that was calculated
        chardata->SetHitPoints(chardata->GetMaxHP().Base());
        chardata->SetMana(chardata->GetMaxMana().Base());
        chardata->SetStamina(chardata->GetMaxPStamina().Base(),true);
        chardata->SetStamina(chardata->GetMaxMStamina().Base(),false);


        psServer::CharacterLoader.SaveCharacterData(chardata, actor);
        Debug1(LOG_NEWCHAR,me->clientnum,"Player Creation Complete");

        // Remove cached objects to make sure that the client gets a fresh character
        // list from the database if it logs out and in within 2 minutes.
        iCachedObject* obj = psserver->GetCacheManager()->RemoveFromCache(psserver->GetCacheManager()->MakeCacheName("list",client->GetAccountID().Unbox()));
        if(obj)
        {
            obj->ProcessCacheTimeout();
            obj->DeleteSelf();
        }
        obj = psserver->GetCacheManager()->RemoveFromCache(psserver->GetCacheManager()->MakeCacheName("auth",client->GetAccountID().Unbox()));
        if(obj)
        {
            obj->ProcessCacheTimeout();
            obj->DeleteSelf();
        }

        // Here everything is ok
        client->SetPID(chardata->GetPID());
        client->SetName(playerName);

        psCharApprovedMessage app(me->clientnum);
        if(app.valid)
            psserver->GetEventManager()->SendMessage(app.msg);
        else
            Bug2("Could not create valid psCharApprovedMessage for client %u.\n",me->clientnum);
    }
    else
    {
        psCharVerificationMesg mesg(me->clientnum);
        size_t z;
        //unfortunately count goes out of valid area so we need to check on charisma

        for(z = 0; z < psserver->GetCacheManager()->GetSkillAmount(); z++)
        {
            unsigned int rank = chardata->Skills().GetSkillRank((PSSKILL) z).Base();

            psSkillInfo* info = psserver->GetCacheManager()->GetSkillByID(z);
            csString name("Not found");
            if(info)
                name.Replace(info->name);

            if(rank > 0)
            {
                if(z >= PSSKILL_AGI && z <= PSSKILL_WILL)
                {
                    mesg.AddStat(rank, name);
                }
                else
                {
                    mesg.AddSkill(rank, name);
                }
            }
        }
        mesg.Construct();
        mesg.SendMessage();
    }

    delete actor;

    if(!upload.verify)
    {
        // Remove char data from the cache
        iCachedObject* obj = psserver->GetCacheManager()->RemoveFromCache(psserver->GetCacheManager()->MakeCacheName("char", chardata->GetPID().Unbox()));
        if(obj)
        {
            obj->ProcessCacheTimeout();
            obj->DeleteSelf();
        }
    }
}
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;
        }
    }
}
/**
 * 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;
}