love.window.setMode(960, 540) local screen_width, screen_height = love.graphics.getDimensions() math.randomseed(os.time()) -- returns selected_object, distance_squared_to_object (if object_list is empty, returns nil, math.huge) local function get_closest_object(object_list, current_object) local selected_object, distance_squared_to_object = nil, math.huge for i = 1, #object_list do local function compare_object() local comparison_object = object_list[i] if comparison_object == current_object then return end local comparison_distance_squared = (current_object.position_x - comparison_object.position_x)^2 + (current_object.position_y - comparison_object.position_y)^2 if comparison_distance_squared < distance_squared_to_object then selected_object = comparison_object distance_squared_to_object = comparison_distance_squared end end compare_object() end return selected_object, distance_squared_to_object end -- source_object and destination_object can be nil to create/delete resources -- returns cargo_amount (actual amount transferred) local function transfer_cargo(source_object, destination_object, cargo_type, cargo_amount) if not source_object then source_object = { cargo_contents = { [cargo_type] = math.huge }, cargo_free_space = 0, } end if not destination_object then destination_object = { cargo_contents = {}, cargo_free_space = math.huge, } end if not source_object.cargo_contents[cargo_type] then return 0 end if not destination_object.cargo_contents[cargo_type] then destination_object.cargo_contents[cargo_type] = 0 end if cargo_amount > source_object.cargo_contents[cargo_type] then cargo_amount = source_object.cargo_contents[cargo_type] end if cargo_amount > destination_object.cargo_free_space then cargo_amount = destination_object.cargo_free_space end destination_object.cargo_contents[cargo_type] = destination_object.cargo_contents[cargo_type] + cargo_amount destination_object.cargo_free_space = destination_object.cargo_free_space - cargo_amount source_object.cargo_contents[cargo_type] = source_object.cargo_contents[cargo_type] - cargo_amount source_object.cargo_free_space = source_object.cargo_free_space + cargo_amount if source_object.cargo_contents[cargo_type] <= 0 then source_object.cargo_contents[cargo_type] = nil end return cargo_amount end local function check_cargo_amount(source_object, cargo_type, cargo_amount) if not source_object.cargo_contents[cargo_type] then return false end if source_object.cargo_contents[cargo_type] >= cargo_amount then return true end return false end local ore_types = { "iron ore", "copper ore", "warp fuel", } -- local cargo_types = {} -- for i = 1, #ore_types do -- cargo_types[#cargo_types + 1] = ore_types[i] -- end -- for i = 1, #cargo_types do -- cargo_types[cargo_types[i]] = i -- end -- TODO review my previous orbits code to add extremely slow background orbits to this -- planets/stars/stations always have fixed orbits; ships have a fixed orbital acceleration but have their own additional -- the "zero relative velocity" key will be a burn whatever amount of fuel/acceleration necessary to match local acceleration -- which.. since the currently stored velocity is a COMPLETELY SEPARATE SYSTEM - is literally just zeroing velocity which makes it even simpler to execute local player_ship = { position_x = screen_width / 2, position_y = screen_height / 2, velocity_x = 0, velocity_y = 0, acceleration = 100, radar_size = 10, cargo_max_space = 100, cargo_free_space = 75, cargo_contents = { ["warp fuel"] = 25, }, cargo_fill_speed = 10, -- TODO fuel capacity and use (idle and while acceleration) -- TODO cargo / mass affects acceleration } local resource_points = {} -- NOTE they're "planets" but I intend for them to be multiple things tbh local function add_resource_points(point_count) for i = 1, point_count do local function make_resource_point() local current_point = { position_x = math.random() * screen_width, position_y = math.random() * screen_height, radar_size = 10, cargo_max_space = 100000, cargo_free_space = 100000, cargo_contents = {}, } -- NOTE can select literally anything local selected_type = ore_types[math.random(#ore_types)] local cargo_amount = math.random() * 10000 transfer_cargo(nil, current_point, selected_type, cargo_amount) local closest_object, distance_squared_to_object = get_closest_object(resource_points, current_point) if closest_object and distance_squared_to_object <= (closest_object.radar_size^2 + current_point.radar_size^2) * 1.5 then return end table.insert(resource_points, current_point) return true end if not make_resource_point() then i = i - 1 end end end add_resource_points(math.random(5)) local last_message = "" function love.update(dt) -- NOTE this control scheme makes diagonal travel faster than horizontal/vertical if love.keyboard.isDown("w") or love.keyboard.isDown("up") then player_ship.velocity_y = player_ship.velocity_y - player_ship.acceleration * dt end if love.keyboard.isDown("a") or love.keyboard.isDown("left") then player_ship.velocity_x = player_ship.velocity_x - player_ship.acceleration * dt end if love.keyboard.isDown("s") or love.keyboard.isDown("down") then player_ship.velocity_y = player_ship.velocity_y + player_ship.acceleration * dt end if love.keyboard.isDown("d") or love.keyboard.isDown("right") then player_ship.velocity_x = player_ship.velocity_x + player_ship.acceleration * dt end -- NOTE this zeros out diagonal velocities faster than horizontal/vertical if love.keyboard.isDown("lshift") or love.keyboard.isDown("rshift") then if player_ship.velocity_x > 0 then player_ship.velocity_x = math.max(0, player_ship.velocity_x - player_ship.acceleration * dt) elseif player_ship.velocity_x < 0 then player_ship.velocity_x = math.min(0, player_ship.velocity_x + player_ship.acceleration * dt) end if player_ship.velocity_y > 0 then player_ship.velocity_y = math.max(0, player_ship.velocity_y - player_ship.acceleration * dt) elseif player_ship.velocity_y < 0 then player_ship.velocity_y = math.min(0, player_ship.velocity_y + player_ship.acceleration * dt) end end player_ship.position_x = player_ship.position_x + player_ship.velocity_x * dt player_ship.position_y = player_ship.position_y + player_ship.velocity_y * dt -- TODO make this toggleable instead of requiring holding a key if love.keyboard.isDown("space") then local function collect_resource() local closest_object, distance_squared_to_object = get_closest_object(resource_points, player_ship) -- TODO ideally, there should be an indicator that you're close enough instead of relying on messages (to be fair, the radius is the indicator) if distance_squared_to_object > closest_object.radar_size^2 then last_message = "Too far to pick up cargo." return end if player_ship.cargo_free_space <= 0 then last_message = "No cargo space left." return end -- TODO allow selecting cargo type local cargo_type = next(closest_object.cargo_contents) if not cargo_type then last_message = "There is nothing here." return end local cargo_amount = math.min(player_ship.cargo_fill_speed * dt, closest_object.cargo_contents[cargo_type]) if cargo_amount <= 0 then last_message = "There is no cargo to pick up." return end cargo_amount = transfer_cargo(closest_object, player_ship, cargo_type, cargo_amount) last_message = "Transfered " .. math.floor(cargo_amount * 100) / 100 .. " " .. cargo_type .. "." end collect_resource() end end local font = love.graphics.getFont() local font_height = font:getHeight() function love.draw() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("line", player_ship.position_x - player_ship.radar_size / 2, player_ship.position_y - player_ship.radar_size / 2, player_ship.radar_size, player_ship.radar_size) for i = 1, #resource_points do local resource_point = resource_points[i] love.graphics.circle("line", resource_point.position_x, resource_point.position_y, resource_point.radar_size) end love.graphics.print(last_message, 1, 1) local inventory_status = {} for cargo_type, cargo_amount in pairs(player_ship.cargo_contents) do table.insert(inventory_status, { cargo_type = cargo_type, cargo_amount = cargo_amount }) end table.sort(inventory_status, function(A, B) return A.cargo_amount > B.cargo_amount end) for i = 1, #inventory_status do inventory_status[i] = inventory_status[i].cargo_type .. ": " .. math.floor(inventory_status[i].cargo_amount * 10) / 10 end local cargo_message = "Cargo: " .. math.min(math.floor((player_ship.cargo_max_space - player_ship.cargo_free_space) * 10) / 10, player_ship.cargo_max_space) .. "/" .. player_ship.cargo_max_space .. ". " .. table.concat(inventory_status, ", ") love.graphics.print(cargo_message, 1, screen_height - font_height - 1) end function love.keypressed(key) if key == "escape" then love.event.quit() elseif key == "1" then -- 25x iron ore -> 5 cargo space if not check_cargo_amount(player_ship, "iron ore", 25) then last_message = "You do not have enough iron ore to upgrade your cargo space. (need 25)" return end last_message = "Cargo space increased by 5." transfer_cargo(player_ship, nil, "iron ore", 25) player_ship.cargo_max_space = player_ship.cargo_max_space + 5 player_ship.cargo_free_space = player_ship.cargo_free_space + 5 elseif key == "2" then -- 25x copper ore -> 1.1x acceleration if not check_cargo_amount(player_ship, "copper ore", 25) then last_message = "You do not have enough copper ore to upgrade your acceleration. (need 25)" return end last_message = "Acceleration increased by 10%." transfer_cargo(player_ship, nil, "copper ore", 25) player_ship.acceleration = player_ship.acceleration * 1.1 elseif key == "return" or key == "kpenter" then -- NOTE soft lock is possible when no warp fuel is generated / left / you use it all up without realizing if not check_cargo_amount(player_ship, "warp fuel", 5) then last_message = "You do not have enough warp fuel to go to a new system. (need 5)" return end -- TODO require not being moving to warp last_message = "Warped to new system!" transfer_cargo(player_ship, nil, "warp fuel", 5) player_ship.position_x = screen_width / 2 player_ship.position_y = screen_height / 2 resource_points = {} add_resource_points(math.random(5)) end end