Add action gathering functionality: implement ActionGathered model and repository, update Pet model and services, and enhance GameItemsRepository with item retrieval methods.

This commit is contained in:
Jose Henrique 2025-02-09 21:22:52 -03:00
parent 653cc451d2
commit 215d4ecb72
18 changed files with 481 additions and 41 deletions

View File

@ -69,7 +69,7 @@ namespace PetCompanion.Controllers
{
try
{
var updatedPet = petService.UpdatePetResources(petId, userId.ToString());
var updatedPet = petService.CollectPetGathered(petId, userId.ToString());
return Ok(updatedPet);
}
catch (Exception ex)

View File

@ -18,6 +18,7 @@ namespace PetCompanion.Data
public DbSet<GameItem> GameItems { get; set; }
public DbSet<Inventory> Inventories { get; set; }
public DbSet<EquippedItem> EquippedItems { get; set; }
public DbSet<ActionGathered> ActionGathered { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@ -66,6 +67,17 @@ namespace PetCompanion.Data
.WithMany()
.HasForeignKey(e => e.GameItemId);
modelBuilder.Entity<ActionGathered>()
.HasOne(ag => ag.Pet)
.WithMany(p => p.ActionGathered)
.HasForeignKey(ag => ag.PetId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ActionGathered>()
.HasOne(ag => ag.GameItem)
.WithMany()
.HasForeignKey(ag => ag.ItemId);
// Seed initial data
var skills = SkillsData.GetInitialSkillsWithoutRelations();
var requirements = SkillsData.GetInitialSkillRequirements();

View File

@ -11,7 +11,7 @@ using PetCompanion.Data;
namespace PetCompanion.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250209011029_InitialCreate")]
[Migration("20250209234852_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
@ -20,6 +20,34 @@ namespace PetCompanion.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.Entity("PetCompanion.Models.ActionGathered", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Amount")
.HasColumnType("INTEGER");
b.Property<int?>("ItemId")
.HasColumnType("INTEGER");
b.Property<string>("PetId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Resource")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ItemId");
b.HasIndex("PetId");
b.ToTable("ActionGathered");
});
modelBuilder.Entity("PetCompanion.Models.EquippedItem", b =>
{
b.Property<int>("Id")
@ -668,6 +696,23 @@ namespace PetCompanion.Migrations
});
});
modelBuilder.Entity("PetCompanion.Models.ActionGathered", b =>
{
b.HasOne("PetCompanion.Models.GameItem", "GameItem")
.WithMany()
.HasForeignKey("ItemId");
b.HasOne("PetCompanion.Models.Pet", "Pet")
.WithMany("ActionGathered")
.HasForeignKey("PetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("GameItem");
b.Navigation("Pet");
});
modelBuilder.Entity("PetCompanion.Models.EquippedItem", b =>
{
b.HasOne("PetCompanion.Models.GameItem", "GameItem")
@ -759,6 +804,8 @@ namespace PetCompanion.Migrations
modelBuilder.Entity("PetCompanion.Models.Pet", b =>
{
b.Navigation("ActionGathered");
b.Navigation("EquippedItemsList");
b.Navigation("Inventory")

View File

@ -71,6 +71,33 @@ namespace PetCompanion.Migrations
table.PrimaryKey("PK_Skills", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ActionGathered",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
PetId = table.Column<string>(type: "TEXT", nullable: false),
Resource = table.Column<string>(type: "TEXT", nullable: true),
ItemId = table.Column<int>(type: "INTEGER", nullable: true),
Amount = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ActionGathered", x => x.Id);
table.ForeignKey(
name: "FK_ActionGathered_GameItems_ItemId",
column: x => x.ItemId,
principalTable: "GameItems",
principalColumn: "Id");
table.ForeignKey(
name: "FK_ActionGathered_Pets_PetId",
column: x => x.PetId,
principalTable: "Pets",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "EquippedItems",
columns: table => new
@ -301,6 +328,16 @@ namespace PetCompanion.Migrations
{ 14, 300, "Food", 8 }
});
migrationBuilder.CreateIndex(
name: "IX_ActionGathered_ItemId",
table: "ActionGathered",
column: "ItemId");
migrationBuilder.CreateIndex(
name: "IX_ActionGathered_PetId",
table: "ActionGathered",
column: "PetId");
migrationBuilder.CreateIndex(
name: "IX_EquippedItems_GameItemId",
table: "EquippedItems",
@ -335,6 +372,9 @@ namespace PetCompanion.Migrations
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ActionGathered");
migrationBuilder.DropTable(
name: "EquippedItems");

View File

@ -17,6 +17,34 @@ namespace PetCompanion.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.Entity("PetCompanion.Models.ActionGathered", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Amount")
.HasColumnType("INTEGER");
b.Property<int?>("ItemId")
.HasColumnType("INTEGER");
b.Property<string>("PetId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Resource")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ItemId");
b.HasIndex("PetId");
b.ToTable("ActionGathered");
});
modelBuilder.Entity("PetCompanion.Models.EquippedItem", b =>
{
b.Property<int>("Id")
@ -665,6 +693,23 @@ namespace PetCompanion.Migrations
});
});
modelBuilder.Entity("PetCompanion.Models.ActionGathered", b =>
{
b.HasOne("PetCompanion.Models.GameItem", "GameItem")
.WithMany()
.HasForeignKey("ItemId");
b.HasOne("PetCompanion.Models.Pet", "Pet")
.WithMany("ActionGathered")
.HasForeignKey("PetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("GameItem");
b.Navigation("Pet");
});
modelBuilder.Entity("PetCompanion.Models.EquippedItem", b =>
{
b.HasOne("PetCompanion.Models.GameItem", "GameItem")
@ -756,6 +801,8 @@ namespace PetCompanion.Migrations
modelBuilder.Entity("PetCompanion.Models.Pet", b =>
{
b.Navigation("ActionGathered");
b.Navigation("EquippedItemsList");
b.Navigation("Inventory")

22
Models/ActionGathered.cs Normal file
View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace PetCompanion.Models
{
public class ActionGathered
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[ForeignKey("Pet")]
public string PetId { get; set; }
public string? Resource { get; set; }
[ForeignKey("GameItem")]
public int? ItemId { get; set; }
public int Amount { get; set; }
[JsonIgnore]
public virtual Pet Pet { get; set; }
public virtual GameItem? GameItem { get; set; }
}
}

View File

@ -27,6 +27,8 @@ namespace PetCompanion.Models
public virtual Inventory Inventory { get; set; }
public virtual ICollection<EquippedItem> EquippedItemsList { get; set; } = new List<EquippedItem>();
public virtual ICollection<ActionGathered> ActionGathered { get; set; } = new List<ActionGathered>();
[NotMapped]
public Dictionary<ItemEquipTarget, int> EquippedItems
{

View File

@ -14,5 +14,7 @@
GATHERING_WISDOM,
GATHERING_GOLD,
GATHERING_FOOD,
EXPLORE,
BATTLE
}
}

View File

@ -31,6 +31,7 @@ namespace PetCompanion
builder.Services.AddScoped<PetClassRepository>();
builder.Services.AddScoped<PetClassService>();
builder.Services.AddScoped<PetActionService>();
builder.Services.AddScoped<SkillService>();
builder.Services.AddScoped<GameItemsRepository>();
builder.Services.AddScoped<GameItemService>();
@ -40,6 +41,10 @@ namespace PetCompanion
builder.Services.AddScoped<PetInventoryRepository>();
builder.Services.AddScoped<PetSkillService>();
builder.Services.AddScoped<PetInventoryService>();
builder.Services.AddScoped<ActionGatheredRepository>();
// Add the background service
builder.Services.AddHostedService<PetActionBackgroundService>();
// Add CORS policy
builder.Services.AddCors(options =>

View File

@ -0,0 +1,47 @@
using PetCompanion.Data;
using PetCompanion.Models;
using Microsoft.EntityFrameworkCore;
namespace PetCompanion.Repositories
{
public class ActionGatheredRepository
{
private readonly ApplicationDbContext _context;
public ActionGatheredRepository(ApplicationDbContext context)
{
_context = context;
}
public IEnumerable<ActionGathered> GetAllActionGatheredByPetId(string petId)
{
return _context.ActionGathered
.Where(ag => ag.PetId == petId)
.Include(ag => ag.GameItem)
.ToList();
}
public ActionGathered CreateActionGathered(ActionGathered actionGathered)
{
var entry = _context.ActionGathered.Add(actionGathered);
_context.SaveChanges();
return entry.Entity;
}
public ActionGathered UpdateActionGathered(ActionGathered actionGathered)
{
var entry = _context.ActionGathered.Update(actionGathered);
_context.SaveChanges();
return entry.Entity;
}
public void DeleteAllActionGatheredByPetId(string petId)
{
var actionsToDelete = _context.ActionGathered
.Where(ag => ag.PetId == petId);
_context.ActionGathered.RemoveRange(actionsToDelete);
_context.SaveChanges();
}
}
}

View File

@ -17,6 +17,11 @@ namespace PetCompanion.Repositories
return _context.GameItems.Find(id);
}
public IEnumerable<GameItem> GetAll()
{
return _context.GameItems;
}
public void Add(GameItem item)
{
_context.GameItems.Add(item);

View File

@ -21,6 +21,18 @@ namespace PetCompanion.Repositories
.Include(p => p.Resources)
.Include(p => p.Inventory)
.Include(p => p.EquippedItemsList)
.Include(p => p.ActionGathered)
.ToList();
}
public IEnumerable<Pet> GetPetWithActions()
{
return context.Pets
.Where(predicate => predicate.PetGatherAction != PetActionGather.IDLE)
.Include(p => p.Stats)
.Include(p => p.Resources)
.Include(p => p.Inventory)
.Include(p => p.EquippedItemsList)
.ToList();
}
@ -32,6 +44,7 @@ namespace PetCompanion.Repositories
.Include(p => p.Resources)
.Include(p => p.Inventory)
.Include(p => p.EquippedItemsList)
.Include(p => p.ActionGathered)
.FirstOrDefault();
}

View File

@ -35,6 +35,14 @@ namespace PetCompanion.Services
}
}
public GameItem GetRandomItem()
{
var items = gameItemsRepository.GetAll();
var random = new Random();
var index = random.Next(items.Count());
return items.ElementAt(index);
}
public void ApplyItemEffect(Pet pet, GameItem item)
{
var effects = item.Effect.Split(';');

View File

@ -0,0 +1,26 @@
namespace PetCompanion.Services
{
public class PetActionBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public PetActionBackgroundService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceProvider.CreateScope())
{
var petActionService = scope.ServiceProvider.GetRequiredService<PetActionService>();
petActionService.LoopPetActions();
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
}

View File

@ -0,0 +1,153 @@
using PetCompanion.Models;
using PetCompanion.Repositories;
namespace PetCompanion.Services
{
public class PetActionService
{
private readonly ActionGatheredRepository actionGatheredRepository;
private readonly PetRepository petRepository;
private readonly GameItemService gameItemService;
public PetActionService(ActionGatheredRepository actionGatheredRepository, GameItemService gameItemService, PetRepository petRepository)
{
this.actionGatheredRepository = actionGatheredRepository;
this.gameItemService = gameItemService;
this.petRepository = petRepository;
}
public IEnumerable<ActionGathered> GetGatheredByPet(string petId)
{
return actionGatheredRepository.GetAllActionGatheredByPetId(petId);
}
public void DeleteAllActionGatheredByPetId(string petId)
{
actionGatheredRepository.DeleteAllActionGatheredByPetId(petId);
}
public void LoopPetActions()
{
var pets = petRepository.GetPetWithActions();
foreach (var pet in pets)
{
var gatheredResources = CalculateGatheredResources(pet);
if (gatheredResources != null)
{
var gatheredResourcesDb = actionGatheredRepository.GetAllActionGatheredByPetId(pet.Id);
foreach (var resource in gatheredResources)
{
if (gatheredResourcesDb.Any(ag => ag.Resource == resource.Resource))
{
var existingResource = gatheredResourcesDb.First(ag => ag.Resource == resource.Resource);
existingResource.Amount += resource.Amount;
actionGatheredRepository.UpdateActionGathered(existingResource);
}
else
{
actionGatheredRepository.CreateActionGathered(resource);
}
}
}
}
}
public ICollection<ActionGathered>? CalculateGatheredResources(Pet pet)
{
if (pet.PetGatherAction == PetActionGather.IDLE)
return null;
var timeElapsed = (DateTime.UtcNow - pet.GatherActionSince).TotalMinutes;
var gathered = new List<ActionGathered>();
var baseRate = timeElapsed + pet.Level * 10; // Base rate per hour
gathered.Add(new ActionGathered
{
Resource = "Junk",
Amount = (int)(baseRate * 2)
});
var random = new Random();
switch (pet.PetGatherAction)
{
case PetActionGather.GATHERING_WISDOM:
gathered.Add(new ActionGathered
{
Resource = "Wisdom",
Amount = (int)(baseRate * (pet.Stats.Intelligence * 2))
});
break;
case PetActionGather.GATHERING_GOLD:
gathered.Add(new ActionGathered
{
Resource = "Gold",
Amount = (int)(baseRate * (pet.Stats.Charisma * 2))
});
break;
case PetActionGather.GATHERING_FOOD:
gathered.Add(new ActionGathered
{
Resource = "Food",
Amount = (int)(baseRate * (pet.Stats.Strength * 1.5))
});
break;
case PetActionGather.EXPLORE:
gathered.Add(new ActionGathered
{
Resource = "Wisdom",
Amount = (int)(baseRate * (pet.Stats.Strength * 3))
});
if (random.Next(0, 100) < 5)
{
pet.Health -= 5;
}
if (random.Next(0, 100) < 5)
{
gathered.Add(new ActionGathered
{
ItemId = gameItemService.GetRandomItem().Id,
Amount = 1
});
}
break;
case PetActionGather.BATTLE:
gathered.Add(new ActionGathered
{
Resource = "Gold",
Amount = (int)(baseRate * (pet.Stats.Strength * 3))
});
if (random.Next(0, 100) < 10)
{
pet.Health -= random.Next(5, 10);
}
if (random.Next(0, 100) < 500)
{
gathered.Add(new ActionGathered
{
ItemId = gameItemService.GetRandomItem().Id,
Amount = 1
});
}
break;
}
for (int i = 0; i < gathered.Count; i++)
{
gathered[i].PetId = pet.Id;
}
return gathered;
}
}
}

View File

@ -16,32 +16,5 @@ namespace PetCompanion.Services
{
return _petClassRepository.GetAllPetClassesInfo();
}
public Resources CalculateGatheredResources(PetStats stats, int petLevel, PetActionGather action, DateTime actionSince)
{
var timeElapsed = (DateTime.UtcNow - actionSince).TotalHours;
var resources = new Resources();
if (action == PetActionGather.IDLE)
return resources;
var baseRate = timeElapsed * 0.5 + petLevel; // Base rate per hour
resources.Junk = (int)(baseRate * 2);
switch (action)
{
case PetActionGather.GATHERING_WISDOM:
resources.Wisdom = (int)(baseRate * (stats.Intelligence * 2));
break;
case PetActionGather.GATHERING_GOLD:
resources.Gold = (int)(baseRate * (stats.Charisma * 2));
break;
case PetActionGather.GATHERING_FOOD:
resources.Food = (int)(baseRate * (stats.Strength * 1.5));
break;
}
return resources;
}
}
}

View File

@ -146,6 +146,12 @@ namespace PetCompanion.Services
if (gameItem == null)
throw new Exception("Item not found");
if (pet == null)
throw new Exception("Pet not found");
if (pet.Inventory.Items.Count + quantity > pet.Inventory.Capacity)
throw new Exception("Not enough space in inventory");
for (int i = 0; i < quantity; i++)
{
pet.Inventory.Items.Add(itemId);

View File

@ -1,4 +1,5 @@
using PetCompanion.Models;
using Microsoft.EntityFrameworkCore.Query;
using PetCompanion.Models;
using PetCompanion.Repositories;
namespace PetCompanion.Services
@ -10,19 +11,22 @@ namespace PetCompanion.Services
private readonly GameItemService gameItemService;
private readonly GameItemsRepository gameItemsRepository;
private readonly PetInventoryService petInventoryService;
private readonly PetActionService petActionService;
public PetService(
PetRepository petRepository,
PetClassService petClassService,
GameItemService gameItemService,
GameItemsRepository gameItemsRepository,
PetInventoryService petInventoryService)
PetInventoryService petInventoryService,
PetActionService petActionService)
{
this.petRepository = petRepository;
this.petClassService = petClassService;
this.gameItemService = gameItemService;
this.gameItemsRepository = gameItemsRepository;
this.petInventoryService = petInventoryService;
this.petActionService = petActionService;
}
public IEnumerable<Pet> GetAllPets(Guid userId)
@ -123,7 +127,7 @@ namespace PetCompanion.Services
}
}
public Resources GetGatheredResources(string petId, string userId)
public IEnumerable<ActionGathered> GetGatheredResources(string petId, string userId)
{
var pet = petRepository.GetPetById(petId, userId);
@ -132,10 +136,10 @@ namespace PetCompanion.Services
throw new Exception("Pet not found");
}
return petClassService.CalculateGatheredResources(pet.Stats, pet.Level, pet.PetGatherAction, pet.GatherActionSince);
return petActionService.GetGatheredByPet(petId);
}
public Pet UpdatePetResources(string petId, string userId)
public Pet CollectPetGathered(string petId, string userId)
{
var pet = petRepository.GetPetById(petId, userId);
if (pet == null)
@ -143,15 +147,43 @@ namespace PetCompanion.Services
throw new Exception("Pet not found");
}
var gatheredResources = petClassService.CalculateGatheredResources(pet.Stats, pet.Level, pet.PetGatherAction, pet.GatherActionSince);
var petGathered = petActionService.GetGatheredByPet(petId);
pet.Resources.Wisdom += gatheredResources.Wisdom;
pet.Resources.Gold += gatheredResources.Gold;
pet.Resources.Food += gatheredResources.Food;
pet.Resources.Junk += gatheredResources.Junk;
pet.GatherActionSince = DateTime.UtcNow;
if (petGathered == null)
{
throw new Exception("No resources to collect");
}
return petRepository.UpdatePetResources(pet);
foreach (var resource in petGathered)
{
if (resource.Resource != null && resource.Resource != string.Empty)
{
switch (resource.Resource)
{
case "Junk":
pet.Resources.Junk += resource.Amount;
break;
case "Food":
pet.Resources.Food += resource.Amount;
break;
case "Gold":
pet.Resources.Gold += resource.Amount;
break;
case "Wisdom":
pet.Resources.Wisdom += resource.Amount;
break;
}
}
else if (resource.ItemId != null && resource.ItemId > 0)
{
petInventoryService.AddItemToPet(petId, userId, resource.ItemId ?? 1, resource.Amount);
}
}
var updatedPet = petRepository.UpdatePet(pet);
petActionService.DeleteAllActionGatheredByPetId(petId);
return updatedPet;
}
public Pet GetPet(string petId, string userId)