diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/character.rb | 81 | ||||
-rw-r--r-- | app/models/character_skill.rb | 16 | ||||
-rw-r--r-- | app/models/concerns/has_costs_and_requirements.rb | 8 | ||||
-rw-r--r-- | app/models/equipment.rb | 19 | ||||
-rw-r--r-- | app/models/item.rb | 7 | ||||
-rw-r--r-- | app/models/item_infix.rb | 21 | ||||
-rw-r--r-- | app/models/monster_kill.rb | 22 |
7 files changed, 151 insertions, 23 deletions
diff --git a/app/models/character.rb b/app/models/character.rb index d386002..2669626 100644 --- a/app/models/character.rb +++ b/app/models/character.rb @@ -9,10 +9,12 @@ class Character < ApplicationRecord has_many :character_items has_many :learned_activities has_many :items, through: :character_items + has_many :item_infixes has_many :character_skills has_many :conditions, through: :states has_many :states has_many :chat_messages + has_many :monster_kills has_many :bazaar_orders validates :name, presence: true # TODO: Make defaults better. This has to allow nil so the `attribute` default works, and I don't like that. @@ -115,14 +117,30 @@ class Character < ApplicationRecord end end - def do_equipment_break_checks(exclude_slots: []) - broken_items = [] - equipment.where.not(slot: exclude_slots).each do |equipment| - if equipment.break_check - broken_items.push(equipment.item) - end + def do_equipment_break_check(skill: nil) + skill = Skill.find_by_gid(skill) if skill&.is_a? String + # TODO: HACK: Should check other stats besides speed stat in the future. + # TODO: HACK: May not want a chance to break if speed is _reduced_. Though no equipment does this yet. + if skill + threatened_equipment = equipment.all.select { |eq| eq.effects&.select { |ef| ef[:gid] == "#{skill.gid}_speed" }&.any? } + else + threatened_equipment = equipment.all end - broken_items + break_slot = Equipment.random_break_slot + return nil unless break_slot + broken_equipment = threatened_equipment.find { |eq| eq.slot == break_slot } + return nil unless broken_equipment + broken_equipment.destroy + broken_equipment.item + end + + def do_item_infix_break_check(skill:) + skill = Skill.find_by_gid(skill) if skill.is_a? String + return nil unless ItemInfix.break_check + broken_ii = item_infixes.where(skill: skill).sample + return nil unless broken_ii + broken_ii.destroy + broken_ii.item end def has_item?(item, quantity = 1) @@ -136,6 +154,10 @@ class Character < ApplicationRecord self.equipment.find_by(item: item) end + def equipment_with_tag(tag) + self.equipment.all.find { |e| e.item.has_tag?(tag) } + end + def open_slots_for(item) full_slots = self.equipment.map { |e| e.slot } item.equip_slots.reject { |slot| full_slots.include?(slot) } @@ -182,6 +204,30 @@ class Character < ApplicationRecord end end + def max_infixes(skill) + skill = Skill.find_by_gid(skill) if skill.is_a? String + 1 + (skill_level(skill) / 20).floor + end + + def available_infixes(skill) + skill = Skill.find_by_gid(skill) if skill.is_a? String + max_infixes(skill) - item_infixes.where(skill: skill).count + end + + def infix(item, skill) + Character.transaction do + shift_item(item, -1) + item_infixes.create(item: item, skill: skill) + end + end + + def remove_infix(item_infix) + Character.transaction do + shift_item(item_infix.item, 1) + item_infix.destroy + end + end + def add_skill_xp(skill, amount) skill = Skill.find_by_gid(skill) if skill.is_a? String Character.transaction do @@ -247,7 +293,14 @@ class Character < ApplicationRecord activity.whatnot[:requirements]&.each do |requirement| case requirement[:type] when "equipment" - return false unless self.equipment_with_gid(requirement[:gid]) + if requirement[:tag] + return false unless self.equipment_with_tag(requirement[:tag]) + else + return false unless self.equipment_with_gid(requirement[:gid]) + end + when "stat" + # TODO: HACK: This won't work with built-in stats! Need to change this to work with power and whatnot. + return false unless self.total_stat_change(requirement[:gid]) >= requirement[:value] when "skill" return false unless self.skill_level(requirement[:gid]) >= requirement[:level] when "hearth_amenity" @@ -278,6 +331,7 @@ class Character < ApplicationRecord def start_resting return false if self.started_resting_at + stop_activity self.update(started_resting_at: Time.now) end @@ -306,17 +360,26 @@ class Character < ApplicationRecord hearth_amenity_effects = self.hearth.built_hearth_amenities.filter_map { |a| a.effects if a.effects } equipment_effects = self.equipment.filter_map { |a| a.effects if a.effects } state_effects = self.states.filter_map { |a| a.effects if a.effects && !a.expired? } - (hearth_amenity_effects + equipment_effects + state_effects).flatten + item_infix_effects = self.item_infixes.filter_map { |a| a.effects if a.effects } + (hearth_amenity_effects + equipment_effects + state_effects + item_infix_effects).flatten end def total_stat_change(gid) effects.filter_map { |e| e[:modifier] if e[:type] == "stat_change" && e[:gid] == gid }.sum end + def total_enemy_stat_change(gid) + effects.filter_map { |e| e[:modifier] if e[:type] == "enemy_stat_change" && e[:gid] == gid }.sum + end + def damage_ranges effects.filter_map { |e| { gid: e[:gid], min: e[:min], max: e[:max] } if e[:type] == "damage" } end + def dots + effects.filter_map { |e| { gid: e[:gid], min: e[:min], max: e[:max], message: e[:message] } if e[:type] == "dot" } + end + def planting_spots [total_stat_change("planting_spots"), 0].max end diff --git a/app/models/character_skill.rb b/app/models/character_skill.rb index 368cc28..406cade 100644 --- a/app/models/character_skill.rb +++ b/app/models/character_skill.rb @@ -39,6 +39,10 @@ class CharacterSkill < ApplicationRecord end end + def self.xp_required_for_level(level) + level <= 120 ? XP_TOTALS_PER_LEVEL[level - 1] : nil + end + def level XP_TOTALS_PER_LEVEL.each_with_index do |total, index| return index if total > self.xp @@ -46,7 +50,7 @@ class CharacterSkill < ApplicationRecord end def total_xp_for_next_level - xp_required_for_level(level + 1) + CharacterSkill.xp_required_for_level(level + 1) end def xp_to_next_level @@ -57,11 +61,13 @@ class CharacterSkill < ApplicationRecord CharacterSkill.top_xp_for(self.skill, limit: nil).map(&:character).map(&:id).index(self.character.id) + 1 end - private - def xp_required_for_level(level) - level <= 120 ? XP_TOTALS_PER_LEVEL[level - 1] : nil - end + def percentage_of_skill_level_completed + xp_gained_this_level = xp - CharacterSkill.xp_required_for_level(level) + total_xp_gain_neeeded_for_entire_level = total_xp_for_next_level - CharacterSkill.xp_required_for_level(level) + (xp_gained_this_level.to_f / total_xp_gain_neeeded_for_entire_level) * 100 + end + private def send_chat_message_if_leveled_up if CharacterSkill.level_for_xp(self.xp_was) < CharacterSkill.level_for_xp(self.xp) chat_message = ChatMessage.new(body: "reached #{self.skill.name} level #{self.level}!", diff --git a/app/models/concerns/has_costs_and_requirements.rb b/app/models/concerns/has_costs_and_requirements.rb index 34ff0f3..9c4abf8 100644 --- a/app/models/concerns/has_costs_and_requirements.rb +++ b/app/models/concerns/has_costs_and_requirements.rb @@ -19,8 +19,14 @@ module HasCostsAndRequirements case req[:type] when "skill" requirements.push "level #{req[:level]} #{Skill.find_by_gid(req[:gid]).name}" + when "stat" + requirements.push "#{req[:value]} #{req[:gid]}" when "equipment" - requirements.push "equipped #{Item.find_by_gid(req[:gid]).name}" + if req[:tag] + requirements.push "equipped #{req[:tag]}" + else + requirements.push "equipped #{Item.find_by_gid(req[:gid]).name}" + end when "hearth_amenity" requirements.push "level #{req[:level]} #{HearthAmenity.find_by_gid(req[:gid]).name}" else diff --git a/app/models/equipment.rb b/app/models/equipment.rb index e1dc7a3..12e8ae7 100644 --- a/app/models/equipment.rb +++ b/app/models/equipment.rb @@ -5,6 +5,17 @@ class Equipment < ApplicationRecord :left_ring, :right_ring, :waist, :legs, :feet, :curio] validates :slot, presence: true, uniqueness: { scope: :character } + def self.random_break_slot + roll = rand + if roll >= 0.99933333 + [:neck, :left_ring, :right_ring].sample + elsif roll >= 0.999 + [:back, :waist, :curio].sample + elsif roll >= 0.998 + [:mainhand, :offhand, :head, :torso, :grip, :legs, :feet].sample + end + end + def slot self[:slot].to_sym end @@ -14,14 +25,6 @@ class Equipment < ApplicationRecord end def break_check - roll = rand - if [:neck, :left_ring, :right_ring].include?(slot) - destroy and return true if roll > 0.9998 - elsif [:back, :waist, :curio].include?(slot) - destroy and return true if roll > 0.9996 - else - destroy and return true if roll > 0.999 - end false end end diff --git a/app/models/item.rb b/app/models/item.rb index 8b80788..0e25b2f 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -13,6 +13,13 @@ class Item < ApplicationRecord self.whatnot && self.whatnot[:use_effects]&.any? end + def infixable?(skill = nil) + skill = Skill.find_by_gid(skill) if skill.is_a? String + return false unless self.whatnot && self.whatnot[:infix_skills]&.any? + return true unless skill + self.whatnot[:infix_skills].select { |data| data[:gid] == skill.gid }.any? + end + def equip_slots return [] unless self.equipment? self.whatnot[:equip_slots].map { |data| data.to_sym } diff --git a/app/models/item_infix.rb b/app/models/item_infix.rb new file mode 100644 index 0000000..c484242 --- /dev/null +++ b/app/models/item_infix.rb @@ -0,0 +1,21 @@ +class ItemInfix < ApplicationRecord + belongs_to :character + belongs_to :item + belongs_to :skill + + before_create :check_max_infixes + + def self.break_check + rand >= 0.99 + end + + def effects + self.item.whatnot[:infix_effects] + end + + private + def check_max_infixes + current_infixes = character.item_infixes.where(skill: skill) + raise :abort if current_infixes.count >= character.max_infixes(skill) + end +end diff --git a/app/models/monster_kill.rb b/app/models/monster_kill.rb new file mode 100644 index 0000000..24519b1 --- /dev/null +++ b/app/models/monster_kill.rb @@ -0,0 +1,22 @@ +class MonsterKill < ApplicationRecord + belongs_to :monster + belongs_to :character + + validates :quantity, numericality: { greater_than_or_equal_to: 0, only_integer: true } + scope :ordered_by_monster_name, -> { includes(:monster).order("monsters.name") } + + after_save :award_titles + + private + def award_titles + character.award_title("spiteful") if quantity >= 1000 + character.award_title("hateful") if quantity >= 10_000 + character.award_title("vicious") if quantity >= 100_000 + + all_kills_quantity = character.monster_kills.sum(:quantity) + character.award_title("slayer") if all_kills_quantity >= 1_000 + character.award_title("butcher") if all_kills_quantity >= 10_000 + character.award_title("slaughterer") if all_kills_quantity >= 100_000 + character.award_title("massacrer") if all_kills_quantity >= 1_000_000 + end +end |