fixed the camera so its motion is no longer tied to framerate

limited minimum and maximum zoom on camera
changed point scaling so that cities get smaller when zooming in
changed selection cursor to hollow circle
changed capital cities to orange squares
included data/earth/ directory so that files are loaded from there when they're not available in the mod folder
when cities are hidden, hovering over travel nodes will display the travel node location (broken for now)
when cities and travel nodes are hidden, hovering over ai markers will display their details
made the information boxes more opaque
changed border-calculating code so that it works with higher resolutions
This commit is contained in:
wan-may 2024-04-24 22:06:41 -03:00
parent ce15bfdcf7
commit 7f457daa94
26 changed files with 33337 additions and 93 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

34
ai.lua
View File

@ -1,13 +1,27 @@
--Manage the AI nodes used by DEFCON.
local t = {}
local bmp = require 'bmp'
local lg = assert( love.graphics )
local locationQuery = require 'locationQuery'
local t = setmetatable( {}, {__index = locationQuery } )
local print = print
local aiNode = {}
local mtAiNode = { __index = aiNode }
function aiNode:formatDisplayInfo()
return ([[AI NODE: %d
LONGITUDE: %3.2f
LATITUDE: %3.2f
ATTACKING: %s
]]):format( self.idx, self.x, self.y, tostring(self.attacking) )
end
function t.load( filename )
local img, imgd = bmp.load( filename )
local nodes = {
visible = true,
all = {},
att = {},
ptsAtt = {},
def = {},
@ -16,15 +30,19 @@ function t.load( filename )
imgd = imgd }
print( "=== Loading AI Markers: ===" )
local idx = 1
for x = 0, 511 do
for y = 0, 284 do
local r, g = imgd:getPixel( x, 284 - y )
if r > 0.5 or g > 0.5 then
local long = x * (360 / imgd:getWidth()) - 180
local lat = y * (200 / img:getHeight()) - 100
local set = (r > 0.5) and nodes.att or nodes.def
set[#set + 1] = {x = long, y = lat}
print( #set, long, lat )
local attacking = (r > 0.5)
local set = attacking and nodes.att or nodes.def
local node = setmetatable( {x = long, y = lat, attacking = attacking, idx = idx}, mtAiNode )
nodes.all[ idx ] = node
set[#set + 1] = node
idx = idx + 1
end
end
end
@ -42,8 +60,14 @@ function t.load( filename )
k = k + 2
end
end
nodes.all = locationQuery.New( nodes.all )
setmetatable( nodes, {__index = t } )
return nodes
end
return setmetatable( nodes, {__index = t } )
function t.selectNearest( nodes, x, y )
return (nodes.all):getClosestPoint( x, y )
end
function t.draw( nodes )

View File

@ -20,12 +20,12 @@ function Camera.GetNodeCoordinate( x, y )
return tfNodes:inverseTransformPoint( x, y )
end
function Camera.Zoom( out )
local scale = out and 1.1 or 0.9
tf:scale( scale, scale )
local x = Camera.x
local y = Camera.y
return Camera.Set( x, y, Camera.w * scale, Camera.h * scale )
function Camera.Zoom( delta )
local scale = 1.0 + delta
if Camera.zoom < 25.0 and delta > 0 or --zooming in
Camera.zoom > 0.5 and delta < 0 then --zooming out
return Camera.Set( Camera.x , Camera.y, Camera.w * scale, Camera.h * scale )
end
end
function Camera.Translate( x, y )
@ -36,9 +36,8 @@ end
--In world coordinates: top left corner at x, y, extent of w, h.
function Camera.Set( x, y, w, h )
print( ("CAMERA: %3.2f %3.2f %3.2f %3.2f"):format(x, y, w, h) )
Camera.x, Camera.y, Camera.w, Camera.h = x, y, w, h
Camera.zoom = w / 360
Camera.zoom = w / 800
tf:reset()
tf:scale( w / 360, -h / 200 )
tf:translate( 180 - x, -y - 100 )

View File

@ -23,27 +23,41 @@ function t.unlockSelection()
end
function t.draw()
if cities.visible then lg.points( points ) end
return lg.points( points )
end
function t.drawSelected( r )
if not cities.visible then return end
function t.drawSelected( )
local c = t.selected
if not c then return end
lg.circle( "fill", c.x, c.y, r )
lg.circle( "line", c.x, c.y, 1.0 )
end
function t.drawCapitals()
if cities.visible then lg.points( caps ) end
end
function t.selectNearestCity(x, y)
if not t.selectionLocked then t.selected = cities:getClosestPoint(x, y) end
function t.selectNearest( cities, x, y )
return cities:getClosestPoint(x, y) --defer to locationQuery
end
local city = {}
local citymt = {__index = city}
function city:formatDisplayInfo()
return (
[[
NAME: %s
COUNTRY: %s
LONGITUDE: %3.2f
LATITUDE: %3.2f
POP: %d
CAPITAL: %s]]):format( self.name, self.country, self.x, self.y, self.pop, tostring(self.capital) )
end
function t.load( filename )
print( "=== LOADING CITIES. ===" )
cities = { visible = true, active = false }
cities = { visible = true, active = false }
local n = 1
local idxPts = 1
local idxCaps = 1
@ -53,11 +67,11 @@ function t.load( filename )
local _, _, x, y, pop, capital = line:sub( 83 ):find( "(%g+)%s+(%g+)%s+(%g+)%s+(%g+)" )
if capital then --check against empty or malformed line
x, y, pop, capital = tonumber( x ), tonumber( y ), tonumber( pop ), ( tonumber( capital ) > 0)
local city = {
local city = setmetatable({
name = line:sub( 1, 39 ):gsub("%s+$",""),
country = line:sub( 42, 82 ):gsub("%s+$",""),
x = x, y = y, pop = pop, capital = capital
}
}, citymt )
cities[n] = city
n = n + 1
@ -77,7 +91,7 @@ function t.load( filename )
cities = locationQuery.New( cities )
setmetatable( getmetatable( cities ).__index, {__index = t } )
print( "LOADED", filename, n )
print( "=== CITIES LOADED:", filename, n, "===" )
return cities
end

View File

@ -11,7 +11,7 @@ function love.conf(t)
t.audio.mixwithsystem = true -- Keep background music playing when opening LOVE (boolean, iOS and Android only)
t.window.title = "dcEarth" -- The window title (string)
t.window.icon = "favicon.png" -- Filepath to an image to use as the window's icon (string)
t.window.icon = "icons/favicon.png" -- Filepath to an image to use as the window's icon (string)
t.window.width = 800 -- The window width (number)
t.window.height = 600 -- The window height (number)
t.window.borderless = false -- Remove all border visuals from the window (boolean)
@ -44,7 +44,7 @@ function love.conf(t)
t.modules.sound = false -- Enable the sound module (boolean)
t.modules.system = true -- Enable the system module (boolean)
t.modules.thread = false -- Enable the thread module (boolean)
t.modules.timer = false -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
t.modules.touch = false -- Enable the touch module (boolean)
t.modules.video = false -- Enable the video module (boolean)
t.modules.window = true -- Enable the window module (boolean)

BIN
data/earth/africa.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
data/earth/ai_markers.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

2745
data/earth/cities.dat Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
data/earth/coastlines.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

15206
data/earth/coastlines.dat Normal file

File diff suppressed because it is too large Load Diff

BIN
data/earth/europe.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

8325
data/earth/international.dat Normal file

File diff suppressed because it is too large Load Diff

BIN
data/earth/northamerica.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
data/earth/russia.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
data/earth/sailable.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
data/earth/southamerica.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
data/earth/southasia.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
data/earth/travel_nodes.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
icons/eye.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -1,7 +1,6 @@
--Shitty acceleration structure: get closest point in set.
--Assumed to be in world coordinates: ( -180, 180 ) x ( -100, 100 )
local t = {}
local hashes = {}
local math = math
local hash = function( x, y, debug )
@ -9,26 +8,36 @@ local hash = function( x, y, debug )
return s
end
function t.getHashes( points )
return assert( points.hashes )
end
function t.getClosestPoint( points, x, y )
local hashes = t.getHashes( points )
local closePoints = hashes[hash( x, y )]
if not closePoints then return end
if not closePoints then
return
end
local distance = math.huge
local px, py, point
for k, v in pairs(closePoints) do
px, py = v.x, v.y
local d = (x - px) * (x - px) + (y - py) * (y - py)
if d < distance then
distance = d
point = v
end
end
return point
end
function t.Edit( points, point, x, y )
local hashes = t.getHashes( points )
local h = hashes[hash( point.x, point.y )]
if h then
for i, p in pairs(h) do
@ -38,25 +47,33 @@ function t.Edit( points, point, x, y )
end
end
end
table.insert( hashes[ hash( x, y ) ], point )
point.x = x
point.y = y
end
function t.Add( points, x, y )
end
function t.New( points )
local hashes = {}
for i = 1, #points do
local x, y = points[i].x, points[i].y
local h = hash( x, y )
hashes[h] = hashes[h] or {}
hashes[h][#hashes[h] + 1] = points[i]
end
return setmetatable( points, {__index = t } )
points.hashes = hashes
do
local count = 0
for k, v in pairs(hashes) do count = count + 1 end
print( "LOCATION QUERY. Points:", count )
end
return setmetatable( points, {__index = t} )
end
return t

View File

@ -25,12 +25,12 @@ end
function love.update( dt )
local tx, ty = 0, 0
local moveCamera = false
if love.keyboard.isScancodeDown( "w" ) then moveCamera = true; ty = ty + 1 end
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - 1 end
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - 1 end
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + 1 end
if love.keyboard.isScancodeDown( "q" ) then Camera.Zoom( true ) end
if love.keyboard.isScancodeDown( "e" ) then Camera.Zoom( false ) end
if love.keyboard.isScancodeDown( "w" ) then moveCamera = true; ty = ty + 30 * dt end
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - 30 * dt end
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - 30 * dt end
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + 30 * dt end
if love.keyboard.isScancodeDown( "q" ) then Camera.Zoom( dt ) end
if love.keyboard.isScancodeDown( "e" ) then Camera.Zoom( -dt ) end
if moveCamera then Camera.Translate( tx, ty ) end
@ -65,7 +65,7 @@ function love.draw()
if not map.loaded then
return love.graphics.print( "Drag and drop folder to begin.")
end
love.graphics.push( "all" )
map.draw()
love.graphics.pop()
@ -85,7 +85,7 @@ function love.draw()
local wx, wy = Camera.GetWorldCoordinate( x, y )
local bx, by = Camera.GetBitmapCoordinate( x, y )
local h = love.graphics.getHeight() - 30
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
love.graphics.setColor( 0.2, 0.1, 0.1, 1.0 )
love.graphics.rectangle( "fill", 0, h, love.graphics.getWidth() / 2, 30 )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.rectangle( "line", 0, h, love.graphics.getWidth() / 2, 30 )
@ -94,30 +94,13 @@ function love.draw()
--Edit box.
love.graphics.rectangle( "line", love.graphics.getWidth() / 2, h, love.graphics.getWidth() / 2, 30 )
if map.loaded and map.cities.selected then
local c = map.cities.selected
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
if map.selected then
love.graphics.setColor( 0.2, 0.1, 0.1, 1.0 )
love.graphics.rectangle( "fill", 0, 0, 150 ,100 )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.rectangle( "line", 0, 0, 150 ,100 )
love.graphics.setColor( 1.2, 1.1, 1.1, 1.5 )
love.graphics.print( ("NAME: %s\nX: %3.2f\nY: %3.2f\nPOP: %d\nCAPITAL: %s\nCOUNTRY: %s"):format(c.name, c.x, c.y, c.pop, tostring(c.capital), c.country), 0, 0 )
elseif map.loaded and map.travelnodes.selected then
local c = map.travelnodes.selected
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
love.graphics.rectangle( "fill", 0, 0, 150 ,100 )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.rectangle( "line", 0, 0, 150 ,100 )
love.graphics.setColor( 1.2, 1.1, 1.1, 1.5 )
love.graphics.print( ("Node: %d\nX: %3.2f\nY: %3.2f\n"):format(c.number, c.x, c.y) )
elseif map.loaded and map.ainodes.selectedNode then
local c = map.ainodes.selected
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
love.graphics.rectangle( "fill", 0, 0, 150 ,100 )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.rectangle( "line", 0, 0, 150 ,100 )
love.graphics.setColor( 1.2, 1.1, 1.1, 1.5 )
love.graphics.print( ("Node: %d\nX: %3.2f\nY: %3.2f\noffensive: %s"):format(c.number, c.x, c.y, c.attack) )
love.graphics.print( map.selected:formatDisplayInfo(), 0, 0 )
end
end
@ -126,7 +109,7 @@ function love.resize(w, h)
end
function love.wheelmoved(x, y)
Camera.Zoom( (y > 0) and true or false )
Camera.Zoom( (y > 0) and 0.5 or -0.5 )
end
function love.mousepressed( x, y, button, istouch, presses )
@ -142,7 +125,13 @@ function love.mousepressed( x, y, button, istouch, presses )
end
function love.mousemoved( x, y, dx, dy, istouch )
if map.loaded and map.cities.visible then map.cities.selectNearestCity( Camera.GetWorldCoordinate( x, y ) ) end
local wx, wy = Camera.GetWorldCoordinate( x, y )
if map.loaded then
if map.cities.visible then map.selected = map.cities:selectNearest( wx, wy )
elseif map.travelnodes.visible then map.selected = map.travelnodes:selectNearest( wx, wy )
elseif map.ainodes.visible then map.selected = map.ainodes:selectNearest( wx, wy )
end
end
end
local function ToggleVisibility( layer )

29
map.lua
View File

@ -2,12 +2,11 @@ local lg = love.graphics
local AI = require 'ai'
local Cities = require 'cities'
local Lines = require 'lines'
local Nodes = require 'nodes'
local Bitmap = require 'bmp'
local Nodes = require 'travelNodes'
local Camera = require 'camera'
local Territory = require 'territory'
local map = {
local map = {
loaded = false,
coastlines = false,
coastlinesLow = false,
@ -78,21 +77,28 @@ function map.draw()
do --all this stuff is drawn in world coordinates, ( -180, 180 ) x ( -100, 100 )
lg.replaceTransform( Camera.tf )
if map.selected then
lg.setColor( 1.0, 0.5, 0.5, 0.9 )
lg.setLineJoin( "miter" )
lg.setLineWidth( 1.0 / Camera.zoom )
lg.circle( "line", map.selected.x, map.selected.y, 0.2 + 1.0 / Camera.zoom )
end
do --points
if map.cities.visible then --points
lg.setColor( 1, 0, 0, 0.5 )
lg.setPointSize( 0.5 * Camera.zoom )
lg.setPointSize( 5.0 )
map.cities.draw()
lg.setColor( 1, 1, 1.0, 0.5 )
lg.setPointSize( 1.0 * Camera.zoom )
lg.setColor( 1, 1, 0.0, 0.5 )
map.cities.drawCapitals()
end
lg.setColor( 1, 1, 1, 0.5 )
map.cities.drawSelected( 15.0 / Camera.zoom )
if map.ainodes.visible then map.ainodes:draw() end
if map.ainodes.visible then
lg.setPointSize( 5.0 )
map.ainodes:draw()
end
do --line stuff
@ -121,6 +127,7 @@ function map.draw()
map.travelnodes:draw()
end
end
end

View File

@ -109,8 +109,9 @@ function t.computeBorder( territory, threshold, key )
local border = territory[key]
local n = 1
for x = 0, 511 do
for y = 0, 284 do
local w, h = territory.imgd:getWidth() - 1, territory.imgd:getHeight() - 1
for x = 0, w do
for y = 0, h do
--Bottom left, bottom right, and top right of pixel in image coordinates:
local blx, bly = x, y + 1
local brx, bry = x + 1, y + 1

View File

@ -1,10 +1,13 @@
--Manage the pathfinding nodes used by DEFCON.
--This is important for a mapping tool because the DEFCON client will not load a map unless
--the pathfinding nodes form a connected graph.
local t = {}
local bmp = require 'bmp'
local locationQuery = require 'locationQuery'
local lg = assert( love.graphics )
local t = setmetatable({}, {__index = locationQuery})
local isSailable
local function isConnected( startNode, endNode )
@ -25,15 +28,17 @@ local function isConnected( startNode, endNode )
return true
end
function t.getClosest( nodes, x, y )
local d = math.huge
local closestNode
for _, node in pairs( nodes.nodes ) do
local nx, ny = node.x, node.y
local nd = (nx - x) * (nx - x) + (ny - y) * (ny - y)
if nd < d then d = nd; closestNode = node end
end
return closestNode
function t.selectNearest( nodes, x, y )
return (nodes.nodes):getClosestPoint( x, y )
end
local travelNode = {}
local mtTravelNode = { __index = travelNode }
function travelNode:formatDisplayInfo()
return ([[ TRAVEL NODE: %d
LONGITUDE: %3.2f
LATITUDE: %3.2f]]):format( self.idx, self.x, self.y )
end
function t.load( filename, sailable )
@ -49,10 +54,10 @@ function t.load( filename, sailable )
if imgd:getPixel( x, 399 - y ) > 0 then
local long = 360 * ( x - 800 ) / 800 - 360 / 2 + 360
local lat = 360 * ( 600 / 800 ) * ( 600 - y ) / 600 - 180
nodes.nodes[n] = {x = long, y = lat}
nodes.nodes[n] = setmetatable({x = long, y = lat, idx = n}, mtTravelNode )
print( nodes.nodes[n]:formatDisplayInfo() )
nodes.points[ 2 * n - 1 ] = long
nodes.points[ 2 * n ] = lat
print( n, long, lat )
n = n + 1
end
end
@ -69,7 +74,9 @@ function t.load( filename, sailable )
end
print( "=== Nodes Loaded ===" )
return setmetatable( nodes, {__index = t } )
nodes.nodes = locationQuery.New( nodes.nodes )
setmetatable( nodes, {__index = t} )
return nodes
end
--Determine if graph has more than one connected component.
@ -78,9 +85,13 @@ function t.isConnected( nodes )
end
function t.draw( nodes )
lg.setPointSize( 10 )
lg.setPointSize( 8 )
lg.setColor( 1, 1, 1, 0.5 )
lg.points( nodes.points )
return t.drawConnections( nodes )
end
function t.drawConnections( nodes )
for i, connection in pairs( nodes.connections ) do
for j in pairs( connection ) do
@ -88,9 +99,6 @@ function t.draw( nodes )
lg.line( ix, iy, fx, fy )
end
end
end
function t.drawConnections( nodes )
end