// Controllers/PlayerController.cs using GameAuthApi.Models; using GameAuthApi.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace GameAuthApi.Controllers; [ApiController] [Route("api/[controller]")] public class PlayerController : ControllerBase { private readonly PlayerService _playerService; private readonly LocationService _locationService; private readonly SpawnableService _spawnableService; private readonly ItemService _itemService; public PlayerController(PlayerService playerService, LocationService locationService, SpawnableService spawnableService, ItemService itemService) { _playerService = playerService; _locationService = locationService; _spawnableService = spawnableService; _itemService = itemService; } [HttpPost] [Authorize(Roles = "USER,SUPER")] public async Task Create([FromBody] CreatePlayerRequest req) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Unauthorized("User ID not found in token."); var existing = await _playerService.GetByUserIdAsync(userId); if (existing != null) return BadRequest("Player already exists for this user."); Location spawnLocation = null; if (!string.IsNullOrWhiteSpace(req.LocationId)) { spawnLocation = await _locationService.GetByIdAsync(req.LocationId); if (spawnLocation == null) return BadRequest("Specified location does not exist."); } else { spawnLocation = await _locationService.GetSpawnLocationAsync(); // If not found, spawnLocation remains null } // create the inventory location var inventoryLocation = new Location { LocationType = "Inventory", X = null, Y = null, Z = null, N = null, NE = null, E = null, SE = null, S = null, SW = null, W = null, NW = null, Players = new List() // optional }; await _locationService.CreateAsync(inventoryLocation); var player = new Player { UserId = userId, Name = req.Name, LocationId = spawnLocation?.Id, InventoryLocationId = inventoryLocation.Id, Climbing = 3 }; await _playerService.CreateAsync(player); if (spawnLocation != null) { await _locationService.AddPlayerToLocationAsync(spawnLocation.Id, player.Id); } return Ok(player); } [HttpPut] [Authorize(Roles = "USER,SUPER")] public async Task Update([FromBody] UpdatePlayerRequest req) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var player = await _playerService.GetByUserIdAsync(userId); if (player == null) return NotFound("Player not found."); player.Name = req.Name; player.Climbing = req.Climbing; await _playerService.UpdateAsync(player); return Ok(player); } [HttpDelete] [Authorize(Roles = "USER,SUPER")] public async Task Delete() { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var role = User.FindFirstValue(ClaimTypes.Role); var player = await _playerService.GetByUserIdAsync(userId); if (player == null) return NotFound("No player found for this user."); // Remove from current location if set if (!string.IsNullOrEmpty(player.LocationId)) { await _locationService.RemovePlayerFromLocationAsync(player.LocationId, player.Id); } await _playerService.DeleteAsync(player.Id); return Ok($"Player '{player.Name}' deleted."); } [HttpPost("teleport")] [Authorize(Roles = "SUPER")] public async Task AdminTeleport([FromBody] TeleportRequest req) { var player = await _playerService.GetByIdAsync(req.PlayerId); if (player == null) return NotFound("Player not found."); var newLocation = await _locationService.GetByIdAsync(req.LocationId); if (newLocation == null) return BadRequest("Target location does not exist."); if (!string.IsNullOrEmpty(player.LocationId)) { await _locationService.RemovePlayerFromLocationAsync(player.LocationId, player.Id); } player.LocationId = newLocation.Id; await _playerService.UpdateAsync(player); await _locationService.AddPlayerToLocationAsync(newLocation.Id, player.Id); return Ok($"Player '{player.Name}' teleported to '{newLocation.X}, {newLocation.Y}'."); } [HttpPost("move")] [Authorize(Roles = "USER,SUPER")] public async Task Move([FromBody] MoveRequest req) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var player = await _playerService.GetByUserIdAsync(userId); if (player == null) return NotFound("Player not found."); if (string.IsNullOrWhiteSpace(player.LocationId)) return BadRequest("Player is not currently in a location."); var currentLocation = await _locationService.GetByIdAsync(player.LocationId); if (currentLocation == null) return BadRequest("Current location not found."); if (string.IsNullOrWhiteSpace(req.Direction)) return BadRequest("Direction is required."); var direction = req.Direction.ToUpper(); var directionMap = new Dictionary { { "N", (0, 1, "S") }, { "NE", (1, 1, "SW") }, { "E", (1, 0, "W") }, { "SE", (1, -1, "NW") }, { "S", (0, -1, "N") }, { "SW", (-1, -1, "NE") }, { "W", (-1, 0, "E") }, { "NW", (-1, 1, "SE") } }; if (!directionMap.ContainsKey(direction)) return BadRequest("Invalid direction. Use N, NE, E, SE, S, SW, W, or NW."); var (dx, dy, reverseDir) = directionMap[direction]; var targetId = typeof(Location).GetProperty(direction)?.GetValue(currentLocation)?.ToString(); Location targetLocation; if (!string.IsNullOrWhiteSpace(targetId)) { targetLocation = await _locationService.GetByIdAsync(targetId); if (targetLocation == null) return BadRequest("Linked location ID is invalid."); var z1 = currentLocation.Z ?? 0; var z2 = targetLocation.Z ?? 0; var zDiff = Math.Abs(z1 - z2); if (zDiff > player.Climbing) { return BadRequest($"Cannot move {req.Direction.ToUpper()}: elevation difference ({zDiff}) exceeds your climbing ability ({player.Climbing})."); } } else { // Generate new location var newX = currentLocation.X + dx; var newY = currentLocation.Y + dy; var newZ = currentLocation.Z + Random.Shared.Next(-1, 2); // -1, 0, or +1 string itemId = null; var spawnables = await _spawnableService.GetAllAsync(); if (spawnables.Any()) { var roll = Random.Shared.Next(0, 100); // 0–99 foreach (var s in spawnables.OrderBy(s => s.SpawnRate)) { if (roll < s.SpawnRate) { var tempItem = new Item { Name = s.Name, LocationId = null, UsesRemaining = s.Uses }; await _itemService.CreateAsync(tempItem); itemId = tempItem.Id; break; } } } targetLocation = new Location { LocationType = "World", X = newX, Y = newY, Z = newZ, Content = itemId != null ? [itemId] : [] }; await _locationService.CreateAsync(targetLocation); if (itemId != null) { var createdItem = await _itemService.GetByIdAsync(itemId); createdItem.LocationId = targetLocation.Id; await _itemService.UpdateAsync(createdItem); } // Link current → new typeof(Location).GetProperty(direction)?.SetValue(currentLocation, targetLocation.Id); // Link new → current typeof(Location).GetProperty(reverseDir)?.SetValue(targetLocation, currentLocation.Id); await _locationService.UpdateAsync(currentLocation); await _locationService.UpdateAsync(targetLocation); // Link additional neighbors foreach (var entry in directionMap) { var dir = entry.Key; var (nx, ny, rev) = entry.Value; var neighbor = await _locationService.GetByCoordinatesAsync( (targetLocation.X ?? 0) + nx, (targetLocation.Y ?? 0) + ny, targetLocation.Z ?? 0); if (neighbor != null) { typeof(Location).GetProperty(dir)?.SetValue(targetLocation, neighbor.Id); typeof(Location).GetProperty(rev)?.SetValue(neighbor, targetLocation.Id); await _locationService.UpdateAsync(neighbor); } } await _locationService.UpdateAsync(targetLocation); } // Move the player await _locationService.RemovePlayerFromLocationAsync(currentLocation.Id, player.Id); await _locationService.AddPlayerToLocationAsync(targetLocation.Id, player.Id); player.LocationId = targetLocation.Id; await _playerService.UpdateAsync(player); return Ok($"Player '{player.Name}' moved {direction} to '{targetLocation.X}, {targetLocation.Y}'."); } [HttpPost("action")] [Authorize(Roles = "USER,SUPER")] public async Task PerformAction([FromBody] PerformActionRequest req) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var player = await _playerService.GetByUserIdAsync(userId); if (player == null) return NotFound("Player not found."); if (string.IsNullOrWhiteSpace(player.LocationId)) return BadRequest("Player is not currently in a location."); if (string.IsNullOrWhiteSpace(req.Action)) return BadRequest("Action is required."); var location = await _locationService.GetByIdAsync(player.LocationId); if (location == null) return BadRequest("Current location not found."); foreach (var itemId in location.Content) { var item = await _itemService.GetByIdAsync(itemId); if (item == null) continue; var spawnable = await _spawnableService.GetByNameAsync(item.Name.ToLower()); if (spawnable == null) continue; if (!string.Equals(spawnable.CollectAction, req.Action, StringComparison.OrdinalIgnoreCase)) continue; var roll = Random.Shared.Next(0, 100); var cumulative = 0; foreach (var outcome in spawnable.CollectResults) { cumulative += outcome.SuccessRate; if (roll < cumulative) { if (!string.IsNullOrEmpty(player.InventoryLocationId)) { var newItem = new Item { Name = outcome.Result, LocationId = player.InventoryLocationId }; await _itemService.CreateAsync(newItem); await _locationService.AddItemToLocationAsync(player.InventoryLocationId, newItem.Id); } if (spawnable.ConsumeOnSuccess) { 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); } } return Ok("Nothing happens."); } return Ok("Nothing happens."); } private static readonly Dictionary DirectionData = new() { { "N", (0, 1, "S") }, { "NE", (1, 1, "SW") }, { "E", (1, 0, "W") }, { "SE", (1, -1, "NW") }, { "S", (0, -1, "N") }, { "SW", (-1, -1, "NE") }, { "W", (-1, 0, "E") }, { "NW", (-1, 1, "SE") } }; }