22 September, 2020

Flying Austro-Hugarian Colors

I didn't get much time to paint this past week so all I got done were the funnels and the white for the colors. I'm hoping to get to the red on them this weekend so I can start in on the last few turrets for the Epic tournament in Scotland next month.

Austro-Hungary Aeronef Fleet Austro-Hungarian Huszár class Destroyer Austro-Hungarian Budapest class Light Battleship

21 September, 2020

Lost Secret Of The Rainforest - Abducted!

Written by Reiko

Adam's Journal #1: "We're finally here in Peru! I'm so excited to be able to help my dad with his project of setting up sustainable industries for the native peoples here. I wonder what kinds of animals I'll get to see while we're here? The rainforest is full of so much variety. I can't wait to get started!"


This guy's totally shady.

Adam and his father Noah have just landed in Peru, but of course we have to go through customs before we can start exploring. In the introduction, I forgot to mention that the shady surveyor's character has a couple of actual voiced lines, which is an interesting contrast to most of the other dialogue, which is only text. He says, "This place is a sewer" and, after bumping into our ride, "Watch the suit!" before brushing himself off and stalking away to the right.

When it's Adam's turn, I open the passport in his inventory and show it to the customs officer [10 points], who stamps it, waves Adam through, and then promptly puts his head down on his desk and appears to take a nap. Nobody else is waiting in the customs line, after all.




Our ride is waiting for us.

Nearby, there's a native holding a sign saying Noah and Adam Greene, so he's clearly waiting for us. I talk to him [1] and he introduces himself to us as Nicanor, from the Ecology Emergency Network, the organization that Noah is working with. He says there's a problem with the supplies, and takes out a list, while Noah goes over to check what's there. This leaves Adam at loose ends.

I look around and find two women supposedly selling fruit pops at a stand nearby, but they say they're on break and rudely shoo Adam away. I can't go into the town yet, either, because Noah will call Adam back, but there's really nothing else in the area to do. It takes me an embarrassingly long time to realize that there's an exit off the right side of the screen (where the shady surveyor disappeared earlier).

In the second area, a grungy dock, after Adam walks in, I overhear a conversation between the shady surveyor and another guy, Gonzales. The surveyor orders him to get "the stuff" loaded so they can get out of there. Then he disappears onto the ship moored at the dock. There's also an old guy fishing on another pier near where I entered, and two other guys having a hushed conversation behind some crates. I also see a run-down warehouse and a large pile of logs from the rainforest, along with a flatbed truck loaded with more logs. I can't seem to get close enough to talk to the guy fishing, even though he doesn't appear to be all that far away.


Right, "fresh off the boat" is exactly what we don't want to be.

If I walk into the area far enough, another guy appears from the same direction I came from, and accosts me, carrying a bird on his arm. He offers to sell me the bird if I give him the money I have. Adam asks how much, and the guy asks how much money he has. Adam wisely says, "I don't think I should tell you that." The guy just shrugs and says I should give him the money I have if I want to buy the bird.

I have the option to refuse, but I decide to go ahead with it, and give the man my money [5]. When the man hands over the bird, Adam immediately releases it, telling it to fly home. The man is aghast, but Adam reminds him that he sold the bird. The man shrugs and wanders off, and that seems to be the end of it. Probably I will encounter the bird at some point later, though, and it will be grateful that Adam freed it.


Who's "Mr. Slaughter"? No one I want to meet!

I then discover almost by accident that I can climb up on the crates and eavesdrop on the nearby conversation. One guy seems to be trying to recruit the other one for Cibola, the company that the shady surveyor mentioned. They mentioned someone they call "Senor Slaughter," which may well refer to the surveyor guy himself.

The nearby ship, which reads "Cibol" (Cibola) has an arm with a rope net that is periodically raised and lowered. I can make Adam walk over onto the net, which causes him to get caught in it when it's raised next. The guy loading the ship gets us out, but tells us to beat it, or we'll get sent to "Mr. Slaughter". Guess he's the one to watch out for.


Here's a way to stuff educational content onto the screen without lecturing.

I don't see anything else to do here, so I go back to the original screen. Noah calls Adam over and says he found something mailed to him. I open the package [5] and find some kind of handheld computer which Noah explains is an environmental scanner prototype. It's called an Ecorder, and Adam's supposed to test it out. Noah suggests there's something about the launch I should scan. As part of his explanation, he already scanned the nearby canoe, so I'm not sure what he means by "launch".


Picking up someone else's garbage. It won't be the last time.


I also notice another shady guy lurking by the now-abandoned fruit pop stall, but I can do nothing with him. So I return to the dock to see if there's anything I can scan there, and find a tourist taking pictures. Even though he doesn't answer when I try talking to him [1], I get a point for some reason. Then I notice that he's dropped something. I try to pick it up, but Adam says he doesn't want that garbage, and I should bag it instead. I use the recycle icon on it [5] to get rid of it.

I go into the ecorder to see what all it does. The previously-scanned item was Town Runoff, about the garbage from the town that ends up in the rivers. I also try playing the "test myself" game, which randomly shows pictures from the database along with two choices. I get most of them right even though I haven't seen any of them in the game yet, and I get a bunch of points [46] when I finish the game.

I go back and forth again, and realize I can use a plank walkway to get to the end of the little pier where the man is fishing. I talk to him [1], and he talks briefly about how he doesn't like the way the fish look, and how there used to be so many more fish in the river years ago, before all the people came.


The scanned result of the ship's leak.

Finally I realize "launch" means the ship moored at the dock. The ecorder lights up when I move it over a hole with liquid spewing out of it, and records the "River Traffic" item [10]. Looks like the ship has a fuel leak, which is also contributing to river pollution.


I knew that other guy was shady too.

When I return to see if Noah and Nicanor are ready to go to town yet, suddenly the shady guy that had been lurking nearby runs over, grabs Noah's suitcase, and runs off toward town. Uh-oh, he's a thief!

Noah decides he's going to have to go to the embassy to get a passport, so Nicanor takes him there, leaving Adam to watch the supplies. (How old is he again?) Adam is tired from the trip, so he gets into the nearby canoe and falls asleep.


Adam's not being a very good guard for the supplies...

Suddenly, things get weird. Two creatures that look like otters, who address each other as Orpheus and Morpheus, appear in the water. One chews through the rope holding the canoe, and while Adam sleeps, together they push the boat away from the dock, along the river, and into the rainforest. Apparently Adam sleeps all night, since the screen darkens and then brightens again. At one point, a monkey appears and peers at the canoe with the sleeping child, then wanders off again. Finally, Adam wakes up, and the animals begin to address him directly.


Animals seem to know that Adam will help them.

Morpheus says that the Forest Heart needs help from a human child, and he looked like a good one, so they picked him. Orpheus has a gift to give us, but he's shy and needs persuading, so Adam needs to do something to coax him out.

First I scan the screen and find the Understory, Littering, Forest Floor, Stilt Root, and River Otter items [5]. So I was right about the creatures being otters. Then I talk to Orpheus [1]. Adam apologizes for being loud, and Orpheus swims a bit closer. I talk to him again [1], and Adam reassures him that he won't hurt him or anything.


The amulet brought by the otter.

Then Orpheus swims right up to the side of the canoe and waits for me to take the necklace around his neck, which I do [5]. It's a beautifully carved amulet, which the otters inform me is Forest Heart's amulet, and Adam needs to journey to her village to find out more. The otters disappear into the water after pushing the canoe the rest of the way over to solid land.

Now what? Well, the Littering item was clearly because there's quite a lot of junk scattered around. I trash five items [5] and also find a sticky leaf to take [5].

That's all for that screen, so I move to the right and immediately encounter the monkey I saw earlier, who accuses me of cutting down trees. Adam assures him that he's by himself and hasn't done anything to the trees, and asks about those who have. The monkey calls them yellow hats and says they turned his home and food into smoke. Then he angrily stalks away. Apparently the logging operation that generated all those logs I saw earlier has done some significant damage to the forest.


As hostile as this sounds, I'm glad the monkey didn't do anything to Adam while he was sleeping.

New scanned items: Buttress, Cecropia Tree, Logging [3]. The tree that fills most of the screen is huge, with twisty roots. Nearby there's a recent campfire still leaking smoke, with a small stump and a chopped up log. The huge tree looks very climbable, but insects swarm Adam when I make an attempt. I use the sticky, sap-covered leaf on Adam [5], which causes him to rub the sap all over himself, commenting that it's stinky, so the bugs should leave him alone. Now I can have him climb the tree to another screen above.

Next time we'll find out what's up there and how that gets us closer to the Forest Heart. Adam seems very calm at having woken up in a different place than he went to sleep in, and at basically having been abducted by otters and left to his own devices deep in the rainforest with no supplies. Maybe he figures he can always get animals to help him if he needs something. It wouldn't be so easy for the rest of us.

Score: 121/1000
Scanned items: 10/82
Inventory: passport, Ecorder, Forest Heart amulet, leaf with sticky sap

Session Time: 1 hour 15 minutes
Total Time: 1 hour 15 minutes

Note Regarding Spoilers and Companion Assist Points: There's a set of rules regarding spoilers and companion assist points. Please read it here before making any comments that could be considered a spoiler in any way. The short of it is that no points will be given for hints or spoilers given in advance of me requiring one. Please...try not to spoil any part of the game for me...unless I really obviously need the help...or I specifically request assistance. In this instance, I've not made any requests for assistance. Thanks!

12 September, 2020

Frictional Fan Jam: Winter Modding Event

Screenshot from Draugemalf's SOMA Winter Asset Pack.

Quick overview

Duration: 11th of December until 19th January 2020
Theme: Winter/Hibernation
Medium: HPL engine modding

Info

Winter is coming for us in the Northern Hemisphere. Get a hot drink, curl up under a blanket, and start up the HPL level editor – it's time for Frictional's Winter Modding Jam!

Join us for a month-long event focused on HPL modding! You're welcome to participate alone or in a small team (up to 5 people). For peer support, head on over to our Discord server.

Are you an artist, writer, or other kind of creator? You can still participate by teaming up with one of the modders. Head on over to #winter_modding_jam on our Discord and find your team! We will also be holding specialised events for you in the future.


Duration

The Jam will start on the 11th of December 2019 and last until the 19th of January 2020.

You can submit your work on the night of Sunday the 19th, as the submissions will be checked on Monday morning.


Theme

Winter and/or Hibernation.

One or both themes should be present in the Fan Jam entry. You are welcome to interpret them however you wish. The mods don't have to have a connection to Frictional Games titles.


Medium

This Frictional Fan Jam is specifically for HPL modding. You are free to use HPL2 and HPL3, or even HPL1 if you're brave enough.

Aside from the game assets, you are also welcome to utilise other assets you can legally use, or have permission to use from the creators.

You can for example use the Winter Asset Pack for SOMA, made by a long-time contributor and one of our Discord moderators, Draugemalf.



Submitting your work

Upload your mod on a platform like ModDB or Steam Workshop.

For entering the event, please submit a link to your work through the following form:

If you worked as a team, have one team member submit the entry.

All works will be showcased on the #winter_modding_jam_showcase on Discord.


Prizes

The jury of Frictional Games employees will pick the winners of the jam. Discord moderator team will not be voting on entries, and are therefore allowed to fully participate in the event.

The winners will receive A5-sized posters of a game of their choosing sent to their home address (team members will be sent theirs separately). The Frictional team from the Malmö office can sign them if you wish. Once our next project is out, the winners will also receive a download key for the game on an available platform of their choosing.

Depending on the amount of entries, the Malmö office Frictional team will stream all of the entries, or only the winners.


Contact

The Fan Jam is organised by Frictional Games' community manager Kira, with support from the Frictional Games Discord moderators. The easiest way to contact either is through the Frictional Games Discord server's #winter_modding_jam channel. The channel can also be used to share ideas with other community members, get feedback, and look for team members.

If you don't have a Discord account, you are welcome to contact Frictional Games through Twitter or our Contact Form, and we will help you as soon as we can.

For general questions: Contact Kira, for example by pinging them on the Discord channel.

For technical questions: Join our Discord server which has an active modding community.



Have fun, we're looking forward to your wintery creations!

Free Fire 1.39.0 APK+OBB Download

Free Fire 1.39.0 APK+OBB



===============================================

How To Install Free Fire 1.39.0 APK+OBB without Errors and Problems





===============================================

Screenshots




  


 Free Fire 1.39.0 APK+OBB :- 
----------------------------------------------------------------------

THANK YOU SO MUCH FOR VISITING OUR SITE.

04 September, 2020

Exploring Monster Taming Mechanics In Final Fantasy XIII-2: The Remaining Tables

Continuing this miniseries of exploring the monster taming mechanics of Final Fantasy XIII-2, we'll finish off the remaining database tables that we want for the data relevant to monster taming. In the last article, we filled in a second table of passive abilities and connected those abilities to the monsters that had them through references in the monster table that used foreign keys to the ability table. In the first article, we had identified four tables besides the monster table that we would need as well, these being abilities, game areas, monster materials, and monster characteristics. We did the passive abilities table, but we still need a table for role abilities. In addition to this role ability table, we'll finish off the game areas, monster materials, and monster characteristics tables. These tables are all small, so we should be able to get through them without much effort.

Final Fantasy XIII-2 Battle Scene

The Monster Role Abilities

Unlike the passive monster abilities, of which there were 218, we only have 57 role abilities to worry about, and they can be found in this FAQ from Krystal109 on GameFAQs.com. Since these abilities are already in two HTML tables, it's easy to copy-and-paste them directly into a spreadsheet and tweak it the way we want it. I used Google sheets, and removed the "Best Infusion Source," "Learned by," and "Comments" columns because we don't need them. Then I split the merged cells in the "Role" column and copied the role names as necessary. Finally, I added a column for whether the ability is infusable or not, depending on which of the two tables it came from, before exporting the sheet to a .csv file.

But wait! There are actually 70 infusable role abilities in the Monster Infusion FAQ that we've been using, plus the non-infusable role abilities from the second HTML table, so we're missing a few. We are dealing with the common occurrence of incomplete data here. I had to cross-check the lists and add in the missing role abilities from the Monster Infusion FAQ. I ended up with 87 abilities in the end. This manual process is still probably faster than writing a script, but we're not sure, yet, if we have all of the non-infusable role abilities. We'll find out in a minute when we try to link up this data with the monster table.

Before we can add these associations, we have to generate a model of the role abilities table in Rails, like so:
$ rails g model RoleAbility name:string role:string infusable:boolean
This creates a migration that's all ready to run:
class CreateRoleAbilities < ActiveRecord::Migration[6.0]
def change
create_table :role_abilities do |t|
t.string :name
t.string :role
t.boolean :infusable

t.timestamps
end
end
end
Now we can add the import of the role abilities to the seeds.rb script right after the passive abilities import:
# passive abilities import from monster_abilities.csv
# ...

csv_file_path = 'db/monster_role_abilities.csv'

CSV.foreach(csv_file_path, {headers: true}) do |row|
role_ability = row.to_hash
role_ability['infusable'] = (role_ability['infusable'] == 'true')
RoleAbility.create!(role_ability)
puts "#{row['name']} added!"
end

# monster data import from monsters.csv
# ...
This import looks almost exactly like the passive abilities import, except that we need to convert the 'true' and 'false' strings for the infusable attribute into boolean values to match that attribute's data type. Once we have the role abilities imported and available, we need to add the associations to the monster table. We do this in a very similar way to how we did the passive ability associations, but this time it's simpler because we don't have to worry about red-locked abilities with the same base name as other abilities or ranks that complicated the passive abilities. We just have to find each role ability in each monster's set of skills and associate them by assigning the role ability to the right skill, like so:
CSV.foreach(csv_file_path, {headers: true}) do |row|
monster = row.to_hash

# ... associate passive abilities ...

monster.keys.select { |key| key.include? '_skill' }.each do |key|
if monster[key]
monster[key] = RoleAbility.find_by(name: monster[key])
if monster[key].nil?
puts "ERROR: monster #{monster['name']} #{key} not found!"
return
end
puts "Found #{key} #{monster[key].name}"
end
end

Monster.create!(monster)
puts "#{row['name']} added!"
end
Now, we can't run this seed script quite yet. We still don't have a database migration or the monster and role ability models set up to handle these associations, so let's do that tedious work. First, the monster table migration needs all of the skill attributes changed from t.string to t.references and add_foreign_key statements need to be added for each skill. There are about a hundred of these, so I'll just show an example of what they look like:
class CreateMonsters < ActiveRecord::Migration[6.0]
def change
create_table :monsters do |t|
# ... all of the other monster attributes ...
t.references :default_skill1
t.references :default_skill2
t.references :default_skill3
t.references :default_skill4
t.references :default_skill5
t.references :default_skill6
t.references :default_skill7
t.references :default_skill8
t.references :lv_02_skill
t.references :lv_03_skill
# ... etc ...
end

# ... passive ability foreign keys ...

add_foreign_key :monsters, :role_abilities, column: :default_skill1_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill2_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill3_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill4_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill5_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill6_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill7_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :default_skill8_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :lv_02_skill_id, primary_key: :id
add_foreign_key :monsters, :role_abilities, column: :lv_03_skill_id, primary_key: :id
# ... etc ...
end
end
With that done, we still need to add the role abilities to the monster model in app/models/monster.rb with belongs_to. Remember how it felt weird that we were saying a monster belongs to its abilities in the last article? Well, that's still the direction we want, so we'll do it again with role abilities, even though it sounds weird:
class Monster < ApplicationRecord
# ... passive ability belongs_to declarations

belongs_to :default_skill1, class_name: 'RoleAbility', optional: true
belongs_to :default_skill2, class_name: 'RoleAbility', optional: true
belongs_to :default_skill3, class_name: 'RoleAbility', optional: true
belongs_to :default_skill4, class_name: 'RoleAbility', optional: true
belongs_to :default_skill5, class_name: 'RoleAbility', optional: true
belongs_to :default_skill6, class_name: 'RoleAbility', optional: true
belongs_to :default_skill7, class_name: 'RoleAbility', optional: true
belongs_to :default_skill8, class_name: 'RoleAbility', optional: true
belongs_to :lv_02_skill, class_name: 'RoleAbility', optional: true
belongs_to :lv_03_skill, class_name: 'RoleAbility', optional: true
# ... etc ...
end
Finally, we need to add the has_many declarations to the role ability model (because each ability has many monsters, right?):
class RoleAbility < ApplicationRecord
has_many :default_skill1_monsters, :class_name => 'Monster', :foreign_key => 'default_skill1'
has_many :default_skill2_monsters, :class_name => 'Monster', :foreign_key => 'default_skill2'
has_many :default_skill3_monsters, :class_name => 'Monster', :foreign_key => 'default_skill3'
has_many :default_skill4_monsters, :class_name => 'Monster', :foreign_key => 'default_skill4'
has_many :default_skill5_monsters, :class_name => 'Monster', :foreign_key => 'default_skill5'
has_many :default_skill6_monsters, :class_name => 'Monster', :foreign_key => 'default_skill6'
has_many :default_skill7_monsters, :class_name => 'Monster', :foreign_key => 'default_skill7'
has_many :default_skill8_monsters, :class_name => 'Monster', :foreign_key => 'default_skill8'
has_many :lv_02_skill_monsters, :class_name => 'Monster', :foreign_key => 'lv_02_skill'
has_many :lv_03_skill_monsters, :class_name => 'Monster', :foreign_key => 'lv_03_skill'
# ... etc ...
end
Whew! We're finally ready to purge the database, run the migration again, and reseed the database, but remember to rename the monster table migration so that the timestamp in the filename is after the new role ability migration because the monster table migration needs to run last:
$ mv <old monster migration name> <new monster migration name>
$ rails db:purge
$ rails db:migrate
$ rails db:seed
After running these commands, we find that a bunch of non-infusable role abilities were missing from the list. Most of these were basic Commando commands like Attack, Ruin, Launch, and Blitz, or variations on Saboteur commands like Dispel II or Heavy Dispelga. When all missing abilities are found and fixed, we should have 127 role abilities. Then there are two typos in the Monster Infusion FAQ monster list that need to be fixed as well: an instance of "Deprotectga" instead of "Deprotega" and an instance of "Medigaurd" instead of "Mediguard." With that task done, we can call the role ability table complete. There are six hidden role abilities, one for each role type, but they are never referenced in the monster attributes because they are fixed by the role type of the monster. By infusing 99 levels worth of monsters of the opposite role type, (e.g. commando and ravager are opposites) the monster will acquire its hidden role ability. It's questionable whether adding them to the database is useful, so let's move on to game areas.

Making a Game Location Graph

You start the game in New Bodhum 003 AF. Actually, you start in Valhalla, but after getting through the intro segment with Lightning and a bunch of over-the-top cut scenes, the game really starts in New Bodhum with Serah. As you progress through the game, you unlock more and more new areas by jumping through time gates. Some locations have multiple time gates that lead to multiple new locations. Different monsters live in different locations, and which locations they live in is noted as one or more of the monsters' location attributes.

We want to capture the graph of these location dependencies in a database table and link the locations to the monster attributes so that we can figure out the earliest possible times in the game that we can acquire monsters with certain abilities. In order to do that, we're going to build a table to represent that graph. We could build this table in a couple of ways. One way is to make an adjacency matrix, but this representation requires a row and column for each node in the graph. In this case such a matrix would be sparsely populated, so we're going to use an adjacency list instead. Each row in the table will have one location's name, and which location leads to this location. Since each location has only one other location as a source, we can get away with this simple table structure. There are only a small number of locations, so the .csv file for this table can be created by hand. Plus, I couldn't find a good list on the internet, so here it is, created from scratch:
name,source
New Bodhum 003 AF,nil
Bresha Ruins 005 AF,New Bodhum 003 AF
Bresha Ruins 300 AF,Bresha Ruins 005 AF
Yaschas Massif 110 AF,Bresha Ruins 300 AF
Yaschas Massif 010 AF,Bresha Ruins 005 AF
Oerba 200 AF,Yaschas Massif 010 AF
Yaschas Massif 01X AF,Oerba 200 AF
Augusta Tower 300 AF,Yaschas Massif 01X AF
Serendipity Year Unknown,Yaschas Massif 01X AF
Sunleth Waterscape 300 AF,Bresha Ruins 005 AF
Coliseum ??? AF,Sunleth Waterscape 300 AF
Archylte Steppe ??? AF,Sunleth Waterscape 300 AF
Vile Peaks 200 AF,Archylte Steppe ??? AF
Academia 400 AF,Sunleth Waterscape 300 AF
Yaschas Massif 100 AF,Academia 400 AF
Sunleth Waterscape 400 AF,Yaschas Massif 100 AF
Augusta Tower 200 AF,Academia 400 AF
Oerba 300 AF,Augusta Tower 200 AF
Oerba 400 AF,Oerba 300 AF
Academia 4XX AF,Augusta Tower 200 AF
The Void Beyond ??? AF,Academia 4XX AF
Vile Peaks 010 AF,Academia 4XX AF
A Dying World 700 AF,Academia 4XX AF
Bresha Ruins 100 AF,A Dying World 700 AF
New Bodhum 700 AF,A Dying World 700 AF
Academia 500 AF,New Bodhum 700 AF
Valhalla ??? AF,Academia 500 AF
Coliseum ??? AF (DLC),New Bodhum 003 AF
Valhalla ??? AF (DLC),New Bodhum 003 AF
Serendipity ??? AF (DLC),New Bodhum 003 AF
As we've already done with the other tables, the first thing we need to do to import this .csv file is generate the model for this location table:
$ rails g model Location name:string source:references
Notice that we created the source attribute as a reference right away, and this reference that's being created is different than the others that we created for passive and role abilities. Whereas those references were associated with new tables, this source reference is associated with the location table itself. To construct this self-reference, we don't actually have to do anything to the migration. Rails does the right thing by default, so the migration is simple:
class CreateLocations < ActiveRecord::Migration[6.0]
def change
create_table :locations do |t|
t.string :name
t.references :source

t.timestamps
end
end
end
We also want to add references to the location attributes in the monster table, so we need to change that migration as well:
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}
# ... the mess of other attribute declarations ...
end

# ... a mess of foreign key declarations ...
end
end
Notice that for these location references, the foreign key is specified directly in the attribute declaration. I recently noticed that this was possible, and it's much cleaner than adding foreign keys at the end, so I'm switching to doing it this way.

The next step is to update the seeds.rb script for the location and monster models. Let's do the models first this time. The location model will need a self-reference for the source attribute and declare that it has many monsters for the location attributes in the monster model, like so:
class Location < ApplicationRecord
belongs_to :source, class_name: 'Location', optional: true
has_many :location_monsters, :class_name => 'Monster', :foreign_key => 'location'
has_many :location2_monsters, :class_name => 'Monster', :foreign_key => 'location2'
has_many :location3_monsters, :class_name => 'Monster', :foreign_key => 'location3'
end
The self-reference doesn't need to declare its foreign key because it's the same name as the name of the reference. Rails can infer the foreign key correctly in this case. Now for the monster model:
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
# ... a mess of other reference declarations ...
end
It makes a little more sense that a monster belongs to a location, so that's nice, but it's really just words. It's the direction of the associations that's important, and belongs_to means the reference is in the current model and points to the Location model, which is what we want. Finally, we can update the seeds.rb script to import locations. This update happens in two parts. First, we need to read from the .csv file we created:
# ... role abilities import from monster_role_abilities.csv ...

csv_file_path = 'db/monster_locations.csv'

CSV.foreach(csv_file_path, {headers: true}) do |row|
location = row.to_hash
location['source'] = Location.find_by(name: location['source'])
if location['source'].nil? && location['name'] != 'New Bodhum 003 AF'
puts "ERROR: location #{location['source']} not found!"
return
end
Location.create!(location)
puts "Location #{location['name']} added!"
end

# ... monster data import from monsters.csv ...
For this import, we're not only populating the table, but we have to correctly assign the self-references. To do that assignment, we had to make sure when writing the .csv file that every location that was referenced in a source attribute appeared before that reference as a named location. That way, when this script looks for a location in the source attribute, it will find it already exists as a location in the table. If there were loops, this import would require two passes to find all of the location names first before assigning references, but there are no loops so lucky us. I also check that each source location is actually found, in case I made any typos, but there is one intentional nil source for where you start the game in New Bodhum 003 AF.  That particular nil source needs to be handled specially. With the location table filled in, we can associate the location attributes in the monster table:
CSV.foreach(csv_file_path, {headers: true}) do |row|
monster = row.to_hash

# ... associate role abilities ...

monster.keys.select { |key| key.starts_with? 'location' }.each do |key|
if monster[key]
monster[key] = Location.find_by(name: monster[key])
if monster[key].nil?
puts "ERROR: monster #{monster['name']} #{key} not found!"
return
end
puts "Found #{key} '#{monster[key].name}'"
end
end

Monster.create!(monster)
puts "#{row['name']} added!"
end
This part of the script is exactly the same as it was for role abilities, just doing it for the location attributes instead. Now we can run this script again, making sure that the monster migration is still the most recent migration since it needs to be performed last:
$ mv <old monster migration name> <new monster migration name>
$ rails db:purge
$ rails db:migrate
$ rails db:seed
After running this script, I found a number of additional typos in the FAQ, so that data validation code is really coming in handy. I also caught an unexpected shortcut that the FAQ author took with locations. If a monster appears in two similar locations, they combined the years with a slash, e.g. "Bresha Ruins 100/300 AF." I had to split these out into two separate lines to adhere to our data model. Once that shortcut and the typos were fixed, the script ran to completion, and that's it for locations.

Wrapping up Monster Materials and Characteristics

This article is getting pretty long, so I'm going to finish off the monster materials and characteristics tables quickly. The characteristics is a straightforward table that doesn't require figuring out anything new. The materials table can also be straightforward, at least for now. The monster materials themselves constitute a simple table with a name, a grade of 1-5, and a type of either biological or mechanical. The trick is that we'll eventually want to know when in the game we can get each grade of monster material because they're used to upgrade our tamed monsters. The way to acquire those materials is by defeating other monsters in battle. The different materials are some of the loot that the monsters drop. This information is all in the Monster Infusion FAQ, but it would require another parser to extract it and import it into the monster table. We can do that later in the series, but right now I want to complete the tables and get on with starting to view the data so we'll just go with making the simple material table.

A table of the materials can be found in the same HTML FAQ from Krystal109. Like the first set of role abilities, we can copy this table into a spreadsheet and then export it to a .csv file. Then we run through the same steps of importing this data into our Rails database, starting with generating a material model:
$ rails g model Material name:string grade:integer material_type:string
The migration is created without need for modification, and right now, this will be a stand-alone table so there's no need to change any model code. All that's left is to import the .csv file in the seeds.rb script:
# ... location import from monster_locations.csv ...

csv_file_path = 'db/monster_materials.csv'

CSV.foreach(csv_file_path, {headers: true}) do |row|
Material.create!(row.to_hash)
puts "Material #{row['name']} added!"
end

# ... monster data import from monsters.csv ...
Then we can do the same thing with the monster characteristics table also found in the HTML FAQ from Krystal109. Do the same drill of copying the table into a spreadsheet and then exporting it to a .csv file before generating another model for this table:
$ rails g model Characteristic name:string description:string
Finally, add this data import to the seeds.rb script:
# ... material import from monster_materials.csv ...

csv_file_path = 'db/monster_characteristics.csv'

CSV.foreach(csv_file_path, {headers: true}) do |row|
Characteristic.create!(row.to_hash)
puts "Characteristic #{row['name']} added!"
end

# ... monster data import from monsters.csv ...
And we're ready to run the migration and import:
$ rails db:purge
$ rails db:migrate
$ rails db:seed
The purge is done first to clean out everything that we already had in the database. Otherwise, when we run it again now, we'll be duplicating all of the other data that we already imported in the seeds.rb script on previous runs. Running this sequence of commands will build a fresh database from scratch, and it should finish cleanly in one shot.

That last step will wrap up the database design and building, for now. We'll have a bit of work to do later when we want to connect the monster materials to the monsters that drop them, but this is good enough to get started with creating views of this data. After spending multiple articles parsing, exporting, importing, and creating data for the database, I'm anxious to get moving on those views, so that's what we'll start looking at next time.