diff --git a/.gitignore b/.gitignore
index 4d5d908..bf9f0aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -371,8 +371,7 @@ site/
 
 .aider.*
 PROMPT.md
-.aider*
-.windsurfrules
+.*rules
 
 ## Python pip/env files
 Pipfile
diff --git a/src/EllieBot/Migrations/PostgreSql/20250324230804_quests.sql b/src/EllieBot/Migrations/PostgreSql/20250324230804_quests.sql
new file mode 100644
index 0000000..a5471d9
--- /dev/null
+++ b/src/EllieBot/Migrations/PostgreSql/20250324230804_quests.sql
@@ -0,0 +1,20 @@
+START TRANSACTION;
+CREATE TABLE userquest (    
+    id integer GENERATED BY DEFAULT AS IDENTITY,
+    questnumber integer NOT NULL,
+    userid numeric(20,0) NOT NULL,
+    questid integer NOT NULL,
+    progress bigint NOT NULL,
+    iscompleted boolean NOT NULL,
+    dateassigned timestamp without time zone NOT NULL,
+    CONSTRAINT pk_userquest PRIMARY KEY (id)
+);
+
+CREATE INDEX ix_userquest_userid ON userquest (userid);
+
+CREATE UNIQUE INDEX ix_userquest_userid_questnumber_dateassigned ON userquest (userid, questnumber, dateassigned);
+
+INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
+VALUES ('20250324230804_quests', '9.0.1');
+
+COMMIT;
diff --git a/src/EllieBot/Migrations/PostgreSql/20250327001838_fishitems.sql b/src/EllieBot/Migrations/PostgreSql/20250327001838_fishitems.sql
new file mode 100644
index 0000000..db03948
--- /dev/null
+++ b/src/EllieBot/Migrations/PostgreSql/20250327001838_fishitems.sql
@@ -0,0 +1,25 @@
+START TRANSACTION;
+ALTER TABLE userfishstats DROP COLUMN bait;
+
+ALTER TABLE userfishstats DROP COLUMN pole;
+
+ALTER TABLE userquest ALTER COLUMN progress TYPE integer;
+
+CREATE TABLE userfishitem (
+    
+    id integer GENERATED BY DEFAULT AS IDENTITY,
+    userid numeric(20,0) NOT NULL,
+    itemtype integer NOT NULL,
+    itemid integer NOT NULL,
+    isequipped boolean NOT NULL,
+    usesleft integer,
+    expiresat timestamp without time zone,
+    CONSTRAINT pk_userfishitem PRIMARY KEY (id)
+);
+
+CREATE INDEX ix_userfishitem_userid ON userfishitem (userid);
+
+INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
+VALUES ('20250327001838_fishitems', '9.0.1');
+
+COMMIT;
diff --git a/src/EllieBot/Migrations/PostgreSql/20250328075848_quests.sql b/src/EllieBot/Migrations/PostgreSql/20250328075848_quests.sql
deleted file mode 100644
index 4392fe6..0000000
--- a/src/EllieBot/Migrations/PostgreSql/20250328075848_quests.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-START TRANSACTION;
-INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
-VALUES ('20250328075848_quests', '9.0.1');
-
-COMMIT;
-
diff --git a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs
index f8ed231..5988066 100644
--- a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs
+++ b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs
@@ -864,57 +864,6 @@ namespace EllieBot.Migrations.PostgreSql
                     b.ToTable("discorduser", (string)null);
                 });
 
-            modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("integer")
-                        .HasColumnName("id");
-
-                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
-
-                    b.Property<bool>("AllowTarget")
-                        .HasColumnType("boolean")
-                        .HasColumnName("allowtarget");
-
-                    b.Property<bool>("AutoDeleteTrigger")
-                        .HasColumnType("boolean")
-                        .HasColumnName("autodeletetrigger");
-
-                    b.Property<bool>("ContainsAnywhere")
-                        .HasColumnType("boolean")
-                        .HasColumnName("containsanywhere");
-
-                    b.Property<DateTime?>("DateAdded")
-                        .HasColumnType("timestamp without time zone")
-                        .HasColumnName("dateadded");
-
-                    b.Property<bool>("DmResponse")
-                        .HasColumnType("boolean")
-                        .HasColumnName("dmresponse");
-
-                    b.Property<decimal?>("GuildId")
-                        .HasColumnType("numeric(20,0)")
-                        .HasColumnName("guildid");
-
-                    b.Property<string>("Reactions")
-                        .HasColumnType("text")
-                        .HasColumnName("reactions");
-
-                    b.Property<string>("Response")
-                        .HasColumnType("text")
-                        .HasColumnName("response");
-
-                    b.Property<string>("Trigger")
-                        .HasColumnType("text")
-                        .HasColumnName("trigger");
-
-                    b.HasKey("Id")
-                        .HasName("pk_expressions");
-
-                    b.ToTable("expressions", (string)null);
-                });
-
             modelBuilder.Entity("EllieBot.Db.Models.FeedSub", b =>
                 {
                     b.Property<int>("Id")
@@ -1859,6 +1808,57 @@ namespace EllieBot.Migrations.PostgreSql
                     b.ToTable("ncpixel", (string)null);
                 });
 
+            modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+                    b.Property<bool>("AllowTarget")
+                        .HasColumnType("boolean")
+                        .HasColumnName("allowtarget");
+
+                    b.Property<bool>("AutoDeleteTrigger")
+                        .HasColumnType("boolean")
+                        .HasColumnName("autodeletetrigger");
+
+                    b.Property<bool>("ContainsAnywhere")
+                        .HasColumnType("boolean")
+                        .HasColumnName("containsanywhere");
+
+                    b.Property<DateTime?>("DateAdded")
+                        .HasColumnType("timestamp without time zone")
+                        .HasColumnName("dateadded");
+
+                    b.Property<bool>("DmResponse")
+                        .HasColumnType("boolean")
+                        .HasColumnName("dmresponse");
+
+                    b.Property<decimal?>("GuildId")
+                        .HasColumnType("numeric(20,0)")
+                        .HasColumnName("guildid");
+
+                    b.Property<string>("Reactions")
+                        .HasColumnType("text")
+                        .HasColumnName("reactions");
+
+                    b.Property<string>("Response")
+                        .HasColumnType("text")
+                        .HasColumnName("response");
+
+                    b.Property<string>("Trigger")
+                        .HasColumnType("text")
+                        .HasColumnName("trigger");
+
+                    b.HasKey("Id")
+                        .HasName("pk_expressions");
+
+                    b.ToTable("expressions", (string)null);
+                });
+
             modelBuilder.Entity("EllieBot.Db.Models.Notify", b =>
                 {
                     b.Property<int>("Id")
@@ -2985,6 +2985,52 @@ namespace EllieBot.Migrations.PostgreSql
                     b.ToTable("unroletimer", (string)null);
                 });
 
+            modelBuilder.Entity("EllieBot.Db.Models.UserQuest", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+                    b.Property<DateTime>("DateAssigned")
+                        .HasColumnType("timestamp without time zone")
+                        .HasColumnName("dateassigned");
+
+                    b.Property<bool>("IsCompleted")
+                        .HasColumnType("boolean")
+                        .HasColumnName("iscompleted");
+
+                    b.Property<int>("Progress")
+                        .HasColumnType("integer")
+                        .HasColumnName("progress");
+
+                    b.Property<int>("QuestId")
+                        .HasColumnType("integer")
+                        .HasColumnName("questid");
+
+                    b.Property<int>("QuestNumber")
+                        .HasColumnType("integer")
+                        .HasColumnName("questnumber");
+
+                    b.Property<decimal>("UserId")
+                        .HasColumnType("numeric(20,0)")
+                        .HasColumnName("userid");
+
+                    b.HasKey("Id")
+                        .HasName("pk_userquest");
+
+                    b.HasIndex("UserId")
+                        .HasDatabaseName("ix_userquest_userid");
+
+                    b.HasIndex("UserId", "QuestNumber", "DateAssigned")
+                        .IsUnique()
+                        .HasDatabaseName("ix_userquest_userid_questnumber_dateassigned");
+
+                    b.ToTable("userquest", (string)null);
+                });
+
             modelBuilder.Entity("EllieBot.Db.Models.UserXpStats", b =>
                 {
                     b.Property<int>("Id")
@@ -3467,6 +3513,48 @@ namespace EllieBot.Migrations.PostgreSql
                     b.ToTable("xpshopowneditem", (string)null);
                 });
 
+            modelBuilder.Entity("EllieBot.Modules.Games.Fish.Db.UserFishItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+                    b.Property<DateTime?>("ExpiresAt")
+                        .HasColumnType("timestamp without time zone")
+                        .HasColumnName("expiresat");
+
+                    b.Property<bool>("IsEquipped")
+                        .HasColumnType("boolean")
+                        .HasColumnName("isequipped");
+
+                    b.Property<int>("ItemId")
+                        .HasColumnType("integer")
+                        .HasColumnName("itemid");
+
+                    b.Property<int>("ItemType")
+                        .HasColumnType("integer")
+                        .HasColumnName("itemtype");
+
+                    b.Property<decimal>("UserId")
+                        .HasColumnType("numeric(20,0)")
+                        .HasColumnName("userid");
+
+                    b.Property<int?>("UsesLeft")
+                        .HasColumnType("integer")
+                        .HasColumnName("usesleft");
+
+                    b.HasKey("Id")
+                        .HasName("pk_userfishitem");
+
+                    b.HasIndex("UserId")
+                        .HasDatabaseName("ix_userfishitem_userid");
+
+                    b.ToTable("userfishitem", (string)null);
+                });
+
             modelBuilder.Entity("EllieBot.Modules.Games.FishCatch", b =>
                 {
                     b.Property<int>("Id")
@@ -3510,14 +3598,6 @@ namespace EllieBot.Migrations.PostgreSql
 
                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 
-                    b.Property<int?>("Bait")
-                        .HasColumnType("integer")
-                        .HasColumnName("bait");
-
-                    b.Property<int?>("Pole")
-                        .HasColumnType("integer")
-                        .HasColumnName("pole");
-
                     b.Property<int>("Skill")
                         .HasColumnType("integer")
                         .HasColumnName("skill");
@@ -4181,4 +4261,4 @@ namespace EllieBot.Migrations.PostgreSql
 #pragma warning restore 612, 618
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/Sqlite/20250324230801_quests.sql b/src/EllieBot/Migrations/Sqlite/20250324230801_quests.sql
new file mode 100644
index 0000000..3798c8a
--- /dev/null
+++ b/src/EllieBot/Migrations/Sqlite/20250324230801_quests.sql
@@ -0,0 +1,19 @@
+BEGIN TRANSACTION;
+CREATE TABLE "UserQuest" (
+    "Id" INTEGER NOT NULL CONSTRAINT "PK_UserQuest" PRIMARY KEY AUTOINCREMENT,
+    "QuestNumber" INTEGER NOT NULL,
+    "UserId" INTEGER NOT NULL,
+    "QuestId" INTEGER NOT NULL,
+    "Progress" INTEGER NOT NULL,
+    "IsCompleted" INTEGER NOT NULL,
+    "DateAssigned" TEXT NOT NULL
+);
+
+CREATE INDEX "IX_UserQuest_UserId" ON "UserQuest" ("UserId");
+
+CREATE UNIQUE INDEX "IX_UserQuest_UserId_QuestNumber_DateAssigned" ON "UserQuest" ("UserId", "QuestNumber", "DateAssigned");
+
+INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
+VALUES ('20250324230801_quests', '9.0.1');
+
+COMMIT;
diff --git a/src/EllieBot/Migrations/Sqlite/20250327001835_fishitems.sql b/src/EllieBot/Migrations/Sqlite/20250327001835_fishitems.sql
new file mode 100644
index 0000000..a8f3bc2
--- /dev/null
+++ b/src/EllieBot/Migrations/Sqlite/20250327001835_fishitems.sql
@@ -0,0 +1,43 @@
+BEGIN TRANSACTION;
+CREATE TABLE "UserFishItem" (    
+    "Id" INTEGER NOT NULL CONSTRAINT "PK_UserFishItem" PRIMARY KEY AUTOINCREMENT,
+    "UserId" INTEGER NOT NULL,
+    "ItemType" INTEGER NOT NULL,
+    "ItemId" INTEGER NOT NULL,
+    "IsEquipped" INTEGER NOT NULL,
+    "UsesLeft" INTEGER NULL,
+    "ExpiresAt" TEXT NULL
+);
+
+CREATE INDEX "IX_UserFishItem_UserId" ON "UserFishItem" ("UserId");
+
+CREATE TABLE "ef_temp_UserFishStats" (    
+    "Id" INTEGER NOT NULL CONSTRAINT "PK_UserFishStats" PRIMARY KEY AUTOINCREMENT,
+    "Skill" INTEGER NOT NULL,
+    "UserId" INTEGER NOT NULL
+);
+
+INSERT INTO "ef_temp_UserFishStats" ("Id", "Skill", "UserId")
+SELECT "Id", "Skill", "UserId"
+FROM "UserFishStats";
+
+COMMIT;
+
+PRAGMA foreign_keys = 0;
+
+BEGIN TRANSACTION;
+DROP TABLE "UserFishStats";
+
+ALTER TABLE "ef_temp_UserFishStats" RENAME TO "UserFishStats";
+
+COMMIT;
+
+PRAGMA foreign_keys = 1;
+
+BEGIN TRANSACTION;
+CREATE UNIQUE INDEX "IX_UserFishStats_UserId" ON "UserFishStats" ("UserId");
+
+COMMIT;
+
+INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
+VALUES ('20250327001835_fishitems', '9.0.1');
diff --git a/src/EllieBot/Migrations/Sqlite/20250328075818_quests.sql b/src/EllieBot/Migrations/Sqlite/20250328075818_quests.sql
deleted file mode 100644
index aa0c9cd..0000000
--- a/src/EllieBot/Migrations/Sqlite/20250328075818_quests.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN TRANSACTION;
-INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
-VALUES ('20250328075818_quests', '9.0.1');
-
-COMMIT;
-
diff --git a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs
index c70ba74..8e5c9c9 100644
--- a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs
+++ b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs
@@ -646,44 +646,6 @@ namespace EllieBot.Migrations.Sqlite
                     b.ToTable("DiscordUser");
                 });
 
-            modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("AllowTarget")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("AutoDeleteTrigger")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("ContainsAnywhere")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<DateTime?>("DateAdded")
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("DmResponse")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<ulong?>("GuildId")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Reactions")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Response")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Trigger")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.ToTable("Expressions");
-                });
-
             modelBuilder.Entity("EllieBot.Db.Models.FeedSub", b =>
                 {
                     b.Property<int>("Id")
@@ -1388,6 +1350,44 @@ namespace EllieBot.Migrations.Sqlite
                     b.ToTable("NCPixel");
                 });
 
+            modelBuilder.Entity("EllieBot.Db.Models.EllieExpression", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("AllowTarget")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("AutoDeleteTrigger")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<bool>("ContainsAnywhere")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime?>("DateAdded")
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("DmResponse")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<ulong?>("GuildId")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<string>("Reactions")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Response")
+                        .HasColumnType("TEXT");
+
+                    b.Property<string>("Trigger")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Expressions");
+                });
+
             modelBuilder.Entity("EllieBot.Db.Models.Notify", b =>
                 {
                     b.Property<int>("Id")
@@ -2224,6 +2224,40 @@ namespace EllieBot.Migrations.Sqlite
                     b.ToTable("UnroleTimer");
                 });
 
+            modelBuilder.Entity("EllieBot.Db.Models.UserQuest", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime>("DateAssigned")
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("IsCompleted")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("Progress")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("QuestId")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("QuestNumber")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<ulong>("UserId")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId");
+
+                    b.HasIndex("UserId", "QuestNumber", "DateAssigned")
+                        .IsUnique();
+
+                    b.ToTable("UserQuest");
+                });
+
             modelBuilder.Entity("EllieBot.Db.Models.UserXpStats", b =>
                 {
                     b.Property<int>("Id")
@@ -2579,6 +2613,37 @@ namespace EllieBot.Migrations.Sqlite
                     b.ToTable("XpShopOwnedItem");
                 });
 
+            modelBuilder.Entity("EllieBot.Modules.Games.Fish.Db.UserFishItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property<DateTime?>("ExpiresAt")
+                        .HasColumnType("TEXT");
+
+                    b.Property<bool>("IsEquipped")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("ItemId")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int>("ItemType")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<ulong>("UserId")
+                        .HasColumnType("INTEGER");
+
+                    b.Property<int?>("UsesLeft")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("UserFishItem");
+                });
+
             modelBuilder.Entity("EllieBot.Modules.Games.FishCatch", b =>
                 {
                     b.Property<int>("Id")
@@ -2610,12 +2675,6 @@ namespace EllieBot.Migrations.Sqlite
                         .ValueGeneratedOnAdd()
                         .HasColumnType("INTEGER");
 
-                    b.Property<int?>("Bait")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int?>("Pole")
-                        .HasColumnType("INTEGER");
-
                     b.Property<int>("Skill")
                         .HasColumnType("INTEGER");
 
@@ -3178,4 +3237,4 @@ namespace EllieBot.Migrations.Sqlite
 #pragma warning restore 612, 618
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/Db/UserFishItem.cs b/src/EllieBot/Modules/Games/Fish/Db/UserFishItem.cs
new file mode 100644
index 0000000..26985bc
--- /dev/null
+++ b/src/EllieBot/Modules/Games/Fish/Db/UserFishItem.cs
@@ -0,0 +1,70 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using EllieBot.Db;
+using EllieBot.Modules.Games.Fish;
+
+namespace EllieBot.Modules.Games.Fish.Db;
+
+/// <summary>
+/// Represents a fishing item owned by a user.
+/// </summary>
+public class UserFishItem
+{
+    /// <summary>
+    /// The unique identifier for this user fish item.
+    /// </summary>
+    [Key]
+    public int Id { get; set; }
+
+    /// <summary>
+    /// The ID of the user who owns this item.
+    /// </summary>
+    public ulong UserId { get; set; }
+    
+    /// <summary>
+    /// The type of the fishing item.
+    /// </summary>
+    public FishItemType ItemType { get; set; }
+    
+    /// <summary>
+    /// The ID of the fishing item.
+    /// </summary>
+    public int ItemId { get; set; }
+    
+    /// <summary>
+    /// Indicates whether the item is currently equipped by the user.
+    /// </summary>
+    public bool IsEquipped { get; set; }
+    
+    /// <summary>
+    /// The number of uses left for this item. Null means unlimited uses.
+    /// </summary>
+    public int? UsesLeft { get; set; }
+    
+    /// <summary>
+    /// The date and time when this item expires. Null means the item doesn't expire.
+    /// </summary>
+    public DateTime? ExpiresAt { get; set; }
+
+
+    public int? ExpiryFromNowInMinutes()
+    {
+        if (ExpiresAt is null)
+            return null;
+        
+        return (int)(ExpiresAt.Value - DateTime.UtcNow).TotalMinutes;
+    }
+}
+
+/// <summary>
+/// Entity configuration for UserFishItem.
+/// </summary>
+public class UserFishItemConfiguration : IEntityTypeConfiguration<UserFishItem>
+{
+    public void Configure(EntityTypeBuilder<UserFishItem> builder)
+    {
+        builder.HasIndex(x => new { x.UserId });
+    }
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/Db/UserFishStats.cs b/src/EllieBot/Modules/Games/Fish/Db/UserFishStats.cs
index fabfab4..8462f1f 100644
--- a/src/EllieBot/Modules/Games/Fish/Db/UserFishStats.cs
+++ b/src/EllieBot/Modules/Games/Fish/Db/UserFishStats.cs
@@ -9,20 +9,4 @@ public sealed class UserFishStats
 
     public ulong UserId { get; set; }
     public int Skill { get; set; }
-
-    public int? Pole { get; set; }
-    public int? Bait { get; set; }
-}
-
-// public sealed class FishingPole
-// {
-    // [Key]
-    // public int Id { get; set; }
-    
-    // public string Name { get; set; } = string.Empty;
-    
-    // public long Price { get; set; }
-
-    // public string Emoji { get; set; } = string.Empty;
-
-// }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/FishConfig.cs b/src/EllieBot/Modules/Games/Fish/FishConfig.cs
index 3b97d76..18b5045 100644
--- a/src/EllieBot/Modules/Games/Fish/FishConfig.cs
+++ b/src/EllieBot/Modules/Games/Fish/FishConfig.cs
@@ -14,43 +14,8 @@ public sealed partial class FishConfig : ICloneable<FishConfig>
     public List<string> StarEmojis { get; set; } = new();
     public List<string> SpotEmojis { get; set; } = new();
     public FishChance Chance { get; set; } = new FishChance();
-    // public List<FishBait> Baits { get; set; } = new();
-    // public List<FishingPole> Poles { get; set; } = new();
+    
     public List<FishData> Fish { get; set; } = new();
     public List<FishData> Trash { get; set; } = new();
-}
-
-// public sealed class FishBait : ICloneable<FishBait>
-// {
-//     public int Id { get; set; }
-//     public string Name { get; set; } = string.Empty;
-//     public long Price { get; set; }
-//     public string Emoji { get; set; } = string.Empty;
-//     public int StackSize { get; set; } = 100;
-//
-//     public string? OnlyWeather { get; set; }
-//     public string? OnlySpot { get; set; }
-//     public string? OnlyTime { get; set; }
-//
-//     public double FishMulti { get; set; } = 1;
-//     public double TrashMulti { get; set; } = 1;
-//     public double NothingMulti { get; set; } = 1;
-//
-//     public double RareFishMulti { get; set; } = 1;
-//     public double RareTrashMulti { get; set; } = 1;
-//     
-//     public double MaxStarMulti { get; set; } = 1;
-// }
-//
-// public sealed class FishingPole : ICloneable<FishingPole>
-// {
-//     public int Id { get; set; }
-//     public string Name { get; set; } = string.Empty;
-//     public long Price { get; set; }
-//     public string Emoji { get; set; } = string.Empty;
-//     public string Img { get; set; } = string.Empty;
-//
-//     public double FishMulti { get; set; } = 1;
-//     public double TrashMulti { get; set; } = 1;
-//     public double NothingMulti { get; set; } = 1;
-// }
\ No newline at end of file
+    public List<FishItem> Items { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/FishItem.cs b/src/EllieBot/Modules/Games/Fish/FishItem.cs
new file mode 100644
index 0000000..09df143
--- /dev/null
+++ b/src/EllieBot/Modules/Games/Fish/FishItem.cs
@@ -0,0 +1,98 @@
+namespace EllieBot.Modules.Games;
+
+/// <summary>
+/// Represents an item used in the fishing game.
+/// </summary>
+public class FishItem
+{
+    /// <summary>
+    /// Unique identifier for the item.
+    /// </summary>
+    public int Id { get; set; }
+    
+    /// <summary>
+    /// Type of the fishing item (pole, bait, boat, potion).
+    /// </summary>
+    public FishItemType ItemType { get; set; }
+    
+    /// <summary>
+    /// Name of the item.
+    /// </summary>
+    public string Name { get; set; } = string.Empty;
+    
+    /// <summary>
+    /// Item Emoji
+    /// </summary>
+    public string Emoji { get; set; } = string.Empty;
+    
+    /// <summary>
+    /// Description of the item.
+    /// </summary>
+    public string Description { get; set; } = string.Empty;
+    
+    /// <summary>
+    /// Price of the item.
+    /// </summary>
+    public int Price { get; set; }
+    
+    /// <summary>
+    /// Number of times the item can be used. Null means unlimited uses.
+    /// </summary>
+    public int? Uses { get; set; }
+    
+    /// <summary>
+    /// Duration of the item's effect in minutes. Null means permanent effect.
+    /// </summary>
+    public int? DurationMinutes { get; set; }
+    
+    /// <summary>
+    /// Multiplier affecting the fish catch rate.
+    /// </summary>
+    public double? FishMultiplier { get; set; }
+    
+    /// <summary>
+    /// Multiplier affecting the trash catch rate.
+    /// </summary>
+    public double? TrashMultiplier { get; set; }
+    
+    /// <summary>
+    /// Multiplier affecting the maximum star rating of caught fish.
+    /// </summary>
+    public double? MaxStarMultiplier { get; set; }
+    
+    /// <summary>
+    /// Multiplier affecting the chance of catching rare fish.
+    /// </summary>
+    public double? RareMultiplier { get; set; }
+    
+    /// <summary>
+    /// Multiplier affecting the fishing speed.
+    /// </summary>
+    public double? FishingSpeedMultiplier { get; set; }
+}
+
+/// <summary>
+/// Defines the types of items available in the fishing game.
+/// </summary>
+public enum FishItemType
+{
+    /// <summary>
+    /// Fishing pole used to catch fish.
+    /// </summary>
+    Pole,
+    
+    /// <summary>
+    /// Bait used to attract fish.
+    /// </summary>
+    Bait,
+    
+    /// <summary>
+    /// Boat used for fishing.
+    /// </summary>
+    Boat,
+    
+    /// <summary>
+    /// Potion that provides temporary effects.
+    /// </summary>
+    Potion
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/FishItemCommands.cs b/src/EllieBot/Modules/Games/Fish/FishItemCommands.cs
new file mode 100644
index 0000000..fca5b15
--- /dev/null
+++ b/src/EllieBot/Modules/Games/Fish/FishItemCommands.cs
@@ -0,0 +1,240 @@
+using EllieBot.Modules.Games.Fish.Db;
+
+namespace EllieBot.Modules.Games;
+
+public partial class Games
+{
+    public class FishItemCommands(FishItemService fis, ICurrencyProvider cp) : EllieModule
+    {
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task FishShop()
+        {
+            var items = fis.GetItems();
+
+            await Response()
+                .Paginated()
+                .Items(items)
+                .PageSize(9)
+                .CurrentPage(0)
+                .Page((pageItems, i) =>
+                {
+                    var eb = CreateEmbed()
+                        .WithTitle(GetText(strs.fish_items_title))
+                        .WithFooter("`.fibuy <id>` to by an item")
+                        .WithOkColor();
+
+                    foreach (var item in pageItems)
+                    {
+                        var description = GetItemDescription(item);
+                        eb.AddField($"{item.Id}",
+                            $"""
+                             {description}
+                              
+                             """,
+                            true);
+                    }
+
+                    return eb;
+                })
+                .AddFooter(false)
+                .SendAsync();
+        }
+
+        private string GetItemDescription(FishItem item, UserFishItem? userItem = null)
+        {
+            var multiplierInfo = GetMultiplierInfo(item);
+
+            var priceText = userItem is null
+                ? $"【 **{CurrencyHelper.N(item.Price, Culture, cp.GetCurrencySign())}** 】"
+                : "";
+
+            return $"""
+                     《 **{item.Name}** 》
+                     {GetEmoji(item.ItemType)} `{item.ItemType.ToString().ToLower()}` {priceText}
+                     {item.Description}
+                     {GetItemNotes(item, userItem)}
+                     {multiplierInfo}
+                    """;
+        }
+
+        private string GetItemNotes(FishItem item, UserFishItem? userItem)
+        {
+            var stats = new List<string>();
+
+            if (item.Uses.HasValue)
+                stats.Add($"**Uses:** {userItem?.UsesLeft ?? item.Uses}");
+
+            if (item.DurationMinutes.HasValue)
+                stats.Add($"**Duration:** {userItem?.ExpiryFromNowInMinutes() ?? item.DurationMinutes}m");
+
+            var toReturn = stats.Count > 0 ? string.Join(" | ", stats) + "\n" : "\n";
+
+            return "\n" + toReturn;
+        }
+
+        public static string GetEmoji(FishItemType itemType)
+            => itemType switch
+            {
+                FishItemType.Pole => @"\🎣",
+                FishItemType.Boat => @"\⛵",
+                FishItemType.Bait => @"\🍥",
+                FishItemType.Potion => @"\🍷",
+                _ => ""
+            };
+
+        private string GetMultiplierInfo(FishItem item)
+        {
+            var multipliers = new FishMultipliers()
+            {
+                FishMultiplier = item.FishMultiplier ?? 1,
+                TrashMultiplier = item.TrashMultiplier ?? 1,
+                RareMultiplier = item.RareMultiplier ?? 1,
+                StarMultiplier = item.MaxStarMultiplier ?? 1,
+                FishingSpeedMultiplier = item.FishingSpeedMultiplier ?? 1
+            };
+
+            return GetMultiplierInfo(multipliers);
+        }
+
+
+        public static string GetMultiplierInfo(FishMultipliers item)
+        {
+            var multipliers = new List<string>();
+            if (item.FishMultiplier is not 1.0d)
+                multipliers.Add($"{AsPercent(item.FishMultiplier)} chance to catch fish");
+
+            if (item.TrashMultiplier is not 1.0d)
+                multipliers.Add($"{AsPercent(item.TrashMultiplier)} chance to catch trash");
+
+            if (item.RareMultiplier is not 1.0d)
+                multipliers.Add($"{AsPercent(item.RareMultiplier)} chance to catch rare fish");
+
+            if (item.StarMultiplier is not 1.0d)
+                multipliers.Add($"{AsPercent(item.StarMultiplier)} to max star rating");
+
+            if (item.FishingSpeedMultiplier is not 1.0d)
+                multipliers.Add($"{AsPercent(item.FishingSpeedMultiplier)} fishing speed");
+
+            return multipliers.Count > 0
+                ? $"{string.Join("\n", multipliers)}\n"
+                : "";
+        }
+
+        private static string AsPercent(double multiplier)
+        {
+            var percentage = (int)((multiplier - 1.0f) * 100);
+            return percentage >= 0 ? $"**+{percentage}%**" : $"**{percentage}%**";
+        }
+
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task FishBuy(int itemId)
+        {
+            var res = await fis.BuyItemAsync(ctx.User.Id, itemId);
+
+            if (res.TryPickT1(out var err, out var eqItem))
+            {
+                if (err == BuyResult.InsufficientFunds)
+                    await Response().Error(strs.not_enough(cp.GetCurrencySign())).SendAsync();
+                else
+                    await Response().Error(strs.fish_item_not_found).SendAsync();
+
+                return;
+            }
+
+            var embed = CreateEmbed()
+                .WithDescription(GetText(strs.fish_buy_success))
+                .AddField(eqItem.Name, GetMultiplierInfo(eqItem));
+
+            await Response()
+                .Embed(embed)
+                .Interaction(_inter.Create(ctx.User.Id,
+                    new ButtonBuilder("Inventory", Guid.NewGuid().ToString(), ButtonStyle.Secondary),
+                    (smc) => FishInv()))
+                .SendAsync();
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task FishUse(int index)
+        {
+            var eqItem = await fis.EquipItemAsync(ctx.User.Id, index);
+
+            if (eqItem is null)
+            {
+                await Response().Error(strs.fish_item_not_found).SendAsync();
+                return;
+            }
+
+            var embed = CreateEmbed()
+                .WithDescription(GetText(strs.fish_use_success))
+                .AddField(eqItem.Name, GetMultiplierInfo(eqItem));
+
+            await Response().Embed(embed).SendAsync();
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task FishUnequip(FishItemType itemType)
+        {
+            var res = await fis.UnequipItemAsync(ctx.User.Id, itemType);
+
+            if (res == UnequipResult.Success)
+                await Response().Confirm(strs.fish_unequip_success).SendAsync();
+            else if (res == UnequipResult.NotFound)
+                await Response().Error(strs.fish_item_not_found).SendAsync();
+            else
+                await Response().Error(strs.fish_cant_uneq_potion).SendAsync();
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task FishInv()
+        {
+            var userItems = await fis.GetUserItemsAsync(ctx.User.Id);
+
+            await Response()
+                .Paginated()
+                .Items(userItems)
+                .PageSize(9)
+                .Page((items, page) =>
+                {
+                    page += 1;
+                    var eb = CreateEmbed()
+                        .WithAuthor(ctx.User)
+                        .WithTitle(GetText(strs.fish_inv_title))
+                        .WithFooter($"`.fiuse <num>` to use/equip an item")
+                        .WithOkColor();
+
+                    for (var i = 0; i < items.Count; i++)
+                    {
+                        var (userItem, item) = items[i];
+                        var isEquipped = userItem.IsEquipped;
+
+                        if (item is null)
+                        {
+                            eb.AddField($"{(page * 9) + i + 1} | Item not found", $"ID: {userItem.Id}", true);
+                            continue;
+                        }
+
+                        var description = GetItemDescription(item, userItem);
+
+                        if (isEquipped)
+                            description = "🫴 **IN USE**\n" + description;
+
+                        eb.AddField($"{i + 1} | {item.Name} ",
+                            $"""
+                             {description}
+                             """,
+                            true);
+                    }
+
+                    return eb;
+                })
+                .AddFooter(false)
+                .SendAsync();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/FishItemService.cs b/src/EllieBot/Modules/Games/Fish/FishItemService.cs
new file mode 100644
index 0000000..4f0ef48
--- /dev/null
+++ b/src/EllieBot/Modules/Games/Fish/FishItemService.cs
@@ -0,0 +1,276 @@
+using LinqToDB;
+using LinqToDB.EntityFrameworkCore;
+using EllieBot.Modules.Games.Fish.Db;
+
+namespace EllieBot.Modules.Games;
+
+/// <summary>
+/// Service for managing fish items that users can buy, equip, and use.
+/// </summary>
+public sealed class FishItemService(
+    DbService db,
+    ICurrencyService cs,
+    FishConfigService fcs) : IEService
+{
+    private IReadOnlyList<FishItem> _items
+        => fcs.Data.Items;
+
+    /// <summary>
+    /// Gets all available fish items.
+    /// </summary>
+    public IReadOnlyList<FishItem> GetItems()
+        => _items;
+
+    /// <summary>
+    /// Gets a specific fish item by ID.
+    /// </summary>
+    public FishItem? GetItem(int id)
+        => _items.FirstOrDefault(i => i.Id == id);
+
+    /// <summary>
+    /// Gets all items of a specific type.
+    /// </summary>
+    public List<FishItem> GetItemsByType(FishItemType type)
+        => _items.Where(i => i.ItemType == type).ToList();
+
+    /// <summary>
+    /// Gets all items owned by a user.
+    /// </summary>
+    public async Task<List<(UserFishItem UserItem, FishItem? Item)>> GetUserItemsAsync(ulong userId)
+    {
+        await using var ctx = db.GetDbContext();
+
+        var userItems = await ctx.GetTable<UserFishItem>()
+            .Where(x => x.UserId == userId)
+            .ToListAsyncLinqToDB();
+
+        return userItems
+            .Select(ui => (ui, GetItem(ui.ItemId)))
+            .Where(x => x.Item2 != null)
+            .ToList();
+    }
+
+    /// <summary>
+    /// Gets all equipped items for a user.
+    /// </summary>
+    public async Task<List<(UserFishItem UserItem, FishItem Item)>> GetEquippedItemsAsync(ulong userId)
+    {
+        await CheckExpiredItemsAsync(userId);
+
+        await using var ctx = db.GetDbContext();
+        var items = await ctx.GetTable<UserFishItem>()
+            .Where(x => x.UserId == userId && x.IsEquipped)
+            .ToListAsyncLinqToDB();
+
+        var output = new List<(UserFishItem, FishItem)>();
+
+        foreach (var item in items)
+        {
+            var fishItem = GetItem(item.ItemId);
+            if (fishItem is not null)
+                output.Add((item, fishItem));
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Buys an item for a user.
+    /// </summary>
+    public async Task<OneOf.OneOf<FishItem, BuyResult>> BuyItemAsync(ulong userId, int itemId)
+    {
+        var item = GetItem(itemId);
+        if (item is null)
+            return BuyResult.NotFound;
+
+        await using var ctx = db.GetDbContext();
+
+        var removed = await cs.RemoveAsync(userId, item.Price, new("fish_item_purchase", item.Name));
+        if (!removed)
+            return BuyResult.InsufficientFunds;
+
+        // Add item to user's inventory
+        await ctx.GetTable<UserFishItem>()
+            .InsertAsync(() => new UserFishItem
+            {
+                UserId = userId,
+                ItemId = itemId,
+                ItemType = item.ItemType,
+                UsesLeft = item.Uses,
+                IsEquipped = false,
+            });
+
+        return item;
+    }
+
+    /// <summary>
+    /// Equips an item for a user.
+    /// </summary>
+    public async Task<FishItem?> EquipItemAsync(ulong userId, int index)
+    {
+        await using var ctx = db.GetDbContext();
+        await using var tr = await ctx.Database.BeginTransactionAsync();
+        try
+        {
+            var userItem = await ctx.GetTable<UserFishItem>()
+                .Where(x => x.UserId == userId)
+                .Skip(index - 1)
+                .Take(1)
+                .FirstOrDefaultAsync();
+
+            if (userItem is null)
+                return null;
+
+            var fishItem = GetItem(userItem.ItemId);
+
+            if (fishItem is null)
+                return null;
+
+            if (userItem.ItemType == FishItemType.Potion)
+            {
+                var query = ctx.GetTable<UserFishItem>()
+                    .Where(x => x.Id == userItem.Id && !x.IsEquipped)
+                    .Set(x => x.IsEquipped, true);
+
+                if (fishItem.DurationMinutes is { } dur)
+                    query = query
+                        .Set(x => x.ExpiresAt, DateTime.UtcNow.AddMinutes(dur));
+
+                await query.UpdateAsync();
+                await tr.CommitAsync();
+                return fishItem;
+            }
+
+            // UnEquip any currently equipped item of the same type
+            // and equip current one
+            await ctx.GetTable<UserFishItem>()
+                .Where(x => x.UserId == userId && x.ItemType == userItem.ItemType)
+                .Set(x => x.IsEquipped, x => x.Id == userItem.Id)
+                .UpdateAsync();
+
+            await tr.CommitAsync();
+
+            return fishItem;
+        }
+        catch
+        {
+            await tr.RollbackAsync();
+            return null;
+        }
+    }
+
+    /// <summary>
+    /// Unequips an item for a user.
+    /// </summary>
+    public async Task<UnequipResult> UnequipItemAsync(ulong userId, FishItemType itemType)
+    {
+        // can't unequip potions
+        if (itemType == FishItemType.Potion)
+            return UnequipResult.Potion;
+
+        await using var ctx = db.GetDbContext();
+
+        var affected = await ctx.GetTable<UserFishItem>()
+            .Where(x => x.UserId == userId && x.ItemType == itemType && x.IsEquipped)
+            .Set(x => x.IsEquipped, false)
+            .UpdateAsync();
+
+        if (affected > 0)
+            return UnequipResult.Success;
+        else
+            return UnequipResult.NotFound;
+    }
+
+    /// <summary>
+    /// Gets the multipliers from a user's equipped items.
+    /// </summary>
+    public async Task<FishMultipliers> GetUserMultipliersAsync(ulong userId)
+    {
+        var equippedItems = await GetEquippedItemsAsync(userId);
+
+        var multipliers = new FishMultipliers();
+
+        foreach (var (_, item) in equippedItems)
+        {
+            multipliers.FishMultiplier *= item.FishMultiplier ?? 1;
+            multipliers.TrashMultiplier *= item.TrashMultiplier ?? 1;
+            multipliers.StarMultiplier *= item.MaxStarMultiplier ?? 1;
+            multipliers.RareMultiplier *= item.RareMultiplier ?? 1;
+            multipliers.FishingSpeedMultiplier *= item.FishingSpeedMultiplier ?? 1;
+        }
+
+        return multipliers;
+    }
+
+    /// <summary>
+    /// Uses a bait item (reduces uses left) when fishing.
+    /// </summary>
+    public async Task<bool> UseBaitAsync(ulong userId)
+    {
+        await using var ctx = db.GetDbContext();
+
+        var updated = await ctx.GetTable<UserFishItem>()
+            .Where(x =>
+                x.UserId == userId &&
+                x.ItemType == FishItemType.Bait &&
+                x.IsEquipped)
+            .Set(x => x.UsesLeft, x => x.UsesLeft - 1)
+            .UpdateWithOutputAsync((o, n) => n);
+
+        if (updated.Length == 0)
+            return false;
+
+        if (updated[0].UsesLeft <= 0)
+        {
+            await ctx.GetTable<UserFishItem>()
+                .DeleteAsync(x => x.Id == updated[0].Id);
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Checks and removes expired items.
+    /// </summary>
+    public async Task CheckExpiredItemsAsync(ulong userId)
+    {
+        await using var ctx = db.GetDbContext();
+
+        var now = DateTime.UtcNow;
+
+        await ctx.GetTable<UserFishItem>()
+            .Where(x => x.UserId == userId && x.ExpiresAt.HasValue && x.ExpiresAt < now)
+            .DeleteAsync();
+    }
+}
+
+/// <summary>
+/// Represents the result of a buy operation.
+/// </summary>
+public enum BuyResult
+{
+    NotFound,
+    InsufficientFunds
+}
+
+/// <summary>
+/// Represents the result of an equip operation.
+/// </summary>
+public enum UnequipResult
+{
+    Success,
+    NotFound,
+    Potion
+}
+
+/// <summary>
+/// Contains multipliers applied to fishing based on equipped items.
+/// </summary>
+public class FishMultipliers
+{
+    public double FishMultiplier { get; set; } = 1.0;
+    public double TrashMultiplier { get; set; } = 1.0;
+    public double StarMultiplier { get; set; } = 1.0;
+    public double RareMultiplier { get; set; } = 1.0;
+    public double FishingSpeedMultiplier { get; set; } = 1.0;
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Fish/FishService.cs b/src/EllieBot/Modules/Games/Fish/FishService.cs
index 1213e55..b31c30c 100644
--- a/src/EllieBot/Modules/Games/Fish/FishService.cs
+++ b/src/EllieBot/Modules/Games/Fish/FishService.cs
@@ -13,7 +13,8 @@ public sealed class FishService(
     IBotCache cache,
     DbService db,
     INotifySubscriber notify,
-    QuestService quests
+    QuestService quests,
+    FishItemService itemService
 )
     : IEService
 {
@@ -24,19 +25,24 @@ public sealed class FishService(
     private static TypedKey<bool> FishingKey(ulong userId)
         => new($"fishing:{userId}");
 
-    public async Task<OneOf.OneOf<Task<FishResult?>, AlreadyFishing>> FishAsync(ulong userId, ulong channelId)
+    public async Task<OneOf.OneOf<Task<FishResult?>, AlreadyFishing>> FishAsync(ulong userId, ulong channelId,
+        FishMultipliers multipliers)
     {
-        var duration = _rng.Next(3, 6);
+        var duration = _rng.Next(3, 6) / multipliers.FishingSpeedMultiplier;
 
         if (!await cache.AddAsync(FishingKey(userId), true, TimeSpan.FromSeconds(duration), overwrite: false))
         {
             return new AlreadyFishing();
         }
 
-        return TryFishAsync(userId, channelId, duration);
+        return TryFishAsync(userId, channelId, duration, multipliers);
     }
 
-    private async Task<FishResult?> TryFishAsync(ulong userId, ulong channelId, int duration)
+    private async Task<FishResult?> TryFishAsync(
+        ulong userId,
+        ulong channelId,
+        double duration,
+        FishMultipliers multipliers)
     {
         var conf = fcs.Data;
         await Task.Delay(TimeSpan.FromSeconds(duration));
@@ -46,8 +52,8 @@ public sealed class FishService(
         var trashChanceMultiplier = Math.Clamp(((2 * MAX_SKILL) - playerSkill) / MAX_SKILL, 1, 2);
 
         var nothingChance = conf.Chance.Nothing;
-        var fishChance = conf.Chance.Fish * fishChanceMultiplier;
-        var trashChance = conf.Chance.Trash * trashChanceMultiplier;
+        var fishChance = conf.Chance.Fish * fishChanceMultiplier * multipliers.FishMultiplier;
+        var trashChance = conf.Chance.Trash * trashChanceMultiplier * multipliers.TrashMultiplier;
 
         // first roll whether it's fish, trash or nothing
         var totalChance = fishChance + trashChance + conf.Chance.Nothing;
@@ -59,13 +65,21 @@ public sealed class FishService(
             return null;
         }
 
-        var items = typeRoll < nothingChance + fishChance
+        var isFish = typeRoll < nothingChance + fishChance;
+
+        var items = isFish
             ? conf.Fish
             : conf.Trash;
 
+        var result = await FishAsyncInternal(userId, channelId, items, multipliers);
+        
+        // use bait
+        if (result is not null)
+        {
+            await itemService.UseBaitAsync(userId);
+        }
 
-        var result = await FishAsyncInternal(userId, channelId, items);
-
+        // skill
         if (result is not null)
         {
             var isSkillUp = await TrySkillUpAsync(userId, playerSkill);
@@ -91,16 +105,16 @@ public sealed class FishService(
                     GetStarText(result.Stars, result.Fish.Stars)
                 ));
             }
-        }
 
-        await quests.ReportActionAsync(userId,
-            QuestEventType.FishCaught,
-            new()
-            {
-                { "fish", result.Fish.Name },
-                { "type", typeRoll < nothingChance + fishChance ? "fish" : "trash" },
-                { "stars", result.Stars.ToString() }
-            });
+            await quests.ReportActionAsync(userId,
+                QuestEventType.FishCaught,
+                new()
+                {
+                    { "fish", result.Fish.Name },
+                    { "type", typeRoll < nothingChance + fishChance ? "fish" : "trash" },
+                    { "stars", result.Stars.ToString() }
+                });
+        }
 
         return result;
     }
@@ -118,20 +132,20 @@ public sealed class FishService(
             var maxSkill = (int)MAX_SKILL;
             await ctx.GetTable<UserFishStats>()
                 .InsertOrUpdateAsync(() => new()
-                {
-                    UserId = userId,
-                    Skill = 1,
-                },
-                (old) => new()
-                {
-                    UserId = userId,
-                    Skill = old.Skill > maxSkill ? maxSkill : old.Skill + 1
-                },
-                () => new()
-                {
-                    UserId = userId,
-                    Skill = playerSkill
-                });
+                    {
+                        UserId = userId,
+                        Skill = 1,
+                    },
+                    (old) => new()
+                    {
+                        UserId = userId,
+                        Skill = old.Skill > maxSkill ? maxSkill : old.Skill + 1
+                    },
+                    () => new()
+                    {
+                        UserId = userId,
+                        Skill = playerSkill
+                    });
 
             return true;
         }
@@ -162,7 +176,11 @@ public sealed class FishService(
         return (skill, (int)MAX_SKILL);
     }
 
-    private async Task<FishResult?> FishAsyncInternal(ulong userId, ulong channelId, List<FishData> items)
+    private async Task<FishResult?> FishAsyncInternal(
+        ulong userId,
+        ulong channelId,
+        List<FishData> items,
+        FishMultipliers multipliers)
     {
         var filteredItems = new List<FishData>();
 
@@ -192,7 +210,20 @@ public sealed class FishService(
             filteredItems.Add(item);
         }
 
-        var maxSum = filteredItems.Sum(x => x.Chance * 100);
+
+        var maxSum = filteredItems
+            .Select(x => (x.Id, x.Chance, x.Stars))
+            .Select(x =>
+            {
+                if (x.Chance <= 15)
+                    return x with
+                    {
+                        Chance = x.Chance *= multipliers.RareMultiplier
+                    };
+
+                return x;
+            })
+            .Sum(x => { return x.Chance * 100; });
 
 
         var roll = _rng.NextDouble() * maxSum;
@@ -209,7 +240,7 @@ public sealed class FishService(
                 caught = new FishResult()
                 {
                     Fish = i,
-                    Stars = GetRandomStars(i.Stars),
+                    Stars = GetRandomStars(i.Stars, multipliers),
                 };
                 break;
             }
@@ -221,22 +252,22 @@ public sealed class FishService(
 
             await uow.GetTable<FishCatch>()
                 .InsertOrUpdateAsync(() => new FishCatch()
-                {
-                    UserId = userId,
-                    FishId = caught.Fish.Id,
-                    MaxStars = caught.Stars,
-                    Count = 1
-                },
-                (old) => new FishCatch()
-                {
-                    Count = old.Count + 1,
-                    MaxStars = Math.Max(old.MaxStars, caught.Stars),
-                },
-                () => new()
-                {
-                    FishId = caught.Fish.Id,
-                    UserId = userId
-                });
+                    {
+                        UserId = userId,
+                        FishId = caught.Fish.Id,
+                        MaxStars = caught.Stars,
+                        Count = 1
+                    },
+                    (old) => new FishCatch()
+                    {
+                        Count = old.Count + 1,
+                        MaxStars = Math.Max(old.MaxStars, caught.Stars),
+                    },
+                    () => new()
+                    {
+                        FishId = caught.Fish.Id,
+                        UserId = userId
+                    });
 
             return caught;
         }
@@ -353,25 +384,30 @@ public sealed class FishService(
     /// if maxStars == 5, returns 1 (40%) or 2 (30%) or 3 (15%) or 4 (10%) or 5 (5%)
     /// </summary>
     /// <param name="maxStars">Max Number of stars to generate</param>
+    /// <param name="multipliers"></param>
     /// <returns>Random number of stars</returns>
-    private int GetRandomStars(int maxStars)
+    private int GetRandomStars(int maxStars, FishMultipliers multipliers)
     {
         if (maxStars == 1)
             return 1;
 
+        var maxStarMulti = multipliers.StarMultiplier;
+        double baseChance;
         if (maxStars == 2)
         {
             // 15% chance of 1 star, 85% chance of 2 stars
-            return _rng.NextDouble() < 0.85 ? 1 : 2;
+            baseChance = Math.Clamp(0.15 * multipliers.StarMultiplier, 0, 1);
+            return _rng.NextDouble() < (1 - baseChance) ? 1 : 2;
         }
 
         if (maxStars == 3)
         {
             // 65% chance of 1 star, 30% chance of 2 stars, 5% chance of 3 stars
+            baseChance = 0.05 * multipliers.StarMultiplier;
             var r = _rng.NextDouble();
-            if (r < 0.65)
+            if (r < (1 - baseChance - 0.3))
                 return 1;
-            if (r < 0.95)
+            if (r < (1 - baseChance))
                 return 2;
             return 3;
         }
@@ -381,26 +417,28 @@ public sealed class FishService(
             // this should never happen
             // 50% chance of 1 star, 25% chance of 2 stars, 18% chance of 3 stars, 7% chance of 4 stars
             var r = _rng.NextDouble();
-            if (r < 0.55)
+            baseChance = 0.02 * multipliers.StarMultiplier;
+            if (r < (1 - baseChance - 0.45))
                 return 1;
-            if (r < 0.80)
+            if (r < (1 - baseChance - 0.15))
                 return 2;
-            if (r < 0.98)
+            if (r < (1 - baseChance))
                 return 3;
             return 4;
         }
 
         if (maxStars == 5)
         {
-            // 40% chance of 1 star, 30% chance of 2 stars, 15% chance of 3 stars, 10% chance of 4 stars, 5% chance of 5 stars
+            // 40% chance of 1 star, 30% chance of 2 stars, 15% chance of 3 stars, 10% chance of 4 stars, 2% chance of 5 stars
             var r = _rng.NextDouble();
-            if (r < 0.4)
+            baseChance = 0.02 * multipliers.StarMultiplier;
+            if (r < (1 - baseChance - 0.6))
                 return 1;
-            if (r < 0.7)
+            if (r < (1 - baseChance - 0.3))
                 return 2;
-            if (r < 0.9)
+            if (r < (1 - baseChance - 0.1))
                 return 3;
-            if (r < 0.98)
+            if (r < (1 - baseChance))
                 return 4;
             return 5;
         }
diff --git a/src/EllieBot/Modules/Games/Fish/FishCommands.cs b/src/EllieBot/Modules/Games/Fish/FishingCommands.cs
similarity index 84%
rename from src/EllieBot/Modules/Games/Fish/FishCommands.cs
rename to src/EllieBot/Modules/Games/Fish/FishingCommands.cs
index d8d0ece..ed971d5 100644
--- a/src/EllieBot/Modules/Games/Fish/FishCommands.cs
+++ b/src/EllieBot/Modules/Games/Fish/FishingCommands.cs
@@ -1,14 +1,13 @@
-using System.ComponentModel.DataAnnotations;
-using System.Text;
-using EllieBot.Modules.Games.Fish;
+using EllieBot.Modules.Games.Fish;
 using Format = Discord.Format;
 
 namespace EllieBot.Modules.Games;
 
 public partial class Games
 {
-    public class FishCommands(
+    public class FishingCommands(
         FishService fs,
+        FishItemService fis,
         FishConfigService fcs,
         IBotCache cache,
         CaptchaService captchaService) : EllieModule
@@ -34,7 +33,10 @@ public partial class Games
                     using var stream = await img.ToStreamAsync();
 
                     var toSend = Response()
-                        .File(stream, "timely.png");
+                        .File(stream, "timely.png")
+                        .Embed(CreateEmbed()
+                            .WithFooter("captcha: type the text from the image")
+                            .WithImageUrl("attachment://timely.png"));
 
 #if GLOBAL_ELLIE
                     if (_rng.Next(0, 8) == 0)
@@ -64,7 +66,8 @@ public partial class Games
             }
 
 
-            var fishResult = await fs.FishAsync(ctx.User.Id, ctx.Channel.Id);
+            var multis = await fis.GetUserMultipliersAsync(ctx.User.Id);
+            var fishResult = await fs.FishAsync(ctx.User.Id, ctx.Channel.Id, multis);
             if (fishResult.TryPickT1(out _, out var fishTask))
             {
                 return;
@@ -78,7 +81,10 @@ public partial class Games
                 .Embed(CreateEmbed()
                     .WithPendingColor()
                     .WithAuthor(ctx.User)
-                    .WithDescription(GetText(strs.fish_waiting))
+                    .WithDescription($"""
+                                      {GetText(strs.fish_waiting)}
+                                      {FishItemCommands.GetMultiplierInfo(multis)}
+                                      """)
                     .AddField(GetText(strs.fish_spot), GetSpotEmoji(spot) + " " + spot.ToString(), true)
                     .AddField(GetText(strs.fish_weather),
                         GetWeatherEmoji(currentWeather) + " " + currentWeather,
@@ -133,7 +139,7 @@ public partial class Games
         }
 
         [Cmd]
-        public async Task Fishlist(int page = 1)
+        public async Task FishList(int page = 1)
         {
             if (--page < 0)
                 return;
@@ -145,20 +151,33 @@ public partial class Games
 
             var catchDict = catches.ToDictionary(x => x.FishId, x => x);
 
+            var items = await fis.GetEquippedItemsAsync(ctx.User.Id);
+            var desc = $"""
+                       🧠 {skill} / {maxSkill}
+                       """;
+
+            foreach (var itemType in Enum.GetValues<FishItemType>())
+            {
+                var i = items.Where(x => x.Item.ItemType == itemType).ToArray();
+
+                desc += " · " + FishItemCommands.GetEmoji(itemType) + " " +
+                        (i.Any() ? string.Join(", ", i.Select(x => x.Item.Name)) : "None");
+            }
+
             await Response()
                 .Paginated()
                 .Items(fishes)
                 .PageSize(9)
                 .CurrentPage(page)
-                .Page((fishes, i) =>
+                .Page((pageFish, i) =>
                 {
                     var eb = CreateEmbed()
-                        .WithDescription($"🧠 **Skill:** {skill} / {maxSkill}")
+                        .WithDescription(desc)
                         .WithAuthor(ctx.User)
                         .WithTitle(GetText(strs.fish_list_title))
                         .WithOkColor();
 
-                    foreach (var f in fishes)
+                    foreach (var f in pageFish)
                     {
                         if (catchDict.TryGetValue(f.Id, out var c))
                         {
@@ -224,9 +243,6 @@ public partial class Games
                 FishingWeather.Clear => "☀️",
                 _ => ""
             };
-
-        
-
     }
 }
 
diff --git a/src/EllieBot/Modules/Games/Fish/strings.json b/src/EllieBot/Modules/Games/Fish/strings.json
new file mode 100644
index 0000000..740a188
--- /dev/null
+++ b/src/EllieBot/Modules/Games/Fish/strings.json
@@ -0,0 +1,21 @@
+{
+  "fish_items_title": "Available Fishing Items",
+  "fish_buy_success": "Item purchased successfully!",
+  "fish_buy_not_found": "Item not found.",
+  "fish_buy_already_owned": "You already own this item.",
+  "fish_buy_insufficient_funds": "You don't have enough currency to buy this item.",
+  "fish_buy_error": "An error occurred while trying to buy the item.",
+  "fish_use_success": "Item equipped successfully!",
+  "fish_use_not_found": "Item not found.",
+  "fish_use_not_owned": "You don't own this item.",
+  "fish_use_expired": "This item has expired.",
+  "fish_use_no_uses": "This item has no uses left.",
+  "fish_use_error": "An error occurred while trying to use the item.",
+  "fish_unequip_success": "Item unequipped successfully!",
+  "fish_unequip_error": "Could not unequip item.",
+  "fish_inv_title": "{0}'s Fishing Inventory",
+  "fish_gift_self": "You can't gift items to yourself.",
+  "fish_gift_not_owned": "You don't own this item.",
+  "fish_gift_equipped": "You can't gift equipped items. Unequip it first.",
+  "fish_gift_success": "Item successfully gifted to {0}!"
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Games/Quests/QuestService.cs b/src/EllieBot/Modules/Games/Quests/QuestService.cs
index c251384..cdc30ee 100644
--- a/src/EllieBot/Modules/Games/Quests/QuestService.cs
+++ b/src/EllieBot/Modules/Games/Quests/QuestService.cs
@@ -13,8 +13,6 @@ public sealed class QuestService(
     DiscordSocketClient client
 ) : IEService, IExecPreCommand
 {
-    private readonly EllieRandom rng = new();
-
     private readonly IQuest[] _availableQuests =
     [
         new HangmanWinQuest(),
@@ -48,10 +46,10 @@ public sealed class QuestService(
 
         _ = Task.Run(async () =>
         {
-            Log.Information("Action reported by {UserId}: {EventType} {Metadata}",
-                userId,
-                eventType,
-                metadata.ToJson());
+            // Log.Information("Action reported by {UserId}: {EventType} {Metadata}",
+               // userId,
+               // eventType,
+               // metadata.ToJson());
             metadata ??= new();
             var now = DateTime.UtcNow;
 
@@ -140,11 +138,11 @@ public sealed class QuestService(
     {
         var today = date.Date;
         var timeUntilTomorrow = today.AddDays(1) - DateTime.UtcNow;
-        if (!await botCache.AddAsync(UserHasQuestsKey(userId), true, expiry: timeUntilTomorrow))
+        if (!await botCache.AddAsync(UserHasQuestsKey(userId), true, expiry: timeUntilTomorrow, overwrite: false))
             return;
 
         await using var uow = db.GetDbContext();
-        var newQuests = GenerateDailyQuestsAsync(userId);
+        var newQuests = GenerateDailyQuestsAsync();
         for (var i = 0; i < MAX_QUESTS_PER_DAY; i++)
         {
             await uow.GetTable<UserQuest>()
@@ -170,7 +168,7 @@ public sealed class QuestService(
         }
     }
 
-    private IReadOnlyList<IQuest> GenerateDailyQuestsAsync(ulong userId)
+    private IReadOnlyList<IQuest> GenerateDailyQuestsAsync()
     {
         return _availableQuests
             .ToList()
diff --git a/src/EllieBot/Modules/Games/Quests/db/UserQuest.cs b/src/EllieBot/Modules/Games/Quests/db/UserQuest.cs
index bc95ad9..9ddfaec 100644
--- a/src/EllieBot/Modules/Games/Quests/db/UserQuest.cs
+++ b/src/EllieBot/Modules/Games/Quests/db/UserQuest.cs
@@ -1,4 +1,6 @@
 using System.ComponentModel.DataAnnotations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
 using EllieBot.Modules.Games.Quests;
 
 namespace EllieBot.Db.Models;
@@ -18,4 +20,19 @@ public class UserQuest
     public bool IsCompleted { get; set; }
 
     public DateTime DateAssigned { get; set; }
+}
+
+public sealed class UserQuestEntityConfiguration : IEntityTypeConfiguration<UserQuest>
+{
+    public void Configure(EntityTypeBuilder<UserQuest> builder)
+    {
+        builder.HasIndex(x => x.UserId);
+        
+        builder.HasIndex(x => new
+        {
+            x.UserId,
+            x.QuestNumber,
+            x.DateAssigned
+        }).IsUnique();
+    }
 }
\ No newline at end of file
diff --git a/src/EllieBot/_common/Sender/ResponseBuilder.PaginationSender.cs b/src/EllieBot/_common/Sender/ResponseBuilder.PaginationSender.cs
index 915a3ed..a28dda4 100644
--- a/src/EllieBot/_common/Sender/ResponseBuilder.PaginationSender.cs
+++ b/src/EllieBot/_common/Sender/ResponseBuilder.PaginationSender.cs
@@ -46,7 +46,7 @@ public partial class ResponseBuilder
                 GetInteractions()
             {
                 var leftButton = new ButtonBuilder()
-                                 .WithStyle(ButtonStyle.Primary)
+                                 .WithStyle(ButtonStyle.Secondary)
                                  .WithCustomId(BUTTON_LEFT)
                                  .WithEmote(InteractionHelpers.ArrowLeft)
                                  .WithDisabled(lastPage == 0 || currentPage <= 0);
@@ -80,7 +80,7 @@ public partial class ResponseBuilder
                 }
 
                 var rightButton = new ButtonBuilder()
-                                  .WithStyle(ButtonStyle.Primary)
+                                  .WithStyle(ButtonStyle.Secondary)
                                   .WithCustomId(BUTTON_RIGHT)
                                   .WithEmote(InteractionHelpers.ArrowRight)
                                   .WithDisabled(lastPage == 0 || currentPage >= lastPage);
diff --git a/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs b/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs
index 3441e14..672e41f 100644
--- a/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs
+++ b/src/EllieBot/_common/Services/Currency/GamblingTxTracker.cs
@@ -172,6 +172,8 @@ public sealed class GamblingTxTracker(
         if (txData is null)
             return;
 
+        await Task.Yield();
+
         if (_gamblingTypes.Contains(txData.Type))
         {
             globalStats.AddOrUpdate(txData.Type,
diff --git a/src/EllieBot/migrate.sh b/src/EllieBot/migrate.sh
index 16699d2..852cb70 100644
--- a/src/EllieBot/migrate.sh
+++ b/src/EllieBot/migrate.sh
@@ -1,26 +1,25 @@
 #!/bin/bash
+set -euo pipefail
 
 # Check if migration name is provided
-if [ -z "$1" ]; then
+if [ $# -eq 0 ]; then
     echo "Error: Migration name must be specified."
     echo "Usage: $0 <MigrationName>"
     exit 1
 fi
 
-MIGRATION_NAME=$1
+MIGRATION_NAME="$1"
 
-# Step 1: Create initial migration
 echo "Creating new migration..."
 
+# Step 1: Create initial migrations
 dotnet build
 
-# Getting previous migration names in order to generate SQL scripts
-dotnet ef migrations add "${MIGRATION_NAME}" --context SqliteContext --output-dir "Migrations/Sqlite" --no-build
-dotnet ef migrations add "${MIGRATION_NAME}" --context PostgresqlContext --output-dir "Migrations/PostgreSql" --no-build
+dotnet ef migrations add "$MIGRATION_NAME" --context SqliteContext --output-dir "Migrations/Sqlite" --no-build
+dotnet ef migrations add "$MIGRATION_NAME" --context PostgresqlContext --output-dir "Migrations/PostgreSql" --no-build
 
 dotnet build
 
-# Check for migration creation success
 if [ $? -ne 0 ]; then
     echo "Error: Failed to create migrations"
     exit 1
@@ -29,34 +28,27 @@ fi
 # Step 2: Generate SQL scripts
 echo "Generating diff SQL scripts..."
 
-NEW_MIGRATION_ID_SQLITE=$(dotnet ef migrations list --context SqliteContext --no-build --no-connect | tail -2 | head -1 | cut -d' ' -f1)
-NEW_MIGRATION_ID_POSTGRESQL=$(dotnet ef migrations list --context PostgresqlContext --no-build --no-connect | tail -2 | head -1 | cut -d' ' -f1)
-
-dotnet ef migrations script init $MIGRATION_NAME --context SqliteContext -o "Migrations/Sqlite/${NEW_MIGRATION_ID_SQLITE}.sql" --no-build
-dotnet ef migrations script init $MIGRATION_NAME --context PostgresqlContext -o "Migrations/Postgresql/${NEW_MIGRATION_ID_POSTGRESQL}.sql" --no-build
+NEW_MIGRATION_ID_SQLITE=$(dotnet ef migrations list --context SqliteContext --no-build --no-connect | tail -n 2 | head -n 1 | awk '{print $1}')
+NEW_MIGRATION_ID_POSTGRESQL=$(dotnet ef migrations list --context PostgresqlContext --no-build --no-connect | tail -n 2 | head -n 1 | awk '{print $1}')
 
+dotnet ef migrations script init "$MIGRATION_NAME" --context SqliteContext -o "Migrations/Sqlite/${NEW_MIGRATION_ID_SQLITE}.sql" --no-build
+dotnet ef migrations script init "$MIGRATION_NAME" --context PostgresqlContext -o "Migrations/PostgreSql/${NEW_MIGRATION_ID_POSTGRESQL}.sql" --no-build
 
 if [ $? -ne 0 ]; then
     echo "Error: Failed to generate SQL script"
     exit 1
 fi
 
+# Step 3: Cleanup migration files
 echo "Cleaning up all migration files..."
 
-# Step 3: Clean up migration files by removing everything
-for file in "Migrations/Sqlite/"*.cs; do
-    echo "Deleting: $(basename "$file")"
-    rm -- "$file"
-done
-
-for file in "Migrations/Postgresql/"*.cs; do
-    echo "Deleting: $(basename "$file")"
-    rm -- "$file"
-done
-
-# Step 4: Adding new initial migration
-echo "Creating new initial migration..."
+find "Migrations/Sqlite" -name "*.cs" -type f -print -delete
+find "Migrations/PostgreSql" -name "*.cs" -type f -print -delete
 
 dotnet build
+
+# Step 4: Create new initial migrations
+echo "Creating new initial migration..."
+
 dotnet ef migrations add init --context SqliteContext --output-dir "Migrations/Sqlite" --no-build
 dotnet ef migrations add init --context PostgresqlContext --output-dir "Migrations/PostgreSql" --no-build
\ No newline at end of file
diff --git a/src/EllieBot/strings/aliases.yml b/src/EllieBot/strings/aliases.yml
index 6d96439..aee816e 100644
--- a/src/EllieBot/strings/aliases.yml
+++ b/src/EllieBot/strings/aliases.yml
@@ -1660,4 +1660,26 @@ massping:
 questlog:
   - questlog
   - qlog
-  - myquests
\ No newline at end of file
+  - myquests
+fishshop:
+  - fishshop
+  - fishop
+fishbuy:
+  - fishbuy
+  - fibuy
+fishuse:
+  - fishuse
+  - fiuse
+  - fiequip
+  - fieq
+  - fiquip
+fishunequip:
+  - fishunequip
+  - fiuneq
+  - fiunequip
+  - fiunuse
+  - fishunuse
+fishinv:
+  - fishinv
+  - finv
+  - fiinv
\ No newline at end of file
diff --git a/src/EllieBot/strings/commands/commands.en-US.yml b/src/EllieBot/strings/commands/commands.en-US.yml
index 50c5a0e..0425cdd 100644
--- a/src/EllieBot/strings/commands/commands.en-US.yml
+++ b/src/EllieBot/strings/commands/commands.en-US.yml
@@ -5214,4 +5214,48 @@ questlog:
   ex:
     - ''
   params:
-    - { }
\ No newline at end of file
+    - { }
+fishshop:
+  desc: |-
+    Opens the fish shop.
+    Lists all fish items available for sale
+  ex:
+    - ''
+  params:
+    - { }
+fishinv:
+  desc: |-
+    Opens your fish inventory.
+    Your inventory contains all items you've purchased but not spent.
+  ex:
+    - ''
+  params:
+    - { }
+fishbuy:
+  desc: |-
+    Purchase a fishing item with the specified id.
+    After purchase the item will appear in your inventory where you can use/equip it.
+  ex:
+    - '1'
+  params:
+    - id:
+        desc: "The ID of the item to buy."
+fishuse:
+  desc: |-
+    Use a fishing item in your inventory.
+    You can unequip it later, unless its a potion.
+  ex:
+    - '1'
+  params:
+    - index:
+        desc: "The index of the item to use."
+fishunequip:
+  desc: |-
+    Unequips an item by specifying its index in your inventory.
+    You can use it again later.
+    You can't unequip potions.
+  ex:
+    - '1'
+  params:
+    - index:
+        desc: "The index of the item to unequip."
\ No newline at end of file
diff --git a/src/EllieBot/strings/responses/responses.en-US.json b/src/EllieBot/strings/responses/responses.en-US.json
index e2a233c..0518c14 100644
--- a/src/EllieBot/strings/responses/responses.en-US.json
+++ b/src/EllieBot/strings/responses/responses.en-US.json
@@ -1248,5 +1248,16 @@
   "quest_log": "Quest Log",
   "dailies_done": "You've completed your dailies!",
   "dailies_reset": "Reset {0}",
-  "daily_completed": "You've completed a daily quest: {0}"
+  "daily_completed": "You've completed a daily quest: {0}",
+  "fish_items_title": "Fishing Shop",
+  "fish_buy_success": "Item purchased successfully!",
+  "fish_item_not_found": "Item not found.",
+  "fish_buy_insufficient_funds": "You don't have enough currency to buy this item.",
+  "fish_buy_error": "An error occurred while trying to buy the item.",
+  "fish_use_success": "Item equipped successfully!",
+  "fish_use_error": "Unknown error occurred while trying to equip the item.",
+  "fish_unequip_success": "Item unequipped successfully!",
+  "fish_unequip_error": "Could not unequip item.",
+  "fish_inv_title": "Fishing Inventory",
+  "fish_cant_uneq_potion": "You can't unequip a potion."
 }
\ No newline at end of file