Search This Blog

Exploring Monster Taming Mechanics in Final Fantasy XIII-2: Monster Characteristics

We've done something with just about all of the data we've extracted on monster taming in this series on exploring Final Fantasy XIII-2 monster taming mechanics—except for the monster characteristics. Last time we finally integrated the monster materials table into our website, so now we're ready to tackle the last orphaned data. But what should we do with these monster characteristics? They each consist of only a name and a description, so it seems kind of boring to just link their names in the monster table to the monster characteristics table. We should be able to do something more useful with them. Conveniently, Bootstrap has a tooltip component that would be a perfect use for this data, so we're going to explore that idea.

Final Fantasy XIII-2 Svarog

Creating Tooltips

Tooltips are a nice little user interface feature that pops up a descriptive box when the user hovers their mouse over a target element on the page. We have just such a set of elements that would warrant a description in the "Growth" column of the monster table. The values of "early peaker," "well-grown," and "late bloomer" are not self-explanatory, so we want to provide descriptions that will better inform the user, if they're confused, with a convenient tooltip.

Tooltips are a bit more involved than the other Bootstrap features we've used, so they need a little JavaScript initialization. We can see from the Bootstrap documentation that we have to include this little code snippet in our HTML to initialize tooltips on a page after the DOM loads:
$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})
To get this to work in Rails, we can stick this JavaScript in a file that we can later reference in our views for the pages that we want tooltips to load. Let's put it in app/javascript/packs/monster_tooltips.js. Next, we can test out the tooltips by adding the following code to our monster table view in app/views/monster/index.html.erb:
<h1>Monsters</h1>

<%= javascript_pack_tag 'monster_tooltips' %>

<table id="monster-table" class="table table-striped table-sm">
  <thead class="thead-dark">
    <tr>
      <th scope="col">Monster Name</th>
      <th scope="col">Role</th>
      <th scope="col">Location</th>
      <th scope="col">Tame Rate</th>
      <th scope="col">Max Level</th>
      <th scope="col">Base HP</th>
      <th scope="col">Base Strength</th>
      <th scope="col">Base Magic</th>
      <th scope="col">Speed</th>
      <th scope="col"><span data-toggle="tooltip" data-original-title="Test Tooltip">Growth</span></th>
      <th scope="col">Immune</th>
      <th scope="col">Resistant</th>
      <th scope="col">Halved</th>
      <th scope="col">Weak</th>
    </tr>
  </thead>
This code snippet is just showing up to the header row of the table, and we can see the javascript_pack_tag that's added to load the monster_tooltips.js file. Then, in the header cells, the "Growth" column header is wrapped in a span that adds a data-toggle property of "tooltip" and a data-original-title property with the text we want to display in the tooltip. Now if we reload the monster table page and hover the cursor over the "Growth" header, we see our tooltip pop up:

Screenshot of tooltip on Growth header in monster table

Neato. Okay, now that we have that sorted out, all we have to do is wrap each growth cell in a span tag and put the correct text in, right?

Adding Growth Tooltips

It's not quite that simple because there are a couple problems. First, we need to connect the growth values with references to the monster characteristics table so we can get at the descriptions. That's not too hard. We've done that kind of thing before. We need to change the data type of the growth attribute to references in the monster table migration and set the foreign key to the characteristics table:
class CreateMonsters < ActiveRecord::Migration[6.0]
  def change
    create_table :monsters do |t|
      t.string :name
      t.string :role
      t.references :location, foreign_key: true
      t.references :location2, foreign_key: {to_table: :locations}
      t.references :location3, foreign_key: {to_table: :locations}
      t.integer :max_level
      t.integer :speed
      t.string :tame_rate
      t.references :growth, foreign_key: {to_table: :characteristics}
      t.string :immune
      t.string :resistant
      t.string :halved
      t.string :weak
      t.references :normal_drop, foreign_key: {to_table: :materials}
      t.references :rare_drop, foreign_key: {to_table: :materials}
      # ... many more attributes ...
    end
  end
end
Then, we add the link to the monster model so we can get to the characteristic model from the growth attribute in the monster model. This code is added to app/models/monster.rb:
class Monster < ApplicationRecord
  belongs_to :location, class_name: 'Location', optional: true
  belongs_to :location2, class_name: 'Location', optional: true
  belongs_to :location3, class_name: 'Location', optional: true
  belongs_to :growth, class_name: 'Characteristic'
  belongs_to :normal_drop, class_name: 'Material', optional: true
  belongs_to :rare_drop, class_name: 'Material', optional: true
  # ... many more belongs_to macros ...
end
Next, we add another one-liner near the end of db/seeds.rb before Monster.create!(monster) so we can set the growth references correctly in the monster table during the database import:
  monster['growth'] = Characteristic.find_by(name: monster['growth'])
We're almost ready to rebuild the database, except for one catch. Not all of the growth values in the monster table exist in the characteristic table! If we do the import now, a bunch of the growth values are going to be nil. Don't worry, though. It's not too many values that we need to add to the db/monster_characteristics.csv file to make everything right, so we'll just do it manually:
name,description
Early Peaker,Reaches a maximum level of 20.
Well-Grown,Reaches a maximum level of 30-60.
Standard,Reaches a maximum level of 30-60.
Very Mercurial,Reaches a maximum level of 20-99. Best stat gains happen very early.
Mercurial,Reaches a maximum level of 70-99. Best stat gains happen early.
Late Bloomer,Reaches a maximum level of 70-99.
Very Late Bloomer,Reaches a maximum level of 70-99. Best stat gains happen late.
Erratic,Reaches a maximum level of 70-99. Best stat gains are random.
Very Erratic,Reaches a maximum level of 70-99. Stat gains are very erratic.
I guess the FAQ writers felt the need to add a few more growth categories, and this is my best guess at what they meant. Anyway, now we should be able to rebuild the database:
$ rails db:drop
$ rails db:migrate
$ rails db:seed
After correcting one misspelling of "Peaker" as "Peeker" for PuPu, we have the updated database with references from the monster table to the characteristic table.

Viewing Monster Tooltips

We're finally ready to add the growth tooltips to the monster table page. We already added the required javascript_pack_tag, but the code is fairly short so I'll just show the whole thing:
<h1>Monsters</h1>

<%= javascript_pack_tag 'monster_tooltips' %>

<table id="monster-table" class="table table-striped table-sm">
  <thead class="thead-dark">
    <tr>
      <th scope="col">Monster Name</th>
      <th scope="col">Role</th>
      <th scope="col">Location</th>
      <th scope="col">Tame Rate</th>
      <th scope="col">Max Level</th>
      <th scope="col">Base HP</th>
      <th scope="col">Base Strength</th>
      <th scope="col">Base Magic</th>
      <th scope="col">Speed</th>
      <th scope="col">Growth</th>
      <th scope="col">Immune</th>
      <th scope="col">Resistant</th>
      <th scope="col">Halved</th>
      <th scope="col">Weak</th>
    </tr>
  </thead>

  <% @monsters.each do |monster| %>
    <tr>
      <td><%= link_to monster.name, monster_path(monster) %></td>
      <td><%= monster.role %></td>
      <td>
        <%= link_to monster.location.name, location_index_path(:highlight => monster.location.name) %>
        <% if monster.location2 %>
          <%= link_to monster.location2.name, location_index_path(:highlight => monster.location2.name) %>
        <% end %>
        <% if monster.location3 %>
          <%= link_to monster.location3.name, location_index_path(:highlight => monster.location3.name) %>
        <% end %>
      </td>
      <td><%= monster.tame_rate %></td>
      <td><%= monster.max_level %></td>
      <td><%= monster.maximum_base_hp %></td>
      <td><%= monster.maximum_base_strength %></td>
      <td><%= monster.maximum_base_magic %></td>
      <td><%= monster.speed %></td>
      <td>
        <span data-toggle="tooltip" data-original-title="<%= monster.growth.description %>">
          <%= monster.growth.name %>
        </span>
      </td>
      <td><%= monster.immune %></td>
      <td><%= monster.resistant %></td>
      <td><%= monster.halved %></td>
      <td><%= monster.weak %></td>
    </tr>
  <% end %>
</table>
All we had to do was wrap a span tag around the growth name and add the growth description as the tooltip. We also needed to make sure to change monster.growth to monster.growth.name because monster.growth is now a reference to the element in the characteristic model. I should also note that in general, tooltips do not need to be attached to span tags. Almost any type of tag will do, although more complex tags that have other bootstrap features attached to them may misbehave. When that happens, wrapping a div or span tag around the element and attaching the tooltip to that tag instead will normally clear things up.

So now we have tooltips on growth categories:

Screenshot of monster table with tooltip on growth category

That's pretty slick. Now it's easy to add the same tooltips to the monster details page, and we want to go in there anyway to fix the monster.growth reference so let's change app/views/monster/show.html.erb:
<h1><%= @monster.name %></h1>

<%= javascript_pack_tag 'monster_tooltips' %>

<table id="monster-table" class="table table-striped table-sm">
  <tr>
    <td><strong>Role</strong></td>
    <td><%= @monster.role %></td>
    <td/>
    <td/>
  </tr>
  <tr>
    <td><strong>Monster Type</strong></td>
    <td><%= @monster.monster_type %></td>
    <td><strong>Constellation</strong></td>
    <td><%= @monster.constellation %></td>
  </tr>
  <tr>
    <td><strong>Location</strong></td>
    <td>
      <%= link_to @monster.location.name, location_index_path(:highlight => @monster.location.name) %>
      <% if @monster.location2 %>
        <br /><%= link_to @monster.location2.name, location_index_path(:highlight => @monster.location2.name) %>
      <% end %>
      <% if @monster.location3 %>
        <br /><%= link_to @monster.location3.name, location_index_path(:highlight => @monster.location3.name) %>
      <% end %>
    </td>
    <td><strong>Tame Rate</strong></td>
    <td><%= @monster.tame_rate %></td>
  </tr>
  <tr>
    <td><strong>Max Level</strong></td>
    <td><%= @monster.max_level %></td>
    <td><strong>Growth</strong></td>
    <td>
      <span data-toggle="tooltip" data-original-title="<%= monster.growth.description %>">
        <%= @monster.growth.name %>
      </span>
    </td>
  </tr>
  <!-- many more table rows -->
</table>
The changes are exactly the same as for the monster index view, and now we have tooltips on the details view as well:

Screenshot of monster detail view with growth tooltip

These growth tooltips are all well and good, but you may be asking yourself, "What about those other monster characteristics?" Well, as it turns out, the other characteristics don't appear anywhere else in our tables so there's not much we can do with them. We could add more attributes to the monster details page that call out these other characteristics, like flameproof or hearty, but that will be a lot of work adding them to every monster for not much gain. The rest of the characteristics table can be used mostly as a reference for looking up the descriptions for the in-game characteristics, should we need to. Most of the remaining ones are pretty self-explanatory anyway.


With that task done, we've connected the last table of data in our monster taming database, and we have a pretty fully-featured website for exploring this data. We're almost done with this series, so next time we'll wrap things up by actually using this data to answer the big question: how can I optimize the monster taming as I play through Final Fantasy XIII-2?

No comments:

Post a Comment