diff --git a/.gitignore b/.gitignore
index a01ec03..99e5bd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ obj
appsettings.Development.json
*.db
*.db-shm
+.*.db-wal
*.csproj.user
\ No newline at end of file
diff --git a/Controllers/PetController.cs b/Controllers/PetController.cs
index d36734a..883cc58 100644
--- a/Controllers/PetController.cs
+++ b/Controllers/PetController.cs
@@ -36,44 +36,7 @@ namespace pet_companion_api.Controllers
return CreatedAtAction(nameof(GetAllPets), new { id = createdPet.Id }, createdPet);
}
- ///
- ///
- ///
- ///
- /// One of `feed`, `play`, `sleep`
- ///
- [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
diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs
index ece3bd8..d187299 100644
--- a/Data/ApplicationDbContext.cs
+++ b/Data/ApplicationDbContext.cs
@@ -12,5 +12,23 @@ namespace pet_companion_api.Data
public DbSet Pets { get; set; }
public DbSet PetStats { get; set; }
public DbSet Resources { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ // Configure DateTime properties to be stored as UTC
+ modelBuilder.Entity()
+ .Property(p => p.GatherActionSince)
+ .HasConversion(
+ v => v.ToUniversalTime(),
+ v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
+
+ modelBuilder.Entity()
+ .Property(p => p.BasicActionCooldown)
+ .HasConversion(
+ v => v != DateTime.MinValue ? v.ToUniversalTime() : v,
+ v => v != DateTime.MinValue ? DateTime.SpecifyKind(v, DateTimeKind.Utc) : v);
+ }
}
}
diff --git a/Migrations/20250201022754_InitialCreate.Designer.cs b/Migrations/20250201022754_InitialCreate.Designer.cs
deleted file mode 100644
index 98a8957..0000000
--- a/Migrations/20250201022754_InitialCreate.Designer.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-//
-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
- {
- ///
- 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("Id")
- .HasColumnType("TEXT");
-
- b.Property("Class")
- .HasColumnType("INTEGER");
-
- b.Property("Level")
- .HasColumnType("INTEGER");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("UserId")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.ToTable("Pets");
- });
-
- modelBuilder.Entity("pet_companion_api.Models.PetStats", b =>
- {
- b.Property("PetId")
- .HasColumnType("TEXT");
-
- b.Property("Charisma")
- .HasColumnType("INTEGER");
-
- b.Property("Intelligence")
- .HasColumnType("INTEGER");
-
- b.Property("Strength")
- .HasColumnType("INTEGER");
-
- b.HasKey("PetId");
-
- b.ToTable("PetStats");
- });
-
- modelBuilder.Entity("pet_companion_api.Models.Resources", b =>
- {
- b.Property("PetId")
- .HasColumnType("TEXT");
-
- b.Property("Food")
- .HasColumnType("INTEGER");
-
- b.Property("Gold")
- .HasColumnType("INTEGER");
-
- b.Property("Junk")
- .HasColumnType("INTEGER");
-
- b.Property("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
- }
- }
-}
diff --git a/Migrations/20250201025206_AddActions.cs b/Migrations/20250201025206_AddActions.cs
deleted file mode 100644
index 6e7717b..0000000
--- a/Migrations/20250201025206_AddActions.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace pet_companion_api.Migrations
-{
- ///
- public partial class AddActions : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AddColumn(
- name: "ActionSince",
- table: "Pets",
- type: "TEXT",
- nullable: false,
- defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
-
- migrationBuilder.AddColumn(
- name: "IsDead",
- table: "Pets",
- type: "INTEGER",
- nullable: false,
- defaultValue: false);
-
- migrationBuilder.AddColumn(
- name: "PetAction",
- table: "Pets",
- type: "INTEGER",
- nullable: false,
- defaultValue: 0);
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropColumn(
- name: "ActionSince",
- table: "Pets");
-
- migrationBuilder.DropColumn(
- name: "IsDead",
- table: "Pets");
-
- migrationBuilder.DropColumn(
- name: "PetAction",
- table: "Pets");
- }
- }
-}
diff --git a/Migrations/20250201025206_AddActions.Designer.cs b/Migrations/20250201173643_Initial.Designer.cs
similarity index 84%
rename from Migrations/20250201025206_AddActions.Designer.cs
rename to Migrations/20250201173643_Initial.Designer.cs
index 05664b0..2c0016a 100644
--- a/Migrations/20250201025206_AddActions.Designer.cs
+++ b/Migrations/20250201173643_Initial.Designer.cs
@@ -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
{
///
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -25,12 +25,15 @@ namespace pet_companion_api.Migrations
b.Property("Id")
.HasColumnType("TEXT");
- b.Property("ActionSince")
+ b.Property("BasicActionCooldown")
.HasColumnType("TEXT");
b.Property("Class")
.HasColumnType("INTEGER");
+ b.Property("GatherActionSince")
+ .HasColumnType("TEXT");
+
b.Property("IsDead")
.HasColumnType("INTEGER");
@@ -41,7 +44,10 @@ namespace pet_companion_api.Migrations
.IsRequired()
.HasColumnType("TEXT");
- b.Property("PetAction")
+ b.Property("PetBasicAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("PetGatherAction")
.HasColumnType("INTEGER");
b.Property("UserId")
@@ -64,6 +70,15 @@ namespace pet_companion_api.Migrations
b.Property("Intelligence")
.HasColumnType("INTEGER");
+ b.Property("MaxCharisma")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxIntelligence")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxStrength")
+ .HasColumnType("INTEGER");
+
b.Property("Strength")
.HasColumnType("INTEGER");
diff --git a/Migrations/20250201022754_InitialCreate.cs b/Migrations/20250201173643_Initial.cs
similarity index 78%
rename from Migrations/20250201022754_InitialCreate.cs
rename to Migrations/20250201173643_Initial.cs
index 35b8e48..73b7145 100644
--- a/Migrations/20250201022754_InitialCreate.cs
+++ b/Migrations/20250201173643_Initial.cs
@@ -1,11 +1,12 @@
-using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace pet_companion_api.Migrations
{
///
- public partial class InitialCreate : Migration
+ public partial class Initial : Migration
{
///
protected override void Up(MigrationBuilder migrationBuilder)
@@ -18,7 +19,12 @@ namespace pet_companion_api.Migrations
Name = table.Column(type: "TEXT", nullable: false),
Class = table.Column(type: "INTEGER", nullable: false),
Level = table.Column(type: "INTEGER", nullable: false),
- UserId = table.Column(type: "TEXT", nullable: false)
+ UserId = table.Column(type: "TEXT", nullable: false),
+ IsDead = table.Column(type: "INTEGER", nullable: false),
+ PetGatherAction = table.Column(type: "INTEGER", nullable: false),
+ GatherActionSince = table.Column(type: "TEXT", nullable: false),
+ PetBasicAction = table.Column(type: "INTEGER", nullable: false),
+ BasicActionCooldown = table.Column(type: "TEXT", nullable: false)
},
constraints: table =>
{
@@ -32,7 +38,10 @@ namespace pet_companion_api.Migrations
PetId = table.Column(type: "TEXT", nullable: false),
Intelligence = table.Column(type: "INTEGER", nullable: false),
Strength = table.Column(type: "INTEGER", nullable: false),
- Charisma = table.Column(type: "INTEGER", nullable: false)
+ Charisma = table.Column(type: "INTEGER", nullable: false),
+ MaxIntelligence = table.Column(type: "INTEGER", nullable: false),
+ MaxStrength = table.Column(type: "INTEGER", nullable: false),
+ MaxCharisma = table.Column(type: "INTEGER", nullable: false)
},
constraints: table =>
{
diff --git a/Migrations/ApplicationDbContextModelSnapshot.cs b/Migrations/ApplicationDbContextModelSnapshot.cs
index 99dae93..3e63dbe 100644
--- a/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -22,12 +22,15 @@ namespace pet_companion_api.Migrations
b.Property("Id")
.HasColumnType("TEXT");
- b.Property("ActionSince")
+ b.Property("BasicActionCooldown")
.HasColumnType("TEXT");
b.Property("Class")
.HasColumnType("INTEGER");
+ b.Property("GatherActionSince")
+ .HasColumnType("TEXT");
+
b.Property("IsDead")
.HasColumnType("INTEGER");
@@ -38,7 +41,10 @@ namespace pet_companion_api.Migrations
.IsRequired()
.HasColumnType("TEXT");
- b.Property("PetAction")
+ b.Property("PetBasicAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("PetGatherAction")
.HasColumnType("INTEGER");
b.Property("UserId")
@@ -61,6 +67,15 @@ namespace pet_companion_api.Migrations
b.Property("Intelligence")
.HasColumnType("INTEGER");
+ b.Property("MaxCharisma")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxIntelligence")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxStrength")
+ .HasColumnType("INTEGER");
+
b.Property("Strength")
.HasColumnType("INTEGER");
diff --git a/Models/Pet.cs b/Models/Pet.cs
index 679f935..73cc59f 100644
--- a/Models/Pet.cs
+++ b/Models/Pet.cs
@@ -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);
+ }
}
}
diff --git a/Models/PetActionGather.cs b/Models/PetActionGather.cs
index 8b4aa3c..bc336cf 100644
--- a/Models/PetActionGather.cs
+++ b/Models/PetActionGather.cs
@@ -1,6 +1,6 @@
namespace pet_companion_api.Models
{
- public enum PetAction
+ public enum PetBasicAction
{
UNKNOWN,
FEED,
diff --git a/Models/PetStats.cs b/Models/PetStats.cs
index cd48302..baa8650 100644
--- a/Models/PetStats.cs
+++ b/Models/PetStats.cs
@@ -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;
}
}
diff --git a/Models/PetUpdateActionRequest.cs b/Models/PetUpdateActionRequest.cs
index 1c4be84..74bce90 100644
--- a/Models/PetUpdateActionRequest.cs
+++ b/Models/PetUpdateActionRequest.cs
@@ -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; }
}
}
diff --git a/Repositories/PetRepository.cs b/Repositories/PetRepository.cs
index 6ef0c31..13bdb98 100644
--- a/Repositories/PetRepository.cs
+++ b/Repositories/PetRepository.cs
@@ -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;
}
diff --git a/Services/PetService.cs b/Services/PetService.cs
index 1804b8b..aa004d1 100644
--- a/Services/PetService.cs
+++ b/Services/PetService.cs
@@ -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);
}