From 43f3e8f0e7af14eb63eabf638d40022d9ebfcc53 Mon Sep 17 00:00:00 2001 From: hz Date: Mon, 9 Jun 2025 23:05:40 -0500 Subject: [PATCH] Implemented a property that will allow a spawnable to be acted on a variable number of times before being consumed. --- Controllers/ItemController.cs | 13 ++++++-- Controllers/PlayerController.cs | 13 ++++++++ Controllers/SpawnableController.cs | 4 ++- GameAuthApi.Tests/ItemControllerTests.cs | 6 ++-- GameAuthApi.Tests/PlayerControllerTests.cs | 32 +++++++++++++++++++ GameAuthApi.Tests/SpawnableControllerTests.cs | 3 +- Models/DTOs/CreateItemRequest.cs | 3 ++ Models/DTOs/CreateSpawnableRequest.cs | 3 ++ Models/DTOs/UpdateItemRequest.cs | 3 ++ Models/DTOs/UpdateSpawnableRequest.cs | 3 ++ Models/Item.cs | 3 ++ Models/Spawnable.cs | 4 +++ 12 files changed, 83 insertions(+), 7 deletions(-) diff --git a/Controllers/ItemController.cs b/Controllers/ItemController.cs index 908e9a5..134601a 100644 --- a/Controllers/ItemController.cs +++ b/Controllers/ItemController.cs @@ -12,11 +12,13 @@ public class ItemController : ControllerBase { private readonly ItemService _itemService; private readonly LocationService _locationService; + private readonly SpawnableService _spawnableService; - public ItemController(ItemService itemService, LocationService locationService) + public ItemController(ItemService itemService, LocationService locationService, SpawnableService spawnableService) { _itemService = itemService; _locationService = locationService; + _spawnableService = spawnableService; } [HttpPost] @@ -27,10 +29,16 @@ public class ItemController : ControllerBase if (location == null) return BadRequest("Invalid location ID."); + var uses = req.UsesRemaining; + var spawnable = await _spawnableService.GetByNameAsync(req.Name.ToLower()); + if (spawnable != null && spawnable.Uses > 0) + uses = spawnable.Uses; + var item = new Item { Name = req.Name, - LocationId = req.LocationId + LocationId = req.LocationId, + UsesRemaining = uses }; await _itemService.CreateAsync(item); @@ -58,6 +66,7 @@ public class ItemController : ControllerBase item.Name = req.Name; item.LocationId = req.LocationId; + item.UsesRemaining = req.UsesRemaining; await _itemService.UpdateAsync(item); return Ok(item); } diff --git a/Controllers/PlayerController.cs b/Controllers/PlayerController.cs index f58baf4..ebb19bb 100644 --- a/Controllers/PlayerController.cs +++ b/Controllers/PlayerController.cs @@ -346,6 +346,19 @@ public class PlayerController : ControllerBase await _locationService.RemoveItemFromLocationAsync(location.Id, itemId); await _itemService.DeleteAsync(itemId); } + else if (item.UsesRemaining > 0) + { + item.UsesRemaining--; + if (item.UsesRemaining <= 0) + { + await _locationService.RemoveItemFromLocationAsync(location.Id, itemId); + await _itemService.DeleteAsync(itemId); + } + else + { + await _itemService.UpdateAsync(item); + } + } return Ok(outcome.Result); } diff --git a/Controllers/SpawnableController.cs b/Controllers/SpawnableController.cs index 331e535..6f9ebe4 100644 --- a/Controllers/SpawnableController.cs +++ b/Controllers/SpawnableController.cs @@ -27,7 +27,8 @@ public class SpawnableController : ControllerBase SpawnRate = req.SpawnRate, CollectAction = req.CollectAction, CollectResults = req.CollectResults, - ConsumeOnSuccess = req.ConsumeOnSuccess + ConsumeOnSuccess = req.ConsumeOnSuccess, + Uses = req.Uses }; await _spawnableService.CreateAsync(spawnable); @@ -47,6 +48,7 @@ public class SpawnableController : ControllerBase spawnable.CollectAction = req.CollectAction; spawnable.CollectResults = req.CollectResults; spawnable.ConsumeOnSuccess = req.ConsumeOnSuccess; + spawnable.Uses = req.Uses; await _spawnableService.UpdateAsync(spawnable); return Ok(spawnable); diff --git a/GameAuthApi.Tests/ItemControllerTests.cs b/GameAuthApi.Tests/ItemControllerTests.cs index 2fbcded..2b73fed 100644 --- a/GameAuthApi.Tests/ItemControllerTests.cs +++ b/GameAuthApi.Tests/ItemControllerTests.cs @@ -18,7 +18,7 @@ public class ItemControllerTests { var loc = new Mock(config); loc.Setup(l => l.GetByIdAsync("bad")).ReturnsAsync((Location?)null); - var controller = new ItemController(new Mock(config).Object, loc.Object); + var controller = new ItemController(new Mock(config).Object, loc.Object, new Mock(config).Object); var result = await controller.Create(new CreateItemRequest { LocationId = "bad", Name = "n" }); @@ -30,7 +30,7 @@ public class ItemControllerTests { var svc = new Mock(config); svc.Setup(i => i.GetByIdAsync("id")).ReturnsAsync((Item?)null); - var controller = new ItemController(svc.Object, new Mock(config).Object); + var controller = new ItemController(svc.Object, new Mock(config).Object, new Mock(config).Object); var result = await controller.Update("id", new UpdateItemRequest { LocationId="loc", Name="x" }); @@ -42,7 +42,7 @@ public class ItemControllerTests { var svc = new Mock(config); svc.Setup(i => i.GetByIdAsync("id")).ReturnsAsync((Item?)null); - var controller = new ItemController(svc.Object, new Mock(config).Object); + var controller = new ItemController(svc.Object, new Mock(config).Object, new Mock(config).Object); var result = await controller.Delete("id"); diff --git a/GameAuthApi.Tests/PlayerControllerTests.cs b/GameAuthApi.Tests/PlayerControllerTests.cs index 0da2ed2..dbb145a 100644 --- a/GameAuthApi.Tests/PlayerControllerTests.cs +++ b/GameAuthApi.Tests/PlayerControllerTests.cs @@ -186,4 +186,36 @@ public class PlayerControllerTests locSvc.Verify(); itemSvc.Verify(); } + + [Fact] + public async Task PerformAction_DecrementsUses() + { + var playerSvc = new Mock(cfg); + var player = new Player { Id = "p", UserId = "u", LocationId = "loc", InventoryLocationId = "inv" }; + playerSvc.Setup(p => p.GetByUserIdAsync("u")).ReturnsAsync(player); + var locSvc = new Mock(cfg); + var loc = new Location { Id = "loc", Content = new List { "i" } }; + locSvc.Setup(l => l.GetByIdAsync("loc")).ReturnsAsync(loc); + var itemSvc = new Mock(cfg); + var item = new Item { Id = "i", Name = "wood", UsesRemaining = 2 }; + itemSvc.Setup(i => i.GetByIdAsync("i")).ReturnsAsync(item); + itemSvc.Setup(i => i.UpdateAsync(It.Is(it => it.UsesRemaining == 1))).Returns(Task.CompletedTask).Verifiable(); + var spawnSvc = new Mock(cfg); + spawnSvc.Setup(s => s.GetByNameAsync("wood")).ReturnsAsync(new Spawnable + { + Name = "wood", + CollectAction = "chop", + CollectResults = new List { new() { Result = "log", SuccessRate = 100 } }, + ConsumeOnSuccess = false + }); + + var controller = new PlayerController(playerSvc.Object, locSvc.Object, spawnSvc.Object, itemSvc.Object) + { + ControllerContext = ContextWithUser("u") + }; + + await controller.PerformAction(new PerformActionRequest { Action = "chop" }); + + itemSvc.Verify(); + } } diff --git a/GameAuthApi.Tests/SpawnableControllerTests.cs b/GameAuthApi.Tests/SpawnableControllerTests.cs index e876573..fdad694 100644 --- a/GameAuthApi.Tests/SpawnableControllerTests.cs +++ b/GameAuthApi.Tests/SpawnableControllerTests.cs @@ -22,7 +22,8 @@ public class SpawnableControllerTests sp.CollectResults.Count == 2 && sp.CollectResults[0].Result == "resource" && sp.CollectResults[0].SuccessRate == 75 && - sp.ConsumeOnSuccess))) + sp.ConsumeOnSuccess && + sp.Uses == 0))) .Returns(Task.CompletedTask).Verifiable(); var controller = new SpawnableController(svc.Object); diff --git a/Models/DTOs/CreateItemRequest.cs b/Models/DTOs/CreateItemRequest.cs index 1c658f6..f3477d0 100644 --- a/Models/DTOs/CreateItemRequest.cs +++ b/Models/DTOs/CreateItemRequest.cs @@ -5,4 +5,7 @@ public class CreateItemRequest public string LocationId { get; set; } public string Name { get; set; } + + // Optional number of uses before the item is removed. + public int UsesRemaining { get; set; } = 0; } diff --git a/Models/DTOs/CreateSpawnableRequest.cs b/Models/DTOs/CreateSpawnableRequest.cs index 83c64ad..b5b4e72 100644 --- a/Models/DTOs/CreateSpawnableRequest.cs +++ b/Models/DTOs/CreateSpawnableRequest.cs @@ -15,4 +15,7 @@ public class CreateSpawnableRequest // Should the spawnable be removed after a successful action? public bool ConsumeOnSuccess { get; set; } = false; + + // Number of successful actions allowed before it disappears (0 for unlimited) + public int Uses { get; set; } = 0; } diff --git a/Models/DTOs/UpdateItemRequest.cs b/Models/DTOs/UpdateItemRequest.cs index f5131a2..df4214e 100644 --- a/Models/DTOs/UpdateItemRequest.cs +++ b/Models/DTOs/UpdateItemRequest.cs @@ -4,4 +4,7 @@ public class UpdateItemRequest { public string LocationId { get; set; } public string Name { get; set; } + + // Adjust remaining uses for the item + public int UsesRemaining { get; set; } = 0; } diff --git a/Models/DTOs/UpdateSpawnableRequest.cs b/Models/DTOs/UpdateSpawnableRequest.cs index ce03fd7..651d5a2 100644 --- a/Models/DTOs/UpdateSpawnableRequest.cs +++ b/Models/DTOs/UpdateSpawnableRequest.cs @@ -11,4 +11,7 @@ public class UpdateSpawnableRequest // Should the spawnable be removed after a successful action? public bool ConsumeOnSuccess { get; set; } = false; + + // Number of successful actions allowed before it disappears (0 for unlimited) + public int Uses { get; set; } = 0; } diff --git a/Models/Item.cs b/Models/Item.cs index 662ec28..1207b8d 100644 --- a/Models/Item.cs +++ b/Models/Item.cs @@ -13,4 +13,7 @@ public class Item public string LocationId { get; set; } public string Name { get; set; } + + // Remaining uses before this item disappears. 0 means unlimited. + public int UsesRemaining { get; set; } = 0; } diff --git a/Models/Spawnable.cs b/Models/Spawnable.cs index bbc68a5..8ee4992 100644 --- a/Models/Spawnable.cs +++ b/Models/Spawnable.cs @@ -21,4 +21,8 @@ public class Spawnable // If true, the spawnable item is removed after a successful action public bool ConsumeOnSuccess { get; set; } = false; + + // Number of times this spawnable can be successfully acted upon + // before it is removed. A value of 0 means unlimited uses. + public int Uses { get; set; } = 0; }