Working prototype code for a graphical display similar to that described by jotwebe.
Works with minimalist UI (only). Just a prototype at this point, but it works, and comments are appreciated.
Noon faces east, rather than north.
Default speeds (other than global) aren't shown. Speeds are shown in the colors of a resource associated with that speed.
Because of the way the game works (acting only at integer value ticks), a constant, average rate isn't the same thing as "when will i move next?" I've made it to be precise about when you will get to act next. Because of that, even at a constant speed, you may see the lengths of the arcs change slightly, as your free energy fluctuates.
If you have enough energy to act instantly, a dot is shown rather than an arc. If you're running slower than default, you'll see the extent of your slowness by a line tangent to the arc, to show how much longer than a turn you'll need to recover from this action.
Code is pretty well annotated, but ugly; it's got too many remnants of failed attempts left in there.
To place in mod/class/uiset/minimalist.lua/_M:displayResources, between Ammo and Hourglass sections (although you could put it just about anyplace in there):
Code: Select all
-----------------------------------------------------------------------------------
game.player.displayClock = true
--control this from elsewhere
--a lot of these functions don't really belong in this file, but I'm concerned about scope
local drawLine = function (xStart, yStart, xEnd, yEnd, dotDensity, color, shader)
--draws a line of a certain dot density
local x, y = xStart, yStart
local length = math.sqrt((x-xEnd)^2+(y-yEnd)^2)
local step = 0
local xSlope, ySlope = (xEnd - xStart)/length, (yEnd -yStart)/length
while step < length do
shat[1]:toScreenPrecise(x, y, dotDensity, dotDensity, 0, 1, 0, 1, color[1], color[2], color[3], 1)
step = step + 1/dotDensity
x = x+xSlope
y = y+ySlope
end
end
local fixAngles = function (angle)
while angle >= 2*math.pi do
angle = angle - 2*math.pi
end
while angle < 0 do
angle = angle + 2*math.pi
end
return angle
end
local polarToCartesian = function (angle, radius)
return radius*math.cos(fixAngles(angle)), radius*math.sin(fixAngles(angle))
end
local drawArc = function (xCenter, yCenter, radius, angleStart, angleEnd, numCorners, dotDensity, color, shader)
--draws an arc centered on, of radius, starting at, ending at, of certain density and color; angle 0 is East, angle pi/2 is South
--if you send it identical numbers for angleStart and angleEnd, it will draw a circle
if numCorners < 3 then return nil end
local start, stop = fixAngles(angleStart), fixAngles(angleEnd)
if stop <= start then stop = stop + 2*math.pi end
local corners = math.ceil(numCorners*(stop-start)/(2*math.pi))
--corners is the number of actual lines our arc will be composed of, whereas numCorners is the number of corners if the arc were to describe an entire circle
for count = 1, corners do
local angleNext = start+(2*math.pi/numCorners)
if angleNext > stop then angleNext = stop end
local xStart, yStart = polarToCartesian(start, radius)
local xStop, yStop = polarToCartesian(angleNext, radius)
drawLine(xStart+xCenter, yStart+yCenter, xStop+xCenter, yStop+yCenter, dotDensity, color, shader)
start = angleNext
end
return true
end
if game.player.displayClock then
--constants
local radiusMax = 21
local radiusMin = 11
local dotDensity = 1 --how many dots we draw per pixel
local cornerDensity = 24 --not scaling to circumference anymore
local xCenter = x + radiusMax
local yCenter = y + radiusMax
local ticksUntilNextAction = function (speed, globalSpeed, energyValue)
--returns number of ticks until creature with given speed, global speed, and initial energy value can act again following an action at speed
--also returns energy value at the next tick the creature can act
local thresholdToAct = 1000
local energyPerTick = 100
energyValue = energyValue - speed*1000
return math.ceil((thresholdToAct-energyValue)/(energyPerTick*globalSpeed)), energyValue + 100*globalSpeed*math.ceil((thresholdToAct-energyValue)/(energyPerTick*globalSpeed))
end
local createFraction = function (fraction, globalSpeed, energyValue, angleStart, xCenter, yCenter, radiusCurrent, radiusMax, cornerDensity, dotDensity, color, shader)
if fraction then
local ticksUntilNext, energyAtNext = ticksUntilNextAction (fraction, globalSpeed, energyValue)
local angleEnd = angleStart + ticksUntilNext*2*math.pi/10
if ticksUntilNext >= 10 then
angleEnd = angleStart
end
if ticksUntilNext > 0 then
drawArc(xCenter, yCenter, radiusCurrent, angleStart, angleEnd, cornerDensity, dotDensity, color, shader)
end
if ticksUntilNext > 10 and energyValue >= 1000 then
local cosine, sine, length = math.cos(angleStart), math.sin(angleStart), 2*radiusMax*(ticksUntilNext-10)/10
--scaling this appropriately just looks wrong, so I'm scaling it down by pi-- appropriate formula should be 2*math.pi*radiusMax*(ticksUntilNext-10)/10
drawLine(xCenter+radiusCurrent*cosine, yCenter+radiusCurrent*sine, xCenter+(radiusCurrent*cosine)+length*sine, yCenter+(radiusCurrent*sine)+length*cosine, dotDensity, color, shader)
--this is executing between player actions with unforseen effects-- is there some way to only call this if waiting on input?
--my solution: don't call unless energy value over the threshold, seems to work, we'll see if it bugs out when i get to mouse-over mobs
end
if ticksUntilNext <= 0 then
local x, y = polarToCartesian(angleStart, radiusCurrent)
dotDensity = dotDensity * 2
shat[1]:toScreenPrecise(xCenter+x - (0.5*dotDensity), yCenter+y - (0.5*dotDensity), dotDensity, dotDensity, 0, 1, 0, 1, color[1], color[2], color[3], 1)
end
return true
else
return false
end
end
--run this for each displayed clock, ugly code
local energyTable = {globalFraction = nil, combatFraction = nil, movementFraction = nil, spellFraction = nil, mindFraction = nil, summonFraction = nil, energyRemaining = nil, numElements = 0}
energyTable.globalFraction = game.player.global_speed or 1
energyTable.numElements = energyTable.numElements + 1
if game.player:combatSpeed() ~= 1 then
energyTable.combatFraction = game.player:combatSpeed()
energyTable.numElements = energyTable.numElements + 1
end
if game.player:combatMovementSpeed() ~= 1 then
energyTable.movementFraction = game.player:combatMovementSpeed()
energyTable.numElements = energyTable.numElements + 1
end
if game.player:combatSpellSpeed() ~= 1 then
energyTable.spellFraction = game.player:combatSpellSpeed()
energyTable.numElements = energyTable.numElements + 1
end
if game.player:combatMindSpeed() ~= 1 then
energyTable.mindFraction = game.player:combatMindSpeed()
energyTable.numElements = energyTable.numElements + 1
end
if game.player:combatSummonSpeed() ~= 1 then
energyTable.summonFraction = game.player.combatSummonSpeed()
energyTable.numElements = energyTable.numElements + 1
end
energyTable.energyRemaining = game.player.energy.value or 0
--I have no idea why # isn't working, but that's why we need numElements, pain in the ass
local angleStart = ((game.turn%10)/10)*2*math.pi
local radiusPerArc, radiusCurrent = nil, nil
if energyTable.numElements <= 1 then
radiusCurrent = radiusMax
radiusPerArc = 0
end
if energyTable.numElements > 1 then
radiusCurrent = radiusMin
radiusPerArc = (radiusMax-radiusMin)/(energyTable.numElements-1)
end
--draw shadow, underlay?
if createFraction (energyTable.summonFraction, energyTable.globalFraction, energyTable.energyRemaining, angleStart, xCenter, yCenter, radiusCurrent, radiusMax, cornerDensity, dotDensity, equi_c) then
radiusCurrent = radiusCurrent + radiusPerArc end
if createFraction (energyTable.mindFraction, energyTable.globalFraction, energyTable.energyRemaining, angleStart, xCenter, yCenter, radiusCurrent, radiusMax, cornerDensity, dotDensity, psi_c) then
radiusCurrent = radiusCurrent + radiusPerArc end
if createFraction (energyTable.spellFraction, energyTable.globalFraction, energyTable.energyRemaining, angleStart, xCenter, yCenter, radiusCurrent, radiusMax, cornerDensity, dotDensity, mana_c) then
radiusCurrent = radiusCurrent + radiusPerArc end
if createFraction (energyTable.combatFraction, energyTable.globalFraction, energyTable.energyRemaining, angleStart, xCenter, yCenter, radiusCurrent, radiusMax, cornerDensity, dotDensity, stam_c) then
radiusCurrent = radiusCurrent + radiusPerArc end
if createFraction (energyTable.movementFraction, energyTable.globalFraction, energyTable.energyRemaining, angleStart, xCenter, yCenter, radiusCurrent, radiusMax, cornerDensity, dotDensity, air_c) then
radiusCurrent = radiusCurrent + radiusPerArc end
createFraction (1, energyTable.globalFraction, energyTable.energyRemaining, angleStart, xCenter, yCenter, radiusMax, radiusMax, cornerDensity, dotDensity, life_c)
--note that by this time, radiusCurrent should equal radiusMax anyways
drawLine(xCenter, yCenter, xCenter + radiusMax + 1, yCenter, 1, {0,0,0}, shader)
--draw our noon marker, which is actually at 3 o'clock, so that when we get to mob mouseover, we can just mirror the mob's clock for easy comparison
--todo:
--if we're mousing over a mob, offset our x, and display their clock to the right of ours
--if we're mousing over our clock, display tooltip giving numerical values
--correct our x and y for the next ui element
--pretty pictures?
end