added some art, added some diary entries on hold

master
chimchooree 8 months ago
parent bee9f5ec45
commit 9be9cad086

@ -0,0 +1,67 @@
<!--201224,200806-->
<h1>making of blessfrey.me</h1>
october 15, 2020<br>
#bottle #css #html #simpletemplate #webdesign #webdevelopment<br>
<br>
Blessfrey.me is a personal website I use to showcase projects and blog my process. It was originally written in PHP, but now it uses <a href="https://bottlepy.org/docs/dev/">Bottle</a>, a Python web framework. <br>
<br>
<h2>why not use a premade blogging platform like WordPress? </h2><br>
Blessfrey.me's needs are fairly simple - some static pages and a blog page. Generalized blogging platforms are overkill for my purposes. I don't need support for multiple authors, comments, localization, e-commerce, and so on. Unused features bog down the website at best and contribute to security vulnerabilities at worst. <br>
<br>
Also, it's fun to write my own platform. I'm learning a lot as I take my website from initial sketches, to Hello World, to various prototypes, to something polished enough to show my friends. Regardless, since it can be considered my programming portfolio, it makes sense that it itself should be something I programmed. <br>
<br>
<h2>why Bottle? </h2><br>
I originally wrote Blessfrey.me in PHP. I switched to Bottle after looking for a templating engine. Bottle comes with SimpleTemplate and can do everything PHP can do but faster and with less verbosity. Plus, I get to write in Python, a much more enjoyable language. <br>
<br>
<h2>how does blessfrey.me work? </h2><br>
<h3>SimpleTemplate </h3><br>
Instead of existing as a static collection of HTML pages on my server, Blessfrey.me's pages are constructed upon request by the website's Bottle script from SimpleTemplate templates. <br>
<br>
Every page uses the frame template pictured below, so the basic skeleton is consistent without requiring copy-pasted code. Each page is individualize through bringing in a new template within <code>{{!base}}</code>. (Double curly brackets refer to variables, and exclamation marks disable escaping.) <br>
<br><center>
<img src="/static/img/ent/blessfrey_website_alltemplate.png" alt="(image: basic template code.)"><br>
</center>
(The code can be found on <a href="https://pastebin.com/mQuGX3Xa">Pastebin</a>.) <br>
<br>
The header template below (brought in at <code>% include('header.tpl')</code>) has some variables, too, which are supplied by the Bottle script. If Bottle doesn't provide a title, it defaults to 'blessfrey.me.' Variables can also be used in paths and URLs. <br>
<br><center>
<img src="/static/img/ent/blessfrey_website_headertemplate.png" alt="(image: header template code.)" width="500" height="54"><br>
</center>
(The code can be found on <a href="https://pastebin.com/JcEU4xTm">Pastebin</a>.) <br>
<br>
You can insert Python code into the templates for dynamic pages. Below is an excerpt of the template for the diary page. This code fills the page with diary entry previews using a for loop. Not shown is the first line <code>% rebase('frame.tpl')</code>, which tells SimpleTemplate to insert this content at the <code>{{!base}}</code> variable in the frame template. <br>
<br><center>
<img src="/static/img/ent/blessfrey_website_snippettemplate.png" alt="(image: diary snippet code from diary template.)" width="500" height="401"><br>
</center>
(The code can be found on <a href="https://pastebin.com/ckEj9Bf2">Pastebin</a>.) <br>
<br>
The Bottle script sends the max number of snippets per page (the <code>limit</code>) and a list of lists containing all the diary snippets. It receives the page number from the URL. For the snippets that will appear on the given page, it converts the list data into HTML code to be displayed in the browser. <br>
<br>
<br>
<h3>Bottle </h3><br>
Bottle takes URLs and generates the corresponding web page upon request. Each URL is tied to a method, which returns a template and a dictionary of data to be used in the template. Since Bottle is a Python framework, you have access to all the Python libraries you need. <br>
<br><center>
<img src="/static/img/ent/blessfrey_website_diaryscript.png" alt="(image: Bottle script excerpt for diary routes.)" width="500" height="288"><br>
</center>
(The code can be found on <a href="https://pastebin.com/Bf96F9Ha">Pastebin</a>.) <br>
<br>
This is what the methods for specific routes look like. So every time you go to Blessfrey.me/diary, one of the above methods is called, depending on whether also you supplied an integer. To generate the page content, it calls a lot of Python functions to find my diary entries, convert them into previews for the snippets section and headlines for the sidebar, and get the current time for the footer. <br>
<br>
<br>
<br>
<h3>CSS </h3><br>
The website is styled using CSS, heavily relying on the CSS Grid and a bit of Flexbox. CSS Grids can be used inside CSS Grids, so my pages are generally blocked out, with smaller internal grids managing specific content. <br>
<br><center>
<img src="/static/img/ent/blessfrey_website_cssgrid.png" alt="(image: CSS Grid traced over screenshot of projects page.)" width="500" height="491"><br>
</center>
<br>
The projects page is an example of nested grids. Almost every page uses the yellow general layout. The content unique to the projects page is mapped out in green, with a section for the header, featured projects, and other projects. The other projects use a 2-column grid in blue to evenly space out all the little thumbnails. <br>
<br>
The CSS code for project's general grid and the nested unfeatured grid are shown below.<br>
<br><center>
<img src="/static/img/ent/blessfrey_website_projectcss.png" alt="(image: projects css code.)"><br>
</center>
(The code can be found on <a href="https://pastebin.com/pVgkmT5k">Pastebin</a>, but you can always see a web page's CSS by right-clicking and viewing the source.) <br>
<br>
Last updated June 8, 2021 <br>
<br>

@ -0,0 +1,70 @@
<!--200806,201126-->
<h1>my first game jam - #weeklygamejam </h1>
september 3, 2020<br>
#gamejam<br>
<br>
<b><a href="https://weeklygamejam.itch.io/">WeeklyGameJam</a></b> is a weekly theme-based game jam hosted through itch.io. It's fairly laid-back for a jam, giving you a full week's time, allowing for premade/stock assets and code, and being understanding towards late submissions. Most people make videogames, but any kind of game is allowed. At the end of the week, streamers will play and critique the submissions while the developers hang out in their chatrooms. <br>
<br>
<center><img src="/static/img/ent/SmallThingThatMakesThings.png" alt="(image: Key art of Elwell and Small Thing, buried in dogs)" width="500" height="223.77"></center> <br>
<br>
<h2>small thing that makes things</h2>
<br>
I participated in Week 85 under the theme Offspring, submitting my game on February 27, 2019. My game was Small Thing That Makes Things, an adventure platformer. You can play it on <a href="https://chimchooree.itch.io/small-thing-that-makes-things">itch.io</a>. <br>
<br>
You play as Hamish T. Elwell, the hero accountant of an overcrowded animal shelter, investigating the recent explosion of the local stray population. You can walk, jump on platforms, collect items, and chat with NPCs. There's multiple endings, depending on your choices. <br>
<br>
<h2>positives </h2>
<br>
For a week-made game, I think STTMT is pretty cute, and I'm pretty happy with it.<img src="/static/img/emo/star.gif" alt=":)"> <br>
<br>
I actually finished a game, and someone actually finished playing it on a stream. That's really cool, even if STTMT isn't all that good. The deadline forced me to make final decisions, complete features, and move on, and there's a lot of value in that. <br>
<br>
The short time-frame forced me to get around to every aspect of game development, many of which I had never done before. I had to learn how to export a Godot project, upload an HTML5 game to itch, and make sure the exported game was complete and playable. Lots of the features were first-times for me, too. I've never written code for platformer movements or moving cameras. This also was the first time I've really gotten branching and conditional dialog working in Godot, which was a skill I immediately applied to Blessfrey. <br>
<br>
<h2>mistakes</h2>
<br>
I recolored OPP's pixel art and lost the high contrast for the rock ledges. They blend into the rocky background, so you can't tell you can jump on them. I didn't even notice until <a href="https://www.twitch.tv/mrjoshuamclean">Joshua McLean</a> pointed this out during his stream. Having more eyes on your game is so important. <br>
<br>
Also the level design is just not interesting. I spent lots of time on the choices and adventure game aspects, while the platforming as an extreme afterthought. It's a game jam, though, what do you expect?<br>
<br>
I took the easy way out with animation. I might have learned more working from scratch, but modifying OPP's sprites to suit my character designs was way faster when I was already struggling to finish on time. <br>
<br>
<h2>screenshots + progression </h2>
<center>
<a target="_blank" href="/static/img/ent/SmallThingThatMakesThings_flatland.png">
<img src="/static/img/ent/SmallThingThatMakesThings_flatland.png" alt="(image: Elwell and Small Thing in a jumble of dogs)" width="500" height="300.92">
</a><br>
Experimenting with Small Thing's summoning mechanic. In this version, the characters are just blocks with collision that can move around the flat, empty world with WASD. <br>
<br><br>
<a target="_blank" href="/static/img/ent/chimchooree_weekly_game_jam.gif">
<img src="/static/img/ent/chimchooree_weekly_game_jam.gif" alt="(gif: Elwell rides a broken-physics dog across the valley)" width="500" height="215.77">
</a><br>
The dog spawning physics was (and still is) really broken, so dogs go flying pretty frequently. It was reliable enough in this version to be used as a moving platform. <br>
<br><br>
<a target="_blank" href="/static/img/ent/SmallThingThatMakesThings_rainingdogs.png">
<img src="/static/img/ent/SmallThingThatMakesThings_rainingdogs.png" alt="(image: Elwell watches the rainfall of dogs from his window)" width="500" height="282">
</a><br>
Dogs rain down upon Elwell's animal shelter in the finished version. <br>
<br><br>
<a target="_blank" href="/static/img/ent/SmallThingThatMakesThings_rainingdogs.png">
<img src="/static/img/ent/SmallThingThatMakesThings_dialog.png" alt="(image: Dialog)" width="500" height="265.12">
</a><br>
Dialog between Elwell and his boss. <br>
</center>
<br>
<h2>conclusion</h2>
<br>
If you're looking for a jam to jump into, WeeklyGameJam is a cute one to try. You aren't too rushed, you get to watch people play your game, you get useful criticism, and the community is pretty chill. The themes are always inspiring, too.<img src="/static/img/emo/heart.gif" alt="<3"> <br>
<br>
<h2>credits</h2>
<ul>
<li>coding and art by chimchooree</li>
<li>Open Pixel Project (OPP) (animations & tiles, edited to fit my characters & palette) @ <a href="http://www.openpixelproject.com/">http://www.openpixelproject.com/</a></li>
<li>Music track "forest" by syncopika under CC-BY 3.0 @ <a href="https://opengameart.org/content/forest">https://opengameart.org/content/forest</a> & <a href="https://greenbearmusic.bandcamp.com/album/bgm-fun-vol-5">https://greenbearmusic.bandcamp.com/album/bgm-fun-vol-5</a></li>
<li>Bad ending image from Wikimedia, credit to Jon Sullivan @ <a href="https://tinyurl.com/y6oswx8v">https://tinyurl.com/y6oswx8v</a> (URL contains spoilers)</li>
<li>SimpleJPC-16 Palette by Adigun Polack @ <a href="https://lospec.com/palette-list/simplejpc-16">https://lospec.com/palette-list/simplejpc-16</a></li>
<li>pixel joy font by chimchooree @ <a href="https://fontstruct.com/fontstructions/show/1596262/pixel-joy">https://fontstruct.com/fontstructions/show/1596262/pixel-joy</a></li>
<li>Tools: <a href="https://godotengine.org/">Godot Engine 3.0.6</a>, <a href="https://graphicsgale.com/us/">GraphicsGale</a></li>
<li>Weekly Game Jam 85, "Offspring" theme @ <a href="https://itch.io/jam/weekly-game-jam-85">https://itch.io/jam/weekly-game-jam-85</a></li>
</ul>
<br>

@ -0,0 +1,49 @@
<!--200806,200903-->
<h1>pretendOS - a game inspired by windows XP </h1>
november 26, 2020<br>
#sideproject<br>
<br>
Getting started with blessfrey's AI was overwhelming, so I took a break and worked on a new game. I didn't get very far. It's not even on my hard drive anymore. It's cute, though. <br>
<br>
Unlike blessfrey, where coding takes up 90% of the effort, pretendOS is mostly graphical assets + sounds with barely any functionality. It's a UI game, after all. Since it's a game requiring a completely opposite skill-set, I was able to make progress on <i>something</i> while reading up on game AI. I also got to work on new things like particle effects. <br>
<br>
<br>
<h2>Inspiration </h2>
<br>
<h3>Windows XP</h3>
The game takes strong aesthetic inspiration from Windows XP and the NDS. It almost fits the 90s vaporwave trend, but I'm too young to really remember Windows 95. I obviously have nostalgia for it as my first operating system (as far as I remember - I was a toddler then), but I mostly knew it as the Lego Island machine propped up in the dining room. As my family upgraded computers over the years, Windows never really impacted me as anything more than something that can run some videogames and whatever popular web browser of the time. That is, until Windows XP. <br>
<br>
XP really hooked me. It was the first operating system I spent as much time exploring as I spent using software. XP's edition of Paint was my favorite yet, I loved fiddling with the themes and accessibility options, especially Microsoft Sam, Rover, and Clippy. I started watching YouTube videos on how to use the Command Prompt. XP was just fun to use. I've found better desktop environments and operating systems since, but I never got over that aesthetic: smooth, blended graphics with hard pixel edges in 32-bit color with an alpha channel, right at the cusp between pixel art and vector graphics. Vaporwave for me is Luna olive green, or the "Fisher-Price interface" as Ars Technica users called it. <br>
<br>
<img src="/static/img/ent/wikipedia_luna.png" alt="(image: Wikipedia excerpt: Critics who did not like the theme characterized it as a 'Fisher-Price interface'.)"><br>
(screenshot from <a href="https://en.wikipedia.org/wiki/Windows_XP_visual_styles">Wikipedia</a> - referencing articles from <a href="https://web.archive.org/web/20091008081626/http://www.pcworld.com/article/117427/full_disclosure_your_take_on_windows_worst_irritations.html">PCWorld</a> and <a href="https://arstechnica.com/information-technology/2014/04/memory-lane-before-everyone-loved-windows-xp-they-hated-it/">Ars Technica</a>)<br>
<br>
<br>
There were a few other experiences behind the game, too. <br>
<br>
<h3>Mother's Day E-Card</h3>
I finally got my husband to try Godot Engine, and he used it to make a digital Mother's Day card for his mom. (Cute.) Opening the card displayed a 3D heart (a "cardioid") that bounced to the beat of a song while 2D cardioid particles rained down. It was all programmatically generated using geometry instead of 3D + 2D assets, so the application was very small. He made the graphics side of things look really interesting, and I wanted to play around with particles, too. Just...not as fancy;; <br>
<br>
<h3>Secret Little Haven</h3>
I also just played a cute pretend OS game on itch called <a href="https://ristar.itch.io/secret-little-haven">Secret Little Haven</a> It's quite short, very story-driven, and kind of buggy, telling Alex's struggle with gender identity through IMs. Honestly, pretend OS games and coming-of-age chat sims are nothing special, but Secret Little Haven's believability makes it really stand out. Instead of defending against cheesy + inaccurate deep web hacking attempts, you use an in-game terminal to get around child locks set by Alex's dad. Those terminal puzzles are the most realistic and relatable hacking I've seen in these games. SLH isn't super sophisticated or in-depth, but it shows how cute and believable a pretend OS game can be. I'd love to make a little environment like that, that's as fun to fiddle around with as XP. <br>
<br>
<br>
<h2>pretendOS </h2>
<br>
So what's pretendOS? Like I said, not much besides pictures. I only worked on it for a few days. <br>
<br>
What really bogged it down was Cat Chat. I wanted an AI chat personality that talked like a cat. I could do that, but that kind of thing takes a lot of time, when this project was really just procrastinating working on AI. <br>
<br>
The rest is cute, though. The icon winks when you click it, the cursor's kinda 2000s-free-animated-cursors, and it's got those Fisher-Price colors and bubbly sounds everywhere that I liked as a kid. <br>
<br>
<center>
<a target="_blank" href="/static/img/ent/pretendOS_catchat.jpeg">
<img src="/static/img/ent/pretendOS_catchat.jpeg" alt="(image: pretend desktop with a Cat Chat application)" width="500" height="281.25">
</a><br>
</center>
<br>
<br>
<h2>the future</h2>
<br>
It's cute. I'd like it to be finished in some way. I went ahead and cloned my old repo. Maybe I'll fill it out with more applications during a game jam or something or at least finally get that cool Secret Little Haven curved screen shader working. <br>
<br>

@ -0,0 +1,43 @@
<!--200806,210527-->
<h1>blessfrey graphic updates + mockups </h1>
december 24, 2020<br>
#mockups #screenshots<br>
<br>
I iterate over the graphics periodically, so I can practice without worrying about polish. Here's some screenshots of different styles I've tried. (Though April 23, 2019's is actually a mockup, that style did run in-engine for a few weeks.)<br>
<br>
<center>
<a target="_blank" href="/static/img/ent/screenshot_August152018.jpeg">
<img src="/static/img/ent/screenshot_August152018.jpeg" alt="(image: Cassia and Bad Cat on the pink carpet tilemap)" width="500" height="313">
</a><br>
August 15, 2018 - Early experimenting with Godot Engine. Collision was just added for sprites and walls. The buttons to the right are for switching between characters (who each have different skillbars). <br>
<br><br>
<a target="_blank" href="/static/img/ent/screenshot_January132019.jpeg">
<img src="/static/img/ent/screenshot_January132019.jpeg" alt="(image: Angel in a periwinkle room full of Bad Cats)" width="500" height="330.72">
</a><br>
January 13, 2019 - Videogame perspective is so different from perspective in illustration. Scale of characters vs environment is another quirk of games I had 0 experience with. I was vaguely going for an old Western RPG style with tall, somewhat realistic sprites with non-distinct faces. Something like Divine Divinity. <br>
<br><br>
<a target="_blank" href="/static/img/ent/screenshot_April232019.png">
<img src="/static/img/ent/screenshot_April232019.png" alt="(image: Angel blasting a neighborhood coyote with fire)" width="500" height="375">
</a><br>
April 23, 2019 - This is a mockup, but the game did look like this for a while. The fireball projectile didn't come until later, though. Here, I was trying to get a little more of a top-down view but not really. Instead of cats, Angel's fighting with a coyote. The government stopped removing coyotes from my old neighborhood, so they killed all the neighborhood cats except one and I saw him running away from a big coyote during a storm, so maybe he's gone now, too. It's just not right. <br>
<br><br>
<a target="_blank" href="/static/img/ent/screenshot_May252019.png">
<img src="/static/img/ent/screenshot_May252019.png" alt="(image: Angel and Chloe in front of a slanted house)" width="500" height="375.18">
</a><br>
May 25, 2019 - The slanted edition was so annoying. It's not isometric, it's just at an obscure angle because I drew these assets more for fun than to actually be practical. I do reuse the tree + bushes a lot, though. I also tried a more chibi sprite because they are soo common, might as well try it out. <br>
<br><br>
<a target="_blank" href="/static/img/ent/screenshot_June292019.png">
<img src="/static/img/ent/screenshot_June292019.png" alt="(image: Lots of Angels and other characters at a shopping center)" width="500" height="278.66">
</a><br>
June 29, 2019 - Trying a shopping center level now. It's reeally spaced apart. It's inspired by a real shopping center;; <br>
<br><br>
<a target="_blank" href="/static/img/ent/screenshot_July252020.png">
<img src="/static/img/ent/screenshot_July252020.png" alt="(image: Angel and some slimes in a cavern)" width="500" height="281.04">
</a><br>
July 25, 2020 - There's some missing in this gap, so I'll add in more pics if I find any. This is the first version of a cavern level for the blessfrey demo. It's inspired by local caves. <br>
<br><br>
</center>
<h2>you're up to date.</h2>
<br>
Hope you enjoyed seeing the different art I've used for blessfrey over the years. Even if I never really polish anything, it's nice to iterate to get a sense of game art and blessfrey's personal style. Hopefully it ends up looking okay okay when I do start polishing. But until then, in the words of YandereDev, "All art is placeholder."<br>
<br>

@ -0,0 +1,56 @@
<!--210218,210107-->
<h1>a look into an RPG achievement system </h1>
april 2, 2021<br>
#achievements #knowledgebase <br>
<br>
Designing an achievement system without any octopus tangles.<br>
<br>
<center><img src="/static/img/ent/KB_octopus.png" alt="(image: an illustration of an octopus tangling up UI, Dialog, and other game systems.)" width="500" height="396.75"></center> <br>
<br>
<h2>what does blessfrey consider an achievement? </h2><br>
<br>
An achievement is generally an objective used to create a metagame. They can vary in difficulty from an obscure discovery to repetitive use of mechanics to basic participation in the main story. Achievements can even be entirely external to the base game's system like <a href="https://steamcommunity.com/stats/221910/achievements">The Stanley Parable's "Go outside" achievement<a>, requiring you to not play for five years. Since achievements can be earned through any in-game system, the achievement system is closely tied to all other game systems. <br>
<br>
Completing an achievement usually awards a trophy, gamer points, or possibly something more practical, like a new game mode or an in-game item. <br>
<br>
Blessfrey feeds <i>every</i> in-game interaction through its achievement system, and its reward delivery is flexible enough not only to give a trophy but to cause <i>anything</i> to happen in response. Giving a trophy for finding a secret room is technically similar to gaining a Fire Resistance skill after standing on a bed of coals for 2 minutes or opening a new store after $1000 is spent at the mall. The only difference is actually notifying the player that an achievement was completed. Ironically, even though I ignore achievements in most games, Blessfrey's achievement system has become the backbone of player progression and world events. <br>
<br>
<br>
<h2>how to design an achievement system </h2><br>
<br>
Octopus-tangling is a major design concern for a system that is so interconnected with every other system. I could scatter achievement code through every other system, but that would be a problem if I ever need to make a fundamental change to the achievement system. Also, it will clutter the other systems if each system has a bunch of achievements tacked onto the bottom. <br>
<br>
Instead, Blessfrey's achievement system, the Knowledge Base, looks more like a mailman between game progress and the achievement system. A mailman Each granular action or world event that contributes to earning an achievement is called a piece of <b>knowledge</b>, which is identified by a <b>key</b> id number. Knowledge is categorized into <b>topics</b>. <b>Event handlers</b> subscribe to topics, waiting for news that a specific piece of knowledge has been encountered. The <b>Knowledge Base</b> itself stores all knowledge and facilitates the learning and forgetting of pieces of knowledge. Event handlers subscribed to the "knowledge_learned" topic will pay out awards. The <b>MessageBus</b> is the mailman that receives information about encountered knowledge and passes it off to all event handlers subscribed to that topic. The event handlers allow me to freely make and delete achievements, and the MessageBus keeps everything separate. <br>
<br>
<br>
<h2>an example</h2><br>
<br>
Let's say you get an achievement for finding the Nurse's Office. The moment the player loads into the Nurse's Office, data will zip back and forth between the MessageBus and the nurse's office object, different event handlers and the Knowledge Base. <br>
<br>
<ol>
<li>(Event Handler) At ready, event handlers call the MessageBus and subscribe to topics. </li>
<li>(Nurse's Office) The player enters the Nurse's Office. The room object sends itself to the MessageBus. </li>
<li>(MessageBus) Receives room object + sends it to all event handlers subscribed to the "place_entered" topic. </li>
<li>(Event Handler) NursesOfficeEntered receives room object. If the room is the Nurse's Office, send its corresponding knowledge key to the MessageBus. It can also verify pre-requisites and gather additional data for the Knowledge Base. This way, the system supports anything I'd like to track about when or how knowledge was learned. </li>
<li>(MessageBus) Receives the knowledge key + sends it to the Knowledge Base. </li>
<li>(Knowledge Base) Finds the knowledge identified by the incoming key. "Learns" by setting that knowledge to true and filling in additional fields if extra data was sent. Sends the knowledge key to the MessageBus. </li>
<li>(MessageBus) Receives the knowledge key + sends it to all "learned" event handlers. </li>
<li>(Event Handler) KnowledgeLearned receives the knowledge key + calls code for any changes resulting from learning this knowledge. Maybe you'll get a Steam achievement, but if the Knowledge Base was being to facilitate game progression, a quest could update, the dialog system could unlock the option to ask about the Nurse's Office, or you could gain a Codex entry about the new location. The changes can be conditional, too, so the handler can track whether all necessary keys have been received before enacting the change. </li>
</ol><br>
<br>
To use the achievement system for cyclical world events, you could trigger knowledge to be "forgotten" or ultimately set back to false in the Knowledge Base. This way, the phases of an event could begin anew. <br>
<br>
<br>
<h2>summary</h2><br>
<br>
Achievements can come from any combination of in-game actions, so an achievement system should be designed separately from the rest of the game. I achieve this through a couple of separate objects. <br>
<br>
<ul>
<li>Event Handlers: The tracking, verifying, and reward payout should be contained within event handlers, which can be generated and freed as needed. They subscribe to general topics and wait for their specific event to occur. </li>
<li>The Knowledge Base tracks the status of all knowledge in the game and can be used to understand how far the player and world have progressed. </li>
<li>The MessageBus is very light and only allows event handlers to subscribe to topics and for incoming message to be transmitted through that topic. It has absolutely no unique checks or code to execute, impartially delivering mail to the address on the envelope. </li>
<li>Another set of event handlers is concerned about the outcome of encountering and learning knowledge and can prompt changes or directly impact other systems, depending on pre-requisites met. </li>
<br>
<br>
Last updated April 2, 2021 <br>
<br>

@ -0,0 +1,102 @@
<!--200806,201015-->
<h1>playing FlightRising with spreadsheets </h1>
may 13, 2021<br>
#offtopic #spreadsheets #petsites <br>
My dragon breeding spreadsheet is really coming together, so I thought sharing it would be a fun break from AI. <br>
<br>
FlightRising is a petsite where you can breed and raise pet dragons. The dragons' appearances are determined by the breeds, genes, and colors of their parents, which have varying levels of dominance. So if you want a dragon that looks a certain way, you'll probably have to find the closest available matches and carefully breed them. There's a lot of factors to keep up with, so spreadsheets work better than keeping it all in my head. <br>
<h2>spreadsheets </h2><br>
<br>
There's sheets for an overview of breeding pairs, individual parents, each breeding project, and calculators and data. If you want the spreadsheet, too, you can download my template for <a href="/download/fr_projects.ods">Calc</a> or <a href="/download/fr_projects.xlsx">Excel</a>. I use <a href="https://www.libreoffice.org/discover/calc/">LibreOffice Calc</a> for the spreadsheets, but it should work the same as Excel. I'll go over how everything works, so you can modify it to suit your own projects. <br>
<h3>individual dragons </h3><br>
The Singles sheet has a row for each parent and a field for sex, breeding status, breed, breed's cooldown, date bred, nest ready, date ready to breed again, and a cooldown countdown. I'm not usually interested in this information by itself, but it's used by the Pairs sheet. <br>
<center><img src="/static/img/ent/fr_singles.png" alt="(image: screenshot from the Singles tab.)" style="display:block;" width="500"></center> <br>
<h4>sex </h4><br>
The sex field is limited to ♂ or ♀ and can be selected using a dropdown menu. The content will color the cell blue or pink. <br>
<br>
I made the dropdown menu through Data Validity (Data > Validity...). <br>
<center><img src="/static/img/ent/FR_datavalidity.jpg" alt="(image: Validity>Criteria. Allow: Cell range. Source: $Calculator.$i$3.$I$4.)" width="500"></center> <br>
The possibilities are in a column in the Calculator sheet and set the criteria to allow that cell range as the source. The color is dictated by Conditional Formatting (Format > Conditional Formatting). <br>
<center><img src="/static/img/ent/fr_conditionalformatting.png" alt="(image: Managing Conditional Formatting. Condition 1: cell value is equal to '♀'. .)" width="500"></center> <br>
There's one condition for boys and one for girls. I set the condition to look for 'cell value is' 'equal to' then either "♂" or "♀" with the quotes. For Apply Style, I made a new style with a blue background for ♂ cells and a pink one for ♀s. <br>
<center><img src="/static/img/ent/fr_newstyle.png" alt="(image: Apply Style>New Style...>Background.)" width="500"></center> <br>
<h4>breeding status </h4><br>
Depending on the breed's cooldown and date of last breeding, the cell will say "Ready" or "Cooldown" in green or red. <br>
<center><img src="/static/img/ent/fr_status_formula.png" alt="(image: =IF(H2<=TODAY(),'Ready','Cooldown'))"></center> <br>
The formula is =IF(H2<=TODAY(),"Ready","Cooldown"). IF takes three parameters here: the condition (if the date ready is today or earlier), the text to display if the condition's true, and the text for false. The colors come from Conditional Formatting again. <br>
<h4>breed </h4><br>
The breed, like the sex, is a Data Validity-determined dropdown menu. The list of breeds is sourced from a column in Calculator. <br>
<center><img src="/static/img/ent/fr_breeds.png" alt="(image: The Calculator Sheet has a column for the Breed and a column for its Cooldown.)" width="500"></center> <br>
<h4>breed's cooldown </h4><br>
Each breed has a different cooldown duration. The field uses a formula to refer to the Breed field and search in Calculator for the corresponding cooldown information. The formula is =VLOOKUP($D2,$Calculator.$G$3:$H$18,2,0). Here, I take the breed, take it to the breed + cooldown columns in the Calculator sheet, and return with the data from the 2nd column in that group. <br>
<center><img src="/static/img/ent/fr_vlookup.png" alt="(image: =VLOOKUP($D2,$Calculator.$G$3:$H$18,2,0))"></center> <br>
<h4>date bred </h4><br>
Every time I breed a dragon, I type the date in its Date Bred field. If the dragon is unbred, I use its birthday instead. <br>
<h4>nest ready </h4><br>
This is a simple formula - the date bred + 6 days. It's 6 because 6 days is amount of time it takes for an egg to hatch. <br>
<center><img src="/static/img/ent/fr_nestready.png" alt="(image: =F2+6)"></center> <br>
<h4>date ready to breed again </h4>
This is another simple formula - the date bred + the cooldown. <br>
<center><img src="/static/img/ent/fr_dateready.png" alt="(image: =F2+E2)"></center> <br>
<h4>cooldown countdown </h4><br>
This one is relatively simple. It's just today minus the cooldown, but I added some steps to add " days" after the number. If there are 0 or less days, I opted for it to say nothing because the default "#N/A" is annoying to look at. The formula is =IF($H2-TODAY()>0,CONCAT($H2-TODAY()," days"),""). You can see IF's three parameters: (condition) there's more than 0 days until cooldown ends, (if true) return that number + " days", (if false), return nothing. CONCAT concatenates the two parameters it's given, so it finds the number of days and adds " days". That means it'll use the plural even for 1. I could use another IF to fix that, but I barely refer to this sheet myself anyways. <br>
<center><img src="/static/img/ent/fr_cooldown.png" alt="(image: =IF($H2-TODAY()>0,CONCAT($H2-TODAY(),' days'),''))"></center> <br>
<h3>dragon pairs </h3><br>
The Pairs sheet is the sheet I check every time a nest opens. At a glance, it tells me which pairs are ready and which ones will be soon. It also lets me check whether my goal is within range of the pair and which of their offspring most closely resembles my goal. The fields are Project, Male, Female, Status, Date Ready, Countdown, Colors, Genes, Best Son, and Best Daughter. <br>
<center><img src="/static/img/ent/fr_pairs.png" alt="(image: screenshot of the Pairs sheet)" width="500"></center> <br>
<h4>project</h4> <br>
I add the project, so I know my goal for the pair. It's helpful when prioritizing or sorting. <br>
<h4>male + female</h4> <br>
I add the dragon pair's names here. Data validity makes sure the name corresponds to a dragon from the Singles sheet. <br>
<center><img src="/static/img/ent/fr_dragonvalidity.png" alt="(image: Data Validity by cell range. Source: $Singles.$A:$Singles.$A.)" width="500"></center><br>
<h4>status </h4> <br>
Both members of the pair must be ready before the pair is ready. =IF(AND((VLOOKUP($B2,$Singles.$A:$C,3,)="Ready"),(VLOOKUP(C2,$Singles.A:C,3,)="Ready")),"Ready","Cooldown"). There's a new function AND, which just takes its parameters and considers them together. All together, the formula wants to take each name in the pair, hunt down that dragon's row in the Singles sheet, and check its status. I use AND so that the condition won't be true unless both dragons are ready. <br>
<center><img src="/static/img/ent/fr_status.png" alt="(image: =IF(AND((VLOOKUP($B2,$Singles.$A:$C,3,)='Ready'),(VLOOKUP(C2,$Singles.A:C,3,)='Ready')),'Ready','Cooldown'))"></center> <br>
<h4>date ready </h4><br>
I find the date for when the pair is ready with =MAX(VLOOKUP($B2,$Singles.$A:$H,8,),VLOOKUP($C2,$Singles.$A:$H,8,)). MAX takes its parameters and returns the larger value. Basically, it wants to use each dragon's name to check when their cooldown will be ready on the Singles sheet. I use MAX because the pair isn't ready until the parent with the longest cooldown is ready. <br>
<center><img src="/static/img/ent/fr_ready.png" alt="(image: =MAX(VLOOKUP($B3,$Singles.$A:$H,8,),VLOOKUP($C3,$Singles.$A:$H,8,)))"></center> <br>
<h4>countdown </h4><br>
The formula =IF($E2-TODAY()>0,CONCAT($E2-TODAY(), " days"),"") is similar to the one from Singles, but this time, I used Conditional Formatting to make countdowns of 1-5 days an eye-catching yellow. <br>
<center><img src="/static/img/ent/fr_countdown.png" alt="(image: =IF($E2-TODAY()>0,CONCAT($E2-TODAY(), ' days'),''))"></center> <br>
<h4>colors + genes</h4> <br>
I can't always find parents that are in range of my goal, so noting the range helps me prioritize. PST stands for Primary, Secondary, and Tertiary, since each dragon's appearance is determined by three colors and three genes. <br>
<center><img src="/static/img/ent/fr_genetics.png" alt="(image: a screenshot from FlightRising of a dragon's genetics.)"></center> <br>
I really only track cash shop genes, since they can only be obtained through breeding or real world money. If I want an in-game cash gene, it's a lot easier to earn money than to gamble with RNG. <br>
<h4>best son + best daughter</h4> <br>
Space is limited, so I want to know which dragons contribute the most to their projects. It can take several generations of dragons to get the desired offspring, so I want to make sure I'm narrowing the color and gene range with each generation. <br>
<h3>calculator + data </h3><br>
The Calculator sheet is a catch-all for data and extra formulas. I have everything needed for Data Validity on other sheets here, and I keep calculators to help know what to write on other sheets. <br>
<center><img src="/static/img/ent/fr_calculator.png" alt="(image: screenshot of the Calculator sheet.)" width="500"></center> <br>
<h4>colors </h4><br>
FlightRising's dragon colors exist in a color wheel. If one dragon has a Cream primary color and mates with an Antique dragon, the offspring can have a primary in Cream, Antique, or White. Since the colors were arbitrarily chosen by the developers, you won't know what to expect without referring to a color chart. The one below was made by <a href="https://flightrising.com/main.php?p=lair&tab=userpage&id=66886">Rauxel</a> and also contains the original, much smaller color range. <br>
<center><img src="/static/img/ent/fr_colorwheel.png" alt="(image: the color wheel by Rauxel.)" width="500"></center> <br>
When going for a particular color, the parents should be as close to that color as possible. I could manually count the colors in-between, but that's unreasonable when there's almost 200 colors. Instead, I have all the colors in a column and a little calculator to tell me how far over or under the parent's colors are. I enter primary, secondary, and tertiary colors of the goal dragon and dream dragon. The distance's magnitude and direction from the goal are calculated automatically. <br>
<br>
The formula for magnitude is =ABS(MATCH($C3,$F$2:$F$178, )-MATCH($B3,$F$2:$F$178, )). MATCH takes the color and returns the position within the color chart. ABS gives the absolute value of its parameter. In other words, I subtract the parent's colors from the goal's colors and get the absolute value. <br>
<center><img src="/static/img/ent/fr_magnitude.png" alt="(image: =ABS(MATCH($C3,$F$2:$F$178, )-MATCH($B3,$F$2:$F$178, )))" width="500"></center> <br>
The formula for direction is =IF((MATCH($C3,$F$2:$F$178,)-MATCH($B3,$F$2:$F$178,))>0,"↓","↑"). Here, it displays ↓ if the parent's colors are below the goal's, and ↑ if otherwise. <br>
<center><img src="/static/img/ent/fr_direction.png" alt="(image: =IF((MATCH($C3,$F$2:$F$178,)-MATCH($B3,$F$2:$F$178,))>0,'↓','↑'))"></center> <br>
The calculator ignores the fact that the color chart is a circle. Honestly, I don't breed along the extremes (white and pink), so I haven't fixed this yet. If you need to, you can find the distance between the parent color and the distance the nearest extreme (either Maize or Pearl), the distance between that extreme and the goal color, then add them together. If you're not sure which extreme is nearer, complete the process for both then take the smaller number - that's what the final formula's going to do anyways. <br>
<h4>breeding info</h4> <br>
These are the columns used by the breeding pages for validity and VLOOKUP. <br>
<h4>breeding day calculator</h4> <br>
If I need to know how long ago a dragon was bred, I can use today's date and the cooldown to find it out. The formula is very simple, just the given date minus the cooldown. <br>
<h3>project sheets </h3><br>
Here I list the dragons by project, tracking their genealogy, sex, colors, genes, and average distance from goal colors. <br>
<center><img src="/static/img/ent/fr_project.png" alt="(image: screenshot of the Boyfriend project sheet)" width="500"></center> <br>
<h4>father + mother </h4><br>
Dragons cannot breed with relatives within 5 generations. To ensure I'm keeping bloodlines separate, I plan a family tree separately from my spreadsheets. Using the father and mother's names, I can remember which family the dragon's in or if it's related to the other dragons at all. <br>
<h4>sex </h4><br>
Male or female, with colored conditional formatting so I can scan by sex more easily. <br>
<h4>primary, secondary, + tertiary colors </h4><br>
I usually need to use the color calculator on Calculators for this information. I list the parent's distance from the goal color in each field. In the first row, I list my goal colors for reference. <br>
<h4>gene 1, 2, + 3 </h4><br>
If the parent has one of the goal genes, I list it here. That way, I can prioritize by gene. I keep the other fields empty. For expensive genes, I only allow parents with genes of equal rarity, so the chance of passing down the goal gene is always 50/50. If my goal is a gene I can just buy, I don't care to track it since buying is easier than breeding. <br>
<h4>average </h4><br>
Genes are 50/50, but my goal colors are usually around a 1/20 chance. Consequently, I'm much more concerned about the color of a dragon than his genes. To give me a general evaluation of how close a dragon is to the goal colors, I use =AVERAGE(E3,G3,I3). The AVERAGE function adds its parameters and divides by the quantity of parameters given. If I had a parent with perfect colors, it would have an average of 0, so ideally, dragons with the lowest average are my most valuable for breeding. Genes usually factor in as well. <br>
<h2>that's all~ </h2>
In closing, I'll share a few of my cutest dragons (all of which are obviously using official FlightRising assets). See you two Thursdays from now! <br>
<center><img src="/static/img/ent/fr_dragons.png" alt="(image: Sand (male Sand Giraffe / Shadow Toxin / Rose Smoke Fae), Abbey (male Platinum Skink / Smoke Peregrine / Pearl Basic Tundra), Rune (male Eldritch Sphinxmoth / Eldritch Hawkmoth / Eldritch Runes Veilspun with eternal youth and dark sclera), Laguna (female Honeydew Cherub / Sanddollar Butterfly / Marigold Firefly Skydancer.)" width="500"></center>
<br>
Last updated May 26, 2021 <br>
<br>

@ -0,0 +1,69 @@
<!--210429,210402-->
<h1>sewing my own clothes </h1>
june 24, 2021<br>
#offtopic <br>
<br>
I've spent more time pattern-making than programming, so let's take a fashion break this week! <br>
<br>
<h2>my life story told through clothes </h2><br>
Hand-me-downs from older girls at school and church have been a staple in my wardrobe for the majority of my life. Anything that was too big, I kept around to grow into later. I loved them because they weren't like anything my peers were wearing. My cousin's old boxy denim dress with white embroidery received comments that I looked like I was from a different fashion era - maybe an insult, but it's a really curious way to be described. Also, sometimes they were old enough to swing back around to the cusp of fashion, like the story of my wide leg jeans. <br>
<br>
When I grew up, I thought I could finally invest in a few nice work and event clothes for my adult wardrobe, since everyone said I was finished growing. Then I get a freakish growth spurt in my early 20s. My doctor wasn't concerned, but nothing fit around my shoulders anymore...everything but hand-me-downs from a old boss's daughter. I welcomed the opportunity to once again lean into hand-me-downs and avoid the costly and demotivating search for clothing in my size. And with the initial pressure of having no clothes resolved, I never really tried to replace my wardrobe. I'm even on trend, since thrifting has exploded in popularity. <br>
<br>
After months of pandemic-era shelter-in-place orders, I crave normalcy and looking cute again. I still don't really have anything in <em>my</em> style, though, just other people's clothes. I'm at a more stable and independent point in my life, so I should dress like it. <br>
<br>
<h2>capsule wardrobe concept </h2><br>
I do nothing without a thesis. With a higher-level concept to guide me, everything down to the little details can stay consistent. Obviously taking a design-oriented approach applies to gamedev, too. It's kind of like a style guide for sewing. <br>
<br>
My functional goal is to fill out my wardrobe with some solid core pieces that are easy to mix and match. I'll worry about more fashion-oriented or niche pieces later. My more abstract concept stems from my story above - my attachment to my old hand-me-downs, my desire to have an individualized style, and a lingering pandemic preference for comfy, unfussy natural fibers. Technically, this translates to playing with the sizing of clothes and mixing of inspirations across decades, genders, and cultures. <br>
<br>
<h2>sketches </h2>
I'll make 3 mock turtlenecks, a button-down shirt, a bodice, a pair of pants, two shorts, a light bomber jacket, a kimono, two pajama sets, a swimsuit, and a few hair accessories. <br>
<br>
<a target="_blank" href="/static/img/ent/capsulewardrobe_sketches2.png">
<img src="/static/img/ent/capsulewardrobe_sketches2.png" alt="(image: a bomber jacket with a luminous light pink polyester and a gray, beige, and light brown lace, a bodice with a white bamboo viscose with pink cherry blossoms and blue birds, a kimono with black inky block batiste, a button-down short sleeve shirt with shank buttons, black piping, and a beetle print, and a mock turtleneck with thumbholes and a metallic foil knit)" width="500">
</a><br>
<b>Bomber</b>: I've had a favorite light rayon bomber jacket for 10 years, but it's wearing out and has some weird bleach stains. This is basically a knockoff of a <a href="https://www.ebay.com/itm/124967194636?hash=item1d18a0100c:g:1K8AAOSw7PdhctB3">BB Dakota design</a>. This one is a shiny pink polyester (a lustrous exception to my natural fiber rule) with multicolored lace trim, a metal zipper, and a beige drawstring. I think I can keep the old BBD jacket, too, but it needs embroidery or appliques over the stains. <br>
<br>
<b>Bodice</b>: This is a light layering top made from batiste. It will have ruffles at the shoulders and long straps from the waist to wrap around and tie in a bow. <br>
<br>
<b>Kimono</b>: I have a favorite purple floral chiffon kimono that has a fraying seam. After three repairs, it needs a semi-retirement. This design but with rayon batiste and a dark inky print...if Mood ever restocks the Penitentiary Block print! <br>
<br>
<b>泣き虫 (Cry Bug)</b>: An oversized poplin button-down with short sleeves and an oversized beetle print. It has black piping along seams, a pocket, and black glass shank buttons. The fit is meant to resemble your dad's shirt where the short sleeves come down too far, the armholes are too deep, and the pocket is too big, but the collar and length will be appropriately proportioned for me. It's white with an oversized print that visually plays up the unusually large proportions of the shirt. The piping and glass shank buttons remain the correct proportions, though. <br>
<br>
<b>Mock Turtle</b>: There's three light mock turtlenecks. This is my favorite staple, but I lost my charcoal gray one 2 moves ago. (RIP) One will be a foil knit with thumbholes, one is a striped knit, and one is a 4x2 rib knit. <br>
<br><br>
<a target="_blank" href="/static/img/ent/capsulewardrobe_sketches1.png">
<img src="/static/img/ent/capsulewardrobe_sketches1.png" alt="(image: A mock turtleneck with gray and white horizontal striped tissue knit, a mock turtleneck with 4x2 black rib knit, pants with seagrass green, brown, and tan plaid linen, a pair of shorts with natural linen with white, lime, and orange stripes and a multicolor fringe ribbon in mint, baby pink, brown, neon coral, baby blue, and straw, and a pair of cuffed shorts with a bow belt in dragon fruit printed linen.)" width="500">
</a><br>
<b>Pants</b>: Straight-legged plaid linen pants with a fly-front closure, belt loops, side pockets, and blind hems. Linen feels great in the summer and lends itself to a well-tailored staple. <br>
<br>
<b>Shorts</b>: Two pairs of linen shorts, one with stripes and striped fringe ribbon trim and the other with a dragon fruit print and cuffs and a bow belt. <br>
<br><br>
<a target="_blank" href="/static/img/ent/capsulewardrobe_sketches3.png">
<img src="/static/img/ent/capsulewardrobe_sketches3.png" alt="(image: a one piece long sleeve swimsuit with a mock turtleneck, an obi belt, three ruffles over the hips, and shorts, a yukata-style pajama set with bishop sleeves and berry red piping and dawn blue jacquard, and a pajama set with gathered sleeves, a gathered empire-waisted top, and gathered shorts with a sailor color and oversized rick rack trim and dark blue jacquard fabric with white bow print. )" width="500">
</a><br>
<b>Swimsuit</b>: The one piece swimsuit is made out of pastel seafoam and pink neoprene with pops of sulfurous yellow in an abstract swampy design. I actually lived around wetlands for most of my life, so it's cool to see a wetlands design instead of another tropical beach or flowering meadow print. It has long sleeves, a mock turtleneck, and an invisible zipper at the center back. The focus is the obi belt made from a contrasting black neoprene. There are three panel-and-tier ruffles over the bottom to give some illusion of volume against the closely-fitted belt and top without adding more unnecessary bulk. I'm not 100% on materials yet and may need to bring in a thinner swimwear tricot for the layers. <br>
<br>
<b>Pajama sets</b>: The pajamas are not my design. They are basically knock-offs from Yahoo! Japan shopping, since I don't feel like importing mall-tier pajamas. (The originals are <a href="https://store.shopping.yahoo.co.jp/open-clothes/xkunken-aihyx401.html?sc_i=shp_pc_search_itemlist_shsrg_img">Japanese-style yukata-style long sleeve pajama set for spring and fall</a> from <a href="https://store.shopping.yahoo.co.jp/open-clothes/">OPEN-CLOTHES</a> and <a href="https://store.shopping.yahoo.co.jp/kittyshop/lh20051312.html">summer pajama set for ladies in their 20s with flared collar and cute short sleeves</a> from <a href="https://shopping.geocities.jp/kittyshop/">kittyshop</a>.) The only real design change is using oversized rick rack instead of ruffles on that second set. Pajamas are the most pressing sartorial area in my life in which I need a self-assertion. People like to gift me pajamas, but they always ask me my size then buy a size or two down. Why won't anyone believe I'm not an XS! The one time I got something that fit, it was unwearably heavy fleece for my year-round hot climate. I don't want to be ungrateful or wasteful, though, so I've either crammed myself into tiny pajamas or sweated since I was little. That's so silly, especially now that I'm an adult. There's beautiful but unused jacquard fabric hanging in my closet. There's nothing stopping me from turning it into cute Japanese jacquard pajamas that actually fit. <br>
<br>
<h2>pattern-making and sewing so far</h2>
I'm working on the 泣き虫 button-down shirt first, but while waiting for the buttons to arrive, I went ahead and got one version of the mock turtleneck pattern done. <br>
<br>
<a target="_blank" href="/static/img/ent/capsulewardrobe_nakimushi_pattern.png">
<img src="/static/img/ent/capsulewardrobe_nakimushi_pattern.png" alt="(image: pattern pieces for the button-down shirt and a ruler and curve tool. )" width="500">
</a><br>
<a target="_blank" href="/static/img/ent/capsulewardrobe_mockturtle_pattern.png">
<img src="/static/img/ent/capsulewardrobe_mockturtle_pattern.png" alt="(image: pattern pieces for the button-down shirt and a ruler and curve tool. )" width="500">
</a><br>
I'm in the middle of sewing the button-down. The sleeves are cuffed and reinforced with interfacing (finally available again after the early pandemic mask-making frenzy) and decorated with the piping. Next, I'll set the sleeves in and work on the button placard. <br>
<br>
<a target="_blank" href="/static/img/ent/capsulewardrobe_nakimushi.png">
<img src="/static/img/ent/capsulewardrobe_nakimushi.png" alt="(image: incomplete button-down shirt with disconnected sleeves. )" width="500">
</a><br>
After the button-down is finished, I really want to tackle a swimsuit next. I was prioritizing everyday clothes, but I've been thinking it would more fun to have a swimsuit as soon as possible. Since I was planning to take my time with it, the obi swimsuit is not only made from the most challenging fabric, but it also has the most elaborate design. It'll be worth the effort, though. <br>
<br>
Anyway, thanks for reading an off-topic post. Back to gamedev next time! <br>
<br>
Last Updated January 11, 2021
<br>

@ -0,0 +1,41 @@
<!--210610,200429-->
<h1>how to attack a moving target </h1>
july 8, 2021<br>
#ai #character #combat #design #movement <br>
<br>
I'll share my tentative design for the attack-movement loop. <br>
<br>
The attack-movement loop needs to allow the character maintain attack range while attacking. The flow is complicated to follow, but this is how it works for now: <br>
<center><a target="_blank" href="/static/img/ent/attack-movement-loop-diagram.png">
<img src="/static/img/ent/attack-movement-loop-diagram.png" alt="(image: diagram of the attack movement loop)" width="500" height="233">
</a><br></center>
The code is color-coded by object. Warm gray is input, orange is the character's action module, yellow is the character, yellow-green is the character's equipment module, blue-green is the attack handler, blue is the AI's attack module, purple is the AI's movement module, pink is the AI, brown is the KnowledgeBase's MessageBus, and cool gray is the character's kinematic body. <br>
<br>
<h2>the loop explained </h2><br>
Upon attack input, the character sets up for attacking and creates an attack timer. On timeout, the character's weapon swings. If the character is out of range, the "out_of_range" signal is emitted. Otherwise, the weapon successfully swings, either emitting "target_dead" or "hit." <br>
<br>
The AI receives these signals. If the target was out of range, it sets up to follow attack target. <br>
<br>
Every AI tick, it prompts the character to pathfind to the target then sets the character's velocity to the current_dot (the first node the character is trying to reach in path) minus the character's global_position. <br>
<br>
Every frame, the character's _process(delta) method calls move_and_collide with velocity * speed * delta. If the character's velocity isn't 0,0, the "moved" event is published to the Knowledge Base's MessageBus. <br>
<br>
The movement handlers are subscribed to "moved," and will emit signals if the character reached either the next waypoint (the target or the chosen goal point at the end of the path) or the current dot (the first point along the pathfinding between the character and the goal point). <br>
<br>
The AI receives these signals. If the next waypoint is reached, it's removed from the list of waypoints, the "arrived_at_attack_target" signal is emitted, and movement is cleared. <br>
<br>
Then the AI receives the "arrived_at_attack_target" signal and prompts the character to begin the attack all over again. <br>
<br>
<h2>in-game </h2><br>
It works in-game, too, but it's pretty janky, especially without animations. If the slime is slow enough, the player character attacks until it gets too far away, moves back in range, and continues attacking. If it's too fast, though, she never gets to attack and jitters constantly after the slime. <br>
<br>
Too fast: <br>
<center><img src="/static/img/ent/attack-follow.gif" alt="(image: Angel follows slime)"></center>
<br>
I'll work it out sooner or later, dependent on how hectic moving turns out to be. <br>
<br>
(By the way, that's my first gif recorded and edited entirely in ffmpeg. It's not pretty, but at least I could write my own bash script without relying on copypasta forum code this time. I was trying to follow the documentation website before, but it's arcane. The man page is much easier to understand and search through.) <br>
<br>
<br>
Last Updated November 13, 2021
<br>

@ -0,0 +1,52 @@
<!--210610,200429-->
<h1>skills aren't a manor; they're the DMV </h1>
april 7, 2022<br>
#skill #redesign <br>
<br>
<h2>designing on autopilot </h2><br>
Phases are necessary for skills to be reactive. For instance, take Blessed Purity: <i>Cure 2 poisons from target. Heal for 35 per poison removed</i>. So the Cure keyword comes first, and Heal cannot activate until the Cure phase resolves. To lay out these phases, I partially copied Guild War's skill effects without much thought: <a href="https://wiki.guildwars.com/wiki/Initial_effect">initial effect</a> -> main function (not sure if there's even a name for this) -> <a href="https://wiki.guildwars.com/wiki/End_effect">end effect</a>. Most keywords are thrown into the main phase, anything that needs to happen immediately goes in initial, and anything that happens at the end goes in the end. <br>
<br>
Naturally, I had a lot of problems with flow. Some keywords (like Bleeding) can last up to half a minute, while others (like Attack) are executed instantly. When I let the skill run without breaks, the end effect executed long before earlier keywords finished up. When I forced keywords to execute one at a time, I had projectiles that awkwardly stuck around until an unrelated keyword timed out. <br>
<br>
It's time to actually turn my brain on and design with diagrams. <br>
<br>
<br>
<h2>building the skill like a manor</h2>
<center><a target="_blank" href="/static/img/ent/SkillManor.png">
<img src="/static/img/ent/SkillManor.png" alt="(image: diagram of a skill. Imagine the entire skill is a manor. The main phase is the main building, while the initial and end phases are additional wings. Within each section are rooms (or keywords). The entry and exit point of the keyword room are conditional statements. The space between wings represents the arbitrary entry and exit points of phases.)" width="500" height="662">
</a></center><br>
It makes sense if you don't really think about it. After all, that makes the flow look clean and linear. <br>
<center><a target="_blank" href="/static/img/ent/SkillManorFlow1200.png">
<img src="/static/img/ent/SkillManorFlow1200.png" alt="(image: diagram of a skill. All initial keywords activate in order, the initial phase is complete, all main keywords activate, phase complete, same for end keywords and effect.)" width="500" height="80"></a></center> <br>
<br>
<h2>criticism </h2>
The concept of a phase brings nothing to skills. Even Guild Wars seems to use "initial effect" and "end effect" more like conditions than integral structure. The majority of skills don't operate in phases anyway. <br>
<br>
Some keywords need to know what skill they belong to, what skill other keywords belong to, and who their user and target are. The poorly understood flow of skills sometimes causes this information to be freed with the skill before it's needed. <br>
<br>
Also, in general, keywords within the same phase run sequentially when that isn't necessarily my intention. It kind of invalidates the entire concept of phases, too. Sure, an end effect keyword is certainly called after an initial effect keyword, but in most versions of Blessfrey's skill.gd, the flow is identical when putting the same keywords one after the other into the main phase. <br>
<br>
It makes me wonder what a phase even is. I can't even define the boundaries of the phase in the diagram because it's so arbitrary. Do my skills need phases at all? <br>
<br>
Keywords already have structure built into them. Keywords lock their effect behind an entry condition (like curing 1 or 2 poisons) and guard the exit with another condition (like bleeding lasting 30 seconds). It would make more sense if end effect keywords were replaced by keywords that trigger upon the completion of earlier keywords, while other keywords are free to fire off simultaneously. <br>
<br>
<br>
<h2>skills should feel like going to the DMV</h2>
Most DMVs I've suffered through in my life have been all-day affairs of standing in lines that stretch around the building through the bad side of town, attended by only one service desk manned by a slow, error-prone clerk. Honestly, that's not too far off from my original skill design. I'm not referring to <i>those</i> DMVs, though. The DMV that's closest to my new location is much more efficient, lets you sit as you wait, and has (novel concept) multiple service windows. That's a much better model for my skill. <br>
<center><a target="_blank" href="/static/img/ent/SkillDMV.png">
<img src="/static/img/ent/SkillDMV.png" alt="(image: illustration of a skill. The skill's DMV has an entry point that leads to the welcome kiosk where each keyword must register its trigger and exit cue. All keywords wait in the same waiting room. Whenever the DMV calls a trigger (like health below 33%), that keyword will be actively served by a clerk until satisfied.)" width="500" height="351"></a></center> <br>
<br>
Since my "DMV" will be a separate entity from the skill (basically an <a href="https://www.blessfrey.me/diary/entries/210402">event handler</a>), the fate of the skill is less important when it comes to data it holds inside. Flow is also more controlled, since the skill itself cannot progress its own current state. <br>
<br>
As for flow, obviously, skills without triggers can fire off immediately. The DMV will connect to the <a href="https://www.blessfrey.me/diary/entries/210402">KnowledgeBase</a> and begin listening for any event topics that could trigger the keywords. Once a keyword's conditions are met, it will become active and be applied to the target. The DMV also sets up a timer for its duration or listens for exit cues to know when to remove the keyword. Some keywords will never activate, but they will be freed once they had their chance. If Blessed Purity cures 0 poisons, heal will receive a sad "0" and free quietly. <br>
<br>
Under the DMV model, the skill's flow becomes more circular. <br>
<center><a target="_blank" href="/static/img/ent/SkillDMVFlow1200.png">
<img src="/static/img/ent/SkillDMVFlow1200.png" alt="(image: diagram of a skill. Each keyword registers triggers and exit cues with the DMV. The DMV subscribes to the topics and listens for triggers and exit cues. A trigger is received and conditions are met, so the keyword becomes active. The keyword is applied. The removal conditions are met, so the keyword is freed. The DMV continues to listen until all keywords had their chance to activate.)" width="500" height="180"></a></center> <br>
<br><br>
<h2>superior design? hopefully </h2>
Nothing's ever going to be perfect, but taking time to plan and be intentional gives a better outcome. Never thought I'd prefer the DMV, but you can learn design concepts from anywhere. <br>
<br>
<br>
Last updated April 7, 2022
<br>

@ -0,0 +1,140 @@
<!--210610,200429-->
<h1>css grid with angular (break into 2?)</h1>
may 5, 2022<br>
#angular #css #webdev <br>
<br>
<h2>start from the beginning</h2><br>
<a href="https://angular.io/guide/setup-local">Angular's documentation</a> shares the process for beginning a new project, but I'll explain it, too. <br>
<br>
<ul>
<li>Installation process will differ depending on your operating system. These commands are for Linux Mint, downstream from Ubuntu and Debian. </li>
<li><a href="https://nodejs.org/en/">Install Node.js </a> </li>
<li><code>sudo curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash</code></li>
<li><code>sudo apt-get install -y nodejs</code></li>
<li><code>sudo apt install -y build-essential</code></li>
<li><code>sudo npm install -g @angular/cli</code> (you can decide whether to share analytics) </li>
</ul>
<br>
<h2>grab the template </h2>
Now you're ready to start an Angular project. Begin in the folder you'd like your project folder to be. Instructions and files are also available on <a href="https://github.com/angular/angular-cli">Angular's github</a>. <br>
<br>
<ul>
<li><code>ng new Example</code> (accept Angular routing and CSS) Change out the name 'Example' with your project name. </li>
<li>Now you'll have a folder named 'Example,' containing your skeleton. If you didn't already have git initialized in the folder, it'll automatically do so for you. </li>
</ul>
<h2>test run </h2>
Now you can run your example project in the browser. <br>
<br>
<ul>
<li><code>cd Example/ </code></li>
<li><code>npm start </code><li>
<li>Now you can browse to your project. By default, it used port 4200, so it's viewable @ <a href="http://localhost:4200/">http://localhost:4200/</a> </li>
<li>When you're done, stop the script by entering ctrl + c into the terminal. </li>
</ul>
<br>
If you see the page below, you have everything you need to get started on your own project. <br>
<center><a target="_blank" href="/static/img/ent/angular_examplepage.png">
<img src="/static/img/ent/angular_examplepage.png" alt="(image: the example webpage that comes with the skeleton files.)" width="500" height="505">
</a></center><br>
<br>
<br>
<h2>adding custom components </h2>
With a basic HTML/CSS website (like blessfrey.me), the CSS Grid allows you to align its nested divs into rows and columns. Angular allows you to break each area into a <a href="https://angular.io/guide/component-overview">component</a> to be styled by the grid. <br>
<br>
I'll use the petsite browser game I'm making as an example: <br>
<center><a target="_blank" href="/static/img/ent/lemonland_cssgrid.png">
<img src="/static/img/ent/lemonland_cssgrid.png" alt="(image: planned layout of my example page: two buffers at the sides named Left and Right, Header at the top, Footer at the bottom, Chatter in between Header and Footer and left of Right, Zone and PDA in the second row from the top, Alert in the third, and Action and Partner in the fourth.)" width="500" height="478">
</a></center><br>
<br>
This webpage has some major elements that will be reused on many pages, which I would like to keep in a general grid. <br>
<ul>
<li>header - wgill hold the logo, home page link, account links </li>
<li>left - a centering buffer, will have a background </li>
<li>right - a centering buffer, will have a background </li>
<li>footer - hold copyright, footer links </li>
</ul>
Then I'd like to nest a grid inside to contain elements more specific to this kind of page. <br>
<ul>
<li>zone - a graphic representing the player's location </li>
<li>pda - a control panel with news, email, friend's list, quick links, inventory, etc </li>
<li>alert - a window for dialog, system messages, etc </li>
<li>action - a window containing player choices and dialog responses </li>
<li>partner - basic info about the active pet </li>
<li>chatter - a live-time chat box </li>
</ul>
<br>
Each of these will need to be added as components via the terminal. <br>
<br>
<ul>
<li><code>cd app/ </code></li>
<li><code>ng g c header </code></li>
<li><code>ng g c zone </code></li>
<li>...and so on. </li>
</ul>
These commands fill your app folder with components for your grid. <br>
<br>
<center><a target="_blank" href="/static/img/ent/angular_componentdir.pngid.png">
<img src="/static/img/ent/angular_componentdir.png" alt="(image: screenshot of each folder generated for each component inside the app folder.)" width="500" height="273">
</a></center><br>
<br>
<h2>writing the css grid </h2>
There's a few HTML files and a few CSS files in your project folder. <br>
<ul>
<li><code>src/index.html</code> is the basic webpage </li>
<li><code>src/app/app.component.html</code> is the app displayed in the webpage </li>
<li>Then there's an HTML file for each component, like <code>src/app/partner/partner.component.html</code> </li>
<br>
<h3>index.html + styles.css </h3>
I'm keeping these two very general, since most of the content on the page is part of the game. <br>
<code><!doctype html></code><br>
<code><html lang="en"></code><br>
<code><head></code><br>
<code> <meta charset="utf-8"></code><br>
<code> <title>lemonland</title></code><br>
<code> <base href="/"></code><br>
<code> <meta name="viewport" content="width=device-width, initial-scale=1"></code><br>
<code> <link rel="icon" type="image/x-icon" href="favicon.ico"></code><br>
<code></head></code><br>
<code><body></code><br>
<code><router-outlet></router-outlet></code><br>
<code> <div class="app-container"></code><br>
<code> <app-root></app-root></code><br>
<code> </div></code><br>
<code></body></code><br>
<code></html></code><br>
<br>
Then the CSS... <br>
<code>* {</code><br>
<code> padding: 0;</code><br>
<code> margin: 0;</code><br>
<code> font-family: verdana;</code><br>
<code> font-size: 18px;</code><br>
<code>}</code><br>
<code>body {</code><br>
<code> background-color: black;</code><br>
<code> color: MidnightBlue;</code><br>
<code>}</code><br>
<br>
<h3>app.component.html + app.component.css </h3>
These two contain all the subcomponents generated earlier. <br>
<code><div class="container"></code><br>
<code> <app-header></app-header></code><br>
<code> <app-left></app-left></code><br>
<code> <router-outlet></router-outlet></code><br>
<code> <div class="content-grid"></code><br>
<code> <app-zone></app-zone></code><br>
<code> <app-pda></app-pda></code><br>
<code> <app-chatter></app-chatter></code><br>
<code> <app-alert></app-alert></code><br>
<code> <app-action></app-action></code><br>
<code> <app-partner></app-partner></code><br>
<code> </div></code><br>
<code> <app-right></app-right></code><br>
<code> <app-footer></app-footer></code><br>
<code></div></code><br>
<br>
Then the CSS grid takes place in the CSS file. <br>
<br>
<br>
Last updated April 12, 2022
<br>

@ -0,0 +1,140 @@
<!--210610,200429-->
<h1>modded Oblivion</h1>
may 5, 2022<br>
#angular #css #webdev <br>
<br>
<h2>My mod list, funny screenshots, my aesthetic that I am going for, mods that I have made, generally my love for this silly game</h2><br>
<a href="https://angular.io/guide/setup-local">Angular's documentation</a> shares the process for beginning a new project, but I'll explain it, too. <br>
<br>
<ul>
<li>Installation process will differ depending on your operating system. These commands are for Linux Mint, downstream from Ubuntu and Debian. </li>
<li><a href="https://nodejs.org/en/">Install Node.js </a> </li>
<li><code>sudo curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash</code></li>
<li><code>sudo apt-get install -y nodejs</code></li>
<li><code>sudo apt install -y build-essential</code></li>
<li><code>sudo npm install -g @angular/cli</code> (you can decide whether to share analytics) </li>
</ul>
<br>
<h2>grab the template </h2>
Now you're ready to start an Angular project. Begin in the folder you'd like your project folder to be. Instructions and files are also available on <a href="https://github.com/angular/angular-cli">Angular's github</a>. <br>
<br>
<ul>
<li><code>ng new Example</code> (accept Angular routing and CSS) Change out the name 'Example' with your project name. </li>
<li>Now you'll have a folder named 'Example,' containing your skeleton. If you didn't already have git initialized in the folder, it'll automatically do so for you. </li>
</ul>
<h2>test run </h2>
Now you can run your example project in the browser. <br>
<br>
<ul>
<li><code>cd Example/ </code></li>
<li><code>npm start </code><li>
<li>Now you can browse to your project. By default, it used port 4200, so it's viewable @ <a href="http://localhost:4200/">http://localhost:4200/</a> </li>
<li>When you're done, stop the script by entering ctrl + c into the terminal. </li>
</ul>
<br>
If you see the page below, you have everything you need to get started on your own project. <br>
<center><a target="_blank" href="/static/img/ent/angular_examplepage.png">
<img src="/static/img/ent/angular_examplepage.png" alt="(image: the example webpage that comes with the skeleton files.)" width="500" height="505">
</a></center><br>
<br>
<br>
<h2>adding custom components </h2>
With a basic HTML/CSS website (like blessfrey.me), the CSS Grid allows you to align its nested divs into rows and columns. Angular allows you to break each area into a <a href="https://angular.io/guide/component-overview">component</a> to be styled by the grid. <br>
<br>
I'll use the petsite browser game I'm making as an example: <br>
<center><a target="_blank" href="/static/img/ent/lemonland_cssgrid.png">
<img src="/static/img/ent/lemonland_cssgrid.png" alt="(image: planned layout of my example page: two buffers at the sides named Left and Right, Header at the top, Footer at the bottom, Chatter in between Header and Footer and left of Right, Zone and PDA in the second row from the top, Alert in the third, and Action and Partner in the fourth.)" width="500" height="478">
</a></center><br>
<br>
This webpage has some major elements that will be reused on many pages, which I would like to keep in a general grid. <br>
<ul>
<li>header - wgill hold the logo, home page link, account links </li>
<li>left - a centering buffer, will have a background </li>
<li>right - a centering buffer, will have a background </li>
<li>footer - hold copyright, footer links </li>
</ul>
Then I'd like to nest a grid inside to contain elements more specific to this kind of page. <br>
<ul>
<li>zone - a graphic representing the player's location </li>
<li>pda - a control panel with news, email, friend's list, quick links, inventory, etc </li>
<li>alert - a window for dialog, system messages, etc </li>
<li>action - a window containing player choices and dialog responses </li>
<li>partner - basic info about the active pet </li>
<li>chatter - a live-time chat box </li>
</ul>
<br>
Each of these will need to be added as components via the terminal. <br>
<br>
<ul>
<li><code>cd app/ </code></li>
<li><code>ng g c header </code></li>
<li><code>ng g c zone </code></li>
<li>...and so on. </li>
</ul>
These commands fill your app folder with components for your grid. <br>
<br>
<center><a target="_blank" href="/static/img/ent/angular_componentdir.pngid.png">
<img src="/static/img/ent/angular_componentdir.png" alt="(image: screenshot of each folder generated for each component inside the app folder.)" width="500" height="273">
</a></center><br>
<br>
<h2>writing the css grid </h2>
There's a few HTML files and a few CSS files in your project folder. <br>
<ul>
<li><code>src/index.html</code> is the basic webpage </li>
<li><code>src/app/app.component.html</code> is the app displayed in the webpage </li>
<li>Then there's an HTML file for each component, like <code>src/app/partner/partner.component.html</code> </li>
<br>
<h3>index.html + styles.css </h3>
I'm keeping these two very general, since most of the content on the page is part of the game. <br>
<code><!doctype html></code><br>
<code><html lang="en"></code><br>
<code><head></code><br>
<code> <meta charset="utf-8"></code><br>
<code> <title>lemonland</title></code><br>
<code> <base href="/"></code><br>
<code> <meta name="viewport" content="width=device-width, initial-scale=1"></code><br>
<code> <link rel="icon" type="image/x-icon" href="favicon.ico"></code><br>
<code></head></code><br>
<code><body></code><br>
<code><router-outlet></router-outlet></code><br>
<code> <div class="app-container"></code><br>
<code> <app-root></app-root></code><br>
<code> </div></code><br>
<code></body></code><br>
<code></html></code><br>
<br>
Then the CSS... <br>
<code>* {</code><br>
<code> padding: 0;</code><br>
<code> margin: 0;</code><br>
<code> font-family: verdana;</code><br>
<code> font-size: 18px;</code><br>
<code>}</code><br>
<code>body {</code><br>
<code> background-color: black;</code><br>
<code> color: MidnightBlue;</code><br>
<code>}</code><br>
<br>
<h3>app.component.html + app.component.css </h3>
These two contain all the subcomponents generated earlier. <br>
<code><div class="container"></code><br>
<code> <app-header></app-header></code><br>
<code> <app-left></app-left></code><br>
<code> <router-outlet></router-outlet></code><br>
<code> <div class="content-grid"></code><br>
<code> <app-zone></app-zone></code><br>
<code> <app-pda></app-pda></code><br>
<code> <app-chatter></app-chatter></code><br>
<code> <app-alert></app-alert></code><br>
<code> <app-action></app-action></code><br>
<code> <app-partner></app-partner></code><br>
<code> </div></code><br>
<code> <app-right></app-right></code><br>
<code> <app-footer></app-footer></code><br>
<code></div></code><br>
<br>
Then the CSS grid takes place in the CSS file. <br>
<br>
<br>
Last updated April 12, 2022
<br>

@ -87,10 +87,10 @@ def find_gallery(name):
gal.append([["stilllife.png","Still Life"],["dragon.png","lazy Abbey"], ["FlightRising.png","Abbey's sprite in FlightRising"],["BlackReshiram_Artfight.png","2022 Artfight attack by BlackReshiram"]])
if name == "Angel":
gal.append("Blessfrey")
gal.append([["Chibipixel.png","One of Angel's sprites. I prefer taller, less cartoony sprites, but this style is so popular it was worth trying."],["pixelangel.png","Pixelart"],["AngelHeadphones.png","Pencil sketch"],["AngelHeadshot.png","Headshot of Angel and all her hair"],["picrew.png","made in あの子がこっちを見ている on Picrew"]])
gal.append([["Chibipixel.png","One of Angel's sprites. I prefer taller, less cartoony sprites, but this style is so popular it was worth trying."],["pixelangel.png","Pixelart"],["AngelHeadphones.png","Pencil sketch"],["AngelHeadshot.png","Headshot of Angel and all her hair"],["picrew.png","made in あの子がこっちを見ている on Picrew"],["AriesAngel.png","made in なさや式CPメーカー on Picrew"]])
if name == "Aries":
gal.append("Blessfrey")
gal.append([["picrew.png","made in あの子がこっちを見ている on Picrew"]])
gal.append([["picrew.png","made in あの子がこっちを見ている on Picrew"],["AriesAngel.png","made in なさや式CPメーカー on Picrew"]])
if name == "Aristen":
gal.append("Black Desert Online")
gal.append([["menu.jpg","Aristen's fancy set"], ["scarf.jpg", "Newbie Aristen"], ["marine.jpg","Aristen in the Epheria Marine Classic Set"]])

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@ -24,6 +24,8 @@
["Tessa.png","Tessa in athleisure"],
["WISE.png","WISE, a lost Canaanite virtual assistant."],
["Rune_bank.png","Learning about American culture with Rune"],
["HeliaWarrior.png","Helia, copying the pose from Warrior's concept art for Guild Wars"],
["RuneBlessSketch.png","Rune and Bless"],
["pencilsketchangelchloe.png","Angel and Chloe"],
["dgbanner.png","Old Blessfrey character art"],
["Willa.png","The original concept for Helia, back when her name was Willa"]

Loading…
Cancel
Save