summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorDavid Gay <david@davidgay.org>2021-05-22 18:10:19 -0400
committerDavid Gay <david@davidgay.org>2021-05-22 18:10:19 -0400
commit44facc2e567eb3c045ce082428f42276e45b0202 (patch)
tree0b302bfd60a6faae698a63e68e3e1a5bdb9e16c2 /app
parent38f3a39221869483e3468e9f4d8cab5450a70f89 (diff)
Monsters and basic combat
Diffstat (limited to 'app')
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/game_controller.rb80
-rw-r--r--app/models/character.rb45
-rw-r--r--app/models/monster.rb31
-rw-r--r--app/views/activities/_results.html.erb2
-rw-r--r--app/views/activities/show.html.erb2
6 files changed, 163 insertions, 1 deletions
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index efd8427..81e07da 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -10,4 +10,8 @@ class ApplicationController < ActionController::Base
def redirect_if_no_active_character
redirect_to new_character_path unless current_char
end
+
+ def roll(sides)
+ rand(sides) + 1
+ end
end
diff --git a/app/controllers/game_controller.rb b/app/controllers/game_controller.rb
index 58644a7..c63fbc5 100644
--- a/app/controllers/game_controller.rb
+++ b/app/controllers/game_controller.rb
@@ -13,6 +13,23 @@ class GameController < ApplicationController
activity.whatnot[:results].each do |result|
type = result[:type]
case type
+ when "monster"
+ next if rand > (result[:chance] || 1)
+ table_roll = rand
+ result[:table].sort_by { |t| -t[:score] }.each do |table_entry|
+ score = table_entry[:score]
+ result[:table_scaling]&.each do |scale_entry|
+ case scale_entry[:type]
+ when "skill"
+ score = score**(1 + (scale_entry[:scale_value] * current_char.skill_level(scale_entry[:gid])))
+ end
+ end
+ if table_roll >= score
+ activity = Activity.find_by_gid(table_entry[:gid])
+ monster = Monster.find_by_gid(table_entry[:gid])
+ resolve_combat_with(monster)
+ end
+ end
when "item"
next if rand > (result[:chance] || 1)
@@ -83,4 +100,67 @@ class GameController < ApplicationController
current_char.shift_item(item, quantity)
@results.push({ type: "item", item: item, quantity: quantity, xp: xp_awards })
end
+
+ def resolve_combat_with(mon)
+ char = current_char
+ char_hp = current_char.max_hp
+ mon_hp = mon.max_hp
+ combat_message = ->(msg) { @results.push({ type: "message", body: "[#{char_hp}/#{char.max_hp}] #{msg}" }) }
+ combat_message.call("You encountered a #{mon.name}.")
+ char_initiative = roll(10) + char.speed
+ mon_initative = roll(10) + mon.speed
+ if char_initiative > mon_initative
+ turn_order = [char, mon]
+ elsif mon_initative > char_initiative
+ turn_order = [mon, char]
+ else
+ turn_order = [char, mon].shuffle
+ end
+ turn_order.cycle do |actor|
+ case actor
+ when char
+ accuracy_roll = roll(20) + char.accuracy
+ evasion_roll = roll(20) + mon.evasion
+ if accuracy_roll >= evasion_roll
+ dealt_damage = roll(4) + char.power # TODO: Replace d4 with weapon damage
+ if accuracy_roll >= (evasion_roll + 10)
+ combat_message.call("You landed a critical hit!")
+ dealt_damage = dealt_damage * 2
+ end
+ blocked_damage = (accuracy_roll >= (roll(20) + mon.block)) ? 0 : mon.block_value
+ resolved_damage = dealt_damage - blocked_damage
+ mon_hp -= resolved_damage
+ combat_message.call("You hit for #{resolved_damage} (#{dealt_damage} - #{blocked_damage} blocked)")
+ elsif evasion_roll > accuracy_roll
+ combat_message.call("The #{mon.name} evaded your attack.")
+ end
+ when mon
+ combat_message.call("Monsters don't get turns yet.")
+ else
+ raise "Invalid combatant (class is #{actor.class})"
+ end
+ if char_hp < 1 || mon_hp < 1
+ if char_hp < 1
+ combat_message.call("You were defeated! You retreat, wounded.")
+ char.increment(:wounds)
+ else
+ combat_message.call("You defeated the #{mon.name}.")
+ mon.whatnot[:awards]&.each do |award_data|
+ case award_data[:type]
+ when "xp"
+ skill = Skill.find_by_gid(award_data[:skill])
+ amount = award_data[:base]
+ char.add_skill_xp(skill, amount)
+ combat_message.call("You gained #{amount} #{skill.name} XP.")
+ else
+ raise "Invalid award type string (#{award_data[:type]})"
+ end
+ end
+ end
+ break
+ else
+ combat_message.call("-" * 20)
+ end
+ end
+ end
end
diff --git a/app/models/character.rb b/app/models/character.rb
index 59ed5ae..bad2bb2 100644
--- a/app/models/character.rb
+++ b/app/models/character.rb
@@ -19,6 +19,22 @@ class Character < ApplicationRecord
after_create :create_skills
after_create { Hearth.create(character: self) }
+ def beastslay_level; skill_level("beastslay"); end
+ def fluxseethe_level; skill_level("fluxseethe"); end
+ def havencast_level; skill_level("havencast"); end
+ def hexcarve_level; skill_level("hexcarve"); end
+ def magiculture_level; skill_level("magiculture"); end
+ def manatrawl_level; skill_level("manatrawl"); end
+ def omenbind_level; skill_level("omenbind"); end
+ def otherforge_level; skill_level("otherforge"); end
+ def planequarry_level; skill_level("planequarry"); end
+ def spicework_level; skill_level("spicework"); end
+ def synthsever_level; skill_level("synthsever"); end
+ def veilreach_level; skill_level("veilreach"); end
+ def wealdreap_level; skill_level("wealdreap"); end
+ def wildscour_level; skill_level("wildscour"); end
+ def worldcall_level; skill_level("worldcall"); end
+
def shift_item(item, amount)
item = Item.find_by_gid(item) if item.is_a? String
CharacterItem.transaction do
@@ -70,6 +86,7 @@ class Character < ApplicationRecord
end
def add_skill_xp(skill, amount)
+ skill = Skill.find_by_gid(skill) if skill.is_a? String
CharacterSkill.find_by(skill: skill).increment!(:xp, amount)
end
@@ -115,6 +132,34 @@ class Character < ApplicationRecord
self.update(activity: activity, activity_started_at: Time.now) if self.can_do_activity?(activity)
end
+ def can_fight?
+ self.wounds < 1
+ end
+
+ def max_hp
+ 10 + self.beastslay_level
+ end
+
+ def speed
+ self.beastslay_level
+ end
+
+ def accuracy
+ self.beastslay_level
+ end
+
+ def power
+ self.beastslay_level
+ end
+
+ def evasion
+ self.beastslay_level
+ end
+
+ def block
+ self.beastslay_level
+ end
+
private
def create_skills
Skill.all.each { |skill| self.character_skills.create(skill: skill, xp: 0) }
diff --git a/app/models/monster.rb b/app/models/monster.rb
new file mode 100644
index 0000000..1b521aa
--- /dev/null
+++ b/app/models/monster.rb
@@ -0,0 +1,31 @@
+class Monster < ApplicationRecord
+ include HasWhatnot
+
+ def max_hp
+ self.whatnot[:max_hp][:base]
+ end
+
+ def speed
+ self.whatnot[:speed][:base]
+ end
+
+ def accuracy
+ self.whatnot[:accuracy][:base]
+ end
+
+ def power
+ self.whatnot[:power][:base]
+ end
+
+ def evasion
+ self.whatnot[:evasion][:base]
+ end
+
+ def block
+ self.whatnot[:block][:base]
+ end
+
+ def block_value
+ self.whatnot[:block_value][:base]
+ end
+end
diff --git a/app/views/activities/_results.html.erb b/app/views/activities/_results.html.erb
index 0fef8fb..5fde590 100644
--- a/app/views/activities/_results.html.erb
+++ b/app/views/activities/_results.html.erb
@@ -11,6 +11,8 @@
<p>You constructed <%= result[:hearth_amenity].name %>.</p>
<% when "activity" %>
<p>You realized how to <%= result[:activity].name %>!</p>
+ <% when "message" %>
+ <p><%= result[:body] %></p>
<% when "error" %>
<p><%= result[:message] %></p>
<% end %>
diff --git a/app/views/activities/show.html.erb b/app/views/activities/show.html.erb
index 84c19a4..c9c6d25 100644
--- a/app/views/activities/show.html.erb
+++ b/app/views/activities/show.html.erb
@@ -2,7 +2,7 @@
<p><%= @activity.description %></p>
<div class="min-w-full my-2 px-1 overflow-auto text-sm border-2 border-gray-800 rounded"
- style="height: 75px" id="result_output">
+ style="height: 30rem;" id="result_output">
</div>
<div id="result_controls">