Refactor pet action management: rename PetAction to PetBasicAction, update related properties and methods, and enhance pet stats with maximum values.

This commit is contained in:
Jose Henrique 2025-02-01 23:08:25 -03:00
parent d6d3dc9f44
commit df62710b9a
14 changed files with 173 additions and 239 deletions

1
.gitignore vendored
View File

@ -5,4 +5,5 @@ obj
appsettings.Development.json appsettings.Development.json
*.db *.db
*.db-shm *.db-shm
.*.db-wal
*.csproj.user *.csproj.user

View File

@ -36,44 +36,7 @@ namespace pet_companion_api.Controllers
return CreatedAtAction(nameof(GetAllPets), new { id = createdPet.Id }, createdPet); return CreatedAtAction(nameof(GetAllPets), new { id = createdPet.Id }, createdPet);
} }
/// <summary> [HttpPut("{petId}/action")]
///
/// </summary>
/// <param name="petId"></param>
/// <param name="action">One of `feed`, `play`, `sleep`</param>
/// <returns></returns>
[HttpPut("{petId}/action/{action}")]
public IActionResult UpdatePetAction(string petId, string action)
{
try
{
PetAction petAction;
switch (action.ToLower())
{
case "feed":
petAction = PetAction.FEED;
break;
case "play":
petAction = PetAction.PLAY;
break;
case "sleep":
petAction = PetAction.SLEEP;
break;
default:
return BadRequest("Invalid action. Valid actions are: feed, play, sleep");
}
var actionRequest = new PetUpdateActionRequest { Action = petAction };
var updatedPet = petService.UpdatePetAction(petId, userId.ToString(), actionRequest);
return Ok(updatedPet);
}
catch (Exception ex)
{
return NotFound(ex.Message);
}
}
[HttpPut("{petId}/action/gather")]
public IActionResult UpdatePetActionGather(string petId, [FromBody] PetUpdateActionRequest actionRequest) public IActionResult UpdatePetActionGather(string petId, [FromBody] PetUpdateActionRequest actionRequest)
{ {
try try

View File

@ -12,5 +12,23 @@ namespace pet_companion_api.Data
public DbSet<Pet> Pets { get; set; } public DbSet<Pet> Pets { get; set; }
public DbSet<PetStats> PetStats { get; set; } public DbSet<PetStats> PetStats { get; set; }
public DbSet<Resources> Resources { get; set; } public DbSet<Resources> Resources { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure DateTime properties to be stored as UTC
modelBuilder.Entity<Pet>()
.Property(p => p.GatherActionSince)
.HasConversion(
v => v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
modelBuilder.Entity<Pet>()
.Property(p => p.BasicActionCooldown)
.HasConversion(
v => v != DateTime.MinValue ? v.ToUniversalTime() : v,
v => v != DateTime.MinValue ? DateTime.SpecifyKind(v, DateTimeKind.Utc) : v);
}
} }
} }

View File

@ -1,116 +0,0 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using pet_companion_api.Data;
#nullable disable
namespace pet_companion_api.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250201022754_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.Entity("pet_companion_api.Models.Pet", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("Class")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Pets");
});
modelBuilder.Entity("pet_companion_api.Models.PetStats", b =>
{
b.Property<string>("PetId")
.HasColumnType("TEXT");
b.Property<int>("Charisma")
.HasColumnType("INTEGER");
b.Property<int>("Intelligence")
.HasColumnType("INTEGER");
b.Property<int>("Strength")
.HasColumnType("INTEGER");
b.HasKey("PetId");
b.ToTable("PetStats");
});
modelBuilder.Entity("pet_companion_api.Models.Resources", b =>
{
b.Property<string>("PetId")
.HasColumnType("TEXT");
b.Property<int>("Food")
.HasColumnType("INTEGER");
b.Property<int>("Gold")
.HasColumnType("INTEGER");
b.Property<int>("Junk")
.HasColumnType("INTEGER");
b.Property<int>("Wisdom")
.HasColumnType("INTEGER");
b.HasKey("PetId");
b.ToTable("Resources");
});
modelBuilder.Entity("pet_companion_api.Models.PetStats", b =>
{
b.HasOne("pet_companion_api.Models.Pet", null)
.WithOne("Stats")
.HasForeignKey("pet_companion_api.Models.PetStats", "PetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("pet_companion_api.Models.Resources", b =>
{
b.HasOne("pet_companion_api.Models.Pet", null)
.WithOne("Resources")
.HasForeignKey("pet_companion_api.Models.Resources", "PetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("pet_companion_api.Models.Pet", b =>
{
b.Navigation("Resources")
.IsRequired();
b.Navigation("Stats")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,52 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace pet_companion_api.Migrations
{
/// <inheritdoc />
public partial class AddActions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "ActionSince",
table: "Pets",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<bool>(
name: "IsDead",
table: "Pets",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "PetAction",
table: "Pets",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ActionSince",
table: "Pets");
migrationBuilder.DropColumn(
name: "IsDead",
table: "Pets");
migrationBuilder.DropColumn(
name: "PetAction",
table: "Pets");
}
}
}

View File

@ -11,8 +11,8 @@ using pet_companion_api.Data;
namespace pet_companion_api.Migrations namespace pet_companion_api.Migrations
{ {
[DbContext(typeof(ApplicationDbContext))] [DbContext(typeof(ApplicationDbContext))]
[Migration("20250201025206_AddActions")] [Migration("20250201173643_Initial")]
partial class AddActions partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -25,12 +25,15 @@ namespace pet_companion_api.Migrations
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("ActionSince") b.Property<DateTime>("BasicActionCooldown")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("Class") b.Property<int>("Class")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("GatherActionSince")
.HasColumnType("TEXT");
b.Property<bool>("IsDead") b.Property<bool>("IsDead")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -41,7 +44,10 @@ namespace pet_companion_api.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("PetAction") b.Property<int>("PetBasicAction")
.HasColumnType("INTEGER");
b.Property<int>("PetGatherAction")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("UserId") b.Property<string>("UserId")
@ -64,6 +70,15 @@ namespace pet_companion_api.Migrations
b.Property<int>("Intelligence") b.Property<int>("Intelligence")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("MaxCharisma")
.HasColumnType("INTEGER");
b.Property<int>("MaxIntelligence")
.HasColumnType("INTEGER");
b.Property<int>("MaxStrength")
.HasColumnType("INTEGER");
b.Property<int>("Strength") b.Property<int>("Strength")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View File

@ -1,11 +1,12 @@
using Microsoft.EntityFrameworkCore.Migrations; using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
namespace pet_companion_api.Migrations namespace pet_companion_api.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class InitialCreate : Migration public partial class Initial : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@ -18,7 +19,12 @@ namespace pet_companion_api.Migrations
Name = table.Column<string>(type: "TEXT", nullable: false), Name = table.Column<string>(type: "TEXT", nullable: false),
Class = table.Column<int>(type: "INTEGER", nullable: false), Class = table.Column<int>(type: "INTEGER", nullable: false),
Level = table.Column<int>(type: "INTEGER", nullable: false), Level = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<string>(type: "TEXT", nullable: false) UserId = table.Column<string>(type: "TEXT", nullable: false),
IsDead = table.Column<bool>(type: "INTEGER", nullable: false),
PetGatherAction = table.Column<int>(type: "INTEGER", nullable: false),
GatherActionSince = table.Column<DateTime>(type: "TEXT", nullable: false),
PetBasicAction = table.Column<int>(type: "INTEGER", nullable: false),
BasicActionCooldown = table.Column<DateTime>(type: "TEXT", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@ -32,7 +38,10 @@ namespace pet_companion_api.Migrations
PetId = table.Column<string>(type: "TEXT", nullable: false), PetId = table.Column<string>(type: "TEXT", nullable: false),
Intelligence = table.Column<int>(type: "INTEGER", nullable: false), Intelligence = table.Column<int>(type: "INTEGER", nullable: false),
Strength = table.Column<int>(type: "INTEGER", nullable: false), Strength = table.Column<int>(type: "INTEGER", nullable: false),
Charisma = table.Column<int>(type: "INTEGER", nullable: false) Charisma = table.Column<int>(type: "INTEGER", nullable: false),
MaxIntelligence = table.Column<int>(type: "INTEGER", nullable: false),
MaxStrength = table.Column<int>(type: "INTEGER", nullable: false),
MaxCharisma = table.Column<int>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {

View File

@ -22,12 +22,15 @@ namespace pet_companion_api.Migrations
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("ActionSince") b.Property<DateTime>("BasicActionCooldown")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("Class") b.Property<int>("Class")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("GatherActionSince")
.HasColumnType("TEXT");
b.Property<bool>("IsDead") b.Property<bool>("IsDead")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -38,7 +41,10 @@ namespace pet_companion_api.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("PetAction") b.Property<int>("PetBasicAction")
.HasColumnType("INTEGER");
b.Property<int>("PetGatherAction")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("UserId") b.Property<string>("UserId")
@ -61,6 +67,15 @@ namespace pet_companion_api.Migrations
b.Property<int>("Intelligence") b.Property<int>("Intelligence")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("MaxCharisma")
.HasColumnType("INTEGER");
b.Property<int>("MaxIntelligence")
.HasColumnType("INTEGER");
b.Property<int>("MaxStrength")
.HasColumnType("INTEGER");
b.Property<int>("Strength") b.Property<int>("Strength")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View File

@ -14,7 +14,28 @@ namespace pet_companion_api.Models
public string UserId { get; set; } public string UserId { get; set; }
public bool IsDead { get; set; } public bool IsDead { get; set; }
public PetActionGather PetAction { get; set; } public PetActionGather PetGatherAction { get; set; }
public DateTime ActionSince { get; set; } public DateTime GatherActionSince { get; set; }
public PetBasicAction PetBasicAction { get; set; }
public DateTime BasicActionCooldown { get; set; }
public void IncrementIntelligence(int amount)
{
var newValue = Stats.Intelligence + amount;
Stats.Intelligence = Math.Min(newValue, Stats.MaxIntelligence);
}
public void IncrementStrength(int amount)
{
var newValue = Stats.Strength + amount;
Stats.Strength = Math.Min(newValue, Stats.MaxStrength);
}
public void IncrementCharisma(int amount)
{
var newValue = Stats.Charisma + amount;
Stats.Charisma = Math.Min(newValue, Stats.MaxCharisma);
}
} }
} }

View File

@ -1,6 +1,6 @@
namespace pet_companion_api.Models namespace pet_companion_api.Models
{ {
public enum PetAction public enum PetBasicAction
{ {
UNKNOWN, UNKNOWN,
FEED, FEED,

View File

@ -11,6 +11,10 @@ namespace pet_companion_api.Models
public int Strength { get; set; } public int Strength { get; set; }
public int Charisma { get; set; } public int Charisma { get; set; }
public int MaxIntelligence { get; set; }
public int MaxStrength { get; set; }
public int MaxCharisma { get; set; }
public static PetStats BuildFromClass(PetClass petClass) public static PetStats BuildFromClass(PetClass petClass)
{ {
var stats = new PetStats(); var stats = new PetStats();
@ -59,6 +63,10 @@ namespace pet_companion_api.Models
break; break;
} }
stats.MaxIntelligence = stats.Intelligence;
stats.MaxStrength = stats.Strength;
stats.MaxCharisma = stats.Charisma;
return stats; return stats;
} }
} }

View File

@ -2,7 +2,7 @@ namespace pet_companion_api.Models
{ {
public class PetUpdateActionRequest public class PetUpdateActionRequest
{ {
public PetAction? Action { get; set; } public PetBasicAction? BasicAction { get; set; }
public PetActionGather? PetActionGather { get; set; } public PetActionGather? GatherAction { get; set; }
} }
} }

View File

@ -45,11 +45,20 @@ namespace pet_companion_api.Repositories
return pet; return pet;
} }
public Pet UpdatePetAction(Pet pet) public Pet UpdatePetBasicAction(Pet pet)
{ {
context.Pets.Attach(pet); context.Pets.Attach(pet);
context.Entry(pet).Property(p => p.PetAction).IsModified = true; context.Entry(pet).Property(p => p.PetBasicAction).IsModified = true;
context.Entry(pet).Property(p => p.ActionSince).IsModified = true; context.Entry(pet).Property(p => p.BasicActionCooldown).IsModified = true;
context.SaveChanges();
return pet;
}
public Pet UpdatePetGatherAction(Pet pet)
{
context.Pets.Attach(pet);
context.Entry(pet).Property(p => p.PetGatherAction).IsModified = true;
context.Entry(pet).Property(p => p.GatherActionSince).IsModified = true;
context.SaveChanges(); context.SaveChanges();
return pet; return pet;
} }
@ -58,7 +67,7 @@ namespace pet_companion_api.Repositories
{ {
context.Pets.Attach(pet); context.Pets.Attach(pet);
context.Entry(pet).Reference(p => p.Resources).IsModified = true; context.Entry(pet).Reference(p => p.Resources).IsModified = true;
context.Entry(pet).Property(p => p.ActionSince).IsModified = true; context.Entry(pet).Property(p => p.GatherActionSince).IsModified = true;
context.SaveChanges(); context.SaveChanges();
return pet; return pet;
} }

View File

@ -21,17 +21,18 @@ namespace pet_companion_api.Services
public Pet CreatePet(Guid userId, PetCreationRequest petRequest) public Pet CreatePet(Guid userId, PetCreationRequest petRequest)
{ {
var petId = Guid.NewGuid().ToString();
var pet = new Pet var pet = new Pet
{ {
Id = Guid.NewGuid().ToString(), Id = petId,
UserId = userId.ToString(), UserId = userId.ToString(),
Name = petRequest.Name, Name = petRequest.Name,
Class = petRequest.Class, Class = petRequest.Class,
Level = 1, Level = 1,
Stats = PetStats.BuildFromClass(petRequest.Class), Stats = PetStats.BuildFromClass(petRequest.Class),
Resources = new Resources(), Resources = new Resources(),
ActionSince = DateTime.UtcNow, GatherActionSince = DateTime.UtcNow,
PetAction = PetActionGather.IDLE, PetGatherAction = PetActionGather.IDLE,
IsDead = false IsDead = false
}; };
@ -46,21 +47,63 @@ namespace pet_companion_api.Services
throw new Exception("Pet not found"); throw new Exception("Pet not found");
} }
if (actionRequest.Action.HasValue) if (actionRequest.BasicAction.HasValue)
{ {
// not implemented var currentTime = DateTime.UtcNow;
} if (pet.PetBasicAction != PetBasicAction.UNKNOWN &&
else if (actionRequest.PetActionGather.HasValue) currentTime < pet.BasicActionCooldown.ToUniversalTime().AddSeconds(10))
{ {
pet.PetAction = actionRequest.PetActionGather.Value; throw new Exception("Pet is still on cooldown");
pet.ActionSince = DateTime.UtcNow; }
return petRepository.UpdatePetAction(pet); pet.BasicActionCooldown = DateTime.UtcNow.AddMinutes(GetCooldownForBasicAction(actionRequest.BasicAction.Value));
pet.PetBasicAction = actionRequest.BasicAction.Value;
switch (actionRequest.BasicAction.Value)
{
case PetBasicAction.FEED:
pet.Resources.Food -= 1;
pet.IncrementStrength(1);
break;
case PetBasicAction.SLEEP:
pet.IncrementIntelligence(1);
pet.IncrementStrength(1);
break;
case PetBasicAction.PLAY:
pet.Resources.Junk -= 1;
pet.IncrementCharisma(1);
break;
}
return petRepository.UpdatePetBasicAction(pet);
}
else if (actionRequest.GatherAction.HasValue)
{
pet.PetGatherAction = actionRequest.GatherAction.Value;
pet.GatherActionSince = DateTime.UtcNow;
return petRepository.UpdatePetGatherAction(pet);
} }
return pet; return pet;
} }
// returns in minutes
private int GetCooldownForBasicAction(PetBasicAction value)
{
switch (value)
{
case PetBasicAction.FEED:
return 5;
case PetBasicAction.PLAY:
return 10;
case PetBasicAction.SLEEP:
return 15;
default:
return 0;
}
}
public Resources GetGatheredResources(string petId, string userId) public Resources GetGatheredResources(string petId, string userId)
{ {
var pet = petRepository.GetPetById(petId, userId); var pet = petRepository.GetPetById(petId, userId);
@ -70,7 +113,7 @@ namespace pet_companion_api.Services
throw new Exception("Pet not found"); throw new Exception("Pet not found");
} }
return _petClassService.CalculateGatheredResources(pet.Stats, pet.Level, pet.PetAction, pet.ActionSince); return _petClassService.CalculateGatheredResources(pet.Stats, pet.Level, pet.PetGatherAction, pet.GatherActionSince);
} }
public Pet UpdatePetResources(string petId, string userId) public Pet UpdatePetResources(string petId, string userId)
@ -81,13 +124,13 @@ namespace pet_companion_api.Services
throw new Exception("Pet not found"); throw new Exception("Pet not found");
} }
var gatheredResources = _petClassService.CalculateGatheredResources(pet.Stats, pet.Level, pet.PetAction, pet.ActionSince); var gatheredResources = _petClassService.CalculateGatheredResources(pet.Stats, pet.Level, pet.PetGatherAction, pet.GatherActionSince);
pet.Resources.Wisdom += gatheredResources.Wisdom; pet.Resources.Wisdom += gatheredResources.Wisdom;
pet.Resources.Gold += gatheredResources.Gold; pet.Resources.Gold += gatheredResources.Gold;
pet.Resources.Food += gatheredResources.Food; pet.Resources.Food += gatheredResources.Food;
pet.Resources.Junk += gatheredResources.Junk; pet.Resources.Junk += gatheredResources.Junk;
pet.ActionSince = DateTime.UtcNow; pet.GatherActionSince = DateTime.UtcNow;
return petRepository.UpdatePetResources(pet); return petRepository.UpdatePetResources(pet);
} }