283 lines
9.9 KiB
C#
283 lines
9.9 KiB
C#
// 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<IActionResult> 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<string>() // 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);
|
||
}
|
||
|
||
[HttpDelete]
|
||
[Authorize(Roles = "USER,SUPER")]
|
||
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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.");
|
||
|
||
var direction = req.Direction.ToUpper();
|
||
var directionMap = new Dictionary<string, (int dx, int dy, string reverse)>
|
||
{
|
||
{ "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 random = new Random();
|
||
var newZ = currentLocation.Z + random.Next(-1, 2); // -1, 0, or +1
|
||
|
||
string itemId = null;
|
||
var spawnables = await _spawnableService.GetAllAsync();
|
||
|
||
if (spawnables.Any())
|
||
{
|
||
var rng = new Random();
|
||
var roll = rng.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
|
||
};
|
||
|
||
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}'.");
|
||
}
|
||
|
||
private static readonly Dictionary<string, (int dx, int dy, string reverse)> 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") }
|
||
};
|
||
}
|