// TODO this is weird to pass slotindex too. needed because hands option though. void TryUseItem(UsableItem itemData, int slotIndex) { // note: no .amount > 0 check because it's either an item or hands // use current item or hands // repeated or one time use while holding mouse down? if (itemData.keepUsingWhileButtonDown || Input.GetMouseButtonDown(0)) { // get the exact look position on whatever object we aim at Vector3 lookAt = look.lookPositionRaycasted; // use it Usability usability = itemData.CanUse(this, slotIndex, lookAt); if (usability == Usability.Usable) { // attack by using the weapon item //Debug.DrawLine(Camera.main.transform.position, lookAt, Color.gray, 1); UseItem(slotIndex, lookAt); // simulate OnUsed locally without waiting for the Rpc to avoid // latency effects: // - usedEndTime would be synced too slowly, hence fire interval // would be too slow on clients // - TryUseItem would be called immediately again afterwards // because useEndTime wouldn't be reset yet due to latency // - decals/muzzle flash would be delayed by latency and feel // bad OnUsedItem(itemData, lookAt); } else if (usability == Usability.Empty) { // play empty sound locally (if any) // -> feels best to only play it when clicking the mouse button once, not while holding if (Input.GetMouseButtonDown(0)) { if (itemData.emptySound) { audioSource.PlayOneShot(itemData.emptySound); } } } // do nothing if on cooldown (just wait) or if not usable at all } }
public void UseItem(int index) { // validate // note: checks durability only if it should be used (if max > 0) if (health.current > 0 && 0 <= index && index < slots.Count && slots[index].amount > 0 && slots[index].item.data is UsableItem) { // use item // note: we don't decrease amount / destroy in all cases because // some items may swap to other slots in .Use() UsableItem itemData = (UsableItem)slots[index].item.data; if (itemData.CanUse(this, index) == Usability.Usable) { // .Use might clear the slot, so we backup the Item first for the Rpc Item item = slots[index].item; itemData.Use(this, index); OnUsedItem(item); } } }
// note: lookAt is available in PlayerLook, but we still pass the exact // uncompressed Vector3 here, because it needs to be PRECISE when shooting, // building structures, etc. public void UseItem(int index, Vector3 lookAt) { // validate if (0 <= index && index < slots.Count && health.current > 0) { // use item at index, or hands // note: we don't decrease amount / destroy in all cases because // some items may swap to other slots in .Use() UsableItem itemData = GetUsableItemOrHands(index); if (itemData.CanUse(this, index, lookAt) == Usability.Usable) { // use it itemData.Use(this, index, lookAt); // reset usage time usageEndTime = Time.time + itemData.cooldown; // RpcUsedItem needs itemData, but we can't send that as Rpc // -> we could send the Item at slots[index], but .data is null // for hands because hands actually live in '.hands' variable // -> we could create a new Item(itemData) and send, but it's // kinda odd that it's different from slot // => only sending hash saves A LOT of bandwidth over time since // this rpc is called very frequently (each weapon shot etc.) // (we reuse Item's hash generation for simplicity) OnUsedItem(itemData, lookAt); } else { // CanUse is checked locally before calling this Cmd, so if we // get here then either our prediction is off (in which case we // really should show a message for easier debugging), or someone // tried to cheat, or there's some networking issue, etc. Debug.LogWarning("UseItem rejected for: " + name + " item=" + itemData.name + "@" + Time.time); } } }