summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/bazaar_controller.rb91
-rw-r--r--app/models/bazaar_order.rb12
-rw-r--r--app/models/character.rb14
-rw-r--r--app/models/character_item.rb14
-rw-r--r--app/models/concerns/destroy_if_zero_quantity.rb18
-rw-r--r--app/views/application/_navbar.html.erb3
-rw-r--r--app/views/bazaar/index.html.erb128
-rw-r--r--config/routes.rb7
-rw-r--r--data/items.yml3
-rw-r--r--db/migrate/20210523151747_create_bazaar_orders.rb13
-rw-r--r--db/schema.rb16
-rw-r--r--test/fixtures/bazaar_orders.yml15
-rw-r--r--test/models/bazaar_order_test.rb7
13 files changed, 322 insertions, 19 deletions
diff --git a/app/controllers/bazaar_controller.rb b/app/controllers/bazaar_controller.rb
new file mode 100644
index 0000000..b31e8fb
--- /dev/null
+++ b/app/controllers/bazaar_controller.rb
@@ -0,0 +1,91 @@
+class BazaarController < ApplicationController
+ def index
+ @bazaar_orders = BazaarOrder.all
+ @items = Item.all
+ end
+
+ def create_order
+ item = Item.find(params[:item_id])
+ quantity = params[:quantity].to_i
+ price_each = params[:price_each].to_i
+ if params[:buy_order] == "1"
+ BazaarOrder.transaction do
+ current_char.shift_item("vestige", (-price_each * quantity))
+ current_char.bazaar_orders.create!(item: item, quantity: quantity,
+ price_each: price_each, buy_order: true)
+ flash[:notice] = "Posted buy order for #{item.name} at #{price_each} vg each."
+ end
+ else
+ BazaarOrder.transaction do
+ current_char.shift_item(item, -quantity)
+ current_char.bazaar_orders.create!(item: item, quantity: quantity,
+ price_each: price_each, buy_order: false)
+ flash[:notice] = "Posted sell order for #{item.name} at #{price_each} vg each."
+ end
+ end
+ rescue ItemQuantityError
+ flash[:alert] = "Failed to post order. Make sure you have vestige or items."
+ ensure
+ redirect_to bazaar_path
+ end
+
+ def accept_offer
+ @bazaar_order = BazaarOrder.find(params[:id])
+ if @bazaar_order.buy_order?
+ BazaarOrder.transaction do
+ quantity = [params[:quantity].to_i, @bazaar_order.quantity].min
+ profit = quantity * @bazaar_order.price_each
+ current_char.shift_item(@bazaar_order.item, -quantity)
+ current_char.shift_item("vestige", profit)
+ @bazaar_order.character.shift_item(@bazaar_order.item, quantity)
+ @bazaar_order.decrement!(:quantity, quantity)
+ @bazaar_order.save!
+ flash[:notice] = "You sold #{quantity} #{@bazaar_order.item.name} and got #{profit} vg."
+ end
+ else
+ BazaarOrder.transaction do
+ quantity = [params[:quantity].to_i, @bazaar_order.quantity].min
+ cost = quantity * @bazaar_order.price_each
+ current_char.shift_item("vestige", -cost)
+ current_char.shift_item(@bazaar_order.item, quantity)
+ @bazaar_order.character.shift_item("vestige", cost)
+ @bazaar_order.decrement!(:quantity, quantity)
+ @bazaar_order.save!
+ flash[:notice] = "You bought #{quantity} #{@bazaar_order.item.name} for #{cost} vg."
+ end
+ end
+ rescue ItemQuantityError
+ flash[:alert] = "Failed to accept order. Make sure you have vestige or items."
+ ensure
+ redirect_to bazaar_path
+ end
+
+ def cancel_offer
+ @bazaar_order = BazaarOrder.find(params[:id])
+ unless @bazaar_order.character == current_char
+ flash[:alert] = "You can't cancel someone else's order."
+ redirect_to bazaar_path and return
+ end
+ if @bazaar_order.buy_order?
+ BazaarOrder.transaction do
+ vestige_to_return = @bazaar_order.quantity * @bazaar_order.price_each
+ @bazaar_order.character.shift_item("vestige", vestige_to_return)
+ @bazaar_order.destroy!
+ flash[:notice] = "You canceled your buy order for #{@bazaar_order.item.name} and got" \
+ " #{vestige_to_return} vg back."
+ end
+ else
+ BazaarOrder.transaction do
+ items_to_return = @bazaar_order.quantity
+ @bazaar_order.character.shift_item(@bazaar_order.item, items_to_return)
+ @bazaar_order.destroy!
+ flash[:notice] = "You canceled your sell order for #{@bazaar_order.item.name} and got" \
+ " #{items_to_return} of them back."
+ end
+ end
+ rescue ItemQuantityError
+ flash[:alert] = "Failed to cancel order."
+ ensure
+ redirect_to bazaar_path
+ end
+end
diff --git a/app/models/bazaar_order.rb b/app/models/bazaar_order.rb
new file mode 100644
index 0000000..d36d520
--- /dev/null
+++ b/app/models/bazaar_order.rb
@@ -0,0 +1,12 @@
+class BazaarOrder < ApplicationRecord
+ include DestroyIfZeroQuantity
+
+ belongs_to :character
+ belongs_to :item
+
+ validates :price_each, :quantity,
+ numericality: { greater_than_or_equal_to: 0,
+ less_than_or_equal_to: 2_000_000_000, only_integer: true }
+ validates :buy_order, inclusion: { in: [true, false] }
+
+end
diff --git a/app/models/character.rb b/app/models/character.rb
index bad2bb2..003f8af 100644
--- a/app/models/character.rb
+++ b/app/models/character.rb
@@ -11,6 +11,7 @@ class Character < ApplicationRecord
has_many :items, through: :character_items
has_many :character_skills
has_many :chat_messages
+ has_many :bazaar_orders
validates :name, presence: true
validates_length_of :name, maximum: 15, message: "can't be longer than 15 characters"
validates_uniqueness_of :name, message: "is already being used"
@@ -35,12 +36,17 @@ class Character < ApplicationRecord
def wildscour_level; skill_level("wildscour"); end
def worldcall_level; skill_level("worldcall"); end
+ def vestige
+ vestige = self.character_items.find_by(item: Item.find_by_gid("vestige"))
+ vestige ? vestige.quantity : 0
+ end
+
def shift_item(item, amount)
item = Item.find_by_gid(item) if item.is_a? String
CharacterItem.transaction do
ci = self.character_items.find_or_initialize_by(item: item)
- ci.increment(:quantity, amount)
- ci.save
+ ci.increment!(:quantity, amount)
+ ci.save!
end
end
@@ -71,7 +77,7 @@ class Character < ApplicationRecord
open_slots = self.open_slots_for(item)
raise EquipmentError unless open_slots.any?
self.shift_item(item, -1)
- self.equipment.create(item: item, slot: open_slots.first)
+ self.equipment.create!(item: item, slot: open_slots.first)
end
end
@@ -80,7 +86,7 @@ class Character < ApplicationRecord
equipment = self.equipment.find_by(slot: slot)
raise EquipmentError unless equipment
item = equipment.item
- equipment.destroy
+ equipment.destroy!
self.shift_item(item, 1)
end
end
diff --git a/app/models/character_item.rb b/app/models/character_item.rb
index b54f799..6ee13ab 100644
--- a/app/models/character_item.rb
+++ b/app/models/character_item.rb
@@ -1,4 +1,6 @@
class CharacterItem < ApplicationRecord
+ include DestroyIfZeroQuantity
+
belongs_to :character
belongs_to :item
validates :quantity, presence: true
@@ -9,17 +11,5 @@ class CharacterItem < ApplicationRecord
end
end
- after_save :destroy_if_zero_quantity
-
scope :ordered_by_item_name, -> { includes(:item).order("items.name") }
-
- private
- def destroy_if_zero_quantity
- if self.quantity == 0
- destroy
- elsif self.quantity < 0
- # TODO: Can improve this (at the least, with reporting, later).
- raise ItemQuantityError
- end
- end
end
diff --git a/app/models/concerns/destroy_if_zero_quantity.rb b/app/models/concerns/destroy_if_zero_quantity.rb
new file mode 100644
index 0000000..6fdfb3e
--- /dev/null
+++ b/app/models/concerns/destroy_if_zero_quantity.rb
@@ -0,0 +1,18 @@
+module DestroyIfZeroQuantity
+ extend ActiveSupport::Concern
+
+ included do
+ after_save :destroy_if_zero_quantity
+ end
+
+ private
+ def destroy_if_zero_quantity
+ byebug
+ if self.quantity == 0
+ destroy
+ elsif self.quantity < 0
+ # TODO: Can improve this (at the least, with reporting, later).
+ raise ItemQuantityError
+ end
+ end
+end
diff --git a/app/views/application/_navbar.html.erb b/app/views/application/_navbar.html.erb
index ef81d6c..c0e2761 100644
--- a/app/views/application/_navbar.html.erb
+++ b/app/views/application/_navbar.html.erb
@@ -12,5 +12,8 @@
<li class="mr-6 inline">
<%= link_to "Hearth", character_hearth_path(current_char) %>
</li>
+ <li class="mr-6 inline">
+ <%= link_to "Bazaar", bazaar_path %>
+ </li>
<% end %>
</ul>
diff --git a/app/views/bazaar/index.html.erb b/app/views/bazaar/index.html.erb
new file mode 100644
index 0000000..63d708f
--- /dev/null
+++ b/app/views/bazaar/index.html.erb
@@ -0,0 +1,128 @@
+<h1 class="text-3xl mb-4">Numedite Bazaar</h1>
+
+<p class="mb-2">The small, cloaked people known as the Numedites travel this way and that across the planes.
+ Because their ways are spiritual in nature, they use their innate magic to facilitate
+ distant trade at no cost to their clientele.</p>
+
+<p class="mb-2">You have <span class="font-bold"><%= current_char.vestige %></span> vestige.</p>
+
+<div class="my-8">
+ <h2 class="text-xl mb-4">Post Order</h2>
+
+ <p class="mb-4">Sell orders can be posted with a caravan of numedites, offering items for vestige.
+ Buy orders can also be posted, offering vestige for items.</p>
+
+ <%= form_with url: bazaar_order_path, class: "my-4" do |f| %>
+ <div>
+ <%= f.label :item_id, "Item" %>
+ <%= f.select :item_id, @items.map { |i| [i.name, i.id] } %>
+ </div>
+ <div>
+ <%= f.label :quantity, "Quantity" %>
+ <%= f.number_field :quantity, required: true, min: 1, max: 2_000_000_000 %>
+ </div>
+ <div>
+ <%= f.label :price_each, "Price Each" %>
+ <%= f.number_field :price_each, required: true, min: 1, max: 2_000_000_000 %>
+ </div>
+ <div>
+ <%= f.label :buy_order, "Post as buy order" %>
+ <%= f.check_box :buy_order %>
+ </div>
+ <div>
+ <%= f.submit "Post Order" %>
+ </div>
+ <% end %>
+</div>
+
+<div class="my-8">
+ <h2 class="text-xl mb-4">Your Orders</h2>
+ <table class="table-auto mb-8">
+ <thead>
+ <tr>
+ <th class="table-header-padded">Item</th>
+ <th class="table-header-padded">Quantity</th>
+ <th class="table-header-padded">Type</th>
+ <th class="table-header-padded">Price Each</th>
+ <th class="table-header-padded">Cancel</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @bazaar_orders.where(character: current_char).each do |bo| %>
+ <tr>
+ <td class="table-cell-padded"><%= bo.item.name %></td>
+ <td class="table-cell-padded"><%= bo.quantity %></td>
+ <td class="table-cell-padded"><%= bo.buy_order? ? "Buy" : "Sell" %></td>
+ <td class="table-cell-padded"><%= bo.price_each %></td>
+ <td class="table-cell-padded">
+ <%= button_to "Cancel", bazzar_cancel_offer_path(id: bo.id), method: :delete %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+</div>
+
+<div class="my-8">
+ <h2 class="text-xl mb-4">Sell Orders</h2>
+
+ <table class="table-auto mb-8">
+ <thead>
+ <tr>
+ <th class="table-header-padded">Item</th>
+ <th class="table-header-padded">Quantity</th>
+ <th class="table-header-padded">From</th>
+ <th class="table-header-padded">Price Each</th>
+ <th class="table-header-padded">Buy</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @bazaar_orders.where(buy_order: false).each do |bo| %>
+ <tr>
+ <td class="table-cell-padded"><%= bo.item.name %></td>
+ <td class="table-cell-padded"><%= bo.quantity %></td>
+ <td class="table-cell-padded"><%= bo.character.name %></td>
+ <td class="table-cell-padded"><%= bo.price_each %></td>
+ <td class="table-cell-padded">
+ <%= form_with url: bazzar_accept_offer_path(id: bo.id) do |f| %>
+ <%= f.number_field :quantity, value: 1, min: 1, max: 2_000_000_000, required: true %>
+ <%= f.submit "Buy" %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+</div>
+
+<div class="my-8">
+ <h2 class="text-xl mb-4">Buy Orders</h2>
+
+ <table class="table-auto mb-8">
+ <thead>
+ <tr>
+ <th class="table-header-padded">Item</th>
+ <th class="table-header-padded">Quantity</th>
+ <th class="table-header-padded">From</th>
+ <th class="table-header-padded">Price Each</th>
+ <th class="table-header-padded">Sell</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @bazaar_orders.where(buy_order: true).each do |bo| %>
+ <tr>
+ <td class="table-cell-padded"><%= bo.item.name %></td>
+ <td class="table-cell-padded"><%= bo.quantity %></td>
+ <td class="table-cell-padded"><%= bo.character.name %></td>
+ <td class="table-cell-padded"><%= bo.price_each %></td>
+ <td class="table-cell-padded">
+ <%= form_with url: bazzar_accept_offer_path(id: bo.id) do |f| %>
+ <%= f.number_field :quantity, value: 1, min: 1, max: 2_000_000_000, required: true %>
+ <%= f.submit "Sell" %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+</div>
diff --git a/config/routes.rb b/config/routes.rb
index f9c1f7b..4eee2a0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -12,7 +12,7 @@ Rails.application.routes.draw do
end
resources :chat_messages, only: [:index, :create]
-
+ resources :locations, only: [:index, :show]
resources :activities, only: [:show]
resources :characters, only: [:show, :new, :create] do
@@ -28,7 +28,10 @@ Rails.application.routes.draw do
end
end
- resources :locations, only: [:index, :show]
+ get "/bazaar", to: "bazaar#index"
+ post "/bazaar", to: "bazaar#create_order", as: :bazaar_order
+ post "/bazaar/accept/:id", to: "bazaar#accept_offer", as: :bazzar_accept_offer
+ delete "/bazaar/cancel/:id", to: "bazaar#cancel_offer", as: :bazzar_cancel_offer
post "/start_activity", to: "activities#start"
post "/finish_activity", to: "game#finish_activity"
diff --git a/data/items.yml b/data/items.yml
index ae1db0f..f97d339 100644
--- a/data/items.yml
+++ b/data/items.yml
@@ -50,3 +50,6 @@ mending_salve:
wood:
name: "wood"
description: "A bit of wood."
+vestige:
+ name: "vestige"
+ description: "Faintly glimmering remnants of magical events. Used as currency."
diff --git a/db/migrate/20210523151747_create_bazaar_orders.rb b/db/migrate/20210523151747_create_bazaar_orders.rb
new file mode 100644
index 0000000..24bded3
--- /dev/null
+++ b/db/migrate/20210523151747_create_bazaar_orders.rb
@@ -0,0 +1,13 @@
+class CreateBazaarOrders < ActiveRecord::Migration[6.1]
+ def change
+ create_table :bazaar_orders do |t|
+ t.references :character, null: false, foreign_key: true
+ t.references :item, null: false, foreign_key: true
+ t.integer :quantity
+ t.integer :price_each
+ t.boolean :buy_order
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6dfbe87..2568430 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2021_05_22_201259) do
+ActiveRecord::Schema.define(version: 2021_05_23_151747) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -28,6 +28,18 @@ ActiveRecord::Schema.define(version: 2021_05_22_201259) do
t.index ["location_id"], name: "index_activities_on_location_id"
end
+ create_table "bazaar_orders", force: :cascade do |t|
+ t.bigint "character_id", null: false
+ t.bigint "item_id", null: false
+ t.integer "quantity"
+ t.integer "price_each"
+ t.boolean "buy_order"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["character_id"], name: "index_bazaar_orders_on_character_id"
+ t.index ["item_id"], name: "index_bazaar_orders_on_item_id"
+ end
+
create_table "built_hearth_amenities", force: :cascade do |t|
t.bigint "hearth_id", null: false
t.bigint "hearth_amenity_id", null: false
@@ -213,6 +225,8 @@ ActiveRecord::Schema.define(version: 2021_05_22_201259) do
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
+ add_foreign_key "bazaar_orders", "characters"
+ add_foreign_key "bazaar_orders", "items"
add_foreign_key "built_hearth_amenities", "hearth_amenities"
add_foreign_key "built_hearth_amenities", "hearths"
add_foreign_key "character_items", "characters"
diff --git a/test/fixtures/bazaar_orders.yml b/test/fixtures/bazaar_orders.yml
new file mode 100644
index 0000000..9245a91
--- /dev/null
+++ b/test/fixtures/bazaar_orders.yml
@@ -0,0 +1,15 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ character: one
+ item: one
+ quantity: 1
+ price_each: 1
+ buy_order: false
+
+two:
+ character: two
+ item: two
+ quantity: 1
+ price_each: 1
+ buy_order: false
diff --git a/test/models/bazaar_order_test.rb b/test/models/bazaar_order_test.rb
new file mode 100644
index 0000000..d65862b
--- /dev/null
+++ b/test/models/bazaar_order_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class BazaarOrderTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end