Liza Shulyayeva

I Have Become Crazy Cat Lady, the Buyer of Cat Strollers

I am sorry in advance for what you are about to read. If witnessing the evolution of a mere human into Crazy Cat Lady is something you may find disturbing, turn back now and pretend you never came to this godforsaken place.

I got my cats a cat stroller. Specifically…this cat stroller (in green). This thing had better last us for life.

First, reasoning (aka excuses):

  • Our cats like being outside, both on our cat-enclosed porch and on harnesses in the shared yard. BUT they can’t really walk like a dog does (it’s more like they walk and we follow them around), which makes it difficult to show them new places.
  • Our cats are heavy. Rigel has pretty much outgrown his original plastic carrier (which wasn’t that great quality to begin with). A cat stroller would provide a more comfortable method of transportation (such as to the vet) for both me and him - I don’t have to lug a heavy cat around in a carrier and he gets a much more smooth and comfy ride.
  • Here in Stockholm it is perfectly acceptable to take your pets on public transport. Having had to take Rigel on public transport before, I know how stressful it can be. All the new sounds and smells and people. One thing that doesn’t help is him being low down on the floor in his plastic carrier. Cats like to be up high. Aside from being more comfortable and roomy, the stroller gives the cats a higher position to sit in, hopefully helping them feel a little less stressed and intimidated.

So anyway, it came a few days ago. Here it is:

Pet Gear NV Pet stroller

Kaytu took to it straight away. She jumped in and I rolled her around the house. Rigel took a little longer, but eventually did the same thing.

Cat in Pet Gear stroller

A little later it was time for our first walk! We had a very quick walk around the small shared yard area out front. I didn’t want to go too far too soon and overwhelm them.

Pushing cat in cat stroller

Even David decided to partake in the Walk of Shame:

Cat stroller walk of shame


This morning we took our first longer trip. We didn’t go too far this time, just to a large playground with a small wooded area about a 5-10 minute walk away (in non-cat-stroller-time!)

Two Cats One Stroller

Kaytu seems to have taken a liking to the stroller as soon as it came, even faster than Rigel. But Rigel was more comfortable being outside at a new place. Maybe it’s because he’s been out of the house so much in taxis and on trains out of necessity. I parked the stroller near a bench (the foot brake has already come in handy) and opened the secure top cover. Both cats already had harnesses and their retractable leads on. Rigel stepped out onto the bench and went to explore the nearby bushy area straight away.

Rigel goes to explore

Kaytu, though, was uncomfortable. I don’t think she’s ever been this far from home. I put the cover down a little more to shade her from the outside world and we followed Rigel around in the stroller.

Nervous cat stroller cat

After some exploration we slowly made our way back in the direction of the house.

Cat exploring a bush

Cat next to cat stroller

We rode with the hood up, my holding the leads. Kaytu was a lot more comfortable by this time and sat up front sniffing around. Then she actually decided to jump down and do some exploring of her own.

Cat explores the world

At one point Rigel jumped back in the stroller, rode there for a few minutes, and then hopped back out to do more roaming.

Rigel cat jumps into cat stroller

Rigel the cat walks next to pet stroller

The cool thing is he actually WALKED most of the way home! Kaytu was content to stay in the stroller and be pushed around, but Rigel walked behind me. It was slow going - he’d walk a few seconds, then stop to look around. I would call him and he’d walk again, then stop again. I think he’s starting to learn what “Come” means!

Rigel learns to come!

At one point we were passed by a huge retriever looking dog. Rigel didn’t seem worried at all - he just sat there and looked at it. The owner made the dog sit a few meters away until the dog calmed down (it was very excited), muttering “Friend! Friend!” while pointing at Rigel. Finally Rigel got bored and headed back in the direction of home.

By the time we got back to the yard Rigel was sitting in the stroller and Kaytu was sitting on top of it - it was quite a sight.

Arriving home in cat stroller

Kaytu trying to figure ot what just happened

Cat on top of stroller

Pee-training Rigel

Note: This is part of a series of posts about Rigel the Maine Coon kitten who was given a urinary infection by a veterinarian in Stockholm during a routine surgery and has been struggling with peeing since. You can read more in the Rigel section.

Rigel turned 1 year old on July 22. I was almost afraid to celebrate. We didn’t think he was going to make it to 8 months, having been scheduled to be put to sleep way back in…was it March…and then miraculously peeing at the last minute, on the day we thought we were going to lose him. Here’s a photo of him on July 22:

1 year old Maine Coon

For his Birthday we finished cat-proofing our porch to let Rigel and Kaytu sit there under supervision without a harness. They love it - Rigel now bolts downstairs whenever he hears me going to the door and demands to be let out every morning.

1 year old Maine Coon on porch

The peeing has been…interesting. He has taken to peeing twice a day, but only one of these times seems to be of his own free will. Basically - he has been peeing on his own at 7-8:30am. Then, at 10pm, we bring the other litter box, water, and toys into the bedroom, open a window crack to get some air, and shut all of us in there. David and I watch a show and try to leave Rigel alone (that is, we’re in the same room but not bothering him). We don’t open the door to let the cats out until Rigel pees. Originally this started as a way for me to keep a closer eye on him, to avoid waking up in the morning to find pee in one of the boxes and not be sure which cat it was. This way I can hear when one of them goes in the same room and wake up to see if it’s Rigel or Kaytu. If Rigel goes to pee we let them out because we know he’s peed enough and any pees we find the next morning will probably be Kaytu’s.

But Rigel seems to have seen the connection between him urinating and being let out of the room. Gradually the time between him being locked in and peeing has decreased, though it varies still. A few times he’s entered the box right after shut-in. I guess it’s his way of saying “Screw you guys I don’t want to be in the same room as you and I’ll pee to prove it.”

Sometimes it seems he really doesn’t want to pee when we do this. Last night, for example, he sat on the windowsill as usual for 20-30 minutes. Then we hopped off and went to consider one litter box…no go…then the other…still no go. Then he walked to the door and made a frustrated Maine Coon chattering sound, pawing at the handle. Seeing that it was no use he begrudgingly stomped into one of the boxes, did a nice pee, and went back to the door - at this point we of course opened it.

I guess he’s been accidentally “pee-trained”. On the one hand this seems good - he can make himself pee “on command” when there is something he wants (though the conditions are pretty strict - 10pm on the dot, both boxes in room, window cracked open…it’s like a ritual). On the other hand why doesn’t he feel like peeing by this point himself? If he pees at ~8am that’s 14 hours from morning pee to lock-in. We know he has plenty of urine in him by then. Why does he wait for us to lock him in to actually go?!

Anyway, as long as he’s getting pee out I’m happy. He’s still on medication and we don’t know what the future holds. Every day we’re grateful for a good Rigel pee.

Laravel Log File Backups to S3

SnailLife does a lot of logging for debugging purposes. Aside from the general laravel.log I have separate loggers for each snail and also write logs for stuff like deleted items etc in separate files.

The problem is all the space this takes up on my Digital Ocean droplet in Laravel’s storage folder. If I leave it for a week or two it fills up and I’ll suddenly find my droplet inaccessible or some recurring commands not being able to finish properly due to insufficient space.

Instead of culling the logs more aggressively I decided to set up a backup to Amazon S3. With Laravel 5’s filesystems this ended up being a pretty simple process.

First I set up an S3 bucket called snaillife-storage with a user that has getObject, createObject, and deleteObject permissions.

I set the S3 credentials in the .env configuration file:

S3_KEY=blah
S3_SECRET=blah
S3_REGION=website-us-east-1
S3_BUCKET=snaillife-storage

Note that I set the region here just in case but in reality I don’t use it. In config/filesystems.php I set up the S3 disk using these credentials (the region setting is removed. I also changed the local disk root to storage_path()):

'local' => [
  'driver' => 'local',
  'root'   => storage_path(),
],

's3' => [
  'driver' => 's3',
  'key'    => env('S3_KEY'),
  'secret' => env('S3_SECRET'),
  'bucket' => env('S3_BUCKET'),
],

Then I made a new artisan command called BackUpLogs:

<?php namespace App\Console\Commands;

use Illuminate\Console\Command;
use Storage;
use Log;
use Carbon\Carbon;
use App;

class BackUpLogs extends Command {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'BackUpLogs';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Back up logs to Amazon S3';

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        if (!App::isLocal()) {
            $localDisk = Storage::disk('local');
            $localFiles = $localDisk->allFiles('logs');
            $cloudDisk = Storage::disk('s3');
            $pathPrefix = 'snailLogs' . DIRECTORY_SEPARATOR . Carbon::now() . DIRECTORY_SEPARATOR;
            foreach ($localFiles as $file) {
                $contents = $localDisk->get($file);
                $cloudLocation = $pathPrefix . $file;
                $cloudDisk->put($cloudLocation, $contents);
                $localDisk->delete($file);
            }
        }
        else {
            Log::info('BackUpLogs not backing up in local env');
        }
    }
}

Note that the directory you specify in $localDisk->allFiles($dir) should be relative to the root path of the local disk - an absolute path does not work.

In Kernel.php I set this to run every hour:

$schedule->command('BackUpLogs')->cron('5 * * * *');

So now every hour all the files in my storage/logs directory are backed up to my S3 bucket and deleted from the local disk.

SnailLife Messaging System

“What’s SnailLife”, you say? Well I’m glad you asked! My snail simulation has gone through a couple of names…and even though I was never 100% happy with Gastropoda it was the best I could come up with - a name that was unique and didn’t allow the project to sound too “gamey” (because it’s not a game). All of the cooler names I could come up with weren’t suitable for various reasons (like domain name availability and such).

But recently I found out about the .life TLD! And I decided that nowadays we have such a varied domain name landscape that .com isn’t as important as it used to be, and definitely not for an obscure hobby project. So Gastropoda is now SnailLife! On to the messaging:

Messaging

I noticed when working on the simulation that it was tough to figure out who died and why when snails disappeared from a jar.

(Sidenote…I just realized…in real life a snail wouldn’t just disappear from a jar if it died. It would sit there and start decomposing until someone put it away. Maybe this should be the case with SnailLife, too).

Anyway, I wanted something to notify me immediately when something important happens, like a snail is born or dies. So I made a rudimentary messaging system to receive notifications from the simulation, which should be able to be pretty easily expanded into a user-to-user messaging system.

First I made a user_messages table with the following columns:

  • messageID
  • recipientID
  • senderID
  • subject
  • content
  • isRead
  • created_at
  • updated_at

Then I made a UserMessage model that looks like this for now:

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class UserMessage extends Model {

    public static $rules = array(
        'recipientID'     => 'integer',
        'senderID'   => 'integer',
        'subject'    => 'alpha_num_spaces',
        'content'    => 'alpha_num_spaces',
        'isRead'     => 'boolean'
    );

    protected $primaryKey = 'messageID';

    protected $fillable = ['recipientID', 'senderID', 'subject', 'content', 'isRead', 'created_at', 'updated_at'];

    public function recipient() {
        $this->hasOne('App\User', 'userID', 'recipientID');
    }

    public function sender() {
        $this->hasOne('App\User', 'userID', 'senderID');
    }

    public function getSenderUserNameAttribute() {
        $username = 'SnailLife';
        if (isset($this->sender)) {
            $username = $this->sender->username;
        }
        return $username;
    }

    public function updateMessage($propertiesToUpdate) {
        $this->update($propertiesToUpdate);
        return true;
    }
}

When a snail is killed or born we create a new message for the user. For example:

if ($this->isEgg) {
    $notification = [
        'recipientID' => $this->ownerID,
        'subject'     => 'An egg has died!',
        'content'     => 'Egg ID ' . $this->snailID . ' has died. Cause of death: ' . $cod
    ];
}
else {
    $notification = [
        'recipientID' => $this->ownerID,
        'subject'     => 'A snail (' . $this->snailID . ') has died!',
        'content'     => 'SnailID ID ' . $this->snailID . ' has died. Cause of death: ' . $cod
    ];
}
$notification = new UserMessage($notification);
$notification->save();

(An egg is really just an instance of a snail, just one without a birthDate, so when an egg or snail dies it’s handled in the same model).

Then there’s the view. When logged in the user gets a notification of unread messages in the header:

<p>
    @if (count(Auth::user()->unreadMessages) > 0)
        <a href="/account/messages"><img src="{!! URL::asset('assets/img/graphics/icons/envelope.png') !!}" alt="You have unread messages"></a> - You've got mail!
    @endif
</p>

Oh, we get unread messages in the User model using an Eloquent hasMany relationship:

public function unreadMessages() {
    return $this->hasMany('App\UserMessage', 'recipientID', 'userID')->where('isRead', '=', 0);
}

Once they click through they get taken to their message page (ignore the double-death messages. That’s being fixed right now…):

User messages

There they can click to view the individual message, delete messages, mark them as read, etc.

Pretty simple and gets the job done for now.

Update on Rigel and Response From DjurAkuten

Context: The Month from Hell.

DjurAkuten

After numerous attempts David finally got through to DjurAkuten, the clinic that did Rigel’s neutering surgery and decided to put a catheter in him because they thought his bladder looked big - the one he left with a urinary infection and an inability to pee.

He spoke to them about what had happened. They requested that we email them all of Rigel’s journals, which we did the same day. A week or two later a letter came in the mail (in Swedish) where they claimed that they had zero responsibility for what happened to Rigel because his bladder was already flaccid when he got there. I’m not sure if they even read their own journals on him. DjurAkuten themselves wrote in their records that they found no blockage and no infection in Rigel’s urine and that his bladder contracted normally while being drained during castration - it was certainly not flaccid. They simply thought his bladder was large and decided to poke around with a catheter during what was meant to be a routine surgery.

It’s disappointing because either they are contradicting their own paperwork without even reading it or they are being intentionally misleading. I might upload all of his journals here later for anyone who’s curious and is willing to either translate or reads Swedish.

Here was my email to them in response, sent a few weeks ago, to which we have had no reply even after calling (I have censored the names of the other clinics involved here):

Attn: Ms Susanne Åhman regarding Rigel‏

Dear Ms Åhman,

I am contacting you through email because it is faster than sending physical mails back and forth.

Please let me know if I misunderstood your mail to us about Rigel, but it seems you are saying DjurAkuten takes zero responsibility for anything that’s happened because our kitten came in with a large bladder? The part in your mail about him having a flaccid bladder is not correct. Your own journals have said that his bladder contracted normally after you decided to stick a catheter in him: “efter tömning drar ub ihop sig vad som ser ut som normalt.” It was certainly not flaccid as your own records show.

In fact, the first time your own journals mention a flaccid bladder was when we brought him back to you the 2nd time when he was unable to pee after your treatment.

We are certainly not saying that the problems were as a result of his castration as your mail seems to imply - we are saying his problems are as a result of your introducing a urinary infection while sticking a catheter in him during what was supposed to be simply a castration surgery. There was no good reason to do this.

An Internal Medicine specialist has examined him and communicated that cat organs, just like human organs, can vary in size and shape. We have been told that just because a bladder is large does not mean it has a problem. The fact that he developed an infection after you decided to put a catheter in him is also indicative of an issue being introduced at your clinic. Our kitten was perfectly happy and healthy before his procedure at your clinic resulted in a urinary infection. I can assure you he did not come in sick with an infected, distended bladder. I know this not just by our own observation of him and his very normal urination frequency before your treatment, but by looking at your own journal notes - you decided to put a catheter in him when you noticed his bladder was large and tested for infection. You found no infection. You have also said that his bladder contracted normally after being drained at that time. Your own vet spoke to me in person after the neutering surgery and said that his large bladder can be totally normal for him and not cause any further problems, especially since he’s been peeing normally all along. We came back to you when our kitten could not pee despite straining to after your surgery and catheterization. You stuck ANOTHER catheter in him to drain him AGAIN and said that he now suddenly has an infection. This is what started this entire chain of events - us bringing a healthy, happy kitten to DjurAkuten for a routine surgery and leaving with a kitten who was unable to pee and had to go through a subsequent bladder biopsy surgery, perineal urethrostomy, and countless catheters, blood tests, and needles being stuck into his bladder. He was sent home to die and we still have no idea how he managed to survive after all this. He is still on heavy medication, special food, and constant watch (we still monitor his urine clumps in a box daily to make sure he is urinating enough after this entire ordeal). We have just begun to get over everything he’s been through and everything we’ve gone through to keep him alive, but now seeing you claim that he came to you already sick despite all evidence to the contrary is even more upsetting.

So - am I understanding correctly in that you claim DjurAkuten did not have anything to do with the initial problem - him being unable to pee after being catheterized at your clinic - even though you tested (and cleared) him for infection, despite the fact that other veterinarians have indicated that he was fine before he saw you and got sick after you decided to stick a catheter in him, and that the treatment he received at your clinic and subsequent infection are more than likely related? You will note that in the journals [other clinic] concludes that his original infection was iatrogenic in nature, ie, introduced in the catheterization process. Please confirm if this is the case, as I want to make sure I have correct information about your standpoint on this before we decide whether to pursue a formal complaint and proceeding to warn our friends, breeders, and the public about your clinic.

Kind regards,

Liza Shulyayeva

We are still hoping that they will reply and clarify what they meant by their mail. It’s possible that it was some sort of misunderstanding, and that DjurAkuten didn’t mean to suggest that Rigel was sick all along despite all evidence indicating otherwise. And if they were really trying to convince us that we brought them a sick kitten despite their own records indicating otherwise I will be extremely disappointed as this would indicate their miscomprehension of their own and other hospitals’ records at best and a blatant lie at worst.

Rigel

Rigel has been suffering from yet another urinary infection after this whole debacle. Perineal Urethrostomy increases the likelihood of infection, and I guess he got unlucky. He is on antibiotics and painkillers/anti-inflammatories. He seems to have gotten worse over the last day (peeing blood again and straining, and starting to pee outside the box) so I rushed another urine sample to the hospital at 4-5am this morning and managed to get an appointment with a vet this afternoon. By the time we had our appointment they had already gotten the results of the sample and said that there was no more bacteria! What was an infection is now a case of cystitis, so our next steps are to:

  • Finish course of antibiotics
  • Appetite booster every other day and special appetite-boosting food
  • Continue with Cystease supplement for bladder lining
  • Start giving Zylkene supplement for stress
  • Continue giving Metacam for 5 days for pain relief and as an anti-inflammatory.
  • Minimize noise and stress as much as possible (coincidentally this seems to have started when David’s kids came over to stay for two weeks a couple of days ago, so noise and excitement maybe triggered something, even though he really likes them).

They also took a blood test and checked his kidney, liver, and other values. Everything seems to be normal, which is a load off my chest.

It’s crazy that one bad incident at a bad vet has spun us into what seems to be an ongoing cycle of vet visits, tests, surgeries, hospital stays, two closets of cat medicine, and endless vet/medicine bills. It’s still amazing to me that he has survived all of these hospital stays and other procedures with his affectionate personality and happiness intact. The difference in his and Kaytu’s approach to life is obvious, though. Even though they’re best friends it’s obvious that Kaytu, with her seemingly carefree personality, hasn’t been through what Rigel has been through - you can see it in their eyes and in how they approach strangers and the outside world. He’s a very strong kitten and we will keep fighting for him and his quality of life no matter what it takes.

Snaily Updates

I was going to try to remember all the things I’ve done in the last month on snails, but this seems impossible. So here’s a lazy list of git commits within this time:

* 5 seconds ago, User messaging and cause of death [deploy:development]
* 4 days ago, Some race view fixes, test out increasing AMR for wild snails since too little energy is being burnt during races. [deploy:development]
* 4 days ago, Cancel races that have been unfinished for 24 hours, freeing up others to rent the race jar. [deploy:development]
* 5 days ago, Remove old unneeded Minion and Latchet stuff [deploy:development]
* 5 days ago, Remove unneeded logs, remove old breeding stuff that is no longer required with the brain [deploy:development]
* 5 days ago, Fixes and cleanup, new racing jar in seeder [deploy:development]
* 5 days ago, Remove start countdown from finished races, fix race result view [deploy:development]
* 5 days ago, Fix race finish [deploy:development]
* 5 days ago, Bug fixing in racing and memory saving [deploy:development]
* 6 days ago, Admin option to return foreign snails to owners, get parent jar [deploy:development]
* 6 days ago, Racing refactoring; stop destroying jars since we'll reuse them [deploy:development]
* 7 days ago, More flexibility for historical event logging; correctly represent snails trying to mate with items [deploy:development[
* 8 days ago, Substrate item creation fix [deploy:development]
* 8 days ago, Gather snails to middle of jar, more exact movement, seed race jars for Mr Casinir [deploy:development]
* 2 weeks ago, Ability to humanely euthanise eggs in jar [deploy:development]
* 2 weeks ago, Some safeguards for user entering snail in race before it was returned from previous race [deploy:development]
* 2 weeks ago, Replace finished and started columns with finishDate, startDate. Auto refresh jar page when race has started. [deploy:development]
* 3 weeks ago, Fix move check for jar view [deploy:development]
* 3 weeks ago, use View [deploy:development]
* 3 weeks ago, Fix swallowing, isolate mood update [deploy:development]
* 3 weeks ago, Resident register [deploy:development]
* 3 weeks ago, Draw gravestone under snail pattern [deploy:development]
* 3 weeks ago, Add rainbow bridge page [deploy:development]
* 3 weeks ago, Display estimated race start time [deploy:development]
* 3 weeks ago, Fix fertility spray [deploy:development]
* 3 weeks ago, Fix bank link [deploy:development]
* 3 weeks ago, Only list items user owns in closet [deploy:development]
* 3 weeks ago, Fix for jar installation [deploy:development]
* 3 weeks ago, Add bank hint to tutorial [deploy:development]
* 3 weeks ago, Switch to Single log mode [deploy:development]
* 4 weeks ago, CustomValidator [deploy:development]
* 4 weeks ago, Stop comparing an item to itself... [deploy:development]
* 4 weeks ago, User shouldn't be able to mix a substrate item with itself [deploy:development]
* 4 weeks ago, Toggle disabling item action form elements on action select [deploy:development]
* 4 weeks ago, Some item stuff [deploy:development]

I also decided to try live streaming Gastropoda development. I’ve only done a couple of sessions so far but it does help to keep me focused. Surprisingly enough a few people actually watch and ask questions. I didn’t really think anyone would be that interested in watching someone code some weirdo snail app. It’s nice because a lot of the “Have you thought about doing this” or “What about this” suggestions people have made are things I have thought of and/or already have implemented!

Items

After finishing yesterday’s post about generating images for user-combined substrate items I realized that I never really provided an explanation about how items work in general (or how they work for now, anyway).

We have a few different item-associated models in Gastropoda. They are (model followed by associated table in parinetheses)

  • ItemType (item_types)
  • ItemTemplate (item_templates)
  • ItemUserTemplate (item_user_templates)
  • ItemNutrition (item_nutrition)
  • ItemUserNutrition (item_user_nutrition)
  • Item (items)

Item types are very generic. Right now I have the following item types:

  • consumable
  • decorative
  • substrate
  • jar
  • breeding
  • terrain
  • training
  • misc

ItemTemplates are pregenerated item…templates that can exist in the world. Note that these are not the existing items in the world, they are just things that could be instantiated into in-world items. Each item template is associated with an item type. Right now they are (template name, item type):

  • Lettuce Leaf (consumable)
  • Medium Sweet Potato (consumable)
  • Spark (decorative)
  • Basic Small Jar (jar)
  • Enthusiast Small Jar (jar)
  • 7-Day Fertility Spray (breeding)
  • Grooved Glass 2mm (terrain)
  • Attractor (training)
  • Garden Dirt (substrate)
  • 1kg Pure Fine Sand (substrate)
  • 1kg Dry Oak Leaves (substrate)

ItemTemplates can have an associated nutrition template. All consumables and substrate items have their own entries in the item_nutrition table.

Items are the actual instances of these item templates existing in the world. They are usually created when stores are restocked or when users mix their own substrate. An item entry consists of basic information about the instance, like:

  • itemID
  • templateID
  • ownerID
  • jarID
  • bitesTaken
  • posX
  • posY
  • isUserTemplate

Which brings me to user templates. Any mixing of substrate creates a new item template, or rather an ItemUserTemplate, and saves it in the item_user_templates table. Substrate also gets its own nutrition entry, in item_user_nutrition. This allows us to easily distinguish user generated items from basic items in the simulation and treat them differently. Then on an instance of an item, if isUserTemplate is true, we know to look for associated templates in the user tables and not the regular tables.

That’s pretty much it. The most annoying part about this setup is that there are so many inter-table dependencies. An item without a template entry is a broken item. A consumable without a template that has an associated nutrition record is broken. Etc…

Images for Combined Substrate

The Laravel 5 migration is complete and I’m back to substrate mixing and brain bug fixing!

Actually, I think now that basic mixing is done I’m going to do a few weeks of just bug fixing. The substrate has no effect on snails’ attributes or behaviour yet, but it will. For now you can just mix different substrate items together into new substrate items.

I’m still not sure what the best way to create images for the custom-mixed substrate might be. The possibilities are pretty much endless - you can mix any substrate-type item with any other substrate-type item.

So what happens is - the user goes to their closet. They pick two substrate items to mix together. Then this whole mess happens:

 protected function mixSubstrate() {
    $itemID1 = Input::get('itemID1');
    $itemID2 = Input::get('itemID2');

    $item1 = $this->findItem($itemID1);
    $item2 = $this->findItem($itemID2);

    $item1WeightG = $item1->template->weightG;
    $item2WeightG = $item2->template->weightG;

    $resultingItemName = Input::get('itemname');

    $item1Attributes = $item1->template->nutrition->getAttributes();
    $item2Attributes = $item2->template->nutrition->getAttributes();

    $imageTitle = preg_replace('/\s+/', '', $resultingItemName) . '.png';
    $fileToSaveTo = '/assets/img/items/terrain/custom/' . $imageTitle;

    $resultingImage = Utility::MergeTwoImages($item1->fullImagePath, $item2->fullImagePath, $fileToSaveTo);

    // Create resulting item template
    $resultingItemTemplate = new ItemUserTemplate();
    $resultingItemTemplate->description = 'Custom item created by combining ' . $item1->template->name . ' and ' . $item2->template->name;
    $resultingItemTemplate->weightG = $item1WeightG + $item2WeightG;
    $resultingItemTemplate->name = $resultingItemName;
    $resultingItemTemplate->typeID = $item1->template->typeID;
    $resultingItemTemplate->ownerID = Auth::user()->userID;
    $resultingItemTemplate->imageSubdir = 'terrain/custom/';
    $resultingItemTemplate->imageName = $imageTitle;
    $resultingItemTemplate->save();

    // Create resulting item nutrition template
    $resultingItemNutrition = new ItemUserNutrition();
    $resultingItemNutrition->templateID = $resultingItemTemplate->templateID;
    foreach ($item1Attributes as $key => $value) {
        if ($key !== 'templateID' && $key !== 'updated_at' && $key !== 'created_at') {
            $resultingItemNutrition->$key = $item1Attributes[$key] + $item2Attributes[$key];
        }
    }
    $resultingItemNutrition->save();

    // Create item of new template
    $resultingItemProperties = [
        'templateID' => $resultingItemTemplate->templateID,
        'ownerID' => Auth::user()->userID,
        'isUserTemplate' => 1
    ];
    $resultingItem = $this->createNewItem($resultingItemProperties);

    // Delete original items
    $item1->deleteItem();
    $item2->deleteItem();

    return Redirect::back()->with('resultingItem', $resultingItem);
}

To actually create the image for the new item I added a new utility function:

public static function MergeTwoImages($imgPath1, $imgPath2, $fileToSaveTo = null) {
    $x = 50;
    $y = 50;
    $root = $_SERVER['DOCUMENT_ROOT'];

    $finalImage = imagecreatetruecolor($x, $y);
    imagesavealpha($finalImage, true);

    $sortedImages = [$imgPath1, $imgPath2];
    sort($sortedImages);

    $image1Layer = imagecreatefrompng($root . $sortedImages[0]);
    $image2Layer = imagecreatefrompng($root . $sortedImages[1]);

    $opacity = 50;

    ImageCopyMerge($finalImage, $image1Layer, 0, 0, 0, 0, 50, 50, $opacity);
    ImageCopyMerge($finalImage, $image2Layer, 0, 0, 0, 0, 50, 50, $opacity);

    header('Content-Type: image/png');
    if ($fileToSaveTo) {
        $fileToSaveTo = $root . $fileToSaveTo;
        imagepng($finalImage, $fileToSaveTo);
    }
    return $finalImage;
}

The results look like this (the below is experimenting with mixing 1kg oak leaves with 1kg sand):

Mixed substrate

It’ll do for now. After some bug fixing I’ll go back to substrate and start having it actually have an effect on the snails.

Migrating to Laravel 5

A few days ago I decided that there’s no use putting it off any longer - it’s time to upgrade to Laravel 5. I’ve been sitting on 4.2 for months and 5 is a major revision to…well…everything.

I knew this would be a large setback in terms of feature work. The migration would break things, things that would make me wish I’d never done it at all. In addition the plugins I’d been using for 4.2 would no longer be compatible with 5 (in fact, I don’t think any of them are…)

I was using:

  • Confide for user authentication
  • Dispatcher for scheduled commands
  • Latchet for a Laravel-specific implementation of Ratchet.

The first two I just got rid of completely, using Laravel 5’s authentication features and new command scheduling feature that does exactly what Dispatcher used to for me.

The last one I haven’t even started on yet. It’s going to be the toughest.

The most “breaking” update in Laravel 5 has been namespaces. I’ve been sitting here adding namespace declaration to all of my files. It’s time consuming, but better in the long run and actually pretty relaxing. If this is the biggest problem I’ll run into during this migration I’ll be happy (Latchet aside, of course).

Anyway, within the next couple of days I hope to have the migration complete and functional. Then it’s back to substrate and brains!

Thinking Through Substrate

After checking in the basic substrate item implementation yesterday I got to thinking - never a good sign.

The original idea was to have some pre-set types of substrate - garden dirt, rock, potting soil, pellets, whatever. But how would these items affect the snails and the jar? Would I need to invent some sort of set of attribute templates? Like “Oh well this garden dirt item is going to decrease jar temperature by 1C.” and “Oh well some snails somehow don’t like gardend dirt and prefer other substrates”. But why? Why would a snail prefer one template to another and how would this be decided?

Considering I’m already going for a lot of detail with consumables (making sure consumable items have macro and vitamin attributes based on their real life counterparts), I should maybe do the same thing for substrate. For example, garden dirt isn’t just garden dirt…it can contain varying amounts of clay, humus, sand, rock, etc. An item called “Garden Dirt” would really be made up of multiple base elements in varying proportions. And that led me to thinking - while a template called “Garden Dirt” should exist and be available for pre-mixed purchase from the Habitat store, users should also be able to mix these elements themselves. As a user I want to buy my own sand, clay, rocks, whatever, and experiment with proportions of each to create substrates of different moisture, porosity, pH level, etc.

I don’t know exactly how this is going to work yet - in fact from this point on I’m kind of making this up as I type. But here’s a quick overview of how consumable items already work.

The database has the following item-related tables (with the specified columns):

  • item_types
    • typeID
    • name
  • item_templates
    • templateID
    • typeID
    • luminance
    • temperature
    • roughness
    • description
    • enduranceMod
    • speedMod
    • aggressionMod
    • modDurationMins
    • weightG
    • bites
    • baseDaysToRot
    • imageName
    • imageSubdir
    • rarity
    • basePrice
    • priorityLayer
    • pingsJar
    • isApplied
  • item_nutrition
    • templateID
    • carbohydrates
    • sugar
    • fat
    • protein
    • vitA
    • vitC
    • vitD
    • vitB6
    • vitB12
    • calcium
    • iron
    • magnesium
  • items
    • itemID
    • templateID
    • ownerID
    • jarID
    • deleted
    • bitesTaken
    • posX
    • posY

I suspect that I will need another table on the same “level” as item_nutrition. Something like item_terrain_elements…or something. It would contain the following columns (or something like this):

  • item_terrain_elements
    • templateID
    • pH
    • porosity
    • carbon (primary nutrient)
    • hydrogen (primary nutrient)
    • oxygen (primary nutrient)
    • nitrogen (primary macronutrient)
    • phosphorus (primary macronutrient)
    • potassium (primary macronutrient)
    • calcium (primary macronutrient)
    • magnesium (primary macronutrient)
    • sulfur (primary macronutrient)
    • iron (primary micronutrient)
    • manganese (primary micronutrient)
    • boron (primary micronutrient)
    • copper (primary micronutrient)
    • zinc (primary micronutrient)
    • molybdenum (primary micronutrient)
    • chloride (primary micronutrient)
    • nickel (primary micronutrient)
    • iodine (other)
    • fluorine (other)
    • selenium (other)
    • cobalt (other)
    • arsenic (other)
    • lithium (other)
    • chromium (other)
    • silicon (other)
    • tin (other)
    • vanadium (other)

The primary nutrients would affect plant growth and the “other” nutrients could affect the animals (ie snails) within the jar.

And looking at the above just now, I don’t see a reason not to just add the above nutrients to the item_nutrition table. Some of the columns (eg magnesium) would already be duplicates anyway…I could maybe just add the above columns to the nutrition table and set anything irrelevant to ‘0’…would there be something wrong with this? I don’t know, I have to think.