project-witchy/imperium-porcorum.love/core/modules/world/baseworld.lua

438 lines
11 KiB
Lua

-- baseworld.lua :: the base world object, that contain just a fast implementation
-- of a 2D world. It doesn't support collision and stuff.
--[[
Copyright © 2019 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 cwd = (...):gsub('%.baseworld$', '') .. "."
local BaseWorld = Object:extend()
local Sti = require(cwd .. "libs.sti")
local CameraSystem = require(cwd .. "camera")
local PADDING_VALUE = 10/100
-- INIT FUNCTIONS
-- All functions to init the world and the map
function BaseWorld:new(scene, actorlist, mapfile)
self.scene = scene
self.actorlist = actorlist
self.mapfile = mapfile
self.cameras = CameraSystem(self)
self:initActors()
self:initPlayers()
self:setActorList(self.actorlist)
self:initMap(self.mapfile)
self:setGravity()
self:register()
self.isActive = true
end
function BaseWorld:setActorList(actorlist)
if actorlist == nil then
error("FATAL: You must set an actorlist to your world")
else
self.obj = require(actorlist)
end
end
function BaseWorld:initMap(mapfile)
self.haveMap = false
self.haveBackgroundColor = false
self.backcolor = {128, 128, 128}
self.mapfile = mapfile
end
function BaseWorld:setGravity(xgrav, ygrav, isDefault)
local xgrav = xgrav or 0
local ygrav = ygrav or 0
local isDefault = isDefault or 0
self.gravity = {}
self.gravity.xgrav, self.gravity.ygrav = xgrav, ygrav
self.gravity.isDefault = isDefault
end
function BaseWorld:register()
self.scene:registerWorld(self)
end
function BaseWorld:reset()
self:initActors()
self:initPlayers()
self:initMap(self.mapfile)
self.cameras:initViews()
collectgarbage()
self:loadMap()
end
-- ACTOR MANAGEMENT FUNCTIONS
-- Basic function to handle actors
function BaseWorld:initActors( )
self.actors = {}
self.currentCreationID = 0
end
function BaseWorld:newActor(name, x, y)
self.obj.index[name](self, x, y)
end
function BaseWorld:newCollision(name, x, y, w, h)
self.obj.collisions[name](self, x, y, w, h)
end
function BaseWorld:registerActor(actor)
actor.creationID = self.currentCreationID
self.currentCreationID = self.currentCreationID + 1
table.insert(self.actors, actor)
end
function BaseWorld:removeActor(actor)
for i,v in ipairs(self.actors) do
if v == actor then
table.remove(self.actors, i)
end
end
end
function BaseWorld:moveActor(actor, x, y, filter)
-- as the baseworld have no collision function, we return empty collision
-- datas, but from the same type than bump2D will return
return x, y, {}, 0
end
function BaseWorld:checkCollision(actor, x, y, filter)
-- as the baseworld have no collision function, we return empty collision
-- datas, but from the same type than bump2D will return
return x, y, {}, 0
end
function BaseWorld:queryRect(x, y, w, h)
local query = {}
local x2, y2 = x + w, y + h
for i,v in ipairs(self.actors) do
if (v.x >= x) and (v.x + v.w >= x1) and
(v.y >= y) and (v.y + v.h >= y1) then
table.insert(query, v)
end
end
return v
end
function BaseWorld:countActors()
return #self.actors
end
function BaseWorld:getActors()
return self.actors
end
function BaseWorld:getVisibleActors(id)
local camx, camy, camw, camh = self.cameras:getViewCoordinate(id)
local paddingw = camw * PADDING_VALUE
local paddingh = camh * PADDING_VALUE
local x = camx - paddingw
local y = camy - paddingh
local w = camw + paddingw * 2
local h = camh + paddingh * 2
return self:queryRect(x, y, w, h)
end
-- INFO FUNCTIONS
-- Give infos about the world
function BaseWorld:isActorIndexed(name)
return (self.obj.index[name] ~= nil)
end
function BaseWorld:isCollisionIndexed(name)
return (self.obj.collisions[name] ~= nil)
end
-- PLAYER MANAGEMENT FUNCTIONS
-- Basic function to handle player actors
function BaseWorld:loadMap()
local mapfile = self.mapfile
if mapfile == nil then
self.haveMap = false
self.haveBackgroundColor = false
self.backcolor = {128, 128, 128}
else
self.haveMap = true
self.map = Sti(mapfile)
self.haveBackgroundColor = true
self.backcolor = self.map.backgroundcolor or {128, 128, 128}
self:loadMapObjects()
end
end
function BaseWorld:initPlayers()
self.players = {}
self.playerNumber = 1
end
function BaseWorld:setPlayerNumber(playerNumber)
self.playerNumber = playerNumber or 1
end
function BaseWorld:addPlayer(actor, sourceid, haveCam)
local player = {}
player.actor = actor
player.sourceid = sourceid or 1
table.insert(self.players, player)
if (haveCam) then
local xx, yy = player.actor:getViewCenter()
self.cameras:addView(xx, yy, player.actor)
end
end
function BaseWorld:sendInputToPlayers(actor)
for i,v in ipairs(self.players) do
--TODO: make the player get from a selected source inputs
local keys = self.scene.sources[v.sourceid].keys
v.actor:getInput(keys)
end
end
function BaseWorld:removePlayer(actor)
for i,v in ipairs(self.players) do
if (v.actor == actor) then
table.remove(self.players, i)
end
end
end
-- MAP FUNCTIONS
-- All map wrappers
function BaseWorld:loadMapObjects()
self:loadMapCollisions()
self:loadMapPlayers()
self:loadMapActors()
end
function BaseWorld:loadMapCollisions()
for k, objectlayer in pairs(self.map.layers) do
if self:isCollisionIndexed(objectlayer.name) then
print("DEBUG: loading actors in " .. objectlayer.name .. " collision layer")
for k, object in pairs(objectlayer.objects) do
self:newCollision(objectlayer.name, object.x, object.y, object.width, object.height)
end
self.map:removeLayer(objectlayer.name)
end
end
end
function BaseWorld:loadMapActors()
for k, objectlayer in pairs(self.map.layers) do
if self:isActorIndexed(objectlayer.name) then
print("DEBUG: loading actors in " .. objectlayer.name .. " actor layer")
for k, object in pairs(objectlayer.objects) do
if (object.properties.batchActor) then
self:batchActor(objectlayer.name, object)
else
self:newActor(objectlayer.name, object.x, object.y)
end
end
self.map:removeLayer(objectlayer.name)
end
end
end
function BaseWorld:batchActor(name, object)
local gwidth = object.properties.gwidth or self.map.tilewidth
local gheight = object.properties.gheight or self.map.tileheight
local x = object.x
local y = object.y
local w = object.width
local h = object.height
local cellHor = math.ceil(w / gwidth)
local cellVert = math.ceil(h / gheight)
for i=1, cellHor do
for j=1, cellVert do
self:newActor(name, x + (i-1)*gwidth, y + (j-1)*gheight)
end
end
end
function BaseWorld:loadMapPlayers()
for k, objectlayer in pairs(self.map.layers) do
if (objectlayer.name == "player") then
print("DEBUG: loading actors in player layer")
local i = 1
for k, object in pairs(objectlayer.objects) do
if (i <= self.playerNumber) then
-- TODO: don't hardcode camera handling
self:addPlayer(self.obj.Player(self, object.x, object.y), i, true)
end
i = i + 1
end
self.map:removeLayer(objectlayer.name)
end
end
end
function BaseWorld:getDimensions()
if self.haveMap then
return self.map.width * self.map.tilewidth,
self.map.height * self.map.tileheight
else
return core.screen:getDimensions()
end
end
function BaseWorld:setBackgroundColor(r, g, b)
self.backcolor = {r, g, b}
self.haveBackgroundColor = true
end
function BaseWorld:removeBackgroundColor()
self.haveBackgroundColor = false
end
function BaseWorld:getBackgroundColor()
return self.backcolor[1]/256, self.backcolor[2]/256, self.backcolor[3]/256
end
-- Lock MANAGEMENT FUNCTIONS
-- Basic function to handle the lock
function BaseWorld:setActivity(activity)
self.isActive = activity
end
function BaseWorld:switchActivity()
self.isActive = (self.isActive == false)
end
function BaseWorld:getActivity()
return self.isActive
end
-- UPDATE FUNCTIONS
-- All update functions
function BaseWorld:update(dt)
self:updateMap(dt)
self:sendInputToPlayers(dt)
self:updateActors(dt)
self.cameras:update(dt)
end
function BaseWorld:updateActors(dt)
local actors = self:getActors()
for i,v in ipairs(actors) do
v:update(dt)
end
end
function BaseWorld:updateMap(dt)
if self.haveMap then
self.map:update(dt)
end
end
-- DRAW FUNCTIONS
-- All function to draw the map, world and actors
function BaseWorld:draw(dt)
self:drawBackgroundColor()
local camNumber = self.cameras:getViewNumber()
if (camNumber == 0) then
self:drawMap()
self:drawActors()
else
for i=1, camNumber do
self.cameras:attachView(i)
self:drawMap(i)
self:drawActors(i)
self.cameras:detachView(i)
self.cameras:drawHUD(i)
end
end
end
function BaseWorld:drawActors(id)
local actors
if (id == nil) then
actors = self:getActors()
else
actors = self:getVisibleActors(id)
end
table.sort(actors, function(a,b)
if (a.depth == b.depth) then
return a.creationID < b.creationID
else
return a.depth > b.depth
end
end)
for i,v in ipairs(actors) do
v:draw()
end
end
function BaseWorld:drawMap(id)
local tx, ty, scale = 0, 0, 1
if id ~= nil then
-- Du à la manière dont fonctionne STI, on est obligé de récupérer les info
-- de position de camera pour afficher la carte par rapport à ces infos
tx, ty = self.cameras:getViewCoordinate(id)
scale = self.cameras:getViewScale(id) or 1
local vx, vy = self.cameras:getOnScreenViewRelativePosition(id)
tx = math.floor(tx - math.abs(vx))
ty = math.floor(ty - math.abs(vy))
end
if self.haveMap then
self.map:draw(-tx, -ty, scale, scale)
end
end
function BaseWorld:drawBackgroundColor()
if self.haveBackgroundColor then
local r, g, b = self.backcolor[1], self.backcolor[2], self.backcolor[3]
love.graphics.setColor(r/256, g/256, b/256)
love.graphics.rectangle("fill", 0, 0, 480, 272)
utils.graphics.resetColor()
end
end
return BaseWorld