Wednesday, January 27, 2021

The Flight of the Flock - alternates

 Yo.


Here are the like 28 versions of the title/background for my painting:































And here are some stages of the painting.












Tuesday, August 20, 2019

How To Use Blendo's Discord Changelog Bot With BitBucket!

Hello.

So I read this:

http://blendogames.com/news/post/2019-07-29-discord-changelog/

(Which is by the guy who made Quadrilateral Cowboy and 30 Flights of Lovin' and other games. He's also a cool guy that I've met in Los Angeles! We've played board games together. He gave our team a bad clue that involved a computer mouse or something iirc and then he took it away, but we all insisted it meant something, but he wasn't allowed to talk to us, and so this led to our failing to put the murderer(s) to death or something. What does that say about 2019??)

The post I linked to above is about how to automate posting commit notes to one's Discord server. Brendon casually tosses in the fact that this could easily be integrated into other version control frameworks or whatever. Well, for me it was a great struggle. But here's what I ended up doing:

1. I downloaded Brendon's source code.

2. I read Brendon's readme.txt and obeyed the part about copy/pasting my Discord webhook URL into the settings.txt file.

3. (THIS IS WHERE I HAD TO START THINKING FOR MYSELF AND THIS IS WHAT I FOUND THAT WORKED:) In my repository for Flock of Dogs, I created a folder 'hooks':

C:\Users\maxac\Documents\flockofdogs\.git\hooks

4. If you have a file named 'commit-msg' inside a folder titled 'hooks' inside a folder titled '.git' inside your repository, then when you call 'git commit' either from the terminal or from SourceTree, git will try to execute that file. So I created a file named 'commit-msg' (yes, it is extensionless!):

C:\Users\maxac\Documents\flockofdogs\.git\hooks\commit-msg

5. In that extensionless, empty file, I wrote the following shell script that calls Brendon's changelogbot.exe. Since it's a shell script, it won't just run in Windows, but I guess however git does things, it has access to Linux stuff, so it works. At least for me. At least on my computer. Today. Here's its contents:





(From inspecting Brendon's source code, he expects at least 4 parameters. The 3rd is a string that is the file name of the file that has the commit message. The 4th is a string to append to the Discord message title after "REVISION"...the the first two arguments he didn't use, so I spoofed them with two '0's. Also, because he expected to use a file with the commit message, that's why I had to pipe the commit message into a new text document, 'commit_message.txt', which I  delete after I'm done with running the changelogbot.)

*********************************************************************
*********************************************************************
*********************************************************************

And voila. This took me several hours. I will talk about the wrong paths I went down in another post, because idk why, except whenever I find cut, little scripts on the internet that do magical things, I always assume the creator did it in like 5 minutes and, at the risk of betraying my own incompetence, I want the world to know my pain. And yes, I consider my 'commit-msg' a cute, little script.

JOIN MY DISCORD
TALK OF FLOCK OF DOGS
TO SEE THIS BABIES IN ACTION


Self Equip With A Quick .zip Tip!!!!!!!






I'm going to tell you a tale of a man and his zipper that wouldn't zip.


(the last [written] blog post was about how I automated building with a unity editor script. then i ran into a problem with zipping the folders. and now.....)

A  few days ago, I was like Oooh. I bet I could write a batch script for zipping my files in like 20 minutes. That'll be cool. SPOILER ALERT I DIDNT END UP USING A BATCH SCRIPT AND YOU KNOW THAT IF U READ THE PREVIOUS BLOG POST BC I CLEARLY CALLED A ZIP FUNCTION FROM WITHIN A CSHARP SCRIPT BUT I WANT TO TELL THIS STORY: Bascially, I wanted to do two commands. (a) Remove old things from folder. (b) Zip up new things into folder. The first command was a lot like this line:
move Dropbox\Flock of Dogs\* Documents\FoDBuildStorage\RemovedFromDropbox
This command says move all files found at Dropbox\Flock of Dogs\ to the folder Documents\FoDBuildStorage\RemovedFromDropbox. Which works and is fine. Then I wanted there to exist some command like (this is my imaginary command below).
zip Documents\FoD Build Storage\CompletedBuilds\* Dropbox\Flock of Dogs
Then later I'd just figure out how to start this batch script from a c# script and then I could do all this zipping from an editor command inside Unity! I figured there'd be some easily accesible cmdlet or whatever, because it's so easy and accessible to just right click a file/folder in Explorer and click SendTo->Compressed Folder. But apparently, there's no way to do that through a normal batch script.

Turns out tho, that with the power of powershell (!), one can summon the powers of the native Windows compression (Although evne this wasn't possible until like 2013 or something when you had to use visual basic scrip or something or the GStream and idk what else, but 2013 has dfeinitely come and gone and so I had to decide if I wanted to learn powershell scripting.)

I briefly considered using 7-zip, which actually compresses things way better normally (this all made me feel very Pied Pipery). Trouble with 7-zip is that the end user would have to have it installed to unzip any FlockOfDogs_1.0_win.7z file.

Turns out extra tho, that executing powershell scripts requires certain authorization. 

Internet forums msotly suggested disabling the requirement for authorization, which sounds like a sweeping, system wide change that could be very scary adn open my system up to vulnerabilities maybe that I know nothing about how to fix. So somebody said well, you don't have to turn it off universally, just when you call the powershll script, use this little ByPass flag, which lets you ByPass the ExecutionPolicy:
powershell -ExecutionPolicy ByPass -File MaxsPowershellScript.ps1
Which when I typed this into the command line, it worked. Which is what I did. But then I don't understand why somebody else's command line argument couldn't start a powershell script the same way? (Idk. more on that later.) Well, if they can, they can. And I am power(shell)less to stop them.

So now I could can run my powershell script, which is where I can properly zip things up. I'd never written a powershell script before, so that was kinda fun and went pretty well. A small issue was that to zip up the 3 newly made builds, my script had to identify them, or it would just act on every file within a folder that I, thru some other means, guarantee only has the newest builds in it, so I don't have to zip up the dozens of completed builds I have saved locally. So either I iterate thru all files in my completed builds folder and then zipping them up, using a destination name which matched their current file name (+ .zip on the end), or I needed a way to just identify the newly build builds. But what if my completed builds folder had more than the most recent builds in it? Do I want to always make sure that folder is clear too? How about, I just pass the dateStamp as an argument to identify the builds I want to zip up? So that meant learning how to pass a variable to a batch script and referencing it. Which for powershll meant using $ signs. (Batch scripts use %s).

Ok, so how do I run this from a c# script? Some googling taught me about c# being able to run processes thru Process.Start ()! You pass it the thing you want to run, in my case, cmd.exe (the command line!!!!). So if I can run the command line, I think I'm golden. And maybe I was. But it didn't work. And it kept not working. And then

i think the reason the process failed


C# Zip Failure And Then Success

So I read on a forum that years ago, c# didn't have a compression library. But with .NET 4.x framework, it does! It has System.IO.Compression.ZipFile (). And that function requires using the assembly System.IO.Compression.FileSystem.dll.

So I checked in Unity what .NET framekwork I was using (pretty sure it wasn't .net 4.x) and sure enough I had to update. So I cliked File->Build. Clicked Player Settings (bot left, see photo). Then in the "Other Settings", selected the .net 4.x options for script runtime version and api compatibility. It may have told me to restart. I don't remember.




So. Then, super excitedly, I typed
using System.IO.Compression;
using System.IO.Compression.FileSystem;
But disappointment. Red squiggly lines abounded. Online forums said you had to "add a reference" from insude Visual Studio.

So I clicked Project->Add Reference and selected System.IO.Compression and clicked ok.



NOTICE HOW THAT LITTLE WHITE BOX ON THE LEFT IS A BOX WITHOUT A CHECKMARK? Well, I opened this dialog box and clicked Ok and nothing happened like 4 times before I realized I had to check that little white box. This is a full confessional of my development process here. So after I checked that box. And the box for System.IO.Compression.FileSystem. Then Visual Studio was so happy!

Then I opened Unity. And Unity was not happy.

Unity was unhappy. Unity said these assemblies didn't exist. Watched some YouTube videos on how to make your own dll and add it to Unity. They just dragged and dropped it into their assets folder! I was like No Way. So then I was like...where's System.IO.Compression.dll on my system I have no idea!

Turns out that it was in the GAC (general assembly cache)! The internet eventually told me that is located at:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.IO.Compression
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.IO.Compression.FileSystem
And I copy/pasted them into my assets folder and voila.

It worked. Unity, VisualStudio, and me...all happy. But I shouldn't have been one hundred percent happy. You should never be 100% happy.

So I went to the OC Indies Meetup last night.

https://www.meetup.com/OC-Indie-Developers-Meetup/

And I told the guys there about my drag and drop solution to adding .net assemblies and they were horrified. Well, not really horrified, but I could read between the lines. They lost all respect for me as a programmer.

Anyway, Tony, a fearless leader of our Meetup, found this thread:

https://forum.unity.com/threads/c-compression-zip-missing.577492/

Wherein the sad truth is revealed that through some bug, these particular assemblies fail to link properly or whatever. And if you just copy/paste them in, while it may work in the short term, it's liable to failing later with new versios of Unity. So...I'm pretty sure I'm getting close to the last version of Unity that I will use for Flock of Dogs, before having to risk breaking things when switching versions, but I'll probably go thru the Unity 2019 versions. So anyway. The thread has a simple solution.

1) Create a file and add the following lines:
-r:System.IO.Compression.dll
-r:System.IO.Compression.FileSystem.dll
2) Save this file as mcs.rsp

3) Step 2 was a trick, if you're using Unity 2018.3, you actually have to name the file csc.rsp.

4) Move the file into your Asset/ directory for your project.

5) What if I upgrade to later versions of Unity, will I have to change the file name back to msc.rsp? Did the Unity guy in the thread mean "for Unity 2018.3 and later"? I don't know.


There you go.

You have been qiuckly equipped with a hot .zip tip.


Thursday, June 27, 2019

A Unity Editor Script To Automate Building To Windows, Mac, And Linux, And Then Zips Them Up

I set out to write a scipt that would allow me, with one click, to walk away from my computer and it would build my Unity project for Windows, Mac, and Linux, zip up the folders, and put them in my Dropbox folder that I will share with my secret play testers. I have done so.

I consulted the following blogs for an example of setting the BuildOptions and Player

https://www.blog.radiator.debacle.us/2015/09/scripting-unity-editor-to-automatically.html

and

https://www.gamasutra.com/blogs/EnriqueJGil/20160808/278440/Unity_Builds_Scripting_Basic_and_advanced_possibilities.php

My script in pseudo code:

[menu button attribute marker]
void BuildFlockOfDogsAndZipItUp ()
{
     DoBuild ("Windows");
     DoBuild ("Mac");
     DoBuild ("Linux");
     ClearFoldersFromDropboxFolder ();
     ZipNewBuildsAndMoveThemToDropboxFolder ();
}

I'll present the guts, then, if you're particularly interested, I'll talk through the journey!


NOTE: WARNING: LOOK AT THIS: If you're not used to writing editor scripts, make sure your script is in a folder in your Assets that is titled Editor.








Starting with the import directives / whatever they're called:


Next, basically, a translation of my pseudo code into C# syntax:


Let's go in order! What does FormatDate() do, you think? It formats the date the way I like it. (It's important that I save the dateStamp string so that I can successfully identify the build folders later when I'm zipping them up.) So here it is:



Next, the BuildPlayer() function is basically mimicking the things you have to/can do when you click File->Build from inside the Unity editor: And this is the function that takes forever, because it's where Unity actually builds the project. Each time it switches the active build target, it takes a while, and each time it actually builds the thing, it takes a while. So as you might have guessed, debugging this function is delicate and can take a while. So edit it with care: 



It's a cinch! So, next (if you scroll up and look back at the pseudo code), I want to move anything that's in my Dropbox folder out of the Dropbox folder:



And finally, the zipping function

fdsad fasf asd asf t wadg fsf aw
(THE SECRET TO THE ZIPPING WILL BE COVERED IN THE NEXT BLOG BC IT WAS TRICKY-WICKY-DO. THIS WON'T JUST work FOR MANY USERS)

dfskjl kj; joi jlkj poj 'lkj 'lkj' pj'oj 'lj 'j



Voila.

And for reference, the whole shebang, BuildFlockOfDogs.cs:


*******************************************************
**********************************************
***************************
********
***
*
***
*********
****************
**********************
******************
*********************
****************
***********
****
**********
************************************
**************************************************************
***********
******************************************
***
*

**
***

*
***************
********


Anyway. If you want to now hear about my struggle with zipping, read the next post!









  • [BONUS: QUICK SNIP TIP: (Secret: I'm just using the Windows snipping tool to get these code snippets and then dropping them in as .pngs instead of doing all the super tedious work arounds for formatting/embedding code snippets and sometimes i like to turn on all the formatting options and it's also like when you read something with strikethrough on, you feel like you found a secret maybe and if the feeling of finding a secret is different than actually finding a secret then i don't know what is.)][]}}}{}!!!!!!!!!!!!!!!!!!!!

Thursday, June 20, 2019

Maps of Flap of Daps

So a while ago I had procdeural level generation in Flock of Dogs. This was before I added networking and before I decided that I would draw tiles of a certain, very large size. Now I've got procedural level generation back in the game working with both systems. I'm using the same algorithm I used from before (and I don't remember where I found it). There's a million tutorials out there for random level generation now, and this one's fairly simple, not innovative, but it's effective and works for Flock of Dogs. Here's an outline of how it works:

1) Creates a 2-dimensional array

int width = 12;
int height = 14;
int[,] map = new int[width, height];


2) It iterates thru all elements and randomly assigns it a 0 or a 1, representing a tile that is clear and a tile that is a wall, respectively. At this point, you'd just get a map full of noise. Ther's one other condition: if the element represents an edge tile, (i.e. take an element map[x,y] when x or y is 0 or when x is equal to width - 1 or y is equal to height - 1), it will always fill it with a 1. This guarantees a solid border.

3) Then, it iterates through the whole map again. Starting with the first tile that is clear, it declares a new Room. A room is a collection of tiles that are touching and are clear. To define a new room, you recursively check each adajcent clear tile for their adjacent clear tiles, adding all of them to the same room. For this algorithm, diagonal tiles are not considered adjacent. After the first open tile has been processed and turned into a room, the algorithm continues on through the tiles, looking for more open tiles. When it finds the next one, before it creates a second room, now it has to check if that tile is part of an existing room. If it isn't part of a room, then it can create a new room.

4) After all rooms have been found, which, by the above method of creation are not connected to each other, it then proceeds to connect them all! It defines the first room as the Main Room. Then it uses a straight line drawing algorithm to set a path of tile spots map[x,y] to 0, for some collection of x's and y's that result in a straight-ish line (can look kinda like stairs, if it's diagonal) from the center of the Main Room to the next room. It then marks that next room as connectedToMain = true. I forgot to mention that a Room is defined not just by its collection of tiles that are open and adjacent, but also if it is connected to main!

4b) The straight line drawing algorithm just like checks the x,y coordinate of the center of Room A compared to the center of Room B, and increments or decrements x or y step by step, clearing each tile, until it reaches the center of the Room B. You can tweak this to adjust how wide you want the connecting passage to be.

5) Then it proceeds to each room and draws a straight line to other rooms until it hits a room that is marked connectedToMain. At which point, it marks all the rooms that it has drawn a line through as connectedToMain = true. And voila. All the rooms are connected now! Yayaya.

6) Define a Unity GameObject that has a Unity Grid, upon which it can create a Unity Tilemap, then iterate thru map and if an element equals 1, instantiate a mountain tile, otherwise leave it blank! The mountain tiles are 'smart tiles', by which I mean, whenever a new tile is added to their Tilemap, they check their list of rules (which I set up) and see which rule applies given their current neighbors and decide which mountain sprite (of the 64 mountain tiles sprties I've made) they should actually be (and its corresponding collision polygon).

*
**
********
*******************
**************************************
****************************************************


****************************************************
**************************************
*******************
********
**
*


Yaya. So, as you might've noticed, that gif already has the islands, clouds, and tetris pieces in place too. After I've generated the tile map, I have a big process for how I place all these things. There's sort of two ways. I either go thru each tile and do a thing or I decide how many things I want to do and then pick random tiles.

For islands, I go through the map again and for each tile that is clear. According to some spawn chance, I may or may not spawn an island in that tile. There's some nuance here for bigger islands that take up several tile spaces, and I'm probably going to revisit this bc I'm not crazy about the look of every island being exactly centered in a tile/the exact center of a 2x2 tiles/never more than 1 island per tile.

For tetris pieces and clouds, I don't iterate thru each tile, but rather start with a random number of objects I want to spawn, then randomly pick a tile an open tile, then randomly pick a position within that tile's boundaries amd place the object there. 

The clouds are generated as cloud groups, which comprise up to a few big clouds, then up to a few times some number of medium clouds, then up to a few times some number of medium clouds times some number of small clouds. If it a storm group, then they'll be rain clouds with random amounts of rain levels.

For placing trees, monster dens, and flower traps, since I happened to have a list of all the islands that have been spawned, and since each island happens to have an accessible array of permissable spawn points, I pick a random island, pick a random spawn point on it, and drop the corresponding object. I then remove that spot from the island's available spawn points.

So anyway, that's a lot of stuff. And there's going to be more. Shops, dams, air rivers, cave entrances/exits, fortresses, oases, beast lairs, villages/cities, whale smiths, kennels, inns, camp grounds, treehouses, a festival, race tracks, and more! I PINKY PROMISE NOTHING WILL BE CUT OR CHANGED FROM THIS PLAN EVER AND IN FACT I WILL ONLY ADD MORE.

So anyway, that's a lot of stuff. And performance, even in this mostly shaderless, mostly particle effectless game, does become an issue. So I create an ObjectsInATileHolder object! And whenever I spawn any of the non moving structures I've mentioned above, I associate them with an ObjectsInATileHolder. Then, if I want to 'turn off' a tile, I tell the corresponding ObjectsInATileHolder to 'turn off' all its objects, which, in Unity terms, just means setting them to inactive, which means that Unity will pay no attention to them in its core game loop. I use a coroutine to check once every second the location of the camera and find out which map tile it's over. If it is over a different tile than it was the second before, it then makes sure to turn on all its neighbor tiles (within a range I've currently set to 2) and makes sure any tiles that were on and that are not within 2 tiles, are turned off.

*
**
********
*******************
**************************************
****************************************************

****************************************************
**************************************
*******************
********
**
*

I generate all this using System.Random, which will reproduce the same 'random' set of values if given the same seed. So fo network synchronization, all I need to do is send that seed number to any connecting clients! (Assuming all the parameters match: map width, map height, room-connection-passage-width, chance to spawn an island, number of trees to spawn, number of clouds to spawn, number of tetris pieces to spawn, number of dens to spawn, etc.)

Monday, June 10, 2019

Arms of Flarms of Darms

A long time ago, I had 4 guns you could equip in Flock of Dogs. The shotgun, the sniper, the battle rifle, the assault rifle, and the pistol. Inspired by Halo, as placeholder weapons. You can see a floating machine gun sprite below:


Eventaully, I removed them from the game and limited players to only use what had been the pistol, but with an infinite clip, and then worked on other parts of the game. And over time, I've felt that I wanted to move the focus away from ranged combat for 3 reasons:

1. Ranged wants to keep their enemies across the screen from them, which means that if there's multiple dog riders targetting different enemies, it gets awkward for them to manage their screen space.

2. Visually very cluttered with everyone's projectiles flying everywhere.

3. Upgrades for ranged weapons usually means more projectiles, faster, bigger. These aggravate the above issues.


So anywayaya. I've made 4 weapons. They are a bow, flail, lance, and shield.



Arrow damage: 8 points
Knockback: 12 units of force
Range: 60 units of distance
Auto draw time: 0.33 s
Bow rotation speed: 720 degrees / s

The bow is fairly straightforward now. Initially, I had you manually draw the bow and struggled with the decision of what happens if you do not fully draw your bow: a weak attack or no attack? If a weak attack, how is that visually communicated? Does the bow automatically fire the direction you're aiming, or is there a bit of drag between your actual input and the rotation of the weapon? (that was a decision I had to make for the lance and shield too). Considering that you'll be doing lots and lots of shooting, it would simply be annoying to fail your shots. Which is, in fact, how pretty much every bow works in video games. The idea of a weaker attack, in the context of this game, seems difficult to communicate and unnecssary.


Flail ball damage: 9 points
Flail ball knockback: 8 units of force
Flail max angular velocity: 1080 degrees / s
Flail min angular velocity: 360 degrees / s
Time to increase angular velcoity from min to max: 2 s
Range: ~5.5 units of distance 


Of course, the first thought for a melee weapon was a sword, which I had actually made a few months ago as an upgrade for the mop, but that may get tossed at this point. I wanted one weapon that wouldn't actually require use of the 2nd thumbstick (aiming). This provides a lower skill floor and a kind of accessibility. The flail fit that perfectly. I experimented with slowing down your movement speed whilst flailing, but it's already somewhat challenging to close in on enemies considering the flail has the shortest range of the current weapons anyway. Plus, what's really the harm in allowing players to be constantly flailing? There may come a time when there's a cost to that, if I decide to implement weapon durability.



Lunge thrust damage: 20 points
Lunge knockback: 15 unity of force
Lunge range: 13 units of distance
Poke damage: 2 points
Poke knockback: 3 units of force
Poke range: 8.5 units of distance
Charge up time: 0.0625 s
Lunge duration: ~ 0.3 s
Poke duration: ~ 0.1 s
Lance rotation speed: 360 degrees / s

The lance delivers a lunge attack if your momentum in the direction of your attack is above a small threshold. Otherwise, it just does a poke. Both attacks can pierce (do damage to multiple enemies). Whereas with the bow I opted not to have a half charged attack option, delivering a well executed attack is everything with the lance and the damage it does matches that. The input is such that if you pull the trigger and hold it, you won't attack until you release. If you just pull and release immediately, your character will complete fully pulling back the lance, and then attacking (thrust/poke). I experimented with just requiring you to charge up the lance fully to do a lunge, and also not having two types of attacks, but rather you just fail to attack if you don't fully charge up. But the idea of flying to the right and then being able to to a lunge thrust backwards, against your momentum, doing full damage, upset the combat realist in me. Currently, I don't check your momentum until the lance has been fully 'charged' or brought back in preparation to strike, which takes like .0625 seconds. I may instead do the check at the time you start 'charging up' your attack, but then I'll hvae to handle the case where you choose to hold the lance in its charged up state. idk.


Time to expand shield: 0.1 s
Shield bash range: 2.5 units of distance
Shield knockback:  20 units of force
Shield holder speed reduction: (uses a air resistance formula based on the square of the velocity of the player)
Shield rotation speed: 1080 degrees / s
Shield expanded rotation speed: 90 degrees / s

The shield slows your movement when it's expanded. It also cannot be aimed as quickly when it is expanded. It also delivers a shield bash when it is expanded. Before I had the idea of expanding it, it was very simple, you just moved and aimed. I knew I wanted a shield bash, however, and I didn't want to involve a second button in weapon usage. I didn't want the player's movement to be permanently retarded by just simply having the shield, so the idea of 'wielding it' or 'holding it up' popped into my mind. So that became the unfurling of the shield.


Their visual polish is not complete and there are some systems (upgradability, durability, solar energizability) that I'm considering. At the moment, I'm more interesting in developing these 4 weapons, rather than making a large arsenal to choose from. These could take many forms. What I'm leaning towards is a small weapon rack on board the whale where you can swap out which weapon you're actively using. This would work similarly to how I had harpoons in the game previously, which I may bring back. So at any time, you can hold 1 weapon and 3 harpoons. Maybe. Or maybe harpoons act as another weapon. And maybe you can have 2 weapons at any time. I lean away from upgrades and prefer the idea of using the solar power as temporary power boosts to weapons that perhaps allow for their special attacks. I think this may be the place where I reincorporate my previous features of the replicator shield (a shield that triples your allies shots that pass thru it) and the electric tether (a rope attached between two players that electrocutes the enemy when they cross it).

By durability, I refer to the weapons being breakable/consummable. The idea of durability just ties into the greater design goal of symbiosis of whale, dog, and rider. The whale needs the riders to fight off attacks and the rider needs the whale to hold its weapon stores. However, issues arise when you're (a) out of weapons completely, (b) how to indicate a weapon's current durability hp, (c) when you swap out a half used up weapon for a fresh one at the weapon rack, (d) determining how each weapon loses its durability. I have some soultions for this, but maybe it will just be annoying to have your weapons wear out on you? There's this general concept of the dog and rider pit stop, which I believe is a very big part of the symbiosis experience I'm trying to design. The pit stop goes something like this: dog and rider have been flying around collecting resources, enganging in combat, landing on islands, whatever. Now the dog is low on water, perhaps its lost a few hearts, and the rider's weapon is broken, and there's a ruby the player has just found. So this is a perfect time for a pit stop. The rider can fly back to whale, dock the dog, walk the ruby up to the whale's mouth, grab a new weapon. The dog gets sprayed down, and fed, and takes a brief nap. And then the rider mounts back up and heads back in the blue skies.

Anyway. I'll talk about the dogs' hydration and food systems anoyther time. I've changed them slightly.


My favorite weapon is the lance! Yayaya!