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
*.db
*.db-shm
.*.db-wal
*.csproj.user

View File

@ -36,44 +36,7 @@ namespace pet_companion_api.Controllers
return CreatedAtAction(nameof(GetAllPets), new { id = createdPet.Id }, createdPet);
}
/// <summary>
///
/// </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")]
[HttpPut("{petId}/action")]
public IActionResult UpdatePetActionGather(string petId, [FromBody] PetUpdateActionRequest actionRequest)
{
try

View File

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

View File

@ -1,11 +1,12 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace pet_companion_api.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@ -18,7 +19,12 @@ namespace pet_companion_api.Migrations
Name = table.Column<string>(type: "TEXT", nullable: false),
Class = 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 =>
{
@ -32,7 +38,10 @@ namespace pet_companion_api.Migrations
PetId = table.Column<string>(type: "TEXT", nullable: false),
Intelligence = 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 =>
{

View File

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

View File

@ -14,7 +14,28 @@ namespace pet_companion_api.Models
public string UserId { get; set; }
public bool IsDead { get; set; }
public PetActionGather PetAction { get; set; }
public DateTime ActionSince { get; set; }
public PetActionGather PetGatherAction { 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
{
public enum PetAction
public enum PetBasicAction
{
UNKNOWN,
FEED,

View File

@ -11,6 +11,10 @@ namespace pet_companion_api.Models
public int Strength { 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)
{
var stats = new PetStats();
@ -59,6 +63,10 @@ namespace pet_companion_api.Models
break;
}
stats.MaxIntelligence = stats.Intelligence;
stats.MaxStrength = stats.Strength;
stats.MaxCharisma = stats.Charisma;
return stats;
}
}

View File

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

View File

@ -45,11 +45,20 @@ namespace pet_companion_api.Repositories
return pet;
}
public Pet UpdatePetAction(Pet pet)
public Pet UpdatePetBasicAction(Pet pet)
{
context.Pets.Attach(pet);
context.Entry(pet).Property(p => p.PetAction).IsModified = true;
context.Entry(pet).Property(p => p.ActionSince).IsModified = true;
context.Entry(pet).Property(p => p.PetBasicAction).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();
return pet;
}
@ -58,7 +67,7 @@ namespace pet_companion_api.Repositories
{
context.Pets.Attach(pet);
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();
return pet;
}

View File

@ -21,17 +21,18 @@ namespace pet_companion_api.Services
public Pet CreatePet(Guid userId, PetCreationRequest petRequest)
{
var petId = Guid.NewGuid().ToString();
var pet = new Pet
{
Id = Guid.NewGuid().ToString(),
Id = petId,
UserId = userId.ToString(),
Name = petRequest.Name,
Class = petRequest.Class,
Level = 1,
Stats = PetStats.BuildFromClass(petRequest.Class),
Resources = new Resources(),
ActionSince = DateTime.UtcNow,
PetAction = PetActionGather.IDLE,
GatherActionSince = DateTime.UtcNow,
PetGatherAction = PetActionGather.IDLE,
IsDead = false
};
@ -46,21 +47,63 @@ namespace pet_companion_api.Services
throw new Exception("Pet not found");
}
if (actionRequest.Action.HasValue)
if (actionRequest.BasicAction.HasValue)
{
// not implemented
}
else if (actionRequest.PetActionGather.HasValue)
{
pet.PetAction = actionRequest.PetActionGather.Value;
pet.ActionSince = DateTime.UtcNow;
var currentTime = DateTime.UtcNow;
if (pet.PetBasicAction != PetBasicAction.UNKNOWN &&
currentTime < pet.BasicActionCooldown.ToUniversalTime().AddSeconds(10))
{
throw new Exception("Pet is still on cooldown");
}
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;
}
// 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)
{
var pet = petRepository.GetPetById(petId, userId);
@ -70,7 +113,7 @@ namespace pet_companion_api.Services
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)
@ -81,13 +124,13 @@ namespace pet_companion_api.Services
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.Gold += gatheredResources.Gold;
pet.Resources.Food += gatheredResources.Food;
pet.Resources.Junk += gatheredResources.Junk;
pet.ActionSince = DateTime.UtcNow;
pet.GatherActionSince = DateTime.UtcNow;
return petRepository.UpdatePetResources(pet);
}