Implemented a property that will allow a spawnable to be acted on a variable number of times before being consumed.

This commit is contained in:
hz
2025-06-09 23:05:40 -05:00
parent 5a27adf034
commit 43f3e8f0e7
12 changed files with 83 additions and 7 deletions
+11 -2
View File
@@ -12,11 +12,13 @@ public class ItemController : ControllerBase
{ {
private readonly ItemService _itemService; private readonly ItemService _itemService;
private readonly LocationService _locationService; private readonly LocationService _locationService;
private readonly SpawnableService _spawnableService;
public ItemController(ItemService itemService, LocationService locationService) public ItemController(ItemService itemService, LocationService locationService, SpawnableService spawnableService)
{ {
_itemService = itemService; _itemService = itemService;
_locationService = locationService; _locationService = locationService;
_spawnableService = spawnableService;
} }
[HttpPost] [HttpPost]
@@ -27,10 +29,16 @@ public class ItemController : ControllerBase
if (location == null) if (location == null)
return BadRequest("Invalid location ID."); 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 var item = new Item
{ {
Name = req.Name, Name = req.Name,
LocationId = req.LocationId LocationId = req.LocationId,
UsesRemaining = uses
}; };
await _itemService.CreateAsync(item); await _itemService.CreateAsync(item);
@@ -58,6 +66,7 @@ public class ItemController : ControllerBase
item.Name = req.Name; item.Name = req.Name;
item.LocationId = req.LocationId; item.LocationId = req.LocationId;
item.UsesRemaining = req.UsesRemaining;
await _itemService.UpdateAsync(item); await _itemService.UpdateAsync(item);
return Ok(item); return Ok(item);
} }
+13
View File
@@ -346,6 +346,19 @@ public class PlayerController : ControllerBase
await _locationService.RemoveItemFromLocationAsync(location.Id, itemId); await _locationService.RemoveItemFromLocationAsync(location.Id, itemId);
await _itemService.DeleteAsync(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); return Ok(outcome.Result);
} }
+3 -1
View File
@@ -27,7 +27,8 @@ public class SpawnableController : ControllerBase
SpawnRate = req.SpawnRate, SpawnRate = req.SpawnRate,
CollectAction = req.CollectAction, CollectAction = req.CollectAction,
CollectResults = req.CollectResults, CollectResults = req.CollectResults,
ConsumeOnSuccess = req.ConsumeOnSuccess ConsumeOnSuccess = req.ConsumeOnSuccess,
Uses = req.Uses
}; };
await _spawnableService.CreateAsync(spawnable); await _spawnableService.CreateAsync(spawnable);
@@ -47,6 +48,7 @@ public class SpawnableController : ControllerBase
spawnable.CollectAction = req.CollectAction; spawnable.CollectAction = req.CollectAction;
spawnable.CollectResults = req.CollectResults; spawnable.CollectResults = req.CollectResults;
spawnable.ConsumeOnSuccess = req.ConsumeOnSuccess; spawnable.ConsumeOnSuccess = req.ConsumeOnSuccess;
spawnable.Uses = req.Uses;
await _spawnableService.UpdateAsync(spawnable); await _spawnableService.UpdateAsync(spawnable);
return Ok(spawnable); return Ok(spawnable);
+3 -3
View File
@@ -18,7 +18,7 @@ public class ItemControllerTests
{ {
var loc = new Mock<LocationService>(config); var loc = new Mock<LocationService>(config);
loc.Setup(l => l.GetByIdAsync("bad")).ReturnsAsync((Location?)null); loc.Setup(l => l.GetByIdAsync("bad")).ReturnsAsync((Location?)null);
var controller = new ItemController(new Mock<ItemService>(config).Object, loc.Object); var controller = new ItemController(new Mock<ItemService>(config).Object, loc.Object, new Mock<SpawnableService>(config).Object);
var result = await controller.Create(new CreateItemRequest { LocationId = "bad", Name = "n" }); var result = await controller.Create(new CreateItemRequest { LocationId = "bad", Name = "n" });
@@ -30,7 +30,7 @@ public class ItemControllerTests
{ {
var svc = new Mock<ItemService>(config); var svc = new Mock<ItemService>(config);
svc.Setup(i => i.GetByIdAsync("id")).ReturnsAsync((Item?)null); svc.Setup(i => i.GetByIdAsync("id")).ReturnsAsync((Item?)null);
var controller = new ItemController(svc.Object, new Mock<LocationService>(config).Object); var controller = new ItemController(svc.Object, new Mock<LocationService>(config).Object, new Mock<SpawnableService>(config).Object);
var result = await controller.Update("id", new UpdateItemRequest { LocationId="loc", Name="x" }); var result = await controller.Update("id", new UpdateItemRequest { LocationId="loc", Name="x" });
@@ -42,7 +42,7 @@ public class ItemControllerTests
{ {
var svc = new Mock<ItemService>(config); var svc = new Mock<ItemService>(config);
svc.Setup(i => i.GetByIdAsync("id")).ReturnsAsync((Item?)null); svc.Setup(i => i.GetByIdAsync("id")).ReturnsAsync((Item?)null);
var controller = new ItemController(svc.Object, new Mock<LocationService>(config).Object); var controller = new ItemController(svc.Object, new Mock<LocationService>(config).Object, new Mock<SpawnableService>(config).Object);
var result = await controller.Delete("id"); var result = await controller.Delete("id");
@@ -186,4 +186,36 @@ public class PlayerControllerTests
locSvc.Verify(); locSvc.Verify();
itemSvc.Verify(); itemSvc.Verify();
} }
[Fact]
public async Task PerformAction_DecrementsUses()
{
var playerSvc = new Mock<PlayerService>(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<LocationService>(cfg);
var loc = new Location { Id = "loc", Content = new List<string> { "i" } };
locSvc.Setup(l => l.GetByIdAsync("loc")).ReturnsAsync(loc);
var itemSvc = new Mock<ItemService>(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<Item>(it => it.UsesRemaining == 1))).Returns(Task.CompletedTask).Verifiable();
var spawnSvc = new Mock<SpawnableService>(cfg);
spawnSvc.Setup(s => s.GetByNameAsync("wood")).ReturnsAsync(new Spawnable
{
Name = "wood",
CollectAction = "chop",
CollectResults = new List<CollectOutcome> { 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();
}
} }
@@ -22,7 +22,8 @@ public class SpawnableControllerTests
sp.CollectResults.Count == 2 && sp.CollectResults.Count == 2 &&
sp.CollectResults[0].Result == "resource" && sp.CollectResults[0].Result == "resource" &&
sp.CollectResults[0].SuccessRate == 75 && sp.CollectResults[0].SuccessRate == 75 &&
sp.ConsumeOnSuccess))) sp.ConsumeOnSuccess &&
sp.Uses == 0)))
.Returns(Task.CompletedTask).Verifiable(); .Returns(Task.CompletedTask).Verifiable();
var controller = new SpawnableController(svc.Object); var controller = new SpawnableController(svc.Object);
+3
View File
@@ -5,4 +5,7 @@ public class CreateItemRequest
public string LocationId { get; set; } public string LocationId { get; set; }
public string Name { get; set; } public string Name { get; set; }
// Optional number of uses before the item is removed.
public int UsesRemaining { get; set; } = 0;
} }
+3
View File
@@ -15,4 +15,7 @@ public class CreateSpawnableRequest
// Should the spawnable be removed after a successful action? // Should the spawnable be removed after a successful action?
public bool ConsumeOnSuccess { get; set; } = false; public bool ConsumeOnSuccess { get; set; } = false;
// Number of successful actions allowed before it disappears (0 for unlimited)
public int Uses { get; set; } = 0;
} }
+3
View File
@@ -4,4 +4,7 @@ public class UpdateItemRequest
{ {
public string LocationId { get; set; } public string LocationId { get; set; }
public string Name { get; set; } public string Name { get; set; }
// Adjust remaining uses for the item
public int UsesRemaining { get; set; } = 0;
} }
+3
View File
@@ -11,4 +11,7 @@ public class UpdateSpawnableRequest
// Should the spawnable be removed after a successful action? // Should the spawnable be removed after a successful action?
public bool ConsumeOnSuccess { get; set; } = false; public bool ConsumeOnSuccess { get; set; } = false;
// Number of successful actions allowed before it disappears (0 for unlimited)
public int Uses { get; set; } = 0;
} }
+3
View File
@@ -13,4 +13,7 @@ public class Item
public string LocationId { get; set; } public string LocationId { get; set; }
public string Name { get; set; } public string Name { get; set; }
// Remaining uses before this item disappears. 0 means unlimited.
public int UsesRemaining { get; set; } = 0;
} }
+4
View File
@@ -21,4 +21,8 @@ public class Spawnable
// If true, the spawnable item is removed after a successful action // If true, the spawnable item is removed after a successful action
public bool ConsumeOnSuccess { get; set; } = false; 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;
} }