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