Sun Nov 29 2020

I didn’t write yesterday. Yesterday I spent most of the day writing this flare launcher GUI for Stormworks via

-- IO definitions
flareCount = property.getNumber("flareCount")
cfIOIndex = 2 -- the Microcontroller output index which is meant to be wired to a CrispyFlare IO Microcontroller

-- vars
c1 = {255,255,255} -- white
c2 = {217, 90, 0}  -- Persian orange
isFiring = false
flaresFired = 0
qFlareCount = 0
qFlaresFired = 0
qFlareDelay = 0
qStartTime = 0
tickCounter = 0
millis = 0
actDuration = 250

-- xCoord, yCoord, height, width, label, isActive, color, activeColor, clickTime
buttons = {
    {["x"]=0, ["y"]=7, ["h"]=7,  ["w"]=8, ["label"]="1", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0 },
    {["x"]=8, ["y"]=7, ["h"]=7,  ["w"]=8, ["label"]="2", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },
    {["x"]=16, ["y"]=7, ["h"]=7,  ["w"]=8, ["label"]="3", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },
    {["x"]=24, ["y"]=7, ["h"]=7,  ["w"]=8, ["label"]="R", ["act"]=false, ["c"]={207,19,0}, ["ac"]={70,0,120}, ["ct"]=0  },
    {["x"]=0, ["y"]=13, ["h"]=7,  ["w"]=8, ["label"]="4", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },
    {["x"]=8, ["y"]=13, ["h"]=7,  ["w"]=8, ["label"]="5", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },    
    {["x"]=16, ["y"]=13, ["h"]=7,  ["w"]=8, ["label"]="6", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0 },
    {["x"]=24, ["y"]=13, ["h"]=7,  ["w"]=8, ["label"]="D", ["act"]=false, ["c"]={207,19,0}, ["ac"]={20,30,255}, ["ct"]=0 },        
    {["x"]=0, ["y"]=19, ["h"]=7,  ["w"]=8, ["label"]="7", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },
    {["x"]=8, ["y"]=19, ["h"]=7,  ["w"]=8, ["label"]="8", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },      
    {["x"]=16, ["y"]=19, ["h"]=7,  ["w"]=8, ["label"]="9", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },        
    {["x"]=24, ["y"]=19, ["h"]=7,  ["w"]=8, ["label"]="", ["act"]=false, ["c"]={120,80,0}, ["ac"]={120,120,120}, ["ct"]=0 },        
    {["x"]=0, ["y"]=25, ["h"]=7,  ["w"]=8, ["label"]="*", ["act"]=false, ["c"]={0,120,0}, ["ac"]={120,120,120}, ["ct"]=0  },        
    {["x"]=8, ["y"]=25, ["h"]=7,  ["w"]=8, ["label"]="0", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,120,120}, ["ct"]=0  },
    {["x"]=16, ["y"]=25, ["h"]=7,  ["w"]=8, ["label"]="#", ["act"]=false, ["c"]={207,19,0}, ["ac"]={20,30,255}, ["ct"]=0  },        
    {["x"]=24, ["y"]=25, ["h"]=7,  ["w"]=8, ["label"]="X", ["act"]=false, ["c"]={207,19,0}, ["ac"]={120,0,0}, ["ct"]=0  }        

 * Converts an HSV color value to RGB. Conversion formula
 * adapted from
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 * Greetz
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
function hsvToRgb(h, s, v, a)
  local r, g, b

  local i = math.floor(h * 6);
  local f = h * 6 - i;
  local p = v * (1 - s);
  local q = v * (1 - f * s);
  local t = v * (1 - (1 - f) * s);

  i = i % 6

  if i == 0 then r, g, b = v, t, p
  elseif i == 1 then r, g, b = q, v, p
  elseif i == 2 then r, g, b = p, v, t
  elseif i == 3 then r, g, b = p, q, v
  elseif i == 4 then r, g, b = t, p, v
  elseif i == 5 then r, g, b = v, p, q

  return r * 255, g * 255, b * 255, a * 255

function isClickWithin(clickX, clickY, button)
	leftBoundary = button['x']
	topBoundary = button['y']
	rightBoundary = button['x']+button['w']
	bottomBoundary = button['y']+button['h']
	if (clickX > leftBoundary and clickX < rightBoundary) then
		if (clickY > topBoundary and clickY < bottomBoundary) then
			return true
	return false

function ceaseFire()
	isFiring = false	

function resetQFlareCount()
	qFlareCount = 0

function resetQFlareDelay()
	qFlareDelay = 0

function resetHash()
	buttons[15]["act"] = false
function resetD()
	buttons[8]["act"] = false

function resetQ()
	-- reset qFlareCount and qFlareDelay

	-- reset the D & # button state
	-- stop the ongoing queue

function processClick(clickX, clickY)
	for index, button in next, buttons do
		if (isClickWithin(clickX, clickY, button)) then
			-- A button was clicked
			if (button["label"] == "*") then
				-- asterisk button was clicked
				print("firing with qFlareCount:"..qFlareCount.." and qFlareDelay:"..qFlareDelay)
				isFiring = true
				qStartTime = millis
				qFlaresFired = 0
			elseif (button["label"] == "R") then
				-- R button was clicked
				-- reset the counter
				flaresFired = 0
			elseif (button["label"] == "D") then
				-- D (delay) button was pressed
				-- deactivate # button state
				buttons[15]["act"] = false
			elseif (button["label"] == "X") then
				-- X button was pressed
			elseif (button["label"] == "#") then
				-- # (Flare Count) button was pressed
				-- deactivate D button state
				buttons[8]["act"] = false
			elseif (
				button["label"] == "0" or
				button["label"] == "1" or
				button["label"] == "2" or
				button["label"] == "3" or
				button["label"] == "4" or
				button["label"] == "5" or
				button["label"] == "6" or
				button["label"] == "7" or
				button["label"] == "8" or
				button["label"] == "9"
			) then
				-- A numbered button was clicked
				-- if the "#"" button is active, the number presses are setting the queue flare count
				if (buttons[15]["act"]) then
					qFlareCount = tonumber(tostring(qFlareCount)..button["label"])
				-- if the D button is active, the number presses are setting the queue flare delay
				if (buttons[8]["act"]) then
					qFlareDelay = tonumber(tostring(qFlareDelay)..button["label"])
			-- Toggle the active state on D or #. All other buttons get set to active state.
			if (button["label"] == "D" or button["label"] == "#") then
				button["act"] = not button["act"]
				button["act"] = true
			-- set the clickTime
			button["ct"] = millis

function processFiring()
	-- Only continue if there's a flare queue
	if (qFlareCount == 0) then 
	timeToFire = qStartTime + qFlareDelay
	-- wait until it's time to fire
	if (timeToFire < millis) then
		-- fire if flares are in stock
		-- and the qflaresFired is less than the target
		if flaresFired < flareCount and qFlaresFired < qFlareCount then
			-- there are flares in stock
			-- increment the flaresFired counter
			flaresFired = flaresFired + 1
			output.setNumber(cfIOIndex, flaresFired)
			-- increment the qFlaresFired counter
			qFlaresFired = qFlaresFired + 1
			-- queue the next firing time
			qStartTime = millis
			-- there are no flares in stock
			-- stop firing


function onTick()
	tickCounter = tickCounter + 1
	millis = math.floor(tickCounter / 60 * 1000)
	isP1 = input.getBool(1)
	in1X = input.getNumber(3)
	in1Y = input.getNumber(4)

	if (isFiring) then

	if pre and not isP1 then
		-- click just occured
		processClick(preInX, preInY)
	pre = input.getBool(1)
	preInX = input.getNumber(3)
	preInY = input.getNumber(4)


function onDraw()
	flaresRemaining = math.floor(flareCount - flaresFired)
	percentageRemaining = flaresRemaining / flareCount

    -- draw the main bg which will covered and form a border
    setC(c1[1], c1[2], c1[3])

    -- draw the black backgrounds
    screen.drawRectF(0, 0, 20, 7) -- heading
    screen.drawRectF(20, 0, 12, 7) -- No. remaining indicator
    screen.drawRectF(0, 7, 24, 32) -- keypad
    screen.drawRectF(24, 7, 10, 32) -- function buttons

	-- determine the color for the heading text 
	if (percentageRemaining == 0) then
	screen.drawTextBox(0, 0, 20, 7, "FLR", 0, 0)

	-- determine the color of remaining indicator based on remaining percentage
	-- 0.35 is green hue (on HSV wheel [0..1])
	local hue = 0.35*percentageRemaining
	local r, g, b = hsvToRgb(hue, 1, 0.45, 1)
	setC(r, g, b)
	screen.drawTextBox(21, 0, 11, 7, flaresRemaining, 0, 0)
	-- draw the buttons
	for index, button in next, buttons do
		if (button["act"]) then
			setC(button["ac"][1], button["ac"][2], button["ac"][3])
    		setC(button["c"][1], button["c"][2], button["c"][3])
	    -- screen.drawRectF(button["x"], button["y"], button["w"], button["h"])
	    -- setC(0, 0, 0)
	    screen.drawTextBox(button["x"], button["y"], button["w"], button["h"], button["label"], 0, 0)
	-- clear button active states
	for index, button in next, buttons do
		if (
			button["act"] and 
			button["ct"]+actDuration < millis and
			not(button["label"] == "#" or button["label"] == "D")
		) then
			button["act"] = false


function setC(r,g,b,a)
	if a==nil then a=255 end

So this is version 2, which is the advanced GUI. Version 1 has a simpler GUI.

Here’s a sample of version 2 which adds a queue and a timer.

User input is as follows.

The user clicks # followed by the number of flares they want to launch. Then, the user clicks D followed by the millisecond delay between each flare launch. Then, the user presses * to execute the queue.

An example for this is the entry #1* which tells the system to fire 1 flare. It omits the D button click because there is only 1 flare, so a delay between flare launches is unecessary.

The next example input is #2D500* which tells CrispyFlare to launch 2 flares with a 500 ms delay between launches.

The final example input is #3D150* which tells CrispyFlare to launch 3 flares with a 150 ms delay between launches.

The count and delay are saved between launches, which means that once programmed, the user can press * a second time to launch the same fire mission as last time. The existing fire mission can be cleared using the X button. The fire mission can be stopped during execution by pressing the X button.

The flare counter can be reset by pressing R. R is meant for “Reloading” in the case that the vessel returns to base and the flares are physically reloaded by the game.

Pretty cool, eh? I have yet to test version 2 in game. I think the script might be too large, and I might have to do some aggressive optimization to fit into the game. That is of course if Pony IDE isn’t lying when it says the script limit is 4096.

I have tested CrispyFlare V1.0 in game. It’s pretty cool! I’ll remember to record some in-game footage of these working when I play next.

This UI is super simple. Press anywhere on the screen, and a flare will be launched, if able.

I now know that there is a problem with this script– it has no reload button! A user would not be able to fire again after reloading their flare tubes, unless they find a way to power-cycle the Microcontroller which drives this UI.

My next goal for writing code in Stormworks is to write code for someone else. I’ve done enough code writing for myself to where I have a good handle on Lua scripting in Stormworks.

This is my new master plan for the end of 2020 and the start of 2021– Generate value for other people.

I’ve long dreamt about making software development my job. Heck, I was so determined to make this a reality that I shortly had the saying, “I will become a paid software developer, or I will die trying.”

It’s still a good saying, although I’m no longer in the headspace where I would end my life if I couldn’t make it work out.

I’m going to make it work out. I might die trying, but that’s going to be from an accident, not an intentional act.

The big thing that’s changed from how I used to do things is that I care about moderation now. I do a lot to ensure that I don’t get burnt out with my work. When I do overdo it, I’m gentle and I take time off to recover. Moderation is really important to me now.

I’ve gone off on a tangent. That’s okay, but let’s bring it back to the point I was trying to make.

Generating value for other people. This is a big mission of my Twitch streaming idea.

I can generate value for people by

  • keeping them company
  • providing them with software that they couldn’t write themselves
  • being nice to them
  • giving them attention
  • teaching them something

In doing this, I get something in return, as well.

  • having someone to talk to
  • being a “guru”
  • having an opportunity to teach
  • learning through making mistakes
  • confidence practice
  • learning through teaching

I think what I get out streaming on Twitch might actually be more than what a viewer would get. I suppose that’s what I could call a harmonious relationship!

I’ve been scared to stream lately. The last stream I did ended up with me being pretty embarrased by my behavior. I started out so confident and calm, then I was surprised by a viewer who interacted with me. I prioritized my focus on their words, and what I was researching and reading aloud became secondary.

I had a codependent relapse of sorts. I suddenly felt the need to perform, and entertain. I lost my cool and reverted to a childlike state where everything was a joke and my thought process was completely illogical. I laughed uncomfortably and I ended the stream feeling like a fool.

I haven’t gone back to it since then. I can do it again, and I want to do it again, but I’ve set a milestone for myself which I need to reach before I go back.

The milestone is the completion of my chat bot, “CrispyBot” and the !request command.

I think I wrote about the request command before… Maybe…

Ok I’ll summarize. A viewer in my Twitch chat could send the message, !request I would like a bot which has the command !fortune and it tells me a fortune.

This input would create a line in my google docs spreadsheet which tracks the requests that I get from viewers.

The columns in that spreadsheet are Requestor, Request Description, and Donation Amount. I am using to integrate the bot with the spreadhseet.

It’s a milestone I set for myself, but it’s probably not worth holding off on the stream for. It’s not like I can’t stream unless I have the request bot. In reality, I’m avoiding streaming because it’s uncomfortable.

It’s uncomfortable to stream via cloud workspace which has little familiarity to my existing development environment. It’s uncomfortable to spin-up the cloud workspace and log into Mumble and X2Go so my voice and keyboard/mouse inputs are sent to the cloud workspace. It’s uncomfortable to engage with strangers and carry a conversation.

What do I really want?

I want money so I can move to Idaho. I want money so I can hire a home cleaner. I want money so I can buy a house. I want money so I can buy a car. I want money so I can rent an office space.

I want money, and I want a good life. I want a life where I earn what money I make. I don’t want a life where I’m given things by a higher power such as a state government. That kind of life is fragile… Again, Washington state is not the place for me anymore. They’ve long been against individual freedom and I don’t want to support that anymore.

So um, yeah.

What to write about now?

Did I finish that previous tangent? Is it time to move on or no?

I think I finished it.

For now, I wrap up this writing and I go do laundry!

I me do this it what huh?


Check out these sexy bikini bodies!

Affy & Gratty

15. I do what I want, I follow my own path, because it makes me me.

92. I allow myself to take a break and do something I enjoy.
91. I make a difference in the world.
90. My spirit is beautiful.

I’m grateful for my sister A. Did I write about her gifting me a new pair of shoes? I haven’t had new shoes in so long! I forgot what new shoes felt like! I’m really grateful that she would buy me new shoes after seeing my old shoes and how holey they were. Super stoked to have Vibram shoes!

I’m grateful for my cell phone camera which I used to take card photos. I use OpenCamera and it’s been really helpful in locking orientation and doing Exposure Bracketing. I end up with a variety of differently exposed photos, one of which I ultimately choose, depending on which turns out best. Also I lock the format to portrait mode which is super helpful. Super nice app.

I’m grateful for vegan pumpkin pie! I’m so grateful that I realize that eating pie for 4 days straight is not a good idea! My poops have been so painful and difficult to pass! I really scarfed down the last of the pie I had left, so I won’t have to worry about the sugar content or the temptation anymore. In the future, I’m going to just say no to sugary food, even if it’s free! That’s not food, that’s digestive system suffering in a container which happens to trick my tongue into thinking it’s a good time!


Looking for VOCALOID trading cards?

Check out Sakura Blossom Trading Post