Arrow Quiver

Umm… I tried duplicating the bow asset WeaponConfig resource and using one without ammunition for the NPC. It worked… but it also worked for the player… I only want it working for the NPC lel

Make sure the one for an NPC has a new ItemID (go to the ItemID in the inspector and delete the entry and a new id will be automatically assigned.

done that, still… the player can use the bow that has no ammunition attached to it (is there something I’m missing…?) :confused:

I don’t even know where to assign the bow weapons he can use

Then the Player has the wrong bow assigned… Is this a bow the player picked up or was it the DefaultWeaponConfig?

AHA, that fixed it… Apparently the Pickup Spawner was assigned to pick up the version that requires Ammunition to work, no wonder it was working for the player. Fixed it!

So in a nutshell, for NPCs:

  • Duplicate the weapon
  • Delete the Unique ID for the duplicate, so a new one is randomly generated
  • Delete the Ammunition Slot
  • if you have any pickups with the weapon, set them to the ‘_Player’ (that’s what I named it) version…

got it!

(When I have another free class I’ll try the two other requests)

And no, there’s only one slot for the player to hold weapons with, under the ‘Fighter.cs’ Script, and I assigned that to ‘Unarmed’

Ahh… this is a bit, aggressively hard tbh. Can you please give me some extra hints? I’m a bit baffled with assigning the foreach loop to the dictionaries (sorry I’m late, I’m trying to work with @bixarrio on the crafting system on the side :sweat_smile: , since I have a serious bug over there that came out of thin air)

Without outright writing it, there’s not much more to hint at…

  • If no currentAmmo, yield break
  • If has currentAmmo yield return a new DroppableItem with the ammo, amount, and the value of the ammo… The callback closure is relatively simple since it will just set the ammo to null and the amount to 0.
public IEnumerable<DroppableItem> GetDroppableItems()

        {

            if (GetAmount() == 0) yield break;

            DroppableItem droppableItem = new DroppableItem {

                item = GetItem(),

                number = GetAmount(),

                relativeValue = GetItem().GetPrice(),

                dropItemCallback = () => {

                   

                }

            };

            yield return droppableItem;

        }

        public void TriggerUpdated()

        {

            quiverUpdated?.Invoke();

        }

I’m just not sure what to put in the lambda function of ‘droppableItem’, since I’m not looping over a dictionary (it’s only one item, there’s nothing to loop over here). Can you please help me there? I don’t think “RemoveAmmunition()” will do the trick of calling them back. I tried it, it just permanently deletes the ammo on death

You’re on the right track.
Inside the Lambda, I would simply put:

currentAmmunition=null;
amount = 0;

I can’t test this immediately, but my guess is, this will call them back on death, and consider their value on death to make sure to store them in the inventory if they’re expensive enough, rather than dropping them, right?

If they are valuable enough to keep, it will stay in the Quiver, because the callback Lambda will not be called.

Alright the system that checks for the arrows’ price on death worked well, but I also noticed that having a mix of arrows and bows involved means sometimes the bow won’t fire, because… well… the arrows in the quiver don’t match the bow, so I want to introduce 2 new ideas to fix this, or at least limit this issue from happening:

  1. If the arrows don’t match the bow, and a specific arrow is wielded, automatically take the bow off, because the ammo and the arrows simply don’t match(something in the lines of: ‘wrong quiver, take that bow off automatically’). I don’t want someone using high level arrows with low level bows for example

  2. Mention the bow that matches the arrows, and the arrows that match the bow, in the item Tooltip that spawns when we hover our mouse on the arrow/bow in the inventory, preferably done automatically through code (I followed the tutorial regarding the spawner tooltip teaser of the inventory Item window in Unity of yours :slight_smile: ). I think this part can use more information… (for further assistance, the code for this lies under ‘InventoryItem.cs’)

  3. For the arrow drops, I want to calculate how many arrows fell to the ground after the enemy is dead (if the player is killed, the arrows that fall will be the less valuable ones as part of his inventory… we already took care of that) and then instantiate them on-enemy-death, so when the enemy dies, the counter calculates how many arrows it took to kill him, and then drop a fraction of them (maybe 80% or so)

If we can get these 3 systems cleared up, my Ranged system will hopefully be clean by then :slight_smile: , so we can move to other systems (I honestly wasn’t expecting all these sort of changes to come to my head)

@Brian_Trotter this is what I’m trying to work on now :slight_smile:

You may be starting to notice why World of Warcraft scrapped their arrow system entirely…

Of the three of these things, only one is even remotely simple, and as we’re well out of course content, fairly low on my todo list…

I can tell you the broad strokes of item 1:
When we try to attack in Fighter, we check to see

  • If the weapon uses a projectile
  • If the weapon has an ammo requirement
  • If the weapon has sufficient ammo

In the case that it has an ammo requirement but doesn’t have sufficient ammo, you can unequip the weapon with two instructions (and it has to be in this order or you’ll lose the weapon)

  • Get the Player’s Inventory and AddToFirstEmptySlot the currentWeaponConfig with a qty of 1
  • Get the Player’s Equipment and remove the item from EquipSlot.Weapon.

That will trigger Fighter to automatically equip the default weapon (Unarmed).

All of the methods needed to do this are already in place on the Inventory and Equipment, and this is your challenge.

For item 2, this is one of those many moving parts scenarios that I can choose to help students struggling with the actual course content or attempt to work on, but not both…

For item 3, again, there are a lot of moving parts… Not saying it can’t be done, but again, it’s low on the priority list or I’m not doing my actual job…

Hi Brian, I gave the first challenge a shot, and my code worked well (although it’s dirty and can use some cleaning), but it only takes the bow off automatically if we try get involved in a fight with the wrong bow and quiver match, and somehow… the extremely distant enemy registers what I just did to him as a hit. Is there anyway we can get this check to happen outside of combat? As in, if the bow was taken off and the NPC attacked the player, then the player runs towards the NPC and play the animation attack against him after that? Not immediately as soon as he takes the bow off (this is a little hard to properly explain…)

I don’t mind either, but I definitely mind the fact that he plays the ‘Unarmed’ combat animation once from a long distance, before realizing he’s too far, and then running to get closer before trying it again, hence why I want the check to occur outside of combat as well, if possible :slight_smile: (an unarmed attack occuring from way beyond the range that was given for it, and it’s actually being registered by the enemy…? come on :stuck_out_tongue_winking_eye: )

If it helps in anyway, here is my updated ‘CheckForProjectileAndSufficientAmmo()’ function:

bool CheckForProjectileAndSufficientAmmo() {

        if (!currentWeaponConfig.HasProjectile()) return true;
        if (currentWeaponConfig.GetAmmunitionItem() == null) return true;
        if (GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) return true;

        if (currentWeaponConfig.GetAmmunitionItem() && !GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) {
            
            GameObject.FindGameObjectWithTag("Player").GetComponent<Inventory>().AddToFirstEmptySlot(currentWeaponConfig, 1, true);
            GameObject.FindGameObjectWithTag("Player").GetComponent<Equipment>().RemoveItem(EquipLocation.Weapon);
            return true;

        }

        return false;

    }

TLDR: The Unarmed combat hit that happens as an immediate result of taking this bow off gets registered by the enemy. I just want my player to run to the enemy before the hit gets executed, so it makes sense to the real-life players, that’s it

Once done with task 1 I’ll give this a shot too, but I’ll need help explaining the logic (I’ll try the coding myself :stuck_out_tongue_winking_eye: ). This one might be crucial, otherwise players will struggle with the mixing and matching of the weapons, and it’ll just be a nightmare for every ranger in the game…

Or we can use the alternate option of explaining everything in the description/naming the bow and arrows the same names, which can be seen in the tooltip… Which, if we can’t get this challenge done, is something I’ll just go ahead and implement (I couldn’t think of a simpler way to do this than get descriptive)

Equipment and Inventory should be on the same GameObject as the Fighter, no need to search for Player… :slight_smile:

I would actually put the check in a few places, but make the Unequip code a separate method.
The CheckForProjectileAndSufficient Ammo() should really just be returning true or false and not taking any actions.

Here’s where I would call CheckForProjectileAndSufficientAmmo():

  • It would be part of my CanAttack() method… if your bow is out of ammo, then you can’t attack, so return false.
  • It would be part of my hit() method, at the end of the section where we spawn the projectile. At this point, if the character is out of ammo this is a good time to call UnequipWeapon() (which would have the add to inventory, remove from equipment code.

Ahh… something went completely wrong, because the NPCs now won’t even bother attacking me… here is my updated code:

Hit():

void Hit() {

        // This function is only used in the AttackBehaviour() function, and 
        //it ensures damage is slightly delayed until the player punches the enemy

        // It also checks if our current weapon has a Projectile or not (in case our player is a ranger)
        // If he does, we launch our projectile (E.g: arrows)
        // If not, just do regular melee damage (as usual)

        if(target == null) return; // ensures no errors occur if the attack animation is playing, but we can't find a target 
                                   // (like when we walk away from an incomplete attack animation: this produces an error, and this is the solution)

        float damage = GetComponent<BaseStats>().GetStat(Stat.Damage);

        // Defending our Player from damage done by target, based on the shield or item he's wearing:
        BaseStats targetBaseStats = target.GetComponent<BaseStats>();
        
        if (targetBaseStats != null) {

                float defence = targetBaseStats.GetStat(Stat.Defence);

                // OLD Damage Formula (Random Hits):

                /* damage *= Random.Range(0f, 1.25f);   // randomizing the hits
                if (Random.Range(0,100) > 95) { damage *= 2.0f; }   // critical hit (i.e: double-hit, but under rare circumstances!) */

                // NEW Damage Formula (Constant):
                //damage /= (1 + defence/damage);
                
                // NEW Damage Formula (Randomized):

                // This formula reduces the effectiveness of weapons as the player gets stronger:
                damage /= (1 + (defence/damage));  // Damage Formula (Power = Power * (1 + defence/enemy damage))
                damage *= UnityEngine.Random.Range(0, 1.25f);  // Randomizing the Damage
                if (UnityEngine.Random.Range(0,100) > 95) damage *= 2.0f;   // Critical hit

        }

        if (currentWeapon.value != null) {

            currentWeapon.value.OnHit();    // returns the type of weapon we just picked up (so we can play audio accordingly)

        }

        /* if (currentWeaponConfig.HasProjectile()) {

            currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, target, gameObject, damage);    // if you have a projectile, launch it (from the instigators' hand, dealing damage based on what it has)
        
        } */

        if (currentWeaponConfig.HasProjectile()) {

            if (currentWeaponConfig.GetAmmunitionItem() && !GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) UnequipRangedWeapon();

            if (currentWeaponConfig.GetAmmunitionItem()) {
                GetComponent<Quiver>().SpendAmmo(1);   
            }

            currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, target, gameObject, damage);

        }

        else {
        
        target.TakeDamage(gameObject, damage);  // Damages the enemy/player, based on our Damage Stats (in 'BaseStats.cs')
        
        }

    }
public bool CanAttack(GameObject combatTarget) 
    {
        
        // if your quiver doesn't have enough ammunition of arrows for the appropriate bow, then you can't attack the enemy:
        if (GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) return false;

        // This function checks if we have a combat target or not. If we don't, we quit the function.
        // If we have a combat target, we let the game know he exists, and he's not dead (hence we can attack him)

        if(combatTarget == null) { return false; }  // no CombatTarget = no fight (avoids the player from attacking innocent NPCs)

        // The only reason to stop attack now, is if the target is out of reach, and out of range (for in-game rangers and mages)
        if (!GetComponent<Mover>().CanMoveTo(combatTarget.transform.position) && !GetIsInRange(combatTarget.transform)) { return false; }
        
        Health targetToTest = combatTarget.GetComponent<Health>();
        return targetToTest != null && !targetToTest.IsDead();  // returns boolean = true if the targetToTest != null and is not dead yet (hence you can attack him)
    }

UnequipRangedWeapon (after adjusting the changes as per your suggestions):

bool CheckForProjectileAndSufficientAmmo() {

        if (!currentWeaponConfig.HasProjectile()) return true;
        if (currentWeaponConfig.GetAmmunitionItem() == null) return true;
        if (GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) return true;

         if (currentWeaponConfig.GetAmmunitionItem() && !GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) {
            
            UnequipRangedWeapon();
            return true;

        }

        return false;

    }

    private void UnequipRangedWeapon() {

            GetComponent<Fighter>().GetComponent<Inventory>().AddToFirstEmptySlot(currentWeaponConfig, 1, true);
            GetComponent<Fighter>().GetComponent<Equipment>().RemoveItem(EquipLocation.Weapon);

        }

And still, the player can perform an Unarmed attack from a rangers’ distance, and the enemy will still register it as a hit (this is the only problem that I really want to fix. Apart from that, all is good with the ranged system)

Under the model I was suggesting, UnequipRangedWeapon() does NOT go inside of CheckForProjectileAndSufficientAmmo(). Note that even if you choose to leave it there, you’re returning TRUE in this case, which would lead to the character attacking even though the bow is being unequipped (and still operating under the bow’s range instead of the unarmed range). If you choose to UnequipRangedWeapon here, then you should return false.

Your enemies aren’t attacking because in CanAttack(), you’re checking ammunition even if the character isn’t using a ranged weapon (or has a quiver). You’ll find that if you give your player a sword, he won’t attack either. Instead, you should be using

if(!CheckForProjectileAndSufficientAmmo()) return false;

Where do you suggest I would place it then?

Oh, i have a question, ‘Fighter.cs’ keeps complaining about a missing ‘GetAmmunitionItem()’ function from ‘WeaponConfig.cs’. Is that part of my coding challenge, or did we miss something out?

Privacy & Terms