306 lines
10 KiB
Lua
306 lines
10 KiB
Lua
-- Cat Haven - UI System
|
|
-- HUD, menus, cat logs, inventory
|
|
|
|
local ui = {}
|
|
|
|
local UIState = {
|
|
showInventory = false,
|
|
showCatLog = true,
|
|
hoveredItem = nil,
|
|
selectedFurniture = nil,
|
|
inventoryPage = 1
|
|
}
|
|
|
|
-- Cached module references
|
|
local core = nil
|
|
local voxel = nil
|
|
|
|
-- HUD rendering
|
|
function ui.renderHUD(pass)
|
|
if not pass then return end
|
|
|
|
if not core then core = require('src.core') end
|
|
if not voxel then voxel = require('src.voxel.init') end
|
|
|
|
local windowWidth, windowHeight = lovr.system.getWindowDimensions()
|
|
if not windowWidth then windowWidth, windowHeight = 1280, 720 end
|
|
|
|
-- Calculate scale based on window size
|
|
local scale = math.min(windowWidth, windowHeight) / 1000
|
|
|
|
-- Happiness meter
|
|
local happiness = core.getHappinessPercentage and core.getHappinessPercentage() or 0
|
|
local barWidth = 300 * scale
|
|
local barHeight = 20 * scale
|
|
local x = windowWidth / 2 - barWidth / 2
|
|
local y = 50 * scale
|
|
|
|
-- Background
|
|
pass:box('fill', x - 5 * scale, y - 5 * scale, 0, barWidth + 10 * scale, barHeight + 10 * scale, 0.01, { 0, 0, 0, 0.5 })
|
|
|
|
-- Bar background
|
|
pass:box('fill', x, y, 0, barWidth, barHeight, 0.01, { 0.3, 0.3, 0.3 })
|
|
|
|
-- Happiness fill
|
|
local fillWidth = barWidth * (happiness / 100)
|
|
local color = { 0.5, 0.8, 0.5 }
|
|
if happiness < 50 then color = { 0.8, 0.6, 0.5 }
|
|
elseif happiness < 80 then color = { 0.8, 0.8, 0.5 }
|
|
end
|
|
pass:box('fill', x, y, 0, fillWidth, barHeight, 0.01, color)
|
|
|
|
-- Text
|
|
pass:text(string.format('Perfect Cat Happiness: %d%%', happiness), x + barWidth / 2, y + barHeight, 0, 0.02 * scale, { 1, 1, 1 }, 'center')
|
|
|
|
-- Cat count
|
|
local catCount = core.state and #core.state.cats or 0
|
|
local maxCats = core.CONFIG and core.CONFIG.maxCats or 8
|
|
pass:text(string.format('Cats: %d/%d', catCount, maxCats), windowWidth - 200 * scale, 50 * scale, 0, 0.02 * scale)
|
|
|
|
-- Game time
|
|
local gameTime = core.state and core.state.gameTime or 0
|
|
local hours = math.floor(gameTime / 3600)
|
|
local minutes = math.floor((gameTime % 3600) / 60)
|
|
pass:text(string.format('Time: %02d:%02d', hours, minutes), windowWidth - 200 * scale, 80 * scale, 0, 0.02 * scale)
|
|
|
|
-- Controls hint
|
|
pass:text('RThumb: Save/Load | ESC: Pause', 50 * scale, windowHeight - 50 * scale, 0, 0.02 * scale)
|
|
|
|
-- Inventory (if open)
|
|
if UIState.showInventory then
|
|
ui.renderInventory(pass, windowWidth, windowHeight, scale)
|
|
end
|
|
|
|
-- Cat log (if open)
|
|
if UIState.showCatLog then
|
|
ui.renderCatLog(pass, windowWidth, scale)
|
|
end
|
|
|
|
-- Placement preview
|
|
if core.state and core.state.placementMode and UIState.selectedFurniture then
|
|
ui.renderPlacementPreview(pass, windowWidth, windowHeight)
|
|
end
|
|
end
|
|
|
|
function ui.renderInventory(pass, windowWidth, windowHeight, scale)
|
|
if not core then core = require('src.core') end
|
|
|
|
local x = windowWidth / 2 - 300 * scale
|
|
local y = windowHeight / 2 - 200 * scale
|
|
local width = 600 * scale
|
|
local height = 400 * scale
|
|
|
|
-- Background
|
|
pass:box('fill', x, y, 0, width, height, 0.01, { 0, 0, 0, 0.8 })
|
|
|
|
-- Title
|
|
pass:text('Inventory', x + width / 2, y + 30 * scale, 0, 0.03 * scale, { 1, 1, 1 }, 'center')
|
|
|
|
-- Furniture items
|
|
local itemHeight = 50 * scale
|
|
local startX = x + 20 * scale
|
|
local startY = y + 80 * scale
|
|
|
|
if core.furnitureTypes then
|
|
for i, furn in ipairs(core.furnitureTypes) do
|
|
local itemY = startY + (i - 1) * itemHeight
|
|
|
|
-- Item box
|
|
pass:box('fill', startX, itemY, 0, width - 40 * scale, itemHeight - 10 * scale, 0.01, { 0.2, 0.2, 0.2 })
|
|
|
|
if UIState.selectedFurniture == furn.id then
|
|
pass:box('line', startX, itemY, 0, width - 40 * scale, itemHeight - 10 * scale, 0.01, { 1, 1, 0 }, 0.02)
|
|
end
|
|
|
|
-- Icon
|
|
pass:box('fill', startX + 10 * scale, itemY + 10 * scale, 0, 30 * scale, 30 * scale, 0.01, furn.color or { 1, 1, 1 })
|
|
|
|
-- Text
|
|
local color = { 1, 1, 1 }
|
|
if UIState.selectedFurniture == furn.id then color = { 1, 1, 0 } end
|
|
|
|
pass:text(furn.name, startX + 50 * scale, itemY + 20 * scale, 0, 0.02 * scale, color)
|
|
local happinessBonus = math.floor((furn.baseHappiness or 0.5) * 100)
|
|
pass:text(string.format('Happiness: +%d%%', happinessBonus), startX + 250 * scale, itemY + 20 * scale, 0, 0.015 * scale)
|
|
end
|
|
end
|
|
|
|
-- Close button
|
|
local closeY = y + height - 40 * scale
|
|
pass:box('fill', x + width - 100 * scale, closeY, 0, 80 * scale, 30 * scale, 0.01, { 0.8, 0.3, 0.3 })
|
|
pass:text('Close', x + width - 60 * scale, closeY + 15 * scale, 0, 0.02 * scale)
|
|
end
|
|
|
|
function ui.renderCatLog(pass, windowWidth, scale)
|
|
if not core then core = require('src.core') end
|
|
|
|
local x = windowWidth - 300 * scale
|
|
local y = 150 * scale
|
|
local width = 280 * scale
|
|
local height = 300 * scale
|
|
|
|
-- Background
|
|
pass:box('fill', x, y, 0, width, height, 0.01, { 0, 0, 0, 0.6 })
|
|
|
|
-- Title
|
|
pass:text('Cat Log', x + width / 2, y + 25 * scale, 0, 0.02 * scale, { 1, 1, 1 }, 'center')
|
|
|
|
-- Log entries
|
|
local entries = core.state and core.state.catLog or {}
|
|
local maxEntries = 8
|
|
local startY = y + 60 * scale
|
|
local lineHeight = 30 * scale
|
|
|
|
for i = math.max(1, #entries - maxEntries + 1), #entries do
|
|
local entry = entries[i]
|
|
local entryY = startY + (i - math.max(1, #entries - maxEntries + 1)) * lineHeight
|
|
|
|
local color = { 1, 1, 1 }
|
|
if entry and entry.type == 'arrival' then color = { 0.5, 1, 0.5 }
|
|
elseif entry and entry.type == 'departure' then color = { 1, 0.5, 0.5 }
|
|
end
|
|
|
|
if entry then
|
|
local label = entry.type == 'arrival' and 'Arrived' or 'Left'
|
|
pass:text(string.format('%s: %s', label, entry.catName or 'Unknown'), x + 10 * scale, entryY, 0, 0.015 * scale, color)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ui.renderPlacementPreview(pass, windowWidth, windowHeight)
|
|
if not core then core = require('src.core') end
|
|
if not voxel then voxel = require('src.voxel.init') end
|
|
|
|
-- Get mouse position in 3D
|
|
local rayStart, rayEnd = ui.getWorldRay(windowWidth, windowHeight)
|
|
local direction = { rayEnd[1] - rayStart[1], rayEnd[2] - rayStart[2], rayEnd[3] - rayStart[3] }
|
|
local length = math.sqrt(direction[1]^2 + direction[2]^2 + direction[3]^2)
|
|
if length > 0.001 then
|
|
direction = { direction[1]/length, direction[2]/length, direction[3]/length }
|
|
else
|
|
direction = { 0, 0, -1 }
|
|
end
|
|
|
|
-- Raycast to floor
|
|
local results = voxel.raycast and voxel.raycast(rayStart, direction, 10) or {}
|
|
|
|
if #results > 0 then
|
|
local hit = results[1]
|
|
local pos = hit.position
|
|
|
|
-- Get furniture size
|
|
local furnType = nil
|
|
if core.furnitureTypes then
|
|
for _, t in ipairs(core.furnitureTypes) do
|
|
if t.id == UIState.selectedFurniture then
|
|
furnType = t
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if furnType and furnType.size then
|
|
local size = furnType.size
|
|
local half = { size[1]/2, size[2]/2, size[3]/2 }
|
|
|
|
pass:box('line', pos.x, pos.y, pos.z, size[1], size[2], size[3], { 1, 1, 0, 0.5 }, 0.1)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ui.getWorldRay(windowWidth, windowHeight)
|
|
if not core then core = require('src.core') end
|
|
|
|
local camera = core.state and core.state.camera
|
|
if not camera then camera = { position = { 0, 2, 0 }, orientation = { 0, 0, 0, 1 }, fov = 90 } end
|
|
|
|
local mouseX, mouseY = lovr.system.getMousePosition()
|
|
if not mouseX then
|
|
mouseX = windowWidth / 2
|
|
mouseY = windowHeight / 2
|
|
end
|
|
|
|
-- Simple perspective projection
|
|
local fov = math.rad(camera.fov or 90)
|
|
local aspect = windowWidth / windowHeight
|
|
|
|
local x = (mouseX / windowWidth - 0.5) * aspect * 2 * math.tan(fov / 2)
|
|
local y = -(mouseY / windowHeight - 0.5) * 2 * math.tan(fov / 2)
|
|
local z = -1
|
|
|
|
-- Normalize direction
|
|
local len = math.sqrt(x*x + y*y + z*z)
|
|
if len > 0.001 then
|
|
x, y, z = x/len, y/len, z/len
|
|
end
|
|
|
|
-- Apply camera rotation (simplified - just use position)
|
|
local dir = { x, y, z }
|
|
|
|
local rayStart = { camera.position[1] or 0, camera.position[2] or 2, camera.position[3] or 0 }
|
|
local rayEnd = { rayStart[1] + dir[1]*10, rayStart[2] + dir[2]*10, rayStart[3] + dir[3]*10 }
|
|
|
|
return rayStart, rayEnd
|
|
end
|
|
|
|
function ui.handleMousePress(x, y, button)
|
|
local windowWidth, windowHeight = lovr.system.getWindowDimensions()
|
|
if not windowWidth then windowWidth, windowHeight = 1280, 720 end
|
|
|
|
-- Check inventory close button
|
|
if UIState.showInventory then
|
|
local scale = math.min(windowWidth, windowHeight) / 1000
|
|
local closeX = windowWidth / 2 + 300 * scale - 100 * scale
|
|
local closeY = windowHeight / 2 + 200 * scale - 40 * scale
|
|
local closeWidth = 80 * scale
|
|
local closeHeight = 30 * scale
|
|
|
|
if x >= closeX and x <= closeX + closeWidth and y >= closeY and y <= closeY + closeHeight then
|
|
UIState.showInventory = false
|
|
if core and core.state then core.state.placementMode = false end
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Check furniture selection in inventory
|
|
if UIState.showInventory then
|
|
local scale = math.min(windowWidth, windowHeight) / 1000
|
|
local startX = windowWidth / 2 - 300 * scale + 20 * scale
|
|
local startY = windowHeight / 2 - 200 * scale + 80 * scale
|
|
local itemHeight = 50 * scale
|
|
|
|
if core and core.furnitureTypes then
|
|
for i, furn in ipairs(core.furnitureTypes) do
|
|
local itemY = startY + (i - 1) * itemHeight
|
|
local width = 600 * scale - 40 * scale
|
|
local height = itemHeight - 10 * scale
|
|
|
|
if x >= startX and x <= startX + width and y >= itemY and y <= itemY + height then
|
|
if UIState.selectedFurniture == furn.id then
|
|
UIState.selectedFurniture = nil
|
|
if core and core.state then core.state.placementMode = false end
|
|
else
|
|
UIState.selectedFurniture = furn.id
|
|
if core and core.state then core.state.placementMode = true end
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ui.updateHUD()
|
|
-- Update any dynamic UI elements
|
|
end
|
|
|
|
function ui.toggleInventory()
|
|
UIState.showInventory = not UIState.showInventory
|
|
end
|
|
|
|
function ui.toggleCatLog()
|
|
UIState.showCatLog = not UIState.showCatLog
|
|
end
|
|
|
|
return ui
|