new website

small-nav
chimchooree 2 years ago
parent 323aa85f0b
commit 2aceea75f5

@ -0,0 +1,506 @@
import datetime, os, re
from bottle import error, response, route, run, static_file, template, TEMPLATE_PATH
def clean_tags(raw):
cleanr = re.compile('<.*?>')
cleantext = re.sub(cleanr, '', raw)
return cleantext
# rss feed
# RSS Generation
def make_rss():
loc = 'diary/entries/'
info = {'items': list_items(gather_and_sort(loc)[0:15])}
# Delete old version
def clear_file(f_name):
if os.path.exists(f_name):
print("removing " + f_name)
os.remove(f_name)
f = open(f_name, 'a+')
def format_rss_time(date):
return datetime.datetime.strptime(date, '%y%m%d').strftime('%a') + ', ' + datetime.datetime.strptime(date, '%y%m%d').strftime('%d %b %Y') + " 05:00:05 GMT"
# Return list of items using list of articles
def list_items(articles):
f_name = "static/xml/blessfrey.xml"
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>" + loc2 + 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>" + loc2 + r[1] + "</guid>" + '\n')
f.write("</item>" + '\n')
f.write("</channel>" + '\n')
f.write("</rss>" + '\n')
f.close()
return result
# recommendations
def list_rec(page):
loc = 'diary/entries/'
result = []
rec = []
comment = ""
if isinstance(page, int):
# Collect recommended articles from comment line
with open('diary/entries/' + str(page)) as f:
comment = f.readline()
comment = comment.replace('<!--','')
comment = comment.replace('-->','')
comment = comment.replace(' ','')
comment = clean(comment)
rec = comment.split(',')
# Convert into array for template to display
for article in rec:
if is_it_time(article):
path = loc + article
data = []
try:
with open(path) as f:
f.readline()
data.append(clean(f.readline().replace('<br>','')))
data.append(path)
result.append(data)
except EnvironmentError:
print("No article @ " + path)
return result
# List latest 5 articles as headline links
def list_headlines(articles):
loc = 'diary/entries/'
result = []
text = []
for article in articles:
path = loc + article
b = []
b.append(path)
with open(path) as f:
f.readline()
text = f.readline()
b.append(clean(text.replace('<br>','')))
result.append(b)
return result
def find_tags(text):
new = text[3].replace('<br>','')
new = new.replace('\n','')
new = new.split(" ")
final = []
for n in new:
if len(n) <= 0:
new.remove(n)
if '#' in n:
final.append(n)
final.sort()
return final
# Return title of article, formatted for sharing via social media
def find_social_title(text):
return clean(text[1]).replace(' ','+')
# Return URL of article
def find_url(path):
return '/' + path.replace('.tpl','')
# Return clean timestamp
def find_timestamp(text):
return text[2].replace('<br>','')
# Return clean title
def find_title(text):
return clean(text[1])
# Return article as list of lines of text
def article2list(article, loc):
text = []
with open(loc + article) as f:
text = f.readlines()
return text
def retrieve_article(page, loc):
text = []
string = ""
with open(loc + str(page)) as f:
text = f.readlines()
for line in text:
string += line
return string
def retrieve_diary_entry_content(page,loc):
text = []
string = ""
with open(loc + str(page)) as f:
lines = f.readlines()
for line in lines:
if lines.index(line) >= 4:
string += line
return string
def prepare_diary_entry(page, loc):
result = []
with open(loc + str(page)) as f:
text = []
text = article2list(str(page), loc)
result.append(find_title(text))
result.append(retrieve_diary_entry_content(page, loc))
result.append(find_timestamp(text))
result.append(find_url(loc + str(page)))
result.append(find_social_title(text))
result.append(find_tags(text))
return result
# Return list of snippets using list of articles
def list_snippets(articles):
loc = 'diary/entries/'
limit = 4
total = len(articles)
result = []
for article in articles:
path = loc + article
text = []
a = []
length = 0
text = article2list(article, loc)
a.append(find_title(text))
a.append(prepare_article(text, path))
a.append(find_timestamp(text))
a.append(find_url(path))
a.append(find_social_title(text))
a.append(find_tags(text))
result.append(a)
return result
# Return list of files with given tag
def pull_tag(files, tag):
pull = []
for f in files:
tags = find_tags(article2list(str(f), 'diary/entries/'))
if "#" + tag in tags:
pull.append(f)
pull.sort(reverse=True)
return pull
# Return line count of file
def count_lines(fname):
with open(fname) as f:
for linenum, line in enumerate(f,1):
pass
return linenum
# Return article text without HTML header
def find_content(text):
length = len(text)
content = ""
# form a string from relevant lines of the article
pos = 0
for line in text:
# skip to line 5
if pos > 4 and pos < length:
content += line
pos += 1
return content
# Snip article and close any open list tags
def prepare_rss_summary(text, path):
content = snip_sentence(find_content(text), path)
if content.count('<ul>') > content.count('</ul>'):
content += '</ul>'
return content
# Snip article and close any open list tags
def prepare_article(text, path):
content = snip_article(find_content(text), path)
if content.count('<ul>') > content.count('</ul>'):
content += '</ul>'
return content
# Remove links, line breaks from snippet
def clean(result):
result = result.replace('\n','')
result = result.replace('<br>','')
result = re.sub(r'<a href=.*?>', '', result)
result = re.sub(r'<img src=.*?>', '', result)
result = re.sub(r'<a target="_blank" href=.*?>', '', result)
result = result.replace('</a>','')
result = re.sub(r'<h\d>','',result)
result = re.sub(r'</h\d>','',result)
result = result.replace('<center>','')
result = result.replace('</center>','')
result = result.replace('<b>','')
result = result.replace('</b>','')
return result
# Return first two sentences of article + " ... "
def snip_sentence(article, path):
article = clean(article)
limit = 100
result = article[0:min(len(article),limit)]
result = result.rsplit(' ',1)[0]
return result + " ... "
# Return first 300 words of article + " ... "
def snip_article(article, path):
article = clean(article)
limit = 300
result = article[0:min(len(article),limit)]
result = result.rsplit(' ',1)[0]
return result + " ... "
# Sort diary - newest to oldest
def sort_files(files):
files.sort(reverse=True)
return files
def curate_files(files):
# remove folders
if 'raw' in files:
files.remove('raw')
if 'extra' in files:
files.remove('extra')
# remove
clean = []
for f in files:
if is_it_time(f):
clean.append(f)
return clean
def is_it_time(date):
today = datetime.datetime.now()
today_string = today.strftime("%y") + today.strftime("%m") + today.strftime("%d")
return int(date) <= int(today_string)
# Return list of all diary entries (exclude raws + extras)
def gather_files(loc):
files = os.listdir(loc)
return files
def gather_and_sort(loc):
return sort_files(curate_files(gather_files(loc)))
def fill_box(new_file):
box = []
with open(new_file) as f:
for line in f:
box.append(line)
box.sort()
return box
# return list of diary entry tags, sorted by frequency
def fill_word_cloud(files):
tags = []
for f in files:
temp = find_tags(article2list(str(f), 'diary/entries/'))
for t in temp:
tags.append(t)
tags.sort()
cloud = []
i = 0
while i < 24:
if len(tags) > 0:
top = max(set(tags), key = tags.count)
cloud.append(top)
tags[:] = [x for x in tags if x != top]
i += 1
return cloud
def find_year():
now = datetime.datetime.now()
return now.strftime('%Y')
## Static ##
# Serve CSS
@route('/static/css/<filename:path>')
def serve_css(filename):
return static_file(filename, root='static/css')
# Serve images
@route('/static/img/<filename:path>')
def serve_img(filename):
return static_file(filename, root='static/img')
# Serve unlisted articles
@route('/static/extra/<filename:re:.*\.cpp>')
def serve_extra(filename):
return static_file(filename, root='static/extra', mimetype='text/plain', download=True)
# Serve XML
@route('/static/xml/<filename:path>')#re:.*\.xml>')
def serve_xml(filename):
return static_file(filename, root='static/xml', mimetype='text/xml')
## Routes ##
# Error Page
@error(404)
def error404(error):
return "unfortunately, a 404 error. the page you're searching for doesn't exist. (or am I just hiding it for now?) try another page! "
@error(500)
def error500(error):
return "unfortunately, a 500 error. something is wrong with the page you're trying to find, if it exists at all. try another page! <a href=https://www.blessfrey.me/>return to blessfrey.me.</a>"
@error(502)
def error502(error):
return "unfortunately, a 502 error. this was likely due to website maintenance. usually it'll be back up before you finish reading this, but otherwise, I'll notice something's wrong soon! <a href=https://www.blessfrey.me/>return to blessfrey.me.</a>"
# Downloads
@route('/download/<filename:path>')
def download(filename):
return static_file(filename, root='static/extra', download=filename)
# Home Page - Index Template
@route('/')
def index():
"""home page"""
loc = 'diary/entries/'
info = {'css': 'index', 'news': list_headlines(gather_and_sort(loc)[0:10]), 'title': 'chimchooree\'s dev space - blessfrey', 'year': find_year()}
return template('index.tpl', info)
# Projects Page - Game Template - system, character, story info
@route('/projects')
def projects():
"""projects page"""
info = {'css': 'projects', 'title': 'chimchooree projects', 'year': find_year()}
return template('projects.tpl', info)
# Presskit Page - Presskit Template - product, developer info
@route('/presskit')
def presskit():
"""press page"""
info = {'css': 'presskit', 'title': 'blessfrey - presskit', 'year': find_year()}
return template('presskit.tpl', info)
# Start on first Diary page if no page given
@route('/diary')
def diary2():
return diary(0)
# Slash is optional
@route('/diary/')
def diary3():
return diary(0)
# Diary Page - Diary Template - list all articles
@route('/diary/<page:int>')
def diary(page):
"""diary page"""
loc = 'diary/entries/'
assert isinstance(page, int)
info = {'css': 'diary', 'title': 'blessfrey - developer diary', 'year': find_year(), 'snippets': list_snippets(gather_and_sort(loc)), 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'total': len(curate_files(gather_files(loc))), 'limit': 8, 'cluster': 3, 'page': page}
return template('diary.tpl', info)
# Entry Page - Feature Template - for articles
@route('/diary/entries/<page:int>')
def entry(page):
"""diary entry"""
if not is_it_time(page):
return error404(404)
loc = 'diary/entries/'
info = {'css': 'feature', 'title': 'blessfrey - developer diary', 'year': find_year(), 'entry': prepare_diary_entry(page, loc), 'recommends': list_rec(page), 'articles': "Articles", 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'page': page}
abs_app_dir_path = os.path.dirname(os.path.realpath(__file__))
abs_views_path = os.path.join(abs_app_dir_path, 'views')
TEMPLATE_PATH.insert(0, abs_views_path )
return template(os.path.join(abs_views_path,'feature.tpl'), info)
# Extra Page - Feature Template - for unlisted articles
@route('/diary/entries/extra/<page>')
def extra(page):
"""diary extra"""
loc = 'diary/entries/extra/'
info = {'css': 'feature', 'title': 'blessfrey - developer diary', 'year': find_year(), 'entry': retrieve_article(page, loc), 'recommends': list_rec(page), 'articles': "Articles", 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'page': page}
abs_app_dir_path = os.path.dirname(os.path.realpath(__file__))
abs_views_path = os.path.join(abs_app_dir_path, 'views')
TEMPLATE_PATH.insert(0, abs_views_path )
return template(os.path.join(abs_views_path,'feature.tpl'), info)
# Start on first Diary tag page if no page given
@route('/diary/tag/<tagin>')
def tag2(tagin):
return tag(tagin, 0)
# Tag Page - Diary Tag Template - list all articles for tag
@route('/diary/tag/<tagin>/<page:int>')
def tag(tagin, page):
"""tag page"""
loc = 'diary/entries/'
assert isinstance(tagin, str)
assert isinstance(page, int)
info = {'css': 'diary', 'title': 'blessfrey - developer diary', 'year': find_year(), 'snippets': list_snippets(pull_tag(gather_and_sort(loc), tagin)), 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'total': len(curate_files(gather_files(loc))), 'limit': 8, 'cluster': 3, 'page': page}
return template('diary.tpl', info)
# Personal Page - Box Template
@route('/box')
def box():
"""personal page"""
info = {'css': 'box', 'title': 'chimchooree\'s personal page', 'year': find_year()}
return template('box.tpl', info)
# Credits Page - Credits Template
@route('/credits')
def credits():
"""credits page"""
info = {'css': 'contact', 'title': 'blessfrey - credits', 'year': find_year()}
return template('credits.tpl', info)
# Contact Page - Contact Template
@route('/contact')
def contact():
"""contact page"""
info = {'css': 'contact', 'title': 'blessfrey - contact chimchooree', 'year': find_year()}
return template('contact.tpl', info)
# Idea Box Page - Box Template
@route('/ideabox')
def ideabox():
"""idea box page"""
info = {'css': 'box', 'title': 'blessfrey - idea box - a collection of inspiring concepts', 'words': fill_box('diary/entries/extra/ideabox'), 'limit': 5, 'year': find_year()}
return template('ideabox.tpl', info)
# Task Box Page - Box Template
@route('/taskbox')
def taskbox():
"""task box page"""
info = {'css': 'box', 'title': 'blessfrey - task box - everything needed to complete blessfrey', 'game_words': fill_box('diary/entries/extra/taskbox'), 'web_words': fill_box('diary/entries/extra/websitebox'), 'limit': 5, 'year': find_year()}
return template('taskbox.tpl', info)
## Main ##
if __name__ == '__main__':
make_rss()
run(host='127.0.0.1', port=9001)

@ -1,23 +0,0 @@
<!--201217,200903-->
<h1>what is blessfrey? </h1>
august 6, 2020<br>
#game <br>
<br>
<b>Blessfrey</b> is a 2D action RPG developed for PC by Chimchooree. <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>
<br>
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. <br>
<br>
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. <br>
<br>
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. <br>
<br>
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. <br>
<br>
Blessfrey has been lots of fun to work on. I hope you enjoy it once a demo and eventually a game drops. <br>
<br>
Last updated June 8, 2021 <br>
<br>

@ -1,15 +0,0 @@
<!--201224,201015-->
<h1>free pixel font </h1>
august 20, 2020<br>
#assets<br>
<br>
<b><a href="https://fontstruct.com/fontstructions/show/1596262/pixel-joy">pixel joy</a></b> is a small, round sans-serif pixel font, made using <a href="https://fontstruct.com/">FontStruct's FontStructor</a> for an old version of Blessfrey. Freely use and edit for your personal and commercial projects. No credit needed. <br>
<br>
Download it from <a href="https://fontstruct.com/fontstructions/show/1596262/pixel-joy">FontStruct</a>.<br>
<br>
<center><img src="/static/img/ent/pixeljoy.jpeg" alt="image: pixel joy font preview"></center> <br>
<br>
Font released under public domain. Sample text in the preview image is from Imogen Heap's "Pocket Sun," and the colors are from <a href="https://lospec.com/palette-list/aap-64">Adigun Polack's AAP-64 palette</a>. :)<br>
<br>
Last updated June 8, 2021 <br>
<br>

@ -1,97 +0,0 @@
<!--200930,201031-->
<h1>august 2020: new server, new site</h1>
september 1, 2020<br>
#diary <br>
<br>
<h2>week 2 </h2>
#apache #css #php #server #webdesign #website <br>
<br>
<h3>tuesday, august 4 </h3>
<ul>
<li>CentOS VPS obtained through OVH. Always wanted a server. </li>
</ul>
<br>
<h3>wednesday, august 5 </h3>
<ul>
<li>Design the website </li>
<li>Prototype of website running on the server </li>
</ul>
<br>
<h3>thursday, august 6 </h3>
<ul>
<li>Create some placeholder graphics, articles + CSS </li>
<li>Add a <a href="https://publish.twitter.com/#">Twitter timeline embed</a> </li>
</ul>
<br>
<h3>friday, august 7 </h3>
<ul>
<li>Decide to use PHP for manage Blessfrey's blog </li>
<li>Set up a local Apache server for practice. It was a little confusing! </li>
<li>Wrote a vaguely functioning Hello World script in PHP </li>
</ul>
<br>
<h3>saturday, august 8 </h3>
<ul>
<li>Added images to live site despite conflicts with Linux's security </li>
</ul>
<br>
<h2>week 3 </h2>
#php #python <br>
<h3>sunday, august 9 </h3>
<ul>
<li>Studied PHP with <a href="https://www.w3schools.com/php/php_intro.asp">W3Schools</a> </li>
</ul>
<br>
<h3>monday, august 10 </h3>
<ul>
<li>Studied PHP with <a href="https://www.w3schools.com/php/php_intro.asp">W3Schools</a> </li>
</ul>
<br>
<h3>saturday, august 15 </h3>
<ul>
<li>Made a Python script that spits out new skill scenes for Godot after answering a little command line questionnaire. I could make it an plugin for the engine, but it would be a lot slower to develop + functionally no better. </li>
<li>Maybe I can rewrite it later in JSON, since Godot can read JSON. </li>
</ul>
<br>
<h2>week 4 </h2>
#mysql #pagination #php <br>
<h3>monday, august 17 </h3>
<ul>
<li>Research PHP + MySQL while attending. I think MySQL is overkill for my purposes, since all my articles couldn't possibly be that much data. It might be interesting for analyzing data, though, like adding a word cloud of the most common tags or a search feature. I'll keep things simple for now. </li>
</ul>
<br>
<h3>august 18 - august 21 </h3>
<ul>
<li>Help run a FlightRising community event while trying out ArcheAge, since my friends play it. </li>
<li>I told a friend I would play Go with him soon but was too busy with the raffle. Then someone told him I was online a lot in ArcheAge...oops...that looked bad lol. It wasn't like I played more than a few minutes at a time. I'm sorry, friend;; </li>
</ul>
<br>
<h3>saturday, august 22 </h3>
<ul>
<li>I work on pagination. The concept boggles my mind, but I can break it into pieces to solve like any other problem. </li>
</ul>
<br>
<h2>week 5 </h2>
#css #cssgrid #html #pagination <br>
<h3>sunday, august 23 </h3>
<ul>
<li>I work on my friends' MMO some. I make my own branch and make my first changes. The work environment is Windows-only, and the server is live with active players. Pretty cool experience. </li>
</ul>
<br>
<h3>monday, august 24 </h3>
<ul>
<li>Still working on pagination in PHP to create a navigation bar for my development blog. </li>
</ul>
<br>
<h3>tuesday, august 25 </h3>
<ul>
<li>Play around with CSS + HTML for the website. The CSS Grid didn't exist the last time I used CSS (for 2000s petsite profiles), so I'm more comfortable with floats. The Grid looks so worth learning, though, for more consistent and responsive web design. </li>
</ul>
<br>
<h3>wednesday, august 26 </h3>
<ul>
<li>Get the landing page looking okay while using the CSS Grid. </li>
</ul>
<br>
Last updated June 8, 2021 <br>
<br>

@ -1,70 +0,0 @@
<!--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>

@ -1,73 +0,0 @@
<!--201001,201112-->
<h1>coroutines in godot engine </h1>
september 17, 2020<br>
#coroutines #godot #programming<br>
<br>
<b>Coroutines</b> are functions that, instead of running to completion, can yield until certain criteria are met. Godot Engine supports coroutines through <a href="https://docs.godotengine.org/en/stable/classes/class_@gdscript.html#class-gdscript-method-yield"><b>yield</b> ( Object object=null, String signal="")</a>, <a href="https://docs.godotengine.org/en/stable/classes/class_gdscriptfunctionstate.html#class-gdscriptfunctionstate-method-resume"><b>resume</b></a>, and the <a href="https://docs.godotengine.org/en/stable/classes/class_gdscriptfunctionstate.html"><b>GDScriptFunctionState</b></a> object.<br>
<br>
<h2>why use a coroutine? </h2>
<br>
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. <br>
<br>
<h2>stoplight example </h2>
<br>
As a basic example of coroutines in Godot Engine, I made a stoplight. Follow along with my code on <a href="https://gitlab.com/chimchooree/stoplight">GitLab</a>. <br>
<br>
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. <br>
<br>
<center>
<a target="_blank" href="/static/img/ent/stoplight_demonstration.gif">
<img src="/static/img/ent/stoplight_demonstration.gif" alt="(gif: demonstration)" width="500" height="281.67">
</a><br>
</center>
<br>
<h2>how does it work? </h2>
<br>
<h3>node hierarchy </h3>
<br><center>
<img src="/static/img/ent/stoplight_nodehierarchy.png" alt="(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.)"><br>
</center>
<br>
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. <br>
<br>
<h3>animation</h3>
<br>
<center>
<img src="/static/img/ent/stoplight_animationframes.png" alt="(image: the AnimatedSprite Stoplight has 4 animations - default (which is no light), green, red, and yellow.)"><br>
</center><br>
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. <br>
<h3>the code </h3>
<br>
This project has two scripts: Main.gd, which is attached to the root node, and Label.gd, which is attached to the Label. <br>
<br>
<b>Main.gd</b> - code available on <a href="https://gitlab.com/chimchooree/stoplight/-/blob/master/Main.gd">GitLab</a><br>
<center>
<img src="/static/img/ent/stoplight_main.png" alt="(image: Main script.)"><br>
</center>
<br>
<b>Label.gd</b> - code available on <a href="https://gitlab.com/chimchooree/stoplight/-/blob/master/Label.gd">GitLab</a><br>
<center>
<img src="/static/img/ent/stoplight_label.png" alt="(image: Label script.)"><br>
</center>
<br>
<h3>how the code works </h3>
<br>
At <code>_ready()</code>, <code>wait()</code> is assigned to the GDScriptFunctionState <code>result</code> and is called for the first color, green. <code>_ready()</code> yields until the given function <code>wait()</code> is completed. <br>
<br>
The wait method yields for the given amount of seconds then sets the stoplight to the given color. <br>
<br>
At <code>wait()</code>'s completion, <code>_ready()</code> calls <code>wait()</code> for yellow, then red. Each is called one at a time, waiting for the color to complete before moving on. <br>
<br>
<h3>interrupting the stoplight </h3>
<br>
The Wait Button interrupts the wait times between colors. Before <code>_ready()</code> yields, it connects the <code>'pressed'</code> signal on the Wait Button. <br>
If the Wait Button is clicked during <code>wait()</code>'s yield, the GDScriptFunctionState <code>result</code> resumes immediately, ignoring <code>wait()</code>'s yield timer. This time, <code>result</code> has a string arg <code>'interrupted on green'</code>, so it will print the result, change the stoplight's color, then print <code>'done: green'</code>. The <code>wait</code> method is complete, so <code>_ready()</code> resumes and calls <code>wait()</code> for the next color. <br>
<br>
<h2>applications </h2>
<br>
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. <br>
<br>
Coroutines enable lots of practical ways to improve the flow and interactivity of your game, so just keep experimenting. <br>
<br>
Last updated June 8, 2021 <br>
<br>

@ -1,46 +0,0 @@
<!--200831,201031-->
<h1>september 2020: bye php, hello bottle</h1>
october 1, 2020<br>
#diary <br>
<h2>week 1 </h2>
#blogging #webdesign
<h3>wednesday, september 2 </h3>
<ul>
<li>Study examples of my favorite blogs, popular blogs, and just any blogs I can find from modern gaming, retro gaming, tech, cooking, beauty, and any genre </li>
<li>Determine essential features for my blog </li>
<li>Plan design of blog </li>
</ul>
<br>
<h2>week 3 </h2>
#bottle #php #python #simpletemplate #webdevelopment
<h3>friday, september 18 </h3>+
<ul>
<li>decided to switch from PHP to Bottle </li>
<li>Hello World in Bottle </li>
<li>Created templates for main pages </li>
<li>Used Bottle to fill values in a template using variables </li>
<li>Used Bottle to avoid repeating HTML code in navigation pane, header, and footer </li>
</ul>
<br>
<h2>week 4 </h2>
#bottle #python #webdevelopment
<h3>friday, september 25</h3>
<ul>
<li>Diary snippets are formatted from data taken from their articles. The article preview is cut to the character limit.</li>
</ul>
<br>
<h3>saturday, september 26</h3>
<ul>
<li>Updated the blessfrey.me script to serve static content like CSS stylesheets + images. Now the pages are actually styled.</li>
</ul>
<br>
<h2>week 5 </h2>
#bottle #debugging #python #webdevelopment
<h3>wednesday, september 30</h3>
<ul>
<li>Fixed bugs for blessfrey.me's static pages - no footer in the middle of the body, no unclosed &lt;ul&gt; tags from diary entry snippets breaking the CSS.</li>
<li><a href="https://validator.w3.org/nu/">w3's Nu Html Checker</a> (lol @ the nu name) is really handy for automatically checking whether all my HTML tags are closed and CSS is valid, especially the parts generated through Bottle + SimpleTemplate. </li>
</ul>
<br>
Last updated June 8, 2021 <br>
<br>

@ -1,67 +0,0 @@
<!--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>

@ -1,90 +0,0 @@
<!--201001,201112-->
<h1>blessfrey in japanese </h1>
october 29, 2020<br>
#godot #internationalization #localization<br>
<br>
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. <br>
<br>
I followed along with GoTut's <a href="https://www.gotut.net/localisation-godot/">"Localisation in Godot"</a> 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. <br>
<br>
<h2>step 1 - make your spreadsheet </h2>
<br>
<center>
<a target="_blank" href="/static/img/ent/internationalization_spreadsheet.png">
<img src="/static/img/ent/internationalization_spreadsheet.png" alt="(image: spreadsheet with three columns - id, en, and ja. id has ids, en has English text, and ja has Japanese text.)" width="500" height="232.26">
</a><br>
</center>
<br>
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. <br>
<br>
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 <a href="https://docs.godotengine.org/en/latest/tutorials/i18n/locales.html#doc-locales">Godot Docs</a>. <br>
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. <br>
<br>
Save your spreadsheet as a .CSV file. <br>
<br>
<br>
<h2>step 2 - import your spreadsheet </h2>
<br>
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. <br>
<br>
<center>
<img src="/static/img/ent/internationalization_translations.png" alt="(image: Project Settings>Localization>Translations shows a TRANSLATION file for each language.)"><br>
</center>
<br>
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. <br>
<br>
<br>
<h2>step 3 - refer to your ids in your scripts </h2>
<br>
<center>
<img src="/static/img/ent/internationalization_script.png" alt="(image: example of an id used in a script)"><br>
</center>
<br>
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 <code>label.set_text("quit")</code>, you'd write <code>label.set_text(tr("quit_game"))</code>. In this example, the id is "quit_game" and its corresponding text in English is "quit."
<br>
<br>
<h2>step 4 - set the game's language </h2>
<br>
<center>
<img src="/static/img/ent/internationalization_setlocale.png" alt="(image: example of setting the locale in a script)"><br>
</center>
<br>
Set the locale in your script, somewhere like _ready() or on a 'change language' button. Here's the line for setting the locale: <code><a href="https://docs.godotengine.org/en/stable/classes/class_translationserver.html">TranslationServer</a>.<a href="https://docs.godotengine.org/en/stable/classes/class_translationserver.html#class-translationserver-method-set-locale">set_locale</a>(<a href="https://docs.godotengine.org/en/stable/tutorials/i18n/locales.html">"ja"</a>)</code>
<br>
<br>
<h2>step 5 - continue adding to your spreadsheet </h2>
<br>
Now that everything's in place, you can keep adding new ids and translations, and Godot will automatically use your changes in-game. <br>
<br>
<br>
<h2>step 6 - insert values into text </h2>
<br>
Languages differ in syntax, so use format strings. <br>
<br>
In your spreadsheet, write <code>%s</code> where your inserted text will go.
<br>
<center>
<img src="/static/img/ent/internationalization_formatcsv.png" alt="(image: in the .CSV, id= character_level, en= Level %s, ja= レバル %s)"><br>
</center>
<br>
Then in your script, write <code>tr(&quot;key&quot;) % inserted_val</code> in place of the formatted string. <br>
<br>
<center>
<img src="/static/img/ent/internationalization_formatscript.png" alt="(image: in the script, set_text(tr(&quot;character_level&quot;) % String(level))"><br>
</center>
<br>
Now the formatted string will appear in-game. <br>
<br>
<center>
<img src="/static/img/ent/internationalization_formatgame.png" alt="(image: screenshot: Level 1)"><img src="/static/img/ent/internationalization_formatgame2.png" alt="(image: screenshot: レベル 1)"><br>
</center>
<br>
<h2>that's the basics </h2>
<center>
<img src="/static/img/ent/internationalization_japanese.png" alt="(image: Blessfrey screenshot in Japanese)" width="500" height="370.46"><br>
</center>
<br>
It all comes together for an old screenshot of Blessfrey's main menu in my broken Japanese. やべーな! <br>
<br>
Last updated June 8, 2021 <br>
<br>

@ -1,87 +0,0 @@
<!--200930,201130-->
<h1>october 2020: a blog that works</h1>
november 1, 2020<br>
#diary <br>
<br>
<h2>week 1 </h2><br>
#bottle #git #python #regularexpression #webdevelopment <br>
<br>
<h3>thursday, october 1 </h3><br>
<ul>
<li>Blessfrey.me's diary properly displays snippets for all articles: a header taken from the title, a truncated view of the article, the date + a place for social media share links, and a link to the article. </li>
<li>Cleaned the snippets enough to look okay and not break the page was a challenge. I used regular expressions to remove HTML link + header tags, <a href="https:/git sta/stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags">which is apparently a lost cause Stack Overflow users stand against daily, hourly even</a>. It's not so bad if it's not user content and the content is pretty predictable, right? </li>
<li><a href="https://pythex.org/">Pythex</a> has been useful for checking whether regular expressions will catch target strings </li>
<li>First command line git merge. I usually do it on the website. It's really simple - switch to the branch you're merging the second branch into, type '<code>git merge second-branch</code>' (<code>second-branch</code> = name of the second branch obviously), and fix any conflicts. </li>
</ul>
<br>
<h3>friday, october 2 </h3><br>
<ul>
<li>All articles are kept in the same folder. There's really no reason to split them up. If I want to differentiate types of articles, I can use tags or something.</li>
<li>Set up blog navigation </li>
</ul>
<br>
<h3>saturday, october 3 </h3><br>
<ul>
<li>Blog removes HTML tags through regex patterns.</li>
<li>Fixed footer strangely appearing in the middle of the body on pages.</li>
</ul>
<br>
<h2>week 2 </h2><br>
#bottle #pagination #python #webdevelopment<br>
<br>
<h3>sunday, october 4 </h3><br>
<ul>
<li>Started using absolute paths when needed. Diary snippet links to articles work now. Articles use CSS now.</li>
<li>Improved navigation for diary. The number link cluster around the current page stays static in length - very early pages compensate by adding more links to the right</li>
</ul>
<br>
<h3>monday, october 5 </h3><br>
<ul>
<li>made 'latest' box in sidebar of diary + articles with working links to the 5 latest articles</li>
</ul>
<br>
<h3>tuesday, october 6 </h3><br>
<ul>
<li>researched and added a robots.txt, sitemap, nofollow links. It's probably unnecessary for my website, but it's fun to LARP that Blessfrey.me is a real professional site.</li>
<li>added social media share links to all snippets that allow you to share the title and a link to Twitter, Facebook, or email. Not sure if Facebook or email work because I don't feel like getting Facebook or connecting email to my browser.</li>
</ul>
<br>
<h3>wednesday, october 7 </h3><br>
<ul>
<li>Added a simple Contact page.</li>
<li>Moved diary entries under a diary directory for that /diary/whatever-entry URL, so people like me can backspace on that URL to get to the higher level page.</li>
</ul>
<br>
<h3>saturday, october 10 </h3><br>
<ul>
<li>For testing, all colors were taken from the latest colors on random color websites, and all divs are stark. Instead, I have added margins, padding, sans-serif font choices, and an okay color palette. I'll pick something more true-to-brand later, but at least it isn't embarrassing now.</li>
</ul>
<br>
<h3>tuesday, october 13</h3><br>
<ul>
<li>updated site skillbar nav background art.</li>
<li>continue working on CSS files.</li>
<li>made Twitter timeline's background transparent on index page</li>
</ul>
<br>
<h3>tuesday, october 16</h3><br>
<ul>
<li>edit game and presskit pages and some diary entries</li>
<li>for site navigation, CSS buttons are now image links with superimposed text. I think keeping text as text instead of part of images helps with accessibility. I use a lot of foreign websites, so personally, I like websites where I can copy + paste.</li>
</ul>
<br>
<h3>saturday, october 17 </h3><br>
<ul>
<li>worked at the cafe today. Since my husband's battery is dead, he finally read some May I Ask For One Final Thing? I love that manga!!</li>
<li>Refactored the main Bottle script. Before, I wrote it hard and fast, so it was reading all the diary entries multiple times per refresh. It's not only cleaner now, it also only reads the files twice.</li>
</ul>
<br>
<h3>monday, october 19 </h3><br>
<ul>
<li>did some sketches for the story slideshow for the game page. they are an iteration of some cg art for the real game, too. I really don't draw enough...</li>
<li>added the 'recommended articles' section to the bottom of diary entries.</li>
<li>added 'extra' articles, for articles that are unlisted from the main directory. I'm using it to include more GDC talk notes per talk while keeping the GDC talk article general and summarized.</li>
</ul>
<br>
Last Updated June 8 <br>
<br>

@ -1,39 +0,0 @@
<!--210218,201029-->
<h1>tidying up my skill phases </h1>
november 12, 2020<br>
#programming #skills<br>
<br>
In Godot Engine, you can call methods from a parent class by prefixing it with a period (.). So to access the <code>move()</code> method from a parent, call <code>.move()</code>. This is called a <b>super method</b>. <br>
<br>
Being called super doesn't mean there isn't a better way, though. I used to use super methods to build customs skills in blessfrey, but subfunctions is tidier. <br>
<br>
(Just so you know, blessfrey's skills have a number of phases of effects that must happen in order: skill press > activation > initial phase > main phase > end phase > cooldown.) <br>
<br>
<br>
<h2>the old way </h2><br>
Initially, I used super methods to give each phase custom effects and call the next phase. This was messy. If I ever redesigned the flow of the skill phases, I'd have to edit every single skill script. It also causes a lot of repetitive code in the individual skill scripts while the base script is tiny. The one-time script being the small one is no fair. <br>
<br><center>
<img src="/static/img/ent/supermethod_old.png" alt="(image: GDscript code using old method)"><br>
</center>
(You can see the old method's code on <a href="https://pastebin.com/DDu1Q7Q6">Pastebin</a>.)
<br>
<br>
<h2>the new way </h2><br>
<br>
Instead, I can bring all the repetitive steps into the base class, sandwiching in a subfunction where the custom effects would take place. Now I only need to add what makes my skill unique inside the subfunction. <br>
<br>
I named the subfunction after its main function and added an underscore to the front. So in the base script, I fit <code>_functionality(user, action_target)</code> into <code>functionality(user, action_target)</code>. Then the individual skill scripts only need the subfunction, none of the other repetitive code from before. The subfunction is empty in the base class and filled with unique code in the child classes. Since skills inherit, the unique <code>_functionality</code> subfunction will be called automatically from the base script's <code>functionality</code>. <br>
<br>
<center>
<a target="_blank" href="/static/img/ent/supermethod_old.png">
<img src="/static/img/ent/supermethod_new.png" alt="(image: GDscript code using new method)" width="500" height="355.36">
</a>
</center>
(You can see the new method's code on <a href="https://pastebin.com/teeYn9jP">Pastebin</a>.)
<br>
<br>
<h2>problem solved! </h2><br>
<br>
The base script holds all the lengthy code in one place, while I only need to write the unique effects in all the hundreds of skill scripts. That saves me time making, refactoring, and maintaining skills. Yay. <br>
<br>

@ -1,49 +0,0 @@
<!--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. 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>

@ -1,33 +0,0 @@
<!--201101,210101-->
<h1>november 2020: dear diary </h1>
december 1, 2020<br>
#blogging #bottle #skills #webdev<br>
<br>
<h2>on topic </h2>
Between the <b>8th and 15th</b>, I wrote a Python script for generating Godot skill scenes from JSON files. As opposed to the questionnaire script I used before, this one can generate multiple skills at a click of a button. It isn't a perfect solution either, though, since my JSON script and the skills I have in the game aren't synced. It'd be better for the skills to be something like JSON files that are read by the game at run-time, unless that would be too costly. That way, I only edit skills in one location and don't need to worry about different versions. <br>
<br>
Skill inheritance is more sophisticated now and allows the script to generate a lot of the functionality of most types of skills now. <br>
<br>
I spend a lot of time on how to make skills because this is a core mechanic of my game, and I plan to have a hundred or more of them. I need an efficient way to update and create the game's skill pool. <br>
<br>
On <b>November 24, 2020</b>, I finally added a diary navigation bar <i>under</i> the diary snippets, so you don't have to scroll back up to go to the next page anymore. I was really putting that off for how essential that is. <br>
<br>
On <b>November 25, 2020</b>, I began retroactively adding diary entries based on old tweets. I added one for my pixel font, one for my Weekly Game Jam submission, one for my inventory system diagram, and one for blessfrey's graphic updates. I finally got Bottle to support self-hosted images instead of using Imgur for everything (lol), and fixed lots of bugs related to actually having embedded images in articles. <br>
<br>
As cool as it is to work on gamedev, this website is also a priority for me as a sort of portfolio. So it would be nice to have lots of articles about my process and different projects. It would be super nice to have pretty graphics everywhere, but as usual, the placeholders are probably going to stay around for a while since they're already half-decent-looking. <br>
<br>
<b>November 26, 2020</b> is Thanksgiving during a pandemic. Wrote up an article for my pretendOS game and my coroutines example. Also, the diary shows 8 snippets per page instead of 4. I finally have enough articles to test an optimal amount shown per page. <br>
<br>
<b>November 27, 2020</b> is Black Friday. I ordered navy eye shadow and fancy sunscreen.<img src="/static/img/emo/star.gif" alt="<3"> Added an article for skill phases + super methods and slimeAI's state transition diagram.<br>
<br>
<b>November 28, 2020</b> is my birthday. <img src="/static/img/emo/heart.gif" alt="<3"> I replaced the game page with a projects page with pretty CSS grid formatting and little thumbnail links to different projects, either to diary entries or git repos. <br>
<br>
<b>November 29, 2020</b>, I make sure all the thumbnails are consistent sizes for the projects page make the images and descriptions into links. I also add a thumbnail for the website itself because why not? It doesn't have anything suitable to link to, so I wrote up an article explaining blessfrey.me's development. I touched up the presskit page and added a PDF download to the page. Downloads are way easier to add than I thought they'd be - seriously took me less than a minute. The presskit page is still ugly, but at least it has better images on it now. I'm getting close to actually letting people see my website (even though it's been publicly accessible pretty much since August but whatever), so I went through my articles again and improved their readability and removed most stuff that isn't really relevant. <br>
<br>
<b>November 30, 2020</b>, I reordered the diary entries again. The daily diaries really should be released after the month is over instead of on the last day of the month. Duh. Also, the 3-week-nonstop-earworm of Seungri's <a href="https://www.youtube.com/watch?v=FHhWO-N4bl4">"Let's Talk About Love"</a> finally went away. It was becoming pathological. <br>
<br>
<h2>off-topic </h2><br>
I joined a Japanese manga translation group on a whim and now provide Japanese-to-English transcripts for a new chapter of a manga about every week and a half. It wasn't obvious, but understanding Japanese is a different skill than rewriting Japanese into English while keeping the jokes, character voice, and cliffhangers/foreshadowing intact;; <br>
<br>
Also, Election Day-turned-Weeks took up a few extra days of nonstop news watching than it usually does, even though I tuned out long before Georgia was called. <br>
<br>

@ -1,51 +0,0 @@
<!--210527,210402-->
<h1>common tropes from media </h1>
december 10, 2020<br>
#writing<br>
<br>
<br>
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 ~ <br>
<br>
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 <br>
<br>
<br>
<h2>opening scene + first level </h2>
<ul>
<li><b>dream sequence</b> - A Link to the Past </li>
<li><b>prison escape</b> - Oblivion, City of Heroes (villains) </li>
<li><b>sparring</b> - Jade Empire </li>
<li><b>starter village attacked, probably burned down</b> - Fable, Dragon Age: Origins </li>
<li><b>toll bandits blocking the bridge/road exiting the starter village</b> - Arcanum, Dragon Age: Origins </li>
<li><b>tutorial island</b> - RuneScape, Guild Wars: Factions + Nightfall </li>
<li><b>waking up</b> - Divine Divinity, The Longest Journey, Planescape: Torment </li>
</ul>
<br><br>
<h2>protagonist </h2>
<ul>
<li><b>amnesiac protagonist</b> - every game, especially MMOs. </li>
</ul>
<br><br>
<h2>levels, story points, fights </h2>
<ul>
<li><b>arena/tournament arc</b> - Fable, Oblivion </li>
<li><b>city building</b> - Neverwinter Nights 2's Crossroad Keep, RuneScape's Miscellania + Etceteria, Nightfall's Sunspear Sanctuary </li>
<li><b>courtroom hearing (usually resolved with the loser pulling out the 'trial by combat' card)</b> - Guild Wars: Nightfall, Guild Wars 2, Neverwinter Nights 2, Mass Effect </li>
<li><b>elevator fight</b> - Half-Life, Metroid </li>
<li><b>fight yourself</b> - Guild Wars's Augury Rock, Mask of the Betrayer, Persona 4
<li><b>final boss is all previous bosses in one room</b> - Fire Emblem, Nightfall </li>
<li><b>infiltrate a fancy party</b> - Guild Wars 2's A Society Function, Mass Effect Kasumi DLC
<li><b>maze level</b> - Mask of the Betrayer's Skein, Planescape: Torment </li>
<li><b>play as a side character</b> - The Cat Lady's cat, Mass Effect's Joker </li>
<li><b>raise someone </b> - the original Kingmaker concept for Oblivion, The Witcher 2 </li>
<li><b>solo boss battle</b> - Guild Wars Augury Rock, Neverwinter Nights 2's trial </li>
<li><b>trapped for a level, especially without your stuff</b> Divine Divinity's Castle + Dungeon, Mask of the Betrayer's Skein</li>
</ul>
<br><br>
<h2>side content </h2>
<ul>
<li><b>minigame tournament</b> - The Witcher's Dice Poker, Eye of the North's Polymock </li>
<li><b>really long fetch quest</b> - Dreamfall's mulled wine, Planescape Torment's Moridor's Box </li>
</ul>
<br>
last updated: 2/21/2021
<br>

@ -1,43 +0,0 @@
<!--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>

@ -1,55 +0,0 @@
<!--201201,210201-->
<h1>december 2020: holiday season☆</h1>
january 1, 2021<br>
<br>
<br>
I didn't keep a diary very well this month;; This is mostly pieced together from my git history. I did play Legacy of Kain: Soul Reaver and Planescape: Torment a lot, though. <br>
<br>
<h2>week 1, december 1-5 </h2><br>
#python <br>
<br>
<ul>
<li>worked on Skillmaker, a Python script that can use JSON data to generate skill scenes + scripts for Godot Engine </li>
<li>Skillmaker can generate descriptions that fit a standard format. It's not used in-game, though, it's just plopped in a comment on the skill script. In the future, I want Skillmaker to generate a CSV file, so Godot can read in the generated description instead of the text being hard-coded inside the engine. That way, editing + translating will be easier. </li>
<li>those descriptions use variables. In the future, those variables will allow skill values to grow with the character's stats. </li>
</ul>
<br>
<h2>week 2, december 6-12 </h2><br>
#python<br>
<br>
<ul>
<li>updated Skillmaker's readme, since expected input has changed a lot </li>
<li>after 8 years, Cyberpunk 2077 finally came out. I can't do $59.99 + 100GB, so the closest I can do is watch Batman Beyond instead. </li>
<li>lots of FlightRising. my flight competed against Light for Dominance. They won, but they had to raffle off a <a href="https://www1.flightrising.com/game-database/item/910">priceless Kickstarter item</a> during the final hours to flip us off the top. I can't imagine how costly that win was for Light, and even then, it wasn't definitive...I wish I tried harder. </li>
</ul>
<br>
<h2>week 3, december 13-19 </h2><br>
<br>
<ul>
<li>probably nothing really very productive</li>
<li>finished reading <a href="https://www.gutenberg.org/files/19942/19942-h/19942-h.htm">Voltaire's Candide</a>. Not sure if it was the best year for a literary attack against the optimistic view that everything turns out for the best, but it's a fun + easy read nonetheless. </li>
<li>started reading <a href="http://www.gutenberg.org/files/32825/32825-h/32825-h.htm">William Bradshaw's The Goddess of Atvatabar</a>, a Utopian hollow Earth novel. If inner earth is interesting, I attached a list to a <a href="https://www1.flightrising.com/dragon/62304375">dragon</a>...credits go to all the article writers I didn't credit </li>
</ul>
<br>
<h2>week 4, december 20-26 </h2><br>
#python #skills #statuseffects<br>
<br>
<ul>
<li>Merry Christmas!! </li>
<li>(keywords are the smallest component of a skill - reusable segments of code for producing a skill effect) </li>
<li>added summon + bleeding keywords </li>
<li>added Skillmaker support for generating skills that use those keywords </li>
<li>maintain this website a little </li>
<li>found a Linux alternative for OneNote that I finally like - <a href="https://www.giuspen.com/cherrytree/">CherryTree</a>. I use it to store research and develop ideas for blessfrey :) </li>
</ul>
<br>
<h2>week 5, december 27-31 </h2><br>
#dia #events #refactoring #story<br>
<br>
<ul>
<li>refactoring the character script. I want to flesh out passive health regeneration/degeneration, so I can have more control with how Bleeding, Burning, and similar keywords will work. But the character script, one of my earliest, was almost 1000 lines, and just looking at it made my brain turn off. I broke it into smaller scripts as a node tree, and now it's 100 lines of code. So clean.
<li>I finally found a diagram editor I like - <a href="http://dia-installer.de/index.html.en">Dia</a> :) It's in Mint's package manager, too. </li>
<li>it's nice for planning branching the flow of events and branching dialog. I wrote a few events with it and made an outline for the game. </li>
<li>I keep the events in my GDD repository now. I used to keep these all over the place, in paper + text documents. (Why didn't I do this earlier?) </li>
</ul>
<br>

@ -1,40 +0,0 @@
<!--200917,201112-->
<h1>inventory as a system diagram </h1>
january 7, 2021<br>
#design #systemdiagram #gamemechanics<br>
<br>
<b>System diagrams</b> 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. <br>
<br>
<br>
<h2>stop + plan before coding </h2><br>
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. <br>
<br>
<br>
<h2>list out your nouns </h2><br>
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. <br>
<br>
<center><img src="/static/img/ent/systemdiagram_inventory.jpeg" alt="(image: system diagram for inventory)" width="500" height="250"></center> <br>
<br>
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. <br>
<br>
(blessfrey has three different kinds of items. <br>
<br><ul>
<li><b>Base Item</b>: holds all the data regardless of how the item is currently expressed </li>
<li><b>Floor Item</b>: sits on the ground and gets picked up by characters. </li>
<li><b>Inventory Item</b>: displayed in inventories, store windows, containers, etc </li>
</ul><br>
Floor + Inventory Items hold a base item inside them that gets popped out and traded around as the item gets expressed in different forms.) </br>
<br>
<h2>connect your nouns with verbs </h2><br>
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. <br>
<br>
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. <br>
<br>
<br>
<h2>write the code </h2><br>
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). <br>
<br>
<br>
<h2>conclusion </h2><br>
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. <br>
<br>

@ -1,13 +0,0 @@
<!--210318,200806-->
<h1>web development resources</h1>
january 21, 2021<br>
#accessibility #color #css #html #webdesign<br>
<br>
I'll collect frequently used resources for web design here. <br>
<br>
<ul>
<li><a href="https://cssgrid-generator.netlify.app/"><b>CSS Grid Generator</b></a> - build a basic CSS Grid by changing the number of columns + rows, adjusting their dimensions, and adding containers. </li>
<li><a href="https://validator.w3.org/nu/#textarea"><b>W3's Nu Html Checker</b></a> - automatically check the validity of your HTML + CSS. </li>
<li><a href="https://webaim.org/resources/contrastchecker/"><b>WebAIM's Contrast Checker</b></a> - check whether your font color contrasts well against your background color, at least in the eyes of a computer. </li>
<li><a href="https://coolors.co/75dbcd-c9dbba-dcdba8-f5cda7-faa381"><b>Coolors</b></a> - select and lock colors then generate palettes by pressing space. Click on a color to view shades + tints, so you can tweak contrast without losing hues. </li>
</ul>

@ -1,196 +0,0 @@
<!--210101,210301-->
<h1>january 2021: new year</h1>
february 1, 2021<br>
<br>
<br>
<h2>week 1, january 1-2 </h2><br>
<br>
<br>
<h3>friday, january 1 - New Year's</h3>
<ul>
<li>2020 is over. Things probably won't be back to normal soon, so let's make the most of 2021!☆ </li>
</ul>
<br>
<h2>week 2, january 3-9 </h2><br>
#character #refactor<br>
<br>
<h3>sunday, january 3 </h3>
<ul>
<li>Trendmood leaked <a href="https://www.temptalia.com/colourpop-x-animal-crossing-collection-swatches/">Colourpop×Animal Crossing</a>. That's one of my favorite IPs and one for which a makeup collaboration was unimaginable, and I don't care. I don't know if it's the market oversaturation or I'm just salty about New Horizons simplifying the villagers. Animal Crossing collabs are a dime a dozen now. (<a href="https://blackmilkclothing.com/collections/blackmilk-x-animal-crossing-new-horizons?utm_campaign=136203_animalcrossing-release-13oct20">BlackMilkClothing×AC</a> is still up after months, even after discounts, ouch.) Feels like Nintendo's devolving back into the toy business sometimes. </li>
<li>Imagine if more videogame makeup leads to Urban Decay×SMT somehow, though...so grungy and neon! </li>
<li>The character script is completely broken up - methods have been moved closer to where they are used. So, methods for calculating xp + level are moved from character to an attribute subscript. </li>
<li>Replaced constants to subnode paths with getters. Some _ready methods need to call methods off child nodes, but those nodes will still be null. I don't understand why very well. ;-; Using a getter, plus being more conscientious about what order to call things within _ready, fixed it. I should probably document it in an article because that's been a perplexing problem since I first starting working on character movement. </li>
</ul>
<br>
<h3>monday, january 4 </h3>
<ul>
<li>Holiday break over. </li>
<li>I visit blessfrey.me to see it's down...oops. It's been down since my last update, so a few days. I never turned the Bottle script back on...OTL sorry </li>
<li>So many bug fixes. This is one of the most dramatic refactors ever, since character was one of my first scripts (a product of inexperience) and I never really improved it, just piled on more code. <li>
<li>The Player and default NPCs are passing. Just got to change a few lines on the specific NPCs like the slimes, and the game should run again, maybe even as intended...but I'm too tired. I'm closing in on the goal of revamping the passive health regen/degen, though. </li>
</ul>
<br>
<h3>wednesday, january 6 </h3>
<ul>
<li>I watched streams of the March for Trump against the Capitol. I collected some riot photos into a <a href="https://www.blessfrey.me/diary/entries/extra/stop-the-steal">gallery</a>. </li>
<li>I organized files better. (Great idea when the game hasn't been able to start since the character restructure.) Fixed a hundred dependencies, file path references. </li>
</ul>
<br>
<h3>thursday, january 7 </h3>
<ul>
<li>I didn't fix the dependencies for the font resources correctly yesterday, but the text is showing up properly in the game now. </li>
<li>Now that the Player (minus its UI) internally uses its new + improved setters, getters, and verbs, I start updating the rest of the objects' character references. </li>
<li>Things that have been mysteriously broken forever are getting fixed left + right. This refactor is already paying off. </li>
</ul>
<br>
<h3>friday, january 8 </h3>
<ul>
<li>I applied the same style from the character refactor to the AI and UI, but not a lot needs to change. It's really just the early scripts that need to be reworked. I'm improving. </li>
<li>There are still systems that need to be updated. I fixed character movement and highlighting targets. </li>
<li>UserControl (Player's AI script) was never really updated to use the new state machines. Now it is coded more similarly to other character AI scripts. </li>
</ul>
<br>
<h3>saturday, january 9 </h3>
<ul>
<li>Continue to update scripts to use the new + improved character refactor, mostly AI and Main Menu scripts. </li>
</ul>
<br>
<h2>week 3, january 10-16 </h2><br>
#character #refactor<br>
<h3>monday, january 10 </h3>
<ul>
<li>More of the same but this time with skills, skill usage AI, and skillbar UI. </li>
</ul>
<br>
<h3>tuesday, january 11 </h3>
<ul>
<li>Finished reading Theodore Dreiser's An American Tragedy (after 6-or-something years of slow reading). </li>
<li>Update click-to-move, since pathfinding had a bug where the character would keep moving forward even after reaching her destination. </li>
<li>More skill updates, mostly with tooltips + activation bars. For the first time, the activation bar disappears after the skill is activated. </li>
</ul>
<br>
<h3>thursday, january 14 </h3>
<ul>
<li>Moving on to items. I begin making sure items use verbs called off the main item object, instead of unrelated nodes calling specific methods off of sub-nodes of the item. </li>
<li>Also, begin refactoring containers. I didn't plan beforehand very well, and they don't have very good separation of concerns. So of course they have some weird, unexpected behavior. It would be amazing if the weirdness could just melt away after refactoring, like what the character refactor accomplished. </li>
<li>Also begin refactoring related systems, including the inventory and item context menu. </li>
</ul>
<br>
<h3>friday, january 15 </h3>
<ul>
<li>Finished reading William Bradshaw's The Goddess Of Atvatabar. Not sure what to read next. I wanted to find George Sand's Laura: A Journey into the Crystal, but it's too obscure and poorly reviewed and untranslated (which is ridiculous for such a prominent writer). Probably too hard to find...but that makes me want to read it even more. </li>
</ul>
<br>
<h3>saturday, january 16 </h3>
<ul>
<li>Refactored containers, both programmatic + UI. </li>
<li>Resolved all the new errors relating to the character refactor by making sure all the expected signals + methods existed and were connected to the proper objects. </li>
</ul>
<br>
<h2>week 4, january 17-23 </h2><br>
#fogofwar #refactor #writing<br>
<br>
<h3>sunday, january 17 </h3>
<ul>
<li>Lots of fixes, both new issues related to the refactor and old "known issues" </li>
<li>Fixed the energy + healthbar displays </li>
<li>Fixed the context menu </li>
<li>An embarrassing one. The inventory has always filled slot 1 then slot 2 then 1, 1, 1...and I had no idea why when it was really simple. I was using a <a href="https://docs.godotengine.org/en/stable/classes/class_vsplitcontainer.html">VSplitContainer</a> instead of a <a href="https://docs.godotengine.org/en/stable/classes/class_vboxcontainer.html">VBoxContainer</a>. Duh. </li>
</ul>
<br>
<h3>monday, january 18 </h3>
<ul>
<li>Epik High's phone premier of <a href="https://www.youtube.com/watch?v=FCsLikmxhV0">Rosario</a>, featuring CL and Zico, two favorite artists who I never imagined together. Amazing! I love that they're collabing with both old school KPOP (B.I!) and newer artists like Heize. </li>
<li>Spent too much time drawing a fog of war mask in GraphicsGale. </li>
</ul>
<br>
<h3>tuesday, january 19 </h3>
<ul>
<li>Started reading Ingersoll Lockwood's Baron Trump's Marvellous Underground Journey - it was one of the <a href="http://www.gutenberg.org/browse/scores/top#books-last1">most-downloaded Gutenberg books</a> yesterday. I remember when the internet discovered it and tried to find similarities between the characters and the Trump family. It's another inner earth tale, though, so it matches my other books. </li>
<li>Finish writing one of my favorite events with a favorite character. :) </li>
</ul>
<br>
<h3>wednesday, january 20 - President Biden's Inauguration</h3>
<li>Wrote branching dialog in Dia. I need to get used to the hotkeys, so I can write faster. I should write more. Before the pandemic, I wrote at least a vignette every day, even if there were lots of duds. </li>
</ul>
<br>
<h3>thursday, january 21 </h3>
<ul>
<li>I've been procrastinating working on Fog of War, but I'm really going to make progress today. I found the 2D Lights as Mask demo to begin learning how Light2Ds and masks work. I applied the concepts to blessfrey in their most rigid hard-coded form, but at least the map isn't completely visible anymore. </li>
<li>I feel like fog of war is a feature that feels really polished, even if it's implemented simply and doesn't add much content. I'm really craving a polish feature right now. I also want to iterate over the art again and go more top-down. </li>
<li>Fog of War means a lot of things. I want something like Planescape: Torment (been playing it lately), where the map starts out black and nothing can be interacted with, the immediate area around the player is lit and fully interactive, and the discovered but out-of-range areas are dimly lit and interactive. The fog of war is usually circular but follows the party's line-of-sight, so sharp corners obstruct the view. </li>
</ul>
<br>
<h3>friday, january 22 </h3>
<ul>
<li>Start reading the Land of the Lustrous manga. </li>
<li>Fiddle around with masks again. I attached the solid light to the player and made masks with various opacities to scatter around the environment. The immediate area is lit, and the rest is dimly lit. Planescape: Torment uses a pixel grid fill for gradients, since it's from 1999. It looks okay in that game, but mine gets that mesmerizing rainbow effect when you move around. (maybe it's called a diffraction pattern?) </li>
</ul>
<br>
<h3>saturday, january 23 </h3>
<ul>
<li>I have a version working with using Area2D that show a child Light2D upon collision with the player. It'd be easy to save and load that, since it's just a binary. </li>
<li>I wanted to repackage my jam gam jam and put it on my website, but I should do that with blessfrey, too. It's a really boring time to want blessfrey up, since I removed all allies, enemies, and currently only have one zone, but it's good to ship periodically. I guess I'll comb through again and either quickly complete or obviously rope off incomplete features. </li>
<li>Apparently, you don't just open HTML files anymore. You have to run <a href="https://godotengine.org/qa/69170/how-to-export-my-project-to-html5-without-errors">python -m http.server</a> in the command line first. <li>
</ul>
<br>
<h2>week 5, january 24-30 </h2><br>
#<br>
<br>
<h3>sunday, january 24 </h3>
<ul>
<li>It's been brought to my attention there's a doll movie "MyScene Goes Hollywood" where the girls have to impress Harvey Weinstein. </li>
<li>Adjust resolution. </li>
</ul>
<br>
<h3>tuesday, january 26 </h3>
<ul>
<li>The journalist lied. Harvey Weinstein isn't in the plot. Still such a crazy regrettable choice of celebrity for a children's movie. </li>
<li>I downloaded Chrome to test my website. </li>
<li>Update website, tweak website code. </li>
</ul>
<br>
<h3>wednesday, january 27 </h3>
<ul>
<li>Worked on character development. Listed out my key characters and bulleted out their backstory, shortcomings/bad experiences, how that affects their relationships with other characters, why that involves them in the story, the changing point in their arc, and where their story can end. I still need to do this exercise for Chloe, the pastor, and Angel's dad. </li>
<li>The main character's still just kind of there, being dragged around in the story. It's difficult for me to add character motivation for a playable character. For the player, that's going to be coming from the game's hook and pacing and personal reaction to the game world. Some people say it's harder to get involved with a cardboard main character, but I don't know. I was absolutely hooked on Lost Eden's story, even if Prince Adam is so empty that clicking on him just takes you to the options menu void. At the same time, a quirky main character adds a lot to the fun for me. I really enjoyed picking all the clueless, amnesiac options for Ren in Persona 5 and making him low-key gay for Akechi. I was also bemused by Sophie's belligerent atheism and her in her Atelier game. I just...never wrote a game story before and don't know how to do it. ;-; </li>
<li>Initially, I had a big cast of characters who didn't have a lot going on with them, but it would be a pain to make resources for all of them. Combining and cutting helped make the characters more interesting. Still, they didn't really have arcs. I also didn't really have the first antagonist as more than a shallow concept until this month. I still don't know her name and appearance...if I even want her to be a girl. </li>
<li>I also worked on plot. I only have a grasp on the plot up to the mid-point and don't know where to finish it. Watching Youtube writing channels helped flesh out what I already had, at least. Since it's a game, it's more fun to have lots of paths and let the player their own place among the different factions. That's ridiculous for scope, though. I hope when I add story missions, it'll be more obvious where to go? But it would sure be easier to edit the story before all the coding and assets are made. ;-; </li>
<li>Maybe I'm too worried about the story. Mechanics are most important at the end of the day. Even among people who play games for the story, they apparently retain very poor comprehension of the story. Instead, their attention is fully on the characters. (citation - some GDC talk) So, so long as I have cute characters, it's all good, right? </li>
<li><a href="https://www.youtube.com/watch?v=v-I9N5LsvPM">How to Plot Your Novel FAST | Writing Advice - Ellen Brock</a> was the best video I watched for understanding the process an editor uses to build a prompt into a plot. </li>
<li><a href="https://www.gdcvault.com/play/1020031/Using-User-Research-to-Improve">Deborah Hendersen's Microsoft user research study</a> talks about how players don't remember anything but the characters. I just found some slides. <a href="https://www.youtube.com/watch?v=4mgK2hL33Vw">Jeremy Bernstein's Creating Strong Video Game Characters</a> mentions it, but there was another that went into more detail...but I haven't watched GDC talks in forever so don't remember. </li>
</ul>
<br>
<h3>thursday, january 28 </h3>
<ul>
<li>The language-change button does something for the first time. Now, choosing English or 日本語 will automatically update all the text. I put all text-bearing objects in a group and update their text from the UI singleton. It's useful if I want more language options, but it's also useful to ensure each text is in my spreadsheet instead of embedded directly in the engine. It's also an opportunity to make my text-displaying code more consistent across my game. </li>
<li>Fixing serialization. </li>
</ul>
<br>
<h3>friday, january 29 </h3>
<ul>
<li>finished up a personal page for my FlightRising badges so imgur and tinypic will stop deleting my precious things. </li>
<li>wrote some bash scripts for updating my website. No more scp and rm and tmux. It's less of a hassle to move small changes to live now. When I want to execute things on my server, I never remember to adjust the permissions... </li>
<li>Serialization works again after the character refactor. </li>
<li>I finally tried using <a href="https://docs.godotengine.org/en/stable/classes/class_theme.html">themes</a> instead of customizing a font for each individual object. Now I can just change the theme instead of each individual object. </li>
<li>Fixed a highlight problem. Used to, when you hovered from one object to an immediate neighbor, you would change targets before you could unhighlight the old target. Now all highlightable targets belong to a group. When you hover over a new target, all members of that group unhighlight. </li>
<li>basically lots of removing the hard-coding </li>
</ul>
<br>
<h3>saturday, january 30 </h3>
<ul>
<li>Refactored skills + updated the skillmaker python script. </li>
<li>Fixed UI bugs as I found them. </li>
<li>Made a system for meaningful object ids. They're in hexadecimal so they can be succinct + flexible at the same time. </li>
</ul>
<br>
<h2>week 6, january 31 </h2><br>
<br>
<h3>sunday, january 31 </h3>
<ul>
<li>I left a pen in the laundry and ruined a nice sweater TT_TT </li>
<li>Fixed more bugs, a little more refactoring for skills + character, and now it's time to add my slime character back into the game. During this refactor, all characters were removed, so the PC's getting lonely. </li>
<li>My refactor was biased towards there only being a PC, so I move some of the player-only logic out of the base character object. </li>
</ul>
<br>
last updated 3/2/2021<br>

File diff suppressed because one or more lines are too long

@ -1,64 +0,0 @@
<!--210107,201112-->
<h1>refactoring characters: black box </h1>
february 18, 2021<br>
#character #refactor <br>
<br>
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. <br>
<br>
The two biggest problems with my character script were the lack of structure + encapsulation. <br>
<br>
<h2>adding structure </h2><br>
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. <br>
<br>
First, I expanded the character's <b>node hierarchy</b>. 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: <br>
<br>
<a target="_blank" href="/static/img/ent/characternodehierarchy.png">
<img src="/static/img/ent/characternodehierarchy.png" alt="(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.)" width="500" height="195">
</a><br>
<br>
<h2>adding encapsulation </h2><br>
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 <b>encapsulation</b>, 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. <br>
<br>
<h3>verbs </h3>
<b>Verbs</b> 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." <br>
<br>
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 <b>black boxes</b>, so the character script only worries about input + output, not how the request is performed. <br>
<br>
<center><img src="/static/img/ent/blackbox.png" alt="(image: illustration of the concept of a black box: input goes in, output comes out, it doesn't matter what happens inside.)"></center> <br>
<br>
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 <code>body.get_parent().UI.skillbar.healthbar.label.set_value("100")</code> instead of something like <code>character.set_health(100)</code>. 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. <br>
<br>
<center><a target="_blank" href="/static/img/ent/characterrefactor_oldvsnewcode.png">
<img src="/static/img/ent/characterrefactor_oldvsnewcode.png" alt="(image: a code excerpt's before and after, can also be read @ https://pastebin.com/xhJqVVKe.)" width="500" height="122.38">
</a></center><br>
<br>
<h3>setters + getters </h3>
Another closely related concept is <b>setters + getters</b>. If you tack a <code>setget</code> 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: <code>setget , get_whatever</code>. If you don't want a getter, don't even add the comma: <code>setget set_whatever</code>. <br>
<br>
<center><img src="/static/img/ent/setters+getters_dollars.png" alt="(image: code example of setters and getters, can also be read @ https://pastebin.com/y6AJVBMu.)"></center> <br>
<br>
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 <code>get_thing()</code>. This way, your variable-specific code is encapsulated, and you are no longer encouraged to manipulate them outside of their little box. <br>
<br>
Unsurprisingly, using verbs, black boxes, and setters + getters fixed a lot of long-running bugs. <br>
<br>
<h2>controlling flow </h2><br>
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.<br>
<br>
<center><img src="/static/img/ent/nodetree_ready.png" alt="(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.)"></center> <br>
<br>
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 <a href="https://www.blessfrey.me/diary/entries/201112">tidying up my skill phases</a>.) This way, I can ensure a node is ready and has all its properties set at the moment it's needed. <br>
<br>
<center><img src="/static/img/ent/setup_base.png" alt="(image: the base character's _ready is broken into more methods. Supermethod are used for calling specialized code at a specific time.)"></center> <br>
<center><img src="/static/img/ent/setup_player.png" alt="(image: the player no longer uses _ready. It uses a supermethod called off the base character's setup method.)"></center> <br>
<br>
<h2>happy script </h2><br>
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. <br>
<br>
I'll finish with a before + after of the base character script. <br>
<br>
<center><img src="/static/img/ent/characterrefactor_before+after.png" alt="(image: 604 lines of spaghetti code vs 374 lines of verbs + setup code)"></center><br>
<br>
Enjoy your day! <br>
<br>
last updated 2/21/21<br>
<br>

@ -1,119 +0,0 @@
<!--210201,210401-->
<h1>february 2021: AI</h1>
march 1, 2021<br>
<br>
<br>
I just feel like rambling about games. <br>
<br>
<h2>week 1, february 1-6 </h2><br>
#design #localization #writing <br>
<br>
<h3>tuesday, february 2 </h3>
<ul>
<li>I miss programming at the library. </li>
<li>I want to redesign my basic slime monster, but couldn't focus. Worked on writing events instead (meeting your first friend + the conclusion of the first boss fight) </li>
<li>Worked on first non-tutorial boss's motives. They were cliche before and didn't really connect to the plot. Now, his backstory ties into other characters more and prompts a new questline. :) </li>
<li>I used brainstorming advice from Diane Callahan - Quotidian Writer's video <a href="https://www.youtube.com/watch?v=CwJGon94Sx4">Writing Exercise: Fleshing Out Your Characters</a>, starting at 5:01. To write how a character would react in a situation, she suggests making a list of 10 possibilities. Usually, the early items are obvious, while the later ones are more interesting. (Maybe that's obvious advice, but it helps me.) </li>
</ul>
<br>
<h3>wednesday, february 3 </h3>
<ul>
<li>My Japanese translations have been reuploaded by several people without credit. I finally participated in something appreciated enough to be stolen lol. </li>
</ul>
<br>
<h3>thursday, february 4 </h3>
<ul>
<li>Finished reading Robert Jordan's New Spring in The Wheel of Time series. It's the prequel oops. The Eye of the World is Book 1. I barely read widely-read books, so I felt like reading something that book youtubers talk about. </li>
<li>Designed the slime's skills, state machine. </li>
<li>In general, I'm working on a particular level of the game that would make for a decent demo: the Slime Kingdom. Depending on your actions, you may meet the Slime King, but I haven't thought of him beyond generic big ole' slime. Maybe he should be a slime boy. </li>
</ul>
<br>
<h3>thursday, february 5 </h3>
<ul>
<li>Procrastination. Fixed lots of "known issues" that would confuse a playtester. The most exciting one to me is the language button. All the text changes immediately, no reloading necessary. I think I'll have a lot of writing in the game, so additional languages are a lofty dream, but having such a system in place allows for any kind of text changes. Now the text can change to pig latin or pirate-speak or the character can become dyslexic or something. Regardless, it forced me to move all the embedded text to a spreadsheet, so it'll be much easier to edit now. </li>
</ul>
<br>
<h3>thursday, february 9 - second impeachment trial of the president begins (first time in U.S. history) </h3>
<br>
<h2>week 2, february 7-13 </h2><br>
#design #writing <br>
<h3>thursday, february 11 </h3>
<ul>
<li>Tried making a worldbuilding bible again. I can never format it in a way I like, though. I did draw some concept art of the Abyss with colored markers. </li>
</ul>
<br>
<h2>week 3, february 14-20 </h2><br>
#design #writing <br>
<h3>wednesday, february 17 </h3>
<ul>
<li>At one point, you befriend Rune, an exiled angel with a teleportation skillset. He's like a lost puppy, so he'll imprint on MC if she's kind to him. His powers provide a lot of opportunity for influencing events. If MC is teleported away suddenly, he can follow her. He can also travel and hide almost anywhere, so he would make a great spy. </li>
<li>He reminds me of two other characters with travel powers. </li>
<li>Gann of Mask of the Betrayer - Maybe it's because he's in a masterful game (definitely in my top 5), but he's kind of cute lol. If you chip away at his (absolutely obnoxious) exterior enough, he support the Spirit-Eater through anything. Since he's a dreamwalker and the campaign involves numerous lengthy dream sequences + visions, that means he can join what would have been solo missions for the Spirit-Eater. (Not to mention his stat bonuses are the best.) The game is hard, too, so his presence is a lifesaver. He also walks in Spirit-Eater's dreams, so he understands her in a deeply personal way. For a guy with deep emotional + personality issues, his loyalty (can you use that word with him?) + intimate connection with the Spirit-Eater is sweeter than it deserves to be. </li>
<li>Strahd - Another D&D guy, coincidentally. I know him from the Ravenloft DOS games. (never got to play tabletop but once at a con ;-;) He has all sorts of extradimensional powers, so he can spy on the party from other dimensions. You can never really be sure when he's watching. That's honestly so creepy. Since he also employs lots of spies, you can never really let your guard down anywhere. </li>
</ul>
<br>
<h3>thursday, february 18 - Perseverance Rover Landing on Mars </h3>
<ul>
<li>I didn't get to watch, though, because every time I checked the news, they were showing the snow in Israel instead;; </li>
<li>We got 5 inches of snow. Amazing! It only snows a few inches every 5-or-so years here. </li>
<li>I wrote more events today (a popup petshop quest + a conversation with an archivist about the lost school of Chronomancy) </li>
<li>There's lots of games with time-altering mechanics (Braid, SUPERHOT, Life is Strange,...), but I'm salty about Guild Wars's canceled campaign, Utopia. From my understanding, it was to introduce the Chronomancer profession, but because they would have to modify the engine so significantly to support it, they decided they might as well make Guild Wars 2 instead. So they scraped Eye of the North together from the remnants of Utopia and switched to seriously developing Guild Wars 2. Then, not only did they not release the Chronomancer for Guild Wars 2, they said they never would. Then they did anyways as some minor skill set for the Mesmer or something idek. It could have been cool, especially Eye of the North was excellent. But no. Instead, it was the impetus for yet another boring WoW-ripoff MMO where you kill everything by running around and spamming 1. </li>
<li>If I put aside how salty just the word <em>Chronomancy</em> makes me, it's an inspiring concept. Doubtless one that's difficult to program, test, and design content for, but still. </li>
<li>For instance, save scumming is a classic issue with gamers (similar to overusing wikis/walkthroughs). There's a lot of pressure to have a perfect playthrough and always win everything and always say the right things and always get lucky with RNG. Maybe through chronomancy, you could provide a mechanic-based way to basically save scum. I'm very interested in the idea of a skill that lets you redo the previous mission or start over from this morning. It would be easier to track and respond to skill usage than saving/loading, so you could work it into the challenges or story. Especially since a skillset that powerful would be highly controversial in-universe. </li>
</ul>
<br>
<h3>friday, february 19 </h3>
<ul>
<li>Entities were based on hex, but it's confusing to know when the engine's recognizing them as string or ints. I changed them to color. </li>
<li>Color can be more of a floating math-thing, so I hope that's not bad. I thiink the numbers will stay the same enough for ids, since I'm using hex and int comparisons. </li>
<li>Since the colors are 6-digit hex numbers, I can't be as specific with each digit's meaning. Oh well. Only assigning meaning to the first 2-3 digits is probably adequate. </li>
<li>Also, every object is now going to inherit from the "entity" that stores + compares ids. </li>
<li>Worked a lot on slime AI - fixes, rewriting code, refactors. The hex stuff was to help the slime differentiate skills on his skillbar. </li>
<li>Tried writing the event for the first encounter with rebel Prince Lotus. I really love Nocturne's candelabrum fights, so it took place in a pocket dimension prison that warps in the MC. I've tried writing his encounter a few times but am never really happy with it. I haven't designed the Abyss's resistance movement very much. </li>
</ul>
<br>
<h3>saturday, february 20 </h3>
<ul>
<li>Website maintenance + article writing </li>
</ul>
<br>
<h2>week 4, february 21 - 27 </h2><br>
#ai #webdev <br>
<h3>sunday, february 21 </h3>
<ul>
<li>working on slime's AI a little bit more. It's not really using the Update() method from its current state. </li>
<li>wrote some more bash scripts for maintaining the website. </li>
<li>Uh, since when has the <a href="https://www.blessfrey.me/credits">credits page</a> been so messed up? It's just straight, unformatted text, and all the other pages using its CSS are fine. </li>
</ul>
<br>
<h3>monday, february 22 </h3>
<ul>
<li>Still investigating why the loop within the slime's planning state doesn't behave as expected. </li>
</ul>
<br>
<h3>thursday, february 25 </h3>
<ul>
<li>The slime's skills are always empty. The first two slots should have skills in them, though. I can't figure out why. </li>
</ul>
<br>
<h3>friday, february 26 </h3>
<ul>
<li>The slime was randomly choosing an index of the skillbar, checking whether its skill was valid, then recursively calling the method again until a valid skill was selected. Instead, the slime now collects the valid skill indices, randomly chooses one, and extracts the valid skill. </li>
<li>...but no skills are ever valid. </li>
</ul>
<br>
<h3>saturday, february 27 </h3>
<ul>
<li>Went on a cafe date with laptops. :) I haven't gone out further than the mailbox in 5 weeks (is that right?), so my productivity needed that so bad. Now back to indefinitely sheltering in place. </li>
<li>During its planning phase, the slime chooses a valid skill to use. </li>
<li>Characters were wiping their skillbar near the start of the game. No skills = no valid skills to choose from. I figured out it was because the skills didn't belong to any class...so when the skillbar rules were enforced, none of the skills belonged to the character's classes. </li>
<li>The slime had a wait timer so it could attack a little before making a new decision. Now it uses coroutines. </li>
</ul>
<br>
<h2>week 5, february 28 - march 6 </h2><br>
#nothing <br>
<h3>sunday, february 28 </h3>
<ul>
<li>Completed my first translated volume of manga. :) Now to translate more things! </li>
</ul>
<br>

@ -1,141 +0,0 @@
<!--210218,210107-->
<h1>python writes my skills for me</h1>
march 4, 2021<br>
#godotengine #json #python <br>
<br>
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. <br>
<br>
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. <br>
<br>
<h2>what do blessfrey skills look like? </h2><br>
<br>
(Click on the shrunk images for an expanded view.) <br>
<br>
They are Godot scenes composed of a single <a href="https://docs.godotengine.org/en/stable/classes/class_node.html">node</a> with an attached script. <br>
<br>
<center><img src="/static/img/ent/skillscenetree.png" alt="(image: That's it - the tree's just one node with a script.)"></center> <br>
<br>
The .TSCN and .GD files look like this in the engine. <br>
<br>
<center><a target="_blank" href="/static/img/ent/skillsceneshot.png">
<img src="/static/img/ent/skillsceneshot.png" alt="(image: Screenshot from the engine - the node tree, attached script, properties, and groups are all neatly displayed.)" width="500" height="281">
</a></center> <br>
<br>
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! <br>
<br>
The same files look like this in xed (similar program to Notepad): <br>
<br>
<center><a target="_blank" href="/static/img/ent/skillscene_xed.png">
<img src="/static/img/ent/skillscene_xed.png" alt="(image: The same files displayed in xed.)" width="500" height="275">
</a></center> <br>
<br>
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. <br>
<br>
<h2>examining the skill scene file </h2><br>
<br>
<center><a target="_blank" href="/static/img/ent/skillscene_cipher.png">
<img src="/static/img/ent/skillscene_cipher.png" alt="(image: The .TSCN file broken into color-coded sections.)" width="500" height="552">
</a></center> <br>
(You can also see the text @ <a href="https://pastebin.com/1mzmDFM5">Pastebin</a>.) <br>
<br>
Since I designed Blessfrey skills to have a simple, consistent structure, the .TSCN for any two skills will be very similar. <br>
After experimenting in + out of Godot Engine, I've found that that first line is always the same. <br>
<br>
<h3>red + yellow resources </h3><br>
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: <br>
<br>
<center><img src="/static/img/ent/skillscene_resproperty.png" alt="(image: This is what resources look like after being loaded into the properties.)"></center> <br>
<br>
The script + the resources look so similar because they are both treated as properties of the node. The formula is... <br>
<br>
<ul>
<li><code>[ext_resource path="</code> </li>
<li>obviously followed by the path of the resource. If resources are kept in a predictable place, it's easier on the script. </li>
<li><code>" type=" </code> </li>
<li>whatever the resource type is (examples include Script, Texture, DynamicFontData) </li>
<li><code>" id=</code> </li>
<li>a unique number (obviously used for referencing) </li>
</ul>
<br>
<h3>green groups</h3><br>
Green marks the groups of each node in the scene. You can see the groups are just an array of strings. <br>
<br>
Broken into pieces, that looks like <br>
<br>
<ul>
<li><code>[node </code> </li>
<li><code>name="CuttheGuy"</code> - the name of the node (node.name) goes between the quotation marks </li>
<li><code>type="Node"</code> - the type of the node, so something like Node, Node2D, or Label </li>
<li><code>groups=[</code> </li>
<li><code>"group_name", </code> - each group name is wrapped in quotation marks and separated by commas + line-breaks. </li>
<li><code>]]</code> - close all brackets </li>
</ul>
<br>
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 <code>[node name="CuttheGuy" type="Node"]</code>. <br>
<br>
<h3>blue properties</h3><br>
The properties are inside blue. Since scripts are treated the same as any other resource property, the attached script is referenced in this section. <br>
<br>
Resources look like <code>icon = ExtResource( 2 )</code>.
<br>
<br>
<code>Icon</code> is my variable. I declared it in a script that the skill inherits from as <code>export(Texture) var icon = preload('res://res/art/skillIcons/EmptySkillSlot.png') setget , get_icon</code>. The <code>script</code> variable was declared by the engine and isn't in my .GD files. <br>
<br>
The <code>2</code> is an id from the yellow section. It must match the resource's number from earlier. <br>
<br>
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 <code>CuttheGuy.png</code>. <br>
<br>
Other properties are more straight-forward. They are the same <a href="https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_exports.html">exported member variables</a> declared in the node's attached .GD script, followed by an equal sign and the value. <br>
</ul>
<br>
<h3>overall </h3><br>
They aren't so confusing when read as text files. The Godot team must have planned them to be human-readable. :) <br>
<br>
<h2>altar of spellmaking </h2><br>
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. <br>
<br>
The tool is named "Altar of Spellmaking" as an Oblivion reference and uses JSON + Python3. <a href="https://docs.godotengine.org/en/stable/classes/class_json.html">Godot can interpret JSON files on its own</a>, 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. <br>
<br>
<h3>writing a skill with JSON </h3><br>
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. <br>
<br>
<center><img src="/static/img/ent/skill_json.png" alt="(image: The skill as it appears in the JSON file.)"></center>
(You can also see the text @ <a href="https://pastebin.com/aYvSr3NQ">Pastebin</a>.) <br>
<br>
All the repetitive, obvious, or derivative aspects of the skill do not need to appear in the JSON, since Python will be compensating. <br>
<br>
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. <br>
<br>
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. <br>
<br>
Conditional effects look like the image below. <br>
<br>
<center><a target="_blank" href="/static/img/ent/skill_json_cond.png">
<img src="/static/img/ent/skill_json_cond.png" alt="(image: A skill entry with a conditional effect.)" width="500" height="255.24">
</a> <br></center>
(You can also see the text @ <a href="https://pastebin.com/khSwk8js">Pastebin</a>.) <br>
<br>
The <code>cond</code> consists of the <code>code</code> (literal GDscript code) and <code>desc</code> (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. <br>
<br>
Eventually, I would prefer the desc to be an id from the translation spreadsheet instead of hard-coded text. (I discussed translation in <a href="https://www.blessfrey.me/diary/entries/201029">blessfrey in japanese</a>.) <br>
<br>
<h3>writing a Godot scene with Python3 </h3><br>
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 <code>f.write</code>. 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: <br>
<br>
<center><a target="_blank" href="/static/img/ent/skill_python.png">
<img src="/static/img/ent/skill_python.png" alt="(image: The main method in the Python script.)" width="500" height="272.73">
</a> <br></center>
(You can also see the text @ <a href="https://pastebin.com/2kV2LGCn">Pastebin</a>.) <br>
<br>
The output is a nested directory of all the skills, so I can just copy + paste the <code>skills</code> folder over the one in the engine. And it works! The first skill image's code looks machine-generated because it was. <br>
<br>
<center><a target="_blank" href="/static/img/ent/skill_dir.png">
<img src="/static/img/ent/skill_dir.png" alt="(image: skills>Common>Brawler>DirtyFighting>CuttheGuy, same directories as the engine.)" width="500" height="128.11">
</a> <br></center>
<br>
<h2>is it better than just making skills in the engine? </h2>
<br>
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! <br>
<br>
last updated March 6, 2021 <br>
<br>

@ -1,161 +0,0 @@
<!--210121,210304-->
<h1>generating an RSS feed with python bottle</h1>
march 18, 2021<br>
#bottle #rss #webdev <br>
<br>
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 <a href="https://gamedev.net/">GameDev.net</a> apparently allows you to syndicate a blog through RSS. I never thought about making an RSS feed, so why not? <br>
<br>
<h2>what is RSS? </h2><br>
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. <br>
<br>
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 <a href="https://publishercenter.google.com/publications#p:id=pfehome">Google News</a> and <a href="https://podcasters.spotify.com/submit">Spotify</a>. <br>
<br>
Here's a few examples from around the internet, a mix of large + small news websites and forums: <br>
<ul>
<li><a href="https://www.theguardian.com/world/rss">The Guardian</a> </li>
<li><a href="https://www.gamasutra.com/blogs/rss/">Gamasutra</a> </li>
<li><a href="https://www.polygon.com/rss/index.xml">Polygon</a> </li>
<li><a href="https://store.steampowered.com/feeds/news.xml">Steam</a> </li>
<li><a href="https://www.gamedev.net/tutorials/rss/">GameDev.net</a> </li>
<li><a href="https://www.nasa.gov/rss/dyn/chandra_images.rss">NASA's Chandra Mission</a> </li>
<li><a href="https://www.temptalia.com/feed/">Temptalia</a> </li>
</ul>
<br>
<h2>what goes into an RSS feed? </h2><br>
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. <br>
<br>
Blogging platforms like <a href="https://wordpress.org/support/article/wordpress-feeds/">WordPress</a> 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 <a href="https://www.blessfrey.me/diary">diary</a> page, the 'latest' box in the diary's sidebar, and the 'news' box on the <a href="https://www.blessfrey.me/">index</a> page, I figure I can format the data one more time into an XML file. <br>
<br>
Here's truncated version of blessfrey.me's feed as an example (also available on <a href="https://pastebin.com/bbrVp58E">Pastebin</a>: <br>
<br>
<code>&lt;?xml version="1.0" encoding="utf-8"?&gt;</code><br>
<code>&lt;rss version="2.0"&gt;</code><br>
<code>&lt;channel&gt;</code><br>
<code>&lt;title&gt;blessfrey.me&lt;/title&gt;</code><br>
<code>&lt;link&gt;https://www.blessfrey.me/&lt;/link&gt;</code><br>
<code>&lt;description&gt;chimchooree's dev space&lt;/description&gt;</code><br>
<code>&lt;language&gt;en-us&lt;/language&gt;</code><br>
<code>&lt;webMaster&gt;chimchooree@mail.com (chimchooree)&lt;/webMaster&gt;</code><br>
<code>&lt;item&gt;</code><br>
<code>&lt;title&gt;making an rss feed&lt;/title&gt;</code><br>
<code>&lt;link&gt;https://www.blessfrey.me/diary/entries//diary/entries/210318&lt;/link&gt;</code><br>
<code>&lt;description&gt;After a few months of quietly running my blog as practice, I want to start sharing my articles with ... &lt;/description&gt;</code><br>
<code>&lt;pubDate&gt;Thu, 18 Mar 2021 8:05 CST&lt;/pubDate&gt;</code><br>
<code>&lt;guid&gt;https://www.blessfrey.me/diary/entries//diary/entries/210318&lt;/guid&gt;</code><br>
<code>&lt;/item&gt;</code><br>
<code>&lt;/channel&gt;</code><br>
<code>&lt;/rss&gt;</code><br>
<br>
I'll explain each tag, but they are also described on the RSS Advisory Board's <a href="https://www.rssboard.org/rss-profile">Best Practices Profile</a>. There are more tags, too, so research documentation + examples to see what suits your website. <br>
<br>
<h3>XML declaration</h3><br>
Identifies the document as XML and defines the version + character encoding. It's required and must be the first line. <br>
<br>
<h3>RSS</h3><br>
The top-level element that defines version number. It's required. <br>
<br>
<h3>channel</h3><br>
Nested within the RSS tags and describes the RSS feed. It's required. <br>
<br>
There's some tags nested within the channel. <code>Title</code>, <code>Link</code>, and <code>Description</code> are required. <br>
<br>
<ul>
<li><b>title</b> - defines the title. Mine is just my website name, but large websites may have multiple feeds. It's required. </li>
<li><b>link</b> - defines the link to the channel. So, either your website or a specific area of your website. It's required. </li>
<li><b>description</b> - describe the channel in a few words. I used my website's tagline. It's required. </li>
<li><b>language</b> - defines the language, using a <a href="https://www.rssboard.org/rss-language-codes">RSS language code</a>. It's optional. </li>
<li><b>webMaster</b> - provide the contact email + name for technical issues regarding the feed. It should look something like <code>example@example.com (Name McName). It's optional. </code> </li>
</ul>
<br>
<h3>item</h3><br>
Nested within the channel. Each article will be defined with the item. <br>
<br>
There's some tags nested within the item. <code>Title</code>, <code>Link</code>, and <code>Description</code> are required. <br>
<br>
<ul>
<li><b>title</b> - defines the title of the article. It's required. </li>
<li><b>link</b> - defines the link to the article. It's required. </li>
<li><b>description</b> - summarize the article in one or two sentences. It's required. </li>
<li><b>pubDate</b> - indicates the date and time of publication, conforming to the <a href="https://www.rssboard.org/rss-profile#data-types-datetime">RFC 822 Date and Time Specification</a>. Follow a pattern like Mon, 15 Oct 2007 14:10:00 GMT. </li>
<li><b>guid</b> - A unique identifying string, which helps aggregators detect duplicates. Aggregators may ignore this field or use it in combination with the title and link values. </li>
</ul>
<br>
<h2>how to make an RSS feed </h2><br>
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. <br>
<br>
Since Bottle is just Python, I can generate the file similar to how I format my articles into diary snippets, pages, and headlines. <br>
<br>
I'll share the main code, but the full code can be viewed on <a href="https://pastebin.com/E0PvNtbp">Pastebin</a>. <br>
<br>
<h3>code example </h3><br>
def make_rss(): <br>
loc = 'diary/entries/' <br>
info = {'items': list_items(gather_and_sort(loc)[0:15])} <br>
<br>
# Return list of items <br>
def list_items(articles): <br>
f_name = "static/xml/blessfrey.xml" # the RSS file <br>
loc2 = 'https://www.blessfrey.me/' <br>
loc = 'diary/entries/' <br>
loc3 = loc2 + loc <br>
result = [] <br>
<br>
for article in articles: <br>
path = loc + article <br>
text = [] <br>
a = [] <br>
length = 0 <br>
text = article2list(article, loc) <br>
a.append(find_title(text)) <br>
a.append(find_url(path)) <br>
a.append(clean_tags(prepare_rss_summary(text, path))) <br>
a.append(find_timestamp(text)) <br>
result.append(a) <br>
<br>
clear_file(f_name) <br>
f = open(f_name, 'w') <br>
f.write("&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;" + '\n') <br>
f.write("&lt;rss version=\"2.0\"&gt;" + '\n') <br>
f.write("&lt;channel&gt;" + '\n') <br>
f.write("&lt;title&gt;blessfrey.me&lt;/title&gt;" + '\n') <br>
f.write("&lt;link&gt;https://www.blessfrey.me/&lt;/link>" + '\n') <br>
f.write("&lt;description&gt;chimchooree's dev space&lt;/description&gt;" + '\n') <br>
f.write("&lt;language&gt;en-us&lt;/language&gt;" + '\n') <br>
f.write("&lt;webMaster&gt;chimchooree@mail.com (chimchooree)&lt;/webMaster&gt;" + '\n') <br>
<br>
for r in result: <br>
f.write("&lt;item&gt;" + '\n') <br>
f.write("&lt;title&gt;" + r[0] + "&lt;/title&gt;" + '\n') <br>
f.write("&lt;link&gt;" + loc3 + r[1] + "&lt;/link&gt;" + '\n') <br>
f.write("&lt;description&gt;" + r[2] + "&lt;/description&gt;" + '\n') <br>
code = r[1].replace(loc,'') <br>
code = code.replace('/','') <br>
f.write("&lt;pubDate&gt;" + format_rss_time(code) + "&lt;/pubDate&gt;" + '\n') <br>
f.write("&lt;guid&gt;" + loc3 + r[1] + "&lt;/guid>" + '\n') <br>
f.write("&lt;/item&gt;" + '\n') <br>
<br>
f.write("&lt;/channel&gt;" + '\n') <br>
f.write("&lt;/rss&gt;" + '\n') <br>
f.close() <br>
<br>
return result <br>
<br>
# Serve XML <br>
@route('/static/xml/&lt;filename:path&gt;') <br>
def serve_xml(filename): <br>
return static_file(filename, root='static/xml', mimetype='text/xml') <br>
<br>
## Main ## <br>
<br>
if __name__ == '__main__': <br>
make_rss() <br>
run(host='127.0.0.1', port=9001) <br>
<br>
<h2>double-check </h2><br>
The <a href="https://www.rssboard.org/rss-validator/">RSS Advisory Board</a> and <a href="<a href="https://validator.w3.org/feed/">W3C</a> 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. <br>
<br>
<h2>double-check </h2><br>
Now I have an RSS feed, available at <a href="/static/xml/blessfrey.xml">https:/blessfrey.me/static/xml/blessfrey.xml</a>. Feel free to use it if you prefer to read through an aggregator and contact me if there's technical problems. <br>
<br>
Last updated March 19, 2021 <br>
<br>

@ -1,113 +0,0 @@
<!--210201,210401-->
<h1>march 2021: AI</h1>
april 1, 2021<br>
<br>
<br>
<h2>week 1, february 28 - march 6 </h2><br>
#ai #webdev <br>
<br>
<h3>monday, march 1 </h3>
<ul>
<li>I went on a walk for 3 hours. Before social distancing, I used to walk everywhere every day. It's so unhealthy to be inside all the time. </li>
</ul>
<br>
<h3>tuesday, march 2 </h3>
<ul>
<li>wrote articles for blessfrey.me </li>
<li>I don't think I moved the new files to the server, so March 1's article might not have been up yesterday. Oops sorry. </li>
</ul>
<br>
<h3>wednesday, march 3 </h3>
<ul>
<li>The server's timezone is so different from mine. It's 5 A.M. there and 11 P.M. here. I guess it's not a big deal, but it's confusing to see my March 4 article already up. </li>
<li>editted articles </li>
<li>Currently, the slime's state machine loop is to target, choose to attack, swing once, and decide whether to swing again. This means the attack speed is dependent on the AI's thinking speed. The physical act of attacking should be dependent on the character's body, not its mind. Instead, I want a pulse timer to determine the rate of swings. The body should be told to begin attacking, and the mind can tell it to stop. </li>
<li>Instead of swinging, the character's attack method sets up a pulse timer with a wait time equal to the character's attack rate. (Later, it will be modified by the sum of attack-rate-modifying effects.) The swing method sends information back to the body + mind but will swinging on every timeout until told to stop. The stop method halts the timer. </li>
</ul>
<br>
<h3>thursday, march 4 </h3>
<ul>
<li>Worked on the website. </li>
<li>Webpages that used my minimal CSS file were broken for a few days. I finally fixed it. It was so simple - the global margins were set too high. I also made the news box on the homepage a little taller. </li>
<li>Made the <a href="https://www.blessfrey.me/credits">credits</a> more compliant with Godot Engine's <a href="https://docs.godotengine.org/en/latest/about/complying_with_licenses.html">Complying with Licenses</a> page. </li>
</ul>
<br>
<h3>friday, march 5 </h3>
<ul>
<li>Developed an RSS feed for blessfrey.me's dev diary. It's served as a static file through Bottle right now. I wonder if that's okay. I'll need to test it later. </li>
</ul>
<br>
<h3>saturday, march 6 </h3>
<ul>
<li>Going through lots of accounts and deciding which ones to begin using again + which ones to delete. I should use my Fediverse and GameDev.net more, update my Ko-Fi, and pretty up my itch.io. And of course I need to post on Twitter more... Everything else should probably be deleted. </li>
<li>I had an Old School RuneScape account under the official blessfrey email? lol why </li>
<li>When I get a working version of Blessfrey that has all the current features together again, I'll upload it to blessfrey.me and itch.io. It'd be nice to just have something up at all, even if it's not so great. </li>
<li>I also should start seriously thinking about making videos, streams, podcasts, etc. I have lots of gamedev experience to draw from now that could make okay video essays + tutorials. I just...need to learn how to make videos. I wonder how much effort it would take to make one video a month? That wouldn't be a great gamedev channel, but just the idea of having videos sounds nice. Streaming would be easier once all the pretty scenes and channel art is set up. The format's better suited towards "watch me code"-style content, so I could probably stream more often than release videos. It's just awkward to explain what you're doing and make mistakes and read documentation live. </li>
<li>Too bad the little-bitty streaming sites never stay around for long - I prefer streaming on the smaller, more engaged communities than on Twitch or Youtube any day. Rest in peace, my favorite stream sites. </li>
<li>At the end of the day, I'm mostly looking to have fun. I don't really have anything I want to promote or sell right now, but I like talking to people about gamedev. </li>
</ul>
<br>
<h3>monday, march 8 </h3>
<ul>
<li>Working on the attack loop. (In Blessfrey, you begin weapon attacking and stop attacking, instead of having to spam attack input. Attack tick is determined by the equipped weapon + keyword modifiers applied to the attacker.) </li>
</ul>
<br>
<h3>friday, march 12 </h3>
<ul>
<li>Added a taskbox page to blessfrey.me. The HTML isn't working within the grid, and I probably want a grid per priority/completion level anyways, but it's fun to look at. </li>
<li>Updated some other pages. </li>
</ul>
<br>
<h3>saturday, march 13 </h3>
<ul>
<li>Attack loop is fixed. It yields in the proper places for the proper amount of time. The attacks communicate their results to the AI via signals. </li>
</ul>
<br>
<h3>sunday, march 14 - Pi Day </h3>
<ul>
<li>I mostly goofed off. I need to work on movement AI, patrol AI, equipment, barriers, teams, stats, and status effects before I can start working on the demo content. After movement + patrol AI are done, I'll release a playable snapshot on blessfrey.me.</li>
<li>For the movement AI, it would be easier to test with a static NPC. (My slime keeps running around + attacking.) So...I added a target dummy NPC. </li>
<li>He can take damage without dying and doesn't have an AI. He'll be more useful later if I can get him to calculate DPS, simulate different resistances, etc. For now, a boring old dummy is fine. </li>
</ul>
<br>
<h3>friday, march 19 </h3>
<ul>
<li>worked on RSS feed again</li>
<li>Looks like movement AI is good. Time for implementing a basic patrol for the slime. </li>
<li>I don't really know where to start with patrols. It's difficult. </li>
<li>I did something similar to patrol routes in the past. Time to look at <a href="https://docs.godotengine.org/en/stable/getting_started/step_by_step/your_first_game.html">Dodge the Creeps</a> again. (I did this tutorial the first day I installed Godot years ago.) </li>
<li>In DtC, monsters spawn from the edges of the screen. To do this, the <a href="https://docs.godotengine.org/en/stable/getting_started/step_by_step/your_first_game.html#spawning-mobs">tutorial</a> uses a <a href="https://docs.godotengine.org/en/stable/classes/class_path2d.html#class-path2d">Path2D</a> and a <a href="https://docs.godotengine.org/en/stable/classes/class_pathfollow2d.html#class-pathfollow2d">PathFollow2D</a>. </li>
</ul>
<br>
<h3>saturday, march 20 </h3>
<ul>
<li>Why am I worrying about making a pathfinding system so much when I already have one? The Knowledge Base (Blessfrey's achievement system) was designed for precisely these kind of problems: being able to identify granular tidbits of gameplay (breaking patrol routes into waypoints), track their completion (reaching a waypoint), and respond to progression (identify next point and travel to it). Since anything can subscribe to patrol-related events, I could even do things like have a guy notice his buddy never reached Point B and go searching for him. </li>
</ul>
<br>
<h3>friday, march 26 </h3>
<ul>
<li>Added a "Forgot" feature to the KnowledgeBase to support cyclical events (like slime patrols) </li>
</ul>
<br>
<h3>saturday, march 27 </h3>
<ul>
<li>Added patrols to slime, but it doesn't work yet </li>
</ul>
<br>
<h3>sunday, march 28 </h3>
<ul>
<li>Game runs again. Maybe I need to refactor movement. </li>
</ul>
<br>
<h3>monday, march 29 </h3>
<ul>
<li>Refactored character movement. </li>
</ul>
<br>
<h3>tuesday, march 30 </h3>
<ul>
<li>Completed the main story in Arcanum. I played as Mis'ror, the Elf débutante with summoning magic. </li>
</ul>
<br>
Last Updated: April 26, 2021 <br>
<br>

@ -1,56 +0,0 @@
<!--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>

@ -1,63 +0,0 @@
<!--210527,210402-->
<h1>how to make a plugin for godot engine </h1>
april 15, 2021<br>
#godotengine <br>
<br>
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. <br>
<br>
<h2>how to make a plugin </h2><br>
<br>
Navigate to your project folder. Within it, make a folder called <b>addons</b>. 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. <br>
<br>
<h3>plugin.cfg </h3><br>
Within the plugin file, make a file called <b>plugin.cfg</b>. Open it in a simple text editor like Notepad and use this template to make your config: <br>
<br>
[plugin]<br>
name="World"<br>
description="A world system."<br>
author="chimchooree"<br>
version="0.4"<br>
script="custom_node.gd"<br>
<br>
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. <br>
<br>
<br>
<h3>custom_node.gd </h3><br>
Within the plugin file, make another file called <b>custom_node.gd</b>. The contents of this node should look like this: <br>
<br>
tool <br>
extends EditorPlugin<br>
<br>
func _enter_tree():<br>
&nbsp;&nbsp;&nbsp;&nbsp;add_custom_type("Room", "Node2D", preload("room.gd"), preload("World.png"))<br>
<br>
func _exit_tree():<br>
&nbsp;&nbsp;&nbsp;&nbsp;remove_custom_type("Room")<br>
<br>
To add a type, supply the <b>add_custom_type</b> 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. <br>
<br>
<br>
<h3>.gd files </h3><br>
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. <br>
<br>
Since my example calls for a Node2D with a "room.gd", I can make a file within the plugins folder called <b>room.gd</b>. I'll keep the contents extremely simple for now: <br>
<br>
extends Node2D <br>
<br>
<br>
<h3>icon </h3><br>
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. <br>
<br>
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 <a href="https://github.com/godotengine/godot/blob/master/LOGO_LICENSE.md">git repo</a>. <img src="/static/img/ent/plugin_icon.png" alt="(image: Squished Godot head)"> <br>
<br>
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." <br>
<br>
<br>
<h2>how to add a plugin </h2><br>
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. <br>
<br>
Happy coding! <br>
<br>
<br>
Last updated April 30, 2021 <br>
<br>

@ -1,46 +0,0 @@
<!--210501,210402-->
<h1>an enemy patrol in godot </h1>
april 29, 2021<br>
#ai #knowledgebase <br>
<br>
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. <br>
<br>
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. <br>
<br>
<h2>fast explanation of the knowledge base </h2><br>
<br>
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. <br>
<br>
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. <br>
<br>
I discuss its design and use more in other articles. <br>
<br>
<h2>why involve the achievement system? </h2><br>
<br>
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. <br>
<br>
<h2>getting back into the patrol flow </h2><br>
<br>
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. <br>
<br>
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. <br>
<br>
<h2>pathfinding and moving </h2><br>
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. <br>
<br>
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. <br>
<br>
<h2>cleaning up </h2><br>
<br>
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. <br>
<br>
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. <br>
<br>
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. <br>
<br>
<h2>finally - the enemy patrol! </h2><br>
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! <br>
<br>
<br>
Last updated April 30, 2021 <br>
<br>

@ -1,135 +0,0 @@
<!--210401,210601-->
<h1>april 2021: will patrols ever work? </h1>
may 1, 2021<br>
#diary <br>
<br>
<br>
<h3>thursday, april 1 - April Fool's Day </h3>
<ul>
<li>I got gnomed by my group leader, and FlightRising's rotated dragons made me think my graphics card was broken. I am the April Fool this year. </li>
<li>To be fair, my graphics card did spark yesterday. It was scary. </li>
</ul>
<br>
<h3>friday, april 2 </h3>
<ul>
<li>maintained blessfrey.me. I like my website and it's starting to get a nice amount of content on it. I should do a graphics update sometime and make the diary + diary snippets pretty. I should at least choose my own color scheme instead of continuing to use the first scheme <a href="https://coolors.co/">Coolors</a> gave me. </li>
<li>refactored AI and character movement. </li>
<li>Before, moving in a direction (like WASD movement) and moving to a point (like point and click) were completely separate systems. I brought them together as one system. This is necessary if I want to be able to modify character vectors through status effects, but it's also easier to maintain. </li>
<li>I had several ways to initiate different phases of movement scattered throughout the program. I brought them together with an entry point. </li>
</ul>
<br>
<h3>wednesday, april 7 </h3>
<ul>
<li>Added handlers. The character shouldn't be its own handler because a handler can only handle one thing. I don't view this as an issue to correct, so instead the AI generates a handler to interpret the results of its attempts to move. Since NPCs use state machines as AI, they will do this during setup upon entering the Movement state and free them upon exiting. </li>
<li>Added support for speed and velocity modifiers. </li>
</ul>
<br>
<h3>thursday, april 8 </h3>
<ul>
<li>Refactored pathfinding. </li>
</ul>
<br>
<h3>friday, april 9 </h3>
<ul>
<li>Utter confusion over Slime Patrol begins. The slime won't move no matter what. I didn't spot the issue that prevented slime move-to-point until the very end of the month, which is frustrating but led to catching and fixing tons of potential issues in movement and related systems. It also got me to clean up the pathfinding debugger and make its output more detailed. </li>
<li>Of course I end up spending way more time than expected on the final update before my first release version...Can't wait to have something up, even if it will be really bare bones! Once slime patrol works, movement is refactored, Bingo's tossed back in, and any noticeable issues are ironed out, I'll ship an HTML5 version to embed on my website. </li>
</ul>
<br>
<h3>saturday, april 10 </h3>
<ul>
<li>Refactoring and debugging...Broke WASD movement and fixed it. </li>
</ul>
<br>
<h3>sunday, april 11 </h3>
<ul>
<li>Refactoring and debugging...Broke mouse movement and fixed it. </li>
</ul>
<br>
<h3>monday, april 12 </h3>
<ul>
<li>Refactoring and debugging...Slime moves in the wrong direction for an indisernible reason. Massively simplified how the slime identifies the order in which patrol points should be followed. </li>
</ul>
<br>
<h3>tuesday, april 13 </h3>
<ul>
<li>I've been writing and drawing more lately. </li>
<li>I feel more comfortable defining the mayor's character now, which is great because she's a major antagonist. She's supposed to be somewhat close in age and maturity to the teen protagonists, despite being in control of a city. I like the idea of a manchild leader with a powerful artifact. Since I'm not necessarily comfortable with writing conflicts between her and the main characters as life-or-death maximum evil encounters, I've been coming up with other stakes for the cast. It's more like petty maximum evil encounters. </li>
<li>She's always been "the mayor" lol. I finally have an idea what her name should be. Since the player's default name is Helia (a sunny name), the mayor will be given a moon name in contrast. Celeste and Selene feel overused in fantasy, and Liviana is too strongly associated with Magic: The Gathering for me. I was considering Chandra, Lunette, Charon, Dione, Liviana, and Mona...Do you think Chandra is too Hindi? For me, it has a strong local association with the Chandra space mission which is local to the setting + it's similar to the boomer name Shondra. </li>
</ul>
<br>
<h3>wednesday, april 14 </h3>
<ul>
<li>I'm partially vaccinated against COVID now. It hurts... </li>
</ul>
<br>
<h3>friday, april 16 </h3>
<ul>
<li>I was stuck in Sender Station in Anachronox for a while and finally got to Sunder. Finally! Progress in something! </li>
<li>The slime moves in a different direction depending on where it spawned. </li>
</ul>
<br>
<h3>saturday, april 17 </h3>
<ul>
<li>Refactoring and debugging... </li>
</ul>
<br>
<h3>sunday, april 18 </h3>
<ul>
<li>The first major problem is found, although correcting it doesn't change the slime's bizarre behavior yet. I was declaring that the slime reached a waypoint when it merely reached a dot. </li>
<li>In Blessfrey, pathfinding is made from a discrete line of "dots" and "waypoints." Waypoints are goal destinations, including waypoints along a patrol route and the global mouse position where the player point and clicked. Dots are the AI-determined positions inbetween the character and the next waypoint. <li>
<li>Refactoring and debugging...Make sure all entities are at position 0,0. </li>
</ul>
<br>
<h3>wednesday, april 21 </h3>
<ul>
<li>The player and NPCs need specialized movement handlers, so I create an Events plugin for Godot as a base movement handler. This allows me to fix player click-to-move, which was broken again. The individualized slime movement handler brings out the slime's first intended behavior! Even though he is moving in the wrong direction, he is selecting the nearest waypoint then cycling through them in order. </li>
</ul>
<br>
<h3>thursday, april 22 </h3>
<ul>
<li>I've been playing Just Dance 4 every day since my arm recovered from the vaccine. I plateaued for a long time with it and am finally making progress. <a href="https://www.youtube.com/watch?v=VAsIrQDxB5M">Selfish & Beautiful Girl</a> came up in autoplay and it always sets me off on a Just Dance craze when that happens. I wish Dance Corporation was real so so so bad. </li>
<li>I determine the slime is patrolling as intended, but his first dot is always 51, -13 even though it's way out of the way. Since that's imbedded in a wall, he never reaches it and never continues his patrol. </li>
<li>Modifying the position of the slime's spawnpoint sometimes changes the first destination. Modifying the shape of the room's navigation also changes the first destination. I don't see any pattern. </li>
<li>Debugging and refactoring...</li>
</ul>
<br>
<h3>friday, april 23 </h3>
<ul>
<li>How did Just Dance released Big Bang songs without me hearing about it? Not only Big Bang, but also G-Dragon's solo career. Amazing!!! Fantastic Baby is Japanese only. I thought the Wii was region-free, but apparently not...Guess I'm stuck to playing 'pretend Just Dance' with Youtube recordings lol. But Bang Bang Bang got a real American release for 2019 that I will definitely investigate.</li>
<li>There are two entry points for movement: move_to_position and move_to_object. I decide to make move_to_object the only entrypoint. This means a waypoint is generated at the position the player wants to move to. This allows for player movement to be more similar to character patrol movement, and it opens up the possibility of allowing the player to select multiple movement points or to draw his own patrol route. </li>
<li>Debugging and refactoring...</li>
</ul>
<br>
<br>
<h3>saturday, april 24 </h3>
<ul>
<li>Breakthroughs all around. I've been developing a quinoa recipe for years and finally used molasses. Molasses isn't really sweet but has more of that robust, almost spicy flavor that is the key to my vision. I tried with brown sugar before, but it mostly just makes quinoa taste sweet which I dislike. For two people I cook 2/3 c soaked quinoa (remove the frothy saponins), 4/3 c water, 1 tsp onion powder, 1 tsp garlic powder, 1/4 tsp salt, 1 tbsp squeezed lemon juice (no seeds), 1 tbsp olive oil, between 1/2 and 1 tbsp molasses, some lemon pulp, and four halved kalamata olives together in an uncovered saucepan. For molasses, 1/2 is too mellow and sweet, while 1 tbsp is strong and spicy but still good. Stir occasionally. After most of the water is boiled away, cover and steam the quinoa at reduced heat. Before serving, mix in mint and chives. It goes really well with tomatos, cucumbers, and spinach, but any vegetables will do. Finally, the taste I envisioned in my head! I'm still tweaking the proportions, but those are definitely the right ingredients. If only kitty didn't force me to give up my chive and mint garden. That would have been even better. </li>
<li>The flavor's supposed to be reminiscent of how I used to prepare Near East's Rosemary & Olive Oil Quinoa & Brown Rice blend. It got bland with the recipe change a while back, then a few years ago, stores stopped carrying it at all. It was the only box food I ever liked, though, and I've never stopped trying to recreate it. (Even though my version doesn't have rosemary, brown rice, or pretty much any of the same ingredients and tastes completely differently but whatever lol.) </li>
<li>The player character is the original character, and the slime was modified from that base. Apparently it had a camera. I reordered the spawn order of the characters, which caused the viewport to follow the slime instead of the player character. It looks like the first camera in the tree is the active one. </li>
<li>I write some story for the Abyss and the Slime Kingdom, but it's not very interesting or funny. At least if I write a lot, I can be choosy and only take the few best events. </li>
</ul>
<br>
<h3>sunday, april 25 </h3>
<ul>
<li>I fixed it. It's not the best fix, but I understand the problem. The slime finally patrols perfectly, no hiccups or unexpected behavior. If the player interrupts him, he can return to the patrol route and resume patrolling. </li>
<li>The problem has to do with initialization. Apparently, the slime's initial global position is 0,0 before it is set to it's spawner's location. </li>
<li>In pathfinding, the first dot is always the character's current global position (the starting point). If pathfinding is performed during this split-second, it will set the starting point as near the origin as possible. The room's navigation polygon didn't always contain the origin and I guess it wasn't always formed correctly, so that's why 51, -13 and points to the left, right, top, and bottom where being set as the first dot. </li>
<li>The pathfinding should take place after everything is ready. I couldn't find the proper order to make this happen naturally. Even with the AI's setup dead last, the global position was not yet set. Delaying the first state transition with a .1 timer does the trick. </li>
<li>The slime spawner sets the slime's global position during its _ready method. Since it's at the top of the tree, I think all the children have to complete their _ready methods before the global position will be set. A major part of the last character refactor was differentiating between logic that should be called at ready in an arbitrary order and logic that should be called in a separate setup method in a controlled order. To really correct this problem, I'll have to do something similar across all game objects so that the room can be set up before the slime is set up. </li>
<li>More refactoring and debugging so everything's clean and pretty. </li>
</ul>
<br>
<h3>monday, april 26 </h3>
<ul>
<li>Now time to make sure all movement uses the new system. Move_to_skill_target, move_to_item, and move_to_target were all handled separately. </li>
<li>The first thing I'd like to tackle is moving to a moving target. I draw diagrams and decide that having move_to_object only consider waypoints may be inferior to also taking targets. Setting the target as the next waypoint allows the player to tab through other targets without interrupting autopathing. Also, tracking movement is probably a job once again for the Knowledge Base and event handlers. </li>
</ul>
<br>
<h3>friday, april 30 </h3>
<ul>
<li>Website maintenance day. I'm starting to wonder if anyone reads this? I don't share links, but it's probably somewhere in Google by now. Especially since I write about Godot Engine some, and that's definitely a topic not a ton of people write about. To see visitors, you need to use trackers, though, and the big ones are overly invasive and by companies I don't particularly like. Maybe I'll have to do some research and see what's available. </li>
<li>It's kind of annoying that the server is in almost a day ahead since it's in a different country. I should change the time zone to match mine sometime. I know it's online, so people in every time zone can read it if they want, but it sure makes predicting articles releases more confusing for me. <br>
</ul>
<br>
Last Updated: June 6, 2021 <br>
<br>

@ -1,102 +0,0 @@
<!--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>

@ -1,39 +0,0 @@
<!--210318,210304-->
<h1>writing a game design document </h1>
may 27, 2021<br>
#gamedesign #gdd #worldbuilding <br>
<br>
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. <br>
<br>
<h2>why keep a GDD if everyone's already on the same page? </h2><br>
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. <br>
<br>
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. <br>
<br>
<h2>download the GDD template </h2><br>
Download my <a href="/download/DesignDocumentTemplate.docx">GDD template</a> 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. <br>
<br>
<h2>worldbuilding bible </h2><br>
I feel it's easier to keep some parts in a different format from my GDD. For worldbuilding, I use a modified version of <a href="https://ellenbrockediting.com/worldbuilding-bible-template/">Ellen Brock's worldbuilding questionnaire</a>. 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. <br>
<br>
<center><img src="/static/img/ent/gdd_worldbuilding.png" alt="(image: Ellen Brock's worldbuilding questionnaire.)"></center> <br>
<br>
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. <br>
<br>
<h2>story + dialog </h2><br>
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. <br>
<br>
<center>
<a target="_blank" href="/static/img/ent/gdd_diagram.png">
<img src="/static/img/ent/gdd_diagram.png" alt="(image: event diagram with an unnecessary amount of choices)" width="500" height="313">
</a></center><br>
<br>
To make my flowcharts, I open diagramming software like <a href="https://wiki.gnome.org/Apps/Dia">Dia</a> 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. <br>
<br>
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. <br>
<br>
<h2>backing up your GDD </h2><br>
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! <br>
<br>
Last updated May 26, 2021 <br>
<br>

@ -1,108 +0,0 @@
<!--210501,210701-->
<h1>may 2021: mostly GDD and CSS</h1>
june 1, 2021<br>
#diary <br>
<br>
<h3>wednesday, may 5 </h3>
<ul>
<li>Still trying to find a nice way to organize my GDD (game design document). There's also a lot of random notes scattered across notebooks and my computer, so I'm also trying to move them all into one place. </li>
<li>I have a general GDD, but I also have a worldbuilding folder for documents about countries and characters and an events folder for outlines, events, and dialog. </li>
<li>My GDD is a 100 page document with the pitch; roadmap; notes on inspiration; a mock store page summary; default controls for the game; notes on art, color, and sound direction; the core loop, the main game mechanics, a philosophy and style guide for the design; a style guide for coding and development; notes on the UI, characters, classes, skills, items, method of progression, social aspects of the game (like Persona character interaction), factions, and the world; and notes on key art, trailers, social media, conventions, contests, and other marketing and opportunities. I also have a reading list with books with themes that complement my game, and (because I can't help myself) a list of easter eggs I want to tuck away in my game. Obviously, the characters, world, and story sections are quick overviews and more focused on technical aspects, since there are separate documents for the designs. </li>
<li>I loosely use <a href="https://ellenbrockediting.com/worldbuilding-bible-template/">Ellen Brock's template</a> and make one per country in my game. Basically, I keep the headings and add my own subheadings relevant to my design with a succinct description. If there's a bullet that fits, I use that as the subheading. If I don't even know where to begin, I can use the bullets to get inspired for what to design next. </li>
<li>My characters aren't very structured, but I keep a text document for each for now. If anything occurs to me, I can jot it in there. </li>
<li>I also keep a research document with the worldbuilding section. If I study something that seems cool for my game but doesn't necessarily fit anywhere, I keep it in here. It uses the same headings from Brock's template. </li>
<li>For game events, I use <a href="https://wiki.gnome.org/Apps/Dia">Dia</a> to make a flowcharts for overall outlines, individual events, and dialog. I keep an outline for the game arcs from beginning to end, along with side arcs. Any time I have an idea for an event or conversation, I write it as its own flowchart, noting goals for the scene at the top. Color coding keeps emotes, conditions, stage directions, and different characters obvious at a glance. <br>
</ul>
<br>
<h3>thursday, may 6 </h3>
<ul>
<li>Filling out GDD folders. </li>
</ul>
<br>
<h3>friday, may 7 </h3>
<ul>
<li>Bringing data out of my personal wiki into my GDD. As much as I love wikis for taking study notes and keeping personal journals, they're too scattered for designing and developing a game. Curding and slowly accumulating loosely connected articles doesn't really match my thought process here. It's much easier to pull out a well-structured word document, jump to the section I want, and see at a glance which sections are incomplete or overly detailed. </li>
</ul>
<br>
<h3>saturday, may 8 </h3>
<ul>
<li>More GDD work. </li>
<li>Maintain website. </li>
</ul>
<br>
<h3>sunday, may 9 </h3>
<ul>
<li>More GDD work. Removed the slime again from Blessfrey, but I don't feel like working on it today. </li>
</ul>
<br>
<h3>wednesday, may 12 </h3>
<ul>
<li>General website maintenance. </li>
</ul>
<br>
<h3>thursday, may 13 </h3>
<ul>
<li>Got my second COVID shot. The first one just caused some soreness, but this one felt like the flu for a week. Glad that's over with for now. </li>
</ul>
<br>
<h3>friday, may 14 </h3>
<ul>
<li>One of the prettiest lolita dresses popped up on Lace Market, <a href="https://lolibrary.org/items/ap-raffine-op">Angelic Pretty's Raffine OP in Navy</a>. Despite being made for Japanese girls, I figured the sizing would work since plenty of average-sized and even plus-sized western women wear AP online. The measurements actually are a little comfortably loose for me, except for the shoulder measurement of 35cm. Sheesh. My shoulders are at least 36cm, so it'd be so uncomfortable if I could move my arms at all. I looked up what other girls do, and they salvage the bows or bring in a similar fabric and expand the dresses that way. What a mess. I guess that's why they always wear JSKs. </li>
<li>Broke the diary entries into smaller pieces in the Bottle script, so they can be styled with CSS. I'm doing more of the HTML and CSS on the server instead of directly adding it in my articles. </li>
<li>Updated the look of the navigation bar. I want it to look more like an RPG skillbar with the nav buttons resembling skill icons. </li>
<li>Updated the look of the diary page with new art assets and CSS. I also am finally pulling the tags from articles into the snippets. </li>
<li>Finally cleaned up the footer copyright. I enjoyed the precision of counting the moment down to the zeptosecond, but it looked pretty ugly. </li>
</ul>
<br>
<h3>saturday, may 15 </h3>
<ul>
<li>I'm so done with only having 2 pairs of pants that are appropriate for fall/winter temperatures and aren't dress pants. It wasn't so bad before COVID because I didn't feel the need to wash clothes after every use, but I really should have at least a week's worth of clothes for the colder seasons. I have beautiful fabric for pants just sitting around, too. It's time to get back on the wagon with sewing. </li>
<li>Update the appearance of the diary page a little more. </li>
</ul>
<br>
<h3>sunday, may 16 </h3>
<ul>
<li>Drawing character art. </li>
</ul>
<br>
<h3>friday, may 21 </h3>
<ul>
<li>Added an item spawner to test move-to-pickup. Fixed some item bugs. </li>
</ul>
<br>
<h3>saturday, may 22 </h3>
<ul>
<li>Drawing some more. Also add some more ideas in the GDD of a different game. </li>
<li>Automoving to an item or skill target was handled slightly differently than the other movement in the game. I'm addressing this to bring all movement together. My design for movement doesn't support following a moving target, though, so I figure I'll address it after I'm happy with the general design for movement. </li>
<li>The player spawner acted more like its own room, so the player could be spawned anywhere. I'd rather a room be a room and a spawner be a spawner. This means the Character Creation screen is now technically a room, just so it can display the player character. </li>
</ul>
<br>
<h3>sunday, may 23 </h3>
<ul>
<li>Drawing character art. </li>
</ul>
<br>
<h3>monday, may 24 </h3>
<ul>
<li>My high school economics teacher showed us a Dave Ramsey video on how you can invest a few hundred dollars a month (I think he even called that our "daily Starbucks" lol) into money market funds and soon be able to buy new cars every year with the interest. I must not understand this well. My investment service's money funds all have a 7-day yield between .01% and .03%. You'd need hundreds of thousands if not a million dollars locked away just to get a $20000 car annually. I don't go to Starbucks anyways, but that's a millenium's worth of talls. </li>
<li>General website maintenance. </li>
</ul>
<br>
<h3>wednesday, may 26 </h3>
<ul>
<li>So busy. </li>
<li>Today, I'll push out my CSS update to go along with tomorrow's article. </li>
</ul>
<br>
<h3>saturday, may 29 </h3>
<ul>
<li>No one told me Anachronox ended on a cliffhanger!!! And the game performed so poorly, the company went out of business before making a sequel. Oh no!!! </li>
<li>At least it's not that bad of a cliffhanger. The story was tied up okay, but it's kind of obvious some of the less important characters were about to play a story critical role. Maybe the fight against the second-to-last boss would have been expanded into an entire arc, too, instead of a sudden boss battle. </li>
<li>Aw man, Anachronox was so fun. I really can't believe gamers let that game series die out...Why do people hate the things I love? </li>
<li>I love Anachronox. I don't even know what to play now that it's over. I'm busy lately anyway, I guess. I might have to go back and see what the end credits meant about the green man, though. </li>
</ul>
<br>
<h3>monday, may 31 - Memorial Day </h3>
<br>
Last Updated: June 9, 2021 <br>
<br>

@ -1,124 +0,0 @@
<!--210429,210402-->
<h1>follow a moving target </h1>
june 10, 2021<br>
#ai #character #movement <br>
<br>
After redesigning the movement system to support <a href="../entries/210429">patrols</a>, I realized the path remains static even if the target moves. Time to tweak the design again. <br>
<br>
<h2>what must be done </h2><br>
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. <br>
<br>
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. <br>
<br>
<h2>upgrading the movement system </h2><br>
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. <br>
<br>
Before, it only set the next waypoint and built a path between the character and the waypoint. <br>
<br>
<h3>old path_to_object </h3><br>
<code># Set Dots Between Character + Given Object<br>
func path_to_object(goal):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if goal.is_in_group("waypoint"):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_next(goal)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;room.add_waypoint(goal.get_gpos())<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;path_to_position(goal.get_gpos())<br>
<br>
# Set Dots Between Character + Given Position<br>
func path_to_position(goal_pos):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_dots(find_dots_pos(goal_pos))</code><br>
<br>
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. <br>
<br>
<h3>the character receives a waypoint instead of the target, so he is unaware of his target's movement </h3><br>
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. <br>
<br>
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. <br>
<br>
<h3>constantly updating the path overwhelms the character </h3><br>
If the target is moving, pathfinding needs to be reassessed periodically. However, it isn't as simple as calling the pathfinding method every tick. <br>
<br>
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. <br>
<br>
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. <br>
<br>
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... <br>
<br>
<h3>new path_to_object </h3><br>
# Set Dots Between Character + Given Object<br>
func path_to_object(goal):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Next Waypoint<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_next(goal)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var goal_pos = goal.get_gpos()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var dots = get_dots()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var cd = get_current_dot()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# If no current dot, set dots between user and goal<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if cd == null:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_dots(find_dots_pos(get_gpos(), goal_pos))<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MessageBus.publish("moved", self)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var last_dot = cd if len(dots) == 0 else dots.back()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Make sure goal has moved significantly<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if goal_pos.distance_to(last_dot) <= get_intimate_space():<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# If goal moved further away<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if get_gpos().distance_to(last_dot) > find_distance(goal):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# If no dots, generate new ones<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if len(dots) == 0:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_dots(find_dots_pos(get_gpos(), goal_pos))<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MessageBus.publish("moved", self)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# If dots, only recalculate part of the path<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var near = get_dots()[0]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for dot in get_dots():<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if dot.distance_to(goal_pos) < near.distance_to(goal_pos):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;near = dot<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var i = get_dots().find(near)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_dots(get_dots().slice(0, i - 1) +<br> find_dots_pos(get_dots()[i-1], goal_pos))<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# If goal moved closer<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_dots(find_dots_pos(last_dot, goal_pos))<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MessageBus.publish("moved", self)</code><br>
<br>
<h2>testing </h2><br>
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.<br>
<br>
<code>var time = 0
func _process(delta):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time += delta
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var mod = Vector2(delta, cos(time) * 2)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set_gpos(get_gpos() + mod)</code><br>
<br>
<br>
<h3>the character never stops autopathing, even after picking up the item </h3>
<br>
Okay, one more fix, then I'll have it. <br>
<br>
Previously, the movement AI relied on conditional statements in its process to detemine arrival at the goal. Instead, the <a href="../entries/210402">achievement system</a> 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. <br>
<br>
Moving the conditional statements from _process to the function that handles the outcome of movement events. <br>
<br>
<h3>new result code </h3><br>
<code>#enum MOVED {ARRIVED, NOT_MOVING, TOO_SLOW, WRONG_DIRECTION}<br>
func handle_moved_result(result, new_user):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if result == 0:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new_user.think("UserMove/Arrived")<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if get_user().get_target() != null:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if track:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if get_user().find_distance_to_target() <= get_user().get_intimate_space():<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;emit_signal('item_arrived')<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;get_waypoints().pop_front()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new_user.clear_movement()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return</code><br>
<br>
<h2>just a few changes makes a big difference </h2><br>
<center>
<img src="/static/img/ent/follow.gif" alt="(image: Angel adjusts her pathfinding to follow a moving item.)"></center><br>
<br>
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! <br>
<br>
(I haven't considered moving items before. They're pretty cool. It's like catching butterflies in Skyrim.) <br>
<br>
Last Updated October 29, 2021
<br>

@ -1,69 +0,0 @@
<!--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>

@ -1,107 +0,0 @@
<!--210601,210801-->
<h1>june 2020: attack and movement </h1>
july 1, 2021<br>
#diary <br>
<br>
<h3>friday, june 4 </h3>
<ul>
<li>I ordered some rayon rib knit for a mock turtleneck, but customer service emailed me saying that it's out of stock. They had tons in stock when I ordered, and there's still 70 yards left today...How is 70 out of stock? ;-; </li>
<li>I want the setting for Blessfrey to be like the United States but not quite. I was using Harry Turtledove's Southern Victory series as a starting point. The Confederate States is definitely an interesting place to study, but I was just using it as an obvious example of an alternative history setting. Really thinking about it, though, the failed U.S. state that interests me the most is Nickajack. The interior of the Confederacy was speckled with pro-Union enclaves trapped within pro-Sucession states. If a particular secession movement succeeded, north Alabama and parts of Tennessee and Georgia would have become the pro-Union state of Nickajack. The idea of Nickajack has captured my imagination since I was a kid, since it comes up in local politics and rants from grownups every once in a while. The disconnect between north and south Alabama persists to this day. Huntsville is a modern booming Southern city, outpacing the other big Alabama cities of Birmingham, Montogomery, and Mobile, but the state refuses to recognize Huntsville as a bigger district. Huntsville also pays a lot of tax money into Alabama, but spending disproportionately favors southern Alabama. Not only that, but there's a distinct cultural disconnect, with northern Alabama's history of space technology, connection to Nazi Germany, and general higher levels of education, income, racial diversity, and Democrat voters compared to the largely agricultural and Republic south. I'm not even sure how accurate this stuff is, though, since this is my summary of years of angry grownup hearsay, I didn't intentionally follow local politics until high school, and I've never lived long enough in southern Alabama to gain a sense of any significant cultural or political differences. At least my husband went to school in southern Alabama, but I don't have access to much more than his largely unfavorable experiences in a small town. Regardless, considering a successful state of Nickajack as a setting seems really fun and probably interestingly obscure for people living elsewhere. </li>
<li>When I went online to learn more about Nickajack, the amount of sources was shockingly small compared to how often I've heard it discussed. About the best I could do is find books about it that are luckily available at my local library. I guess I should have checked the library first, since the only times Nickajack's come up in my reading have been in local historical journals and newspapers in the library's archives. Makes sense, since I only hear about other secession struggles directly from people who used to live in states with similar tension. (Superior vs. Michigan is one I learned through a Michigan-born classmate in 5th grade. Is everyone exposed to these rants, or do I attract separationist radicals?) </li>
<li>Civil War era secessionist states are open wounds in American politics these days. The reality of the Confederacy or my representation of it doesn't really matter, though, since it's been reduced to a conversial symbol. It either represents (to the left) racism, slavery, and radical right politics or (to the right) the rejection of the expanding government and progressive policies invading the private lives of citizens. If anyone cares at all, it could mean virulently angry things to people in the current year. Nickajack is personal to me and very unique as a setting, though, so I'll use it. </li>
<li>Anyway, I checked out some cool library books about local history. It'll be fun to intentionally study it instead of generally learn it through osmosis. </li>
</ul>
<br>
<h3>sunday, june 6 </h3>
<ul>
<li>I feel like I've been so busy with translating manga, sewing new clothes, and socializing that I haven't worked on Blessfrey enough. I kinda forgot I'm so close to my first demo release. That would be so cool. Today I worked on refactoring movement. </li>
</ul>
<br>
<h3>tuesday, june 8 </h3>
<ul>
<li>First time going by myself to work on my laptop at the cafe since 2018! They let you go maskless if you're vaccinated and it's usually empty anyway, so I finally got to smile at the barista. </li>
<li>Finally got to wear my new lipstick, too. Too bad my rosy mauve Rush expired, and Urban Decay's discontinuing their perfect Vice lipsticks. Maybe I should have bought one last bullet, but I couldn't resist a fancy closeout Becca lipstick. Mauve Truffle was out of stock, so I got pink Sorbet. I wore it with Looxi Genesis and JD Glow Good Gawd for a silver-lavender eye with a green shift. </li>
<li>Let's make a schedule for sewing my new capsule wardrobe: </li>
<li>Today - 泣き虫 cutting done </li>
<li>June 10 - findings, trims, and accent fabric arrive </li>
<li>June 13 - turtleneck, pajama pants patterns done </li>
<li>June 20 - foil, tissue, nightshade cutting done </li>
<li>June 27 - turtleneck, pajama pants sewing done </li>
<li>July 4 - order Penitentiary, 4x2 knit </li>
<li>??? - those arrive </li>
<li>July 4 - 泣き虫 sewing done </li>
<li>July 11 - bomber pattern done </li>
<li>July 18 - bomber cutting done </li>
<li>July 25 - bomber sewing done </li>
<li>August 1 - plaid pants pattern done </li>
<li>August 8 - plaid pants cutting done </li>
<li>August 15 - plaid pants sewing done </li>
<li>August 22 - silk turban, silk scrunchies done </li>
<li>August 29 - wrap pajama pattern done </li>
<li>September 5 - wrap pajama cutting done </li>
<li>September 12 - wrap pajama sewing done </li>
<li>September 19 - kimono pattern done </li>
<li>September 26 - kimono, 4x2 turtleneck cutting done </li>
<li>October 3 - kimono, 4x2 turtleneck sewing done </li>
<li>October 10 - bodice pattern done </li>
<li>October 17 - bodice cutting done </li>
<li>October 24 - bodice sewing done </li>
<li>October 31 - rickrack pajamas pattern done </li>
<li>November 7 - rickrack pajamas cutting done </li>
<li>November 14 - rickrack pajamas sewing done </li>
<li>November 21 - trip </li>
<li>If I keep moving at a good pace, I can finish 3 mock turtlenecks, a button shirt, a bodice, pants, a jacket, a kimono, two pajama sets, a silk turban, some silk scrunchies, and a pair of pajama pants for my husband. If I go faster, I can also include the two shorts I have planned. Too bad if not, but it's still a big step up from wearing hand-me-downs and my husband's clothes all the time. Plus, pajamas that actually fit will be so great. </li>
</ul>
<br>
<h3>wednesday, june 9 </h3>
<ul>
<li>Finally, the character follows a moving target. </li>
</ul>
<br>
<h3>thursday, june 10 </h3>
<ul>
<li>I got groceries at Target, so I had to peek into the Gamestop next door. What happened? Even the Nintendo game section is tiny now, and 70% of the store is dedicated to Star Wars and anime clothes and toys. It's been trending this way for years, but at least they had a wall of Nintendo games before. They only had a few used games and controllers, and I didn't see any consoles at all. It might as well be Hot Topic now, minus the band merch. </li>
<li>Did some blogging </li>
</ul>
<video width="480" height="360" controls>
<source src="/static/img/webm/WhereAllThe Videogames_WillSmith.webm">
Your browser does not support the video tag to play the Will Smith "where all the video games?" WebM.
</video>
<br>
<h3>saturday, june 12 </h3>
<ul>
<li>OBS hasn't been able to capture my windows since around February, so I stopped recording GIFs. (lol) I really do want some GIFs for my dev diary, so after looking around, it's apparently a widespread issue for Linux users with no real fix in sight. Dang, I guess it's a real issue. I'm not a fancy streamer with lots of scenes or anything, so I can just record my screen, but that's not good news for OBS at all. For now, my crops are just going to have be taken from the entire screen instead of just the little window. </li>
<li>At least ffmpeg, though very difficult to write, is able to make nice gifs. If OBS gets worse, I guess I'll have to use it for screen capture, too. </li>
<li>Anyway, now my latest article has a GIF, and I can get rid of the following-moving-target test assets. </li>
</ul>
<br>
<h3>tuesday, june 15 </h3>
<ul>
<li>Made move-to-skill-target work more similarly to move-to-item-target </li>
<li>Added Slime Rally, a test enemy that constantly patrols between two points and never attacks. </li>
<li>Began documenting the attack loop better and fixing minor issues </li>
<br>
<h3>thursday, june 17 </h3>
<ul>
<li>Added Training Dummy back, since the old version was incompatible </li>
<br>
<h3>friday, june 18 </h3>
<ul>
<li>Fixed UI target bars to accurately reflect target's HP </li>
<br>
<h3>monday, june 21 </h3>
<ul>
<li>updated the player's attack loop and connected it to the achievement system </li>
<br>
<h3>saturday, june 26 </h3>
<ul>
<li>Officially started househunting and put our first offer on a house down today. It's a highly competitive market, but interest rates are below 3%. Mostly, my husband is sick of apartment rent for a decent apartment costing more than a mortgage payment. Househunting is unexpectedly time-consuming with all the tours and negotiations. My parents never moved houses, so this is a completely new process to me. </li>
<li>Apparently, people are flooding over from California and buying houses that they've only seen pictures of, waiving inspections, offering 50k over the asking price, and paying in cash. A realtor friend said some realtors are even buying cruises for the sellers as part of the deal. </li>
</ul>
<br>
<h3>sunday, june 27 </h3>
<ul>
<li>Player follows Slime Rally during attack loop </li>
<br>
Last Updated October 17, 2021 <br>
<br>

@ -1,41 +0,0 @@
<!--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>

@ -1,35 +0,0 @@
<!--210701,210901-->
<h1>july 2021: mostly moving (IRL, not in-game)</h1>
august 1, 2021<br>
#diary <br>
<br>
<h3>sunday, july 4 - Independence Day </h3>
<ul>
<li>My favorite holiday! I celebrate every year by making a red-white-and-blue dessert and watching the fireworks. This year, I tried blueberry and strawberry crepes. Surprisingly, they aren't any harder to make than omelets. I think I'll be able to cook these more regularly! I followed <a href="https://www.iheartnaptime.net/strawberry-crepes/">I Heart Nap Time's recipe</a> but used almond flour instead. </li>
<br>
<h3>friday, july 8 </h3>
<ul>
<li>I ordered some rayon rib knit for a mock turtleneck, but customer service emailed me yet again saying that it's out of stock. There were over 100 yards in stock when I placed my order...all I want is 2 yards. I thought the housing market was unreasonable, but the basic black fabric market is brutal. </li>
<li>Urban Decay announced their new Vice lipstick. It's official. My all-time favorite lipstick line is discontinued, and they're replacing it with boring colors. Good-bye gunmetals, blacks, and purples. I can't believe the "Does Pink Make You Puke?" brand is chasing cookie-cutter LA influencers now. </li>
<br>
<h3>friday, july 9 </h3>
<ul>
<li>Learned how to add WebMs to my website. </li>
<li>Learning how to record in ffmpeg. </li>
<br>
<h3>saturday, july 10 </h3>
<ul>
<li>Finished documenting the attack-movement loop </li>
<li>Improved the 8th's article. It was pretty empty before. </li>
<br>
<h3>sunday, july 11 </h3>
<img src="/static/img/ext/yaoihands.jpg" alt="(image: meme where the manga guys are drawn with tiny heads and giant hands)">
<ul>
<li>A while back, I found a used bookstore run by an old lady that almost exclusively sells Harlequin novels (we're talking 3 or 4 solid aisles). Did you know that's a publishing house, not a genre? And it's not just something old ladies talk about - it's this entire world with ridiculously distinctive branding. That crazy red branding unlocked a memory of when I found a free manga on Google Books, but it was co-authored by some Western woman. The artstyle was kinda like that meme where the guys have tiny heads, and it was about an architect lady in an office or something. The whole thing felt really Western (read as "boring" lol). I looked it up and, turns out, that was a Harlequin <i>manga</i>. Apparently Harlequin's a Canadian company, but they have a Japanese branch that converts the stories into manga for the Japanese audience. Who knew the influence of those books reaches across cultural divides? </li>
<li>Romance novels in general are items of pure marketing, so they're fun to observe in the wild like that. If you haven't watched <a href="https://www.youtube.com/watch?v=UJiv14uPOac">Chris Zukowski's GDC talk about genre marketing tropes</a>, I recommend it. Starting at 28:15, he compares effective romance novel covers to effective game covers of various genres and opens my eyes to the sophistication of visual language. </li>
<li>Anyway, I bought something while I was there to be polite: Marion Zimmer Bradley's The Heirs of Hammerfell (unrelated to The Elder Scrolls). I started reading it today. It's an okay 80s-90s fantasy book. </li>
<li>Made tags into links that show all diary entries that share that tag </li>
<li>Added a word cloud, ordered by frequency. I love blogs with word clouds so much and finally have one of my own. </li>
<br>
Last Updated July 11, 2021 <br>
<br>

@ -1,17 +0,0 @@
<!--210801,211001-->
<h1>august 2021: moving </h1>
september 1, 2021<br>
#diary <br>
<br>
<h3>summary </h3><br>
August was all about the new house. So many appointments! At least the bulk of planned appointments ended this week. Once the final checklist for moving out of the old apartment is 100% complete (and that deadline is closing in), I'll switch to cleaning and unpacking the house. Moving is exhausting. <br>
<br>
To entertain myself for the long stretches of time without internet access, I grabbed the first Japanese language visual novel I saw without sexual content. (I'm kind of a prude lol.) It's so risky to play roulette with a random old VN for a whole month. I didn't even read the <a href="https://vndb.org/v7716">VNDB's</a> synopsis beforehand. Gekka Ryouran Romance is basically a reverse harem game set in a prestigious school, full of rich important kids who can get away with anything. The whole thing gives me Hunter Biden vibes, and the brother guy gives me uncomfortable incest vibes. No one said it was going to be weird. ;-; <br>
<br>
The vocabulary level is higher than anything I've read so far, barring technical or political articles. I'm getting to the point with Japanese that I don't need a dictionary for most manga, so needing a dictionary multiple times per sentence made me feel like a beginner again. It feels good to be challenged. The contrast does highlight the ironically low vocabulary level of other series featuring honor students, bookworms, STEM workers, etc, though. I suppose most characters aren't as smart as they claim lol. <br>
<br>
Honestly, the writing is pretty good, though, even if the vocabulary is challenging and the characters are scary. I'd recommend it for the engrossing art and atmosphere alone, so long as you're not bothered by creepy clingy weird guys and a super creepy clingy weird girl. Now that I have access to a real computer again, though, I'm going back to 90s rpgs. <br>
<br>
<br>
Last updated january 25, 2022 <br>
<br>

@ -1,11 +0,0 @@
<!--210901,211101-->
<h1>september 2021: moving in </h1>
october 1, 2021<br>
#diary <br>
<br>
<h3>summary </h3><br>
Another month of moving. <br>
<br>
<br>
Last updated january 25, 2022 <br>
<br>

@ -1,34 +0,0 @@
<!--210001,211201-->
<h1>october 2021: catch-up </h1>
november 1, 2021<br>
#diary <br>
<br>
<h3>summary </h3><br>
Mostly catching up to deadlines, giving extra play-time to kitty, and making new friends in Pixelmon. Also, sewing a few more garments is a stronger priority than gamedev lately. <br>
<br>
Gamedev-wise, I'm doing an integration test and playtesting. It's a real struggle to make the controls do everything I expect them to. Game companies make contextual controls look easy. Maybe I'll find time to implement a full event, start to finish, complete with dialog and a multiphase boss fight and rewards once the integration test is done. ;-; I think I have all the programming-centric systems in place to do that. Art, animation, and sound will be its own wave. <br>
<br>
Anyway, I can't believe I've been titling my monthly diary entries as "2020" all year. Finally fixed that. Also...why is the whole website on such an old version? Have I not been pushing anything to live? I thought I had a word frequency cloud and a working diary page navigation pane. Weird. <br>
<br>
<h3>sunday, october 17 </h3>
<ul>
<li>It was below 80°F/27°C for the first time all year! My favorite season has finally arrived. Time for my fall coat, warm spicy perfume, and apple butter toast with chai! The heat has been miserable, even indoors, so it will not be missed. </li>
<li>Last time I started Blessfrey, it didn't run at all. I was really motivated to add an events and a cutscene, but I debugged the game instead. Today it works okay, but I realize you can no longer talk to Chloe. That makes a lot of sense, since I designed the move-do-something loop to only worry about combat. I should get confident about the controls for interact, activate, attack, talk, pick up, move to target, don't move to target, and probably lots of other potentially overlapping controls. So...I need to make more diagrams. </li>
</ul>
<br>
<h3>saturday, october 29 </h3>
<ul>
<li>I wish I documented more. I want to document the current movement system better (even though it feels like I do that all the time) to better explain dots, nexts, waypoints, etc, and how they are derived. I also want a solid reference for what groups I'm using and what layers everything exists on/is affected by. Time to start some new documents. </li>
<li>The player doesn't move again. She was never advancing to the next dot. (Obviously the first dot in pathfinding will be the player's starting position.) I made the AI's method for getting the next dot skip anything at the player's global position, which works fine for now. I bet I'll run into more problems the next time she tries to pathfind around a curve, though. </li>
<li>I want to add dialog back in as part of an integration test, so the player needs to be able to interact with Chloe again. Of course, she mysteriously isn't tracked as interactable. Last time I remade the characters, I didn't get the layers/masks right, so I take the chance to make entities more consistent and improve my documentation. After that, I could start some dialog at interact, but Jiskan-16 is still bizarrely invisible in-engine and in-game at size 16. (How did that suddenly happen?? It's <em>made</em> for size 16!) After I match dialog's theme to the main menu theme (identical rn anyway), the text appears properly in the window once again. I've got dialog back. </li>
<li>I added an obstacle wall to break up the square testing room. Against my predictions, she can pathfind around corners. Whew. </li>
<li>Next, I want the player to use a skill on Chloe. The skills in the player's skillbar are generic'd and all the text doesn't display. (Stupid Jiskan.)</li>
</ul>
<br>
<h3>saturday, october 31 - halloween</h3>
<ul>
<li>Usually I prepare a costume or at least put out my skeleton doormat, but Halloween almost completely slipped my mind this year. At least I got candy. I've always lived in apartments, so I've never had trick-or-treaters before. I thought they all did Trunk or Treat or some school function instead these days, but we actually did have a few visitors. :) </li>
</ul>
<br>
Last updated january 25, 2022 <br>
<br>

@ -1,56 +0,0 @@
<!--211001,211201-->
<h1>november 2021: guild theme park trip </h1>
december 1, 2021<br>
#diary <br>
<br>
<h3>friday, november 12 </h3>
<ul>
<li>Still making sure all the systems are integrated together...Death, XP, Levels, Level-Up Rewards, XP Bar, Themes for Skill Library and Skillbar</li>
<li>If more than 50 million levels are gained at once, the game lags for a few seconds. Maybe level-up rewards could be divvied out more efficently, but apparently the <a href="https://www.guinnessworldrecords.com/world-records/highest-experience-level-in-an-rpg">highest level in any game</a> is 65,535 (Fallout 4 and Guardian's Crusade). I'm not going to worry about a Guinness-tier edge case now, if ever. </li>
</ul>
<br>
<h3>saturday, november 13 </h3>
<ul>
<li>The USPS tracking tab on my phone had my neoprene coming in on the 15th. I refreshed, and it changed to the 16th with "arriving on time." On time means nothing if you can just change the target date like that... </li>
<li>Move to Skill Target, Debug, Theme for Inventory, Inspect Menu, Debug </li>
<li>Identical (or worse, incompatibly different) code for getting the name from the translation server is scattered everywhere. Instead, I'm moved it to the entity object from which characters, skills, items, etc, are all derived. I wonder if I should phase out display_name altogether and always retrieve the name from the translation server. </li>
</ul>
<h3>sunday, november 14 </h3>
<ul>
<li>My package is still "arriving on time" despite being updated to the 17th by 9 P.M. </li>
<li>Inspect Menu, Themes for Store
<li>Now character inherits from tangible which inherits from entity, so I moved inspect menu code to tangible. I think it'll be easier to keep everything consistent if it's all done within a shared object. </li>
</ul>
<br>
<h3>monday, november 15 </h3>
<ul>
<li>package received! time to sew gogogogogogo </li>
</ul>
<br>
<h3>saturday, november 20 </h3>
<ul>
<li>Adding stores back in, accessible through inspect menu atm </li>
<li>Items, merchandise use translation server instead of static names </li>
</ul>
<br>
<h3>sunday, november 21 - saturday, november 27 </h3>
<ul>
<li>Visited a few guildmates at Universal, Gatorland, and Daytona Beach. We also did karaoke in Orlando, putt-putt in Daytona, and played Jackbox with the guild at night over hotel wifi. I did really bad at all the scored activities, but I crushed in Jackbox. I've never been to Universal or the beach before. It was one of the most fun weeks of my life and so cool meeting everyone. It's amazing how smoothly everything went, considering we're not the most organized group in the world. </li>
<li>I was leaning towards missing out due to the pandemic, but I have been taking CDC guidelines seriously for over a year. I'm glad I didn't let COVID stand in the way of living my life. I'll continue to avoid going out for frivolous reasons, but if it's something I'd really regret missing out on, I have to go. It's not like my sacrifices have made a difference anyway, with people like my parents refusing to pause their weekly out-of-state vacations. </li>
<li>Finished my swimsuit, 4 mock turtlenecks, and repaired a pair of jeans in time for the trip, too, so I was able to look nice. Next I want to sew a new outfit for my vintage doll, some pajamas, and a jacket. </li>
<li>Kitty hasn't stopped purring and acting like a kitten since he's gotten out of boarding. They might have put the fear into him. </li>
</ul>
<br>
<h3>sunday, november 28 - my birthday</h3>
<ul>
<li>Mostly spent my birthday sleeping and catching up on chores, but I was able to redeem my birthday coupon at Publix for free ice cream. I thought it would be a pint, but they gave me a whole half-gallon. They're seriously vying for best birthday program, but the Laneige lip mask from Sephora maybe wins 2021. Most lip products do nothing for me, but there are a few that at least prevent my lips from becoming dull and chapped like the Winky Lux matcha balms. The Laneige blows everything out of the water, though. My lips have never looked so big and smooth, and I'm in the middle of a dry winter. </li>
</ul>
<br>
<h3>rest of the month </h3>
<ul>
<li>My husband's hosting Christmas this year, but the new house still has lots of random boxes and cluttered surfaces. Family'll be in town in two weeks, so cleaning is top priority. We barely have any furniture or decorations, so I hope it goes okay. </li>
<li>My Christmas cactus is flowering. :) </li>
</ul>
<br>
Last updated April 7, 2022 <br>
<br>

@ -1,57 +0,0 @@
<!--211201,220201-->
<h1>december 2021: hosting christmas </h1>
january 1, 2021<br>
#diary <br>
<br>
<h3>sunday, december 5 </h3>
<ul>
<li>My family's hosting Christmas this year, so frantically cleaning, fixing everything up, and decorating. This is most of what occupied my mind, honestly, instead of Blessfrey. </li>
<li>I didn't keep up with daily journaling, so...judging from my git log... </li>
</ul>
<h3>friday, december 10 </h3>
<ul>
<li>The inspect menu now opens at the cursor's location. Apparently I'm not the only one struggling with PopupMenus getting trapped at origin. I think the solution is to set the rect_global_position <em>after</em> calling popup(). </li>
<li>I can't find UI stuff, so I restructured the folders. Also, I added a functional pick up from floor items' inspect menu. </li>
</ul>
<br>
<h3>saturday, december 11 </h3>
<ul>
<li>Struggling with xp bar...The UI element apparently never updates. </li>
</ul>
<h3>sunday, december 19 </h3>
<ul>
<li>Updating skill generator tool (Python3) to match latest version of the skill script. ID is now a color, skill keyword input uses dictionary keys instead of direct values. Now values can be dynamic, and their changes can be reflected in skill descriptions. </li>
</ul>
<h3>monday, december 20 </h3>
<ul>
<li>Added Took Damage and Died events. Dying stops working unexpectedly all the time, so applying death to characters externally through events is more manageable. </li>
</ul>
<h3>wednesday, december 22 </h3>
<ul>
<li>XP works, isn't earned 4 times (what was up with that?), and the xp bar and level label are accurate. </li>
</ul>
<h3>thursday, december 23 </h3>
<ul>
<li>Adding the projectile keyword to the skillmaker script. Initially, I plan for projectile keywords to contain another keyword (as in, a projectile that carries a damage keyword to target then applies it to target). </li>
<li>Instead, projectiles are going to use a skill upon arrival, so they only need a reference to what skill they will use. This way, I don't have to add support for recursion to my skillmaker tool. </li>
</ul>
<h3>tuesday, december 28 </h3>
<ul>
<li>Projectiles and characters have some overlap, but the current object layers for entities have all the wrong data and logic at the base. I listed out the different categories for entities in my game (character, projectile, item, activators, etc) and plan better layers. Bodies get corresponding layers as well. </li>
</ul>
<h3>wednesday, december 29 </h3>
<ul>
<li>Changed all prints to print_debugs with a console command. So tired of hunting for their location. </li>
<li>The character is actively taking user input during character creation, so weird stuff happens when I try to name her. Now I'm doling out ignore_input through events, too. </li>
</ul>
<h3>thursday, december 30 </h3>
<ul>
<li>Implementing the Gun skill to test projectiles. Projectiles are now going to share move_to_destination code with the character's pathfinding/click-to-move code. </li>
</ul>
<h3>friday, december 31 </h3>
<ul>
<li>Still working on projectiles. </li>
</ul>
<br>
Last Updated January 10, 2021 <br>
<br>

@ -1,42 +0,0 @@
<!--210204,210513-->
<h1>new year's resolution (2022) - boring finances </h1>
january 13, 2022<br>
#offtopic <br>
<br>
This blog is my gamedev diary, but it's fun to use it as a real diary, too. Back to gamedev stuff tomorrow! <br>
<br>
<a href=”https://www.blessfrey.me/diary/entries/210204”>Last year's resolution</a> was to have fun experiences with the things I have at home, since the pandemic drastically restricted my ability to have fun with other people outside. The new year lifted my spirits and encouraged me to get back into learning, growing, and expressing myself. This year's resolution will once again be about making the most of my resources, but this time I want to approach it from the angle of frugality and money-making. <br>
<br>
I always make a budget for everything and enjoy tracking my finances and investments. Honestly, I just really enjoy spreadsheets and incorporate them into every game I play, including the <a href="https://www.blessfrey.me/diary/entries/210513">petsite</a> I've been playing lately. I was also always the treasurer in every club I joined and made all their spreadsheets and budgets. It's more than just fun for me, though. I feel like it clears up a lot of uncertainty when you know where your money is coming from and going, and how long your savings will last. <br>
<br>
Financial awareness suddenly feels more important with buying our first house, paying for our own repairs (no landlord to take care of everything anymore), rising food costs, and general rising cost of everything. My resolution involves identifying the most effective money saving methods without making my family uncomfortable and trying new ways to make extra cash this year. The money will go towards savings, investments, and furniture. <br>
<br>
<h2>cutting spending </h2><br>
Mostly looking into cheaper versions of old recipes, make meals that seem differentiated even while using the same few grocery items over a week, taking less baths (honestly, the water heater bills can add up if it's your only form of pandemic entertainment lol), and cutting frivolous and fun expenditures where I can without going crazy. <br>
<br>
<br>
<h2>making money </h2><br>
Obviously, cutting spending is painful. It'd be a lot better if I could just make more money to make up for the unreasonable inflation. Passive income and side gigs have always interested me, but I've never put forth the effort. Making it one year's resolution might be the push I need. <br>
<br>
I've gotten lots of compliments on the professional quality of my homesewn clothing and face masks, so I should be able to work out some niche item that's easy to mass-produce. I do my own pattern-making, so another route would be to rework my patterns for a variety of sizes, digitize them, and sell them online. <br>
<br>
I'm not going to act like my artwork is great, but I think I could produce usable themed assets, too. My code isn't an object of envy either, but I could write some assets. There are plenty of gamedev stores, so I could get my real start selling tilesets on <a href="https://itch.io/game-assets">itch.io</a> instead of working on this game until the sun explodes. <br>
<br>
Maybe I could program something worth selling? Some kind of minigame. <br>
<br>
I've also never tried buying or selling things online. <br>
<br>
Another weird thing, but I've never had a credit card before. Debit cards have poor protection against fraud, so I might as well have a credit card, even if I'll only treat it like a security layer. Some of them have pretty good rewards. It could be worth choosing one based on rewards rather than rates. I wouldn't mind cash back or cheap vacations just from buying gas and groceries. I've never had one before, though, so I'd have to learn all the terms first. Intentionally borrowing money is so uncomfortable. <br>
<br>
...Just some ideas to try. Every little bit counts, I guess. <br>
<br>
<h2>let's get to work </h2><br>
<center><img src="/static/img/ent/sims3_novel.jpg" alt="(Sim typing away at the computer.)" width="500" height="375"></center> <br>
<br>
If only I could be a sim and trap myself in my desk for 3 days until a lucrative novel is completed, email it to a publisher, then get enough royalties every week to live comfortably for the rest of my life, despite eggs costing §11. If I had access to motherlode, I might even tolerate the frequent house fires and random zombies eating my garden every night. <br>
<br>
<br>
Be well in the new year. Later. :) <br>
<br>
Last updated April 7, 2022 <br>
<br>

@ -1,20 +0,0 @@
<!--220310,220224-->
<h1>goalless games </h1>
january 14, 2022<br>
#game-design #philosophy<br>
<br>
<h2>goalless games </h2><br>
Some developers are happy to make loose, meandering sandbox games without no true win or fail state. The concept of goalless games is controversial among semantic people, but I feel that the genre with Garry's Mod and all the dressup games and physics simulators is enviably successful if not totally legitimate. It's not like the overwhelmingly popular Minecraft and old RuneScape don't share one foot in this genre, either, with their relative lack of direction and gameplay dominated by self-driven goals. I don't even feel like a central story or main objectives would improve these games (especially after watching Minecraft tack on some tedious hunger mechanic and an awkward final boss). <br>
<br>
<h2>my need for structure </h2><br>
I'm just not a goalless game designer myself. <br>
<br>
It sounds nice to provide an open world where the player can set out after his own goals without the dev placing horse blinders on his head. In reality, though, a game designer can't force a player to share the game's goals in the first place, so there's no need to purposefully design a game to be goalless. For me, I feel like neglecting to set a game's goal reflects a lack of a game development goal. A goal is helpful to not only the player but also to the developer. A central vision of the game's progression will imbue each piece of the game with more purpose and help them fit together more seamlessly as a whole. It's a safeguard against filling a game with pointless, incongruent clutter at whim. Obviously not every developer needs a goal-oriented approach, but I work better with one. <br>
<br>
No matter what philosophy the game designer has, though, a player will do what he wants to do, even if it has nothing to do with the goal of the game. For example, roleplayers are prominent members of MMO communities, and they might never max out a character or finish the main storyline. They throw out all the game designers' work and focus on finding the perfect backdrop and acting out their own scene instead. There are plenty of screensaver simulators and 3D chat servers out there for them, but they turn up in "real" goal-driven games, too. There are touches of this aberrant behavior in everyone who doodles with bullet holes, names their character something funny to harvest out-of-context dialog screenshots, or hoards a useless item. <br>
<br>
<h2>players do whatever they want anyway </h2><br>
So in a way, game designers really don't need to design a goalless game. They can trust players to forge their own fun from even the most rigid hallway simulator. In my opinion, deliberately not designing goals runs the greater risk of making players too lost, bored, or overwhelmed to find their own fun or not even finding incentive to try the game in the first place. A better approach is in the middle, building towards a purpose while taking a tip from goalless games by filling the world with choices, interesting tools, and interactibles that are fun for fun's sake. At the end of the day, though, obviously do what works for your players! <br>
<br>
Last updated January 12, 2022
<br>

@ -1,28 +0,0 @@
<!--200806,201224-->
<h1>designing blessfrey's first demo </h1>
january 20, 2022<br>
#demo <br>
<br>
<h2>my goals </h2><br>
The systems and game mechanics in Blessfrey are mostly present and functional, so I feel like it's a good time to practice releasing, hosting, and supporting a game. The first release will just be a tech demo. I want it to showcase the features in the game, have structure and goals, and have some of the same gameplay feel of future releases. Hopefully it's fun, but we'll see when it's all bolted together. <br>
<br>
The core of the game is curating from a wide variety of skills and combining them into a viable skillbar. I'm going to try to get away with skimping on content to put more focus on the release process, but skill variety deserves the most attention. I'll shoot for 30 skills for now, or in other words, 3-4 skillbar's worth without repeats. <br>
<br>
<h2>finding structure </h2><br>
I could release a <a href="https://www.blessfrey.me/diary/entries/220114">goalless demo</a>, but I fear people wouldn't explore long enough to discover any depth. If I could rip a level from the finished game, structure and goals would already be built in, but this demo will be more or less original content. I'll have to build structure as I go along. <br>
<br>
This demo should showcase more gameplay than story and world, so it should probably have more in common structurally with a tutorial room. I'll just fallback on the tutorial bingo approach again and hope it never loses its luster. Bingo is a great excuse to list gameplay prompts for the player while giving them some choice on how to proceed. It's also a game in itself, so I don't need to take my game design much further: every full consecutive set of activities is a little win with a prize and a full board bingo is a big win with a big prize. I'm not going to worry about a fail state, so the demo can be more like a playground than a challenge. <br>
<br>
The demo shouldn't just resemble a tutorial - it should <em>be</em> a tutorial. It is the first time unguided players will be able to play, so there needs to be lots of in-game guidance. I'll test a stealth tutorial and see how it compares to a more conventional tutorial. Ideally, the tutorial should be easy enough for veterans to breeze through without noticing it's even there, but nudges and tips are given to those who need it. It'd be even cooler if the tutorial doesn't break immersion, coming from level design, believable dialog, or from passers-by setting an example. Maybe it's a terrible idea and there's a reason why all games start out feeling like post No Child Left Behind era public school, but I want to try it out. Since I have the <a href="https://www.blessfrey.me/diary/entries/210402">event system</a>, it's just a matter of listening for lack of movement, unnecessary key spamming, stalling in quest progress, or any other apparent failures and sending someone over to help out. <br>
<br>
<h2>world and story </h2><br>
I won't be taking an exerpt of the story or using fully realized locations, but the demos should at least conceivably fit within the full game. This time, I'm just going to use two of the major supporting characters, Chloe and Night, (both healers) and enough of the school to service a demo. The courtyard is a relatively safe area with some easier enemies to the far side of the map, guarding collectible items. If you die, the checkpoint is in this field, managed by Chloe. The nurse's office is an item shop, run by Night. The storage room is a maze full of monsters and treasure. The areas, monsters, and dialog will be more involved than anything I've ever tested before, and it'll include a stress test skill. Let's see how the engine handles it! <br>
<br>
<h2>ugly </h2><br>
Of course, it's going to be pretty rough overall. It's the first alpha build released to the public, so there are a few bugs I know are still in there, some capable of crashing the game. Also, there are no frills when it comes to the audiovisual side - barely any animations, all audio is from the public domain, effects are carelessly placed, and so on. Like Yandere Dev says, "all assets are placeholder." :) <br>
<br>
Anyway, back to working on the demo! <br>
<br>
<br>
Last updated January 20, 2022
<br>

@ -1,91 +0,0 @@
<!--220310,220224-->
<h1>hostility</h1>
january 27, 2022<br>
#design #mechanic<br>
<br>
<br>
<h2>what is hostility? </h2><br>
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 <a href="https://wiki.guildwars.com/wiki/Aggro">aggro</a> in Guild Wars because weaving through patrol patterns and balling mobs is one of my favorite things from any game. <br>
<br>
<h3>when does a character become hostile? </h3><br>
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. <br>
<br>
Usually, if an NPC is hostile, that means a threat got too close. Currently, proximities in Blessfrey mirror <a href="https://en.wikipedia.org/wiki/Proxemics">Edward T. Hall's zoning</a> 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. <br>
<center><a href="https://en.wikipedia.org/wiki/Proxemics#/media/File:Personal_Space.svg"><img src="/static/img/ent/Personal_Space.svg" alt="(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.)" width="500" height="267.72"></a></center>
(By <a href="//commons.wikimedia.org/wiki/User:WebHamster" title="User:WebHamster";>WebHamster</a> - <span class="int-own-work" lang="en">Own work</span>, <a href="https://creativecommons.org/licenses/by-sa/3.0" title="Creative Commons Attribution-Share Alike 3.0">CC BY-SA 3.0</a>, <a href="https://commons.wikimedia.org/w/index.php?curid=6147809">Link</a>) <br>
<br>
An NPC will become hostile under a few conditions: <br>
<ul>
<li>An enemy faction member enters its social distance. Every character has a RangeBubble (Area2D) representing its social space. When a character enters range, its AI will assess both set of factions and change states upon finding a conflict. If there is a significant level disparity in the opponent's favor, though, the character will remain idle. </li>
<li>Someone attacked it or dealt damage to it. If their opponent isn't in range, they will become hostile and begin searching or ambushing. (There is no friendly fire, so teams aren't going to implode.) </li>
<li>Someone was damaged or attacked by it. Once again, if their opponent isn't in range, they will begin the hunt. </li>
<li>When the majority of a team is hostile towards an opponent, any team of the same faction or type entering the social space of the hostile team will also become hostile towards it. </li>
<li>When one member of a team becomes hostile, the others remain idle until aggravated. This allows skilled players to pull individual opponents away at a time without alerting the others and divide and conquer. This also prevents one foolhardy teammate from programmatically pulling aggro onto its entire team. </li>
</ul>
<br>
<h3>what changes when a character is hostile? </h3><br>
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. <br>
<br>
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. <br>
<br>
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 <a href="">Aion aggro sound effect</a> in my head.) <br>
<br>
<h3>when is a character no longer hostile? </h3><br>
An NPC will lose hostility under a few conditions:
<ul>
<li>Enough distance has spread between the NPC and its opponent. Usually, an NPC will not pursue an opponent very far. The farther the NPC is led from its idle route, the less tolerance it has to pursue its opponent. However, if pulled slowly, a few steps at a time, it can be led almost anywhere. </li>
<li>Its opponent exits the NPC's territory. Some NPCs have lines they will not cross. </li>
<li>Its opponent or itself dies. </li>
<li>The opponent no longer belongs to a conflicting faction. </li>
</ul>
<br>
<h3>what happens after a character is no longer hostile? </h3><br>
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. <br>
<br>
<h2>hostility in action </h2><br>
This next section is mostly for me. Let's run through some faction clash examples after illustrating the relationship of teammates and allies.<br>
<br>
<br>
<h3>team hierarchy </h3><br>
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. <br>
<br>
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. <br>
<br>
<center><img src="/static/img/ent/team_hierarchy.png" alt="(A diagram of team hierarchy. )" width="500" height="375"></center> <br>
<br>
<h3>scenarios </h3><br>
<center><img src="/static/img/ent/teams_collide.png" alt="(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.)" width="500" height="375"></center> <br>
<br>
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. <br>
<br>
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. <br>
<br>
<ul>
<li>A attacked V. V and A are hostile towards each other. B and D gain hostility towards V from being in range of A. W and Y gain hostility towards A from V. Now A, B, and D are opposed to V, W, and Y and vice versa and begin fighting. C and X are bound to cluster closer or be approached by opponents and join in soon. </li>
<li>A lays a trap. V walks into the trap and takes damage. A and V are now hostile towards each other. A and V will prepare for battle and begin hunting for each other. Their RangeBubbles will extend farther during this time. If they search for too long or travel too many steps without finding an opponent, they will resume their idle positions. If A finds an opponent, any teammates or allies within their RangeBubble will gain hostility towards the opponent and move within range. Combat will ensue. If very few or no members were in range of A, its team will idle on without it. If most members were in range, the remaining members will flock to their team and inevitably gain hostility and enter combat. </li>
<li>A one-hit-kills V from a safe distance. A becomes hostile and searches for an opponent. Team 2 is oblivious. </li>
<li>Teams 1 and 2 are hostile towards each other. Team 1 changes to the same faction. Teams 1 and 2 lose hostility. They will now consider each other friendly and possibly support each other. If one team gains hostility towards another team, the other will share hostility if still in range. </li>
<li>Teams 1 and 2 are friendly with each other and following each other. Team 1's faction changes to an adversarial one. They are all so close, they all gain hostility towards the other team's members immediately. </li>
<li>All of team 1 is hostile towards all of team 2. Team 3 enters range of team 2. All of team 3 gains hostility towards team 1 the moment a member of team 2 enters range of a single member of team 3. </li>
<li>Team 1 and team 2 are hostile towards each other with team 3 approaching. Ally P intersects with ally Y, triggering hostility between them. Teams 1 and 2 remain oblivious towards Team 3 and vice-versa. If team 3 starts going somewhere else, P will be left behind. Members of team 3 will gain hostility on a case by case basis as they flock nearby and inevitably get too close. </li>
<li>Team 1 and team 2 are hostile towards each other with team 3 approaching. Teammate M intersects with ally Y, triggering hostility between all of team 3 and ally Y. Team 2 will eventually enter the fray coincidentally. </li>
</ul>
<br>
<h3>ally chains </h3><br>
<br>
<center><img src="/static/img/ent/ally_chain.png" alt="(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.)" width="500" height="375"></center> <br>
<br>
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. <br>
<br>
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. <br>
<br>
If the necromancer dies, the lich and paladins will no longer restrain themselves from becoming hostility towards each other. <br>
<br>
<br>
<h2>what I learned </h2><br>
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. <br>
<br>
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! <br>
<br>
Last updated January 31, 2022
<br>

@ -1,120 +0,0 @@
<!--220101,220301-->
<h1>january 2022: finally a break </h1>
february 1, 2022<br>
#diary <br>
<br>
<h3>saturday, january 1 </h3>
<ul>
<li>I'm finally caught up! It feels like I've been going nonstop for months...and now nothing! My next goals are to get the house not merely presentable but cute and comfortable and to get a demo out for my game. </li>
<li>For New Year's Eve, we watched an anime stream with guildies. When the neighbors started shooting fireworks, I asked my husband to go on a walk together around the lake. We went to the bridge, which was a little sentimental since one of the first New Years I celebrated was in RuneScape on the Keldagrim bridge with some random passerby. It was nice. At least, it was nice until my husband spotted an alligator. It was just his head sticking out of the water, gliding along beside us. When we spotted him, he dived. It was pretty freaky. Judging from the size of his head, he was twice my size. A handler at Gatorland told me they are usually pretty slow on land (especially during winter), but this guy was thrashing around. We took our bet with running the entire way back home. Looking it up online, though, it says they can easily outrun a human. Aaaahhhhhhhhhhh! </li>
<li>A week later, everyone in my family and my husband's family tested positive for COVID except for us. I feel so bad. We've been using COVID fear as an excuse to avoid family gatherings, but they heard none of it this year. There are vulnerable people on both sides and not everyone's vaccinated. My side thinks they caught it from work, and his side thinks they caught it from traveling, but everyone came down with it at the same time...It had to be the get-together. Dang. </li>
<li>At least Christmas went well while it lasted. Best Christmas ever, honestly. Never been so happy to finally see everyone again. </li>
</ul>
<h3>wednesday, january 5 </h3>
<ul>
<li>Colored new sprites, portraits, and threw together a mockup for the second or third demo's level, whichever one will have a simple branching story event. It'll be much cooler than the first proof-of-concept, vertical slice demo. I want to finish this one and move on already. Also messed with interact code, so you can't talk to Chloe after she dies. </li>
<li>Watched The Weekenders while I did that. People have a lot of nostalgia for a lot of cartoons, but it's never for the ones I liked. Isn't everything like that? This show holds up okay. Cute characters, and I like their willingness to put two random characters together and show how they interact. </li>
</ul>
<br>
<h3>friday, january 7 </h3>
<ul>
<li>There's a moved handler per entity. I only need one. I moved it to the MessageBus for now. </li>
<li>Projectiles move to target and use their skill now. Whew. </li>
</ul>
<h3>saturday, january 8 </h3>
<ul>
<li>The projectile won't free itself. My husband suggested it should use a 'Banish' skill on itself. Worked like a charm! </li>
</ul>
<h3>sunday, january 9 </h3>
<ul>
<li>Now I want the projectile to apply a status effect. </li>
<li>Bleeding is freed before it can have any effect because the skill frees too quickly. Instead, the status effect is added to the victim's tree instead of the skill's tree. </li>
<li>Bleeding doesn't really do much since the character's passive regeneration outpaces it. I start reworking regeneration so it slows during combat, accelerates to max rate, and can drain instead of heal. This also involves adding the concept of hostility. </li>
</ul>
<h3>monday, january 10 </h3>
<ul>
<li>Web maintenance </li>
</ul>
<h3>friday, january 14 </h3>
<ul>
<li>Before, I wanted to jump right in and force a murky concept of "in combat" and "out of combat" (or "hostility"), and it didn't turn out well. When I took a moment to research, draw charts, and write user stories, hostility clicked. Implementing a feature feels more instantly gratifying than the abstract process of design or devops. The temptation of coding from the hip is too strong to resist, but much of that code gets stashed and abandoned, including the hostility code. Instead of flailing around in the editor, that time might as well have been spent playing Divine Divinity if I really didn't feel like doing design work...When will I learn? </li>
</ul>
<h3>saturday, january 15 </h3>
<ul>
<li>Web maintenance </li>
</ul>
<h3>sunday, january 16 </h3>
<ul>
<li>I think everything is working enough. I haven't tested all the UI elements, so maybe there's something wrong with adjusting the volume or something, but I'm happy with actual gameplay so far. I want to add hostility, factions, equipment, and a few more things, but I could work on forever on accessories to gameplay with that mindset. The core of this game is the skills, and now that degeneration and status effects are implemented, skills are more or less in a finished state. AI is also probably ready to support anything I want out of it, so gameplay is ready to go. Sounds like a perfect time for a basic release copy. </li>
<li>The release copy will be a fairly simple game. The player will find a bingo card that will prompt the player to explore the game's mechanics in 3 rooms. In other words, it'll just be a tech demo. </li>
<li>I made a bingo board with fonts from my computer, and after all the typesetting and saving each square, it turns out I picked GPL ones. I'm not exactly sure what that requires, but I'm not dealing with a potential forced full source code release on top of learning how to release anything at all. I'm not even sure how deep the GPL goes. I'm open to sharing my repo later, but not this early and not over a font used in one UI element. Remaking the graphic was a pain, but I want to try to follow contracts. The demo is free, but I'm pretty sure my website could be considered a portfolio, in which case, everything I put up there is technically commercial work. </li>
</ul>
<h3>monday, january 17 </h3>
<ul>
<li>Putting together all the art assets for the demo levels </li>
</ul>
<h3>thursday, january 20 </h3>
<ul>
<li>Made all the entities for the demo. </li>
<li>The integration test wasn't good enough, but TBF, I'm basing slimes off of an untested character and plastic tubs off of the last-tested-versions-ago shopping cart. Nothing works. </li>
<li>Refactoring and debugging: ids, name keys, art, AI nodes, inspect menu, updating any old code </li>
</ul>
<h3>friday, january 21 </h3>
<ul>
<li>Added navmesh to demo levels </li>
<li>Refactoring and debugging: spawnpoints, updating any old code. </li>
<li>Removed sex option from character creation. TBH, adding a male playable character is a lot of extra work since the player character isn't a complete Tabula Rasa. I'm not going to do the bizarre thing a lot of games do where the girl is just the guy again with swapped pronouns. It's so weird and cringy. Even Guild Wars 2, which made a huge deal about roleplaying your character through dialog, forces some strange lecherous desire for the human noble women onto your character no matter your sex. Either they meant for all male main characters to be straight and all female main characters to be gay (lol), or they are yet another lazy dev. I'd want to actually rewrite everything to acknowledge your character's sex, so I couldn't bother unless I was discovered by a player-base who'd appreciate it. Female protagonists is my comfort zone, though, so I can't promise a male protagonist wouldn't be strangely effeminate anyway. </li>
</ul>
<h3>saturday, january 22 </h3>
<ul>
<li>With refactoring, I'm trying to reduce the differences yet again among all the types of entities. Highlighting an entity upon hover should more or less work the same, whether it's an item or a character. </li>
<li>Refactoring and debugging: highlight for container </li>
</ul>
<h3>monday, january 24 </h3>
<ul>
<li>Will it ever work? ;~; When this is done, I'm maintaining a generic character, item, container, etc, so I can copy and paste something up-to-date instead of something old and broken. I don't think I need a character maker like I do for skillmaker because it could never make a fully realized character complete with unique dialog and AI. I'd have to change so much stuff by hand that it wouldn't do much better than a simple copypasta character. </li>
<li>I should have been testing in a mock game level the entire time instead of testing each chunk in a vacuum. I want to hold back on adding new features until this demo is published, but I still don't like all the nuances to the controls. I guess they're fine for the moment, but I have so much I want to improve. </li>
<li>Refactoring and debugging: layer/mask, projectile permeable walls, adding back missing objects for containers (where did the UI portion go??), highlight for container, character, and item. </li>
</ul>
<h3>tuesday, january 25 </h3>
<ul>
<li>I'm getting really good at making my own french fries and chips. I think boiling them first then frying in hot oil is best so far. I boil them in bay leaves and black pepper oil, fry in peanut oil, and toss in salt and cracked black pepper. If only fried pickles were so easy. I also made spice cookie dough rolls to leave in the freezer, mostly following <a href="https://homemadehooplah.com/christmas-spice-cookies/">Homemade Hooplah's Christmas spice cookie recipe</a>. Instead of flour, I'm trying pecan flour. It seems fattier, moister, and tastes nuttier. </li>
<li>Website maintenance </li>
<li>Refactoring and debugging: container, inventory items, highlight, hitbox </li>
</ul>
<h3>wednesday, january 26 </h3>
<ul>
<li>UI plates were either static textures that toggle with hide() and show() or some kind of PopUp. I'm converting them into containers that can be popped into the content container of a custom window, complete with dragging and X to exit. I'm adding this only because the UI plates overlapped before, making the game unnecessarily annoying to play. I'll try to add window focus bringing it to the front and only allowing each window to be open once at a time, but those don't prevent anyone from playing. Later, I'll have the UI remember where you like each window and make them look less stark. </li>
<li>Refactoring and debugging: container, inventory items </li>
</ul>
<h3>thursday, january 27 </h3>
<ul>
<li>Containers work with custom windows </li>
<li>Refactoring and debugging: character creation screen </li>
</ul>
<h3>friday, january 28 </h3>
<ul>
<li>Adding skill library and containers to custom windows </li>
</ul>
<h3>saturday, january 29 </h3>
<ul>
<li>Refactoring and debugging: known skills use containers instead of being a cluster of floating labels and textures </li>
</ul>
<h3>sunday, january 30 </h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=roughtzsCDI">Ring Ding Dong</a> is stuck in my head. Ring diggy ding diggy ding ding ding 오직 너만 들린다 Ring ding dong ring ding dong♪ This must be exactly the kind of song that would give my neighbor vibes of living next door to Kaoru from Welcome to the NHK. At least, if I didn't wear earbuds. </li>
<li>Less graphic weirdness with displaying skill library in a custom window </li>
<li>Smartphone uses containers instead of being a random cluster. Inventory app no longer opens with hotkey, though. </li>
<li>Refactoring and debugging: translation server keys, entity ids </li>
</ul>
<h3>monday, january 31 </h3>
<ul>
<li>NYT bought <a href="https://www.powerlanguage.co.uk/wordle/">Wordle</a>...dang. Fun while it lasted. </li>
<li>More or less added critical hits, life drain, cure, heal, mark, eye strain keywords and 7 new skills that use them.</li>
<li>Skillmaker supports keywords that rely on co-routines </li>
<li>Refactoring and debugging: keywords, status effects </li>
</ul>
<br>
Last Updated January 31, 2022 <br>
<br>

@ -1,115 +0,0 @@
<!--220201,220401-->
<h1>february 2022: paving the road to the first finalized skills </h1>
february 1, 2022<br>
#diary <br>
<br>
<h3>tuesday, february 1 </h3>
<ul>
<li>updated ID code - Each skill (and most other objects) have a 6-digit hex ID, so they can all be identified by a unique color. Each digit means something, and I'm currently solidifying them after making sure the color system works and I have had a lot of time to decide the basic categories for things. If I go back on any decisions after this point, I'll have gaps instead of rewrites. Any IDs that get assigned now will forever stand for those objects. </li>
<li>updated translation keys - Every piece of text corresponds to a key. Blessfrey is English only (with some random bad 日本語 translations for fun), but it's nice to keep my text separate. The only exception is dialog, but I'll figure that out later. Before, I used long keys like skill_desc_cut_the_guy, but now I'm finalizing short codes to replace them. The first 2 characters of name keys for objects is the type (sk for skill), and the rest is unique to the object. Descriptions add de to the front but are otherwise the same. Now Cut the Guy's keys are skcugu and deskcugu. I love codes like this because unintended words form sometimes and popular naming patterns are revealed, but they're also simply more convenient to type out. </li>
<li>updated skillmaker.py (I use a script to generate skills instead of writing all the GDscript stuff by hand) to support new skill keywords: cure (remove a status effect), bleeding (attrition DPS), heal (opposite of damage), life drain (direct DPS), mark (nerf, comparable to the Guild Wars 1 hex), summon (spawn an ally), and projectile (entity that carries a keyword to apply on the target upon collision). </li>
<li>added 11 skills to the script's json input file. </li>
<li>The Attack keyword now rolls for damage, either remaining unchanged for bad rolls or increasing by the critical attack multiplier for good rolls. </li>
<li>Replaced duration timeout with remove for all status effects to accomodate new removal conditions. </li>
<li>moved level and xp code to the Actor object instead of being exclusive to Characters.
<li>began adding Life Drain, Health Absorption (negate incoming damage), and Cure into the game </li>
<li>Usually, if I encounter a bug, I'll fix it right away. I think AGILE recommends doing that? (or at least, whenever it isn't recommending you do whatever works best for you.) Right now, I'm focused on adding completed skills into the game. I made a bug.txt for anything I encounter that falls more under polish and doesn't directly impact the needs of the specific skill I'm working on. I put some random ideas in there, too. When my skills are good to go, I'll have plenty of little things to fix afterwards. </li>
</ul>
<br>
<h3>wednesday, february 2 </h3>
<ul>
<li>Documenting each keyword in the game design document as I go... </li>
<li>skillmaker.py supports energy loss, energy gain (modify current skill currency by an integer), eye strain (status effect: high chance to miss outgoing attacks) </li>
<li>added 4 skills to the json </li>
<li>Health Absorption is now called Negate. </li>
<li>Began adding energy loss/gain, slow (decrease movement speed), and level skills (passively affects all entities in the area; gives each level a different challenge). Removed stances for now. Generalized keywords like that are more like a tag to add to a skill than an expected structure that can be generated by Python. </li>
<li>Adding lots of new skills and entities to the spreadsheets I keep in my game design folder. It helps prevent shared IDs and keys. </li>
<li>The Explosives attribute of the Chemist job is replaced by Materials Engineering. </li>
</ul>
<br>
<h3>saturday, february 5 </h3>
<ul>
<li>Continuing to finalize color code system, track proposed skills and entities, and add JSON skills </li>
<li>Add skillmaker.py support for knockdown (cannot move or attack), oil (applies keywords to equipment) and begin implementing them in-game </li>
<li>I need to implement windows before the demo's release because sometimes the Skill Library gets covered by other unmovable UI elements, and it's kind of unplayable. Before, some UI plates were always there but hidden, and they were static floating textures. Now all UI elements will be standardized as the content of "windows" which are instanced and freed as needed, can be moved around, can be brought to the front or hidden behind other windows, and can remember their customized location. They won't be 100% before the demo's release, but they will no longer permanently cover up the Skill Library. Also, they'll probably all have a matching, basic window bar instead of something more subtle or cute. </li>
<li>While I'm at it, I'm rebuilding the entire smartphone (will ultimately hold most UI stuff and maybe some texting or minigames or shopping or whatever) from containers. I didn't really know how to use containers before, but I should have been using them for providing structure and organization to content inside UI elements. </li>
</ul>
<br>
<h3>monday, february 7 </h3>
<ul>
<li>Continuing to finalize color code system, track proposed skills and entities, and add JSON skills </li>
<li>Adding Interrupt (cancel activating skill), Energy Degeneration (drain skill currency) in-game </li>
</ul>
<br>
<h3>tuesday, february 8 </h3>
<ul>
<li>Continuing to finalize color code system, track proposed skills and entities, and add JSON skills </li>
<li>Adding Interrupt (cancel activating skill), Energy Degeneration (drain skill currency) in-game </li>
</ul>
<br>
<h3>wednesday, february 9 </h3>
<ul>
<li>Continuing to finalize color code system, track proposed skills and entities, and add JSON skills </li>
<li>Adding level skills in-game, tweaking level layout </li>
</ul>
<br>
<h3>saturday, february 12 </h3>
<ul>
<li>I played Guild Wars today for probably the first time since they added the anniversary elite skills and weapons. I periodically log in to open presents and wander around the Plains of Jarin, maybe even decide I'll finally make a good Paragon. This time, I actually played through the main story on my Assassin. Sins never clicked with me, despite them being the closest profession to the Blessfrey job I've been playing as for months (Brawler). They're not quite the same, but they both emphasize critical hits, high mobility, and dodging/deflecting instead of high armor. Sins are all about shadowstepping all over the field, though. Brawlers have more improvised weaponry and environment-based skills. Still, I actually finally liked sins today for something other than raptor farming. I made the first build that feels good based on skills that stood out to me, ironing out any weaknesses revealed during battle, just like old times. </li>
<li>Hopefully this means I'm in an okay place designing the Brawler skills. It's not like I don't play in other styles, but I have a strong preference for life steal/drain, managing huge mobs of minions, preventing damage (love love love playing prot in FA so so much), and any magic class in general in any game. I bounce ideas off other people, but no one else is really working on this game besides me. With 10k hours on my Guild Wars necromancer and hundreds at best on melee professions, I wondered how bad it would show through my melee job designs. </li>
<li>Finished creating JSON entries for all proposed skills, though they don't necessarily have the fields completed. Also added a line to the skillmaker to warn me if any skill shares an ID...not that that should ever happen, of course. </li>
<li>Made a skill name idea TXT. Too many ideas to use right now keep popping in my head as I name all the demo skills. </li>
</ul>
<br>
<h3>february 13-16 </h3>
<ul>
<li>Proofreading JSON file, making sure every field is accurate to the listings in the spreadsheets. </li>
<li>Documenting keywords in the GDD </li>
</ul>
<br>
<h3>thursday, february 17 </h3>
<ul>
<li>Continuing to finalize color code system, track proposed skills and entities, and add JSON skills. </li>
<li>I think I'm ready to start finalizing the list of skills that will appear in the first demo. I renamed the old skill and entity stockkeeping spreadsheets to archives, and only objects that will appear in the live game will be copied over to the official spreadsheets. </li>
<li>Not everything will be represented, but I want a little taste of the Brawler, Disciple, and Chemist jobs. I have a few NPCs who will have a few skills equipped, the player will be able to learn a small collection of skills, and there will be a few level skills, hidden skills required to make the game run (some background world stuff is the result of skills being used by the engine...probably no one will realize it's happening but it's an idea I really wanted to realize through making this game but lol whatever), etc. These few defined skills will be implemented for the demo, and all other skills will wait for future releases. </li>
<li>Brawler can be easy to play but hopefully more original than a straight melee class like Soldier will be, so it's been the player character default for a while. I'm also including a "healer" per level. One is Chloe, a Disciple, who provides direct healing and cures for status effects. The other is Night, a Chemist, who provides damage protection, buffs, and conditional healing. There will also be Chemist enemy slimes with life drain who can buff their Brawler melee allies. The Tamer job is also present, since Tamer cats will be in the courtyard. They're not really the most representative of their job, but Mean Cats and Bad Cats were my first NPCs in Godot, so they kind of end up in everything I make. </li>
<li>Characters have two jobs (through multiclassing) and will automatically know the default skills of both jobs, plus the common default skills. That looks like a few arrays of skill resources. I have over 100 skills right now to test, so I made skillmaker.py also spit out a TXT file with all the skills pre-formatted for copy-and-pasting directly into the player's skill pool. </li>
<li>Removing the Elementalist (lol), Heavy, and Light jobs from the game. The Disciple job is probably what all the magic class players will choose, though the inspiration and abilities are completely different from the Guild Wars Elementalist. It'll probably be more of a Transmutation Wizard from Dungeons & Dragons or something. Heavy and Light were combined into Soldier, which will accomodate both heavy tanky and quick, precise melee styles. </li>
</ul>
<br>
<h3>friday, february 18 </h3>
<ul>
<li>Proofreading JSON file, adding all the healing keywords </li>
</ul>
<br>
<h3>sunday, february 20 </h3>
<ul>
<li>More JSON </li>
<li>Added skills to all the NPCs' skillbars </li>
</ul>
<br>
<h3>thursday, february 24 </h3>
<ul>
<li>Documenting keywords in the GDD </li>
</ul>
<br>
<h3>sunday, february 27 </h3>
<ul>
<li>I left Empty Skill out of the official skills list. Oops. Can't have empty skill slots without that hidden skill. </li>
<li>Now all the skills should be more or less final. I begin manually adding each skill individually, add all the missing features and iron out all the bugs that the skill requires, test and make sure it works well, then make sure the python skillmaker can reproduce it perfectly. I'm going to have to do this with most skills at first, but it should snowball after a while. Currently, I've only ensured the skillmaker can reproduce skills with damage, attack/heal, summon, projectile, burning, and bleeding keywords. </li>
<li>Once all the keywords are supported by skillmaker, 80% of the skills will be able to be generated at the push of a button. There will be some special skills that don't use keywords (like the hidden dev skill Banish that is used to delete unneeded entities during the game) and probably some special skills that will need me to add a custom line or two. I want Blessfrey skills to have a strong, consistent keyword-based language, just like Guild Wars and Magic: The Gathering. </li>
<li>Keywords cluster into specific jobs, so I guess I'll add skills alphabetically. That way I can run into a broader assortment of keywords more quickly. </li>
<li>I start with Adrenaline Rush, a Critical Eye Brawler skill that increases the skill user's critical rate and attack rate. I begin adding support to the game for modifying those values and implementing those keywords. </li>
</ul>
<br>
<h3>monday, february 28 </h3>
<ul>
<li>Implementing critical attack rate and attack rate modifiers. </li>
<li>Fixing some bugs related to highlighting and targeting </li>
<li>I've been writing quests again. I wish I wrote more often. I used to write a scene for Blessfrey every day. Good or bad, surely if I have a ton of content, I can use the best 10% and have an interesting world. Oh well. </li>
<li>When I write random NPC side quests, I use very generalized faceless people like it's King's Field or something. Maybe the other world can be like that, but Lucrest is supposed to be a relatively small town full of familiar faces. I made a list of all the random positions in town (cashier for clothing store, regular at cafe, gondolier, etc) and am going to start filling them out. It's a lot of characters, and they don't <i>need</i> to be these deep and richly human side characters, but they will to some extent paint the picture of Lucrest and the greater world around it and how the story is changing everything. </li>
<li>Some characters, I already have a strong inspiration for. (I have tens of OCs from all kinds of novels I've written in private...Surely some of them can be adapted to live in Lucrest.) For the rest, I use the random button in Sims 3's Create-A-Sim as a starting point. I definitely struggle to create anyone other than cute young girls, so it's a helpful nudge to include some grown-ups, elderly people, and children, too. And guys. It's so hard to design guys... It's ridiculous because grungy, gritty old guys are my favorite characters in other works. </li>
</ul>
<br>
Last updated March 4, 2022 <br>
<br>

@ -1,30 +0,0 @@
<!--220127,210708-->
<h1>applied skills vs. applied keywords </h1>
march 24, 2022<br>
#skill <br>
<br>
<h2>monitoring skills </h2><br>
In Blessfrey, entities like to know where effects come from. <br>
<br>
For one, knowing the source of a killing blow allows for cute little feedback like the Binding of Isaac's death screen. <br>
<center><img src="/static/img/ent/bindingofisaac_deathscreen.png" alt="(image: the death screen in Binding of Isaac looks like a torn diary page, showing a crude illustration of what killed him and a vague description of the area where he died.)"</center> <br>
More importantly, it allows mechanics to interact more freely. If the bleeding status effect on a character is linked with the Cat Scratch skill, used by Bad Cat, that opens up possibilities. Maybe there could be a skill that reflects target's status effects back unto the original dealer. Maybe there could be an effect that negates skills from animal sources. Maybe a skill could heal an ally for even more you are both suffering from the same skill. The source is important if I want to do anything beyond the basic "cure bleeding." <br>
<br>
<br>
<h2>how should the data be bundled? </h2>
Skills themselves contain keywords, references to the user and target, and any data that could possibly be needed. It's immediately obvious to just directly tack the still onto the back of an array on the target. It can never be that easy, though. Skills contain keywords, all of which have different durations and aren't necessarily in the tree at the same time. Also, there's not a lot of applications for cycling through all the applied skills, besides making something like Guild Wars's <a href="https://wiki.guildwars.com/wiki/User_interface#Effects_monitor">effects monitor</a> and <a href="https://wiki.guildwars.com/wiki/User_interface#Damage_monitor">damage monitor</a>. <br>
<br>
<center><img src="/static/img/ent/guildwars_skillmonitor.png" alt="(image: Guild Wars's UI. At the top-left corner are all the icons of skills actively applied on your character. Along the left side of the screen is a list of the sources from which you recently took damage.)"</center> <br>
<br>
There aren't a lot of cases where the game would be looking to remove any instances of "Cat Scratch." However, there would plenty of skills looking to remove bleeding or status effects. <br>
<br>
It's a more direct approach to treat keywords and skills separately in the code. Characters can keep an array with references to the applied keywords instead of applied skills. The other data can be bundled with it as an array: [keyword, source, [tags]]. <br>
<br>
The keyword is the important part because, in bleeding's case, it's the main culprit for the actual health degeneration of the character. It also has its own duration, so there's no conflicts with holding onto bleeding even if all other keywords from its skill expiring. The source is just a noncommittal reference, so it and its data can be accessed as needed. Maybe the keyword and skill contain everything needed, but I'm going to pull out some frequently accessed attributes into tags to see if that helps with efficiency. <br>
<br>
<h2>it'll be better this way </h2>
It's always a good idea to separate keywords from their governing skill. When skills and keywords are decoupled, each can run their course without interference. <br>
<br>
<br>
Last updated April 7, 2022
<br>

@ -1,43 +0,0 @@
<!--220301,220501-->
<h1>march 2022: demo skills, new game </h1>
april 1, 2022<br>
#diary <br>
<br>
<h3>thursday, march 3 </h3>
<ul>
<li>Adrenaline Rush demonstates working critical and attack rate increases; effects are properly removed at timeout </li>
<li>refactored highlight; it only uses control nodes as it always should have; data can be added and removed to the highlight </li>
<li>skillbar allows common skills to be equipped on a character's skillbar, no matter their job </li>
</ul>
<br>
<h3>friday, march 4 </h3>
<ul>
<li>refactoring attack and movement, since move-to is wonky; attack no longer waits for a complete loop to realize it's out of range and should move to target </li>
<li>allow for UI windows to toggle </li>
</ul>
<br>
<h3>saturday, march 5 </h3>
<ul>
<li>Appraise adds floor item value to its highlight </li>
<li>added skills with an instant activation time </li>
<br>
<h3>sunday, march 6 </h3>
<ul>
<li>inventory items are highlightable </li>
<li>tweaked appearance of inventory UI </li>
<br>
<h3>saturday, march 19 </h3>
<ul>
<li>started drawing some new keyart for Blessfrey </li>
<br>
<h3>tuessday, march 29 </h3>
<ul>
<li>started seriously developing Lemonland, an action-oriented petsite </li>
<br>
<h3>thursday, march 31 </h3>
<ul>
<li>started digitizing the gdd for Lemonland. I started writing it probably around 2017. </li>
<br>
</ul>
Last updated April 8, 2022 <br>
<br>

@ -1,49 +0,0 @@
<!--220127,210708-->
<h1>skills aren't a manor; they're the DMV </h1>
april 7, 2022<br>
#skill #redesign <br>
<br>
<h2>skills are unmanageable as is </h2><br>
The effect of a Blessfrey skill is broken into keywords and conditions. For example, Blessed Purity cures 2 poisons from target and heals for 35 per poison removed. Technically, it's a skill with the cure keyword and a conditional heal keyword. This way, "heal" code is reusable, and players can rely on "heal" skills to be consistent. <br>
<br>
Phases are necessary for skills to be reactive. For Blessed Purity, cure is first, and heal cannot activate until it received the outcome of cure. To achieve this, 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 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 the flow of the skill. 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 ran long before earlier keywords finished up. When I forced keywords to execute one at a time, I had projectiles that awkwardly persisted until an unrelated keyword timed out. Timing was unpredictable, and instead of implementing a third fix, I wanted to critically assess the internal structure of the skill. <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 straight. <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>
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. 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>

@ -1,49 +0,0 @@
<!--210610,200429-->
<h1>skills aren't a manor; they're the DMV </h1>
april 7, 2022<br>
#skill #redesign <br>
<br>
<h2>skills are unmanageable as is </h2><br>
The effect of a Blessfrey skill is broken into keywords and conditions. For example, Blessed Purity cures 2 poisons from target and heals for 35 per poison removed. Technically, it's a skill with the cure keyword and a conditional heal keyword. This way, "heal" code is reusable, and players can rely on "heal" skills to be consistent. <br>
<br>
Phases are necessary for skills to be reactive. For Blessed Purity, cure is first, and heal cannot activate until it received the outcome of cure. To achieve this, 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 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 the flow of the skill. 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 ran long before earlier keywords finished up. When I forced keywords to execute one at a time, I had projectiles that awkwardly persisted until an unrelated keyword timed out. Timing was unpredictable, and instead of implementing a third fix, I wanted to critically assess the internal structure of the skill. <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 straight. <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>
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. 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>

@ -1,31 +0,0 @@
<!--220301,220501-->
<h1>april 2022: the flowers bloomed </h1>
april 1, 2022<br>
#diary <br>
<br>
<h3>monday, april 4 </h3>
<iframe width="500" height="218" src="https://www.youtube.com/embed/eN5mG_yMDiM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ul>
<li>we've waited through 4 years of silence, 6 years since the last album, military service, Burning Sun, Seungri's retirement, Taeyang's marriage and birth of his first child, the forced cancellation of their tour due to COIVD, TOP's termination with the label and founding of his own label. Big Bang finally came back, even if it feels more like a good-bye. I appreciate other directors, so I almost forgot G-Dragon is the more masterful director in KPOP after such a long silence. The video says everything in 3 minutes. Still Life blows everything away. So much joy. </li>
<li>it means everything that Seungri's battered chair is present beside the others (G-Dragon's chair is the director's chair) and all five of the lines in the final logo are intact. They aren't cowards and didn't abandon him, even if the media, corrupt courts, and public opinion did. I wish I had that level of strength and loyalty. </li>
</ul>
<br>
<h3>saturday, april 9 </h3>
<ul>
<li>starting to get my bearings with Angular after spending an hour on tutorials before bed the past two days. </li>
</ul>
<br>
<h3>sunday, april 10 </h3>
<ul>
<li>finally got a new refrigerator installed. The old one's been broken, and new ones have been delayed. I missed ice cream. ;-; Delivery got rescheduled to during church, though, which was disappointing. </li>
<li>implementing the CSS grid with Angular...so hard. </li>
</ul>
<br>
<h3>monday, april 11 </h3>
<ul>
<li>painted my nails OPI Quest for Quartz - it's from the XBOX collection. The quality is way below normal OPI nail polish, it's so packed with glitter that it's difficult to get a smooth finish with but somehow still looks matte once dry, and the color has as strange undertones as first-party 360 controllers. Flattering pinks and nudes are difficult colors for me to find, though, and this nearly nude mauvey medical pink is perfect. Yay, finally a nude polish that doesn't turn orange or brown on me...thanks, Microsoft? </li>
<li><a href="../220505">documenting my process so far</a> and making a nested grid within yesterday's grid </li>
</ul>
<br>
Last updated April 12, 2022 <br>
<br>

@ -1,140 +0,0 @@
<!--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,33 @@
<!--201217,200903-->
<h1>blessfrey </h1>
june 9, 2022<br>
#blessfrey #game <br>
<br>
<b>Blessfrey</b> is a 2D action RPG developed in Godot Engine for PC by Chimchooree in the USA. Its major inspiration is Guild Wars 1. <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>
<br>
<h2>Mechanics</h2>
Rely on your skill and creativity while exploring a rural southern town, getting involved in local shenanigans, and uncovering the mysteries the downtown dungeon. <br>
<br>
Class progression is free-form, and virtually no decision is permanent. You will pick a permanent job during character creation, but you will be able to choose among 5+ side jobs during gameplay for multiclassing. Find your perfect combination to clear levels and express your playstyle. <br>
<br>
Each class has its own style of skills associated with it. Skills are individual powers gained through gameplay which give specific effects when activated. 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. <br>
<br>
You'll not only manage your own skillbar but also the skillbars of your AI companions. <br>
<br>
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. <br>
<br>
<h2>Story </h2>
You play as Helia, a girl from the United States who has to live with her uncle in the Confederate States for the summer. Through dare or curiosity, she <br>
<br>
<h2>Setting </h2>
Blessfrey's development has taught me a lot about programming and devops. I hope you enjoy it whenever the demo and full game drop. <br>
<br>
Chimchooree<br>
<br>
Last updated June 4, 2022 <br>
<br>

@ -1,28 +0,0 @@
<!--200810-->
<h1>my favorite GDC talks </h1>
september 18, 2020<br>
game design, marketing<br>
<br>
I really should be keeping a list of these with descriptions, so why not keep them in an article? <br>
<br>
<h2><a href="https://www.youtube.com/watch?v=W20t1zCZv8M">Automated Testing and Instant Replays in Retro City Rampage </a></h2><br>
Vblank Entertainment's Brian Provinciano (2015) <br>
log button inputs to replay the game, for use in preproducing bugs, sharing replays on the community leaderboard, running cutscenes, and even controlling AI. It is 100% accurate in deterministic engines but also helpful in less deterministic engines.
<br>
<ul>
<li><a href="/static/extra/SimpleInputRec.cpp">I backed up the code he shared here. </a> It is a simple example of how to record and playback button input, written in C++.</li>
</ul>
<br>
<br>
<h2><a href="https://www.youtube.com/watch?v=UJiv14uPOac">Empathizing with Steam: How People Shop for Your Game </a></h2><br>
Chris Zukowski (2020) <br>
tips for how to design your Steam store page based on Zukowski's screenshare and shopping diary observations of ordinary people shopping on Steam <br>
<br>
<ul>
<li>Essentially, make your gameplay genre absolutely clear within the first 4 screenshots and in the short description so that people will wishlist your game to buy during a seasonal Steam Sale. </li>
<li>Approach your wishlisters as complete newcomers. Jazz up your Steam page before a Steam Sale. Release an update, post in your forums, put a Santa hat on your character. When wishlisters return to your page, they will see an active game and be sold on it all over again. </li>
<li>His conclusions are very similar to how I shop on Steam, except I could care less for the tag section. </li>
<li>The romantic indie fiction section on Amazon dwarves the indie game section on Steam. To be immediately visible to their audience, romance authors follow a clear visual language on their covers to communicate their genres and sub-genres. Zukowski uses this as an extreme method for attracting your audience using the tropes of your genre, pointing out common UI elements and the shooter guy on every FPS cover. </li>
<li>More notes @ <a href="/diary/entries/extra/gdc-design-steam-store">/diary/entries/extra/gdc-design-steam-store </a>
</ul>
<br>

@ -1,35 +0,0 @@
<!--200810-->
<h1>slimeAI - state transition diagram </h1>
december 10, 2020<br>
#ai #programming<br>
<br>
<br>
blessfrey's slimes use finite state machines. <b>Finite state machines</b> (FSM) are models with a finite number of states that either decide to generate output or transition to a new state based on input. They can only be in one state at a time. <br>
<br>
<br>
<h2>why use a finite state machine? </h2><br>
Because they are simple and flexible, while still allowing for interesting behavior. FSMs reflect our own mental model of how slimes behave (attack mode, wander mode, sleep mode, etc). These different modes or states are written as manageable chunks that easy to code, debug, update, and refactor. It's also efficient with processor time, since the behavior is abstracted to basic conditionals. <br>
<br>
A state can be as broad or specific as you need, so long as it's granular. So they can be more easily debugged and swapped out with one another, it's a good idea to have one entry point and one exit point for all your states.<br>
<br>
Of course, since FSMs can be used for anything with states and transitions. You can think of specific menus, mechanics or even your entire game in terms of states. I'm just using them here for understanding the AI for my slime monster. <br>
<br>
<br>
<h2>designing AI </h2><br>
Planning ahead is extra important, since any dead-ends will halt your slime. I used a state transition diagram to design the slime's AI. In the diagram, circles represent states and arrows represent transitions, triggered by some kind of event. I marked the initial state with a star, but Buckland recommends a double circle. <br>
<br>
The slime is a simple creature. It idles, chases anything that gets too close, and attacks until its target dies. Here's the diagram version of that:<br>
<br><center>
<img src="/static/img/ent/slimeai_diagram.png" alt="(image: a state transition diagram for slime ai.)" width="500" height="250"><br>
</center>
<br>
The states are Idle, Choose Target, Attack, and Move to Target. The initial state is Idle. <br>
<br>
If there's a valid target, the slime enters Choose Target. If there's no longer a valid target, it returns to Idle. If it successfully gets a target, it begins moving to target. If the target becomes invalid, it returns to Idle. Once the target is in attack range, it enters Attack. If the foe moves out of range, it returns to Move to Target. If the target becomes invalid, it returns to Choose Target. And the cycle continues. <br>
<br>
No matter how you follow the arrows, there are no dead-ends and all states can be reached eventually, so it'll work okay when it's implemented as code. <img src="/static/img/emo/heart.gif" alt="<3"><br>
<br>
<br>
<h2>reference </h2><br>
<a href="https://www.goodreads.com/book/show/161139.Programming_Game_AI_by_Example"><b>Programming Game AI by Example by Mat Buckland</b></a>. It's an easy to understand introduction to game math + AI concepts. The code examples are in C++, but they are still useful for understanding the underlying principles. I didn't even know where to begin before I started reading and it got me pretty decent groundwork for AI creatures with different behavior that reacts to the players.<img src="/static/img/emo/heart.gif" alt="<3"> <br>
<br>

File diff suppressed because one or more lines are too long

@ -1,28 +0,0 @@
<!--200810-->
<h1>my favorite GDC talks </h1>
january 7, 2020<br>
#gamedesign #marketing<br>
<br>
I really should be keeping a list of these with descriptions, so why not keep them in an article? <br>
<br>
<h2><a href="https://www.youtube.com/watch?v=W20t1zCZv8M">Automated Testing and Instant Replays in Retro City Rampage </a></h2><br>
Vblank Entertainment's Brian Provinciano (2015) <br>
log button inputs to replay the game, for use in preproducing bugs, sharing replays on the community leaderboard, running cutscenes, and even controlling AI. It is 100% accurate in deterministic engines but also helpful in less deterministic engines.
<br>
<ul>
<li><a href="/static/extra/SimpleInputRec.cpp">I backed up the code he shared here. </a> It is a simple example of how to record and playback button input, written in C++.</li>
</ul>
<br>
<br>
<h2><a href="https://www.youtube.com/watch?v=UJiv14uPOac">Empathizing with Steam: How People Shop for Your Game </a></h2><br>
Chris Zukowski (2020) <br>
tips for how to design your Steam store page based on Zukowski's screenshare and shopping diary observations of ordinary people shopping on Steam <br>
<br>
<ul>
<li>Essentially, make your gameplay genre absolutely clear within the first 4 screenshots and in the short description so that people will wishlist your game to buy during a seasonal Steam Sale. </li>
<li>Approach your wishlisters as complete newcomers. Jazz up your Steam page before a Steam Sale. Release an update, post in your forums, put a Santa hat on your character. When wishlisters return to your page, they will see an active game and be sold on it all over again. </li>
<li>His conclusions are very similar to how I shop on Steam, except I could care less for the tag section. </li>
<li>The romantic indie fiction section on Amazon dwarves the indie game section on Steam. To be immediately visible to their audience, romance authors follow a clear visual language on their covers to communicate their genres and sub-genres. Zukowski uses this as an extreme method for attracting your audience using the tropes of your genre, pointing out common UI elements and the shooter guy on every FPS cover. </li>
<li>More notes @ <a href="/diary/entries/extra/gdc-design-steam-store">/diary/entries/extra/gdc-design-steam-store </a>
</ul>
<br>

@ -1,94 +0,0 @@
<!--200810-->
<h1>questionnaire for making a fantasy race </h1>
march 4, 2021, 2020<br>
#design #lore #worldbuilding<br>
<br>
<h2>Aspects of Culture </h2>
General things to consider about your fantasy race to fill out their culture. Obviously not every point is going to be relevant. <br>
<br>
<h3>Basics </h3>
<ul>
<li>Basic Needs </li>
<li>Reproduction </li>
<li>Survival </li>
<li>Climate </li>
<li>Enemies </li>
<li>Allies </li>
</ul>
<br>
<h3>Lifecycles </h3>
<ul>
<li>Birth - how and where are offspring born? who is allowed to give birth? </li>
<li>Parenthood </li>
<li>Childhood </li>
<li>Coming of Age </li>
<li>Love </li>
<li>Mating </li>
<li>Marriage </li>
<li>Death </li>
<li>Education </li>
<li>Career </li>
</ul>
<br>
<h3>Religion + Legend </h3>
<ul>
<li>Dominant Religions </li>
<li>Denominations </li>
<li>Cults </li>
<li>Atheism </li>
<li>Religious Figures </li>
<li>Central themes of the religious teachings </li>
<li>Explanations for the natural world - how the world came to be? the origin of Man? </li>
<li>Afterlife - where do they go after they die? </li>
<li>Evidence for religion </li>
<li>Local legends </li>
</ul>
<br>
<h3>Climate </h3>
<ul>
<li>Preferred climate, climates inhabitted </li>
<li>How do they adapt to their climate? </li>
<li>Danger </li>
<li>How does their climate connect and separate them from other cultures? </li>
<li>What raw materials are available to them? </li>
<li>What seasons do they have? </li>
<li>Weather </li>
</ul>
<br>
<h3>History </h3>
<ul>
<li>How is history recorded and shared? Is it accurate? </li>
<li>Origin of their people, nation </li>
<li>Significant events - expansion, wars, diaspora, exile, mass murder, transfers of power </li>
<li>Local history - early settlement, original population, development, local heroes, times that national events or figures that visited the local area </li>
</ul>
<br>
<h3>Culture </h3>
<ul>
<li>Cultural Identity </li>
<li>Housing </li>
<li>Cuisine, Dining </li>
<li>Leading Industries, Imports, Exports, Trade </li>
<li>Class Stratification, Castes </li>
<li>Factions, Politics </li>
<li>Gender, Gender Roles </li>
<li>Race, Sub-race - Where and when did the other races or subraces arrive? Where do they tend to live? Influence on the greater culture, and vice versa? Race relations? Stereotypes? How are biracial people perceived? </li>
<li>Foreign culture - immigration policies to and from, perception of immigrants </li>
<li>Crime - Forms of crime, punishment </li>
<li>Health </li>
<li>Beauty, Fashion - Beauty Standards/Desired traits for mating, Differences among different demographics + factions, Influences </li>
<li>Rituals, Holidays, Festivals, Fairs, Masquerades </li>
<li>Language </li>
<li>Naming practices - when are names received? how many names are given? are names ever changed? what are naming conventions and how do they differ among the different factions? are there naming authorities or rules? </li>
<li>Etiquette </li>
<li>Art - Architecture </li>
<li>Entertainment - sports, hobbies</li>
<li>Symbols, Color associations </li>
</ul>
<br>
<h3>Others </h3>
<ul>
<li>Time </li>
<li>
</ul>
<br>

@ -1,19 +0,0 @@
<!--220310,220224-->
<h1>goalless games </h1>
january 14, 2022<br>
#game-design #philosophy<br>
<br>
<h2>goalless games </h2><br>
Some developers are happy to make loose, meandering sandbox games without no true win or fail state. The concept of goalless games is controversial among semantic people, but I feel that the genre with Garry's Mod and all the dressup games and physics simulators is enviably successful if not totally legitimate. It's not like the overwhelmingly popular Minecraft and old RuneScape don't share one foot in this genre, either, with their relative lack of direction and gameplay dominated by self-driven goals. I don't even feel like a central story or main objectives would improve these games (especially after watching Minecraft tack on some tedious hunger mechanic and an awkward final boss). <br>
<br>
<h2>my need for structure </h2><br>
I'm just not a goalless game designer myself. <br>
<br>
It sounds nice to provide an open world where the player can set out after his own goals without the dev placing horse blinders on his head. In reality, though, a game designer can't force a player to share the game's goals in the first place, so there's no need to purposefully design a game to be goalless. For me, I feel like neglecting to set a game's goal reflects a lack of a game development goal. A goal is helpful to not only the player but also to the developer. A central vision of the game's progression will imbue each piece of the game with more purpose and help them fit together more seamlessly as a whole. It's a safeguard against filling a game with pointless, incongruent clutter at whim. Obviously not every developer needs a goal-oriented approach, but I work better with one. <br>
<br>
No matter what philosophy the game designer has, though, a player will do what he wants to do, even if it has nothing to do with the goal of the game. For example, roleplayers are prominent members of MMO communities, and they might never max out a character or finish the main storyline. They throw out all the game designers' work and focus on finding the perfect backdrop and acting out their own scene instead. There are plenty of screensaver simulators and 3D chat servers out there for them, but they turn up in "real" goal-driven games, too. There are touches of this aberrant behavior in everyone who doodles with bullet holes, names their character something funny to harvest out-of-context dialog screenshots, or hoards a useless item. <br>
<br>
So in a way, game designers really don't need to design a goalless game. They can trust players to forge their own fun from even the most rigid hallway simulator. In my opinion, deliberately not designing goals runs the greater risk of making players too lost, bored, or overwhelmed to find their own fun or not even finding incentive to try the game in the first place. A better approach is in the middle, building towards a purpose while taking a tip from goalless games by filling the world with choices, interesting tools, and interactibles that are fun for fun's sake. At the end of the day, though, obviously do what works for your players! <br>
<br>
Last updated January 12, 2022
<br>

@ -1,153 +0,0 @@
Finish your project with Spiral + AGILE
Hello. I'm learning gamedev, and teaching is part of learning. I hope sharing my notes on process helps a little.
Do you never actually finish projects? Software methodologies draw a road map to release something.
Videogames are complex and potentially neverending projects without limitations and a thoughtful approach. Exploratory programming can be enlightening, but a lack of limitations leads to nothing anyone will ever use. Moving forward with a clear process reduces the risk of the feature creep, too much complexity, and scope issues that leads to delays and project abandonment. There are a number of software methodologies that help manage software projects in particular. Im using a spiral methodology with AGILE.
Spiral
Spiral methodologys name is self-explanatory You work through iterative cycles of planning, risk management, developing, refactoring, and testing. You will naturally discover flaws in your original design or be inspired to add new features throughout the project, so you can always go back to the planning phase to properly consider them. If you are trying to force a solution through code, you can return to planning. You test early and often, gaining insight from yourself and playtesters on shortcomings and successes of your design and execution. You refactor periodically to reduce complexity and review your code critically, which paves the way for smoother development in the future. You also are encouraged to keep moving you cant over-analyze, overdo, over-optimize any particular step of the process.
Stage 1 is Planning by setting objectives, considering alternatives, and setting constraints. Stage 2 is Risk Management. Emphasize uncertain features when prototyping. Stage 3 is development and testing. Stage 4 is Evaluating. And back to Stage 1.
AGILE
AGILE is used by big important tech companies like IBM. Its cyclical with consistent time-frames (called “sprints”) of planning, working, and evaluating. Its different from spiral, though, as AGILE is more of a philosophy. At the end of a sprint, every team member is encouraged to review every part of the process as a group. Everyone stands up. Everyone talks candidly for a few minutes. Consider the value of your design, your tools, the length of the sprints, the nature of the review itself, everything. If any part of the process is flawed, catch it and change it early. Be flexible in your pursuit of the best process for the team.
MVP
Together, using a spiral methodology with AGILE, you should shoot for a minimum viable product (MVP). This could be a vertical slide, a horizontal slice, a demo, or something functional like that. Approach your project how Bob Ross approaches a painting. He doesnt spend a season on a little tree in the corner; he works the whole canvas and builds on his happy accidents. Find the joy of programming. Your game should be playable as soon as possible maybe ugly and dumb and pointless but playable. At any step, no matter how far you are from finishing, have something that works as a finished project.
To do that, you need to decide what the most essential and defining features of your game are and work on them first. Move on to less essential features as time goes on until your games done. Or until youre done working on it. Either way, youll have a playable game to offer people.
Prototype
If you're not even sure if your game is fun or possible, it's better to quickly test with a prototype. You can find a way to abstract your problem or distill it into something smaller to build or present to potential players. Use cut paper, quick sketches, action figures, free tilesets, whatever can represent your game cheaply and quickly. Maybe you can build a board or card game on paper that demonstrates your mechanic. Maybe you can make a room in your engine and spawn in too many items to test performance. Maybe you can share some key concept art or mockups with potential players and gauge responses. If you don't like your results, it's way easier to edit 20 minutes of construction paper than six months of production code.
When is my concept ready?
Your concept is all of your interesting ideas for your game. If it sounds like a fun game with concrete features, youre ready.
I keep my ideas in a personal wiki so I can see the inter-connectivity of my concepts. I also jot notes into a notes app on my phone. Writing it down is always better than keeping it floating in your head.
Make your concept actionable
Next, reword your concept into a series of unambiguous items that can be demonstrated clearly through your code. These are functional requirements. There are nonfunctional requirements, too, including the target framerate on specific hardware or the paradigm necessary to make modding more accessible. Just start writing them out in bullet points. You dont need to know all your function requirements yet, and you will have to rework parts of your project later anyways. If you use an iterative methodology, youll return to evaluating and planning phases plenty of times before release.
Pick a theme
Build your first functional requirements around a theme that is central to your game. I think AGILE calls it a story. For an RPG, it could be combat, maybe characters… Give each requirement a code to make them easily track-able.
Code your list
I start with character. Down the list, I name them AA, AB, AC, AD…
Functional Requirements - Character
• AA - A character has a name. Demonstrate that a character has a name by making a character and printing its name.
• AB - In a game, there can be multiple characters. Demonstrate that you can add a character and remove a character by printing the names of the existing characters.
• AC - A character can have abilities. The abilities belong to the character. Demonstrate by having multiple characters and printing their names and abilities.
• AD - There is a pool of abilities the character knows. There are abilities that they have equipped. Demonstrate by printing the abilities they know and the abilities they have equipped with their name.
• AE - Abilities can be added or removed from a character. Demonstrate by printing the list of abilities you know and the list of abilities you're using. Then add a new ability from the list you know to the list you are using and print the list of abilities you're using.
• AF - A character may not know all the possible abilities. Demonstrate by listing all of the abilities in the game. Then list all the abilities the character knows.
• AG - The character can't use abilities he doesn't know. Demonstrate by trying to add an ability from the list of all abilities in the game that the character doesn't know to the list of abilities the character is using. Print that you cannot do that.
• AH - The character can't have the same ability equipped more than once. Demonstrate by trying to add an ability the character already has equipped. Remove original from the slot it is in and add new ability to that slot. Print abilities character is using.
• AI - Skills go in slots and slots can be empty. Demonstrate by having no abilities in the character's slots and printing the list of skills the character is using. Put abilities in the slots and print what abilities is in each slot. Remove abilities from the slots and print what abilities is in each slot.
• AJ - When putting a skill in a slot that already has a skill, the skill is replaced. Demonstrate by having a skill in a slot then putting a new skill in that slot. Print the skill that is in that slot.
• AK - An item has a name. Demonstrate by printing an item's name.
• AL - There are items that can be put in a character's inventory. Demonstrate by adding an item to the character's inventory then printing what is in the inventory.
•AM - Characters have equipment slots. Demonstrate by adding an item to the equipment slots then printing what is in the equipment slots.
Rate by difficulty
Next, assign a difficulty level to each item. Do not focus on what you know; focus on what you do not know. The goal is to express your unknowns about the task. If you need to remember how to meet a requirement, if you dont know how to do a requirement, if you need to learn new tools in order to meet a requirement, youll face varying levels of unknowns. Unknowns increase risk, so they increase your difficulty rating.
I use Fibonacci numbers (1, 2, 3, 5, 8, 13,...) to represent difficulty levels. 1 is no problem at all, 3 is easy but with vague uncertainty, 13 has uncertainties upon uncertainties and should be broken into less risky tasks, etc.
There are two methods to rating difficulty. Assign your lowest difficult value to the easiest item on the list. (Thats what I do.) Or, find an item with an average difficulty relative to your list and assign it a middle number. If you are working on a team, you need to vote on each rating. The rating should represent the difficulty of the items for your team because you have not assigned individual work yet. Next, rate the other items relative to the it. If anything is rated conspicuously high, see if it can be broken into smaller, more manageable items.
This is an entirely subjective estimate. Dont worry too much about the exact difficulty you assign just approximate it. Youll give more accurate and meaningful ratings with practice.
Choose length of sprints
Choose a time-frame with enough time to get practical work done that is still granular. Dont worry too much about the perfect length of time you can always change the length of sprints at the next evaluation phase. I choose a week.
Choose this sprints functional requirements
Using the difficulty ratings, determine what is a weeks worth of work. Obviously, you can only pick items with prerequisites if you are also doing the prerequisites during the same sprint. Use the ratings to avoid being overwhelmed. Youll get better using your ratings with practice.
For a real company, you take this list and do it and no more for the duration of the sprint. You have other team members, other teams, clients, and all kinds of people who rely on the schedule. Disrupting that hurts the process of other people. If its just you or your team is small, you have more freedom to adjust your schedule if you severely misjudge the difficulty of your work. I finished the “weeks” worth of work in a few hours and was able to just add more requirements since its just me.
My initial first weeks requirements...
• (1) AA - A character has a name. Demonstrate that a character has a name by making a character and printing its name.
• (3) AB - In a game, there can be multiple characters. Demonstrate that you can add a character and remove a character by printing the names of the existing characters.
• (5) AC - A character can have abilities. The abilities belong to the character. Demonstrate by having multiple characters and printing their names and abilities.
• (5) AD - There is a pool of abilities the character knows. There are abilities that they have equipped. Demonstrate by printing the abilities they know and the abilities they have equipped with their name.
• (8) AE - Abilities can be added or removed from a character. Demonstrate by printing the list of abilities you know and the list of abilities you're using. Then add a new ability from the list you know to the list you are using and print the list of abilities you're using.
Next, write code that meets the requirements. Heres some output that demonstrates meeting the requirements:
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>>
========== RESTART: C:\Users\Toma\Pythons\Dungeon_Girls\src\game.py ==========
AA: A character has a name: Chloe
AB: There can be multiple characters.
AB: Characters: [Chloe, Willa]
AB: You can add a character.
AB: Characters: [Chloe, Willa, Destry, Eden]
AB: You can remove a character.
AB: Characters: [Chloe, Willa, Destry]
AC: Characters can know skills. Chloe: [Cure, Summon Archangel, Candlelight] & Willa: [Fireball, Demonspeak]
AD: Characters can know skills and have skills equipped.
AD:Chloe's library: [Cure, Summon Archangel, Candlelight] & skillbar: [None, Summon Archangel, Cure, Candlelight, None]
AE: Skills can be added to a character.
AE: Chloe: [Identify, Summon Archangel, Cure, Candlelight, None]
AE: Skills can be removed from a character.
AE: Chloe: [Identify, Summon Archangel, Cure, None, None]
>>>
Then you'll be able to evaluate your code and process, plan your next steps, maybe prototype something, maybe develop something, maybe refactor something, test, and continue the spiral. If you follow a methodology, I think your chances of finishing at least something is pretty good.
Thanks for reading.
Follow for more game development on Twitter @lilchimchooree.

@ -1,77 +0,0 @@
<!--makeup-->
<h1>anti-haul: 10 palettes I almost bought </h1>
december 1, 2021<br>
<br>
Today I'm pretending to be a beauty blogger with this prompt lifted from a <a href="https://www.youtube.com/watch?v=sIeOQ1ARpYw">Angelica Nyqvist</a> video. Anti-hauls are a reaction to the popularity of haul content on social media. Instead of sharing your haul and all the reasons you bought things, you look critically at new products and discuss why you won't buy them. It encourages conscious consumerism and eliminates FOMO, or that's what Kimberly Clark intended when popularizing the concept. Most anti-hauls on Youtube are just trashing ridiculous or undesirable products these days. <br>
<br>
I realized my Urban Decay eyeshadow singles had PTFE (Teflon) in it and got skeeved, despite the lack of evidence to suggest it is dangerous to wear around the delicate mucous membrane of the eyes. Some of them were 5 years old anyway, so it was a good opportunity to refresh my makeup collection. Eyeshadow palettes are so much more exciting and colorful than they used to be, (not to mention, I'm better off now than I was as a teenager), so at least 10 beauties made my list. Only 3 made the cut, though: the ABH Norvina in the purple velvet packaging, NARS Extreme Effects, and ColourPop Lil Ray of Sunshine. Let's share the runners-up! <br>
<br>
(By the way, only Urban Decay's old subway token eyeshadows have PTFE. The old and new Moondust and the new singles in the clear square containers are PTFE-free. ) <br>
<br>
<h2>Pat McGrath Iconic Illumination </h2>
Pat McGrath is a monumental makeup artist, and I've followed her runway work long before she had a brand of her own. I can't help but follow her palettes, even if they are ridiculously expensive and overly focused on warm pinks and golds. This is the one quad that really makes me double-take, though. A light shade, a warm red or reddish brown, a gold, and a dark brown are my essential shades, and this all shimmer version is beyond classy. It's appealing to have them together in a fancy quad in a formula by one of my favorite makeup artists. Even if the palette wasn't $65 for 4 shadows, I already own enough versions of this look to intentionally buy any more, though. Buying it or not, I do appreciate her stamping approval on my favorite color combination by releasing them as a quad. <br>
<br>
<h2>NARS Climax </h2>
A true fall palette, and it sings to me as someone looking to buy some mauve and olive eyeshadows. The neutrals here resemble the Iconic Illumination quad. Alongside the mauve and olive, they draw my eye even more, but I already own a neutral-leaning version in Norvina and a warm coppery version in Extreme Effects. Pretty or not, it's a major strike against a $49 palette for it to be half-full of colors I already own. <br>
<br>
I've been investigating olives and find those with rich brown or mustard undertones instead of gray or true olive undertones to be the most beautiful on my skin. I haven't seen Climax in real life, but Youtube swatches seem to set Up in Smoke in the camp of olives that sheer out to gray. <br>
<br>
I also learned that Nars is no longer cruelty-free, and they seem to have made this decision solely to chase after the lucrative Chinese market. The oversexualized product names are silly, even if they're supposed to be feminist or whatever, but they never garner more than an eyeroll from me so long as the makeup's quality. Compromising your company's values for profit, on the other hand, is truly offensive. They make some of my favorite makeup, too. :( <br>
<br>
<h2>Natasha Denona Mini Retro </h2>
I think a lot of people feel that if $129 for a big palette is too much, $25 for a mini is an accessible way to own something from her. All of her minis are gorgeous and wearable, but none of them are quite right for me. Glam is too warm-toned, and Star really only grips me with its grungry red-green duochrome. I could convince myself that Retro is just right for me, though, with it's pink-green dichotomy and undupable Industrial. Really, though, I like mauves over pinks, and these pinks are too tan-leaning for my tastes. Even though Industrial is an undeniably gorgeous green-gray, I've already found that I favor wearing green-browns. I suspect I'd prefer admiring this one over wearing it. <br>
<br>
<br>The price is attractive but still a major issue for me. $5 per eyeshadow is what I pay for hand-picked indie shades, so why should I pay that rate for a standardized product? <br>
<br>
Maybe someday there will be a Natasha Denona palette for me, but I don't have to settle for the Mini Retro. <br>
<br>
<h2>Ace Beaute Tropical Vibes </h2>
I've never liked Ace Beaute's color stories, so I wasn't following them until Youtube flashed me a thumbnail with Amy Loves Beauty holding the olive palette of my dreams. It's an indulgent assortment of all the swampy greens I could want and more. <br>
<br>
Tropical Vibes is much more than an olive palette with its variety of warm and cool tones and pops of color and shimmer, but let's be real. I want it for the greens. I don't even want a tealy blue, and I don't need another orange, red, or dark brown. $40 and 15 pans is excessive just to satisfy an olive craving. I think I'll survive holding out for a few olive singles. <br>
<br>
<h2>Kaja Bento in Glowing Guava </h2>
Kaja has the cutest packaging, and their bento boxes are their cutest offering. Really, I want a bento for the concept. They seem so easy to travel with, keep in your purse, and do effortless touchups throughout the day with. I imagine not even needing a brush with these. The most appealing is the mauve, but the brown and yellow ones are all darling. <br>
<br>
The main appeal of these things is all in my head. I haven't relied solely on fingers since my messy middle school Maybelline eyeshadow duo. If I want to travel, my tiny magnetic palette is more than adequate. Also, it may not be immediately obvious, but the mauve's shades are so close to the plums in Extreme Effects that I'd practically be owning the same colors twice. <br>
<br>
<h2>ColourPop It's a Mood </h2>
I never give a second look at these giant Morphe or ColourPop palettes, but give ColourPop 500 chances and they'll hit you with something. Oblivion is olive I want, with its medium depth and warm undertones. They paired it with all my favorite autumnal jewel tones and purples, which is a color family my brown eyes certainly wouldn't mind wearing more often. Then they launched it at a severe discount. I was in trouble until I really looked at the thing.<br>
<br>
Crystallizing my taste in eyeshadow is a serious flaw, surprisingly enough. Out of 3 palettes and a few singles, I own almost perfect dupes for 12 of the 35 shades. Some of them swatch better than my dupes, but I prefer the higher quality ingredients in mine. To sour the deal, 3 more shades are plastic glitters and 1 more is a pressed pigment that isn't eye-safe. Maybe it would have saved me money if this had been my first palette, but I don't mind finding a dupe for Oblivion, considering looking into getting some purples someday, and reaffirming my affinity for the colors I already own. <br>
<br>
<h2>ColourPop The Child </h2>
What can I say? I really want all these palettes with an interesting take on green. Of course one of ColourPop's most unique palettes by far has to be a Disney cashgrab advertising a Netflix Disney cashgrab. Even though I'd love some more light-colored shimmers, let's be real. I only want this $16 9-pan palette for Little Frog and Float Your Crib. They aren't my favorite versions of olive, either, and they're trapped in packaging I actively dislike. I really shouldn't have almost bought this palette as many times as I have. I'd definitely wear it, but surely I'd like something else more. <br>
<br>
<h2>Huda Beauty Jaguar </h2>
This is nearly a perfect fall color story, and it reviewed quite well. I've been looking for taupes and mauves, so I really considered buying this one. <br>
<br>
It's really too bad this is one of Huda's Chinese-produced 9-pans. Theoretically, a brand can maintain high standards for labor rights, environmental protection, and product quality within a Chinese factory, but if they truly valued in those things, would they really swap out their usual Italian factories for Chinese ones? I have some doubts. Especially since these Chinese 9-pans typically don't review as well as the Italian palettes. <br>
<br>
Still, though, no other fall palette is quite as stunning in their product photography and social media swatches. It took swatching in real life to shatter my infatuation with it. My preference for shiny eyeshadow is either a smooth, metallic satin or a frost. The online swatches didn't reveal how much visible glitter was in these shadows. They were even slightly patchy for me under Sephora's lighting. All in all, I've lost my interest in buying. <br>
<br>
Still a beauty, though! Prettiest palette Huda's ever released. <br>
<br>
<h2>Natasha Denona Triochrome </h2>
Now this is a really special palette. It completely lacks the basic row of warm neutrals and browbone shimmers, and instead has a highly readable, very wearable layout of shades in sulfuric greens, diverse purples, and off-beat orange "neutrals." Natasha Denona is my favorite makeup artist, and her looks using this palette are the rare few I'd replicate for myself. In fact, I already try with the orange-brown and purple in Norvina, but they're far from a dupe. <br>
<br>
The palette received some criticism for its lackluster multichromes and high percentage of mattes. I don't understand the matte hate at all, and I think that although my Terra Moons multichrome probably has way more shifts and interesting colors, I'd actually wear her more subtle ones more often. Effectively, tamer shifts would read as intensely dimensional shimmers on the eyes, so it's like using a lot of different shimmers in one. Unless, of course, reviewers are lying and her multichromes rock. They look perfectly shifty on her makeup artistry videos to me. <br>
<br>
So why not buy it? Natasha Denona's big palettes are SO expensive. $129 for 15 pans! Apparently her formula is fabulous, and this one has 3 expensive multichromes, but the price is astronomical for makeup. $129/15 is $8.60 per shadow. More realistically, you could price the three multichromes like Terra Moons's $17 extreme multichromes then divvy out $6.50 for the 12 remaining mattes. That's double Looxi, more expensive than Sydney Grace, Devinah, and ColourPop, and slightly less than JD Glow and Give Me Glow. According to youtubers, indie multichromes blow these out of the water, so the dupe may even be a step up. The main reason to buy a palette in my opinion is to get savings by buying the shadows in bulk in standardized packaging. I can't believe you could fully dupe this palette with hand-picks and potentially save money, depending on how many free shipping weekends you hit. Who knows? If you hit up a cheaper brand during a sale, you might even have enough money left over to buy the coolest magnetic palette on Etsy to hold it all in and easily crush the basic purple Triochrome case. <br>
<br>
Sorry, Natasha Denona. I don't need your palette to be inspired by your color combinations and eyelooks. Even at a $60 sale price. (Speaking of FOMO, this limited edition palette is only just now going on sale?)<br>
<br>
<h2>ColourPop Flutter By </h2>
The most sensical, truly cool-toned mauve palette, with a dupe of one of the most covetable Pat McGrath shades: Wild Wing vs. Velouria. The range of light to dark, variety of finishes, and dedication to showing all the variations of mauve is very smart and usable. Cool tones are rare in the makeup world but are very flattering with my colors. Plus, the butterfly cover is special to me, since butterflies are my spirit animal, according to a random online quiz. It's also so affordable to be considered my #1 most difficult palette to anti-haul, giving you 12 pans with decent quality for $18 and frequent sales. <br>
<br>
So why have I never confirmed my order? There's only one reason: Overpacked. ColourPop just had to ruin a beautiful palette with a plastic glitter pan that isn't eye-safe. Maybe it's not a big deal, since I won't wear it nor will I ever be able to 100% cut plastic waste out of my life, but I'd rather wait for a non PET mauve. <br>
<br>
<h2>conclusion </h2>
Makeup isn't <em>that</em> expensive, but I'd rather not buy second-rate clutter and save my money for investments, needs, or at least nice things in other categories. I no longer work in an office that requires a full face of makeup, so it's an absolutely frivolous purchase for me anyway. <br>
<br>
Understanding what draws me to new releases can clue me into the handful of singles that I'd enjoy just as much as the big honkin' palettes. It also prevents me from buying 15 of the same plums, coppers, and golds, which I would absolutely do if allowed to act on impulse. At the end of the day, my Norvina palette is all I need for soft spring and summer looks, and Extreme Effects has all the best coppers and plums for moody fall looks. My ColourPop 9-pan has the best yellow and orange money can buy, save the Natasha Denona Sunrise palette at 6x the price. A few colorful jewel-toned eyeliner shadows fills in the rest of my needs. <br>
<br>
I picked up a few mauve and olive singles during Black Friday, but the palettes listed above thankfully prove that my taste is narrow enough to otherwise be accomodated by what I already have. Good thing, too, since these things tally up to $330 at sale price minus shipping. Instead, I suspect I'll be just as happy with $20 of singles plus shipping, alongside my old eyeshadows. <br>
<br>
It's hard when face masks cover up much of what makes us look unique, the pandemic forces us away from people, and makeup is suddenly forced into an all-or-nothing situation with eyeshadow. Try to make the most of what you have already and be creative. <br>
<br>

@ -1,15 +0,0 @@
<!--reading-journal-->
<h1>Magic: A Fantastic Comedy in a Prelude and Three Acts</b> - G.K. Chesterton (1913) </h1>
january 8, 2020<br>
<br>
<br>
Did you know G.K. Chesterton wrote a play? Its existence really surprised me while browsing Project Gutenberg. I know him mostly as the author of The Everlasting Man and lots of other books about philosophy and religion. He's not stuffy, though. His style is so poetic and dramaticized through use of personification, he makes the topics as enjoyable as fiction...at least, from what I remember. It's been 10 years since I read anything else by him. <br>
<br>
Apparently, George Bernard Shaw wrote a letter pressuring him into drama because he felt he was wasting his talent on journalist. (According to <a href="https://www.chesterton.org/lecture-25/">Chesterton University's Lecture 25: Magic by Dale Ahlquist</a>) <br>
"I shall deliberately destroy your credit as an essayist, as a journalist, as a critic, as a Liberal, as everything that offers your laziness as a refuge, until starvation and shame drive you to serious dramatic parturition. I shall repeat my public challenge to you; vaunt my superiority; insult your corpulence; torture Belloc; if necessary, call on you and steal your wifes affections by intellectual and athletic displays, until you contribute something to British drama." (also from that <a href="https://www.chesterton.org/lecture-25/">link</a>) <br>
<br>
You can read Magic, too, at <a href="https://www.gutenberg.org/files/19094/19094-h/19094-h.htm">Project Gutenberg</a>. Plays are meant to be performed, though, so hopefully you can find something on Youtube at least. <br>
<br>
<br>

@ -1,14 +0,0 @@
<!--reading-journal-->
<h1>Drinker of Souls - Jo Clayton (1986) </h1>
january 8, 2020<br>
<br>
<br>
I usually read classics or philosophy, so I thought I'd cut loose and read my first 80s science fiction novel: Drinker of Souls, from Jo Clayton's Drinker of Souls trilogy. Unfortunately, Clayton's novels are probably all out of print and won't be entering the public domain for a long, long time. I found mine at a thrift store. <br>
<br>
I just picked up a random scifi book, but apparently the Skeen Trilogy by this author is more popular. <br>
<br>
Review here
<br>
I read half of the next book, Blue Magic, but it was kind of boring and the characters were less interesting for me. It explored more parallel worlds, all with dramatically different levels of technology and magic. It had that 'wizards using computers' aesthetic that isn't done often enough. The setting reminded me of the Sigil in Planescape: Torment, how characters from all over get trapped in Brann's world. There was the guy from a futuristic trapped there with his phaser, like that number tattoo guy in the Buried Village who tells you about his cyberpunk world. <br>
<br>

@ -1,18 +0,0 @@
<!--200917,201112-->
<h1>writing resources </h1>
march 4, 2021<br>
<br>
<br>
Blessfrey has a story and character arcs. Let's get more confident with our story with online resources. <br>
<br>
<h2>plot </h2><br>
<ul>
<li><a href="https://www.youtube.com/watch?v=v-I9N5LsvPM">Ellen Brock - How to Plot Your Novel FAST | Writing Advice</a> - She takes a randomly generated story idea and builds the plot using techniques she uses with her clients. Summarize the basic idea, ask questions about what you don't know, answer the questions, make a list of all the scenes you know should be included, go back and flesh things out. </li>
</ul>
<br>
<h2>general </h2><br>
<ul>
<li><a href="https://www.youtube.com/c/QuotidianWriter/videos">Diane Callahan - Quotidian Writer</a> </li>
<li><a href="https://tvtropes.org/">TV Tropes</a> </li>
</ul>
<br>

@ -1,67 +0,0 @@
<!--article,article-->
<h1>title in lowercase </h1>
date in lowercase<br>
#accessibility #ai #apache #blogging #bottle #color #css #design #gamedescription #gamedesign #gamejam #gamemechanics #git #html #internationalization #json #localization #marketing #mockup #nginx #php #programming #python #regex #regularexpression #server #simpletemplate #skills #systemdiagram #webdev #webdesign #website<br>
<br>
<b>term</b> is etc. <br>
<br>
Capitalize some words. <br>
<br>
<ul>
<li>CSS, HTML, JSON, PHP </li>
<li>CSS Grid, Flexbox </li>
<li>MMO, RPG </li>
<li>NPC, WASD, XP </li>
<li>.CSV </li>
<li>IM, PC, UI, URL, VPS </li>
<li>GDC, YouTube</li>
<li>2D, 3D </li>
<li>First Class, Second Class</li>
</ul>
<br>
<br>
<h2>lowercase </h2>
<ul>
<li>blessfrey, chimchooree, pixel joy, pixel sparrow </li>
<li>gamedev, webdev </li>
</ul>
<br>
<ul>
<li>coroutine </li>
<li>lol </li>
</ul>
<br>
How to spell words... <br>
<br>
<ul>
<li>Chatroom </li>
<li>Cooldown </li>
<li>Eye Shadow </li>
<li>Freeform </li>
<li>Gameplay </li>
<li>Hello World</li>
<li>Multiclassing </li>
<li>Mockup </li>
<li>Petsite </li>
<li>Playstyle </li>
<li>Plugin </li>
<li>Premade </li>
<li>Skillbar </li>
<li>Subfunction </li>
<li>Videogame </li>
<li>Web Page </li>
</ul>
<br>
<h2>subtitle </h2><br>
spacing like this <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">
<br><br>
<h3>subsubtitle with BrandName or CSS or chloe </h3>
<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>
<br>

@ -1,132 +0,0 @@
<!---->
<h1>FlightRising badges </h1>
january 8, 2020<br>
<br>
<br>
Privately hosting my collection of community badges, in case imgur goes up in flames. <br>
<br><center>
<b>Fire - Lava Bank - 1/21</b> <br>
<img src="/static/img/fr/badge/XUdWkkW.gif" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/b1AOHoJ.gif" alt="(image: FlightRising badge)"><br>
<br>
<b>Fire - Snowy Safari Zone - 1/21</b> <br>
<img src="/static/img/fr/badge/berniecg_copy.gif" alt="(image: FlightRising badge)"><br>
<br>
<b>Fire - WildFire Lottery - 1/21</b> <br>
<img src="/static/img/fr/badge/A6QWEY1.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/3ukZc7M.png.gif" alt="(image: FlightRising badge)"><br>
<br>
<b>Fire vs Light - 12/20</b> <br>
<img src="/static/img/fr/badge/yaVmX6u.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/xDr8a1W.png" alt="(image: FlightRising badge)"><br>
<br>
<img src="/static/img/fr/badge/ZVJmarp.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/tcCHz91.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/vnh3Mff.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/LYPRI6F.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/SjrQCts.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/GgpglGe.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/aaDmWw4.png" alt="(image: FlightRising badge)"><br>
<br>
<b>Arcane - 9/20</b> <br>
<img src="/static/img/fr/badge/rlMOyL9.png" alt="(image: FlightRising badge)"><br>
<br>
<b>Water - Dom Push - 9/20</b> <br>
<img src="/static/img/fr/badge/qjHFcDc3_o.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/nZVpG4qA_o.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/zizf7QON_o.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/w6nm2ozw_o.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/j8UN9lFU_o.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/jp31RUVs_o.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/BQ0130S.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/kEdMEAr.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/ooqpaDf.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/TPdrafh.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/uC4Bb27.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/xQhJ1xN.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/z9l1QrF.png" alt="(image: FlightRising badge)">
<br>
<b>Fire - Flameforger's Festival - Exalts, Geyser's Grocery Games - 8/20</b> <br>
<img src="/static/img/fr/badge/xtPgVlw.png" alt="(image: FlightRising badge)"><img src="/static/img/fr/badge/JkzY8tq.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/4hr3mkF.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/Ugwb1bB.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Vsjjk1F.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/TikpO8M.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/9fWw4WY.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/aFpsZk8.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/gmc2rJz.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/KBPvEjz.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/q2tTUld.png" alt="(image: FlightRising badge)">
<br>
<b>Fire - <a href="https://www1.flightrising.com/forums/adopt/2898646/1">Pyro's Kitchen Window Bonsai</a> - 8/20</b> <br>
<img src="/static/img/fr/comm/BJ41FcK.gif" alt="(image: FlightRising commission)">
<img src="/static/img/fr/comm/dNdPm9U.gif" alt="(image: FlightRising commission)">
<img src="/static/img/fr/comm/Lqh2ToP.gif" alt="(image: FlightRising commission)">
<img src="/static/img/fr/comm/upFf3lM.gif" alt="(image: FlightRising commission)">
<img src="/static/img/fr/comm/XRywlxo.png" alt="(image: FlightRising commission)">
<br>
<b>Fire - Lava Bank - 8/20</b> <br>
<img src="/static/img/fr/badge/5jRjhgz.png" alt="(image: FlightRising badge)">
<br>
<b>Lightning - Thundercrack Carnivale - 7/20</b> <br>
<img src="/static/img/fr/badge/6sma760.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/UBTHVjT.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/yNi4cd2.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Qku4U0s.png" alt="(image: FlightRising badge)">
<br>
<b>Fire - Shocking Safari Zone - 7/20</b> <br>
<img src="/static/img/fr/badge/0QLVhCv.png" alt="(image: FlightRising badge)">
<br>
<b>Fire - Baldwin's Brew for the Bank Over 9000 - 7/20</b> <br>
<img src="/static/img/fr/badge/Cauldron1.gif" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/bm199ed.png" alt="(image: FlightRising badge)">
<br>
<b>Ice - 7/20</b> <br>
<img src="/static/img/fr/badge/ebTXM7P.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/d2Sm0UO.png" alt="(image: FlightRising badge)">
<br>
<b>Arcane - 7/20</b> <br>
personality quiz result: Starseeker <br>
<img src="/static/img/fr/badge/1532000966.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/R1I3CHl.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/jy44Pp7.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Y6Uxj5O.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/p1lH9gN.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/wBDxnV5.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/t5oWqxC.gif" alt="(image: FlightRising badge)">
<br>
<b>Fire - Shining Safari Zone - 7/20</b> <br>
<img src="/static/img/fr/badge/l3XfYkQ.gif" alt="(image: FlightRising badge)">
<br>
<b>Fire - Trash for Cash - 7/20</b> <br>
<img src="/static/img/fr/badge/ASvvnTt.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Pixel_Trash_for_Cash_v2.png" alt="(image: FlightRising badge)">
<br>
<b>Wind vs Plague - 6-7/20</b> <br>
<img src="/static/img/fr/badge/wvpfire.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/hogZwcV.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/wvpwind.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/dqixKfS.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/WvP-OOF-t1.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/WvP-OOF-t2.png" alt="(image: FlightRising badge)">
<br>
<img src="/static/img/fr/badge/Kp99Tes.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Saa8Z5q.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/G130THg.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/vhzV2z9.png" alt="(image: FlightRising badge)">
<br>
<b>Fire - Subspecies Raffle - 5-6/20</b> <br>
<img src="/static/img/fr/badge/Cake.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Candles.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/Balloons.png" alt="(image: FlightRising badge)">
<img src="/static/img/fr/badge/PartyHat.png" alt="(image: FlightRising badge)">
<br></center>
<br>
Last updated: 1/29/21<br>
<br>

@ -1,288 +0,0 @@
<code>/******************************************************************************/</code>
<code>// SIMPLE INPUT RECORD/PLAYBACK</code>
<code>// (c) 2015 Brian Provinciano</code>
<code>//</code>
<code>// You are free to use this code for your own purposes, no strings attached.</code>
//
// This is a very basic sample to record and playback button input.
// It's most useful when activated on startup, deactivated on shutdown for
// global button recording/playback.
//
// For details on more advanced implementations, see my GDC 2015 session:
// -> Automated Testing and Instant Replays in Retro City Rampage
// The slides and full video will be available on the GDC Vault at a later date.
/******************************************************************************/
/******************************************************************************/
// wrap it so it can be conditionally compiled in.
// for example, set INPUTREPLAY_CAN_RECORD to 1 to play the game and record the input, set it to 0 when done
// INPUTREPLAY_CAN_RECORD takes priority over INPUTREPLAY_CAN_PLAYBACK
#define INPUTREPLAY_CAN_PLAYBACK 1
#define INPUTREPLAY_CAN_RECORD 1
#define INPUTREPLAY_INCLUDED (INPUTREPLAY_CAN_PLAYBACK || INPUTREPLAY_CAN_RECORD)
/******************************************************************************/
#if INPUTREPLAY_INCLUDED
#define INPUT_BUTTONS_TOTAL 32 // up to 32
#define MAX_REC_LEN 0x8000 // the buffer size for storing RLE compressed button input (x each button)
/******************************************************************************/
typedef struct
{
unsigned short *rledata;
unsigned short rlepos;
unsigned short datalen;
unsigned short currentrun;
} ButtonRec;
/******************************************************************************/
// if INPUTREPLAY_CAN_RECORD, as soon as this class is instanced, it will automatically record when instanced/created.
// statically creating this as a global will blanket the entire play session
//
// if INPUTREPLAY_CAN_PLAYBACK, playback will begin as soon as LoadFile() is used
//
class SimpleInputRec
{
unsigned int m_buttonstate;
ButtonRec m_buttons[INPUT_BUTTONS_TOTAL];
bool m_bRecording;
unsigned char* m_data;
public:
SimpleInputRec()
: m_buttonstate(0)
, m_data(NULL)
, m_bRecording(true)
{
}
~SimpleInputRec()
{
if(m_data)
{
#if INPUTREPLAY_CAN_RECORD
WriteToFile();
#endif
delete[] m_data;
}
}
// run each frame before the game uses the live button input.
// when recording, it saves the live input
// during playback, it overwrites the live input
void Update(bool bForce = false);
// to start a playback
#if INPUTREPLAY_CAN_PLAYBACK
bool LoadFile(KSTR szfilename);
#endif
// to finish recording
#if INPUTREPLAY_CAN_RECORD
void WriteToFile();
#endif
};
/******************************************************************************/
void SimpleInputRec::Update(bool bForce)
{
#if INPUTREPLAY_CAN_RECORD
if(m_bRecording)
{
unsigned int newbuttons = nesinput.buttons;
// allocate and initialize
if(!m_data)
{
m_data = new unsigned char[INPUT_BUTTONS_TOTAL * MAX_REC_LEN * 2];
unsigned short* dataptr = (unsigned short*)m_data;
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.rledata = dataptr;
dataptr += MAX_REC_LEN;
btn.rlepos = 0;
btn.currentrun = 0;
btn.datalen = MAX_REC_LEN;
}
}
// write RLE button bit streams
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
if(bForce || (newbuttons&(1<<i)) != (m_buttonstate&(1<<i)) || btn.currentrun==0x7FFF)
{
if(btn.currentrun)
{
int bit = (m_buttonstate>>i)&1;
btn.rledata[btn.rlepos++] = (bit<<15) | btn.currentrun;
}
btn.currentrun = bForce? 0 : 1;
}
else
{
++btn.currentrun;
}
}
m_buttonstate = newbuttons;
}
#endif
#if INPUTREPLAY_CAN_PLAYBACK
if(!m_bRecording)
{
bool bIsRunning = false;
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
if(btn.rledata)
{
bIsRunning = true;
if(!btn.currentrun && btn.rlepos<btn.datalen)
{
unsigned short value = btn.rledata[btn.rlepos++];
btn.currentrun = value&0x7FFF;
m_buttonstate &= ~(1<<i);
m_buttonstate |= ((value>>15)&1)<<i;
--btn.currentrun;
}
else
{
if(btn.currentrun)
{
--btn.currentrun;
}
else if(btn.rlepos==btn.datalen)
{
btn.rledata = NULL;
}
}
}
}
if(bIsRunning)
{
// TODO: this is where you can overwrite the live button state to the prerecorded one
systeminput.buttons = m_buttonstate;
}
}
#endif
}
/******************************************************************************/
#if INPUTREPLAY_CAN_PLAYBACK
bool SimpleInputRec::LoadFile(KSTR szfilename)
{
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.datalen = 0;
btn.rledata = NULL;
btn.rlepos = 0;
btn.currentrun = 0;
}
delete[] m_data;
m_bRecording = false;
if(fcheckexists(szfilename))
{
FILE* f = fopen(szfilename, "wb");
if(f)
{
// WARNING: You'll want to do more error checking, but just to keep it simple...
fseek(f,0,SEEK_END);
unsigned long filelen = ftell(f);
fseek(f,0,SEEK_SET);
m_data = new unsigned char[filelen];
fread(m_data, 1, filelen, f);
fclose(f);
unsigned char* bufptr = m_data;
int numbuttons = bufptr[0] | (bufptr[1]<<8);
bufptr += 2;
if(numbuttons <= INPUT_BUTTONS_TOTAL)
{
for(int i=0; i<numbuttons; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.datalen = bufptr[0] | (bufptr[1]<<8);
bufptr += 2;
}
for(int i=0; i<numbuttons; ++i)
{
ButtonRec& btn = m_buttons[i];
if(btn.datalen)
{
// WARNING: Endian dependent for simplcicity
btn.rledata = (unsigned short*)bufptr;
bufptr += btn.datalen*2;
}
}
}
return true;
}
}
return false;
}
#endif
/******************************************************************************/
#if INPUTREPLAY_CAN_RECORD
void SimpleInputRec::WriteToFile()
{
if(m_data && m_bRecording)
{
Update(true);
FILE* f = fopen("_autorec.rec","wb");
if(f)
{
fputc(INPUT_BUTTONS_TOTAL, f);
fputc(0, f);
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
fputc((unsigned char)btn.rlepos, f);
fputc(btn.rlepos >> 8, f);
}
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
// WARNING: Endian dependent for simplcicity
fwrite(btn.rledata, 2, btn.rlepos, f);
}
fclose(f);
}
}
}
#endif
/******************************************************************************/
#endif // INPUTREPLAY_INCLUDED
</code>

@ -1,36 +0,0 @@
<!--200918-->
<h1>my favorite GDC talks: Empathizing with Steam: How People Shop for Your Game by Chris Zukowski (2020) </h1>
september 18, 2020<br>
marketing, sale, steam, wishlist<br>
<br>
<a href="https://www.youtube.com/watch?v=UJiv14uPOac">Empathizing with Steam: How People Shop for Your Game by Chris Zukowski (2020)</a> - tips for how to design your Steam store page based on Zukowski's screenshare and shopping diary observations of ordinary people shopping on Steam
<ul>
<li>Essentially, make your gameplay genre absolutely clear within the first 4 screenshots and in the short description so that people will wishlist your game to buy during a seasonal Steam Sale.</li>
<li>Approach your wishlisters as complete newcomers. Jazz up your Steam page before a Steam Sale. Release an update, post in your forums, put a Santa hat on your character. When wishlisters return to your page, they will see an active game and be sold on it all over again.</li>
<li>His conclusions are very similar to how I shop on Steam, except I could care less for the tag section.</li>
<li>first 4 images are shown when hovering over thumbnail in Steam. Make them represent the pillars of your gameplay, so the genre is clear.</li>
<li>don't try to coerce new audiences into trying your game. try to find your audience and show them exactly what they are looking for.
<li>include UI in screenshots, so gamers can decipher the genre and some gameplay mechanics</li>
<li>Gamers naturally compare new games with the leading games of their genres to decide whether they will enjoy it.</li>
<li>indie romance authors (who compete on a market of 8 million vs. steam's 40 thousand) use clear visual language in their coverart: tartan kilt + sword + distant castle = highland romance; animal behind a hunk = shapeshifter romance; woman in front of group of hunks = reverse harem. So a reader who sees a cover with multiple guys and a wolf, she knows it's a reverse harem of shape-shifting wolf boys without any descriptions or trailers.</li>
<li>applying the wisdom of the romance authors, understand your audience's genre interests as sub-sub-sub-genres and make your genre crystal clear in your store page.</li>
<li>Apply the tropes of your genre. The FPS coverart guy with the gun on all CoD and BF games, the two Street Fighter healthbars on the top of any fighter game</li>
<li>Part 4 - How to manage a Steam sale</li>
<li>People check their wishlist during sales, looking for discounts.</li>
<li>People don't remember why they wishlisted games, so your page must look fresh even to your wishlist crew. Before a sale, post an update, theme your capsule image, post an announcement, upload a small patch, and comment in the forums.</li>
<li>People remember games that are always on sale. Frequent sales increase familiarity.</li>
<li>Power users use gg.deals or steamdb to track historic lows. Strategically staircase your way down over time. Coincide your all-time-lows with Big Steam Sales. Harness the fear of missing out.</li>
<li>How to get from wishlist to cart</li>
<li>Before clicking 'buy,' they check developer and publisher for someone they recognize or trust. They check 'more from this studio' to check familiarity with the dev's other games.</li>
<li>Release more games to build this trust and familiarity.</li>
<li>They check the 'relevant to you' bar to see if the genre matches games they've played and that friends with similar taste also like the game.</li>
<li>How to manage your sales pricing</li>
<li>How to get from cart to library</li>
<li>After loading cart, they ask their friends whether they should go for it.</li>
<li>Your current customers have to become ambassadors to complete this purchase loop. Treat them well! </li>
<li>Abandon cart recovery in other online retailers beckons shoppers back before the sale ends. Steam lacks this, potentially losing devs 10% in revenue - https://www.annexcloud.com/blog/31-shopping-cart-abandonment-statistics-for-2018/</li>
<li>Do a second marketing push during the last day of a sale.</li>
<li>5 - How to wishlisters into buyers</li>
<li></li>
</ul>
<br>

@ -1,546 +0,0 @@
abyssal whip
adamantine
airship
alchemy
aloe
alpine
amber fossil
ambrosia
amulet
anachronism
anagram
animal parade
aphrodisiac
apple
apothecary
apricot
asterism
atelier
aura
baby's breath
backhoe
bad cat
Baldur
bamboo
bandit camp
bandit king
basil
bell tower
birthday dress
birthday girl
birthstone
black bear
black market
The Black Hand
black knight
black water
black widow
blackguard
bloodstone
blue and yellow
blue bell
blue blood
blue rose
bluebell
bonfire
bossa nova
box garden
breath of life
bubble bath
bubble tea
bunny boy
buried treasure
butter cake
butterfly woman
café
cambion
camel
candlelight
candy store
caravan
cashmere
castle
cat café
cat familiar
cat man
catnip
catacombs
cathedral
celestial
cemetery
centaur
chained to rocks
chain mail
chalcedony
chameleon
chamomile
chandelier
changeling
chant
charmeuse
cheese bread
chiffon
chimera
chives
chocolate factory
chronomancer
chrysanthemum
cipher
citadel
citrine
city of the dead
clipart
clover
cloud gray
cobweb
coin flip
cold iron
confetti
constellation
cookie cutter
copycat
coral
cotton
couture
cove
crab
crazy quilt
Crossroad Keep
crucibled
crystal sea
cucumber
daeva
dark horse
dark hour
dark paradise
death's-head hawkmoth
deep space
denarius
dhampir
diadem
Diana
diorama
dimetrodon
dire wolf
dollhouse
donkey
doppelgänger
double life
dragonfruit
dragonstone
dreamweaver
dryad
dump truck
Dymer
dystopia
ectoplasm
edelweiss
egg hunt
El Dorado
elemental
elf
elipse
elixir of life
emberlight
Entrana
eventide
epitaph
Epsom salt
eternal youth
ettin
evil twin
fable
fairy lights
fairy ring
falling leaves
faun
favored soul
feather
femme covert
femme fatale
fiery sword
fire ball
fire opal
firebrand
fishing village
floating
flower girl
flying
folklore
fountain of youth
forge
fortress
freezing fog
fringe
frog prince
fullness of time
gallows
Garden of Eden
gargoyle
genie
geode
ghost orchid
ghost ship
ghost town
gingham
girl in the internet
girl in white
glow stick
glow-in-the-dark stars
goblin
gold rush
gold standard
golden apple
golden orb weaver
golem
Grand Canyon
gray weather
griffon
grove
guild
guilty pleasure
hall of monuments
halo
hand of God
The Hanged Man
hanging garden
happy place
haunted
haunted house
headscarf
heart
hearth goddess
heather
hermit
hermit crab
hobgoblin
holly
holy grail
honeycomb
honeydew
honeysuckle
horizon
hot air balloon
hot and cold game
hot cocoa
hydrangea
igari makeup
illuminated text
imp
incense
invisible man
iridescence
iris
ironwood
ivory
jack-o'-lantern
jacquard
jade chain
jasmine
jellyfish
juicebox
kaleidoscope
kesi silk
kigurumi
kingdom
koi pond
La Belle Sans Merci
labyrinth
lady cat
Lady Luck
Lady of Shallot
Lady of the Lake
lady in black
lake of fire
lair
lamentation
lavender
lemonade
licorice
life-giver
lighthouse
lily
lip gloss
lip oil
litany
Little Red Riding Hood
lizardman
lobster
locket
Lokasenna
loner
loom
lost city
lost continent
lost soul
lotus
love letter
love potion
low poly
lullaby
lunar eclipse
lust
macramé
mages guild
magic carpet
malachite
man in the moon
mandrake
manna
marine
Mars
mask
masquerade
mead of poetry
mean girl
melon soda
memento mori
memoir
mercury rivers and lakes
metamorphosis
meteorite
metropolis
miasma
Midas Touch
Milky Way
millennial moon
mind game
miniature
mink
mirage
mire
mirror of Venus
mirror on the wall
mithril
monastery
money tree
monk's hood
monkey and bear
moon rock
moonflower
moonglow
moonstone
morning glory
mossy cobblestone
moth
mudskipper
mural
muse
naga
necromancer
nectar
Nephilim
nightlight
nightmare
nocturnality
nymph
oneiromancer
oblivion
odalisque
olive
omen
opal
orb
orchid
orichalcum
orphan
painted lady
palace
panda eyes
paper crane
paper fan
paper lantern
paragon
pearl
periwinkle
personality test
phantom residue
phantom thief
pharisee
philosopher's stone
pickpocketing
pilgrimage
pixie stick
pinwheel
planetary weather
plastic vampire fangs
plushie
poetry and prose
pomegranate
poppy
post-rock
pretzel
princess of Kentucky
principality
prism
prison planet
poinsettia
popcorn
porcelain
propaganda
prophecy
proverb
psalm
pseudonym
pumpkin carving
qirin
quiz show
rainbow
rainy day
raw honey
reaper
reclining woman
red
reverse trap
road roller
rogue
rooibos tea
rose gold
rose petal
rose quartz
rosemary
runestone
s'more
sailor fuku
Sanctum
sandalwood
sandcastle
sandman
sandstone
sanguine, my brother
sardonic
satire
satyr
scone
scrying pool
scythe
sea foam
sea glass
sea of stars
sea storm
seastrand
secret garden
secret passageway
secret room
seven deadly sins
seven heavenly virtues
shapeshifter
sheep
shipwreck
shoegaze
shooting star
shoujo manga
silk
silk embroidery
singing sword
skeleton
skipping stone
slide puzzle
slime
smartphone
smoke and mirrors
smoothie
snow globe
soldier of fortune
soma
soulmate
space cowboy
spooky chews
squid
spellbook
spelunking
spice cookie
spiderweb
spire
spirit animal
spy
stained-glass
star sapphire
stardust
stargazer
starry sky
statuesque
sticker book
stinky cat
stone circle
stone face
stone people
stonewash
storm cloud
strawberries and cream
strawberry blonde
strawberry shortcake
streusel
sunflower
swamp light
swan song
sword in the stone
taffy
talking forest
tatting
tea garden
tears of Guthix
terrarium
three women
tide pool
tiger's eye
time traveler
toasted marshmallow
topaz
topiary
toy kingdom
treasure trails
tree of life
treehouse
trick or treat
trickster
tulip
tungsten
underwater
underworld
unicorn horn
unstoppable mailman
urban legend
utopia
vampire
Venice
vanity of vanities
Venus
waiting room
waking dream
walled city
wallflower
wampus cat
wandering eye
wanderlust
wasp lady
watchmen on the wall
water bearer
water nymph
waterfall room
waterlily
weeping willow
Wheel of Fortune
Whip-Poor-Will
whisper
white Christmas
will-o'-wisp
wind chimes
wings
wishing well
wisteria
witchhouse
witching hour
wizard tower
wolf's-bane
woman and serpent
the woodsman
workshop
wormwood
wreath
wyvern
yak
Yggdrasil
Ys
zealot
zoo

@ -1,22 +0,0 @@
Hello!
I'm chimchooree, the dev behind the Dungeon Girls RPG. Right now, it's just me handling the programming, pixel art, animations, and story-writing during my free-time.
What's Dungeon Girls?
Dungeon Girls - Active development Dungeon Girls is an after-school RPG that encourages experimenting with skills and making your class fit your play-style. Play smartphone apps while your friends gear up at the shopping center before jumping into the local dungeon.
Gameplay - You will make AI companions and rely on managing your team's skillsets to find creative strategies for combat and environmental puzzle solving. Each character's skillbar has limited slots and can only be modified in safe areas. You will build your skillbar based on the enemies' weaknesses, party needs, and skill synergies. Skills are not only useful in combat. Use skills to gain deeper interactions with NPCs, explore the map, and craft useful items.
When you're not fighting, hang out with friends, keep up with schoolwork, and uncover the mystery of the town.
Explain the Tiers
My tiers are simple and help keep the game development flowing. Documenting, reflecting, and planning is part of my natural process. It helps keep my vision consistent. I hope you appreciate the behind-the-scenes look.
Visitor - Everyone can read my blog and see pictures I upload.
$1 Fireball - Gain access to voting in polls.
$5 Alchemy - Receive a monthly micro webzine of my progress, whether it's in coding, art, animation, or planning. I already keep a daily diary on my computer, so I will edit for interest and make it a little pretty for you.
All patrons can participate in closed and open betas during beta periods. I need lots of different kinds of people to test the game. Contact me with bugs and if any part of the game feels awkward, ugly, or confusing. You'll help me polish the game. If you have any ideas, suggestions, or feedback, I'd love to hear it!
All patrons receive a game key on the platform of their choice whenever I release a game.
What's Patreon?
Patreon manages monthly payments in exchange for perks. If you're looking for a one-time tip jar, try https://ko-fi.com/chimchooree.
Have a nice day!

@ -1,36 +0,0 @@
<!---->
<h1>reading journal </h1>
january 8, 2020<br>
<br>
<br>
I'll collect summaries, thoughts, and excerpts as I read. I used to read so much more. I should read more. <br>
<br>
<b>reading now</b> <br>
<ul>
<li><b>The Goddess of Atvatabar: being the history of the discovery of the interior world, and conquest of Atvatabar</b> - William R. Bradshaw (1892) </li>
<li><b>An American Tragedy</b> - Theodore Dreiser (1925) </li>
<li><b>How Few Remain</b> - Harry Turtledove (1997) </li>
</ul>
<br>
<br>
<b>books to read next</b> <br>
<ul>
<li><b>The Divine Comedy</b> - Dante Alighieri (1320) </li>
<li><b>The Pilgrim's Progress from This World, to That Which Is to Come</b> - John Bunyan (1678) </li>
<li><b>The Faerie Queen</b> - Edmund Spenser (1590) </li>
<li><b>Waiting for Godot</b> - Samuel Beckett (1953) </li>
<li><b>Metamorphoses</b> - Ovid (8) </li>
<li><b>The Canterbury Tales</b> - Geoffrey Chaucer (1320) </li>
<li><b>Phantastes</b> - George MacDonald (1858) </li>
<li><b>The Lady of Shalott</b> - Alfred Tennyson (1833, 1842) </li>
</ul>
<br>
<br>
<b>completed books</b> <br>
<ul>
<li><b>Candide</b> - Voltaire (1759) </li>
<li><b>Drinker of Souls</b> - Jo Clayton (1986) </li>
<li><b>Making of a Bigot</b> - Rose Macaulay (1914) </li>
<li><b>Magic: A Fantastic Comedy in a Prelude and Three Acts</b> - G.K. Chesterton (1913) </li>
</ul>
<br>

@ -1,65 +0,0 @@
<!--210201-->
<h1>March for Trump </h1>
january 6, 2020<br>
<br>
<br>
The March for Trump interrupted Congress during the joint session to certify the Electoral College vote. It looks like a Far Cry game. All photos are stolen from Twitter, so sorry for inconsistent credits + quality. <br>
<br>
<center><a target="_blank" href="/static/img/ext/stealthevote_18.png">
<img src="/static/img/ext/stealthevote_18.png" alt="(image: pro-Trump crowds gather around Capitol Hill)" width="500" height="371.35">
</a></center>pro-Trump crowds gather around Capitol Hill <br>credit: Anadolu Agency, Getty Images <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_16.jpeg">
<img src="/static/img/ext/stealthevote_16.jpeg" alt="(image: civilians scale Capitol Hill)" width="500" height="333.25">
</a></center>civilians scale Capitol Hill <br>credit: Stephanie Keith, REUTERS <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_2.jpeg">
<img src="/static/img/ext/stealthevote_2.jpeg" alt="(image: civilians scale Capitol Hill)" width="500" height="667">
</a></center>civilians scale Capitol Hill <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_20.png">
<img src="/static/img/ext/stealthevote_20.png" alt="(image: civilians clash with D.C. police)" width="500" height="333">
</a></center>civilians clash with D.C. police <br>credit: Julio Cortez, AP <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_1.jpeg">
<img src="/static/img/ext/stealthevote_1.jpeg" alt="(image: civilians clash with D.C. police)" width="500" height="333.25">
</a></center>civilians clash with D.C. police <br>credit: Julio Cortez, AP <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_4.jpeg">
<img src="/static/img/ext/stealthevote_4.jpeg" alt="(image: civilian dangles from Senate Chamber)" width="500" height="333.44">
</a></center>civilian dangles from Senate Chamber <br>credit: Win McNamee, Getty Images <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_17.jpg">
<img src="/static/img/ext/stealthevote_17.jpg" alt="(image: protestors breach the Capitol Building)" width="500" height="333.57">
</a></center>civilians confront Capitol security <br>credit: Manuel Balce Ceneta, AP <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_10.jpeg">
<img src="/static/img/ext/stealthevote_10.jpeg" alt="(image: civilian clashes with Capitol security)" width="500" height="358.09">
</a></center>civilian clashes with Capitol security <br>credit: Kevin Dietsch / Pool, EPA <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_7.png">
<img src="/static/img/ext/stealthevote_7.png" alt="(image: representatives take cover in the Capitol)" width="500" height="333.33">
</a></center>representatives take cover in the Capitol <br>credit: Tom Williams / CQ-Roll Call, Inc, Getty Images <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_3.jpeg">
<img src="/static/img/ext/stealthevote_3.jpeg" alt="(image: armed standoff with police as civilians break into House Chamber)" width="500" height="333">
</a></center>armed standoff with police as civilians break into the House Chamber <br>credit: J. Scott Applewhite, AP <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_11.png">
<img src="/static/img/ext/stealthevote_11.png" alt="(image: Capitol Building in disarray during breach)" width="500" height="667">
</a></center>Capitol Building in disarray during breach <br>credit: Olivier Douliery, AFP <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_12.png">
<img src="/static/img/ext/stealthevote_12.png" alt="(image: Capitol Building in disarray during breach)" width="500" height="667">
</a></center>Capitol Building in disarray during breach <br>credit: Olivier Douliery, AFP <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_9.png">
<img src="/static/img/ext/stealthevote_9.png" alt="(image: police munition explosion on Capitol Hill)" width="500" height="333.25">
</a></center>police munition explosion on Capitol Hill <br>credit: Evelyn Hockstein, The Washington Post <br>
<br><br>
<center><a target="_blank" href="/static/img/ext/stealthevote_8.png">
<img src="/static/img/ext/stealthevote_8.png" alt="(image: Capitol Hill engulfed in tear gas and smoke)" width="500" height="333.25">
</a></center>Capitol Hill engulfed in tear gas and smoke <br>credit: Leah Millis, REUTERS <br>
<br>
last updated 2/21/21 <br>
<br>

@ -1,90 +0,0 @@
<del>player character</del>
<strike>text read from CSV</strike>
<s>branching dialog</s>
<span class="strike">save/load</span>
remappable hotkeys
<del>click+drag skills for skillbar</del>
<del>skill phases</del>
<del>attack loop</del>
<del>AI changes states</del>
<del>HP,MP,XP,enemy,skill monitor UI</del>
equip items
equipment mods stats
<del>item pickup</del>
<del>item drop</del>
<del>item usage</del>
containers
<del>doors between zones</del>
<del>variables in dialog</del>
<del>usable items</del>
<del>xp upon killing</del>
<del>learn skills</del>
classes have attributes
attributes affect skill power
<del>debug messages</del>
console commands
<del>loot drops</del>
decent character animation
<del>fog of war</del>
<del>kickable objects</del>
<del>context menu</del>
<del>sound UI</del>
<del>death</del>
resurrection
<del>level system</del>
in-game credits
<del>config file</del>
decent object highlight
decent map art (buffer)
decent map art (slime kingdom)
decent map art (school)
<del>buy items</del>
sell items
decent shop art
<del>click-to-move</del>
<del>WASD movement</del>
<del>choose target</del>
<del>BGM support</del>
<del>move to target</del>
<del>attacking AI</del>
<del>skill usage AI</del>
patrol AI
CG
Rune + Bless boss AI
rez shrine
quest app
menu apps
decent phone UI animation
blessfrey logo
blessfrey keyart
animation (UI)
SFX (character)
SFX (UI)
SFX (environment)
custom music
rearrange UI
<del>events + achievement support</del>
resizable fonts
<del>item shop restocks</del>
<del>item shop random stock</del>
15 skills per class
9 classes
status effects
class changes
<del>skill keyword effects</del>
projectiles
impassible walls
permeable walls
<del>activators</del>
<del>skill tooltips</del>
<del>use skill via key/click</del>
<del>HP/MP regen/degen</del>
<del>name the PC</del>
character emotes
rich text with CSV
decent character portraits
decent UI art
teams of characters
flocking AI
update highlight with name change
highlight shows player's display name, not tl'd key

@ -1,11 +0,0 @@
manage team
manage teammates (inv)
manage teammates (skills)
manage teammates (class)
manage teammates (equipment)
gifts
followers
40 skills per class
lots of minigames
equipment changes character appearance
end game credits

@ -1,72 +0,0 @@
<!---->
<h1>to do </h1>
january 30, 2020<br>
<br>
<br>
A to-do list I can see anywhere with Wi-Fi. <br>
<br>
<h2>UI </h2>
<ul>
<li>Get close to something that does something - it is highlighted. Character, floor item, activator, door. </li>
<li>Adjust layer for all canvas layers </li>
<li>Can click & drag UI elements - Phone, Skill Library, Bingo Card, etc. </li>
<li>Can modify and save/load/default all UI elements </li>
</ul>
<br>
<h2>Skill </h2>
<ul>
<li>Description values update with stat changes </li>
</ul>
<br>
<h2>Tools </h2>
<ul>
<li>Match Altar of Spellmaking output to current refactored skill script/scenes </li>
</ul>
<br>
<h2>Codes </h2>
<ul>
<li>1 - skill </li>
<li>2 - char </li>
<li>3 - item </li>
<li>4 - location </li>
<li>5 - app </li>
</ul>
<br>
<h3>Skill - 0x100f00f00f </h3>
<ul>
<li>1 - skill </li>
<li>00f - profession. Brawler(1) </li>
<li>00f - stat </li>
<li>00f - individual skill </li>
</ul>
<br>
<h3>Char - 0x3f0f00f00f </h3>
<ul>
<li>3 - char </li>
<li>f - category. DG(1), NPC(2), Boss(3) </li>
<li>0f - race. Human(1), Angel(2), Cat(3), Slime(4)</li>
<li>00f - faction. None(0), DG(1) </li>
<li>00f - individual </li>
</ul>
<br>
<h3>Item - 0x31f00f000f </h3>
<ul>
<li>3 - item </li>
<li>1 - essential </li>
<li>f - category. Currency(1), Consumable(2), Equipment(3), Trophy(4), Sign(5), Literature(6) </li>
<li>00f - subcategory </li>
<li>000f - individual </li>
</ul>
<br>
<h3>Loc - 0x41f00f000f </h3>
<ul>
<li>4 - location </li>
<li>1 - interior/exterior </li>
<li>0f - category. Dungeon(1), School(2), Mall(3) </li>
<li>0f - subcategory </li>
<li>000f - individual </li>
</ul>
<br>

@ -1,37 +0,0 @@
draw a home page image
define a color scheme
standardize formatting of articles
proofread all text on website
prewrite articles
plan articles
make a real timestamp for copyright
draw some navbar skill icons
draw a navbar BG
draw backgrounds for homepage sections
choose fonts
thumbnails for projects
'what is blessfrey?' page overhaul
system section
characters section
story section
extras (wallpaper, assets)
cat store page
gamejam page
pretendOS page
blessfrey.me page
make my git look nice
backup everything on git
projects page
diary page
diary snippet background
diary snippet link decoration
diary boxes backgrounds
contact page
pitch writeup
diary nav link decoration
presskit page
presskit download up-to-date
contact page
about me writeup
ideabox page
taskbox page

@ -1,345 +1,6 @@
import datetime, os, re import datetime, os, re
from bottle import error, response, route, run, static_file, template, TEMPLATE_PATH from bottle import error, response, route, run, static_file, template, TEMPLATE_PATH
def clean_tags(raw):
cleanr = re.compile('<.*?>')
cleantext = re.sub(cleanr, '', raw)
return cleantext
# rss feed
# RSS Generation
def make_rss():
loc = 'diary/entries/'
info = {'items': list_items(gather_and_sort(loc)[0:15])}
# Delete old version
def clear_file(f_name):
if os.path.exists(f_name):
print("removing " + f_name)
os.remove(f_name)
f = open(f_name, 'a+')
def format_rss_time(date):
return datetime.datetime.strptime(date, '%y%m%d').strftime('%a') + ', ' + datetime.datetime.strptime(date, '%y%m%d').strftime('%d %b %Y') + " 05:00:05 GMT"
# Return list of items using list of articles
def list_items(articles):
f_name = "static/xml/blessfrey.xml"
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>" + loc2 + 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>" + loc2 + r[1] + "</guid>" + '\n')
f.write("</item>" + '\n')
f.write("</channel>" + '\n')
f.write("</rss>" + '\n')
f.close()
return result
# recommendations
def list_rec(page):
loc = 'diary/entries/'
result = []
rec = []
comment = ""
if isinstance(page, int):
# Collect recommended articles from comment line
with open('diary/entries/' + str(page)) as f:
comment = f.readline()
comment = comment.replace('<!--','')
comment = comment.replace('-->','')
comment = comment.replace(' ','')
comment = clean(comment)
rec = comment.split(',')
# Convert into array for template to display
for article in rec:
if is_it_time(article):
path = loc + article
data = []
try:
with open(path) as f:
f.readline()
data.append(clean(f.readline().replace('<br>','')))
data.append(path)
result.append(data)
except EnvironmentError:
print("No article @ " + path)
return result
# List latest 5 articles as headline links
def list_headlines(articles):
loc = 'diary/entries/'
result = []
text = []
for article in articles:
path = loc + article
b = []
b.append(path)
with open(path) as f:
f.readline()
text = f.readline()
b.append(clean(text.replace('<br>','')))
result.append(b)
return result
def find_tags(text):
new = text[3].replace('<br>','')
new = new.replace('\n','')
new = new.split(" ")
final = []
for n in new:
if len(n) <= 0:
new.remove(n)
if '#' in n:
final.append(n)
final.sort()
return final
# Return title of article, formatted for sharing via social media
def find_social_title(text):
return clean(text[1]).replace(' ','+')
# Return URL of article
def find_url(path):
return '/' + path.replace('.tpl','')
# Return clean timestamp
def find_timestamp(text):
return text[2].replace('<br>','')
# Return clean title
def find_title(text):
return clean(text[1])
# Return article as list of lines of text
def article2list(article, loc):
text = []
with open(loc + article) as f:
text = f.readlines()
return text
def retrieve_article(page, loc):
text = []
string = ""
with open(loc + str(page)) as f:
text = f.readlines()
for line in text:
string += line
return string
def retrieve_diary_entry_content(page,loc):
text = []
string = ""
with open(loc + str(page)) as f:
lines = f.readlines()
for line in lines:
if lines.index(line) >= 4:
string += line
return string
def prepare_diary_entry(page, loc):
result = []
with open(loc + str(page)) as f:
text = []
text = article2list(str(page), loc)
result.append(find_title(text))
result.append(retrieve_diary_entry_content(page, loc))
result.append(find_timestamp(text))
result.append(find_url(loc + str(page)))
result.append(find_social_title(text))
result.append(find_tags(text))
return result
# Return list of snippets using list of articles
def list_snippets(articles):
loc = 'diary/entries/'
limit = 4
total = len(articles)
result = []
for article in articles:
path = loc + article
text = []
a = []
length = 0
text = article2list(article, loc)
a.append(find_title(text))
a.append(prepare_article(text, path))
a.append(find_timestamp(text))
a.append(find_url(path))
a.append(find_social_title(text))
a.append(find_tags(text))
result.append(a)
return result
# Return list of files with given tag
def pull_tag(files, tag):
pull = []
for f in files:
tags = find_tags(article2list(str(f), 'diary/entries/'))
if "#" + tag in tags:
pull.append(f)
pull.sort(reverse=True)
return pull
# Return line count of file
def count_lines(fname):
with open(fname) as f:
for linenum, line in enumerate(f,1):
pass
return linenum
# Return article text without HTML header
def find_content(text):
length = len(text)
content = ""
# form a string from relevant lines of the article
pos = 0
for line in text:
# skip to line 5
if pos > 4 and pos < length:
content += line
pos += 1
return content
# Snip article and close any open list tags
def prepare_rss_summary(text, path):
content = snip_sentence(find_content(text), path)
if content.count('<ul>') > content.count('</ul>'):
content += '</ul>'
return content
# Snip article and close any open list tags
def prepare_article(text, path):
content = snip_article(find_content(text), path)
if content.count('<ul>') > content.count('</ul>'):
content += '</ul>'
return content
# Remove links, line breaks from snippet
def clean(result):
result = result.replace('\n','')
result = result.replace('<br>','')
result = re.sub(r'<a href=.*?>', '', result)
result = re.sub(r'<img src=.*?>', '', result)
result = re.sub(r'<a target="_blank" href=.*?>', '', result)
result = result.replace('</a>','')
result = re.sub(r'<h\d>','',result)
result = re.sub(r'</h\d>','',result)
result = result.replace('<center>','')
result = result.replace('</center>','')
result = result.replace('<b>','')
result = result.replace('</b>','')
return result
# Return first two sentences of article + " ... "
def snip_sentence(article, path):
article = clean(article)
limit = 100
result = article[0:min(len(article),limit)]
result = result.rsplit(' ',1)[0]
return result + " ... "
# Return first 300 words of article + " ... "
def snip_article(article, path):
article = clean(article)
limit = 300
result = article[0:min(len(article),limit)]
result = result.rsplit(' ',1)[0]
return result + " ... "
# Sort diary - newest to oldest
def sort_files(files):
files.sort(reverse=True)
return files
def curate_files(files):
# remove folders
if 'raw' in files:
files.remove('raw')
if 'extra' in files:
files.remove('extra')
# remove
clean = []
for f in files:
if is_it_time(f):
clean.append(f)
return clean
def is_it_time(date):
today = datetime.datetime.now()
today_string = today.strftime("%y") + today.strftime("%m") + today.strftime("%d")
return int(date) <= int(today_string)
# Return list of all diary entries (exclude raws + extras)
def gather_files(loc):
files = os.listdir(loc)
return files
def gather_and_sort(loc):
return sort_files(curate_files(gather_files(loc)))
def fill_box(new_file):
box = []
with open(new_file) as f:
for line in f:
box.append(line)
box.sort()
return box
# return list of diary entry tags, sorted by frequency
def fill_word_cloud(files):
tags = []
for f in files:
temp = find_tags(article2list(str(f), 'diary/entries/'))
for t in temp:
tags.append(t)
tags.sort()
cloud = []
i = 0
while i < 24:
if len(tags) > 0:
top = max(set(tags), key = tags.count)
cloud.append(top)
tags[:] = [x for x in tags if x != top]
i += 1
return cloud
def find_year(): def find_year():
now = datetime.datetime.now() now = datetime.datetime.now()
@ -357,13 +18,8 @@ def serve_css(filename):
def serve_img(filename): def serve_img(filename):
return static_file(filename, root='static/img') return static_file(filename, root='static/img')
# Serve unlisted articles
@route('/static/extra/<filename:re:.*\.cpp>')
def serve_extra(filename):
return static_file(filename, root='static/extra', mimetype='text/plain', download=True)
# Serve XML # Serve XML
@route('/static/xml/<filename:path>')#re:.*\.xml>') @route('/static/xml/<filename:path>')
def serve_xml(filename): def serve_xml(filename):
return static_file(filename, root='static/xml', mimetype='text/xml') return static_file(filename, root='static/xml', mimetype='text/xml')
@ -380,127 +36,14 @@ def error500(error):
def error502(error): def error502(error):
return "unfortunately, a 502 error. this was likely due to website maintenance. usually it'll be back up before you finish reading this, but otherwise, I'll notice something's wrong soon! <a href=https://www.blessfrey.me/>return to blessfrey.me.</a>" return "unfortunately, a 502 error. this was likely due to website maintenance. usually it'll be back up before you finish reading this, but otherwise, I'll notice something's wrong soon! <a href=https://www.blessfrey.me/>return to blessfrey.me.</a>"
# Downloads
@route('/download/<filename:path>')
def download(filename):
return static_file(filename, root='static/extra', download=filename)
# Home Page - Index Template # Home Page - Index Template
@route('/') @route('/')
def index(): def temp():
"""home page""" """temp"""
loc = 'diary/entries/' info = {'css': 'index', 'title': 'chimchooree\'s dev space - blessfrey', 'year': find_year()}
info = {'css': 'index', 'news': list_headlines(gather_and_sort(loc)[0:10]), 'title': 'chimchooree\'s dev space - blessfrey', 'year': find_year()}
return template('index.tpl', info) return template('index.tpl', info)
# Projects Page - Game Template - system, character, story info
@route('/projects')
def projects():
"""projects page"""
info = {'css': 'projects', 'title': 'chimchooree projects', 'year': find_year()}
return template('projects.tpl', info)
# Presskit Page - Presskit Template - product, developer info
@route('/presskit')
def presskit():
"""press page"""
info = {'css': 'presskit', 'title': 'blessfrey - presskit', 'year': find_year()}
return template('presskit.tpl', info)
# Start on first Diary page if no page given
@route('/diary')
def diary2():
return diary(0)
# Slash is optional
@route('/diary/')
def diary3():
return diary(0)
# Diary Page - Diary Template - list all articles
@route('/diary/<page:int>')
def diary(page):
"""diary page"""
loc = 'diary/entries/'
assert isinstance(page, int)
info = {'css': 'diary', 'title': 'blessfrey - developer diary', 'year': find_year(), 'snippets': list_snippets(gather_and_sort(loc)), 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'total': len(curate_files(gather_files(loc))), 'limit': 8, 'cluster': 3, 'page': page}
return template('diary.tpl', info)
# Entry Page - Feature Template - for articles
@route('/diary/entries/<page:int>')
def entry(page):
"""diary entry"""
if not is_it_time(page):
return error404(404)
loc = 'diary/entries/'
info = {'css': 'feature', 'title': 'blessfrey - developer diary', 'year': find_year(), 'entry': prepare_diary_entry(page, loc), 'recommends': list_rec(page), 'articles': "Articles", 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'page': page}
abs_app_dir_path = os.path.dirname(os.path.realpath(__file__))
abs_views_path = os.path.join(abs_app_dir_path, 'views')
TEMPLATE_PATH.insert(0, abs_views_path )
return template(os.path.join(abs_views_path,'feature.tpl'), info)
# Extra Page - Feature Template - for unlisted articles
@route('/diary/entries/extra/<page>')
def extra(page):
"""diary extra"""
loc = 'diary/entries/extra/'
info = {'css': 'feature', 'title': 'blessfrey - developer diary', 'year': find_year(), 'entry': retrieve_article(page, loc), 'recommends': list_rec(page), 'articles': "Articles", 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'page': page}
abs_app_dir_path = os.path.dirname(os.path.realpath(__file__))
abs_views_path = os.path.join(abs_app_dir_path, 'views')
TEMPLATE_PATH.insert(0, abs_views_path )
return template(os.path.join(abs_views_path,'feature.tpl'), info)
# Start on first Diary tag page if no page given
@route('/diary/tag/<tagin>')
def tag2(tagin):
return tag(tagin, 0)
# Tag Page - Diary Tag Template - list all articles for tag
@route('/diary/tag/<tagin>/<page:int>')
def tag(tagin, page):
"""tag page"""
loc = 'diary/entries/'
assert isinstance(tagin, str)
assert isinstance(page, int)
info = {'css': 'diary', 'title': 'blessfrey - developer diary', 'year': find_year(), 'snippets': list_snippets(pull_tag(gather_and_sort(loc), tagin)), 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'total': len(curate_files(gather_files(loc))), 'limit': 8, 'cluster': 3, 'page': page}
return template('diary.tpl', info)
# Personal Page - Box Template
@route('/box')
def box():
"""personal page"""
info = {'css': 'box', 'title': 'chimchooree\'s personal page', 'year': find_year()}
return template('box.tpl', info)
# Credits Page - Credits Template
@route('/credits')
def credits():
"""credits page"""
info = {'css': 'contact', 'title': 'blessfrey - credits', 'year': find_year()}
return template('credits.tpl', info)
# Contact Page - Contact Template
@route('/contact')
def contact():
"""contact page"""
info = {'css': 'contact', 'title': 'blessfrey - contact chimchooree', 'year': find_year()}
return template('contact.tpl', info)
# Idea Box Page - Box Template
@route('/ideabox')
def ideabox():
"""idea box page"""
info = {'css': 'box', 'title': 'blessfrey - idea box - a collection of inspiring concepts', 'words': fill_box('diary/entries/extra/ideabox'), 'limit': 5, 'year': find_year()}
return template('ideabox.tpl', info)
# Task Box Page - Box Template
@route('/taskbox')
def taskbox():
"""task box page"""
info = {'css': 'box', 'title': 'blessfrey - task box - everything needed to complete blessfrey', 'game_words': fill_box('diary/entries/extra/taskbox'), 'web_words': fill_box('diary/entries/extra/websitebox'), 'limit': 5, 'year': find_year()}
return template('taskbox.tpl', info)
## Main ## ## Main ##
if __name__ == '__main__': if __name__ == '__main__':
make_rss()
run(host='127.0.0.1', port=9001) run(host='127.0.0.1', port=9001)

@ -1,122 +0,0 @@
* {
font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif;
}
a {
text-decoration: none;
}
a:link {
color: red;
}
a:visited {
color: hotpink;
}
a:hover {
color: green;
}
a:active {
color: blue;
}
mark {
background-color: #900C3F;
color: hotpink;
}
ul { list-style-position: inside; }
.strike {
text-decoration: line-through;
}
.grid {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px auto;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
body {
background-color: #e5c5d1;
color: #900C3F;
}
.whitespace { grid-area: 1 / 1 / 2 / 4; }
.blessfrey-logo { grid-area: 1 / 2 / 2 / 3; }
.body-row { grid-area: 3 / 1 / 4 / 4; }
.content { grid-area: 3 / 2 / 4 / 3; }
.nav-row { grid-area: 2 / 1 / 3 / 4; }
.nav-grid {
grid-area: 2 / 2 / 3 / 3;
display: flex;
flex-direction:column;
justify-content:flex-end;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
table, th, td {
border: 1px solid #330532;
}
th, td {
padding: 5px;
}
.footer-content {
padding-top: 5px;
text-align: right;
overflow: hidden;
}

@ -1,116 +0,0 @@
* {
padding:0;
margin:0;
font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif;
font-size: 18px;
}
a {
text-decoration: none;
}
a:link {
color: red;
}
a:visited {
color: hotpink;
}
a:hover {
color: green;
}
a:active {
color: blue;
}
mark {
background-color: #900C3F;
color: hotpink;
}
ul { list-style-position: inside; }
h1 { font-size: 30px;}
h2 { font-size: 25px;}
.grid {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px auto;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
body {
background-color: #e5c5d1;
color: #900C3F;
}
.whitespace { grid-area: 1 / 1 / 2 / 4; }
.blessfrey-logo { grid-area: 1 / 2 / 2 / 3; }
.body-row { grid-area: 3 / 1 / 4 / 4; }
.content { grid-area: 3 / 2 / 4 / 3;}
.nav-row { grid-area: 2 / 1 / 3 / 4; }
.nav-grid {
grid-area: 2 / 2 / 3 / 3;
display: flex;
flex-direction:column;
justify-content:flex-end;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
.footer-content {
padding-top: 5px;
text-align: right;
overflow: hidden;
}

@ -1,379 +0,0 @@
* {
padding:0;
margin:0;
font-family: "Ubuntu", "Open Sans", "Calibri", "Arial", sans-serif;
}
a {
text-decoration: none;
}
ul { list-style-position: inside; }
.grid {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px auto;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.whitespace {
grid-area: 1 / 1 / 2 / 4;
display: flex;
flex-direction:column;
justify-content:center;
align-items: center;
background-color: #900C3F;
}
.blessfrey-logo {
grid-area: 1 / 2 / 2 / 3;
padding-top: 5px;
color: #FBDAEC;
}
/* navigation pane */
.nav-row {
grid-area: 2 / 1 / 3 / 4;
background-color: #900C3F;
}
.nav-grid {
grid-area: 2 / 2 / 3 / 3;
display: flex;
flex-direction:column;
justify-content:flex-end;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
.nav-link a:hover {
font-size: 21px;
color: #ecd5d2;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
/* Body */
.body-row {
grid-area: 3 / 1 / 4 / 4;
background-color: #080410;
}
.content {
grid-area: 3 / 2 / 4 / 3;
display: grid;
grid-template-columns: 2fr 1fr;
grid-template-rows: 40px auto 40px;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.dir-row {
grid-area: 1 / 1 / 2 / 3;
background-color: #080410;
color: #F9B3D7;
}
.diary-dir {
background-color: #080410;
font-size: 23px;
padding: 0px;
margin: 0px;
color: #AAA39D;
font-weight: bold;
text-shadow:
-1px -1px 1px #080410,
0 -1px 1px #080410,
2px -1px 1px #080410,
2px 0 1px #080410,
2px 2px 1px #080410,
0 2px 1px #080410,
-1px 2px 1px #080410,
-1px 0 1px #080410;
}
.diary-dir a {
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px #080410,
0 -1px 1px #080410,
2px -1px 1px #080410,
2px 0 1px #080410,
2px 2px 1px #080410,
0 2px 1px #080410,
-1px 2px 1px #080410,
-1px 0 1px #080410;
}
.diary-dir.top {
grid-area: 1 / 1 / 2 / 3;
padding-top: 3px;
}
.diary-dir-left {
display: inline-block;
vertical-align: middle;
}
.diary-dir-numbers {
height: 29px;
display: inline-block;
white-space: nowrap;
}
.diary-dir-number {
background-color: #404664;
display: inline-block;
white-space: nowrap;
border-radius: 25px;
padding-left: 4px;
padding-right: 4px;
}
.diary-dir-right {
display: inline-block;
vertical-align: middle;
}
.diary-pages {
grid-area: 2 / 1 / 3 / 2;
}
.snippet {
margin-top: 0px;
margin-bottom: 50px;
}
.snippet-title {
background-image: url(../img/ele/diaryheader.png);
background-size: 100%;
height: 40px;
padding: 10px;
font-size: 10px;
}
.snippet-title a {
color: #E3E0DE;
font-weight: bold;
text-shadow:
-1px -1px 1px #324832,
0 -1px 1px #324832,
2px -1px 1px #324832,
2px 0 1px #324832,
2px 2px 1px #324832,
0 2px 1px #324832,
-1px 2px 1px #324832,
-1px 0 1px #324832;
}
.snippet-title a:hover {
color: #ecd5d2;
font-weight: bold;
text-shadow:
-1px -1px 1px #324832,
0 -1px 1px #324832,
2px -1px 1px #324832,
2px 0 1px #324832,
2px 2px 1px #324832,
0 2px 1px #324832,
-1px 2px 1px #324832,
-1px 0 1px #324832;
}
.snippet-content {
background-color: #ecd5d2;
color: #080410;
padding: 10px;
font-size: 15px;
}
.date-line {
background-color: #ecd5d2;
color: #080410;
padding: 5px;
padding-left: 15px;
font-size: 15px;
text-align: left;
}
.snippet-tags {
background-color: #ecd5d2;
padding: 10px;
font-size: 15px;
}
.snippet-tag {
background-color: #b9b4af;
border-radius: 25px;
padding: 3px;
padding-left: 4px;
padding-right: 4px;
color: black;
font-size: 15px;
font-weight: bold;
display: inline-block;
}
.social-line {
background-color: #404664;
padding-top: 5px;
padding-left: 15px;
}
.social-line a {
color: #E3E0DE;
font-weight: bold;
}
.snippet-link {
margin-top: -35px;
float: right;
font-size: 15px;
}
.snippet-bottom {
background-image: url(../img/ele/diarybottom.png);
background-size: 100%;
height: 25px;
}
.sidebar {
grid-area: 2 / 2 / 3 / 3;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
align-content: start;
padding-left: 7px;
}
.sidebar a:link {
color: #213021;
}
.sidebar a:hover {
color: #486438;
}
.sidebar a:visited {
color: #213021;
}
.sidebar a:active {
color: #945634;
}
.about-box {
grid-area: 1 / 1 / 2 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
margin-top: 0px;
padding-bottom: 15px;
}
.twitter-box {
grid-area: 2 / 1 / 3 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
padding-bottom: 15px;
}
.latest {
grid-area: 3 / 1 / 4 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
list-style-position: inside;
margin: 15px;
padding: 5px;
}
.latest-text {
text-align: left;
list-style-position: inside;
padding: 5px;
}
.wordcloud {
grid-area: 4 / 1 / 5 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
padding-bottom: 15px;
}
.disclosure {
grid-area: 5 / 1 / 6 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
padding-bottom: 15px;
}
.diary-dir.bottom {
grid-area: 3 / 1 / 4 / 3;
}
.footer-content {
background-color: #080410;
color: #ecd5d2;
padding-top: 5px;
padding-right: 25px;
box-shadow: 0 100vh 0 100vh #581845;
text-align: right;
overflow: hidden;
}

@ -1,324 +0,0 @@
* {
padding:0;
margin:0;
font-family: "Ubuntu", "Open Sans", "Calibri", "Arial", sans-serif;
}
a {
text-decoration: none;
}
ul,ol { list-style-position: inside; }
.grid {
display: grid;
grid-template-columns: auto 533px 267px auto;
grid-template-rows: 25px 90px repeat(3, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.whitespace {
grid-area: 1 / 1 / 2 / 5;
display: flex;
flex-direction:column;
justify-content:center;
align-items: center;
background-color: #900C3F;
}
.blessfrey-logo {
grid-area: 1 / 2 / 2 / 4;
padding-top: 5px;
color: #FBDAEC;
}
/* navigation pane */
.nav-row {
grid-area: 2 / 1 / 3 / 5;
background-color: #900C3F;
}
.nav-grid {
grid-area: 2 / 2 / 3 / 4;
display: flex;
flex-direction:column;
justify-content:flex-end;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
/* Body */
.body-row {
grid-area: 3 / 1 / 5 / 5;
background-color: #080410;
}
.diary-entry {
grid-area: 3 / 2 / 4 / 3;
margin-top: 40px;
margin-bottom: 30px;
word-wrap: break-word;
}
.diary-title {
background-color: #486438;
border-top-left-radius: 25px;
border-top-right-radius: 25px;
color: #e6e8ef;
padding: 10px;
padding-top: 55px;
padding-bottom: 25px;
font-size: 25px;
text-align: center;
font-weight: bold;
text-shadow:
-1px -1px 1px #324832,
0 -1px 1px #324832,
2px -1px 1px #324832,
2px 0 1px #324832,
2px 2px 1px #324832,
0 2px 1px #324832,
-1px 2px 1px #324832,
-1px 0 1px #324832;
}
.date-line {
padding: 10px;
background-color: #1c2628;
color: #aaa39d;
text-align: center;
}
.diary-content {
background-color: #ecd5d2;
color: #080410;
text-indent: 50px;
padding: 15px;
padding-top: 25px;
}
.diary-content img {
display: block;
margin-top: 25px;
margin-bottom: 0px;
margin-left: 0px;
}
.diary-content h2 {
margin-top: 25px;
margin-bottom: 15px;
text-indent: 0px;
}
.diary-content h3 {
margin-top: 25px;
margin-bottom: 0px;
text-indent: 0px;
}
.diary-content h4 {
margin-top: 25px;
margin-bottom: 0px;
text-indent: 0px;
}
.diary-tags {
background-color: #1c2628;
padding: 10px;
font-size: 15px;
}
.diary-tag {
background-color: #b9b4af;
border-radius: 25px;
padding: 3px;
padding-left: 4px;
padding-right: 4px;
color: #1c2628;
font-size: 15px;
font-weight: bold;
display: inline-block;
}
.share-links {
background-color: #1c2628;
color: #aaa39d;
padding-top: 25px;
padding-bottom: 25px;
text-align: center;
}
.sidebar {
grid-area: 3 / 3 / 5 / 4;
display: grid;
grid-template-columns: auto;
grid-template-rows: repeat(4, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
align-content: start;
padding-left: 7px;
margin-top: 40px;
}
.sidebar a:link {
color: #213021;
}
.sidebar a:hover {
color: #486438;
}
.sidebar a:visited {
color: #213021;
}
.sidebar a:active {
color: #945634;
}
.about-box {
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
margin-top: 0px;
padding-bottom: 15px;
}
.twitter-box {
grid-area: 2 / 1 / 3 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
padding-bottom: 15px;
}
.latest {
grid-area: 3 / 1 / 4 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
list-style-position: inside;
margin: 15px;
padding: 5px;
}
.latest-text {
text-align: left;
list-style-position: inside;
padding: 5px;
}
.disclosure {
grid-area: 4 / 1 / 5 / 2;
border-radius: 25px;
background-color: #95939F;
color: #111718;
text-align: center;
margin: 15px;
padding: 5px;
padding-bottom: 15px;
}
.recommend {
grid-area: 4 / 2 / 5 / 3;
background-color: #080410;
color: #F9B3D7;
display: grid;
grid-template-columns: auto auto auto;
grid-template-rows: auto auto;
grid-column-gap: 0px;
grid-row-gap: 0px;
padding: 5px;
}
.more {
grid-area: 1 / 1 / 2 / 4;
background-color: #486438;
border-radius: 25px;
padding: 5px;
text-align: center;
color: #080410;
}
.rec-box { grid-area: 2 / 2 / 3 / 3; }
.snip {
width: 150px;
float: left;
margin: 30px;
background-color: #95939f;
border-radius: 25px;
padding: 20px;
text-align: center;
font-size: 16px;
}
.snip a {
color: #080410;
}
.snip a:hover {
color: #404664;
}
.snip a:visited {
color: #080410;
}
.snip a:active {
color: #404664;
}
.footer-row {
grid-area: 5 / 1 / 6 / 5;
background-color: #080410;
}
.footer-content {
background-color: #080410;
color: #ecd5d2;
padding-top: 5px;
padding-right: 25px;
box-shadow: 0 100vh 0 100vh #581845;
text-align: right;
overflow: hidden;
}

@ -1,156 +0,0 @@
* {
padding:0;
margin:0;
font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif;
}
a {
text-decoration: none;
}
ul { list-style-position: inside; }
.textbox {
margin: 20px;
padding: 20px;
background-color: #C9C2D6;
color: #080410;
}
.grid {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px 1fr 100px;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.whitespace {
grid-area: 1 / 1 / 2 / 4;
display: flex;
flex-direction:column;
justify-content:center;
align-items: center;
background-color: #900C3F;
}
.blessfrey-logo {
grid-area: 1 / 2 / 2 / 3;
padding-top: 5px;
color: #FBDAEC;
}
/* navigation pane */
.nav-row {
grid-area: 2 / 1 / 3 / 4;
background-color: #900C3F;
}
.nav-grid {
grid-area: 2 / 2 / 3 / 3;
display: flex;
flex-direction:row;
justify-content:flex-end;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
/* Body */
.body-row {
grid-area: 3 / 1 / 4 / 4;
background-color: #080410;
}
.pages {
grid-area: 3 / 2 / 4 / 3;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: repeat(4, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
background-color: #080410;
word-wrap: break-word;
}
.desc {
grid-area: 1 / 1 / 2 / 2;
background-color:;
}
.story {
grid-area: 2 / 1 / 3 / 2;
background-color:;
}
.system {
grid-area: 3 / 1 / 4 / 2;
background-color:;
}
.graphics {
grid-area: 4 / 1 / 5 / 2;
background-color:;
}
.footer-row {
grid-area: 4 / 1 / 5 / 4;
background-color: #900C3F;
}
.footer-content {
background-color: #581845;
padding-top: 5px;
text-align: right;
overflow: hidden;
box-shadow: 0 100vh 0 100vh #581845;
}

@ -2,6 +2,7 @@
padding:0; padding:0;
margin:0; margin:0;
font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif; font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif;
color: #F9B3D7;
} }
a { a {
@ -123,44 +124,6 @@ a {
background-color: #900C3F; background-color: #900C3F;
} }
.tweets {
grid-area: 4 / 3 / 5 / 4;
margin-right: 0px;
margin-left: 0px;
margin-top: 10px;
margin-bottom: 5px;
}
.news {
grid-area: 4 / 5 / 5 / 6;
display: flex;
flex-direction:column;
background-color: #581845;
color: #F9B3D7;
height: 150px;
overflow-y: scroll;
margin-right: 0px;
margin-left: 0px;
margin-top: 15px;
margin-bottom: 0px;
padding-top: 5px;
padding-right: 5px;
padding-bottom: 5px;
padding-left: 8px;
}
.news a:link {
color: #F9B3D7;
}
.news a:visited {
color: #F9B3D7;
}
.news a:hover {
color: #f463ad;
}
.news a:active {
color: #f463ad;
}
.headline { .headline {
margin-right: 0px; margin-right: 0px;

@ -1,163 +0,0 @@
* {
padding:0;
margin:0;
font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif;
}
a {
text-decoration: none;
}
ul { list-style-position: inside; }
.textbox {
margin: 20px;
padding: 20px;
background-color: #C9C2D6;
color: #080410;
}
.grid {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px 1fr 100px;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.whitespace {
grid-area: 1 / 1 / 2 / 4;
align-items: center;
background-color: #900C3F;
}
.blessfrey-logo { grid-area: 1 / 2 / 2 / 3;
padding-top: 5px;
color: #FBDAEC;
}
/* navigation pane */
.nav-row {
grid-area: 2 / 1 / 3 / 4;
background-color: #900C3F;
}
.nav-grid {
grid-area: 2 / 2 / 3 / 3;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
/* Body */
.body-row {
grid-area: 3 / 1 / 4 / 4;
background-color: #080410;
}
.pages {
grid-area: 3 / 2 / 4 / 3;
display: grid;
grid-template-columns: 800px;
grid-template-rows: repeat(9, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
background-color: #;
word-wrap: break-word;
}
.fact {
grid-area: 1 / 1 / 2 / 2;
}
.desc {
grid-area: 2 / 1 / 3 / 2;
}
.features {
grid-area: 3 / 1 / 4 / 2;
}
.dev {
grid-area: 4 / 1 / 5 / 2;
}
.videos {
grid-area: 5 / 1 / 6 / 2;
}
.graphics {
grid-area: 6 / 1 / 7 / 2;
justify-content:center;
align-items: center;
}
.contact {
grid-area: 7 / 1 / 8 / 2;
}
.credits {
grid-area: 8 / 1 / 9 / 2;
}
.permissions {
grid-area: 9 / 1 / 10 / 2;
}
.footer-row {
grid-area: 4 / 1 / 5 / 4;
background-color: #080410;
}
.footer-content {
background-color: #080410;
color: #F9B3D7;
padding-top: 5px;
text-align: right;
overflow: hidden;
box-shadow: 0 100vh 0 100vh #581845;
}

@ -1,197 +0,0 @@
* {
padding:0;
margin:0;
font-family: trebuchet ms; arial; calibri; gill sans; helvetica neue; candara; geneva; verdana; sans-serif;
}
a {
text-decoration: none;
}
ul { list-style-position: inside; }
.grid {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px repeat(2, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.whitespace {
grid-area: 1 / 1 / 2 / 4;
display: flex;
flex-direction:column;
justify-content:center;
align-items: center;
background-color: #900C3F;
}
.blessfrey-logo {
grid-area: 1 / 2 / 2 / 3;
padding-top: 5px;
color: #FBDAEC;
}
/* navigation pane */
.nav-row {
grid-area: 2 / 1 / 3 / 4;
background-color: #900C3F;
}
.nav-grid {
grid-area: 2 / 2 / 3 / 3;
display: flex;
flex-direction:column;
justify-content:flex-end;
display: grid;
grid-template-columns: auto repeat(4, 70px) auto;
grid-template-rows: 90px;
grid-column-gap: 17px;
grid-row-gap: 0px;
align-items: end;
justify-items: center;
}
.nav-bar {
grid-area: 1 / 1 / 2 / 7;
background-image: url(../img/ele/navbar.png);
height: 86px;
width: 800px;
}
.nav-index {
grid-area: 1 / 2 / 2 / 3;
padding-bottom: 3px;
position: relative;
}
.nav-game {
grid-area: 1 / 3 / 2 / 4;
padding-bottom: 3px;
position: relative;
}
.nav-diary {
grid-area: 1 / 4 / 2 / 5;
padding-bottom: 3px;
position: relative;
}
.nav-presskit {
grid-area: 1 / 5 / 2 / 6;
padding-bottom: 3px;
position: relative;
}
.nav-link {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%)
}
.nav-link a {
font-size: 21px;
color: #E6E8EF;
font-weight: bold;
text-shadow:
-1px -1px 1px black,
0 -1px 1px black,
2px -1px 1px black,
2px 0 1px black,
2px 2px 1px black,
0 2px 1px black,
-1px 2px 1px black,
-1px 0 1px black;
}
/* Body */
.body-row {
grid-area: 3 / 1 / 4 / 4;
background-color: #080410;
}
.content {
grid-area: 3 / 2 / 4 / 3;
display: grid;
grid-template-columns: repeat(2, 400px);
grid-template-rows: 90px repeat(2, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.project-header {
grid-area: 1 / 1 / 2 / 3;
color: #F9B3D7;
font-size: 15px;
margin-top: 25px;
padding-top: 10px;
padding-left: 10px;
}
.featured {
grid-area: 2 / 1 / 3 / 3;
}
.featured-box {
}
.featured-desc {
color: #F9B3D7;
font-size: 25px;
text-align: right;
margin-right: 10px;
}
.featured-desc a:link {
color: #66C7F4;
}
.featured-desc a:visited {
color: #CB9CF2;
}
.featured-desc a:hover {
color: #F9B3D7;
}
.featured-desc a:active {
color: #f463ad;
}
.more {
grid-area: 3 / 1 / 4 / 3;
width: 800px;
display: grid;
grid-template-columns: auto auto;
}
.more-box {
}
.more-desc {
color: #F9B3D7;
font-size: 18px;
text-align: center;
margin-right: 10px;
}
.more-desc a:link {
color: #66C7F4;
}
.more-desc a:visited {
color: #CB9CF2;
}
.more-desc a:hover {
color: #F9B3D7;
}
.more-desc a:active {
color: #f463ad;
}
.footer-row {
grid-area: 4 / 1 / 5 / 4;
background-color: #080410;
}
.footer-content {
background-color: #080410;
color: #F9B3D7;
padding-top: 5px;
text-align: right;
overflow: hidden;
box-shadow: 0 100vh 0 100vh #581845;
}

@ -1,16 +0,0 @@
.parent {
display: grid;
grid-template-columns: auto 800px auto;
grid-template-rows: 25px 90px repeat(2, auto);
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.whitespace { grid-area: 1 / 1 / 2 / 4; }
.logo { grid-area: 1 / 2 / 2 / 3; }
.nav-row { grid-area: 2 / 1 / 3 / 4; }
.nav-bar { grid-area: 2 / 2 / 3 / 3; }
.body-row { grid-area: 3 / 1 / 4 / 4; }
.content { grid-area: 3 / 2 / 4 / 3; }
.footer-row { grid-area: 4 / 1 / 5 / 4; }

@ -1,288 +0,0 @@
/******************************************************************************/
// SIMPLE INPUT RECORD/PLAYBACK
// (c) 2015 Brian Provinciano
//
// You are free to use this code for your own purposes, no strings attached.
//
// This is a very basic sample to record and playback button input.
// It's most useful when activated on startup, deactivated on shutdown for
// global button recording/playback.
//
// For details on more advanced implementations, see my GDC 2015 session:
// -> Automated Testing and Instant Replays in Retro City Rampage
// The slides and full video will be available on the GDC Vault at a later date.
/******************************************************************************/
/******************************************************************************/
// wrap it so it can be conditionally compiled in.
// for example, set INPUTREPLAY_CAN_RECORD to 1 to play the game and record the input, set it to 0 when done
// INPUTREPLAY_CAN_RECORD takes priority over INPUTREPLAY_CAN_PLAYBACK
#define INPUTREPLAY_CAN_PLAYBACK 1
#define INPUTREPLAY_CAN_RECORD 1
#define INPUTREPLAY_INCLUDED (INPUTREPLAY_CAN_PLAYBACK || INPUTREPLAY_CAN_RECORD)
/******************************************************************************/
#if INPUTREPLAY_INCLUDED
#define INPUT_BUTTONS_TOTAL 32 // up to 32
#define MAX_REC_LEN 0x8000 // the buffer size for storing RLE compressed button input (x each button)
/******************************************************************************/
typedef struct
{
unsigned short *rledata;
unsigned short rlepos;
unsigned short datalen;
unsigned short currentrun;
} ButtonRec;
/******************************************************************************/
// if INPUTREPLAY_CAN_RECORD, as soon as this class is instanced, it will automatically record when instanced/created.
// statically creating this as a global will blanket the entire play session
//
// if INPUTREPLAY_CAN_PLAYBACK, playback will begin as soon as LoadFile() is used
//
class SimpleInputRec
{
unsigned int m_buttonstate;
ButtonRec m_buttons[INPUT_BUTTONS_TOTAL];
bool m_bRecording;
unsigned char* m_data;
public:
SimpleInputRec()
: m_buttonstate(0)
, m_data(NULL)
, m_bRecording(true)
{
}
~SimpleInputRec()
{
if(m_data)
{
#if INPUTREPLAY_CAN_RECORD
WriteToFile();
#endif
delete[] m_data;
}
}
// run each frame before the game uses the live button input.
// when recording, it saves the live input
// during playback, it overwrites the live input
void Update(bool bForce = false);
// to start a playback
#if INPUTREPLAY_CAN_PLAYBACK
bool LoadFile(KSTR szfilename);
#endif
// to finish recording
#if INPUTREPLAY_CAN_RECORD
void WriteToFile();
#endif
};
/******************************************************************************/
void SimpleInputRec::Update(bool bForce)
{
#if INPUTREPLAY_CAN_RECORD
if(m_bRecording)
{
unsigned int newbuttons = nesinput.buttons;
// allocate and initialize
if(!m_data)
{
m_data = new unsigned char[INPUT_BUTTONS_TOTAL * MAX_REC_LEN * 2];
unsigned short* dataptr = (unsigned short*)m_data;
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.rledata = dataptr;
dataptr += MAX_REC_LEN;
btn.rlepos = 0;
btn.currentrun = 0;
btn.datalen = MAX_REC_LEN;
}
}
// write RLE button bit streams
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
if(bForce || (newbuttons&(1<<i)) != (m_buttonstate&(1<<i)) || btn.currentrun==0x7FFF)
{
if(btn.currentrun)
{
int bit = (m_buttonstate>>i)&1;
btn.rledata[btn.rlepos++] = (bit<<15) | btn.currentrun;
}
btn.currentrun = bForce? 0 : 1;
}
else
{
++btn.currentrun;
}
}
m_buttonstate = newbuttons;
}
#endif
#if INPUTREPLAY_CAN_PLAYBACK
if(!m_bRecording)
{
bool bIsRunning = false;
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
if(btn.rledata)
{
bIsRunning = true;
if(!btn.currentrun && btn.rlepos<btn.datalen)
{
unsigned short value = btn.rledata[btn.rlepos++];
btn.currentrun = value&0x7FFF;
m_buttonstate &= ~(1<<i);
m_buttonstate |= ((value>>15)&1)<<i;
--btn.currentrun;
}
else
{
if(btn.currentrun)
{
--btn.currentrun;
}
else if(btn.rlepos==btn.datalen)
{
btn.rledata = NULL;
}
}
}
}
if(bIsRunning)
{
// TODO: this is where you can overwrite the live button state to the prerecorded one
systeminput.buttons = m_buttonstate;
}
}
#endif
}
/******************************************************************************/
#if INPUTREPLAY_CAN_PLAYBACK
bool SimpleInputRec::LoadFile(KSTR szfilename)
{
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.datalen = 0;
btn.rledata = NULL;
btn.rlepos = 0;
btn.currentrun = 0;
}
delete[] m_data;
m_bRecording = false;
if(fcheckexists(szfilename))
{
FILE* f = fopen(szfilename, "wb");
if(f)
{
// WARNING: You'll want to do more error checking, but just to keep it simple...
fseek(f,0,SEEK_END);
unsigned long filelen = ftell(f);
fseek(f,0,SEEK_SET);
m_data = new unsigned char[filelen];
fread(m_data, 1, filelen, f);
fclose(f);
unsigned char* bufptr = m_data;
int numbuttons = bufptr[0] | (bufptr[1]<<8);
bufptr += 2;
if(numbuttons <= INPUT_BUTTONS_TOTAL)
{
for(int i=0; i<numbuttons; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.datalen = bufptr[0] | (bufptr[1]<<8);
bufptr += 2;
}
for(int i=0; i<numbuttons; ++i)
{
ButtonRec& btn = m_buttons[i];
if(btn.datalen)
{
// WARNING: Endian dependent for simplcicity
btn.rledata = (unsigned short*)bufptr;
bufptr += btn.datalen*2;
}
}
}
return true;
}
}
return false;
}
#endif
/******************************************************************************/
#if INPUTREPLAY_CAN_RECORD
void SimpleInputRec::WriteToFile()
{
if(m_data && m_bRecording)
{
Update(true);
FILE* f = fopen("_autorec.rec","wb");
if(f)
{
fputc(INPUT_BUTTONS_TOTAL, f);
fputc(0, f);
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
fputc((unsigned char)btn.rlepos, f);
fputc(btn.rlepos >> 8, f);
}
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
// WARNING: Endian dependent for simplcicity
fwrite(btn.rledata, 2, btn.rlepos, f);
}
fclose(f);
}
}
}
#endif
/******************************************************************************/
#endif // INPUTREPLAY_INCLUDED

Binary file not shown.

Binary file not shown.

@ -1,287 +0,0 @@
/******************************************************************************/
// SIMPLE INPUT RECORD/PLAYBACK
// (c) 2015 Brian Provinciano
//
// You are free to use this code for your own purposes, no strings <code>attached.
//
// This is a very basic sample to record and playback button input.
// It's most useful when activated on startup, deactivated on shutdown for
// global button recording/playback.
//
// For details on more advanced implementations, see my GDC 2015 session:
// -> Automated Testing and Instant Replays in Retro City Rampage
// The slides and full video will be available on the GDC Vault at a later date.
/******************************************************************************/
/******************************************************************************/
// wrap it so it can be conditionally compiled in.
// for example, set INPUTREPLAY_CAN_RECORD to 1 to play the game and record the input, set it to 0 when done
// INPUTREPLAY_CAN_RECORD takes priority over INPUTREPLAY_CAN_PLAYBACK
#define INPUTREPLAY_CAN_PLAYBACK 1
#define INPUTREPLAY_CAN_RECORD 1
#define INPUTREPLAY_INCLUDED (INPUTREPLAY_CAN_PLAYBACK || INPUTREPLAY_CAN_RECORD)
/******************************************************************************/
#if INPUTREPLAY_INCLUDED
#define INPUT_BUTTONS_TOTAL 32 // up to 32
#define MAX_REC_LEN 0x8000 // the buffer size for storing RLE compressed button input (x each button)
/******************************************************************************/
typedef struct
{
unsigned short *rledata;
unsigned short rlepos;
unsigned short datalen;
unsigned short currentrun;
} ButtonRec;
/******************************************************************************/
// if INPUTREPLAY_CAN_RECORD, as soon as this class is instanced, it will automatically record when instanced/created.
// statically creating this as a global will blanket the entire play session
//
// if INPUTREPLAY_CAN_PLAYBACK, playback will begin as soon as LoadFile() is used
//
class SimpleInputRec
{
unsigned int m_buttonstate;
ButtonRec m_buttons[INPUT_BUTTONS_TOTAL];
bool m_bRecording;
unsigned char* m_data;
public:
SimpleInputRec()
: m_buttonstate(0)
, m_data(NULL)
, m_bRecording(true)
{
}
~SimpleInputRec()
{
if(m_data)
{
#if INPUTREPLAY_CAN_RECORD
WriteToFile();
#endif
delete[] m_data;
}
}
// run each frame before the game uses the live button input.
// when recording, it saves the live input
// during playback, it overwrites the live input
void Update(bool bForce = false);
// to start a playback
#if INPUTREPLAY_CAN_PLAYBACK
bool LoadFile(KSTR szfilename);
#endif
// to finish recording
#if INPUTREPLAY_CAN_RECORD
void WriteToFile();
#endif
};
/******************************************************************************/
void SimpleInputRec::Update(bool bForce)
{
#if INPUTREPLAY_CAN_RECORD
if(m_bRecording)
{
unsigned int newbuttons = nesinput.buttons;
// allocate and initialize
if(!m_data)
{
m_data = new unsigned char[INPUT_BUTTONS_TOTAL * MAX_REC_LEN * 2];
unsigned short* dataptr = (unsigned short*)m_data;
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.rledata = dataptr;
dataptr += MAX_REC_LEN;
btn.rlepos = 0;
btn.currentrun = 0;
btn.datalen = MAX_REC_LEN;
}
}
// write RLE button bit streams
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
if(bForce || (newbuttons&(1<<i)) != (m_buttonstate&(1<<i)) || btn.currentrun==0x7FFF)
{
if(btn.currentrun)
{
int bit = (m_buttonstate>>i)&1;
btn.rledata[btn.rlepos++] = (bit<<15) | btn.currentrun;
}
btn.currentrun = bForce? 0 : 1;
}
else
{
++btn.currentrun;
}
}
m_buttonstate = newbuttons;
}
#endif
#if INPUTREPLAY_CAN_PLAYBACK
if(!m_bRecording)
{
bool bIsRunning = false;
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
if(btn.rledata)
{
bIsRunning = true;
if(!btn.currentrun && btn.rlepos<btn.datalen)
{
unsigned short value = btn.rledata[btn.rlepos++];
btn.currentrun = value&0x7FFF;
m_buttonstate &= ~(1<<i);
m_buttonstate |= ((value>>15)&1)<<i;
--btn.currentrun;
}
else
{
if(btn.currentrun)
{
--btn.currentrun;
}
else if(btn.rlepos==btn.datalen)
{
btn.rledata = NULL;
}
}
}
}
if(bIsRunning)
{
// TODO: this is where you can overwrite the live button state to the prerecorded one
systeminput.buttons = m_buttonstate;
}
}
#endif
}
/******************************************************************************/
#if INPUTREPLAY_CAN_PLAYBACK
bool SimpleInputRec::LoadFile(KSTR szfilename)
{
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.datalen = 0;
btn.rledata = NULL;
btn.rlepos = 0;
btn.currentrun = 0;
}
delete[] m_data;
m_bRecording = false;
if(fcheckexists(szfilename))
{
FILE* f = fopen(szfilename, "wb");
if(f)
{
// WARNING: You'll want to do more error checking, but just to keep it simple...
fseek(f,0,SEEK_END);
unsigned long filelen = ftell(f);
fseek(f,0,SEEK_SET);
m_data = new unsigned char[filelen];
fread(m_data, 1, filelen, f);
fclose(f);
unsigned char* bufptr = m_data;
int numbuttons = bufptr[0] | (bufptr[1]<<8);
bufptr += 2;
if(numbuttons <= INPUT_BUTTONS_TOTAL)
{
for(int i=0; i<numbuttons; ++i)
{
ButtonRec& btn = m_buttons[i];
btn.datalen = bufptr[0] | (bufptr[1]<<8);
bufptr += 2;
}
for(int i=0; i<numbuttons; ++i)
{
ButtonRec& btn = m_buttons[i];
if(btn.datalen)
{
// WARNING: Endian dependent for simplcicity
btn.rledata = (unsigned short*)bufptr;
bufptr += btn.datalen*2;
}
}
}
return true;
}
}
return false;
}
#endif
/******************************************************************************/
#if INPUTREPLAY_CAN_RECORD
void SimpleInputRec::WriteToFile()
{
if(m_data && m_bRecording)
{
Update(true);
FILE* f = fopen("_autorec.rec","wb");
if(f)
{
fputc(INPUT_BUTTONS_TOTAL, f);
fputc(0, f);
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
fputc((unsigned char)btn.rlepos, f);
fputc(btn.rlepos >> 8, f);
}
for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
{
ButtonRec& btn = m_buttons[i];
// WARNING: Endian dependent for simplcicity
fwrite(btn.rledata, 2, btn.rlepos, f);
}
fclose(f);
}
}
}
#endif
/******************************************************************************/
#endif // INPUTREPLAY_INCLUDED

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

@ -1,259 +0,0 @@
JASC-PAL
0100
256
8 4 16
185 180 175
148 86 52
103 122 113
107 255 123
64 70 100
72 100 56
140 102 64
104 102 116
140 150 140
48 54 76
236 214 224
28 38 40
50 72 50
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save