Visual low priority item:
When you throw a weapon that has charges, it throws the weapon, delete the charge but there is nothing showing in your hand after the initial throw. You can still hit your Ranged attack button and throw it again thus deleting another charge etc but the weapon never shows back up visually in your left hand.
Now if you have two of those weapons. Put one in your range slot and the other in the ammo slot. After throwing it, its still in your off hand.
Weapons with no charges, that pulls the ammo from your inventory, has the same problem.
Stacked throwing Weapons do not have this problem (last i checked they didn't).
Am I correct this is code in special_attacks.cpp that handles the Throwing for players?
Code:
void Client::ThrowingAttack(Mob* other) { //old was 51
//conditions to use an attack checked before we are called
//make sure the attack and ranged timers are up
//if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow
if((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())) {
mlog(COMBAT__RANGED, "Throwing attack canceled. Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime());
Message(0, "Error: Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime());
return;
}
int ammo_slot = SLOT_RANGE;
const ItemInst* RangeWeapon = m_inv[SLOT_RANGE];
if (!RangeWeapon || !RangeWeapon->IsType(ItemClassCommon)) {
mlog(COMBAT__RANGED, "Ranged attack canceled. Missing or invalid ranged weapon (%d) in slot %d", GetItemIDAt(SLOT_RANGE), SLOT_RANGE);
Message(0, "Error: Rangeweapon: GetItem(%i)==0, you have nothing to throw!", GetItemIDAt(SLOT_RANGE));
return;
}
const Item_Struct* item = RangeWeapon->GetItem();
if(item->ItemType != ItemTypeThrowing && item->ItemType != ItemTypeThrowingv2) {
mlog(COMBAT__RANGED, "Ranged attack canceled. Ranged item %d is not a throwing weapon. type %d.", item->ItemType);
Message(0, "Error: Rangeweapon: GetItem(%i)==0, you have nothing useful to throw!", GetItemIDAt(SLOT_RANGE));
return;
}
mlog(COMBAT__RANGED, "Throwing %s (%d) at %s", item->Name, item->ID, target->GetName());
if(RangeWeapon->GetCharges() == 1) {
//first check ammo
const ItemInst* AmmoItem = m_inv[SLOT_AMMO];
if(AmmoItem != NULL && AmmoItem->GetID() == RangeWeapon->GetID()) {
//more in the ammo slot, use it
RangeWeapon = AmmoItem;
ammo_slot = SLOT_AMMO;
mlog(COMBAT__RANGED, "Using ammo from ammo slot, stack at slot %d. %d in stack.", ammo_slot, RangeWeapon->GetCharges());
} else {
//look through our inventory for more
sint32 aslot = m_inv.HasItem(item->ID, 1, invWherePersonal);
if(aslot != SLOT_INVALID) {
//the item wont change, but the instance does, not that it matters
ammo_slot = aslot;
RangeWeapon = m_inv[aslot];
mlog(COMBAT__RANGED, "Using ammo from inventory slot, stack at slot %d. %d in stack.", ammo_slot, RangeWeapon->GetCharges());
}
}
}
int range = item->Range +50/*Fudge it a little, client will let you hit something at 0 0 0 when you are at 205 0 0*/;
mlog(COMBAT__RANGED, "Calculated bow range to be %.1f", range);
range *= range;
if(DistNoRootNoZ(*target) > range) {
mlog(COMBAT__RANGED, "Throwing attack out of range... client should catch this. (%f > %f).\n", DistNoRootNoZ(*target), range);
//target is out of range, client does a message
return;
}
else if(DistNoRootNoZ(*target) < (RuleI(Combat, MinRangedAttackDist)*RuleI(Combat, MinRangedAttackDist))){
return;
}
if(!IsAttackAllowed(target) ||
IsCasting() ||
IsSitting() ||
(DivineAura() && !GetGM()) ||
IsStunned() ||
IsMezzed() ||
(GetAppearance() == eaDead)){
return;
}
//send item animation, also does the throw animation
SendItemAnimation(target, item, THROWING);
// Hit?
if (!target->CheckHitChance(this, THROWING, 13)) {
mlog(COMBAT__RANGED, "Ranged attack missed %s.", target->GetName());
target->Damage(this, 0, SPELL_UNKNOWN, THROWING);
} else {
mlog(COMBAT__RANGED, "Throwing attack hit %s.", target->GetName());
sint16 WDmg = GetWeaponDamage(target, item);
if(WDmg > 0)
{
sint32 TotalDmg = 0;
int minDmg = 1;
uint16 MaxDmg = GetThrownDamage(WDmg, TotalDmg, minDmg);
mlog(COMBAT__RANGED, "Item DMG %d. Max Damage %d. Hit for damage %d", WDmg, MaxDmg, TotalDmg);
target->MeleeMitigation(this, TotalDmg, minDmg);
ApplyMeleeDamageBonus(THROWING, TotalDmg);
TryCriticalHit(target, THROWING, TotalDmg);
if(TotalDmg > 0)
{
sint32 hate = (2*WDmg);
target->AddToHateList(this, hate, 0, false);
}
target->Damage(this, TotalDmg, SPELL_UNKNOWN, THROWING);
}
else
target->Damage(this, -5, SPELL_UNKNOWN, THROWING);
}
if(target && (target->GetHP() > -10))
TryWeaponProc(RangeWeapon, target);
//consume ammo
DeleteItemInInventory(ammo_slot, 1, true);
CheckIncreaseSkill(THROWING);
//break invis when you attack
if(invisible) {
mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack.");
BuffFadeByEffect(SE_Invisibility);
BuffFadeByEffect(SE_Invisibility2);
invisible = false;
}
if(invisible_undead) {
mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack.");
BuffFadeByEffect(SE_InvisVsUndead);
BuffFadeByEffect(SE_InvisVsUndead2);
invisible_undead = false;
}
if(invisible_animals){
mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack.");
BuffFadeByEffect(SE_InvisVsAnimals);
invisible_animals = false;
}
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer;
sa_out->spawn_id = GetID();
sa_out->type = 0x03;
sa_out->parameter = 0;
entity_list.QueueClients(this, outapp, true);
safe_delete(outapp);
}
}