From: <la...@us...> - 2011-07-05 14:19:46
|
Revision: 7497 http://planeshift.svn.sourceforge.net/planeshift/?rev=7497&view=rev Author: landson Date: 2011-07-05 14:19:40 +0000 (Tue, 05 Jul 2011) Log Message: ----------- - Fleshed out range special attacks class, current modeled after melee specials but makes stronger use of range in the scripting. Modified Paths: -------------- soc/2011/combat/src/server/bulkobjects/psAttackDefault.cpp soc/2011/combat/src/server/bulkobjects/psattackmelee.cpp soc/2011/combat/src/server/bulkobjects/psattackrange.cpp soc/2011/combat/src/server/bulkobjects/psattackrange.h Modified: soc/2011/combat/src/server/bulkobjects/psAttackDefault.cpp =================================================================== --- soc/2011/combat/src/server/bulkobjects/psAttackDefault.cpp 2011-07-05 12:37:41 UTC (rev 7496) +++ soc/2011/combat/src/server/bulkobjects/psAttackDefault.cpp 2011-07-05 14:19:40 UTC (rev 7497) @@ -560,9 +560,6 @@ { 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; } @@ -572,9 +569,6 @@ { 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; } Modified: soc/2011/combat/src/server/bulkobjects/psattackmelee.cpp =================================================================== --- soc/2011/combat/src/server/bulkobjects/psattackmelee.cpp 2011-07-05 12:37:41 UTC (rev 7496) +++ soc/2011/combat/src/server/bulkobjects/psattackmelee.cpp 2011-07-05 14:19:40 UTC (rev 7497) @@ -61,8 +61,8 @@ } psAttackMelee::~psAttackMelee() { + ///TODO: Clean up Pointers - } bool psAttackMelee::Load(iResultRow& row) { @@ -206,8 +206,6 @@ angle = 360; angle = (angle/2)*(PI/180); // convert degrees to radians - // divided by two so it's equally on both sides - //CPrintf(CON_DEBUG, "Spell has an effect arc of %1.2f radians to either side of LOS.\n", angle); csArray<gemObject*> nearby = psserver->entitymanager->GetGEM()->FindNearbyEntities(sector, targetPos, radius); for (size_t i = 0; i < nearby.GetSize(); i++) @@ -292,12 +290,12 @@ psserver->GetNPCManager()->QueueAttackPerception(attker, targetNPC); } - // Spell hit successfully. Run the script. - MathEnvironment env; - env.Define("Actor", attacker); - env.Define("Target", target); - env.Define("OrigTarget", origTarget); // the epicentre of an AOE attack/original cast target - env.Define("Power", power); + // Spell hit successfully. Run the script. + MathEnvironment env; + env.Define("Actor", attacker); + env.Define("Target", target); + env.Define("OrigTarget", origTarget); // the epicentre of an AOE attack/original cast target + env.Define("Power", power); outcome->Run(&env); Modified: soc/2011/combat/src/server/bulkobjects/psattackrange.cpp =================================================================== --- soc/2011/combat/src/server/bulkobjects/psattackrange.cpp 2011-07-05 12:37:41 UTC (rev 7496) +++ soc/2011/combat/src/server/bulkobjects/psattackrange.cpp 2011-07-05 14:19:40 UTC (rev 7497) @@ -65,36 +65,284 @@ } bool psAttackRange::Load(iResultRow& row) { + id = row.GetInt("id"); + name = row["name"]; + image = row["image_name"]; + Animation = row["attack_anim"]; + description = row["attack_description"]; + AttackSpeed = row.GetFloat("speed"); + + type = psserver->GetCacheManager()->GetAttackTypeByName(row["attackType"]); + + attackRange = MathExpression::Create(row["range"]); + aoeRadius = MathExpression::Create(row["aoe_radius"]); + aoeAngle = MathExpression::Create(row["aoe_angle"]); + outcome = psserver->GetProgressionManager()->FindScript(row["outcome"]); + CS_ASSERT(aoeRadius && aoeAngle && outcome); + + successChance = MathExpression::Create(row["successchance"]); + + LoadPrerequisiteXML(requirements,row["requirements"]); + + TypeRequirements = new psPrereqOpAttackType(type); + return true; } bool psAttackRange::CanAttack(Client *client) { - return true; + if(TypeRequirements) + { + if(TypeRequirements->Check(client->GetCharacterData())) + { + if(requirements) + return requirements->Check(client->GetCharacterData()); + else + return true; + } + else + return false; + } + else + { + if(requirements) + return requirements->Check(client->GetCharacterData()); + else + return true; + } } bool psAttackRange::IsDualWield(psCharacter* attacker) { - return true; + if(type->OneHand) + return true; + else + return false; } bool psAttackRange::Attack(gemObject *attacker, gemObject* target, INVENTORY_SLOT_NUMBER slot) { + psCharacter *Character=attacker->GetCharacterData(); + psItem *Weapon=Character->Inventory().GetEffectiveWeaponInSlot(slot); + uint32 weaponID = Weapon->GetUID(); + float latency = Weapon->GetLatency(); + int delay = 0; + if(AttackSpeed>0) + delay = (int)(((AttackSpeed+latency)/2)*1000); //each attack has a speed value, each weapon has a latency, the average of the 2 will give the speed of the attack + else + delay = (int)((latency)*1000); //however if the attack don't have a speed value, hten weapon latency is used. + //^this is useful for making some attacks slower, but allowing weapon speed and choice to still be important + + psCombatAttackGameEvent *event; + event = new psCombatAttackGameEvent(delay,this,attacker,target,slot,attacker->GetClientID(),target->GetClientID()); + event->GetAttackerData()->TagEquipmentObject(slot,event->id); + psserver->GetEventManager()->Push(event); + return true; } void psAttackRange::Affect(psCombatAttackGameEvent *event) { + gemObject* attacker = event->attacker; + gemObject* target = event->target; + float range = event->weapon->GetRange(); + // Look for targets + float power = MIN(MaxPower, PowerLevel(attacker->GetCharacterData(), event->weapon, range)); + + MathEnvironment env; + env.Define("Power", power); + env.Define("WeaponSkill", event->attacker->GetCharacterData()->GetSkillRank(event->weapon->GetWeaponType()->skill).Current()); + env.Define("RelatedStat", event->attacker->GetCharacterData()->Stats()[type->related_stat].Current()); + env.Define("Range", range); + float radius = aoeRadius->Evaluate(&env); + float angle = aoeAngle->Evaluate(&env); + //checks the chances of the attack being successful, will implement a script, if it is successful then the attack goes through, if its not then it dont + if(!CalculateSuccess(event, range)) + { + psserver->SendSystemInfo(attacker->GetClientID(),"Your Attack failed");//will flesh this out a bit later + return; + } + bool hasAmmo = true; + if(event->weapon->GetUsesAmmo()) + { + //would ilke to add a dedicated ammo slot, but haven't gotten that far yet + INVENTORY_SLOT_NUMBER otherHand = event->GetWeaponSlot() == PSCHARACTER_SLOT_RIGHTHAND ? PSCHARACTER_SLOT_LEFTHAND:PSCHARACTER_SLOT_RIGHTHAND; + + psItem* otherItem = event->GetAttackerData()->Inventory().GetInventoryItem(otherHand); + if (otherItem == NULL) + { + hasAmmo = false; + } + 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<event->GetAttackerData()->Inventory().GetInventoryIndexCount() && !bFound; i++) + { + psItem* currItem = event->GetAttackerData()->Inventory().GetInventoryIndexItem(i); + if (currItem && + currItem->GetContainerID() == otherItem->GetUID() && + event->weapon->GetAmmoTypeID().In(currItem->GetBaseStats()->GetUID())) + { + otherItem = currItem; + bFound = true; + } + } + if (!bFound) + hasAmmo = false; + } + else if (!event->weapon->GetAmmoTypeID().In(otherItem->GetBaseStats()->GetUID())) + { + hasAmmo = false; + } + + if(hasAmmo) + { + psItem* usedAmmo = event->GetAttackerData()->Inventory().RemoveItemID(otherItem->GetUID(), 1); + if (usedAmmo) + { + usedAmmo->Destroy(); + psserver->GetCharManager()->UpdateItemViews(attacker->GetClient()->GetClientNum()); + } + } + else + return; + + int affectedCount = 0; + if (radius < 0.01f) // single target + { + AffectTarget(attacker, target, target, power); + affectedCount++; + } + else // AOE (Area of Effect) + { + csVector3 attackerPos; + csVector3 targetPos; + float yrot; // in radians + iSector *sector; + + target->GetPosition(targetPos, yrot, sector); + attacker->GetPosition(attackerPos, yrot, sector); + + // directional vector for a line from attacker to original target + csVector3 attackerToTarget; + attackerToTarget = targetPos - attackerPos; + + if (angle <= SMALL_EPSILON || angle > 360) + angle = 360; + + angle = (angle/2)*(PI/180); // convert degrees to radians + + csArray<gemObject*> nearby = psserver->entitymanager->GetGEM()->FindNearbyEntities(sector, targetPos, radius); + for (size_t i = 0; i < nearby.GetSize(); i++) + { + if (!attacker->GetClient()->IsAllowedToAttack(nearby[i])); + continue; + + if (angle < PI) + { + csVector3 attackerToAffected; + nearby[i]->GetPosition(attackerToAffected, sector); + + // 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; + } + + AffectTarget(attacker, target, nearby[i], power); + affectedCount++; + } + + if (affectedCount > 0) + { + psserver->SendSystemInfo(attacker->GetClientID(), "%s affected %d %s.", name.GetData(), affectedCount, (affectedCount == 1) ? "target" : "targets"); + } + else + { + psserver->SendSystemInfo(attacker->GetClientID(), "%s has no effect.", name.GetData()); + } + } + + + } } +bool psAttackRange::CalculateSuccess(psCombatAttackGameEvent *event, float range) +{ -int psAttackRange::CalculateAttack(psCombatAttackGameEvent *event, psItem* subWeapon) + //This is a trial thing, will probably change up later, relies heavily on the script always coming up with a number between 1 and 100, otherwise it breaks(over 100 would work but be pointless) + if(!successChance) + return true; + + MathEnvironment env; + env.Define("WeaponSkill",event->attacker->GetCharacterData()->GetSkillRank(event->weapon->GetWeaponType()->skill).Current()); + env.Define("Range",range); + + float chance = successChance->Evaluate(&env); + + int randomnum = psserver->rng->Get(100); + + if(randomnum <= chance) + return true; + + return false; +} +float psAttackRange::PowerLevel(psCharacter *attacker, psItem* weapon, float range) const { - return 0; + static MathScript *script = NULL; + if (!script) + { + script = psserver->GetMathScriptEngine()->FindScript("CalculateAttackPowerLevel"); + CS_ASSERT(script); + } + MathEnvironment env; + env.Define("RelatedStat", attacker->Stats()[type->related_stat].Current()); + env.Define("WeaponSkill", attacker->GetSkillRank(weapon->GetWeaponType()->skill).Current()); + env.Define("Range", range); + script->Evaluate(&env); + + MathVar *power = env.Lookup("PowerLevel"); + CS_ASSERT(power); + return power->GetValue(); + } - -void psAttackRange::AffectTarget(psCombatAttackGameEvent *event,int attack_result) +bool psAttackMelee::AffectTarget(gemObject* attacker, gemObject* origTarget, gemObject* target, float power) { + if (!attacker->GetClient()->IsAllowedToAttack(target,true)) + return false; + gemActor *attackee = dynamic_cast<gemActor*>(target); + gemActor *attker = dynamic_cast<gemActor*>(attacker); + if (attackee) + { + gemNPC *targetNPC = dynamic_cast<gemNPC*>(target); + if (targetNPC) + psserver->GetNPCManager()->QueueAttackPerception(attker, targetNPC); + } + // Spell hit successfully. Run the script. + MathEnvironment env; + env.Define("Actor", attacker); + env.Define("Target", target); + env.Define("OrigTarget", origTarget); // the epicentre of an AOE attack/original cast target + env.Define("Power", power); + + outcome->Run(&env); + } \ No newline at end of file Modified: soc/2011/combat/src/server/bulkobjects/psattackrange.h =================================================================== --- soc/2011/combat/src/server/bulkobjects/psattackrange.h 2011-07-05 12:37:41 UTC (rev 7496) +++ soc/2011/combat/src/server/bulkobjects/psattackrange.h 2011-07-05 14:19:40 UTC (rev 7497) @@ -69,15 +69,34 @@ bool Attack(gemObject *attacker, gemObject* target, INVENTORY_SLOT_NUMBER slot); void Affect(psCombatAttackGameEvent *event); - int CalculateAttack(psCombatAttackGameEvent *event, psItem* subWeapon = NULL); + + + bool CalculateSuccess(psCombatAttackGameEvent *event, float range); private: - void AffectTarget(psCombatAttackGameEvent *event,int attack_result); - /** - * Queues up the next attack, specific to the default attack at the moment - */ - void QueueAttack(gemObject *attacker,INVENTORY_SLOT_NUMBER weaponslot,gemObject *target,int attackerCID, int targetCID); - MathScript *calc_decay; ///< This is the particular calculation for decay. - MathScript* staminacombat;///< if the player is too tired, stop fighting. We stop if we don't have enough stamina to make an attack with the current stance. + void AffectTarget(gemObject* attacker, gemObject* origTarget, gemObject* target, float power); + + float PowerLevel(psCharacter *attacker, psItem* weapon, float range) const; + float AttackSpeed; + csString Animation; ///< possible attack animation + + csRef< psPrereqOp > requirements; ///< all non weapon based attack requirements. + csRef< psPrereqOp > TypeRequirements; ///< all Attack Type based requirements(not handled as a script) + + int MaxPower; ///< the maximum amount of power an attack can have + + /// AOE Radius: (Power, WaySkill, RelatedStat) -> Meters + MathExpression *aoeRadius; + /// AOE Angle: (Power, WaySkill, RelatedStat) -> Degrees + MathExpression *aoeAngle; + ///Chance of Success + MathExpression *successChance; + ///The Max Range of the attack + MathExpression *attackRange; + /// The progression script: (Power, Caster, Target) -> (side effects) + ProgressionScript *outcome; + + + }; #endif \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |