diff --git a/src/articles b/src/articles index 1fd4dd4..3ee5db9 100644 --- a/src/articles +++ b/src/articles @@ -1,14 +1,40 @@ -new entry every 2 weeks on thursdays -new summary every month (doesn't count as one of the entries) +26: entry every 2 weeks on thursdays +12: summary every month (doesn't count as one of the entries) + 1: yearly summary +39: total articles -everything's coming together: a new website - new website announcement, plans for the future -gator swimsuit - full design process from concept to sewing +everything's coming together: a new website - new website announcement, old website process, plans for the future skills aren't a manor; they're the DMV - skill system redesign +no-legs the cat - demo announcement +blessfrey rpg - blessfrey announcement +lemonland: adventure petsite - lemonland announcement +#weeklygamejam - sttmt announcement +blessfrey over the years - screenshot gallery + the achievement system is the central nervous system of my game (part 1) - defining achievements in Blessfrey designing an achievement system (part 2) - diagramming the achievement system -no-legs the cat - demo announcement -animal parade - collection announcement -church girls - collection announcement +coroutines explanation +system diagrams: designing inventory +character refactor +skillmaker +make a godot plug-in +enemy patrol +attack-movement loop +hostility + factions + +rss feed +css grid with angular + +writing a game design document +common tropes I notice + +gator swimsuit - full design process from concept to sewing +animal parade - collection announcement, presentation +church girls - collection announcement, presentation + +playing FR with spreadsheets + +planescape torment - game journal, assess as a gamedev divine divinity - game journal, assess as a gamedev arcanum - game journal, assess as a gamedev YIIK - game journal, assess as a gamedev @@ -16,9 +42,9 @@ FlightRising - game journal, assess as a gamedev Verpets - game journal, assess as a gamedev oblivion - game journal, assess as a gamedev Goddess of Atvatabar - reading journal, assess for themes, etc -Jeremiah - some takeaways from Bible reading +lkj july diary - no daily. just short summary of generalized activities. IRL stuff is okay but nothing too detailed or off-topic - - +august diary +september diary diff --git a/src/diary/entries/200806 b/src/diary/entries/200806 new file mode 100644 index 0000000..5c98090 --- /dev/null +++ b/src/diary/entries/200806 @@ -0,0 +1,23 @@ + +

what is blessfrey?

+august 6, 2020
+#game
+
+Blessfrey is a 2D action RPG developed for PC by Chimchooree.
+
+ + (image: Lots of Angels and other characters at a shopping center) +
+
+The game is designed to pit your skill and creativity against a series of combat and puzzle challenges while exploring the depths of the downtown dungeon.
+
+Class progression is free-form, and virtually no decision is permanent. At character creation, you will choose a permanent First Class, but you can unlock new classes for multiclassing during gameplay. Swap out Second Classes to find a combination to overcome challenges and express your playstyle.
+
+Each class has its own style of skills associated with it. Skills are individual powers gained through gameplay which give specific effects. Your skillbar only has 8 skill slots and can only be edited in safe areas. The challenge comes from discovering effective strategies and synergies against the next area.
+
+Skills are gained during exploration. As you find new areas, encounter enemies, and interact with your surroundings, you will internalize those experiences as new skills. There are multiple paths to learning, so you are free to focus on your favorite parts of the game.
+
+Blessfrey has been lots of fun to work on. I hope you enjoy it once a demo and eventually a game drops.
+
+Last updated June 8, 2021
+
diff --git a/src/diary/entries/200903 b/src/diary/entries/200903 new file mode 100644 index 0000000..0932b9d --- /dev/null +++ b/src/diary/entries/200903 @@ -0,0 +1,70 @@ + +

my first game jam - #weeklygamejam

+september 3, 2020
+#gamejam
+
+WeeklyGameJam 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.
+
+
(image: Key art of Elwell and Small Thing, buried in dogs)

+
+

small thing that makes things

+
+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 itch.io.
+
+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.
+
+

positives

+
+For a week-made game, I think STTMT is pretty cute, and I'm pretty happy with it.:)
+
+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.
+
+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.
+
+

mistakes

+
+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 Joshua McLean pointed this out during his stream. Having more eyes on your game is so important.
+
+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?
+
+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.
+
+

screenshots + progression

+
+ + (image: Elwell and Small Thing in a jumble of dogs) +
+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.
+

+ + (gif: Elwell rides a broken-physics dog across the valley) +
+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.
+

+ + (image: Elwell watches the rainfall of dogs from his window) +
+Dogs rain down upon Elwell's animal shelter in the finished version.
+

+ + (image: Dialog) +
+Dialog between Elwell and his boss.
+
+
+

conclusion

+
+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.<3
+
+

credits

+ +
diff --git a/src/diary/entries/200917 b/src/diary/entries/200917 new file mode 100644 index 0000000..f6be89e --- /dev/null +++ b/src/diary/entries/200917 @@ -0,0 +1,73 @@ + +

coroutines in godot engine

+september 17, 2020
+#coroutines #godot #programming
+
+Coroutines are functions that, instead of running to completion, can yield until certain criteria are met. Godot Engine supports coroutines through yield ( Object object=null, String signal=""), resume, and the GDScriptFunctionState object.
+
+

why use a coroutine?

+
+Coroutines allow for scripted game scenarios that respond dynamically to the player and the changing game world. They let you bounce between functions, step-by-step, and respond to interruptions. This means functions can be automatically called at the completion of other functions, animations, player actions, in-game events, or timers. Add in interruptions and conditionals, and you have a tool for building a responsive game world.
+
+

stoplight example

+
+As a basic example of coroutines in Godot Engine, I made a stoplight. Follow along with my code on GitLab.
+
+In my example, the light changes every few seconds, going from green, yellow, then finally red. The light changes immediately if the Walk Button is pressed. This project demonstrates methods that can wait, resume, and be affected through player action.
+
+
+ + (gif: demonstration) +
+
+
+

how does it work?

+
+

node hierarchy

+
+(image: node hierarchy - Root is a node named Main. It's children are TextureRect BG, AnimatedSprite Stoplight, Sprite WalkButton, and a Label. Stoplight's child is a Sprite. WalkButton's child is a TextureButton.)
+
+
+I have a TextureRect background, an AnimatedSprite stoplight, a Sprite walk button with a TextureButton, and a label for displaying a timer. Since this is a simple example, most of the code is attached to the root. It's better to have code closer to where it's being used and to watch your separation of concerns in real projects, though.
+
+

animation

+
+
+(image: the AnimatedSprite Stoplight has 4 animations - default (which is no light), green, red, and yellow.)
+

+The light is changed by setting its animation to one of these options. Each is one-frame - just the stoplight with the one or none of the lights colored in.
+

the code

+
+This project has two scripts: Main.gd, which is attached to the root node, and Label.gd, which is attached to the Label.
+
+Main.gd - code available on GitLab
+
+(image: Main script.)
+
+
+Label.gd - code available on GitLab
+
+(image: Label script.)
+
+
+

how the code works

+
+At _ready(), wait() is assigned to the GDScriptFunctionState result and is called for the first color, green. _ready() yields until the given function wait() is completed.
+
+The wait method yields for the given amount of seconds then sets the stoplight to the given color.
+
+At wait()'s completion, _ready() calls wait() for yellow, then red. Each is called one at a time, waiting for the color to complete before moving on.
+
+

interrupting the stoplight

+
+The Wait Button interrupts the wait times between colors. Before _ready() yields, it connects the 'pressed' signal on the Wait Button.
+If the Wait Button is clicked during wait()'s yield, the GDScriptFunctionState result resumes immediately, ignoring wait()'s yield timer. This time, result has a string arg 'interrupted on green', so it will print the result, change the stoplight's color, then print 'done: green'. The wait method is complete, so _ready() resumes and calls wait() for the next color.
+
+

applications

+
+The outcomes in this example can be swapped out with anything. I use coroutines in Blessfrey's skills to manage the flow of phases from activation, different phases of effects, cooldown, and interactions with any counters. I also use it in the basic weapon attack so the character continuously swings at the rate of his attack speed until he cancels, uses a skill, or moves. It could also be used for something like cars that stop and honk when the player walks in front of them then drive off once the path is clear.
+
+Coroutines enable lots of practical ways to improve the flow and interactivity of your game, so just keep experimenting.
+
+Last updated June 8, 2021
+
diff --git a/src/diary/entries/201029 b/src/diary/entries/201029 new file mode 100644 index 0000000..30d43cc --- /dev/null +++ b/src/diary/entries/201029 @@ -0,0 +1,90 @@ + +

blessfrey in japanese

+october 29, 2020
+#godot #internationalization #localization #translation
+
+Instead of hard-coding text, keep it in a spreadsheet instead. It's easier to organize, edit, and it also makes possible future translations a much smoother process.
+
+I followed along with GoTut's "Localisation in Godot" guide, but the process is pretty simple. It's a great guide, so honestly just follow theirs instead. I'll echo the process here in case it's taken down, though.
+
+

step 1 - make your spreadsheet

+
+
+ + (image: spreadsheet with three columns - id, en, and ja. id has ids, en has English text, and ja has Japanese text.) +
+
+
+Instead of writing your text directly into Godot, refer to the text by ids instead. These ids will be kept in the first column of your spreadsheet. Don't use spaces.
+
+The rest of the columns will hold the corresponding text in different languages. Name these columns after the language's locale code. English's is en, and Japanese's is ja. You can find the more codes in the Godot Docs.
+It works just fine if you're only using one language. If you have multiple languages but don't provide a translation for a specific id, whenever your game is set to that language, references to that script will show blank text.
+
+Save your spreadsheet as a .CSV file.
+
+
+

step 2 - import your spreadsheet

+
+Make sure your .CSV spreadsheet is in your game folder, so Godot can automatically import files. Wherever you import your text, make sure you check 'Comma' for the delimiter or separator options. You'll get a few .TRANSLATION files.
+
+
+(image: Project Settings>Localization>Translations shows a TRANSLATION file for each language.)
+
+
+From Godot's top menu, go to Project>Project Settings...>Localization>Translations and add all your .TRANSLATION files. They'll be right next to wherever you saved your .CSV.
+
+
+

step 3 - refer to your ids in your scripts

+
+
+(image: example of an id used in a script)
+
+
+It's really simple stuff. Anywhere you would have written a string, like "quit", you instead use its id wrapped in tr(). So instead of label.set_text("quit"), you'd write label.set_text(tr("quit_game")). In this example, the id is "quit_game" and its corresponding text in English is "quit." +
+
+

step 4 - set the game's language

+
+
+(image: example of setting the locale in a script)
+
+
+Set the locale in your script, somewhere like _ready() or on a 'change language' button. Here's the line for setting the locale: TranslationServer.set_locale("ja") +
+
+

step 5 - continue adding to your spreadsheet

+
+Now that everything's in place, you can keep adding new ids and translations, and Godot will automatically use your changes in-game.
+
+
+

step 6 - insert values into text

+
+Languages differ in syntax, so use format strings.
+
+In your spreadsheet, write %s where your inserted text will go. +
+
+(image: in the .CSV, id= character_level, en= Level %s, ja= レバル %s)
+
+
+Then in your script, write tr("key") % inserted_val in place of the formatted string.
+
+
+(image: in the script, set_text(tr("character_level") % String(level))
+
+
+Now the formatted string will appear in-game.
+
+
+(image: screenshot: Level 1)(image: screenshot: レベル 1)
+
+
+

that's the basics

+
+(image: Blessfrey screenshot in Japanese)
+
+
+It all comes together for an old screenshot of Blessfrey's main menu in my broken Japanese. やべーな!
+
+Last updated June 8, 2021
+
diff --git a/src/diary/entries/201210 b/src/diary/entries/201210 new file mode 100644 index 0000000..d042362 --- /dev/null +++ b/src/diary/entries/201210 @@ -0,0 +1,51 @@ + +

common tropes from media

+december 10, 2020
+#writing
+
+
+I like collecting common tropes from games I play. Maybe it can it root out cliches? Or inspire some game beats? Here's a few tropes spotted in multiple games ~
+
+If you're worried about spoilers, games mentioned are Arcanum, The Cat Lady, Divine Divinity, Dreamfall: The Longest Journey, Fable, Fire Emblem, Guild Wars, Guild Wars 2, Half-Life, Jade Empire, Legend of Zelda: A Link to the Past, The Longest Journey, Mass Effect, Neverwinter Nights 2, Oblivion, Persona 4, Planescape: Torment, RuneScape 2
+
+
+

opening scene + first level

+ +

+

protagonist

+ +

+

levels, story points, fights

+ +

+

side content

+ +
+last updated: 2/21/2021 +
diff --git a/src/diary/entries/201224 b/src/diary/entries/201224 new file mode 100644 index 0000000..70a6565 --- /dev/null +++ b/src/diary/entries/201224 @@ -0,0 +1,43 @@ + +

blessfrey graphic updates + mockups

+december 24, 2020
+#mockups #screenshots
+
+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.)
+
+
+ + (image: Cassia and Bad Cat on the pink carpet tilemap) +
+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).
+

+ + (image: Angel in a periwinkle room full of Bad Cats) +
+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.
+

+ + (image: Angel blasting a neighborhood coyote with fire) +
+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.
+

+ + (image: Angel and Chloe in front of a slanted house) +
+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.
+

+ + (image: Lots of Angels and other characters at a shopping center) +
+June 29, 2019 - Trying a shopping center level now. It's reeally spaced apart. It's inspired by a real shopping center;;
+

+ + (image: Angel and some slimes in a cavern) +
+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.
+

+
+

you're up to date.

+
+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."
+
diff --git a/src/diary/entries/210218 b/src/diary/entries/210218 new file mode 100644 index 0000000..a2adf33 --- /dev/null +++ b/src/diary/entries/210218 @@ -0,0 +1,64 @@ + +

refactoring characters: black box

+february 18, 2021
+#character #refactor
+
+The character script was one of blessfrey's first scripts. Since it's never seen a serious refactor, it has retained its clueless beginner style to this day. Every single line of code related to characters was buried somewhere in this unmanageable monolith. The time has finally come for refactoring.
+
+The two biggest problems with my character script were the lack of structure + encapsulation.
+
+

adding structure


+An entire game can fit in one mega script, but an object-oriented approach is more manageable. It's good to keep your code close to where it is implemented.
+
+First, I expanded the character's node hierarchy. I combed through the code, forming abstract categories for each section. These categories became my subnodes. (Nodes are objects in Godot, by the way.) If a subnode was too complex, it was broken into another level of subnodes. Then, I cut+pasted as much code as I could into these subscripts. My tree now looks like this, when before, it just had the AI, Body, and UI nodes:
+
+ + (image: character's node tree. Character's children are AI, Body, DropTable, Actions, UI, Inventory, Skillbar, Serialization, InspectMenu, Equipment, Properties, Effects, Events, SkillLibrary, and Class. Under Properties are Health, Energy, and XP. Under Skillbar is Skillslot. Under SkillLibrary is Skill. Under Class is FirstClass and SecondClass. Under AI is State. Under Body is ClickableArea, RangeBubble, Camera, Sprite, Hitbox, Feet, Effects, Animation, Light, and Debug. Under the Body's Sprite is Highlight.) +
+
+

adding encapsulation


+Within the monolith, every part of the character had direct access to every other part. A major step towards getting everything running again was adding encapsulation, or grouping related logic + data into objects then restricting direct access of its internal components from other objects. I did this through designating entrypoints as the only way of performing actions or accessing data. These entrypoints are the character's verbs and setters + getters.
+
+

verbs

+Verbs are what the object does. Some of them are obvious, like "use skill," "be damaged," and "pick up item," but some of them are more abstract, like "calculate level."
+
+The character script should act as a central hub, executing verbs by contacting subnodes for the actual logic and passing back the output. These subnodes should act as black boxes, so the character script only worries about input + output, not how the request is performed.
+
+
(image: illustration of the concept of a black box: input goes in, output comes out, it doesn't matter what happens inside.)

+
+Before, I didn't apply this concept to the character at all. Outside objects would travel all over the tree to pick out specific methods. That resulted in code like body.get_parent().UI.skillbar.healthbar.label.set_value("100") instead of something like character.set_health(100). As I modified systems, all the outside references made it difficult to remove old versions. Since everything didn't necessarily use the entrypoint, there was a lot of redundant code + unexpected behavior.
+
+
+ (image: a code excerpt's before and after, can also be read @ https://pastebin.com/xhJqVVKe.) +

+
+

setters + getters

+Another closely related concept is setters + getters. If you tack a setget followed by method names onto a variable, those methods will be called whenever you access that variable. The first method is the setter and is conventionally prefixed with "set_", while the second is the getter. If you don't want a setter, don't write anything before the comma: setget , get_whatever. If you don't want a getter, don't even add the comma: setget set_whatever.
+
+
(image: code example of setters and getters, can also be read @ https://pastebin.com/y6AJVBMu.)

+
+Setters + getters are related because they increase consistency. If something needs to happen every time a variable is changed, the setter is a good entrypoint. If a variable needs to be obtained in a specific way, that process can be taken care of inside a get_thing(). This way, your variable-specific code is encapsulated, and you are no longer encouraged to manipulate them outside of their little box.
+
+Unsurprisingly, using verbs, black boxes, and setters + getters fixed a lot of long-running bugs.
+
+

controlling flow


+Another problem popped up concerning when the tree's nodes initialize. Now that everything isn't in the same script, everything isn't ready at the same time. To see the initialization order, I made a small project. Each node prints its name when at ready, and, as you see, it works from lowest level to highest level, top node to bottom node.
+
+
(image: The order goes from top-to-bottom node, lowest level to highest level. The tree, code, and output can be read @ https://pastebin.com/Z4VG9ey5.)

+
+To have more control over the flow of my character, dependency-sensitive nodes rely more on a setup method than the _ready method. Also, characters who inherit from the base script use a super _setup method called from the setup method for their own specialized needs. (Super methods are discussed in tidying up my skill phases.) This way, I can ensure a node is ready and has all its properties set at the moment it's needed.
+
+
(image: the base character's _ready is broken into more methods. Supermethod are used for calling specialized code at a specific time.)

+
(image: the player no longer uses _ready. It uses a supermethod called off the base character's setup method.)

+
+

happy script


+The refactor paid off immediately. So many "known issues" were immediately resolved. The character scripts are way easier to maintain now. The main script's halved in size. And I feel like I've improved a lot since I first started working on blessfrey. I wanted to go through the rest of the program and apply these concepts throughout, but it turned out I was already using them. It was just this ancient mess of a script that was left behind.
+
+I'll finish with a before + after of the base character script.
+
+
(image: 604 lines of spaghetti code vs 374 lines of verbs + setup code)

+
+Enjoy your day!
+
+last updated 2/21/21
+
diff --git a/src/diary/entries/210304 b/src/diary/entries/210304 new file mode 100644 index 0000000..56f379d --- /dev/null +++ b/src/diary/entries/210304 @@ -0,0 +1,141 @@ + +

python writes my skills for me

+march 4, 2021
+#godotengine #json #python
+
+Similar to Magic: The Gathering cards, the functionality of my skills is composed of keywords. For instance, a skill with the 'damage' keyword + 'bleeding' keyword will inflict pure damage + a bleeding status effect.
+
+Since skills derive their effects from existing keywords, each individual skill has very little to no unique code. Repetitive, formulaic tasks are better left to scripts, so I wrote a skillmaker to write skills for me.
+
+

what do blessfrey skills look like?


+
+(Click on the shrunk images for an expanded view.)
+
+They are Godot scenes composed of a single node with an attached script.
+
+
(image: That's it - the tree's just one node with a script.)

+
+The .TSCN and .GD files look like this in the engine.
+
+
+ (image: Screenshot from the engine - the node tree, attached script, properties, and groups are all neatly displayed.) +

+
+But Godot files are just simple text, so you can open them in any text editor. Really, you could write the bulk of a Godot game in Notepad, if you wanted to. That's perfect for me!
+
+The same files look like this in xed (similar program to Notepad):
+
+
+ (image: The same files displayed in xed.) +

+
+The .GD file is identical to what gets displayed in Godot Engine, but the .TSCN is a little cryptic. If I learn how the syntax, I can make a script to write it for me. I don't understand all of it, since I only deciphered what I need for making a skill.
+
+

examining the skill scene file


+
+
+ (image: The .TSCN file broken into color-coded sections.) +

+(You can also see the text @ Pastebin.)
+
+Since I designed Blessfrey skills to have a simple, consistent structure, the .TSCN for any two skills will be very similar.
+After experimenting in + out of Godot Engine, I've found that that first line is always the same.
+
+

red + yellow resources


+The red + yellow sections are the scene's resources. This includes any .GD files attached to nodes in the scene tree and any resources (textures, fonts, etc) you load as properties. Those are the properties that look like my icon:
+
+
(image: This is what resources look like after being loaded into the properties.)

+
+The script + the resources look so similar because they are both treated as properties of the node. The formula is...
+
+ +
+

green groups


+Green marks the groups of each node in the scene. You can see the groups are just an array of strings.
+
+Broken into pieces, that looks like
+
+ +
+Two interesting things about the group_name property. One, the engine adds a comma at the end of every line, even to last or only group of the node. Two, if your scene node has no groups, this property will be omitted from the node. If CuttheGuy belonged to no groups, it would just look like [node name="CuttheGuy" type="Node"].
+
+

blue properties


+The properties are inside blue. Since scripts are treated the same as any other resource property, the attached script is referenced in this section.
+
+Resources look like icon = ExtResource( 2 ). +
+
+Icon is my variable. I declared it in a script that the skill inherits from as export(Texture) var icon = preload('res://res/art/skillIcons/EmptySkillSlot.png') setget , get_icon. The script variable was declared by the engine and isn't in my .GD files.
+
+The 2 is an id from the yellow section. It must match the resource's number from earlier.
+
+If I store all my skill icons in the skillIcons directory and give them the proper name, they'll be found when they're needed, no manually loading necessary. This is a test skill with a generic skill icon, but it would be named something like CuttheGuy.png.
+
+Other properties are more straight-forward. They are the same exported member variables declared in the node's attached .GD script, followed by an equal sign and the value.
+ +
+

overall


+They aren't so confusing when read as text files. The Godot team must have planned them to be human-readable. :)
+
+

altar of spellmaking


+So as you modify the groups and properties of a node, the engine is actually just formatting your input into text files. Since I have a functional understanding of how to manually format data, I can write a skill-formatting script to do it for me.
+
+The tool is named "Altar of Spellmaking" as an Oblivion reference and uses JSON + Python3. Godot can interpret JSON files on its own, so maybe someday the game will automatically generate skills from on the JSON file at load time. For now, though, I see a frivolous excuse to use Python, so the interpreter is going to be in Python. Its input shall be a JSON file, and its output shall be matching .TSCN + .GD files.
+
+

writing a skill with JSON


+JSON isn't a Turing complete language, just a notation. It looks like a dictionary and uses attribute-value relationships. It's good at storing nested groups of information and can be easily interpretted by other languages. Since my skills can be described as a collection of attributes + keyword effects, they can wrestled into the JSON format.
+
+
(image: The skill as it appears in the JSON file.)
+(You can also see the text @ Pastebin.)
+
+All the repetitive, obvious, or derivative aspects of the skill do not need to appear in the JSON, since Python will be compensating.
+
+I identify the skill by its name in lower-case + underscores just for my sake. The program doesn't care what the skill's id is or if it has an id at all.
+
+Inside, I have some basic attributes like the id and associated class + stat. The tags will become some of the node's groups. The keywords take place in the skill's phases, which are lists of dictionaries. To build the skill phase, Python will interate over each item in the dictionary and use the internal data to supply the keyword's necessary input or conditions.
+
+Conditional effects look like the image below.
+
+
+ (image: A skill entry with a conditional effect.) +
+(You can also see the text @ Pastebin.)
+
+The cond consists of the code (literal GDscript code) and desc (an English translation of that code). It's not perfect, but it's quicker for me to just write the code and write the skill description than try to have Python translate. This does mean my code has to be perfect, and I have pressure to adhere to the standard language of a skill description. That's a problem, but it is much smaller than the problem of not using JSON at all.
+
+Eventually, I would prefer the desc to be an id from the translation spreadsheet instead of hard-coded text. (I discussed translation in blessfrey in japanese.)
+
+

writing a Godot scene with Python3


+Using Python to format JSON values into a Godot scene + script is kind of ridiculous, so I have 168 lines of code and half of them start with f.write. Now that it's written, though, it's easy to maintain. Since the overall structure doesn't change, all I have to do is tweak a line here or there to reflect refactors. I'll include a picture of the main method, so you can get a sense of the flow of the program:
+
+
+ (image: The main method in the Python script.) +
+(You can also see the text @ Pastebin.)
+
+The output is a nested directory of all the skills, so I can just copy + paste the skills folder over the one in the engine. And it works! The first skill image's code looks machine-generated because it was.
+
+
+ (image: skills>Common>Brawler>DirtyFighting>CuttheGuy, same directories as the engine.) +
+
+

is it better than just making skills in the engine?

+
+If I only had ten or eleven skills, then probably not. I'm planning on having around 40 skills per class, though, and I'm only one person. Since the skills are very formulaic, I'd rather have Python do all the copying + pasting for me while I worry about what makes my skills unique!
+
+last updated March 6, 2021
+
diff --git a/src/diary/entries/210318 b/src/diary/entries/210318 new file mode 100644 index 0000000..ad68601 --- /dev/null +++ b/src/diary/entries/210318 @@ -0,0 +1,161 @@ + +

generating an RSS feed with python bottle

+march 18, 2021
+#bottle #rss #webdev
+
+After a few months of quietly running my blog as practice, I want to start sharing my articles with other people. I looked over my favorite gamedev communities and saw that GameDev.net apparently allows you to syndicate a blog through RSS. I never thought about making an RSS feed, so why not?
+
+

what is RSS?


+Before the massive centralized content platforms came into the mainstream, the internet was more like a constellation of individual websites. In lieue of algorithm-driven feeds and push notifications from major social media, RSS was designed to bring content from scattered websites into one place.
+
+RSS and its predecessors have been around since the 90s. RSS 2.0 (what blessfrey.me uses) was published in 2002. Even through it's old and falling in popularity, it's still used by some large aggregators today like Google News and Spotify.
+
+Here's a few examples from around the internet, a mix of large + small news websites and forums:
+ +
+

what goes into an RSS feed?


+RSS files themselves are written in XML. They should contain the latest 10-15 entries along with things like their title, link, summary, date, and author.
+
+Blogging platforms like WordPress already take care of the RSS feed, but there's no shortage of third-party RSS creators on the internet. Since I have already written code to display + summarize my articles on the diary page, the 'latest' box in the diary's sidebar, and the 'news' box on the index page, I figure I can format the data one more time into an XML file.
+
+Here's truncated version of blessfrey.me's feed as an example (also available on Pastebin:
+
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0">
+<channel>
+<title>blessfrey.me</title>
+<link>https://www.blessfrey.me/</link>
+<description>chimchooree's dev space</description>
+<language>en-us</language>
+<webMaster>chimchooree@mail.com (chimchooree)</webMaster>
+<item>
+<title>making an rss feed</title>
+<link>https://www.blessfrey.me/diary/entries//diary/entries/210318</link>
+<description>After a few months of quietly running my blog as practice, I want to start sharing my articles with ... </description>
+<pubDate>Thu, 18 Mar 2021 8:05 CST</pubDate>
+<guid>https://www.blessfrey.me/diary/entries//diary/entries/210318</guid>
+</item>
+</channel>
+</rss>
+
+I'll explain each tag, but they are also described on the RSS Advisory Board's Best Practices Profile. There are more tags, too, so research documentation + examples to see what suits your website.
+
+

XML declaration


+Identifies the document as XML and defines the version + character encoding. It's required and must be the first line.
+
+

RSS


+The top-level element that defines version number. It's required.
+
+

channel


+Nested within the RSS tags and describes the RSS feed. It's required.
+
+There's some tags nested within the channel. Title, Link, and Description are required.
+
+ +
+

item


+Nested within the channel. Each article will be defined with the item.
+
+There's some tags nested within the item. Title, Link, and Description are required.
+
+ +
+

how to make an RSS feed


+I'm generating the RSS every time I update my website. That way, it always exists on the server and can be served as a static file. If I used a template, the RSS file would not exist unless it was accessed. I'm not sure if that would be an issue and haven't experimented.
+
+Since Bottle is just Python, I can generate the file similar to how I format my articles into diary snippets, pages, and headlines.
+
+I'll share the main code, but the full code can be viewed on Pastebin.
+
+

code example


+def make_rss():
+ loc = 'diary/entries/'
+ info = {'items': list_items(gather_and_sort(loc)[0:15])}
+
+# Return list of items
+def list_items(articles):
+ f_name = "static/xml/blessfrey.xml" # the RSS file
+ loc2 = 'https://www.blessfrey.me/'
+ loc = 'diary/entries/'
+ loc3 = loc2 + loc
+ result = []
+
+ for article in articles:
+ path = loc + article
+ text = []
+ a = []
+ length = 0
+ text = article2list(article, loc)
+ a.append(find_title(text))
+ a.append(find_url(path))
+ a.append(clean_tags(prepare_rss_summary(text, path)))
+ a.append(find_timestamp(text))
+ result.append(a)
+
+ clear_file(f_name)
+ f = open(f_name, 'w')
+ f.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + '\n')
+ f.write("<rss version=\"2.0\">" + '\n')
+ f.write("<channel>" + '\n')
+ f.write("<title>blessfrey.me</title>" + '\n')
+ f.write("<link>https://www.blessfrey.me/</link>" + '\n')
+ f.write("<description>chimchooree's dev space</description>" + '\n')
+ f.write("<language>en-us</language>" + '\n')
+ f.write("<webMaster>chimchooree@mail.com (chimchooree)</webMaster>" + '\n')
+
+ for r in result:
+ f.write("<item>" + '\n')
+ f.write("<title>" + r[0] + "</title>" + '\n')
+ f.write("<link>" + loc3 + r[1] + "</link>" + '\n')
+ f.write("<description>" + r[2] + "</description>" + '\n')
+ code = r[1].replace(loc,'')
+ code = code.replace('/','')
+ f.write("<pubDate>" + format_rss_time(code) + "</pubDate>" + '\n')
+ f.write("<guid>" + loc3 + r[1] + "</guid>" + '\n')
+ f.write("</item>" + '\n')
+
+ f.write("</channel>" + '\n')
+ f.write("</rss>" + '\n')
+ f.close()
+
+ return result
+
+# Serve XML
+@route('/static/xml/<filename:path>')
+def serve_xml(filename):
+ return static_file(filename, root='static/xml', mimetype='text/xml')
+
+## Main ##
+
+if __name__ == '__main__':
+ make_rss()
+ run(host='127.0.0.1', port=9001)
+
+

double-check


+The RSS Advisory Board and W3C have feed validation services that can check the syntax of Atom or RSS feeds. It's nice to check but don't feel pressured to meet all the recommendations if they don't suit your needs.
+
+

double-check


+Now I have an RSS feed, available at https:/blessfrey.me/static/xml/blessfrey.xml. Feel free to use it if you prefer to read through an aggregator and contact me if there's technical problems.
+
+Last updated March 19, 2021
+
diff --git a/src/diary/entries/210415 b/src/diary/entries/210415 new file mode 100644 index 0000000..8534830 --- /dev/null +++ b/src/diary/entries/210415 @@ -0,0 +1,63 @@ + +

how to make a plugin for godot engine

+april 15, 2021
+#godotengine
+
+If Godot Engine doesn't have the functionality you need, you can extend it with your own plugins. You can also use it to make specialized nodes to suit your project.
+
+

how to make a plugin


+
+Navigate to your project folder. Within it, make a folder called addons. Within the addons folder, make a folder and name it after your plugin. Within the plugin folder, you will need a few files: plugin.cfg, custom_node.gd, a .gd file for each custom node your plugin will add, and a .png icon for your custom nodes.
+
+

plugin.cfg


+Within the plugin file, make a file called plugin.cfg. Open it in a simple text editor like Notepad and use this template to make your config:
+
+[plugin]
+name="World"
+description="A world system."
+author="chimchooree"
+version="0.4"
+script="custom_node.gd"
+
+Obviously, fill the space within the quotation marks with your own information. The script's value should be kept the same, though. Once you have these six lines in your file, save and close. Avoid writing these files in a full word processor like Microsoft Word because they tend to fill your documents with extra characters. Even if they aren't displayed in the word processor, they will make your file unreadable to Godot.
+
+
+

custom_node.gd


+Within the plugin file, make another file called custom_node.gd. The contents of this node should look like this:
+
+tool
+extends EditorPlugin
+
+func _enter_tree():
+    add_custom_type("Room", "Node2D", preload("room.gd"), preload("World.png"))
+
+func _exit_tree():
+    remove_custom_type("Room")
+
+To add a type, supply the add_custom_type method with 4 parameters: the name of your custom node, the node type (Node2D, AnimatedSprite, etc), your custom node's preloaded script, and a preloaded icon. Every type you add must also be removed. Use this process to add as many custom nodes as you need.
+
+
+

.gd files


+For each custom node you declare in the custom_node.gd file, you'll need to supply a .gd script. These are going to look just like your other scripts in the editor. You can write them in full now or leave them relatively blank, but at least declare the type at the top.
+
+Since my example calls for a Node2D with a "room.gd", I can make a file within the plugins folder called room.gd. I'll keep the contents extremely simple for now:
+
+extends Node2D
+
+
+

icon


+Icon must be .png files with the dimensions of 16x16 pixels. They are just for you to see within the editor's node tree, so you can draw something fancy or just squish down the same Godot head everyone uses. You can also make a unique icon for each node or just use the same one across all nodes. Just make sure the file name matches what's used in custom_node.gd.
+
+If you want to use my squished icon, that's 100% fine with me. If you need to check the license for the original Godot head, though, it's on their git repo. (image: Squished Godot head)
+
+When choosing whether to make something beautiful or lazy, let Colossians 3:23 echo in your head: "And whatever you do, do it heartily, as to the Lord and not to men."
+
+
+

how to add a plugin


+Now, inside Godot Engine's editor, go to the top-left menu and select Project>Project Settings and go to the Plugins tab. If your files came out okay, your new plugin should appear in the listing with all the data from your plugin.cfg file. Under the Status heading, make sure the plugin is set to "Enable" or "Active". To check whether your plugin's working, try to add a new node to a scene and search for your custom type. It should pop up with your custom icon in the search. If you add it, it will already have its custom script attached and ready to go.
+
+Happy coding!
+
+
+Last updated April 30, 2021
+
diff --git a/src/diary/entries/210429 b/src/diary/entries/210429 new file mode 100644 index 0000000..9204fb3 --- /dev/null +++ b/src/diary/entries/210429 @@ -0,0 +1,46 @@ + +

an enemy patrol in godot

+april 29, 2021
+#ai #knowledgebase
+
+My patrol routes look like a series of waypoints (Position2Ds) which are the children of a patrol (Node2D). The patrol node is in a group named after the patrol route. Whenever the enemy decides to patrol, adds all the waypoints into an array. The nearest waypoint is set as the enemy's next point. The enemy then pathfinds by building a constellation of dots leading to that next point and moves fowards. To determine whether it is successfully moving towards or has reached the next point, the Knowledge Base is called in to help.
+
+The Knowledge Base is a system of event handlers and a message bus used originally to accomodate achievements. Since it is a clean, self-contained system that is aware of and can respond toevery event that happens in the game, it has grown to be the driving force behind dynamic events and game progression.
+
+

fast explanation of the knowledge base


+
+Essentially, individual, granular events are assigned a knowledge key. The Knowledge Base stores all knowledge in the game. Upon the encounter of a key in the wild, the key is used to "learn" a piece of knowledge by the Knowledge Base. Event Handlers are subscribed to the MessageBus, and once certain pieces knowledge are learned, they can generate an outcome.
+
+So let's say every tea has a unique knowledge key. Upon drinking mint tea, the "item_used" event is passed to the MessageBus, the Knowledge Base learns the "mint_tea" knowledge, and the tea achievement event handler takes note. The Knowledge Base groups all the tea knowledge arrays into the tea category. Once the tea category is complete, the tea drinker achievement is awarded to the player.
+
+I discuss its design and use more in other articles.
+
+

why involve the achievement system?


+
+The character or its AI could track its own proximity and vectors, but tying character movement into this achievement system allows more creativity in how the game world can respond to patrols. When event handlers can listen for certain characters to reach certain points, events like having guards switch shifts or get suspicious of missing buddies can happen naturally instead of being hard-coded. I could even do silly things like have an "Arbiter of Left" who censures any character who moves left within his zone. At the very least, I could do what many games do and include a step counter somewhere in a statistics menu.
+
+

getting back into the patrol flow


+
+So, to determine the enemy's progress towards reaching the next point, the enemy's going to generate an event handler for the "moved" event upon entering the patrol state. This event handler is going to be subscribed to "moved." Every time the enemy moves, it's going to publish "moved" and itself to the MessageBus, so the event handler can be notified. The event handler is going to take the enemy and evaluate whether it's reached the next point or an intermediary dot yet. The handler can also evaluate whether it's on course. Once the event handler knows how the enemy's doing, it will shoot off the appropriate signal.
+
+The enemy's AI will receive the signal and respond. If it's arrived at the next point, it will get the index of the current next point (the child order of the patrol node) and set the waypoint at the following index as the new next point. Then it can repeat the process of moving to this next point. If the AI finds out the enemy isn't on course, if can check to see if it's stuck on the terrain, affected by a paralysis status effect, or being pushed around by other characters, then respond appropriately.
+
+

pathfinding and moving


+Pathfinding is mostly handled by the Godot Engine, but there's an important distinction to maintain between the waypoints that form a patrol route and intermediary pathfinding points between a character and a waypoint. Lack of clarity in my design led to a few weeks of confusion.
+
+The "path_to_object" method handles finding a path between the character and goal point. It sets the given object as the next point and sets the character's "dots" as the discrete line between itself and the next point. These dots are determined by the zone's Navigation2D's built-in get_simple_path method. The dots are stored in the state machine. Upon getting new dots, it sets its current dot by popping the front dot off the array. The patrol state, every time Execute is called, sets the enemy's velocity as the current dot - the character's global position. It also published "moved" to the MessageBus for the event handler to handler. To actually move, the enemy's KinematicBody2D calls move_and_collide from its _process method with the parameter get_velocity() * get_speed() * delta.
+
+

cleaning up


+
+Since the enemy might switch out of the patrol state at any moment, the setup and teardown are important. I also was sure to use signals when communicating to the patrol state instead of direct calls.
+
+Every state in Blessfrey is structured into an Enter, Execute, and Exit method. Enter is called once upon state transition, Execute is called every time the AI's timer ticks, and Exit is called once upon transitioning out of the state. The Enter and Exit are where setup and teardown are called respectively.
+
+The setup instances the moved handler, subscribes it to "moved" through the message bus, and connects all the necessary signals between the event handler + patrol state and the patrol state + state machine. The teardown disconnects all these signals and unsubscribes + queue_frees the event handler. That way, all loose ends are tied up.
+
+

finally - the enemy patrol!


+Finally got the enemy patrol into the game. It required a redesign of the character movement system, the development of new features for the Knowledge Base, and tons of debugging, but I'm pretty happy with my little enemies. Next, I'll refactor movement, since there's still some fragments of the old movement system cluttering my AI scripts, but after that, I'll try publishing a release version for HTML5 to put up on my website. Looking forward to it!
+
+
+Last updated April 30, 2021
+
diff --git a/src/diary/entries/210513 b/src/diary/entries/210513 new file mode 100644 index 0000000..b47f8d1 --- /dev/null +++ b/src/diary/entries/210513 @@ -0,0 +1,102 @@ + +

playing FlightRising with spreadsheets

+may 13, 2021
+#offtopic #spreadsheets #petsites
+My dragon breeding spreadsheet is really coming together, so I thought sharing it would be a fun break from AI.
+
+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.
+

spreadsheets


+
+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 Calc or Excel. I use LibreOffice Calc 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.
+

individual dragons


+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.
+
(image: screenshot from the Singles tab.)

+

sex


+The sex field is limited to ♂ or ♀ and can be selected using a dropdown menu. The content will color the cell blue or pink.
+
+I made the dropdown menu through Data Validity (Data > Validity...).
+
(image: Validity>Criteria. Allow: Cell range. Source: $Calculator.$i$3.$I$4.)

+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).
+
(image: Managing Conditional Formatting. Condition 1: cell value is equal to '♀'. .)

+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.
+
(image: Apply Style>New Style...>Background.)

+

breeding status


+Depending on the breed's cooldown and date of last breeding, the cell will say "Ready" or "Cooldown" in green or red.
+
(image: =IF(H2<=TODAY(),'Ready','Cooldown'))

+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.
+

breed


+The breed, like the sex, is a Data Validity-determined dropdown menu. The list of breeds is sourced from a column in Calculator.
+
(image: The Calculator Sheet has a column for the Breed and a column for its Cooldown.)

+

breed's cooldown


+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.
+
(image: =VLOOKUP($D2,$Calculator.$G$3:$H$18,2,0))

+

date bred


+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.
+

nest ready


+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.
+
(image: =F2+6)

+

date ready to breed again

+This is another simple formula - the date bred + the cooldown.
+
(image: =F2+E2)

+

cooldown countdown


+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.
+
(image: =IF($H2-TODAY()>0,CONCAT($H2-TODAY(),' days'),''))

+

dragon pairs


+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.
+
(image: screenshot of the Pairs sheet)

+

project


+I add the project, so I know my goal for the pair. It's helpful when prioritizing or sorting.
+

male + female


+I add the dragon pair's names here. Data validity makes sure the name corresponds to a dragon from the Singles sheet.
+
(image: Data Validity by cell range. Source: $Singles.$A:$Singles.$A.)

+

status


+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.
+
(image: =IF(AND((VLOOKUP($B2,$Singles.$A:$C,3,)='Ready'),(VLOOKUP(C2,$Singles.A:C,3,)='Ready')),'Ready','Cooldown'))

+

date ready


+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.
+
(image: =MAX(VLOOKUP($B3,$Singles.$A:$H,8,),VLOOKUP($C3,$Singles.$A:$H,8,)))

+

countdown


+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.
+
(image: =IF($E2-TODAY()>0,CONCAT($E2-TODAY(), ' days'),''))

+

colors + genes


+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.
+
(image: a screenshot from FlightRising of a dragon's genetics.)

+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.
+

best son + best daughter


+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.
+

calculator + data


+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.
+
(image: screenshot of the Calculator sheet.)

+

colors


+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 Rauxel and also contains the original, much smaller color range.
+
(image: the color wheel by Rauxel.)

+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.
+
+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.
+
(image: =ABS(MATCH($C3,$F$2:$F$178, )-MATCH($B3,$F$2:$F$178, )))

+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.
+
(image: =IF((MATCH($C3,$F$2:$F$178,)-MATCH($B3,$F$2:$F$178,))>0,'↓','↑'))

+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.
+

breeding info


+These are the columns used by the breeding pages for validity and VLOOKUP.
+

breeding day calculator


+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.
+

project sheets


+Here I list the dragons by project, tracking their genealogy, sex, colors, genes, and average distance from goal colors.
+
(image: screenshot of the Boyfriend project sheet)

+

father + mother


+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.
+

sex


+Male or female, with colored conditional formatting so I can scan by sex more easily.
+

primary, secondary, + tertiary colors


+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.
+

gene 1, 2, + 3


+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.
+

average


+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.
+

that's all~

+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!
+
(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.)
+
+Last updated May 26, 2021
+
diff --git a/src/diary/entries/210527 b/src/diary/entries/210527 new file mode 100644 index 0000000..d908e37 --- /dev/null +++ b/src/diary/entries/210527 @@ -0,0 +1,39 @@ + +

writing a game design document

+may 27, 2021
+#gamedesign #gdd #worldbuilding
+
+A game design document (GDD) is a detailed document used to communicate the vision for a videogame. They are used internally by AAA game developers to keep hundreds of people on the same page, but it's worth considering keeping one as a small team or individual. I'll share how I organize mine.
+
+

why keep a GDD if everyone's already on the same page?


+Even small games are complex pieces of software requiring a broad skillset spanning computer science, design, art, music, creative writing, and marketing. The development process can take years, too. A GDD can serve as a single place to collect your thoughts and document the evolution of your design over time. Even as a single person, it's been helpful to give every aspect a little thought as I fill it out. Also, whenever I need to refer back to something, it's a boon to have an organized GDD instead of random notebooks and files.
+
+Of course, writing a GDD isn't developing a game. Barely anyone shares their GDD outside of their team, so unless your team or publisher has extra requirements, they only exist to facilitate game development. If you can't keep the document up-to-date with development or it would never be referenced by anyone, consider alternative forms of documentation. Sometimes a GDD is more effective as a game prototype, a mood board, or merely a thought in your head. If you're keeping scattered notes like I did, though, consider compiling them into a single word document or keeping them all in a binder.
+
+

download the GDD template


+Download my GDD template and make a copy every time you have a new game idea so you never forget any! Obviously, it's just a template. If some parts aren't suitable for your genre or development process, swap them out for something better.
+
+

worldbuilding bible


+I feel it's easier to keep some parts in a different format from my GDD. For worldbuilding, I use a modified version of Ellen Brock's worldbuilding questionnaire. I keep her headings and delete the detailed bullet prompts for less clutter. In general, I try to write my own prompts so they are closely tailored to my fantasy world. If I don't even know where to begin, though, her prompts are a great starting point.
+
+
(image: Ellen Brock's worldbuilding questionnaire.)

+
+Every nation in my game gets their own copy that's written from their perspective, since different people groups can have different experiences or explanations regarding the same world.
+
+

story + dialog


+The pacing and direction of game narratives are dependent on the player's actions, so the stories are less like monolithic pages of text and more like a series of events strung together. For that reason, I don't keep the story or major events in my GDD. I keep an outline of the story and each scene in individual flowchart documents instead, so I can move the pieces around and connect them freely.
+
+
+ + (image: event diagram with an unnecessary amount of choices) +

+
+To make my flowcharts, I open diagramming software like Dia and make a box for the title and one for the goals of the scene. Then, using color coding to separate character dialog, conditional statements, stage directions, and emotes, I write the event box-by-arrow-by-box.
+
+Every time I have an idea for a scene, I scribble it in a flowchart to keep with my GDD. Some of them are dumb, but it's never bad to have a giant pile of potential game events.
+
+

backing up your GDD


+Finally, there's no point to keeping everything together in one place if the hard drive loses them. Try to keep a current copy in about 3 places. I have a GDD folder that contains an individual folder for each game. That way, it's easy to push all my GDDs to git at once. Better safe than sorry!
+
+Last updated May 26, 2021
+
diff --git a/src/diary/entries/210610 b/src/diary/entries/210610 new file mode 100644 index 0000000..035040d --- /dev/null +++ b/src/diary/entries/210610 @@ -0,0 +1,124 @@ + +

follow a moving target

+june 10, 2021
+#ai #character #movement
+
+After redesigning the movement system to support patrols, I realized the path remains static even if the target moves. Time to tweak the design again.
+
+

what must be done


+Autopathing to attack targets, skill targets, and item targets still partially relies on an old version of the movement system. Also, characters never update their pathfinding, so they cannot pursue moving targets. With some changes, the movement system can officially support following any of these targets, no matter where they go.
+
+For now, I'll update the movement system so the character can autopath after a moving item then pick it up once within reach. Since autopathing to items works identically to the others, the fix will be the same, too.
+
+

upgrading the movement system


+I can keep the same system more or less, but one function is going to have to be rewritten: the character's path_to_object method.
+
+Before, it only set the next waypoint and built a path between the character and the waypoint.
+
+

old path_to_object


+# Set Dots Between Character + Given Object
+func path_to_object(goal):
+     if goal.is_in_group("waypoint"):
+          set_next(goal)
+     else:
+          room.add_waypoint(goal.get_gpos())
+     path_to_position(goal.get_gpos())
+
+# Set Dots Between Character + Given Position
+func path_to_position(goal_pos):
+     set_dots(find_dots_pos(goal_pos))

+
+In order to follow moving targets, it needs to use the goal object itself. Also, it needs to be know when to only rebuild part of the path.
+
+

the character receives a waypoint instead of the target, so he is unaware of his target's movement


+It took waypoints instead of the goal in the first place for consistency's sake. Since a target can either be a position (as with click-to-move) or an object (as with autopath-to-item) and the movement system only has one entry point, the old system only accepted objects. So when clicking-to-move, a Position2D waypoint is generated at the global mouse position.
+
+I took the consistency a step further and also generated waypoints at the position of object targets. If the character only has a waypoint, though, he cannot know whether the target is moving. Fortunately, the system only requires an object with a global position, not a waypoint in particular. Providing the goal directly to the character not only resolved issues but also simplified my code.
+
+

constantly updating the path overwhelms the character


+If the target is moving, pathfinding needs to be reassessed periodically. However, it isn't as simple as calling the pathfinding method every tick.
+
+For one, the first point in the path will always be the character's starting position. If pathfinding is performed more quickly than the character can register arriving at the first point, he will either be frozen in place or jittering wildly.
+
+For two, it's bad for performance. Generally, the efficiency of a lightweight Godot game on modern hardware is not a critical concern, but it's not like I've never managed to bog down the performance through lazy pathfinding. Probably best to avoid extra pathfinding operations when possible. If the target hasn't moved at all, no need to recalculate anything. If the target has moved closer to the character, maybe only the farthest points need to be reconsidered.
+
+The next playable release after the bingo version will have a teleporting boss, so I'll probably need to be more thoughtful about pathfinding then. For now, though, these two fixes should do it...
+
+

new path_to_object


+# Set Dots Between Character + Given Object
+func path_to_object(goal):
+     # Next Waypoint
+     set_next(goal)
+     var goal_pos = goal.get_gpos()
+     var dots = get_dots()
+     var cd = get_current_dot()
+     # If no current dot, set dots between user and goal
+     if cd == null:
+          set_dots(find_dots_pos(get_gpos(), goal_pos))
+          MessageBus.publish("moved", self)
+          return
+     var last_dot = cd if len(dots) == 0 else dots.back()
+     # Make sure goal has moved significantly
+     if goal_pos.distance_to(last_dot) <= get_intimate_space():
+          return
+     # If goal moved further away
+     if get_gpos().distance_to(last_dot) > find_distance(goal):
+          # If no dots, generate new ones
+          if len(dots) == 0:
+               set_dots(find_dots_pos(get_gpos(), goal_pos))
+               MessageBus.publish("moved", self)
+               return
+     # If dots, only recalculate part of the path
+     var near = get_dots()[0]
+          for dot in get_dots():
+               if dot.distance_to(goal_pos) < near.distance_to(goal_pos):
+                    near = dot
+          var i = get_dots().find(near)
+          set_dots(get_dots().slice(0, i - 1) +
find_dots_pos(get_dots()[i-1], goal_pos))
+     # If goal moved closer
+     else:
+          set_dots(find_dots_pos(last_dot, goal_pos))
+     MessageBus.publish("moved", self)
+
+

testing


+Now let's test it. I don't have any items that move around, so I'll quickly throw that in. I'll add some movement based on a sine wave into the _process method.
+
+var time = 0 + +func _process(delta): +     time += delta +     var mod = Vector2(delta, cos(time) * 2) +     set_gpos(get_gpos() + mod)
+
+
+

the character never stops autopathing, even after picking up the item

+
+Okay, one more fix, then I'll have it.
+
+Previously, the movement AI relied on conditional statements in its process to detemine arrival at the goal. Instead, the achievement system handles arrival for the new movement system. Since the process is called faster than the event handlers can function, the old AI system picked up and queue_free'd the floor item before the new system could recognize it had arrived at the goal. This meant the character never truly arrived and never knew to halt the movement process or clear movement variables.
+
+Moving the conditional statements from _process to the function that handles the outcome of movement events.
+
+

new result code


+#enum MOVED {ARRIVED, NOT_MOVING, TOO_SLOW, WRONG_DIRECTION}
+func handle_moved_result(result, new_user):
+     if result == 0:
+          new_user.think("UserMove/Arrived")
+          if get_user().get_target() != null:
+          if track:
+               if get_user().find_distance_to_target() <= get_user().get_intimate_space():
+                    emit_signal('item_arrived')
+          get_waypoints().pop_front()
+          new_user.clear_movement()
+          return

+
+

just a few changes makes a big difference


+
+(image: Angel adjusts her pathfinding to follow a moving item.)

+
+To test, I made some moving items for the player to try to pick up. Now instead of bee-lining for the last known location of an item, she constantly readjusts her path towards the current location of the item. Looks good to me!
+
+(I haven't considered moving items before. They're pretty cool. It's like catching butterflies in Skyrim.)
+
+Last Updated October 29, 2021 +
diff --git a/src/diary/entries/210708 b/src/diary/entries/210708 new file mode 100644 index 0000000..3e6b5c5 --- /dev/null +++ b/src/diary/entries/210708 @@ -0,0 +1,41 @@ + +

how to attack a moving target

+july 8, 2021
+#ai #character #combat #design #movement
+
+I'll share my tentative design for the attack-movement loop.
+
+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:
+
+ (image: diagram of the attack movement loop) +
+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.
+
+

the loop explained


+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."
+
+The AI receives these signals. If the target was out of range, it sets up to follow attack target.
+
+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.
+
+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.
+
+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).
+
+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.
+
+Then the AI receives the "arrived_at_attack_target" signal and prompts the character to begin the attack all over again.
+
+

in-game


+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.
+
+Too fast:
+
(image: Angel follows slime)
+
+I'll work it out sooner or later, dependent on how hectic moving turns out to be.
+
+(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.)
+
+
+Last Updated November 13, 2021 +
diff --git a/src/diary/entries/220101 b/src/diary/entries/220101 new file mode 100644 index 0000000..9c73ad3 --- /dev/null +++ b/src/diary/entries/220101 @@ -0,0 +1,40 @@ + +

inventory as a system diagram

+january 7, 2021
+#design #systemdiagram #gamemechanics
+
+System diagrams illustrate how components interact within a system. It saves so much headache to step back and plan with a system diagram before jumping into code.
+
+
+

stop + plan before coding


+I want to move blessfrey's inventory into an app on the player character's smartphone. Before, it was displayed in a random pop-up window. It was poorly planned, so the programmatic inventory and the UI were too tightly coupled to easily pop into the phone screen. Instead of wrestling, it's easier to start over and actually plan before I code this time.
+
+
+

list out your nouns


+A simple way to start thinking about a system is to list out its nouns + verbs. Jot down the entities that interact with your system.
+
+
(image: system diagram for inventory)

+
+For blessfrey's inventory, that's the inventory (programmatic), the player character, the inventory app (UI), and base items. The inventory app is related to the smartphone and inventory items. Items are related to floor items, which are related to rooms.
+
+(blessfrey has three different kinds of items.
+

+Floor + Inventory Items hold a base item inside them that gets popped out and traded around as the item gets expressed in different forms.)
+
+

connect your nouns with verbs


+I wrote the entities in pink and moved them around until the placement was decently readable. Then I connected the concepts with arrows. These arrows represent the verbs, which I explicitly labeled in yellow.
+
+The flow of these arrows can be very important. If you are modeling AI, for instance, no matter what path the program takes, there shouldn't be dead-ends. Seeing mistakes like that is easier with a diagram than lines of code. Otherwise, the flow is always generally useful for figuring out which methods are needed for each class and how they connect.
+
+
+

write the code


+At this point, coding is a bit easier now that it's in some ways a transcription of the diagram. The entities are data (classes or objects), and the arrows are logic (methods).
+
+
+

conclusion


+Your diagram doesn't have to be fancy or formal to get the point across to yourself, and it just takes a minute to save all the headache later. Don't skip this step or you'll have to rewrite the inventory system just to display it in a different window.
+
diff --git a/src/diary/entries/220127 b/src/diary/entries/220127 new file mode 100644 index 0000000..81d7137 --- /dev/null +++ b/src/diary/entries/220127 @@ -0,0 +1,91 @@ + +

hostility

+january 27, 2022
+#design #mechanic
+
+
+

what is hostility?


+Hostility is a state that a character's AI state machine can enter. More specific states will inherit from the hostile state to prompt targeting, aggressive, and defensive behavior. It's a very similar concept to aggro in Guild Wars because weaving through patrol patterns and balling mobs is one of my favorite things from any game.
+
+

when does a character become hostile?


+NPCs generally will not seek out the player for combat. They will either stand stationary or follow their patrol route, oblivious of the player until becoming hostile.
+
+Usually, if an NPC is hostile, that means a threat got too close. Currently, proximities in Blessfrey mirror Edward T. Hall's zoning for interpersonal distances. Intimate distance is the range for physical interaction and melee attacks and social distance is the range for assessing hostility and ranged attacks.
+
(image: A visualization of proxemics by WebHamster of Wikipedia. Around someone are 4 concentric circles with varying diameters: within 25 feet is their public space, 12 feet is their social space, 4 feet is their personal space, and 1.5 feet is their intimate space.)
+(By WebHamster - Own work, CC BY-SA 3.0, Link)
+
+An NPC will become hostile under a few conditions:
+ +
+

what changes when a character is hostile?


+A hostile NPC will enter a combative AI state, usually with the goal of pursuing its opponent until either is killed or out of range. During combat, its passive health regeneration will slow, while energy regeneration will remain constant. Maybe certain skills and items can't be used during combat. If it has hostility towards multiple targets, it will prioritize targets according to its targeting AI state, probably favoring the nearest and weakest opponents.
+
+Each NPC will express hostility differently. Broadly, hostile behavior falls into 3 groups: offensive, defensive, and targeting. Offensive states may involve melee or ranged attacks, weapon attacks or offensive skill usage, single-target attacks or AoE, frontlining or backlining, and so on. Defensive states may involve buffing or healing, fleeing or kiting, protecting themselves or their teammates, and so on. The targeting state will allow the character to have unique opinions on who is most deserving of its attention.
+
+When a character becomes hostile toward the player, there will be feedback, such as battle music playing, the opponent's name turning red, or a sound effect playing. (Now I have the Aion aggro sound effect in my head.)
+
+

when is a character no longer hostile?


+An NPC will lose hostility under a few conditions: + +
+

what happens after a character is no longer hostile?


+The character will return to its idle position or the nearest waypoint of its patrol route. Its passive health regeneration returns to its usual rate. Possibly some skills and items will be combat-only and will become disabled.
+
+

hostility in action


+This next section is mostly for me. Let's run through some faction clash examples after illustrating the relationship of teammates and allies.
+
+
+

team hierarchy


+Teams have a leader. If there are additional teammates, they will be followers of the leader. Teams can be allied with other teams. Generally, the leader of the ally team will become a follower of the main team's leader, while its teammates will be followers of their local leader. If a teammate has a pet, raises a minion, casts a summon, or triggers an event that grants it an ally, that character will be the leader of its own group, allied with the responsible teammate. Alliances can be gained or broken.
+
+Teams will be more or less oblivious to their allies' position and lose them if they aren't keeping up. They will support allies in combat, but they characters usually prioritize teammates over allies.
+
+
(A diagram of team hierarchy. )

+
+

scenarios


+
(An illustration of the chain resulting from paladins allying with a necromancer who summons a vampire who converts a ghoul who is friends with a chaos dwarf who build a war golem.)

+
+A, B, and C are in team 1. D is allied with them. They are preparing to attack the idle team 2 (V, W, and X with ally Y). Both teams will be spotted by the patrolling team 3 (M, N, and O with ally P) in a minute. Team 2 and 3 are members of the same faction, while Team 1 is of an opposing faction. A, B, and D are within range of either other, while C is investigating something just out of range. V, W, and X are within range, while Y is stuck behind a rock just out of range. M, N, and O are patrolling, while P walks too slowly to be in immediate range of the others. A, V, and M are leaders of their teams. They lead, while their teammates flock with them. Allies are members of their own team(s). Ally team leaders will follow the team leader of the main team, while their teammates flock with them. Allies can have their own allies, resulting in large trails of allied groups. Allies will not engage against allies or support direct enemies of their allies. Allies may engage against allies removed or support direct enemies of allies twice removed, though.
+
+Technically, allies are in their own team by themselves. Allies will flock within their own team and follow the general position of the main team or the individual teammate they are allied with. The main team will flock within their own team and ignore the positions of their allies. Allies will only share hostility with the main team through the coincidental case of being the same faction or type.
+
+ +
+

ally chains


+
+
(An illustration of the chain resulting from paladins allying with a necromancer who summons a vampire who converts a ghoul who is friends with a chaos dwarf who build a war golem.)

+
+If a necromancer is allied with a team of paladins, they will only enter hostility through their own terms. For instance, the paladins may attack a bear, but the necromancer will ignore it until the bear is a direct threat to itself. If the paladins attack another necromancer, the enemy necromancers will likely ignore the paladin-allied necromancer and never pose a direct threat. The allied necromancer may express idle supportive behavior like buffing the paladins, but it will remain idle. The politics of the main team trump that of other roaming teams. The allied necromancer will not enter combat with the paladins merely because it is the same type as the necromancers. It will also not idly heal the team that is currently directly opposed to its paladin allies. If the enemy necromancers were fighting a team of merchants, and the paladins completely ignore both groups, the necromancer ally will enter the fray in support of the necromancers and against the merchants. This may draw the paladins in, who will support the merchants and fight the necromancers. The allied necromancer will immediately lose hostility towards anyone directly opposed to the main team's leader.
+
+If the necromancer raises 2 liches, each lich is the leader of its own team, and each team is allied with the necromancer. Let's say the liches summon vampiric minions, one of which converts someone to an allied ghoul whose alliance remains constant with its chaos dwarf buddy who in turn is allied with its war golem. The paladin will respect the necromancer and liches but become hostile towards the vampires and all other evil monsters and vice-versa. In the case all these groups were factionless, the paladin would be neutral towards the vampires and everything else down the chain. Neutral characters are generally low priority in combat, but AoE attacks can easily whip up hostility.
+
+If the necromancer dies, the lich and paladins will no longer restrain themselves from becoming hostility towards each other.
+
+
+

what I learned


+Before, I was trying to force the combat state into flat booleans, but clearly there is more nuance to the issue. A character can be hostile but not in active combat. A character can be in combat but not hostile. Also, hostility is more of a product of a character's surroundings, actions, and actions inflicted on it. Structurally, it needs to resemble (extremely obviously) an AI state rather than any kind of variable.
+
+The characters' state machines now behave more like the above scenarios, and I'd love to make a cutscene-like demo for Blessfrey's second demo that sets differently factioned groups on clashing patrols to make sure it all works. Look forward to it!
+
+Last updated January 31, 2022 +
diff --git a/src/diary/entries/220505 b/src/diary/entries/220505 new file mode 100644 index 0000000..956693a --- /dev/null +++ b/src/diary/entries/220505 @@ -0,0 +1,140 @@ + +

css grid with angular (break into 2?)

+may 5, 2022
+#angular #css #webdev
+
+

start from the beginning


+Angular's documentation shares the process for beginning a new project, but I'll explain it, too.
+
+ +
+

grab the template

+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 Angular's github.
+
+ +

test run

+Now you can run your example project in the browser.
+
+ +
+If you see the page below, you have everything you need to get started on your own project.
+
+ (image: the example webpage that comes with the skeleton files.) +

+
+
+

adding custom components

+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 component to be styled by the grid.
+
+I'll use the petsite browser game I'm making as an example:
+
+ (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.) +

+
+This webpage has some major elements that will be reused on many pages, which I would like to keep in a general grid.
+ +Then I'd like to nest a grid inside to contain elements more specific to this kind of page.
+ +
+Each of these will need to be added as components via the terminal.
+
+ +These commands fill your app folder with components for your grid.
+
+
+ (image: screenshot of each folder generated for each component inside the app folder.) +

+
+

writing the css grid

+There's a few HTML files and a few CSS files in your project folder.
+