314 lines
8.6 KiB
Lua
314 lines
8.6 KiB
Lua
|
-- scenes/world :: a world scene, able to handle actors that'll act like
|
||
|
-- in a videogame world, using a bump2D/3D engine.
|
||
|
-- Actors are technically all 3D, but can work as simple 2D objects
|
||
|
|
||
|
--[[
|
||
|
Copyright © 2024 Kazhnuz
|
||
|
|
||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||
|
this software and associated documentation files (the "Software"), to deal in
|
||
|
the Software without restriction, including without limitation the rights to
|
||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||
|
subject to the following conditions:
|
||
|
|
||
|
The above copyright notice and this permission notice shall be included in all
|
||
|
copies or substantial portions of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
]]
|
||
|
|
||
|
local Scene = require "framework.scenes.minimal"
|
||
|
local World = Scene:extend()
|
||
|
|
||
|
local Bump = require("framework.libs.bump")
|
||
|
local Bump3D = require("framework.libs.bump-3dpd")
|
||
|
local Sti = require("framework.scenes.world.maps.tiled")
|
||
|
|
||
|
local Camera = require("framework.scenes.world.camera.minimal")
|
||
|
|
||
|
local Vector3D = require "framework.libs.brinevector3D"
|
||
|
|
||
|
local function _tryFunction(toUpdate, dt, func)
|
||
|
if (toUpdate ~= nil) then
|
||
|
func(dt)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:new(datas)
|
||
|
assert(self.def ~= nil, "Definition not found, world should be created with the function world()")
|
||
|
self:_initActorList(self.def.actorPath)
|
||
|
|
||
|
local map = self.def.defaultMap
|
||
|
if (datas ~= nil) then
|
||
|
map = datas.map or self.def.defaultMap
|
||
|
end
|
||
|
|
||
|
self:_initMap(map)
|
||
|
|
||
|
self.actors = {}
|
||
|
self.bodies = Bump3D.newWorld(50)
|
||
|
self.shapes = Bump.newWorld(50)
|
||
|
|
||
|
self.camera = Camera(self, self.def.camera)
|
||
|
|
||
|
World.super.new(self)
|
||
|
|
||
|
self:_load()
|
||
|
end
|
||
|
|
||
|
function World:onLoad()
|
||
|
|
||
|
end
|
||
|
|
||
|
function World:update(dt)
|
||
|
_tryFunction(self.map, dt, function(dt) self.map:update(dt) end)
|
||
|
_tryFunction(self.player, dt, function(dt) self.player:updateActor(dt) end)
|
||
|
_tryFunction(self.camera, dt, function(dt) self.camera:update(dt) end)
|
||
|
for _, actor in ipairs(self.actors) do
|
||
|
-- TODO: use camera to handle which actor are updated or updatable
|
||
|
actor:updateActor(dt)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:draw()
|
||
|
self.camera:attachView()
|
||
|
self:drawLowerLayers()
|
||
|
self:drawShapes()
|
||
|
self:drawUpperLayers()
|
||
|
self.camera:detachView()
|
||
|
if (self.player ~= nil) then
|
||
|
self.player:drawHUD()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:drawUpperLayers()
|
||
|
if (self.map ~= nil) then
|
||
|
self.map:drawUpperLayers()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:drawLowerLayers()
|
||
|
if (self.map ~= nil) then
|
||
|
self.map:drawLowerLayers()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:addPlayer(x, y, z)
|
||
|
self:addActor("player", { x = x, y = y, z = z })
|
||
|
end
|
||
|
|
||
|
-- Callbacks
|
||
|
-- Pass some callbacks to the player
|
||
|
|
||
|
-- Map handling
|
||
|
-- TODO
|
||
|
|
||
|
function World:_initMap(map)
|
||
|
if (map ~= nil) then
|
||
|
print("Map " .. map .. " will be loaded")
|
||
|
self.map = Sti(self, map)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:_load()
|
||
|
if (self.map ~= nil) then
|
||
|
print("loading objects...")
|
||
|
self.map:loadObjects()
|
||
|
end
|
||
|
self:onLoad()
|
||
|
end
|
||
|
|
||
|
function World:getBox()
|
||
|
local x, y, w, h
|
||
|
if (self.map ~= nil) then
|
||
|
x, y, w, h = self.map:getBox()
|
||
|
else
|
||
|
x, y = 0, 0
|
||
|
w, h = core.screen:getDimensions()
|
||
|
end
|
||
|
return {x = x, y = y, z = 0}, {w = w, h = h, d = 1}
|
||
|
end
|
||
|
|
||
|
-- ActorList Handling
|
||
|
-- Handle which actor can be loaded
|
||
|
|
||
|
function World:_initActorList(actorPath)
|
||
|
assert(actorPath ~= nil, "The actorPath variable must be set and contain the path where the actor can be loaded")
|
||
|
self.actorPath = actorPath
|
||
|
|
||
|
self.index = {}
|
||
|
self.obj = {}
|
||
|
|
||
|
self:_indexActors()
|
||
|
|
||
|
self:_prepareActorObject("gfx")
|
||
|
end
|
||
|
|
||
|
function World:_prepareActorObject(name)
|
||
|
local Actor = require("framework.scenes.world.actors." .. name)
|
||
|
self.obj[name] = Actor
|
||
|
end
|
||
|
|
||
|
function World:_getActorObject(name)
|
||
|
if (self.obj[name] ~= nil) then
|
||
|
return self.obj[name]
|
||
|
else
|
||
|
local Actor = require(self.actorPath .. "." .. name)
|
||
|
self.obj[name] = Actor
|
||
|
return Actor
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:_indexActors(pathToCheck, parent)
|
||
|
if (pathToCheck == nil) then
|
||
|
pathToCheck = string.gsub(self.actorPath, "%.", "/") .. "/"
|
||
|
end
|
||
|
if (parent == nil) then
|
||
|
parent = ""
|
||
|
end
|
||
|
|
||
|
self:_scanFolder(pathToCheck, parent)
|
||
|
end
|
||
|
|
||
|
function World:_scanFolder(path, parent)
|
||
|
local files = love.filesystem.getDirectoryItems(path)
|
||
|
for i, file in ipairs(files) do
|
||
|
local filepath = path .. file
|
||
|
|
||
|
if (love.filesystem.getInfo(filepath).type == "file") then
|
||
|
local data = love.filesystem.newFileData(filepath)
|
||
|
local extension = data:getExtension()
|
||
|
local fileName = string.sub(file, 1, #file - #extension - 1)
|
||
|
|
||
|
if (extension == "lua") then
|
||
|
self.index[parent .. fileName] = true
|
||
|
--print(filepath, parent .. fileName)
|
||
|
end
|
||
|
else
|
||
|
self:_scanFolder(path .. file .. "/", parent .. file .. ".")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Actor handling
|
||
|
-- Make it easy to handle actors
|
||
|
|
||
|
function World:isActorIndexed(name)
|
||
|
if (self.obj[name] ~= nil) then
|
||
|
return true
|
||
|
end
|
||
|
return (self.index[name] == true)
|
||
|
--return worldUtils.isModuleAvailable(self.actorPath .. "." .. name)
|
||
|
end
|
||
|
|
||
|
function World:newActor(name, x, y, z, properties, mapname)
|
||
|
-- TODO: handle properties, especially for gizmos
|
||
|
return self:addActor(name, { x = x, y = y, z = z })
|
||
|
end
|
||
|
|
||
|
function World:addActor(name, position)
|
||
|
local Actor = self:_getActorObject(name)
|
||
|
if (name == "player") then
|
||
|
self.player = Actor(self, position)
|
||
|
else
|
||
|
table.insert(self.actors, Actor(self, position))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:removeActor(actor)
|
||
|
for i,v in ipairs(self.actors) do
|
||
|
if v == actor then
|
||
|
table.remove(self.actors, i)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Bodies handling
|
||
|
-- Handle collisions and stuff. They're handled in 3D by default
|
||
|
|
||
|
local function _getBodyPosition(body)
|
||
|
if (body.getPosition ~= nil) then
|
||
|
return body:getPosition()
|
||
|
else
|
||
|
return body.position
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function World:isCollisionIndexed(name)
|
||
|
return (self.def.collisions ~= nil and self.def.collisions[name] ~= nil)
|
||
|
end
|
||
|
|
||
|
function World:newCollision(name, x, y, z, w, h, d)
|
||
|
local bodyDef = self.def.collisions[name]
|
||
|
local body = {
|
||
|
name = name,
|
||
|
type = bodyDef.type,
|
||
|
position = Vector3D(x, y, z or 0),
|
||
|
dimensions = { w = w, h = h, d = d or 1 },
|
||
|
isSolid = bodyDef.isSolid,
|
||
|
}
|
||
|
self:registerBody(body)
|
||
|
end
|
||
|
|
||
|
function World:registerBody(body)
|
||
|
local position = _getBodyPosition(body)
|
||
|
return self.bodies:add(body, position.x, position.y, position.z, body.dimensions.w, body.dimensions.h,
|
||
|
body.dimensions.d)
|
||
|
end
|
||
|
|
||
|
function World:updateBody(body)
|
||
|
local position = _getBodyPosition(body)
|
||
|
return self.bodies:update(body, position.x, position.y, position.z, body.dimensions.w, body.dimensions.h,
|
||
|
body.dimensions.d)
|
||
|
end
|
||
|
|
||
|
function World:removeBody(body)
|
||
|
return self.bodies:remove(body)
|
||
|
end
|
||
|
|
||
|
function World:checkCollisionAtPoint(body, position, filter)
|
||
|
local x, y, z, cols, collNumber = self.bodies:check(body, position.x, position.y, position.z, filter)
|
||
|
return Vector3D(x, y, z or 0), cols, collNumber
|
||
|
end
|
||
|
|
||
|
-- Shape handling
|
||
|
-- Handle shapes, which make know what object is visible or not
|
||
|
|
||
|
function World:registerShape(actor)
|
||
|
local position, dimensions = actor:getShape()
|
||
|
return self.shapes:add(actor, position.x, position.y, dimensions.w, dimensions.h)
|
||
|
end
|
||
|
|
||
|
function World:updateShape(actor)
|
||
|
local position, dimensions = actor:getShape()
|
||
|
return self.shapes:update(actor, position.x, position.y, dimensions.w, dimensions.h)
|
||
|
end
|
||
|
|
||
|
function World:removeShape(actor)
|
||
|
return self.shapes:remove(actor)
|
||
|
end
|
||
|
|
||
|
function World:checkShapeIntersection(actor, position)
|
||
|
return self.shapes:check(actor, position.x, position.y)
|
||
|
end
|
||
|
|
||
|
function World:getShapeInRect(position, dimensions)
|
||
|
return self.shapes:queryRect(position.x, position.y, dimensions.w, dimensions.h)
|
||
|
end
|
||
|
|
||
|
function World:drawShapes()
|
||
|
local position, dimensions = self.camera:getViewCoordinate()
|
||
|
local shapes = self:getShapeInRect(position, dimensions)
|
||
|
for _, shape in ipairs(shapes) do
|
||
|
shape:draw()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return World
|