summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Gay <david@davidgay.org>2021-06-06 19:06:46 -0400
committerDavid Gay <david@davidgay.org>2021-06-06 19:06:46 -0400
commite37402ff309311a14d7dd666d0d8b29517504017 (patch)
tree3d6604805e9004bc0c37130f451376e79a68c989
parent3622126380278d9bed8ea0e1e05a0bd1ea040596 (diff)
Leviathans
-rw-r--r--app/errors/monster_spawn_error.rb2
-rw-r--r--app/lib/activity_processor.rb57
-rw-r--r--app/models/location.rb1
-rw-r--r--app/models/monster_spawn.rb35
-rw-r--r--app/models/monster_spawn_combat.rb6
-rw-r--r--app/views/application/_results.html.erb3
-rw-r--r--app/views/locations/show.html.erb10
-rw-r--r--data/activities.yml18
-rw-r--r--data/items.yml6
-rw-r--r--data/monsters.yml43
-rw-r--r--db/migrate/20210606215806_create_monster_spawns.rb10
-rw-r--r--db/migrate/20210606215818_create_monster_spawn_combats.rb11
-rw-r--r--db/schema.rb25
-rw-r--r--test/fixtures/monster_spawn_combats.yml11
-rw-r--r--test/fixtures/monster_spawns.yml9
-rw-r--r--test/models/monster_spawn_combat_test.rb7
-rw-r--r--test/models/monster_spawn_test.rb7
17 files changed, 249 insertions, 12 deletions
diff --git a/app/errors/monster_spawn_error.rb b/app/errors/monster_spawn_error.rb
new file mode 100644
index 0000000..5a6a667
--- /dev/null
+++ b/app/errors/monster_spawn_error.rb
@@ -0,0 +1,2 @@
+class MonsterSpawnError < StandardError
+end
diff --git a/app/lib/activity_processor.rb b/app/lib/activity_processor.rb
index c0eaab1..11f0db0 100644
--- a/app/lib/activity_processor.rb
+++ b/app/lib/activity_processor.rb
@@ -39,6 +39,18 @@ class ActivityProcessor
when "xp"
puts "Result: #{result}"
handle_xp_result(result)
+ when "monster_spawn"
+ raise TooManyWoundsError unless @character.can_fight?
+ next if rand > (result[:chance] || 1)
+
+ @results.push({ type: "br" })
+
+ monster_spawn = MonsterSpawn.where(location: Location.find_by_gid(result[:location])).select(&:alive?).first
+ raise MonsterSpawnError unless monster_spawn
+
+ @results.push({ type: type, monster_spawn: monster_spawn })
+ resolve_combat_with(monster_spawn)
+ break
when "monster"
raise TooManyWoundsError unless @character.can_fight?
next if rand > (result[:chance] || 1)
@@ -129,6 +141,10 @@ class ActivityProcessor
@character.stop_activity
@results.replace([{ type: "error",
message: "You can't fight in your condition. You'll have to heal a wound." }])
+ rescue MonsterSpawnError
+ @character.stop_activity
+ @results.replace([{ type: "error",
+ message: "There are no living leviathans here." }])
end
private
@@ -193,9 +209,16 @@ class ActivityProcessor
end
def resolve_combat_with(mon)
+
+ monster_spawn = nil
+ if mon.is_a? MonsterSpawn
+ monster_spawn = mon
+ mon = monster_spawn.monster
+ end
+
char = @character
char_hp = @character.max_hp
- mon_hp = mon.max_hp
+ mon_hp = monster_spawn.present? ? monster_spawn.remaining_hp : mon.max_hp
combat_message = ->(msg) { @results.push({ type: "message", body: "[#{char_hp}/#{char.max_hp}] #{msg}" }) }
char_initiative = roll(20) + char.speed
mon_initative = roll(20) + mon.speed
@@ -244,6 +267,13 @@ class ActivityProcessor
combat_message.call("#{target.name} evaded #{actor.name}'s attack.")
end
if char_hp < 1 || mon_hp < 1
+ if monster_spawn
+ hp_lost = monster_spawn.remaining_hp - mon_hp
+ if hp_lost > 0
+ monster_spawn.monster_spawn_combats.create(monster_spawn: monster_spawn, character: char,
+ hp_lost: monster_spawn.remaining_hp - mon_hp)
+ end
+ end
if char_hp < 1
@results.push({ type: "message", body: "You were defeated! You retreat, wounded." })
char.wounds += 1
@@ -256,16 +286,21 @@ class ActivityProcessor
end
else
@results.push({ type: "message", body: "You slew the #{mon.name}." })
- mon.whatnot[:awards]&.each do |award_data|
- case award_data[:type]
- when "title"
- handle_title_result(award_data)
- when "xp"
- handle_xp_result(award_data)
- when "item"
- handle_item_result(award_data)
- else
- raise "Invalid award type string (#{award_data[:type]})"
+ if monster_spawn
+ char.stop_activity
+ return
+ else
+ mon.whatnot[:awards]&.each do |award_data|
+ case award_data[:type]
+ when "title"
+ handle_title_result(award_data)
+ when "xp"
+ handle_xp_result(award_data)
+ when "item"
+ handle_item_result(award_data)
+ else
+ raise "Invalid award type string (#{award_data[:type]})"
+ end
end
end
end
diff --git a/app/models/location.rb b/app/models/location.rb
index b7c5bf5..e008270 100644
--- a/app/models/location.rb
+++ b/app/models/location.rb
@@ -2,5 +2,6 @@ class Location < ApplicationRecord
include HasWhatnot
has_many :activities
+ has_many :monster_spawns
validates :gid, :name, presence: true
end
diff --git a/app/models/monster_spawn.rb b/app/models/monster_spawn.rb
new file mode 100644
index 0000000..d34e390
--- /dev/null
+++ b/app/models/monster_spawn.rb
@@ -0,0 +1,35 @@
+class MonsterSpawn < ApplicationRecord
+ belongs_to :monster
+ belongs_to :location
+ has_many :monster_spawn_combats
+
+ after_update :check_hp
+ after_create :send_chat_message
+
+ def alive?
+ self.remaining_hp > 0
+ end
+
+ def remaining_hp
+ self.monster.max_hp - MonsterSpawnCombat.where(monster_spawn: self).sum(:hp_lost)
+ end
+
+ private
+ def send_chat_message
+ chat_message = ChatMessage.new(body: "A leviathan has appeared in #{location.name}!",
+ chat_room: ChatRoom.find_by_gid("news"))
+ if chat_message.save
+ ChatRoomChannel.broadcast_chat_message(chat_message)
+ end
+ end
+
+ def check_hp
+ if alive?
+ chat_message = ChatMessage.new(body: "The #{monster.name} in #{location.name} has been slain!",
+ chat_room: ChatRoom.find_by_gid("news"))
+ if chat_message.save
+ ChatRoomChannel.broadcast_chat_message(chat_message)
+ end
+ end
+ end
+end
diff --git a/app/models/monster_spawn_combat.rb b/app/models/monster_spawn_combat.rb
new file mode 100644
index 0000000..50a565b
--- /dev/null
+++ b/app/models/monster_spawn_combat.rb
@@ -0,0 +1,6 @@
+class MonsterSpawnCombat < ApplicationRecord
+ belongs_to :monster_spawn
+ belongs_to :character
+
+ validates :hp_lost, numericality: { greater_than_or_equal_to: 0, only_integer: true }
+end
diff --git a/app/views/application/_results.html.erb b/app/views/application/_results.html.erb
index 31fb93e..ec62991 100644
--- a/app/views/application/_results.html.erb
+++ b/app/views/application/_results.html.erb
@@ -17,6 +17,9 @@
<% when "monster" %>
<p>You encountered a <%= result[:monster].name %>.</p>
<p class="text-xs italic"><%= result[:monster].description %></p>
+ <% when "monster_spawn" %>
+ <p>You found the <%= result[:monster_spawn].monster.name %>!</p>
+ <p class="text-xs italic"><%= result[:monster_spawn].monster.description %></p>
<% when "xp" %>
<p class="text-xs">You gained <%= result[:xp] %> <%= result[:skill].name %> XP.</p>
<% when "title" %>
diff --git a/app/views/locations/show.html.erb b/app/views/locations/show.html.erb
index ff7b2a8..fcc668a 100644
--- a/app/views/locations/show.html.erb
+++ b/app/views/locations/show.html.erb
@@ -3,6 +3,16 @@
<p class="italic"><%= @location.description %></p>
</div>
+<% @location.monster_spawns.select(&:alive?).each do |ms| %>
+ <p class="text-yellow-400">A <%= ms.monster.name %> is ravaging this location! (<%= ms.remaining_hp %> HP)</p>
+ <%# TODO: HACK %>
+ <% activity = Activity.find_by_gid("beastslay_leviathan_floret_region") %>
+ <%= form_with url: start_activity_path(activity) do |f| %>
+ <%= f.hidden_field :id, value: activity.id %>
+ <%= f.submit "Hunt" %>
+ <% end %>
+<% end %>
+
<% @location.activities.order(:name).each do |activity| %>
<div class="my-4">
<h2 class="text-xl"><%= link_to activity.name, activity_path(activity) %></h2>
diff --git a/data/activities.yml b/data/activities.yml
index 9e1fb8c..1ab47e3 100644
--- a/data/activities.yml
+++ b/data/activities.yml
@@ -2453,3 +2453,21 @@ craft_midoras_mudtub_mash:
xp:
- gid: "spicework"
value: 10
+beastslay_leviathan_floret_region:
+ name: "Hunt a Leviathan in the Floret Region"
+ description: "You are hunting down a leviathan ravaging the Floret Region."
+ whatnot:
+ duration:
+ base: 80
+ minimum: 40
+ scaling:
+ - type: "skill"
+ gid: "beastslay"
+ scale_value: 1
+ - type: "stat"
+ gid: "beastslay_speed"
+ scale_value: 1
+ results:
+ - type: "monster_spawn"
+ location: "floret_region"
+ chance: 1
diff --git a/data/items.yml b/data/items.yml
index dcfe80a..255bd1c 100644
--- a/data/items.yml
+++ b/data/items.yml
@@ -1010,3 +1010,9 @@ onus_of_vision:
- type: "stat_change"
gid: "speed"
modifier: -2
+balgoloth_skull:
+ name: "balgoloth skull"
+ description: "The enormous skull of a balgoloth. About as big as your entire upper body."
+ whatnot:
+ tags:
+ - "material"
diff --git a/data/monsters.yml b/data/monsters.yml
index 8185c59..59024d6 100644
--- a/data/monsters.yml
+++ b/data/monsters.yml
@@ -267,3 +267,46 @@ crypt_writhe:
- type: "item"
chance: 0.001
gid: "crypt_writhe_trophy"
+balgoloth:
+ name: "balgoloth"
+ description: >-
+ A fur-covered, bipedal creature standing at least twenty feet tall. It strides across the lands with great
+ sloth, brandishing thick, foot-long claws. It can smell thoughts from miles away, and the inner machinations of
+ anything smarter than a grinpad enrages it. It only returns to calm once the thoughts have been silenced.
+ whatnot:
+ tags:
+ - "leviathan"
+ max_hp:
+ base: 250
+ speed:
+ base: 20
+ accuracy:
+ base: 25
+ power:
+ base: 40
+ evasion:
+ base: 12
+ resistances:
+ - gid: "physical"
+ base: 40
+ - gid: "energy"
+ base: 20
+ - gid: "fire"
+ base: -20
+ hit_effects:
+ - type: "damage"
+ gid: "bash"
+ min: 20
+ max: 40
+ awards:
+ - type: "xp"
+ gid: "beastslay"
+ base: 100
+ - type: "item"
+ chance: 1
+ gid: "balgoloth_skull"
+ - type: "item"
+ chance: 1
+ gid: "vestige"
+ min_quantity: 250
+ max_quantity: 500
diff --git a/db/migrate/20210606215806_create_monster_spawns.rb b/db/migrate/20210606215806_create_monster_spawns.rb
new file mode 100644
index 0000000..96ccefe
--- /dev/null
+++ b/db/migrate/20210606215806_create_monster_spawns.rb
@@ -0,0 +1,10 @@
+class CreateMonsterSpawns < ActiveRecord::Migration[6.1]
+ def change
+ create_table :monster_spawns do |t|
+ t.references :monster, null: false, foreign_key: true
+ t.references :location, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20210606215818_create_monster_spawn_combats.rb b/db/migrate/20210606215818_create_monster_spawn_combats.rb
new file mode 100644
index 0000000..a2dd894
--- /dev/null
+++ b/db/migrate/20210606215818_create_monster_spawn_combats.rb
@@ -0,0 +1,11 @@
+class CreateMonsterSpawnCombats < ActiveRecord::Migration[6.1]
+ def change
+ create_table :monster_spawn_combats do |t|
+ t.references :monster_spawn, null: false, foreign_key: true
+ t.references :character, null: false, foreign_key: true
+ t.integer :hp_lost
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 55a66d9..6e34c95 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_06_06_214340) do
+ActiveRecord::Schema.define(version: 2021_06_06_215818) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -196,6 +196,25 @@ ActiveRecord::Schema.define(version: 2021_06_06_214340) do
t.index ["sender_id"], name: "index_messages_on_sender_id"
end
+ create_table "monster_spawn_combats", force: :cascade do |t|
+ t.bigint "monster_spawn_id", null: false
+ t.bigint "character_id", null: false
+ t.integer "hp_lost"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["character_id"], name: "index_monster_spawn_combats_on_character_id"
+ t.index ["monster_spawn_id"], name: "index_monster_spawn_combats_on_monster_spawn_id"
+ end
+
+ create_table "monster_spawns", force: :cascade do |t|
+ t.bigint "monster_id", null: false
+ t.bigint "location_id", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["location_id"], name: "index_monster_spawns_on_location_id"
+ t.index ["monster_id"], name: "index_monster_spawns_on_monster_id"
+ end
+
create_table "monsters", force: :cascade do |t|
t.string "gid"
t.string "name"
@@ -292,6 +311,10 @@ ActiveRecord::Schema.define(version: 2021_06_06_214340) do
add_foreign_key "learned_activities", "characters"
add_foreign_key "messages", "characters", column: "recipient_id"
add_foreign_key "messages", "characters", column: "sender_id"
+ add_foreign_key "monster_spawn_combats", "characters"
+ add_foreign_key "monster_spawn_combats", "monster_spawns"
+ add_foreign_key "monster_spawns", "locations"
+ add_foreign_key "monster_spawns", "monsters"
add_foreign_key "states", "characters"
add_foreign_key "states", "conditions"
add_foreign_key "title_awards", "characters"
diff --git a/test/fixtures/monster_spawn_combats.yml b/test/fixtures/monster_spawn_combats.yml
new file mode 100644
index 0000000..66d1fff
--- /dev/null
+++ b/test/fixtures/monster_spawn_combats.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ monster_spawn: one
+ character: one
+ hp_lost: 1
+
+two:
+ monster_spawn: two
+ character: two
+ hp_lost: 1
diff --git a/test/fixtures/monster_spawns.yml b/test/fixtures/monster_spawns.yml
new file mode 100644
index 0000000..4106695
--- /dev/null
+++ b/test/fixtures/monster_spawns.yml
@@ -0,0 +1,9 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ monster: one
+ location: one
+
+two:
+ monster: two
+ location: two
diff --git a/test/models/monster_spawn_combat_test.rb b/test/models/monster_spawn_combat_test.rb
new file mode 100644
index 0000000..7717a34
--- /dev/null
+++ b/test/models/monster_spawn_combat_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class MonsterSpawnCombatTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/models/monster_spawn_test.rb b/test/models/monster_spawn_test.rb
new file mode 100644
index 0000000..07d8c85
--- /dev/null
+++ b/test/models/monster_spawn_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class MonsterSpawnTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end