Compare commits

...

23 Commits

Author SHA1 Message Date
binarycat
577b7576cf added level in new format to repo 2022-03-16 14:21:35 -04:00
binarycat
fa4b2c86b5 somewhat kinda got things working again 2022-03-16 13:54:25 -04:00
binarycat
e8cef497d4 reworking level loading logic 2022-03-16 13:54:25 -04:00
binarycat
b3a12305da trying to do chunked level loading 2022-03-16 13:54:24 -04:00
lustlion
236e23177d function to improve entities moving on collisions and fixed accordingly
made math.round()
changed vector() and fixed accordingly
2022-03-16 18:50:10 +01:00
binarycat
4d94cc805d serialization function 2022-03-15 20:16:35 -04:00
lustlion
9c4b5431ee fix adjusting select to camera 2022-03-13 10:47:17 +01:00
lustlion
ba1c0f0c89 getPoints and getCoords 2022-03-13 10:43:07 +01:00
lustlion
ef632d50ee multiselecting entities! 2022-03-13 10:38:20 +01:00
lustlion
5bcf25a461 fix typos and functions 2022-03-13 10:36:19 +01:00
lustlion
1039479c47 Keybinds can be occupied (when being checked down) 2022-03-13 09:57:44 +01:00
lustlion
62555b4526 improvement to moveSpawns so it can move multiple spawns correctly 2022-03-13 09:42:35 +01:00
lustlion
a4af57ca6c cleanup useless function 2022-03-13 09:34:34 +01:00
binarycat
5189bef537 cleanup 2022-03-12 14:06:55 -05:00
binarycat
eab4cbbcdc use Rect:containsPoint in isThereObjectAt 2022-03-12 13:56:16 -05:00
binarycat
829963e080 add Rect, begin work on multiselect 2022-03-12 13:17:52 -05:00
binarycat
82246dc0c6 multiselect region can be drawn with the right mouse button 2022-03-12 13:16:39 -05:00
lustlion
3a5e0b395b fix indentation and cleaning 2022-03-12 18:28:35 +01:00
lustlion
feed65cf6d floored coords when adding or moving entity spawns 2022-03-12 18:20:25 +01:00
lustlion
1883bcd78b drawTextBox to use style table instead of a lot of arguments 2022-03-12 18:20:06 +01:00
lustlion
3c1746d914 particles time to be handled in frames instead of seconds optionally 2022-03-12 18:19:07 +01:00
binarycat
f091fba9f7 change editor_mode to editor.active, minor refactor, and start work on multiselect 2022-03-12 11:46:45 -05:00
binarycat
27f1dc71c0 added Rect class 2022-03-12 11:12:29 -05:00
29 changed files with 603 additions and 332 deletions

View File

@@ -77,3 +77,16 @@ function Camera:positionAt(x,y)
self.pos.y = math.floor((y/self.height)*self.height)
end
-- translate screen coordinates to game coordinates
function Camera:ptScreenToGame(pt)
return self.pos + pt
end
function Camera:mouseScreenPos()
return Point:new(love.mouse.getX(),love.mouse.getY()) / game.scale
end
-- return the mouse position as game coordinates
function Camera:mouseGamePos()
return self:ptScreenToGame(self:mouseScreenPos())
end

51
code/chunk.lua Normal file
View File

@@ -0,0 +1,51 @@
-- pieces of levels
Chunk = {all = {}}
Chunk.__index = Chunk
-- CLASS METHODS
-- box == nil for global chunks
function Chunk:new(filename, box)
local o = { filename = filename, box = box }
setmetatable(o, self)
self.all[o] = true
end
function Chunk:getExportList()
local r = {}
for chunk in pairs(self.all) do
table.insert(r, {chunk.filename, chunk.box})
end
return r
end
-- INSTANCE METHODS
function Chunk:containsPoint(pt)
return self.box == nil or self.box:containsPoint(pt)
end
function Chunk:load()
if self.loaded then
return
end
logPrint("loading chunk "..self.filename)
self.data = dofile(level_current.."/chunks/"..self.filename)
self.loaded = { rooms = {}, collisions = {} }
LevelTiles = self.data.tiles
indexLevelTiles()
optimizeTileObjects(self.loaded.collisions)
for _, v in ipairs(self.data.rooms or {}) do
local room = Collision:new(v[1],v[2],v[3],v[4])
table.insert(self.data.rooms, room)
table.insert(LoadedObjects.Rooms, room)
end
logPrint("loaded chunk with "..#self.loaded.collisions.." collisions")
end
function Chunk:save(chunkdir)
return love.filesystem.write(chunkdir.."/"..self.filename, "return "..serialize_lua_value(self.data))
end

View File

@@ -88,3 +88,7 @@ function Collision:draw(color)
love.graphics.setColor(0,1,90,0.5)
love.graphics.rectangle("line",self.from.x-Camera.pos.x, self.from.y-Camera.pos.y, self.width, self.height)
end
function Collision:asRect()
return Rect:fromCoords(self.from.x, self.from.y, self.to.x, self.to.y)
end

View File

@@ -1,17 +1,37 @@
assert(editor == nil)
editor = {
active = false,
room_mode = false,
--palette_mode = false,
palette = {
active = false,
scroll = Point:new(0, 0),
},
multiselect = {
active = false,
sweeping = false,
box = nil,
},
pan = { fixed = false, speed = 3 },
}
function stepEditor()
animateTiles()
local osweep = editor.multiselect.sweeping
editor.multiselect.sweeping = Keybind:checkDown(Keybind.editor.entity_select)
frameDebug("sweeping: "..tostring(editor.multiselect.sweeping))
if editor.multiselect.sweeping and not editor.multiselect.active then
print("multiselect enabled")
editor.multiselect.active = true
end
if not osweep and osweep ~= editor.multiselect.sweeping then
editor.multiselect.box = nil
end
if editor.multiselect.active then
doEditorMultiselect()
end
if Keybind:checkPressed(Keybind.editor.room_mode) then
if love.keyboard.isDown("lshift") then
editor.room_mode = "delete"
@@ -80,7 +100,7 @@ function stepEditor()
if name_prompt.canceled then return end
Prompt:new({
name = "filename",
input = "level.lua",
input = "unnamed_level",
func = function(file_prompt)
if file_prompt.canceled then return end
exportLevel(name_prompt.input, file_prompt.input)
@@ -89,13 +109,6 @@ function stepEditor()
end,
}):activate()
end
if Keybind:checkPressed(Keybind.debug.editor) then
editor_mode = not editor_mode
deselectSpawns()
createTileObjects()
restartGame()
end
end
function scrollEditor(y)
@@ -128,6 +141,10 @@ function drawEditor()
if editor.palette.active then
doEditorPalette()
end
if editor.multiselect.box ~= nil then
frameDebug("drawing multiselect "..tostring(editor.multiselect.box))
drawEditorMultiselect()
end
end
function doEditorEdit()
@@ -204,7 +221,9 @@ function doEditorEdit()
else
if Keybind:checkDown(Keybind.editor.entity_select) then
deselectSpawns()
selectSpawns(mouse_x,mouse_y)
if editor.multiselect.box then
selectSpawns(editor.multiselect.box)
end
end
if Keybind:checkDown(Keybind.editor.entity_move) then
moveSpawns(mouse_x,mouse_y)
@@ -353,7 +372,21 @@ end
function drawEditorRooms()
for _, room in pairs(LoadedObjects.Rooms) do
love.graphics.setColor(0,0,100,1)
love.graphics.rectangle("line",room.from.x-Camera.pos.x, room.from.y-Camera.pos.y, room.width, room.height)
love.graphics.setColor(0,0,1,1)
room:asRect():draw("line")
end
end
function drawEditorMultiselect()
love.graphics.setColor(0,1,1,1)
editor.multiselect.box:draw("line")
end
function doEditorMultiselect()
local mousept = Camera:mouseGamePos()
if editor.multiselect.box == nil then
editor.multiselect.box = Rect:fromPoints(mousept, mousept)
elseif editor.multiselect.sweeping then
editor.multiselect.box.max = mousept
end
end

View File

@@ -36,20 +36,30 @@ function Arrow:drawBackground()
end
function Arrow:doPhysics()
if not self:isCollidingAt(self.pos.x + self.vel.x, self.pos.y, LoadedObjects.Collisions) then
self.pos.x = self.pos.x + self.vel.x
else
self.stuck = true
end
if not self:isCollidingAt(self.pos.x, self.pos.y + self.vel.y, LoadedObjects.Collisions) then
self.pos.y = self.pos.y + self.vel.y
else
self.stuck = true
-- horizontal collision
self:moveX(
self.vel.x,
function()
self.stuck = true
end
)
if not self.stuck then
-- vertical collision
self:moveY(
self.vel.y,
function()
self.stuck = true
end
)
end
if self.stuck then
self.pos.x = self.pos.x + self.vel.x * (2/3)
self.pos.y = self.pos.y + self.vel.y * (2/3)
self.vel.x = 0
self.vel.y = 0
end
self:adjustLight()
end

View File

@@ -67,6 +67,13 @@ function CursedBook:doLogic()
elseif self.status == 4 then
end
if self.isFlying then
local random_x = math.random(-4, 4)/100
local random_y = math.random(-4, 4)/100
self.vel.x = self.vel.x + random_x
self.vel.y = self.vel.y + random_y
end
end
function CursedBook:handleAnimation()
@@ -101,15 +108,21 @@ function CursedBook:handleAnimation()
end
function CursedBook:doPhysics()
if self.isFlying then
local random_x = math.random(-4, 4)/100
local random_y = math.random(-4, 4)/100
self.vel.x = self.vel.x + random_x
self.vel.y = self.vel.y + random_y
end
-- move
self:moveWithCollision()
-- horizontal collision
self:moveX(
self.vel.x,
function()
self.vel.x = 0
end
)
-- vertical collision
self:moveY(
self.vel.y,
function()
self.vel.y = 0
end
)
-- final position
self:adjustLight()
end

View File

@@ -34,7 +34,6 @@ function Fairy:new(x,y)
end
function Fairy:doLogic()
if self:checkVisionLine(main_player,self.vision_range) then
self.target.x = main_player.pos.x + main_player.target_offset.x
@@ -63,31 +62,19 @@ function Fairy:doLogic()
local distance_x = self.target.x - self.pos.x
local distance_y = self.target.y - self.pos.y
local angle = getAngleFromVector(distance_x,distance_y)
local angle = getAngleFromVector(vector(distance_x,distance_y))
local distance = math.sqrt(distance_x ^ 2 + distance_y ^ 2)
if distance < self.range then
self.vel.x = self.vel.x * 0.9
self.vel.y = self.vel.y * 0.9
local random_x = math.random(-1, 1)
local random_y = math.random(-1, 1)
self.vel.x = self.vel.x * 0.9 + random_x/10
self.vel.y = self.vel.y * 0.9 + random_y/10
else
self.vel.x = math.cos(angle)*self.speed
self.vel.y = math.sin(angle)*self.speed
end
self.particle_timer = self.particle_timer + 1
if self.particle_timer >= self.particle_time then
self.particle_timer = 0
local particle_data = {
animation = animation.particle.simple,
animation_speed = 1,
sprite_tint = hex2rgb("#fed100"),
sprite_alpha_fade = true,
direction = angle-math.rad(180+math.random(60)-30),
speed = 0.8*(distance/50),
speed_increase = -0.01,
time = 0.75
}
Particle:new(self.pos.x,self.pos.y,particle_data)
local random_x = math.random(-6, 6)
local random_y = math.random(-6, 6)
self.vel.x = math.cos(angle)*self.speed + random_x/10
self.vel.y = math.sin(angle)*self.speed + random_y/10
end
end
@@ -95,19 +82,42 @@ function Fairy:handleAnimation()
self.body:animate()
--if self:isCollidingWith(main_player) then self.sprite_tint = {1,0,0} else self.sprite_tint = {1,1,1} end
self:draw(self.body)
self.particle_timer = self.particle_timer + 1
if self.particle_timer >= self.particle_time then
local vector = vector(self.vel.x,self.vel.y)
local angle = getAngleFromVector(vector)
self.particle_timer = 0
local particle_data = {
animation = animation.particle.simple,
animation_speed = 1,
sprite_tint = hex2rgb("#fed100"),
sprite_alpha_fade = true,
direction = angle-math.rad(180+math.random(60)-30),
speed = 1,
speed_increase = -0.01,
time = 0.75
}
Particle:new(self.pos.x,self.pos.y,particle_data)
end
end
function Fairy:doPhysics()
local random_x = math.random(-4, 4)/10
local random_y = math.random(-4, 4)/10
self.vel.x = self.vel.x + random_x
self.vel.y = self.vel.y + random_y
self:moveWithCollision()
self.vel.x = 0
self.vel.y = 0
-- horizontal collision
self:moveX(
self.vel.x,
function()
self.vel.x = 0
end
)
-- vertical collision
self:moveY(
self.vel.y,
function()
self.vel.y = 0
end
)
-- final position
self:adjustLight()
end

View File

@@ -36,10 +36,6 @@ function HookAnchor:drawBackground()
)
end
function HookAnchor:doPhysics()
end
function Fairy:debug()
Entity.debug(self)
end

View File

@@ -44,8 +44,9 @@ function Kupo:doLogic()
self.target.y = main_player.pos.y - main_player.target_offset.y
local distance_x = self.target.x - self.pos.x
local distance_y = self.target.y - self.pos.y
local distance = math.sqrt(distance_x ^ 2 + distance_y ^ 2)
local angle = getAngleFromVector(distance_x,distance_y)
local angle = getAngleFromVector(vector(distance_x,distance_y))
self.draw_bow = false
if distance <= self.range then
if self.hostile == true then
@@ -153,7 +154,3 @@ function Kupo:handleAnimation()
)
end
end
function Kupo:doPhysics()
self:moveWithCollision()
end

View File

@@ -21,8 +21,14 @@ function Particle:new(x,y,particle_data)
o.sprite_flip = particle_data.sprite_flip or o.sprite_flip
o.time = particle_data.time or nil
if o.time ~= nil then o.time = o.time * game.framerate end
if o.time ~= nil then
if particle_data.time_unit ~= nil
and particle_data.time_unit == "frames" then
o.time = o.time
else
o.time = o.time * game.framerate
end
end
o.timer = 0
o.vel = {
@@ -83,26 +89,32 @@ function Particle:handleAnimation()
end
end
function Particle:doPhysics()
function Particle:doLogic()
-- adjust speed
if self.speed_increase ~= 0 then
self.speed = self.speed + self.speed_increase
self.vel.x = self.speed * math.cos(self.direction)
self.vel.y = self.speed * math.sin(self.direction)
end
-- move
self:moveWithCollision()
if self.light ~= nil then
self:adjustLight()
self.light.range = self.light_range * self.sprite_alpha/2
end
if self.time ~= nil then
if self.timer >= self.time then self:kill() end
end
end
function Particle:doPhysics()
-- horizontal collision
self:moveX(
self.vel.x
)
-- vertical collision
self:moveY(
self.vel.y
)
-- final position
self:adjustLight()
end
function Particle:debug()
-- draw center CYAN
love.graphics.setColor(0,1,1)

View File

@@ -155,7 +155,7 @@ function Player:doLogic()
end
-- set dash values
self.dashDirection = getAngleFromVector(horizontal, vertical)
self.dashDirection = getAngleFromVector(vector(horizontal, vertical))
self.dash_timer = math.floor(self.dash_time * game.framerate)
end
else
@@ -232,10 +232,10 @@ function Player:doPhysics()
-- hook state
if self.is_hooked then
self.move_x = 0
local hook = vector(self.pos.x, self.pos.y, self.hook_anchor.x, self.hook_anchor.y)
local hook = vector(self.hook_anchor.x - self.pos.x, self.hook_anchor.y - self.pos.y)
local dist = math.min(getVectorValue(hook), self.hook_distance)
local hook_angle = getAngleFromVector(hook[1],hook[2])-math.rad(180)
local hook_angle = getAngleFromVector(hook)-math.rad(180)
if Keybind:checkDown(Keybind.move.right) then
hook_angle = hook_angle - self.hook_swing_speed
@@ -251,7 +251,8 @@ function Player:doPhysics()
animation_speed = 0,
sprite_tint = hex2rgb("#fed100"),
sprite_alpha = 0.5,
time = 0.05,
time = 4,
time_unit = "frames",
sprite_flip = {
x = self.sprite_flip.x,
y = self.sprite_flip.y
@@ -263,8 +264,13 @@ function Player:doPhysics()
local pos_y = self.hook_anchor.y + dist * math.sin(hook_angle)
self.vel.x = self.vel.x + pos_x - self.pos.x
self.vel.y = self.vel.y + pos_y - self.pos.y
self.pos.x = pos_x
self.pos.y = pos_y
self:moveX(
pos_x - self.pos.x
)
self:moveY(
pos_y - self.pos.y
)
end
@@ -275,24 +281,26 @@ function Player:doPhysics()
end
-- horizontal collision
if not self:isCollidingAt(self.pos.x + self.vel.x, self.pos.y, LoadedObjects.Collisions) then
self.pos.x = self.pos.x + self.vel.x
self.wall_hit = 0
else
self.wall_hit = math.sign(self.vel.x)
self.vel.x = 0
end
self.wall_hit = 0
self:moveX(
self.vel.x,
function()
self.wall_hit = math.sign(self.vel.x)
self.vel.x = 0
end
)
-- vertical collision
if not self:isCollidingAt(self.pos.x, self.pos.y + self.vel.y, LoadedObjects.Collisions) then
self.pos.y = self.pos.y + self.vel.y
else
if self.vel.y > 0 then
self.is_on_ground = true
self.dash_count = self.dash_amount
self:moveY(
self.vel.y,
function()
if self.vel.y > 0 then
self.is_on_ground = true
self.dash_count = self.dash_amount
end
self.vel.y = 0
end
self.vel.y = 0
end
)
-- if u collision w hazard, respawn
if self:isCollidingAt(self.pos.x, self.pos.y, LoadedObjects.Hazards) then
@@ -326,10 +334,10 @@ function Player:handleAnimation()
elseif self.vel.y < 0 then
self.body = self.body:change(animation.nancy.jump)
self.mask = self.mask:change(self.mask_type.jump)
elseif self.vel.x + self.move_x ~= 0 then
elseif self.vel.x + self.move_x ~= 0 and not self.is_hooked then
self.body = self.body:change(animation.nancy.run)
self.mask = self.mask:change(self.mask_type.run)
else
elseif not self.is_hooked then
self.body = self.body:change(animation.nancy.idle)
self.mask = self.mask:change(self.mask_type.idle)
end

View File

@@ -5,6 +5,7 @@ function Entity:new(x,y)
local o = {}
o.pos = {x = x, y = y}
o.move_remainder = {x = 0, y = 0}
o.vel = {x = 0, y = 0}
o.direction = 0
@@ -69,24 +70,53 @@ end
function Entity:doLogic()
end
function Entity:move()
self.pos.x = self.pos.x + self.vel.x
self.pos.y = self.pos.y + self.vel.y
function Entity:moveX(amount, func)
self.move_remainder.x = self.move_remainder.x + amount
local move = math.round(self.move_remainder.x)
if move ~= 0 then
self.move_remainder.x = self.move_remainder.x - move
local sign = math.sign(move)
while math.round(move) ~= 0 do
if not self:isCollidingAt(
self.pos.x + sign,
self.pos.y,
LoadedObjects.Collisions
) then
self.pos.x = self.pos.x + sign
move = move - sign
if tostring(move) == "nan" then error() end
else
if func then
func()
end
break
end
end
end
end
function Entity:moveWithCollision()
-- horizontal collision
if not self:isCollidingAt(self.pos.x + self.vel.x, self.pos.y, LoadedObjects.Collisions) then
self.pos.x = self.pos.x + self.vel.x
else
self.vel.x = 0
end
-- vertical collision
if not self:isCollidingAt(self.pos.x, self.pos.y + self.vel.y, LoadedObjects.Collisions) then
self.pos.y = self.pos.y + self.vel.y
else
self.vel.y = 0
function Entity:moveY(amount, func)
self.move_remainder.y = self.move_remainder.y + amount
local move = math.round(self.move_remainder.y)
if move ~= 0 then
self.move_remainder.y = self.move_remainder.y - move
local sign = math.sign(move)
while math.round(move) ~= 0 do
if not self:isCollidingAt(
self.pos.x,
self.pos.y + sign,
LoadedObjects.Collisions
) then
self.pos.y = self.pos.y + sign
move = move - sign
if tostring(move) == "nan" then error() end
else
if func then
func()
end
break
end
end
end
end
@@ -120,8 +150,9 @@ function Entity:checkVisionLine(entity,range)
local distance_x = target_x - self.pos.x
local distance_y = target_y - self.pos.y
local distance = vector(distance_x,distance_y)
local angle = getAngleFromVector(distance_x,distance_y)
local angle = getAngleFromVector(distance)
local distance = math.sqrt(distance_x ^ 2 + distance_y ^ 2)
local is_colliding = true
@@ -227,8 +258,9 @@ function Entity:checkVisionLineDebug(entity,range)
local distance_x = target_x - self.pos.x
local distance_y = target_y - self.pos.y
local distance = vector(distance_x,distance_y)
local angle = getAngleFromVector(distance_x,distance_y)
local angle = getAngleFromVector(distance)
local distance = math.sqrt(distance_x ^ 2 + distance_y ^ 2)
if distance < range then
@@ -277,6 +309,9 @@ function Entity:debug()
end
end
function Entity:doPhysics()
end
function Entity:handleAnimation()
end

View File

@@ -53,10 +53,6 @@ function stepGame()
initMenu("dialog",dialog_sequence.example)
end
if Keybind:checkPressed(Keybind.debug.editor) then
editor_mode = true
end
if Keybind:checkPressed(Keybind.debug.recording) then
if DemoRecording then
Demo:endRecord()

View File

@@ -20,7 +20,6 @@ function drawGameworldBackground()
for i = 1, #LevelTiles do
for j = 1, #LevelTiles[i] do
if LevelTiles[i][j].id ~= 0 then
local depth = TileData[LevelTiles[i][j].id].depth
drawTile(
LevelTiles[i][j],

View File

@@ -1,90 +1,51 @@
function exportLevel(levelname, filename)
love.filesystem.createDirectory("export")
filename = filename or "output.lua"
if string.sub(filename, 1, 1) ~= "/" then
filename = "export/"..filename
function exportLevel(levelname, dirname)
dirname = "export/"..dirname
if love.filesystem.exists(dirname) then
-- TODO: prompt to overwrite
error("file already exists")
end
local ok = love.filesystem.createDirectory(dirname)
if not ok then
logPrint("error creating directory")
end
exportFile = io.open(filename, "w+")
if exportFile then
logPrint("Exporting level \"".. levelname .. "\"...")
exportFile:write("return {")
logPrint("- level name")
exportFile:write("\n name = \"" .. levelname .. "\",")
logPrint("- tileset")
for k, v in pairs(tileset) do
if v == LevelData.tileset then
exportFile:write("\n tileset = tileset." .. k .. ",")
end
end
logPrint("- properties")
exportFile:write("\n properties = {")
logPrint(" - darkness: ".. tostring(LevelData.properties.darkness))
exportFile:write("\n darkness = " .. tostring(LevelData.properties.darkness))
exportFile:write("\n },")
logPrint("- tiles")
exportFile:write("\n tiles = {")
local rows = #LevelTiles
for i = 1, #LevelTiles do
if i > 1 then
exportFile:write(", ")
end
exportFile:write("\n { ")
for j = 1, #LevelTiles[i] do
if j ~= 1 then
exportFile:write(", ")
end
exportFile:write(tostring(LevelTiles[i][j].id))
end
exportFile:write("}")
logPrint(" - row "..i.."/"..rows.." "..math.floor(100*((i-1)*100/rows))/100 .."%")
end
exportFile:write("\n },")
logPrint("- objects")
exportFile:write("\n objects = {")
logPrint(" - spawns")
exportFile:write("\n spawns = {")
for i, v in ipairs(LoadedObjects.Spawns) do
if i > 1 then
exportFile:write(",")
end
exportFile:write("\n {")
exportFile:write(v.archetype.type)
exportFile:write(",{")
for i=1, #v.args do
if i > 1 then
exportFile:write(",")
end
exportFile:write(v.args[i])
end
exportFile:write("}}")
end
exportFile:write("\n },")
logPrint(" - rooms")
exportFile:write("\n rooms = {")
for i, room in ipairs(LoadedObjects.Rooms) do
if i > 1 then
exportFile:write(",")
end
exportFile:write("\n {{")
exportFile:write(room.from.x)
exportFile:write(",")
exportFile:write(room.from.y)
exportFile:write("},{")
exportFile:write(room.to.x)
exportFile:write(",")
exportFile:write(room.to.y)
exportFile:write("}}")
end
exportFile:write("\n },")
exportFile:write("\n },")
logPrint("Exporting complete.")
exportFile:write("\n}")
exportFile:close()
logPrint("Exporting level \"".. levelname .. "\"...")
local exportTable = {}
exportTable.name = levelname
exportTable.tileset = "library"
exportTable.properties = LevelData.properties
--exportTable.tiles = LevelTiles
--logPrint("- objects")
--exportTable.objects = { spawns = {}, rooms = {} }
--logPrint(" - spawns")
--for i, v in ipairs(LoadedObjects.Spawns) do
--exportTable.objects.spawns = {v.archetype.name,{},v.args}
--end
--logPrint(" - rooms")
--for i, room in ipairs(LoadedObjects.Rooms) do
--- table.insert(exportTable.objects.rooms,{room:asRect():getCoords()})
--end
exportTable.chunks = Chunk:getExportList()
logPrint("Writing to file...")
local ok, err = love.filesystem.write(dirname.."/level.lua", "return "..serialize_lua_value(exportTable))
if ok then
logPrint("Saving chunks...")
local chunkdir = dirname.."/chunks"
love.filesystem.createDirectory(chunkdir)
for chunk in pairs(Chunk.all) do
local ok, err = chunk:save(chunkdir)
if not ok then error(err) end
end
logPrint("Exporting complete.")
else
-- TODO: clean up created files
logPrint("Exporting failed: "..err)
end
end

View File

@@ -17,6 +17,10 @@ Keybind.debug = {}
Keybind.editor = {}
Keybind.generic = {}
function Keybind:isAvailable(action)
return not action.occupied
end
function Keybind:checkDown(action)
if DemoPlayback then
for _, demo_action in pairs(DemoAction[CurrentDemoFrame]) do
@@ -37,9 +41,11 @@ function Keybind:checkDown(action)
if action.demo ~= nil then
Demo:recordAction(action.demo)
end
action.occupied = true
return true
end
end
action.occupied = false
return false
end
end

View File

@@ -1,7 +1,25 @@
function loadLevelTiles()
math.randomseed(3)
LevelData = dofile("data/levels/"..level_current)
level_current = "data/levels/level1"
LevelData = dofile(level_current.."/level.lua")
LoadedObjects.Collisions = {}
LoadedObjects.Platforms = {}
LoadedObjects.Ladders = {}
LoadedObjects.Hazards = {}
if type(LevelData.tileset) == "string" then
LevelData.tileset_name = LevelData.tileset
end
LevelData.tileset = tileset[LevelData.tileset_name]
getLevelTileData()
for _, v in ipairs(LevelData.chunks) do
Chunk:new(v[1], v[2])
end
local global_chunk = next(Chunk.all)
global_chunk:load()
LoadedObjects.Collisions = global_chunk.loaded.collisions
LevelTiles = global_chunk.data.tiles
--[[
on level format:
@@ -12,26 +30,26 @@ function loadLevelTiles()
overlay_depth = foreground/background overlay depth
type = collision type
]]
getLevelTileData()
LevelTiles = LevelData.tiles
updateLevelDimensions()
indexLevelTiles()
createTileObjects()
createRoomObjects()
getSpawns()
--
--createTileObjects()
--createRoomObjects()
--getSpawns()
end
function createRoomObjects()
LoadedObjects.Rooms = {}
for _, v in pairs(LevelData.objects.rooms) do
table.insert(LoadedObjects.Rooms, Collision:new(v[1][1],v[1][2],v[2][1],v[2][2]))
table.insert(LoadedObjects.Rooms, Collision:new(v[1],v[2],v[3],v[4]))
end
end
function getSpawns()
LoadedObjects.Spawns = {}
for _, v in pairs(LevelData.objects.spawns) do
addSpawn(v[1],unpack(v[2]))
--addSpawn(v[1],unpack(v[2]))
end
end
@@ -121,11 +139,8 @@ function reduceLevelCanvas(horizontal,vertical)
end
function getLevelTileData()
for k, v in pairs(tileset) do
if v == LevelData.tileset then
TileData = dofile("data/tileset/"..k..".lua")
end
end
TileData = dofile("data/tileset/"..LevelData.tileset_name..".lua")
end
function reloadLevelTiles()
@@ -159,10 +174,10 @@ end
function indexLevelTiles()
TileIndex = {}
local this_tileset = LevelData.tileset
-- index from tileset
local width = LevelData.tileset:getPixelWidth()/tile_properties.width
local height = LevelData.tileset:getPixelHeight()/tile_properties.height
local width = this_tileset:getPixelWidth()/tile_properties.width
local height = this_tileset:getPixelHeight()/tile_properties.height
for i = 0, height do
for j = 0, width do
TileIndex[i*width+j+1] = love.graphics.newQuad(
@@ -170,7 +185,7 @@ function indexLevelTiles()
i*tile_properties.height,
tile_properties.width,
tile_properties.height,
LevelData.tileset:getDimensions()
this_tileset:getDimensions()
)
end
end
@@ -220,6 +235,9 @@ end
function instanceTile(id)
local tile = {}
if type(id) == "table" then
id = id.id
end
tile.id = id
local Properties = TileData[tile.id]
@@ -258,7 +276,7 @@ function drawGridDisplay()
end
end
function optimizeTileObjects()
function optimizeTileObjects(dest)
logPrint("Optimizing Objects...")
local unoptimized = 0
local isTileOptimized = {}
@@ -270,8 +288,9 @@ function optimizeTileObjects()
end
for i = 1, #LevelTiles do
for j = 1, #LevelTiles[i] do
if LevelTiles[i][j].id ~= 0 then
local type = TileData[LevelTiles[i][j].id].type
if LevelTiles[i][j].id ~= 0 and TileData[LevelTiles[i][j].id] then
local tile_dat = TileData[LevelTiles[i][j].id]
local type = tile_dat.type
if type == "whole" and not isTileOptimized[i][j] then
isTileOptimized[i][j] = true
local n = 1
@@ -337,29 +356,27 @@ function optimizeTileObjects()
base_x + tile_properties.width * tile_properties.scale * n,
base_y + tile_properties.height * tile_properties.scale * m
)
table.insert(LoadedObjects.Collisions,col)
table.insert(dest,col)
end
end
end
end
logPrint("collisions optimized from " .. unoptimized .. " to " .. #LoadedObjects.Collisions)
--logPrint("collisions optimized from " .. unoptimized .. " to " .. #LoadedObjects.Collisions)
end
function createTileObjects()
LoadedObjects.Collisions = {}
LoadedObjects.Platforms = {}
LoadedObjects.Ladders = {}
LoadedObjects.Hazards = {}
-- currently broken
function createTileObjects(dest)
optimizeTileObjects()
optimizeTileObjects(dest.collisions)
for i = 1, #LevelTiles do
for j = 1, #LevelTiles[i] do
if LevelTiles[i][j].id ~= 0 then
local type = TileData[LevelTiles[i][j].id].type
local light = TileData[LevelTiles[i][j].id].light
local tile_dat = TileData[LevelTiles[i][j].id] or {}
local type = tile_dat.type
local light = tile_dat.light
local base_x = tile_properties.scale * j * tile_properties.width + tile_properties.scale * (level_properties.offset.x - tile_properties.height)
local base_y = tile_properties.scale * i * tile_properties.height + tile_properties.scale * (level_properties.offset.y - tile_properties.height)
@@ -372,6 +389,9 @@ function createTileObjects()
)
end
local col
local list = dest.collisions
-- wholes are handled in optimization now
--[[if type == "whole" then
local col = Collision:new(
@@ -383,33 +403,33 @@ function createTileObjects()
table.insert(LoadedObjects.Collisions,col)
else]]if type == "half_bottom" then
local col = Collision:new(
col = Collision:new(
base_x,
base_y + tile_properties.height/2 * tile_properties.scale,
base_x + tile_properties.width * tile_properties.scale,
base_y + tile_properties.height * tile_properties.scale
)
table.insert(LoadedObjects.Collisions,col)
elseif type == "half_top" then
local col = Collision:new(
col = Collision:new(
base_x,
base_y ,
base_x + tile_properties.width * tile_properties.scale,
base_y + tile_properties.height/2 * tile_properties.scale
)
table.insert(LoadedObjects.Collisions,col)
elseif type == "half_right" then
local col = Collision:new(
col = Collision:new(
base_x + tile_properties.height/2 * tile_properties.scale,
base_y,
base_x + tile_properties.width * tile_properties.scale,
base_y + tile_properties.height * tile_properties.scale
)
table.insert(LoadedObjects.Collisions,col)
elseif type == "half_left" then
@@ -580,7 +600,8 @@ function createTileObjects()
end
elseif type == "ladder_right" then
-- TODO: fix ladders
--[[elseif type == "ladder_right" then
local ladder = Collision:new(
base_x + (tile_properties.width-4)* tile_properties.scale,
@@ -638,7 +659,7 @@ function createTileObjects()
)
table.insert(LoadedObjects.Platforms,plat)
elseif type == "bottom_hazard" then
]]elseif type == "bottom_hazard" then
local hazard = Collision:new(
@@ -647,9 +668,10 @@ function createTileObjects()
base_x + tile_properties.width * tile_properties.scale,
base_y + tile_properties.height * tile_properties.scale
)
table.insert(LoadedObjects.Hazards,hazard)
list = dest.hazards
end
table.insert(list, col)
end
end
end

View File

@@ -8,20 +8,22 @@ function math.sign(x)
end
end
function vector(init_x, init_y, final_x, final_y)
local distance_x = final_x - init_x
local distance_y = final_y - init_y
return {distance_x, distance_y}
function math.round(x)
return math.floor(x+0.5)
end
function vector(x, y)
return {x = x, y = y}
end
function getVectorValue(vector)
return math.sqrt(vector[1] ^ 2 + vector[2] ^ 2)
return math.sqrt(vector.x ^ 2 + vector.y ^ 2)
end
function getAngleFromVector(x,y)
function getAngleFromVector(vector)
local reduce = 0
if x < 0 then
if vector.x < 0 then
reduce = math.rad(180)
end
return math.atan(y/x) - reduce
return math.atan(vector.y/vector.x) - reduce
end

View File

@@ -25,10 +25,7 @@ function isThereObjectAt(x,y,objectType)
for _, object in pairs(objectType) do
if object.is_disabled then
-- Dont calculate if dissabled
elseif x >= object.from.x
and x <= object.to.x
and y >= object.from.y
and y <= object.to.y then
elseif object:asRect():containsPoint(Point:new(x, y)) then
object.is_colliding = true
return true
end
@@ -36,14 +33,6 @@ function isThereObjectAt(x,y,objectType)
return false
end
function isThereCollisionAt(x,y)
if x >= 0 and x < #CollisionTable
and y >= 0 and y < #CollisionTable[0] then
return CollisionTable[math.floor(y)][math.floor(x)]
end
return false
end
-- flags
function setCollisionFlags()
local Check = {

86
code/rect.lua Normal file
View File

@@ -0,0 +1,86 @@
-- based of of plan9's Rectangle struct
-- rect.max is not counted as "in" the rectangle
Rect = {}
Rect.__index = Rect
function Rect:fromPoints(pt1, pt2)
local o = { min = pt1, max = pt2 }
setmetatable(o, self)
return o
end
function Rect:getPoints()
return self.min, self.max
end
function Rect:fromCoords(x1, y1, x2, y2)
return Rect:fromPoints(Point:new(x1, y1), Point:new(x2, y2))
end
function Rect:getCoords()
return self.min.x, self.min.y, self.max.x, self.max.y
end
-- clone refers to a deep copy
function Rect:clone()
return Rect:fromCoords(self.min.x, self.min.y, self.max.x, self.max.y)
end
-- make sure min and max refer to the correct corners
-- acts in place, returns self
function Rect:fix()
if self.min.x > self.max.x then
self.min.x, self.max.x = self.max.x, self.min.x
end
if self.min.y > self.max.y then
self.min.y, self.max.y = self.max.y, self.min.y
end
end
function Rect:width()
return self.max.x - self.min.x
end
function Rect:height()
return self.max.y - self.min.y
end
function Rect:size()
return Point:new(self:width(), self:height())
end
function Rect:__add(pt)
return Rect:fromPoints(self.min + pt, self.max + pt)
end
function Rect:corners()
return {
self.min:copy(), -- top left
Point:new(self.max.x, self.min.y), -- top right
Point:new(self.min.x, self.max.y), -- bottom left
self.max:copy(), -- bottom right
}
end
function Rect:containsPoint(pt)
return pt.x >= self.min.x
and pt.y >= self.min.y
and pt.x <= self.max.x
and pt.y <= self.max.y
end
function Rect:overlapsRect(other)
return self.min.x < other.max.x
and self.max.x > other.min.x
and self.min.y < other.max.y
and self.max.y > other.min.y
end
function Rect:draw(style)
love.graphics.rectangle(style, self.min.x - Camera.pos.x, self.min.y - Camera.pos.y, self:width(), self:height())
end
function Rect:__tostring()
return "Rect["..tostring(self.min).." "..tostring(self.max).."]"
end

View File

@@ -7,6 +7,7 @@ require "data/sfx"
require "code/locale"
-- support functions
require "code/serialize"
require "code/math"
require "code/draw"
require "code/hex"
@@ -14,6 +15,8 @@ require "code/in_out"
-- classes
require "code/point"
require "code/rect"
require "code/chunk"
require "code/objects"
require "code/level"
require "code/camera"

31
code/serialize.lua Normal file
View File

@@ -0,0 +1,31 @@
local function quote(str, lvl)
--lvl = lvl or
local rp = "]"..(lvl or "").."]"
if string.match(str, rp) then
return quote(str, (lvl or "") .. "=")
end
return "["..(lvl or "").."["..str..rp
end
function serialize_lua_value(val)
if type(val) == "number" then
return tostring(val)
elseif type(val) == "string" then
-- TODO: use different quotes if ']]' appears in the value
return quote(val)
elseif type(val) == "table" then
local r = "{"
for k, v in pairs(val) do
r = r .. "[ "..serialize_lua_value(k).." ]="..serialize_lua_value(v)..","
end
return r .. "}"
elseif val == nil then
return "nil"
elseif val == false then
return "false"
elseif val == true then
return "true"
end
error("serialization failed")
end

View File

@@ -20,7 +20,11 @@ function deselectSpawns()
end
end
function selectSpawns(x,y)
function selectSpawns(rect)
local x, y = rect:getPoints()
local select_rect = Rect:fromPoints(x-{x=Camera.pos.x,y=Camera.pos.y},y-{x=Camera.pos.x,y=Camera.pos.y})
select_rect:fix()
for _, spawn in pairs(LoadedObjects.Spawns) do
local offset_x, offset_y = spawn.archetype.display:getCenteredOffset()
@@ -28,9 +32,9 @@ function selectSpawns(x,y)
local top = spawn.args[2] - Camera.pos.y - offset_y
local right = spawn.args[1] - Camera.pos.x + offset_x
local bottom = spawn.args[2] - Camera.pos.y + offset_y
local x = (x / game.scale)
local y = (y / game.scale)
if x >= left and y >= top and x <= right and y <= bottom then
local spawn_rect = Rect:fromCoords(left, top, right, bottom)
if spawn_rect:overlapsRect(select_rect) then
spawn.selected = true
end
@@ -47,10 +51,22 @@ function selectSpawns(x,y)
end
function moveSpawns(x,y)
local move_x = nil
local move_y = nil
for _, spawn in pairs(LoadedObjects.Spawns) do
if spawn.selected then
spawn.args[1] = math.floor(x/game.scale)+Camera.pos.x
spawn.args[2] = math.floor(y/game.scale)+Camera.pos.y
local difference_x = math.floor((x/game.scale)+Camera.pos.x) - spawn.args[1]
local difference_y = math.floor((y/game.scale)+Camera.pos.y) - spawn.args[2]
if move_x == nil or Point.abs({x=difference_x,y=difference_y}) < Point.abs({x=move_x,y=move_y}) then
move_x = difference_x
move_y = difference_y
end
end
end
for _, spawn in pairs(LoadedObjects.Spawns) do
if spawn.selected then
spawn.args[1] = spawn.args[1] + move_x
spawn.args[2] = spawn.args[2] + move_y
end
end
end
@@ -72,6 +88,9 @@ function promptSpawnNew()
for i=2, #result+1 do
print("arg #"..i-1)
args[i-1] = result[i]
if i < 4 then
args[i-1] = math.floor(args[i-1])
end
end
else
args = {0,0}

View File

@@ -5,16 +5,19 @@ function addElement(self)
self.id = #UIElement
end
function drawTextBox(text,x,y,color,background_color)
local color = color or {1,1,1,1}
local background_color = background_color or {0,0,0,1}
function drawTextBox(text,x,y,style)
local style = style or {}
local c1, c2, c3, a = love.graphics.getColor()
local width = locale_font:getWidth(text)
local height = locale_font:getHeight(text)
local margin = 5
local color = style.color or {1,1,1,1}
local background_color = style.background_color or {0,0,0,1}
local margin = style.margin or 5
local lines = 1
for i in text:gmatch("\n") do
lines = lines + 1
lines = lines + 1
end
love.graphics.setColor(unpack(color))

View File

@@ -62,8 +62,10 @@ function Prompt:draw()
self.name .. ": " .. self.input,
self.pos.x,
self.pos.y,
self.color,
self.background_color
{
color = self.color,
background_color = self.background_color
}
)
end

View File

@@ -1,42 +0,0 @@
return {
name = "Dev Level",
tileset = tileset.library,
properties = {
darkness = false
},
tiles = {
{ 1, 4, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{ 1, 4, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{ 1, 4, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{ 1, 4, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{ 1, 4, 0, 0, 0, 2, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14},
{ 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 13, 13, 13, 13, 13, 13, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13},
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
},
objects = {
spawns = {
{Fairy,{100,88}},
{HookAnchor,{200,89,100}},
{HookAnchor,{400,89,120}}
},
rooms = {
{{96,64},{544,320}},
{{0,0},{112,176}}
},
},
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
return {[ [[tileset]] ]=[[library]],[ [[chunks]] ]={[ 1 ]={[ 1 ]=[[global.lua]],},},[ [[name]] ]=[[unnamed]],[ [[properties]] ]={[ [[darkness]] ]=false,},}

View File

@@ -6,9 +6,10 @@ function love.load()
secs = 0
menu_type = "no"
-- FIXME: this overrides a standard library!
debug = false
debug_collision = false
editor_mode = false
--editor_mode = false
text_size = 1
@@ -103,6 +104,15 @@ function love.update(dt)
return
end
if Keybind:checkPressed(Keybind.debug.editor) then
if editor.active then
deselectSpawns()
createTileObjects()
restartGame()
end
editor.active = not editor.active
end
if love.keyboard.isDown("f7") then
local test_prompt = Prompt:new({
name = "test prompt",
@@ -127,7 +137,7 @@ function love.update(dt)
if menu_type ~= nil then stepMenu(menu_type) end
--editor
if editor_mode then
if editor.active then
stepEditor()
else
stepGame()
@@ -136,7 +146,7 @@ end
function love.wheelmoved(_, y)
if editor_mode then
if editor.active then
scrollEditor(y)
end
end
@@ -150,7 +160,7 @@ function love.draw()
game_resize = false
end
if editor_mode then
if editor.active then
drawEditor()
else
drawGame()