Compare commits

...

69 Commits

Author SHA1 Message Date
Pascal Abresch 42b0e04312 New network backend 2024-02-25 21:48:04 +01:00
Pascal Abresch f76f59015d adjust run script 2024-02-25 13:28:01 +01:00
Pascal Abresch 0d1b808848 Add new network encoded/decoder 2024-02-25 13:27:44 +01:00
Pascal Abresch 5b5f21dcce activate new main menu music... 2024-02-11 22:48:13 +01:00
Pascal Abresch 0e0faf0d41 New main menu music! Too many changes to remember
and i'm not splitting this up >:(
2024-02-11 22:43:28 +01:00
Pascal Abresch 4f15220f0a add Poppy menu music 2024-02-10 23:13:02 +01:00
Pascal Abresch dca9be008b remove menumessage, move server spin up 2023-11-05 17:47:28 +01:00
Pascal Abresch 3ccbb66b2d bubbles 2023-11-04 15:15:35 +01:00
Pascal Abresch 609e7ed5fe add layout.labelWShadow
This shouuld be removed later, this is a "cheap" hack
for text shadows and should be done with a GLSL shader properly
2023-11-04 15:14:58 +01:00
Pascal Abresch d1e253a8e1 add menu sound 2023-10-30 23:00:25 +01:00
Pascal Abresch 31a6234ed2 add white textures 8,4 2023-09-29 12:01:24 +02:00
Pascal Abresch 52413ab1b1 add white textures 2023-09-29 11:57:45 +02:00
Pascal Abresch 7a8ca2ef1e WIP: physics update 2023-01-13 20:57:35 +01:00
Pascal Abresch ac349ae0fa simplify ui code a bit, finish new buttons 2022-12-18 11:33:15 +01:00
Pascal Abresch 87248d5139 fix flying 2022-12-18 11:32:37 +01:00
Pascal Abresch 8b2a9e4b30 server update 2022-12-17 17:22:46 +01:00
Pascal Abresch 1a64cc4334 Fix server network 2022-12-12 15:25:03 +01:00
Pascal Abresch 1501ee13b1 reenable some textures 2022-12-04 11:11:19 +01:00
Pascal Abresch 72c301c6b4 add some sources 2022-12-04 11:10:57 +01:00
Pascal Abresch cbe4755d35 script to build package 2022-12-04 11:09:55 +01:00
Pascal Abresch 9ab9d0669c add source for nep textures 2022-12-04 11:09:25 +01:00
Pascal Abresch 25c9bf62fb misc changes 2022-12-04 11:08:48 +01:00
Pascal Abresch 1d87524d40 Add new player sprite; make menu message a function
Co-authored-by: Noriko
2022-12-04 11:07:51 +01:00
Pascal Abresch 6b50f9a6ca Add buttons rendering (WIP)
Co-authored-by: Noriko
2022-12-04 11:05:58 +01:00
Pascal Abresch 0a512bf243 camera offset 2022-12-04 11:04:21 +01:00
Pascal Abresch c78a94d550 enable misc textures 2022-12-04 11:03:53 +01:00
Pascal Abresch 0ae47972a3 Change window casing 2022-10-18 14:27:13 +02:00
Pascal Abresch 26265cfabf rename variables; make shared commands much easier to use 2022-10-18 14:07:38 +02:00
Pascal Abresch c813f5e745 delete singlemain 2022-10-18 14:07:09 +02:00
Pascal Abresch 2a20018ff6 start server or client properly from cli 2022-10-18 14:06:20 +02:00
Pascal Abresch e17fa512be Simplify hitcheck logic slightly 2022-10-13 12:56:01 +02:00
Pascal Abresch 51cb7aab16 Adjust FixMime script 2022-10-13 12:27:25 +02:00
Pascal Abresch 963bdd4f42 move helptext to escape menu 2022-10-10 20:27:31 +02:00
Pascal Abresch c36ca674df improve escape menu 2022-10-10 20:15:52 +02:00
Pascal Abresch 0a680ff7a5 add layout.inset; make menu button more buttony 2022-10-10 20:06:04 +02:00
Pascal Abresch bce53de48c adjust default world size 2022-10-10 19:46:38 +02:00
Pascal Abresch 92ea3fe781 Add saving; add escape menu 2022-10-10 19:46:27 +02:00
Pascal Abresch ae6e7171a8 add moss texture 2022-10-09 14:39:45 +02:00
Pascal Abresch eeb1756052 add some source textures 2022-10-09 14:38:52 +02:00
Pascal Abresch 5118472ef4 make dog slightly smaller 2022-10-09 14:36:49 +02:00
Pascal Abresch c513087c7a add some resiliance against missing values 2022-10-09 14:36:33 +02:00
Pascal Abresch d6015ef155 fix menu server entries 2022-10-09 12:52:44 +02:00
Pascal Abresch 761fa00cbd redo ui 2022-10-09 12:52:17 +02:00
Pascal Abresch 388964b2c3 style fix 2022-10-09 12:51:50 +02:00
Pascal Abresch a5649210a5 fix canvas, more errors 2022-10-09 12:51:38 +02:00
Pascal Abresch 2306586af4 style fix 2022-10-09 12:50:22 +02:00
Pascal Abresch 03f18b8c8a adjust default window size 2022-10-09 12:49:43 +02:00
Pascal Abresch 126ae0e127 add placeholder for new server 2022-08-04 22:12:18 +02:00
Pascal Abresch 91fbc72297 add missing files 2022-08-04 22:01:24 +02:00
Pascal Abresch 0ef7436318 minor menu improvement 2022-08-04 21:54:40 +02:00
Pascal Abresch 39bce76863 add server running info label 2022-08-04 21:40:20 +02:00
Pascal Abresch 540b25379f improve main menu, add nicer button 2022-08-04 21:40:08 +02:00
Pascal Abresch 4de4a5eb9f minor fix 2022-08-04 21:14:56 +02:00
Pascal Abresch 2f2a098ca6 Change some stuff 2022-08-04 21:06:21 +02:00
Pascal Abresch cf2821152e Pull server into this repo directly 2022-07-03 20:45:22 +02:00
Pascal Abresch 7499e6dc40 simplify ui code a bit 2022-07-03 20:39:15 +02:00
Pascal Abresch dc5397331b add avatar syncing 2022-07-03 20:00:30 +02:00
Pascal Abresch a195005f91 update readme 2022-07-03 19:59:52 +02:00
Pascal Abresch fb0c3357dc update UI code 2022-07-03 19:57:11 +02:00
Pascal Abresch 4835007941 Move some textures 2022-07-03 19:26:15 +02:00
Pascal Abresch 926448965c Fix scaling for some textures, sort authors too 2022-07-03 16:03:15 +02:00
Pascal Abresch 522e6f6026 move some player textures; add dog texture 2022-07-02 19:55:09 +02:00
Pascal Abresch e3e516fee4 fix minor thing 2022-06-27 21:20:38 +02:00
Pascal Abresch 7c8fef10bd Add swColorpicker 2022-06-27 21:16:53 +02:00
Pascal Abresch df21735a7b Update textures take #1 2022-06-27 20:28:57 +02:00
Pascal Abresch b076b5c613 fix mispelling 2022-06-27 20:08:01 +02:00
Pascal Abresch e5f7d21bfc slightly update sidebar 2022-06-27 20:06:43 +02:00
Pascal Abresch 594b1d2651 Fix usage of love2d default fonts
Thanks to pgimeno for pointing this out!
2022-06-25 22:32:02 +02:00
Pascal Abresch ab40ec1b34 Update textures take #1 2022-06-25 22:03:07 +02:00
170 changed files with 1995 additions and 889 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "server"]
path = server
url = https://git.gryphno.de/nephele/poppy-server

30
FixMime
View File

@ -1,16 +1,42 @@
#!sh
#adjust the next line... not sure how to figure this out from a shellscript, sorry :)
cd ~/proj/poppy-client-loeve
# This mechanism is fairly unreliable, but it is the only "okay" way currently :/
# realpath is also not part of any POSIX standard
# This script exists because mimetypes on Haiku are not derived from pathnames (.lua)
# but git does not store the mimetype (extended attribute) either
cd $(dirname $(realpath "$0"))
if test $? -eq 1; then
alert --stop "cant figure out where this script is" "Ill adjust it!"
exit
fi
touch log
for i in lua/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i;
done
for i in server/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i;
done
for i in server/commands/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i;
done
for i in server/unpriviliged_commands/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i;
done
for i in shared/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i;
done
alert --info "Fixed mime types
$(cat log)"
rm log

View File

@ -2,7 +2,6 @@
This is the Poppy game\
A server is required to play the game, if you do not have one running then\
run ```love .``` in the ```server/``` directory for a local one for now.\
(per default the client will connect to this server, it is currently a hardcoded constant in lua/networkThread.lua :/ )\
The performance is currently not so great on older computers,\
@ -15,7 +14,7 @@ I have not investigated this much yet. Safe to say it is not intended to run so
* Loading textures
* drawing on canvases
* physics calculation for collison and movement
* drawing physics collisions (on the physics layer)
* drawing physics collisions (on the physics layer) (debug)
* Basic networking
* Get clientID
* Draw all players
@ -25,17 +24,28 @@ I have not investigated this much yet. Safe to say it is not intended to run so
* Send disconnect on client error
* Chat support
* Retrieve initial game state (partial)
* Get users name
* Get users avatar
* Get world size
* Drawing
* Draw with custom color
* Re-do base textures as gray scale to color in arbitrarily
* UI
* UI to connect to specific server
* Reconnect to server
* connect to other server during a session
## Not implemented (rough goals anyhow)
* UI to connect to specific server
* Reconnect to server
* connect to other server during a session
* Open second window? Open second instance instead? (does love2d allow that?)
* Saving the game (server stuff?)
* Loading the game/map (server stuff?)
* Remember visited servers (save the list)
* Add new servers to the list by user input
* Add new servers at runtime via DNS-SD mDNS discovery
* Saving the map
* Loading the map from disk (and send it to the server)
* Loading the map from server on connect
* confirmation before clearing canvas (that's quite destructive :D)
* Make a way to add dialogs and stuff
@ -72,7 +82,9 @@ I have not investigated this much yet. Safe to say it is not intended to run so
* Networking support
* Figure out why and how physics is tied to framerate, why does jump height differ?
* Implement initial state sync (mainly server work)
* Implement initial state sync
* Server remembers plays last position and tells you
* Server remembers drawn stuff and tells you
* Add ack que, resend lost packets (but only for stuff like drawing and such, movement may drop)
* Tell the user if the server doesn't support the networking version requested

View File

@ -1,11 +1,12 @@
love.conf = function(conf)
conf.window.height = 480
conf.identity = "Poppy"
conf.window.height = 612
conf.window.minheight = 480
conf.window.width = 640
conf.window.width = 1024
conf.window.minwidth = 640
conf.window.resizable = true
conf.window.vsync = -1
conf.window.title = "poppy"
conf.window.title = "Poppy"
conf.version = "11.0"
conf.modules.joystick = false

View File

@ -2,14 +2,16 @@ local CAM_SPEED = 10
camera = {}
camera.target = 0
camera.offset = { x = 0, y = 300 }
camera.offset = { x = 0, y = 0 }
local fullscreen = false
local altPressed, enterPressed = false, false
function camera.keypressed(key, _)
if key == "return" then enterPressed = true end
if key == "lalt" then altPressed = true end
if key == "f11" or (altPressed and key == "return") or (enterPressed and key == "lalt") then
if key == "f11"
or (altPressed and key == "return")
or (enterPressed and key == "lalt") then
fullscreen = not fullscreen
love.window.setFullscreen(fullscreen, "desktop")
end
@ -93,7 +95,7 @@ end
function camera.setplayer()
if camera.target == 0 then return end
player = players[camera.target]
player = participants[camera.target]
camera.offsetclamp()
local perfectx = math.floor(player.x * -1) + ((window.x -300)/2 -((player.avatar:getWidth() /2) / scale)) / scale + camera.offset.x
local perfecty = math.floor(player.y * -1) + (window.y/2 -((player.avatar:getHeight() /2) / scale)) / scale + camera.offset.y
@ -105,7 +107,7 @@ end
function camera.update()
if camera.target == 0 then return end
player = players[camera.target]
player = participants[camera.target]
--[[ Update camera to track player ]]--
local perfectx = math.floor(player.x * -1) + ((window.x -300)/2 -(player.avatar:getWidth() /2)) / scale + camera.offset.x
local perfecty = math.floor(player.y * -1) + (window.y/2 -(player.avatar:getHeight() /2)) / scale + camera.offset.y

94
lua/commands.lua Normal file
View File

@ -0,0 +1,94 @@
local player = require("lua.player")
local utils = require("shared.utils")
local fonts = require("shared.fonts")
return function(commands)
-- arg1 here is the clientID, but the server suplies 0
-- as this is the "origin"
-- don't want to overload/specialcase this, so the clientID is passed
-- as first argument instead.
commands.init = function(_, clientID, nickname, avatarHash, worldID, x, y)
print("Connected as " .. clientID .. " with name " .. nickname)
print("New world is " .. worldID .. " Size is [" .. x .. "|" .. y .. "]")
participants[clientID] = player.newPlayer(
{name = nickname, avatar = levelloop.avatars[avatarHash].image, y=y})
participants[clientID].y = y - participants[clientID].avatar:getHeight()
camera.target = clientID
levelloop.clientID = clientID
local world = { id = worldID, x = x, y = y }
levelloop.loadworld(world)
camera.setplayer()
end
commands.serverQuit = function(clientID, message)
menu.init(message)
end
commands.ping = function(clientID)
levelloop.networkSend("pong")
end
commands.saveWorld = function(clientID, status, savename)
if status == false then
ui.addChatEntry("SERVER", "Failed to save world!")
elseif status == "sucess" then
ui.addChatEntry("SERVER", "saved world. " .. savename)
end
end
commands.playerJoin = function(_, clientID, nickname, avatarHash)
ui.addChatEntry("", nickname .. " Joined the game")
local avatar = { image = love.graphics.newImage("textures/player/nephele/fallback.png") }
if levelloop.avatars[avatarHash] then
avatar = levelloop.avatars[avatarHash]
end
participants[clientID] = player.newPlayer({name = nickname, avatar = avatar.image})
participants[clientID].label = love.graphics.newText(fonts.smallFont, nickname)
end
commands.playerLeave = function(_, clientID)
if participants[clientID] then
local name = participants[clientID].name
ui.addChatEntry("", name .. " Left the game")
participants[clientID] = nil
end
end
commands.deletePhysics = function(clientID, x, y, width, height)
if clientID == camera.target then return end
if not participants[clientID] then return end
physics.remove_solid(x, y, width, height)
end
commands.drawPhysics = function(clientID, x, y, width, height)
if clientID == camera.target then return end
if not participants[clientID] then return end
physics.draw_solid(x, y, width, height)
end
commands.chatMessage = function(clientID, message)
local name
if clientID == "0" then
name = ""
else
if participants[clientID] and participants[clientID].name then
name = participants[clientID].name
else
name = "UNKNOWN?"
end
end
ui.addChatEntry(name, message)
end
end

View File

@ -3,60 +3,66 @@ function alignToGrid(num, thing)
return num - (num % thing)
end
local utils = require("shared.utils")
function drawing.clearAllCanvases()
love.graphics.setCanvas(gameloop.buffer.pixel.fg)
love.graphics.setCanvas(levelloop.Canvas.fg)
love.graphics.clear()
love.graphics.setCanvas(gameloop.buffer.pixel.bg)
love.graphics.setCanvas(levelloop.Canvas.bg)
love.graphics.clear()
love.graphics.setCanvas(gameloop.buffer.pixel.dbg)
love.graphics.setCanvas(levelloop.Canvas.dbg)
love.graphics.clear()
love.graphics.setCanvas()
gameloop.buffer.physics = {}
levelloop.Canvas.physics = {}
end
function drawing.clearAllCanvasesNetwork()
gameloop.networkSend("clearCanvas")
levelloop.remoteCall("clearCanvas")
drawing.clearAllCanvases()
end
function drawing.fillCanvas(layer, hash)
love.graphics.setCanvas(gameloop.buffer.pixel[layer])
love.graphics.clear()
for x = 0, gameloop.world.x -gameloop.textures[hash].image:getPixelWidth(),
gameloop.textures[hash].image:getPixelWidth() do
for y = 0, gameloop.world.y -gameloop.textures[hash].image:getPixelHeight(),
gameloop.textures[hash].image:getPixelHeight() do
love.graphics.draw(gameloop.textures[hash].image, x, y)
end
function drawing.fillCanvas(layer, hash, color)
love.graphics.setCanvas(levelloop.Canvas[layer])
love.graphics.clear()
utils.color_push()
love.graphics.setColor(color)
for x = 0, levelloop.world.x -levelloop.textures[hash].image:getPixelWidth(),
levelloop.textures[hash].image:getPixelWidth() do
for y = 0, levelloop.world.y -levelloop.textures[hash].image:getPixelHeight(),
levelloop.textures[hash].image:getPixelHeight() do
love.graphics.draw(levelloop.textures[hash].image, x, y)
end
love.graphics.setCanvas()
end
utils.color_pop()
love.graphics.setCanvas()
end
function drawing.fillCanvasNetwork(layer, hash)
gameloop.networkSend(unit("fillCanvas", layer, love.data.encode("string", "base64", hash)))
drawing.fillCanvas(layer, hash)
function drawing.fillCanvasNetwork(layer, hash, color)
color.nwType = "color"
levelloop.remoteCall("fillCanvas", layer, hash, color)
drawing.fillCanvas(layer, hash, color)
end
function drawing.keyreleased(key, _)
assert(drawing.color)
if key == "c" then
drawing.clearAllCanvasesNetwork()
end
if key == "f" then
drawing.fillCanvasNetwork("fg", drawing.cursorHash)
drawing.fillCanvasNetwork("fg", drawing.cursorHash, drawing.color)
end
if key == "b" then
drawing.fillCanvasNetwork("bg", drawing.cursorHash)
drawing.fillCanvasNetwork("bg", drawing.cursorHash, drawing.color)
end
end
local drawmouse = {}
function drawing.input(dt)
local textures = gameloop.textures
local textures = levelloop.textures
local cursor = drawing.cursor
if not cursor or not cursor.data then return end
assert(cursor, "Failed to find cursor")
@ -73,8 +79,8 @@ function drawing.input(dt)
end
mousex = mousex / scale - view.x
mousey = mousey / scale - view.y
if mousex > gameloop.world.x or
mousey > gameloop.world.y or
if mousex > levelloop.world.x or
mousey > levelloop.world.y or
mousex < 0 or
mousey < 0 then
return
@ -99,18 +105,17 @@ function drawing.input(dt)
if not (thisx == thisx) or not (thisy == thisy) then return end
local ctrlDown = love.keyboard.isDown("lctrl") or love.keyboard.isDown("rctrl")
love.graphics.setBlendMode("replace")
if ctrlDown then
love.graphics.setCanvas(gameloop.buffer.pixel.bg)
love.graphics.setCanvas(levelloop.Canvas.bg)
else
love.graphics.setCanvas(gameloop.buffer.pixel.fg)
love.graphics.setCanvas(levelloop.Canvas.fg)
end
if love.mouse.isDown(1) then -- only delete
color_push()
utils.color_push()
love.graphics.setColor(drawing.color)
love.graphics.draw(cursor.image, thisx, thisy)
color_pop()
utils.color_pop()
if ctrlDown then
drawing.draw_texture(drawing.cursorHash, thisx, thisy, "bg", drawing.color)
else
@ -120,7 +125,8 @@ function drawing.input(dt)
cursor.image:getPixelHeight())
end
else
color_push()
love.graphics.setBlendMode("replace")
utils.color_push()
love.graphics.setColor(0,0,0,0)
love.graphics.rectangle("fill", thisx, thisy,
cursor.image:getPixelWidth(),
@ -137,9 +143,9 @@ function drawing.input(dt)
cursor.image:getPixelWidth(),
cursor.image:getPixelHeight())
end
color_pop()
utils.color_pop()
love.graphics.setBlendMode("alpha")
end
love.graphics.setBlendMode("alpha")
love.graphics.setCanvas()
end
drawmouse.lastX = mousex
@ -155,8 +161,8 @@ function drawing.draw_texture(hash, x, y, layer, color)
if not color[4] then color[4] = 1 end
local last = drawing.lastTexture
if hash ~= last.hash or x ~= last.x or y ~= last.y or layer ~= last.layer then
gameloop.networkSend(unit("drawTexture", x, y,
layer, love.data.encode("string", "base64", hash), color[1], color[2], color[3], color[4]))
color.nwType = "color"
levelloop.remoteCall("drawTexture", x, y, layer, hash, color)
end
last.hash, last.x, last.y, last.layer = hash, x, y, layer
end
@ -168,7 +174,7 @@ function drawing.del_texture(x, y, width, height, layer)
local last = drawing.lastTextureDel
if x ~= last.x or y ~= last.y or width ~= last.width
or height ~= last.height or layer ~= last.layer then
gameloop.networkSend(unit("deleteTexture", x, y, width, height, layer))
levelloop.remoteCall("deleteTexture", x, y, width, height, layer)
last.x, last.y, last.width, last.height, last.layer = x, y, width, height, layer
end
end
@ -177,7 +183,7 @@ end
local lastDWP = { x = 0, y = 0, width = 0, height = 0}
function drawing.draw_solid(x, y, width, height)
if x ~= lastDWP.x or y ~= lastDWP.y or width ~= lastDWP.width or height ~= lastDWP.height then
gameloop.networkSend(unit("drawPhysics", x, y, width, height))
levelloop.remoteCall("drawPhysics", x, y, width, height)
physics.draw_solid(x, y, width, height)
lastDWP.x, lastDWP.y, lastDWP.width, lastDWP.height = x, y, width, height
end
@ -187,14 +193,15 @@ end
local lastRMP = { x = 0, y = 0, width = 0, height = 0}
function drawing.remove_solid(x, y, width, height)
if x ~= lastRMP.x or y ~= lastRMP.y or width ~= lastRMP.width or height ~= lastRMP.height then
gameloop.networkSend(unit("deletePhysics", x, y, width, height))
levelloop.remoteCall("deletePhysics", x, y, width, height)
physics.remove_solid(x, y, width, height)
lastRMP.x, lastRMP.y, lastRMP.width, lastRMP.height = x, y, width, height
end
end
function drawing.init()
local path = "textures/blocks/krock/Glass/3.png"
local path = "textures/blocks/krock/Glass/03.png"
local texture = {data = love.image.newImageData(path),
image = love.graphics.newImage(path)}
drawing.color = {1, 1, 1, 1}
@ -203,7 +210,6 @@ function drawing.init()
drawing.cursor = textures[drawing.cursorHash]
texture, path = nil, nil
local clientChannel = love.thread.getChannel ( "clientChannel" );
mouse.lastpos = {}
mouse.lastpos.x = 0

View File

@ -1,26 +1,44 @@
gameloop = {}
gameloop.world = nil
gameloop.textures = {}
local rpc = require("server.rpc")
local v2_message = require("lua.network")
local socket = require "socket"
local util = require("server.utils")
levelloop = {}
levelloop.CLIENT = true
levelloop.world = nil
levelloop.textures = {}
levelloop.avatars = {}
commands = {}
require("lua.commands")(commands)
require("shared.commands")(commands)
local protocolVersion = "PPY\x03"
local socket = require "socket"
local utils = require("shared.utils")
local fonts = require("shared.fonts")
local errorHandler = require("shared.error")
local rpc = require("server.rpc")
local constants = require("server.constants")
local format = require("shared.networkFormat")
levelloop.nwChecklist = {}
levelloop.Canvas = {}
local count = 1
local function normalDraw()
if count == 1 then print("use canvas!") count = 2 end
local layerTransform = love.math.newTransform(math.floor(view.x) * scale, math.floor(view.y) * scale, 0, scale, scale)
-- Draw background
if drawBackground then
color_push()
utils.color_push()
love.graphics.setColor(0.05, 0.05, 0.05)
love.graphics.rectangle("fill", math.floor(view.x) * scale, math.floor(view.y) * scale, gameloop.world.x * scale, gameloop.world.y * scale)
love.graphics.rectangle("fill", math.floor(view.x) * scale, math.floor(view.y) * scale, levelloop.world.x * scale, levelloop.world.y * scale)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(gameloop.buffer.pixel.bg, layerTransform)
color_pop()
love.graphics.draw(levelloop.Canvas.bg, layerTransform)
utils.color_pop()
end
-- draw player
for i, player in pairs(players) do
for i, player in pairs(participants) do
local playerTransform = love.math.newTransform(
math.floor(player.x) * scale + math.floor(view.x) * scale,
math.floor(player.y) * scale + math.floor(view.y) * scale,
@ -29,7 +47,7 @@ local function normalDraw()
love.graphics.draw(player.avatar, playerTransform)
if player.name then
if not player.label then
player.label = love.graphics.newText(ui.smallFont, player.name)
player.label = love.graphics.newText(fonts.smallFont, player.name)
end
local labelTransform = love.math.newTransform(
math.floor(player.x - (player.label:getWidth() / 2) +
@ -41,7 +59,7 @@ local function normalDraw()
end
-- Draw foreground
love.graphics.draw(gameloop.buffer.pixel.fg, layerTransform)
love.graphics.draw(levelloop.Canvas.fg, layerTransform)
-- Draw UI
love.graphics.draw(ui.buffer)
@ -51,184 +69,156 @@ end
local function debugDraw()
normalDraw()
local layerTransform = love.math.newTransform(math.floor(view.x) * scale, math.floor(view.y) * scale, 0, scale, scale)
for i = 30, 120, 30 do
love.graphics.draw(ui.speedText[i/30], 400, i-30)
end
love.graphics.draw(levelloop.Canvas.dbg, layerTransform)
love.graphics.draw(levelloop.Canvas.dbg2, layerTransform)
love.graphics.draw(gameloop.buffer.pixel.dbg, layerTransform)
love.graphics.draw(gameloop.buffer.pixel.dbg2, layerTransform)
love.graphics.setCanvas(gameloop.buffer.pixel.dbg2)
love.graphics.setCanvas(levelloop.Canvas.dbg2)
love.graphics.clear()
love.graphics.setCanvas()
end
local function normalNetworkSend(args)
if clientID == "0" then print("Invalid clientID?") end
local request = "poppyV002" ..US.. clientID ..US.. args
gameloop.client:send(request)
local function remoteCall(name, ...)
local request = protocolVersion .. format.encodeFunction(name, levelloop.clientID or 0, ...)
levelloop.client:send(request)
end
local function debugNetworkSend(args)
assert(clientID ~= 0)
local request = "poppyV002" ..US.. clientID ..US.. args
print("=> " .. util.pprint(request))
gameloop.client:send(request)
end
function normalNetworkSync()
local message, errorString = nil, false
while not errorString do
local message, errorString = gameloop.client:receive()
local function networkSync()
while true do
local message, fault = levelloop.client:receive()
if not message then
if errorString == "timeout" then return else
error(errorString)
end
end
local response = rpc.validate(message)
if not response.errorMsg then
rpc.execute(v2_message, response.clientID, response.record)
message = nil
else
error(errorMsg)
end
end
end
function debugNetworkSync()
local message, errorString = nil, false
while not errorString do
local message, errorString = gameloop.client:receive()
if message then print("<= " .. util.pprint(message)) end
if not message then
if errorString == "timeout" then return else
menu.init()
if fault == "timeout" then
return
else
error(fault)
end
end
local response = rpc.validate(message)
if not response.errorMsg then
rpc.execute(v2_message, response.clientID, response.record)
message = nil
local protocolVersion, index = string.unpack("<c4", message, 1)
if protocolVersion ~= "PPY\x03" then
print("Dropping packet with invalid protocol " .. protocolVersion)
else
error(errorMsg)
print("got poppy package")
local DecoderType, index = string.unpack("<I1", message, index)
if (DecoderType == 0x81) then
name, clientID, args = format.decodeFunction(message, index)
print(name)
print(#args)
for i, val in pairs(args) do
print(i, val)
end
commands[name](clientID, table.unpack(args))
end
end
end
end
local function errorhandlerNetwork(msg)
gameloop.networkSend(unit("playerLeave"))
return errorhandler(msg)
levelloop.remoteCall("playerLeave")
return errorHandler.redscreen(msg)
end
function errorhandler(msg)
local errorfont
if love.filesystem.getInfo("fonts/font.ttf") then
errorfont = love.graphics.newFont("fonts/font.ttf", 45)
else
errorfont = love.graphics.getFont(45)
end
love.graphics.reset()
love.graphics.origin()
love.graphics.clear(.4, 0, 0)
love.graphics.setColor(1, 1, 1)
local message = love.graphics.newText(errorfont, "Poppy has encountered a critical fault")
local message2 = love.graphics.newText(errorfont, "Press q to exit")
local message3 = love.graphics.newText(errorfont, msg)
love.graphics.draw(message, 40, 40, 0, 1, 1)
love.graphics.draw(message2, 40, 95, 0, 1, 1)
love.graphics.draw(message3, 40, 145, 0, 1, 1)
local indent = 0
local messageline
for line in debug.traceback():gmatch("([^\n]*)\n?") do
messageline = love.graphics.newText(errorfont, line)
love.graphics.draw(messageline, 40, 255 + indent, 0, 1, 1)
indent = indent + 55
end
love.graphics.present()
return function()
love.event.pump()
for event, action in love.event.poll() do
if event == "quit" then return 1 end
if event == "keypressed" then
if action == "q" then
love.event.quit( 1 )
end
end
end
love.timer.sleep(0.1)
end
end
function gameloop.mousepressed(mousex, mousey)
function levelloop.mousepressed(mousex, mousey)
ui.mousepressed(mousex, mousey)
end
function gameloop.init(server, nickname)
love.errorhandler = errorhandler
assert(server, "Gameloop called without server to connect to?")
assert(nickname, "Gameloop called without nickname?")
gameloop.server = server
function levelloop.loadAvatars()
local container = {}
local entries = love.filesystem.getDirectoryItems("textures/player")
if #entries == 0 then return error("no avatars found") end
for i, file in pairs(entries) do
local path = "textures/player" .."/".. file
local entry = love.filesystem.getInfo(path)
if entry.type == "directory" then
local textures = love.filesystem.getDirectoryItems(path)
for i, texture in pairs(textures) do
print(path)
local imageData = love.image.newImageData(path .. "/" .. texture)
local image = love.graphics.newImage(path .. "/" .. texture)
local imageHash = love.data.hash("sha512", imageData:getString())
container[imageHash] = {data = imageData, image = image}
end
end
end
levelloop.avatars = container
end
function levelloop.init(server, nickname, avatarHash)
love.errorhandler = errorHandler.redscreen
assert(server, "levelloop called without server to connect to?")
assert(nickname, "levelloop called without nickname?")
assert(avatarHash, "levelloop called without avatar?")
levelloop.server = server
scale = 1
love.graphics.setDefaultFilter("nearest", "nearest", 0)
layer = true
grid = 16
color = {}
mouse = {}
window = {}
window.x, window.y = love.graphics.getDimensions()
players = {}
participants = {}
textures = {}
view = {}
view.x = 0
view.y = 0
clientID = "0"
gameloop.client = socket.udp()
gameloop.client:setpeername(server.adress, server.port)
gameloop.client:settimeout(0)
-- Must be done now because it has to be ready before "init" returns from the server
-- which is /before/ the game world is loaded
levelloop.loadAvatars()
print("Socketnname: " .. gameloop.client:getsockname())
levelloop.client = socket.udp()
levelloop.client:setpeername(server.adress, server.port)
levelloop.client:settimeout(0)
CLIENT = true
SERVER = false
print("Socketnname: " .. levelloop.client:getsockname())
print("Connecting to [" .. server.adress .."]:" .. server.port)
local request = "poppyV002" ..US.. "0" ..US.. unit("init", nickname)
gameloop.client:send(request)
if NETWORK_DEBUG then print("=> " .. util.pprint(request)) end
remoteCall("init", nickname, avatarHash)
if not NETWORK_DEBUG then
gameloop.networkSend = normalNetworkSend
gameloop.networkSync = normalNetworkSync
else
gameloop.networkSend = debugNetworkSend
gameloop.networkSync = debugNetworkSync
end
levelloop.remoteCall = remoteCall
levelloop.networkSync = networkSync
love.update = function(dt)
gameloop.networkSync(dt)
networkSync(dt)
love.timer.sleep((1/60) -dt)
end
local infoFont
if love.filesystem.getInfo("fonts/font.ttf") then
infoFont = love.graphics.newFont("fonts/font.ttf", 45)
else
infoFont = love.graphics.newFont(45)
end
local message = love.graphics.newText(infoFont, "Connecting...")
love.graphics.draw(message, 40, 40, 0, 1, 1)
love.draw = function(dt)
love.graphics.present()
love.graphics.setColor(0.05, 0.05, 0.05)
love.graphics.rectangle("fill", 0, 0, window.x, window.y)
love.graphics.setColor(1, 1, 1)
love.graphics.draw(message, 40, 40, 0, 1, 1)
end
end
function gameloop.normalVisible(dt)
gameloop.networkSync(dt)
function levelloop.normalVisible(dt)
networkSync(dt)
camera.input(dt)
physics.input(dt)
drawing.input(dt)
@ -238,20 +228,20 @@ function gameloop.normalVisible(dt)
end
function gameloop.reducedVisible(dt)
gameloop.networkSync(dt)
function levelloop.reducedVisible(dt)
networkSync(dt)
physics.update(dt)
love.timer.sleep((1/60) -dt)
end
function gameloop.keypressed(key, _)
function levelloop.keypressed(key, _)
physics.keypressed(key, _)
camera.keypressed(key, _)
end
function gameloop.keyreleased(key, _)
function levelloop.keyreleased(key, _)
if key == "r" then
drawBackground = not drawBackground
end
@ -261,53 +251,52 @@ function gameloop.keyreleased(key, _)
end
function gameloop.loadworld(world)
function levelloop.loadworld(world)
-- Game connected!
love.errorhandler = errorhandlerNetwork
local buffer = {}
buffer.pixel = {}
buffer.pixel.fg = love.graphics.newCanvas(world.x, world.y)
buffer.pixel.bg = love.graphics.newCanvas(world.x, world.y)
--love.errorhandler = errorhandlerNetwork
print("make canvas!")
levelloop.Canvas.fg = love.graphics.newCanvas(world.x, world.y)
levelloop.Canvas.bg = love.graphics.newCanvas(world.x, world.y)
if PHYSICS_DEBUG then
buffer.pixel.dbg = love.graphics.newCanvas(world.x, world.y)
buffer.pixel.dbg2 = love.graphics.newCanvas(world.x, world.y)
levelloop.Canvas.dbg = love.graphics.newCanvas(world.x, world.y)
levelloop.Canvas.dbg2 = love.graphics.newCanvas(world.x, world.y)
end
buffer.physics = {}
gameloop.buffer = buffer
levelloop.Canvas.physics = {}
levelloop.world = world
ui.init()
drawing.init()
love.update = gameloop.normalVisible
love.update = levelloop.normalVisible
love.visible = function(visible)
if visible then
love.update = gameloop.normalVisible
love.update = levelloop.normalVisible
else
love.update = gameloop.reducedVisible
love.update = levelloop.reducedVisible
end
end
drawBackground = true
love.keyreleased = gameloop.keyreleased
love.keypressed = gameloop.keypressed
love.mousepressed = gameloop.mousepressed
love.keyreleased = levelloop.keyreleased
love.keypressed = levelloop.keypressed
love.mousepressed = levelloop.mousepressed
if not PHYSICS_DEBUG then
love.draw = normalDraw
else
love.draw = debugDraw
end
love.resize = function()
normalDraw()
ui.resize()
camera.resize()
end
love.quit = function()
gameloop.networkSend(unit("playerLeave"))
levelloop.remoteCall("playerLeave")
end
gameloop.world = world
levelloop.world = world
end

View File

@ -1,18 +0,0 @@
setmetatable(_G, {
__index = function(self, idx)
if rawget(self, idx) == nil then
error("attempted to read implicit global:" .. idx)
end
end
})
PHYSICS_DEBUG = true
NETWORK_DEBUG = true
constants = require("server.constants")
US = string.char(31)
local menu = require("lua.menuloop")
function love.load()
menu.init()
end

View File

@ -1,25 +1,36 @@
layout = {}
uiState = {}
local layout = {}
layout.uiState = {}
layout.uiState.Menu = {}
layout.uiState.GameUI = {}
layout.uiState.MainMenu = {}
local debugPrint = function(string)
local fonts = require("shared.fonts")
local utils = require("shared.utils")
local asserts = require("shared.asserts")
local catalog = require("shared.catalog")
function layout.debugPrint(string)
--print(string)
end
function layout.canvasAndColor(container, x, y, simulate)
asserts.number("layout.canvasAndColor", 2, x, y)
if simulate then return layout.handle(container[1], x, y, simulate) end
color_push()
utils.color_push()
love.graphics.setCanvas(container.canvas)
x, y = layout.handle(container[1], x, y, simulate)
love.graphics.setCanvas()
color_pop()
utils.color_pop()
return x, y
end
function layout.rotate(container, x, y, simulate)
assert_num("layout.rotate", 2, x, y)
assert_num("layout.rotate (container)", container.rotation)
asserts.number("layout.rotate", 2, x, y)
asserts.number("layout.rotate (container)", container.rotation)
local transform = love.math.newTransform(0, 0, container.rotation)
local noTransform = love.math.newTransform(0, 0, 0)
love.graphics.replaceTransform(transform)
@ -30,7 +41,8 @@ end
function layout.frame(container, x, y, simulate)
assert_num("layout.frame", 2, x, y)
asserts.number("layout.frame", 2, x, y)
x2, y2 = layout.handle(container[1], x +1, y +1, simulate)
if not simulate then
love.graphics.rectangle("line", x, y, x2 -x, y2 -y)
@ -41,31 +53,22 @@ end
function layout.cursorSelect(container, x, y, simulate)
x2, y2 = layout.handle(container[1], x, y, simulate)
assert(container.uiSelect)
if not simulate then
table.insert(uiState, { identifier = container.identifier, kind=container.kind,
table.insert(layout.uiState[container.uiSelect], { identifier = container.identifier, kind=container.kind,
x=x, x2=x2, y=y, y2=y2 })
end
return x2, y2
end
function layout.linewidth(container, x, y, simulate)
if simulate then return layout.handle(container[1], x, y, simulate) end
assert(container.width, "layout.linewidth (container) no container.width provided!")
local oldWidth = love.graphics.getLineWidth()
love.graphics.setLineWidth(container.width)
x, y = layout.handle(container[1], x, y, simulate)
love.graphics.setLineWidth(oldWidth)
return x, y
end
function layout.spacer(container, x, y, simulate)
assert(container.width or container.height, "No arguments for layout.spacer!")
assert(container.width == container.width,
"Argument container.width to layout.spacer must not be NaN!")
assert(container.height == container.height,
"Argument container.height to layout.spacer must not be NaN!")
local width = container.width or 0
local height = container.height or 0
if container[1] then
@ -82,12 +85,12 @@ function layout.drawTexture(container, x, y, simulate)
if not simulate then
love.graphics.draw(texture, x, y, 0, scale, scale)
end
return x + texture:getWidth()* scale, y + texture:getHeight() * scale
return x + (texture:getWidth() * scale), y + texture:getHeight() * scale
end
function layout.drawHash(container, x, y, simulate)
local texture = gameloop.textures[container.hash]
local texture = levelloop.textures[container.hash]
local scale = container.scale or 1
if not texture.image then
texture.image = love.graphics.newImage(texture.data)
@ -99,42 +102,80 @@ function layout.drawHash(container, x, y, simulate)
end
function layout.bwColorPicker(container, x, y, simulate)
asserts.number("layout.bwColorPicker", 2, x, y)
local startx, starty = x, y
granularity = container.granularity or 0.04
local points = {}
local myx = x
local endx = 0
local pointSize = container.pointSize or 8
if not simulate then
utils.color_push()
love.graphics.setPointSize(pointSize)
for c=0, 1.0, granularity do
love.graphics.setColor(c, c, c, 1)
love.graphics.points(myx+(pointSize/2), y +(pointSize/2))
myx = myx + pointSize
end
utils.color_pop()
local iterationCount = (1.0 / granularity)
maxx = x + pointSize * iterationCount
table.insert(layout.uiState.GameUI, { granularity = granularity, pointSize = pointSize,
kind=container.kind,
x=x, x2=maxx, y=y, y2=y + pointSize })
else
local iterationCount = (1.0 / granularity)
maxx = x + pointSize * iterationCount
maxy = y + pointSize
return maxx, maxy
end
return maxx, y + pointSize
end
function layout.colorPicker(container, x, y, simulate)
assert(x and y)
asserts.number("layout.colorPicker", 2, x, y)
local startx, starty = x, y
granularity = container.granularity or 0.2
local points = {}
local myx, myy = x, y
local endx = 0
local pointsize = 16
local pointSize = container.pointSize or 8
if not simulate then
color_push()
love.graphics.setPointSize(pointsize)
utils.color_push()
love.graphics.setPointSize(pointSize)
for r=0, 1.0, granularity do
for g=0, 1.0, granularity do
for b=0, 1.0, granularity do
love.graphics.setColor(r, g, b, 1)
myx=myx+16
love.graphics.points(myx-4.5, myy+4.5)
myx=myx+pointSize
love.graphics.points(myx-(pointSize/2) -.5, myy +(pointSize/2))
end
end
myy = myy + 16
myy = myy + pointSize
if myx > endx then endx = myx end
myx = x
end
color_pop()
utils.color_pop()
local iterationCount = (1.0 / granularity) + 1
maxx = x + pointsize * iterationCount * iterationCount
table.insert(uiState, { granularity = granularity, pointsize = pointsize,
maxx = x + pointSize * iterationCount * iterationCount
table.insert(layout.uiState.GameUI, { granularity = granularity, pointSize = pointSize,
kind=container.kind,
x=x, x2=maxx, y=y, y2=myy })
else
local iterationCount = (1.0 / granularity) + 1
maxx = x + pointsize * iterationCount * iterationCount
maxy = y + pointsize * iterationCount
maxx = x + pointSize * iterationCount * iterationCount
maxy = y + pointSize * iterationCount
return maxx, maxy
end
return maxx, myy
end
@ -142,34 +183,64 @@ end
function layout.color(container, x, y, simulate)
if simulate then return layout.handle(container[1], x, y, simulate) end
assert(container.color, "layout.color Color!")
assert(#container.color == 3 or #container.color == 4)
color_push()
utils.color_push()
love.graphics.setColor(container.color)
x, y = layout.handle(container[1], x, y, simulate)
color_pop()
utils.color_pop()
return x, y
end
function layout.rect(container, x, y, simulate)
assert_num("layout.rect", 2, x, y)
assert_num("layout.rect (container)", 2, container.width, container.height)
if not simulate then
if container.color then
color_push()
love.graphics.setColor(container.color.r, container.color.g, container.color.b, container.color.a or 1)
asserts.number("layout.rect", 2, x, y)
asserts.number("layout.rect (container)", 2, container.width, container.height)
if simulate then return x + container.width, y + container.height end
if container.color then
utils.color_push()
love.graphics.setColor(container.color.r, container.color.g, container.color.b, container.color.a or 1)
love.graphics.rectangle(container.fill or "fill", x, y, container.width, container.height)
utils.color_pop()
else
love.graphics.rectangle(container.fill or "fill", x, y, container.width, container.height)
color_pop()
else
love.graphics.rectangle(container.fill or "fill", x, y, container.width, container.height)
end
end
return x + container.width, y + container.height
end
function layout.button(container, x, y, simulate)
local textObject = love.graphics.newText(fonts.headerFont, container.text)
local uiSelect = layout.uiState[container.uiSelect] or error("layout.button passed invalid uiSelect!" .. container.uiSelect or "no value!")
assert(container.identifier, "layout.button no identifier passed!")
local buttonColor = catalog.buttonColors[container.buttonColor or "blue"]
local width = textObject:getWidth()
local height = textObject:getHeight()
local factor = 1.15
if not simulate then
utils.color_push()
love.graphics.setColor(buttonColor[1])
love.graphics.rectangle("fill", x, y, width* factor, height* factor,
height* factor/4, height* factor/4)
love.graphics.setColor(buttonColor[2])
love.graphics.rectangle("line", x, y, width* factor, height* factor,
height* factor/4, height* factor/4)
love.graphics.setColor(buttonColor[3])
love.graphics.draw(textObject, x + width * 0.075 , y + height * 0.075)
utils.color_pop()
end
table.insert(uiSelect, { identifier = container.identifier, kind="button",
x=x, x2=x+(width *factor), y=y, y2=y + (height * factor) })
return x+ (width *factor), y + (height * factor)
end
function layout.overlayRect(container, x, y, simulate)
assert_num("layout.overlayRect", 2, x, y)
asserts.number("layout.overlayRect", 2, x, y)
winx, winy = layout.handle(container[1], x, y, simulate)
width = winx -x
height = winy -y
@ -180,11 +251,33 @@ function layout.overlayRect(container, x, y, simulate)
end
function layout.labelWShadow(container, x, y, simulate)
-- TODO: Replace this with a text shadow shader FFS
if not container.font then container.font = fonts.normalFont end
local text = love.graphics.newText( container.font, container.text )
if not text then text = love.graphics.newText( container.font, "Unset text!!") end
if not simulate then
utils.color_push()
love.graphics.setColor(0,0,0)
love.graphics.draw(text, x, y +4)
love.graphics.draw(text, x, y +2)
love.graphics.draw(text, x, y +0)
love.graphics.draw(text, x +2, y +2)
love.graphics.draw(text, x +2, y +0)
love.graphics.draw(text, x +4, y +4)
love.graphics.draw(text, x +4, y +2)
love.graphics.draw(text, x +4, y +0)
utils.color_pop()
love.graphics.draw(text, x +2, y +2)
end
return x + text:getWidth() + 2, y + text:getHeight() +2
end
function layout.label(container, x, y, simulate)
if not container.font then container.font = ui.font end
if not container.font then container.font = fonts.normalFont end
local text = love.graphics.newText( container.font, container.text )
if not text then text = "Unset text!!" end
if not text then text = love.graphics.newText( container.font, "Unset text!!") end
if not simulate then
love.graphics.draw(text, x, y)
end
@ -214,7 +307,40 @@ function layout.horizontal(container, x, y, simulate)
if ret.y > bigy then bigy = ret.y end
x = ret.x
end
return x,bigy
return x, bigy
end
function layout.naiveGrid(container, startX, startY, simulate)
-- This is naiveGrid and not grid because the splitting alghorythm splits
-- *after* the threshold is reached, thus individual lines can be quite a bit longer
-- depending on the passed content.
-- todo: rewrite this with a (possibly) more expensive grid function that
-- simulates everything before doing it
local maxX, maxY = startX, startY
local workX, workY = startX, startY -- The current "pointer" for where to draw
local retX, retY = startX, startY -- the values returned by the newly drawn shape
local width = container.width
if not simulate then
assert(container[1], "naiveGrid called without arguments!")
else
if not container[1] then return startX, startY end
end
for i, v in ipairs(container) do
retX, retY = layout.handle(v, workX, workY, simulate)
if retY > maxY then maxY = retY end
if retX > startX + width then
workY = maxY
workX = startX
if retX > maxX then maxX = retX end
else
workX = retX
end
end
return maxX, maxY
end
@ -247,13 +373,22 @@ function layout.vertical(container, x, y, simulate)
end
function layout.skip(container, x, y, simulate)
return x, y
end
local depth = -1
function layout.handle(container, x, y, simulate)
assert_num("layout.handle", 2, x, y)
asserts.number("layout.handle", 2, x, y)
assert(container, "layout.handle called without container")
local name = container["name"]
if not name then return end
assert(name, "layout.handle container without name")
assert(layout[name], "Unknown layout function " .. name .. " called")
depth = depth +1
local depthCursor = ""
for i=0, depth do
@ -261,22 +396,24 @@ function layout.handle(container, x, y, simulate)
end
if depth == 0 then
if simulate then
debugPrint("Starting simulation")
layout.debugPrint("Starting simulation")
else
debugPrint("Starting rendering")
layout.debugPrint("Starting rendering")
end
end
debugPrint(depthCursor.. ">" .. container.name)
layout.debugPrint(depthCursor.. ">" .. container.name)
x, y = layout[name](container, x, y, simulate)
assert_num("layout.handle" .. container.name .. " is not returning properly!", 2, x, y)
debugPrint(depthCursor.."<" .. container.name)
asserts.number("layout.handle" .. container.name .. " is not returning properly!", 2, x, y)
layout.debugPrint(depthCursor.."<" .. container.name)
depth = depth -1
if depth == -1 then
if simulate then
debugPrint("simulation ended\n")
layout.debugPrint("simulation ended\n")
else
debugPrint("rendering ended\n")
layout.debugPrint("rendering ended\n")
end
end
return x, y
end
return layout

View File

@ -1,9 +1,16 @@
menu = {}
menu.serverlist = {
{description = "A localhost server [ipv4]", host = "127.0.0.1", port = "11150"},
{description = "A localhost server [ipv6]", host = "::1", port = "11150"}
{description = "A localhost server", host = "::1", port = "11150"},
{description = "A localhost server [ipv4]", host = "127.0.0.1", port = "11150"}
}
local utils = require("shared.utils")
local fonts = require("shared.fonts")
local catalog = require("shared.catalog")
local icon = require("shared.icon")
local layout = require("lua.layout")
local altPressed, enterPressed = false, false
function menu.keyreleased(key, _)
if key == "lalt" then
@ -25,11 +32,6 @@ function menu.keypressed(key, _)
end
function menu.update(dt)
love.timer.sleep((1/60) -dt)
end
function menu.resize()
menu.width, menu.height = love.window.getMode()
menu.Canvas = love.graphics.newCanvas(width, height)
@ -40,20 +42,50 @@ function menu.resize()
end
local firstClient = true
function menu.mousepressed(mousex, mousey)
local textures = gameloop.textures
for i, v in ipairs(uiState) do
if mousex >= v.x and mousex <= v.x2 then
if mousey >= v.y and mousey <= v.y2 then
if v.kind == "server" then
local names = { "Herbert", "Thorben", "Martin", "Heinrich", "Dietrich", "Markus", "Florian", "Helmut", "Willhelm", "Fritz", "Gustav", "Konrad", "Berta", "Charlotte", "Hildegard", "Lieselotte", "Gudrun", "Giesela", "Margarete", "Antonia", "Friederike", "Clotilde", "Marlies", "Hedwig" }
local lastNames = { "Müller", "Schmidt", "Meier", "Bauer", "Werner", "Schumacher", "Bergmann", "Eisenhauer", "Heisenberg" }
local fullName = names[love.math.random(1, #names)] .. " " .. lastNames[love.math.random(1, #lastNames)]
local serverID = v.identifier
local serverEntry = menu.serverlist[serverID]
gameloop.init({ adress = serverEntry.host, port = serverEntry.port }, fullName)
uiState = {}
for i, v in ipairs(layout.uiState.MainMenu) do
if mousex >= v.x and mousex <= v.x2 and mousey >= v.y and mousey <= v.y2 then
if v.kind == "localServer" then
menuMusic[menuMusic.trac]:stop()
love.graphics.reset()
love.graphics.origin()
love.graphics.clear(.6, 0, .6)
love.graphics.setColor(1, 1, 1)
love.graphics.present()
local serverloop = require("server.main")
serverloop.init(v.identifier)
elseif v.kind == "server" then
if firstClient then
menuMusic[menuMusic.trac]:stop()
dofile "lua/gameloop.lua"
dofile "lua/layout.lua"
dofile "lua/ui.lua"
dofile "lua/camera.lua"
dofile "lua/physics.lua"
dofile "lua/drawing.lua"
setmetatable(_G, {
__index = function(self, idx)
if rawget(self, idx) == nil then
error("attempted to read implicit global:" .. idx)
end
end
})
PHYSICS_DEBUG = true
NETWORK_DEBUG = true
firstclient = false
end
local textures = levelloop.textures
local fullName = catalog.firstNames[love.math.random(1, #catalog.firstNames)]
.. " " .. catalog.surNames[love.math.random(1, #catalog.surNames)]
local serverID = v.identifier
local serverEntry = menu.serverlist[serverID]
local avatar = love.image.newImageData(catalog.default.avatar)
local avatarHash = love.data.hash("sha512", avatar:getString())
levelloop.init({ adress = serverEntry.host, port = serverEntry.port }, fullName, avatarHash)
layout.uiState.MainMenu = {}
end
end
end
@ -61,68 +93,245 @@ end
menu.serverentry = function(id, description, host, port)
return {name = "horizontal",
{name = "label", text = description },
{name = "spacer", width = 20 },
{name = "label", text = "[".. host .."]"},
{name = "label", text = ":".. port },
{name = "spacer", width = 25 },
{name = "cursorSelect", kind = "server", identifier = id,
{name = "drawTexture", texture = love.graphics.newImage("textures/menu/serverPlay.png")}}}
return {name = "cursorSelect", kind = "server", identifier = id, uiSelect = "MainMenu",
{name = "horizontal",
{name = "drawTexture", texture = icon.joinServer},
{name = "spacer", width = 10 },
{name = "labelWShadow", text = description },
{name = "spacer", width = 20 },
{name = "labelWShadow", text = "host: ".. host, font = fonts.smallFont},
{name = "spacer", width = 10 },
{name = "labelWShadow", text = "port:".. port, font = fonts.smallFont},
}
}
end
menu.layoutServerList = function()
local list = {name = "vertical"}
local list = {}
for i, v in ipairs(menu.serverlist) do
table.insert(list, menu.serverentry(i, v.description, v.host, v.port))
end
return list
return unpack(list)
end
menu.layout = {name ="vertical",
{name = "spacer", height = 20},
{name = "horizontal",
{name = "spacer", width = 20},
{name = "drawTexture", texture = love.graphics.newImage("ressources/Poppy.png")},
{name = "label", text = "Welcome to poppy!"},
},
{name = "horizontal",
{name = "spacer", width = 60},
{name = "vertical",
{name = "spacer", height = 60},
{name = "label", text = "Pick a server to join!"},
{name = "spacer", height = 20},
menu.layoutServerList()
menu.layoutLocalServer = function()
return {name = "vertical",
{name = "cursorSelect", kind = "localServer",
identifier = {"::1", "11150"}, uiSelect = "MainMenu",
{name = "horizontal",
{name = "spacer", width = 30},
{name = "drawTexture",
texture = icon.startServer
},
{name = "spacer", width = 5},
{name = "labelWShadow", text = "Create a local server"},
}
},
{name = "cursorSelect", kind = "localServer",
identifier = {"0.0.0.0", "11150"}, uiSelect = "MainMenu",
{name = "horizontal",
{name = "spacer", width = 30},
{name = "drawTexture",
texture = icon.startServer
},
{name = "spacer", width = 5},
{name = "labelWShadow", text = "Create a local server [IPV4]"},
}
},
{name = "spacer", height = 20},
}
end
menu.layout = function(message)
return {name ="vertical",
{name = "spacer", height = 20},
{name = "horizontal",
{name = "spacer", width = 20},
{name = "drawTexture", texture = icon.logo},
{name = "labelWShadow", font = fonts.bigFont, text = "Welcome to poppy!"},
},
{name = "horizontal",
{name = "spacer", width = 50},
{name = "vertical",
{name = "labelWShadow", text = "Pick a server to join!", font = fonts.headerFont},
{name = "spacer", height = 20},
{name = "horizontal",
{name = "spacer", width = 10},
{name = "vertical",
{name = "vertical",
menu.layoutServerList(),
},
{name = "vertical",
{name = "spacer", height = 10},
{name = "horizontal",
{name = "drawTexture", texture = icon.addServer},
{name = "spacer", width = 10 },
{name = "labelWShadow", text = "placeholder (new server)" },
{name = "spacer", width = 20 },
{name = "labelWShadow", text = "host: newhost", font = fonts.smallFont},
{name = "spacer", width = 10 },
{name = "labelWShadow", text = "port: newport", font = fonts.smallFont},
}
}
}
}
}
}
}
}
end
function drawMenu()
color_push()
function drawMenu(message)
utils.color_push()
love.graphics.setCanvas(menu.Canvas)
uiState = {}
layout.uiState.MainMenu = {}
love.graphics.clear( )
layout.handle(menu.layout, 0, 0)
layout.handle(menu.layout(message), 0, 0)
local sizex, sizey = layout.handle(menu.layoutLocalServer(), 0, 0, true)
layout.handle(menu.layoutLocalServer(), 0, menu.height - sizey)
love.graphics.setCanvas()
color_pop()
utils.color_pop()
end
function menu.draw(w, h)
local bubbles = {}
function menu.draw()
for i=1, #bubbles do
drawBubble(bubbles[i])
end
love.graphics.draw(menu.Canvas)
layout.handle(debugAudio(), 600, 300)
end
function menu.init()
menuMusic = {}
menuMusic[1] = love.audio.newSource("sound/Poppy Main Menu theme_2.ogg", "stream")
menuMusic[2] = love.audio.newSource("sound/Poppy Main Menu theme_2.ogg", "stream")
menuMusic.duration = menuMusic[1]:getDuration("seconds")
menuMusic.samples = menuMusic[1]:getDuration("samples")
menuMusic.fading = false
menuMusic.trac = 1
menuMusic.nextTrac = 2
menuMusic.fadeStart = 0
menuMusic.fadeEnd = 0
menuMusic.fadeTime = 0
function musicUpdate(dt)
if not menuMusic.fading then
if menuMusic[menuMusic.trac]:tell("seconds") >= menuMusic.duration - 20 then
menuMusic.fading = true
menuMusic.fadeStart = menuMusic[menuMusic.trac]:tell("samples")
menuMusic.fadeTime = menuMusic.samples - menuMusic.fadeStart
menuMusic[menuMusic.nextTrac]:setVolume(1)
menuMusic[menuMusic.nextTrac]:play()
end
else
local fadefraction = ( menuMusic.fadeStart - menuMusic[menuMusic.trac]:tell("samples") )
/ menuMusic.fadeTime
menuMusic[menuMusic.trac]:setVolume(fadefraction)
menuMusic[menuMusic.nextTrac]:setVolume(fadefraction - 1)
if fadefraction > 0.95 then
menuMusic[menuMusic.trac]:stop()
menuMusic[menuMusic.nextTrac]:setVolume(1)
local trac = menuMusic.trac
menuMusic.trac = menuMusic.nextTrac
menuMusic.nextTrac = trac
menuMusic.fading = false
end
end
end
function debugAudio()
return {name = "vertical",
{name = "labelWShadow", text = "CUR: " .. menuMusic.trac, font = fonts.smallFont},
{name = "labelWShadow", text = "CUR " .. menuMusic.duration, font = fonts.smallFont},
{name = "labelWShadow", text = "CUR " .. menuMusic[menuMusic.trac]:tell("seconds"), font = fonts.smallFont},
{name = "labelWShadow", text = "CUR " .. menuMusic.samples, font = fonts.smallFont},
{name = "labelWShadow", text = "CUR " .. menuMusic[menuMusic.trac]:tell("samples"), font = fonts.smallFont},
{name = "labelWShadow", font = fonts.smallFont, text = "CUR volume:" .. menuMusic[menuMusic.trac]:getVolume()},
{name = "spacer", height = 20},
{name = "labelWShadow", text = "NXT: " .. menuMusic.nextTrac, font = fonts.smallFont},
{name = "labelWShadow", text = "NXT " .. menuMusic.duration, font = fonts.smallFont},
{name = "labelWShadow", text = "NXT " .. menuMusic[menuMusic.nextTrac]:tell("seconds"), font = fonts.smallFont},
{name = "labelWShadow", text = "NXT " .. menuMusic.samples, font = fonts.smallFont},
{name = "labelWShadow", text = "NXT " .. menuMusic[menuMusic.nextTrac]:tell("samples"), font = fonts.smallFont},
{name = "labelWShadow", font = fonts.smallFont, text = "NXT volume:" .. menuMusic[menuMusic.nextTrac]:getVolume()},
}
end
local elapsed = 0
function menu.update(dt)
if elapsed > 1 / 60 then
for i=1, #bubbles do
local bubble = bubbles[i]
local sx = bubble.sx * dt
local sy = bubble.sy * dt
local bubble = bubbles[i]
bubble.x = bubble.x + sx
bubble.y = bubble.y + sy
if bubble.x + bubble.radius >= menu.width then
bubble.x = menu.width - bubble.radius
bubble.sx = -bubble.sx
end
if bubble.x - bubble.radius <= 0 then
bubble.x = bubble.radius
bubble.sx = -bubble.sx
end
if bubble.y - bubble.radius <= 0 then
bubble.y = bubble.radius
bubble.sy = -bubble.sy
end
if bubble.y + bubble.radius >= menu.height then
bubble.y = menu.height - bubble.radius
bubble.sy = -bubble.sy
end
end
elapsed = 0
end
elapsed = elapsed + dt
musicUpdate(dt)
end
function drawBubble(bubble)
utils.color_push()
love.graphics.setColor(bubble.color[1]* .6, bubble.color[2] * .6, bubble.color[3] * .6, bubble.color[4])
love.graphics.circle("fill", bubble.x, bubble.y, bubble.radius )
love.graphics.setColor(bubble.color[1]* .9, bubble.color[2] * .9, bubble.color[3] * .9, bubble.color[4])
love.graphics.circle("fill", bubble.x, bubble.y, bubble.radius -3 )
--love.graphics.setColor(bubble.color)
--love.graphics.circle("fill", bubble.x, bubble.y, bubble.radius/2 )
utils.color_pop()
end
function menu.init(message)
menu.width, menu.height = love.window.getMode()
math.randomseed(os.time())
for i=1,24 do
local bubble = {}
bubble.radius = 10 + math.floor(math.random() * 30)
bubble.color = catalog.pastelColors[math.floor(1 + math.random() * #catalog.pastelColors - 0.001)]
bubble.y = bubble.radius + math.floor(math.random() * (menu.height - (2* bubble.radius)))
bubble.x = bubble.radius + math.floor(math.random() * (menu.width - (2* bubble.radius)))
bubble.sx = 20 - math.random() * 40
bubble.sy = 20 - math.random() * 40
table.insert(bubbles, bubble)
end
menuMusic[menuMusic.trac]:play()
menu.Canvas = love.graphics.newCanvas(menu.width, menu.height)
drawMenu()
love.draw = menu.draw
love.update = menu.update
love.keyreleased = menu.keyreleased
love.keypressed = menu.keypressed
love.mousepressed = menu.mousepressed
end
return menu

View File

@ -1,157 +0,0 @@
local player = require("lua.player")
local util = require("server.utils")
local v2_message = {}
v2_message.init = function(clientID, args)
local nickname, args = util.nextStringRecord(args)
local worldID, args = util.nextStringRecord(args)
local x, args = util.nextIntRecord(args)
local y = tonumber(args)
print("Connected as " .. clientID .. " with name " .. nickname)
print("New world is " .. worldID .. " Size is [" .. x .. "|" .. y .. "]")
players[clientID] = player.newPlayer()
camera.target = clientID
_G.clientID = clientID
players[clientID].name = nickname
local world = { id = worldID, x = x, y = y }
gameloop.loadworld(world)
end
v2_message.ping = function(clientID)
gameloop.networkSend("pong")
end
v2_message.playerJoin = function(clientID, name)
ui.addChatEntry("", name .. " Joined the game")
players[clientID] = player.newPlayer()
players[clientID].label = love.graphics.newText(ui.smallFont, name)
players[clientID].name = name
end
v2_message.playerLeave = function(clientID)
if players[clientID] then
local name = players[clientID].name
ui.addChatEntry("", name .. " Left the game")
players[clientID] = nil
end
end
v2_message.moveUpdate = function(clientID, args)
if not players[clientID] then return end
local x, args = util.nextIntRecord(args)
local y, args = util.nextIntRecord(args)
local speedx, args = util.nextIntRecord(args)
local speedy = util.clean(args:gsub("^.-\31",""))
players[clientID].x = x
players[clientID].y = y
players[clientID].speed.x = speedx
players[clientID].speed.y = speedy
end
v2_message.moveFlying = function(clientID, args)
if not players[clientID] then return end
players[clientID].flying = (args == "true")
end
v2_message.deletePhysics = function(clientID, args)
if clientID == camera.target then return end
if not players[clientID] then return end
physics.remove_solid(util.getIntRecords(4, args))
end
v2_message.drawPhysics = function(clientID, args)
if clientID == camera.target then return end
if not players[clientID] then return end
physics.draw_solid(util.getIntRecords(4, args))
end
v2_message.drawTexture = function(clientID, args)
if clientID == camera.target then return end
local x, args = util.nextIntRecord(args)
local y, args = util.nextIntRecord(args)
local layer, args = util.nextStringRecord(args)
local hash, args = util.nextStringRecord(args)
hash = love.data.decode("string", "base64", hash)
local r, args = util.nextIntRecord(args)
local g, args = util.nextIntRecord(args)
local b, a = util.nextIntRecord(args)
a = tonumber(a)
if not r or not g or not b then
r, g, b = 1, 1, 1
end
if not a then
a = 1
end
if not gameloop.textures[hash] then print("Failed to find hash!") return end
love.graphics.setBlendMode("replace")
if layer == "fg" then
love.graphics.setCanvas(gameloop.buffer.pixel.fg)
elseif layer == "bg" then
love.graphics.setCanvas(gameloop.buffer.pixel.bg)
end
if not gameloop.textures[hash].image then love.graphics.newImage(gameloop.textures[hash].data) end
color_push()
love.graphics.setColor(r, g, b ,a)
love.graphics.draw(gameloop.textures[hash].image, x, y)
color_pop()
love.graphics.setBlendMode("alpha")
love.graphics.setCanvas()
end
v2_message.deleteTexture = function(clientID, args)
if clientID == camera.target then return end
local x, args = util.nextIntRecord(args)
local y, args = util.nextIntRecord(args)
local width, args = util.nextIntRecord(args)
local height, layer = util.nextIntRecord(args)
if layer == "fg" then
love.graphics.setCanvas(gameloop.buffer.pixel.fg)
elseif layer == "bg" then
love.graphics.setCanvas(gameloop.buffer.pixel.bg)
end
love.graphics.setBlendMode("replace")
color_push()
love.graphics.setColor(0,0,0,0)
love.graphics.rectangle("fill", x, y, width, height)
color_pop()
love.graphics.setBlendMode("alpha")
love.graphics.setCanvas()
end
v2_message.chatMessage = function(clientID, args)
local name
if clientID == "0" then
name = ""
else
name = players[clientID].name
end
ui.addChatEntry(players[clientID].name, args)
end
v2_message.clearCanvas = function(clientID)
drawing.clearAllCanvases()
end
v2_message.fillCanvas = function(clientID, args)
local layer, hash = util.nextStringRecord(args)
hash = love.data.decode("string", "base64", hash)
drawing.fillCanvas(layer, hash)
end
return v2_message

View File

@ -1,5 +1,8 @@
physics = {}
local asserts = require("shared.asserts")
local utils = require("shared.utils")
function physics.keyreleased_debug(key, _)
if key == "kp4" then
player.x = player.x -1
@ -17,30 +20,30 @@ end
function physics.remove_solid(x, y, sizex, sizey)
assert_num("physics.remove_solid", 4, x, y, sizex, sizey)
asserts.number("physics.remove_solid", 4, x, y, sizex, sizey)
for coord_x=x, x + sizex-1 do
for coord_y=y, y + sizey-1 do
gameloop.buffer.physics[coord_x*gameloop.world.x+coord_y] = nil
levelloop.Canvas.physics[coord_x*levelloop.world.x+coord_y] = nil
end
end
end
function physics.draw_solid(x, y, sizex, sizey)
assert_num("physics.draw_solid", 4, x, y, sizex, sizey)
asserts.number("physics.draw_solid", 4, x, y, sizex, sizey)
for coord_x=x, x + sizex-1 do
for coord_y=y, y + sizey-1 do
gameloop.buffer.physics[coord_x*gameloop.world.x+coord_y] = {0, 0, 0, true }
levelloop.Canvas.physics[coord_x*levelloop.world.x+coord_y] = {0, 0, 0, true }
end
end
end
function draw_colision(x, y, x2, y2, pixelx, pixely)
color_push()
utils.color_push()
love.graphics.setLineWidth(1)
love.graphics.setDefaultFilter("nearest", "nearest", 0)
love.graphics.setCanvas(gameloop.buffer.pixel.dbg2)
love.graphics.setCanvas(levelloop.Canvas.dbg2)
if pixelx then
love.graphics.setColor(.6, 0, .6, 1)
love.graphics.rectangle("fill", x, y, x2, y2)
@ -51,15 +54,15 @@ function draw_colision(x, y, x2, y2, pixelx, pixely)
love.graphics.rectangle("fill", x, y, x2, y2)
end
love.graphics.setCanvas()
color_pop()
utils.color_pop()
end
function physics.is_blocked_x(x, y, sizex)
x, y = math.floor(x), math.floor(y)
assert_num("physics.is_blocked_x", 3, x, y, sizex)
asserts.number("physics.is_blocked_x", 3, x, y, sizex)
for coord_x=x ,x + sizex -1 do
if gameloop.buffer.physics[coord_x * gameloop.world.x+y] and gameloop.buffer.physics[coord_x*gameloop.world.x+y][4] then
if levelloop.Canvas.physics[coord_x * levelloop.world.x+y] and levelloop.Canvas.physics[coord_x*levelloop.world.x+y][4] then
if PHYSICS_DEBUG then draw_colision(x, y, sizex, 1, coord_x, y) end
return true
end
@ -71,9 +74,9 @@ end
function physics.is_blocked_y(x, y, sizey)
x, y = math.floor(x), math.floor(y)
assert_num("physics.is_blocked_y", 3, x, y, sizey)
asserts.number("physics.is_blocked_y", 3, x, y, sizey)
for coord_y=y, y + sizey -1 do
if gameloop.buffer.physics[x*gameloop.world.x+coord_y] and gameloop.buffer.physics[x*gameloop.world.x+coord_y][4] then
if levelloop.Canvas.physics[x*levelloop.world.x+coord_y] and levelloop.Canvas.physics[x*levelloop.world.x+coord_y][4] then
if PHYSICS_DEBUG then draw_colision(x, y, 1, sizey, x, coord_y) end
return true
end
@ -85,16 +88,14 @@ end
function physics.keypressed( key, code, isrepeat)
if isrepeat then return end
if camera.target == 0 then return end
player = players[camera.target]
local player = participants[levelloop.clientID]
if key == "p" then
player.flying = not player.flying
levelloop.remoteCall("moveFlying", player.flying)
if player.flying then
player.speed.y = 0
gameloop.networkSend(unit("moveFlying", "true"))
else
gameloop.networkSend(unit("moveFlying", "false"))
end
end
end
@ -102,7 +103,7 @@ end
function physics.input(dt)
if camera.target == 0 then return end
player = players[camera.target]
local player = participants[levelloop.clientID]
local speedModified = dt * 20
if love.keyboard.isDown("rshift") or love.keyboard.isDown("lshift")
then
@ -124,7 +125,7 @@ function physics.input(dt)
local posx = player.x + player.avatar:getWidth()
local posy = player.y + player.avatar:getHeight()
if physics.is_blocked_x(posx - player.avatar:getWidth(), posy, player.avatar:getWidth()) or
player.y == gameloop.world.y - player.avatar:getHeight() then
player.y == levelloop.world.y - player.avatar:getHeight() then
player.speed.y = -25
end
end
@ -132,16 +133,16 @@ end
function physics.cap_world(width, height, newx, newy, new_sx, new_sy)
if newx > gameloop.world.x - width then
newx = gameloop.world.x - width
if newx > levelloop.world.x - width then
newx = levelloop.world.x - width
new_sx = 0
end
if newx < 0 then
newx = 0
new_sx = 0
end
if newy > gameloop.world.y - height then
newy = gameloop.world.y - height
if newy > levelloop.world.y - height then
newy = levelloop.world.y - height
new_sy = 0
end
if newy < 0 then
@ -151,129 +152,123 @@ function physics.cap_world(width, height, newx, newy, new_sx, new_sy)
return newx, newy, new_sx, new_sy
end
local SKIP = false
function physics.update(dt)
SKIP = false
for i, player in pairs(players) do
assert(player.x and player.y and player.speed.y and player.speed.x, "Player " .. i .. " has an invalid position!")
--friction
local new_sx = player.speed.x - player.speed.x / 16
local new_sy
if player.flying then
new_sy = player.speed.y - player.speed.y / 16
else
new_sy = player.speed.y - player.speed.y / 6
function physics.updatePlayer(dt, player, localplayer)
local new_sx, new_sy
if player.flying then
new_sy = player.speed.y - player.speed.y / 6
new_sx = player.speed.x - player.speed.x / 6
else
new_sx = player.speed.x - player.speed.x / 16
new_sy = player.speed.y - player.speed.y / 16
end
if not new_sx then new_sx = 0 end
if not new_sy then new_sy = 0 end
if (new_sx < 0.001 and new_sx > -0.001) and (new_sy < 0.001 and new_sy > -0.001) then return end
--falling
if not player.flying then
new_sy = new_sy +dt * 100
end
--new positions
local futureX = player.x + player.speed.x
local futureY = player.y + player.speed.y
assert(futureX and futureY)
-- cap boundaries
futureX, futureY, new_sx, new_sy = physics.cap_world(player.avatar:getWidth(), player.avatar:getHeight(), futureX, futureY, new_sx, new_sy)
local granularity = math.sqrt(math.abs(player.speed.x * player.speed.x)+ math.abs(player.speed.y * player.speed.y)) + 1
local stepsizeX = player.speed.x / granularity
local stepsizeY = player.speed.y / granularity
assert(granularity and stepsizeX and stepsizeY)
local currentX = player.x
local currentY = player.y
if granularity < 1 then granularity = 1 end
if player.flying then
player.x, player.y = futureX, futureY
player.speed.x = new_sx
player.speed.y = new_sy
if localplayer then
physics.send_update(player, dt)
end
return
end
local blocked_x, blocked_y = false, false
for iteration=1, granularity do
intermediateX = currentX + (stepsizeX * iteration)
intermediateY = currentY + (stepsizeY * iteration)
if not new_sx then new_sx = 0 end
if not new_sy then new_sy = 0 end
--falling
if not player.flying then
new_sy = new_sy +dt * 100
end
--new positions
local futureX = player.x + player.speed.x
local futureY = player.y + player.speed.y
assert(futureX and futureY)
-- cap boundaries
futureX, futureY, new_sx, new_sy = physics.cap_world(player.avatar:getWidth(), player.avatar:getHeight(), futureX, futureY, new_sx, new_sy)
local granularity = math.sqrt(math.abs(player.speed.x * player.speed.x)+ math.abs(player.speed.y * player.speed.y)) + 1
local stepsizeX = player.speed.x / granularity
local stepsizeY = player.speed.y / granularity
assert(granularity and stepsizeX and stepsizeY)
local currentX = player.x
local currentY = player.y
if not granularity or not stepsizeX and not stepSizeY then SKIP = true end
if granularity < 1 then granularity = 1 end
assert(currentX and currentY)
if player.flying then -- skip physics
player.x, player.y = futureX, futureY
player.speed.x = new_sx
player.speed.y = new_sy
if camera.target == i then -- FIXME: uses camera.target
physics.send_update(player, dt)
if intermediateY > futureY then -- UP
if physics.is_blocked_x(intermediateX, intermediateY, player.avatar:getWidth()) then
intermediateY = currentY
blocked_y = true
end
end
if intermediateY < futureY then --DOWN
local lowerY = intermediateY + player.avatar:getHeight() -1
if physics.is_blocked_x(intermediateX, lowerY, player.avatar:getWidth()) then
intermediateY = currentY
blocked_y = true
end
end
if intermediateX > futureX then -- ONLY LEFT
if physics.is_blocked_y(intermediateX, intermediateY, player.avatar:getHeight()) then
blocked_x = true
end
end
if intermediateX < futureX then -- ONLY RIGHT
local lowerX = intermediateX + player.avatar:getWidth() -1
if physics.is_blocked_y(lowerX, intermediateY, player.avatar:getHeight()) then
blocked_x = true
end
end
intermediateX, intermediateY, new_sx, new_sy = physics.cap_world(player.avatar:getWidth(), player.avatar:getHeight(), intermediateX, intermediateY, new_sx, new_sy)
if blocked_x and not blocked_y then -- X blocked
player.y = intermediateY
return
end
if blocked_y and not blocked_x then
player.x = intermediateX
return
end
do -- SCOPE
local blocked_x, blocked_y = false, false
if not SKIP then
for iteration=1, granularity do
intermediateX = currentX + (stepsizeX * iteration)
intermediateY = currentY + (stepsizeY * iteration)
if intermediateY > futureY then -- UP
if physics.is_blocked_x(intermediateX, intermediateY, player.avatar:getWidth()) then
intermediateY = currentY
blocked_y = true
end
end
if intermediateY < futureY then --DOWN
local lowerY = intermediateY + player.avatar:getHeight() -1
if physics.is_blocked_x(intermediateX, lowerY, player.avatar:getWidth()) then
intermediateY = currentY
blocked_y = true
end
end
if intermediateX > futureX then -- ONLY LEFT
if physics.is_blocked_y(intermediateX, intermediateY, player.avatar:getHeight()) then
blocked_x = true
end
end
if intermediateX < futureX then -- ONLY RIGHT
local lowerX = intermediateX + player.avatar:getWidth() -1
if physics.is_blocked_y(lowerX, intermediateY, player.avatar:getHeight()) then
blocked_x = true
end
end
intermediateX, intermediateY, new_sx, new_sy = physics.cap_world(player.avatar:getWidth(), player.avatar:getHeight(), intermediateX, intermediateY, new_sx, new_sy)
if blocked_x and not blocked_y then -- X blocked
--new_sy = 0
player.y = intermediateY
end
if blocked_y and not blocked_x then
--new_sx = 0
player.x = intermediateX
end
if blocked_x and blocked_y then
break
end
if not blocked_x and not blocked_y then
player.x = intermediateX
player.y = intermediateY
end
end
end -- SCOPE
if blocked_x and blocked_y then
return
end
-- cap low speed
if new_sx > -.1 and new_sx < .1 then
new_sx = 0
end
if new_sy > -.1 and new_sy < .1 then
new_sy = 0
end
player.speed.x = new_sx
player.speed.y = new_sy
if i == camera.target then -- FIXME: uses camera.target
physics.send_update(player, dt)
if not blocked_x and not blocked_y then
player.x = intermediateX
player.y = intermediateY
end
end
player.speed.x = new_sx
player.speed.y = new_sy
if localplayer then
physics.send_update(player, dt)
end
end
function physics.update(dt)
for i, player in pairs(participants) do
assert(player.x and player.y and player.speed.y and player.speed.x, "Player has an invalid position!")
physics.updatePlayer(dt, player, i == levelloop.clientID)
end
local player = participants[levelloop.clientID]
ui.updateSpeed(player.speed.x, player.speed.y, player.x, player.y)
end
@ -282,7 +277,7 @@ last.packet, last.x, last.y, last.speedx, last.speedy = 0, 0, 0, 0, 0
function physics.send_update(player, dt)
if last.packet > 0.03 then
if last.x ~= player.x or last.y ~= player.y then
gameloop.networkSend(unit("moveUpdate", player.x, player.y, player.speed.x, player.speed.y))
levelloop.remoteCall("moveUpdate", player.x, player.y, player.speed.x, player.speed.y)
last.packet = 0
last.x, last.y = player.x, player.y
last.speedx, last.speedy = player.speedx, player.speedy

View File

@ -1,11 +1,14 @@
local playerfuncs = {}
playerfuncs.newPlayer = function(arg)
assert(arg.avatar)
assert(arg.name)
local playerTable = {}
playerTable.avatar = love.graphics.newImage("textures/player/andysphinx/personb.png")
playerTable.avatar = arg.avatar
playerTable.name = arg.name
playerTable.speed = {}
playerTable.speed.x = 0
playerTable.speed.y = 0
playerTable.x = 16
playerTable.x = 16
playerTable.y = 16
playerTable.flying = false
return playerTable

View File

@ -1,40 +1,83 @@
ui = {}
ui.textureTree = nil
if love.filesystem.getInfo("fonts/font.ttf") then
ui.smallFont = love.graphics.newFont("fonts/font.ttf", 14)
ui.font = love.graphics.newFont("fonts/font.ttf", 20)
ui.bigFont = love.graphics.newFont("fonts/font.ttf", 45)
else
ui.smallFont = love.graphics.getFont(14)
ui.font = love.graphics.getFont(20)
ui.bigFont = love.graphics.getFont(45)
end
local layout = require("lua.layout")
local utils = require("shared.utils")
local fonts = require("shared.fonts")
ui.speedText = {
love.graphics.newText(fonts.normalFont, "0000"),
love.graphics.newText(fonts.normalFont, "0000"),
love.graphics.newText(fonts.normalFont, "0000"),
love.graphics.newText(fonts.normalFont, "0000")
}
ui.sidebarScale = 1
ui.sidebarWidth = 13 * 16
function ui.update_scale(p_scale)
scale = p_scale
end
function ui.updateSpeed(xspeed, yspeed, xpos, ypos)
ui.speedText = {
love.graphics.newText(fonts.normalFont, "XSpeed: " .. xspeed),
love.graphics.newText(fonts.normalFont, "YSpeed: " .. yspeed),
love.graphics.newText(fonts.normalFont, "X: " .. xpos),
love.graphics.newText(fonts.normalFont, "X: " .. ypos)
}
end
ui.helptext = [[[P] toggle fly
[A/D] Move
[Hold Shift] Move faster
{A/D} Move
{Shift} Move faster
[Space] Jump
[A/S/DW] Move (flying)
{A/S/DW} Move (flying)
[C] Clear canvas
[F] Fill foreground
[G] Fill background
[Mouse1] Draw
[Mouse2] Delete
[Hold ctrl] Draw on Background
[+/-/PGUP/PGDWN] Zoom canvas
[Cursor keys] Move camera
{Mouse1} Draw
{Mouse2} Delete
{ctrl} Background draw
[PGUP/PGDWN] Zoom canvas
{Cursor keys} Move camera
]]
local menuVisible = false
local textEnabled = false
local textEntry = ""
function ui.keyreleased(key, _)
if key == "escape" then
menuVisible = not menuVisible
if menuVisible then
love.visible = function(visible)
if visible then
love.update = function(dt)
levelloop.networkSync(dt)
physics.update(dt)
camera.update(dt)
love.timer.sleep((1/60) -dt)
end
else
love.update = function(dt)
levelloop.networkSync(dt)
physics.update(dt)
end
end
end
ui.drawMenu(window.x, window.y)
else
love.visible = function(visible)
if visible then
love.update = levelloop.normalVisible
else
love.update = levelloop.reducedVisible
end
end
ui.draw(window.x, window.y)
end
love.visible(true)
return
end
if key == "return" and not love.keyboard.isDown("lalt") then
textEnabled = true
love.keyboard.setTextInput(true)
@ -44,14 +87,14 @@ function ui.keyreleased(key, _)
love.visible = function(visible)
if visible then
love.update = function(dt)
gameloop.networkSync(dt)
levelloop.networkSync(dt)
drawing.input(dt)
physics.update(dt)
camera.update(dt)
end
else
love.update = function(dt)
gameloop.networkSync(dt)
levelloop.networkSync(dt)
physics.update(dt)
end
end
@ -61,14 +104,14 @@ function ui.keyreleased(key, _)
if key == "return" and not love.keyboard.isDown("lalt") then
textEnabled = false
if textEntry ~= "" then
gameloop.networkSend(unit("chatMessage", textEntry))
levelloop.remoteCall("chatMessage", textEntry)
textEntry = ""
end
ui.draw(window.x, window.y)
love.keyboard.setTextInput(false)
love.keyreleased = gameloop.keyreleased
love.keypressed = gameloop.keypressed
love.update = gameloop.normalVisible
love.keyreleased = levelloop.keyreleased
love.keypressed = levelloop.keypressed
love.update = levelloop.normalVisible
elseif key == "backspace" then
textEntry = string.sub(textEntry, 0, #textEntry -1)
ui.draw(window.x, window.y)
@ -76,8 +119,8 @@ function ui.keyreleased(key, _)
end
love.keypressed = function(key, _) return end
else
love.keyreleased = gameloop.keyreleased
love.keypressed = gameloop.keypressed
love.keyreleased = levelloop.keyreleased
love.keypressed = levelloop.keypressed
end
end
@ -97,11 +140,11 @@ function ui.chatEntry()
fill = "fill",
color = { r = .1, g = .1, b = .1, .7 }
},
{name = "frame",
{name = "label",
text = passedEntry
{name = "frame",
{name = "label",
text = passedEntry
}
}
}
}
return chatEntry
end
@ -125,22 +168,22 @@ function ui.addChatEntry(user, message)
end
local entry = {name = "rotate",
rotation = 0, -- OKAY DIEGO >:(
{name = "copySize",
{name = "rect",
fill = "fill",
color = { r = .1, g = .1, b = .1, a = .6 }
{name = "copySize",
{name = "rect",
fill = "fill",
color = { r = .1, g = .1, b = .1, a = .6 }
},
{name = "vertical",
{name = "label",
text = chatText,
font = fonts.smallFont
},
{name = "vertical",
{name = "label",
text = chatText,
font = ui.smallFont
},
{name = "spacer",
height = 2
}
{name = "spacer",
height = 2
}
}
}
}
table.insert(chatentries, entry)
ui.draw(window.x, window.y)
end
@ -153,18 +196,16 @@ function ui.loadTextures(directory, container)
for i, file in pairs(entries) do
local path = directory .."/".. file
local entry = love.filesystem.getInfo(path)
if entry.type == "directory" then
if entry.type == "directory" and file ~= ".git" then
container[file] = {}
container[file] = ui.loadTextures(path, container[file])
elseif entry.type == "file" or entry.type == "symlink" then
if file ~= "license.txt" then
local imageData = love.image.newImageData(path)
local status, imageData = pcall(love.image.newImageData, path);
if status then
local hash = love.data.hash("sha512", imageData:getString())
gameloop.textures[hash] = {data = imageData}
levelloop.textures[hash] = {data = imageData}
container[file] = hash
end
elseif entry.type == "other" then
error("Can't figure out what " .. path .. " is!")
end
end
return container
@ -172,31 +213,29 @@ end
function textureEntry(hash)
local texture = gameloop.textures[hash]
local texture = levelloop.textures[hash]
assert(texture, "No texture found for textureentry?")
assert(texture.data, "No imagedata found for textureentry?")
if not texture.image then
texture.image = love.graphics.newImage(texture.data)
end
local width = texture.image:getWidth()
local scale = 1
if width <= 16 then scale = 2 end
if width <= 8 then scale = 4 end
if drawing and drawing.cursor and hash == drawing.cursor then
return{name = "overlayRect",
local height = texture.image:getHeight()
if drawing and drawing.cursorHash and hash == drawing.cursorHash then
return {name = "overlayRect",
fill = "line",
{name = "drawHash",
hash = hash,
scale = scale
scale = ui.sidebarScale
}
}
else
return{name = "cursorSelect",
return {name = "cursorSelect", uiSelect = "GameUI",
identifier = hash,
kind = "picker",
{name = "drawHash",
hash = hash,
scale = scale
scale = ui.sidebarScale
}
}
end
@ -204,52 +243,54 @@ end
function textureRun(entryTable, width)
local textures = gameloop.textures
local superEntry = {name = "vertical"}
local entries = {name = "horizontal"}
local textures = levelloop.textures
local entries = {name = "naiveGrid", width = width - 20}
local entryTableSorted = {}
-- Sort the entries alphapetically (or well, probably more... "byte compared") :)
-- This implies that in <author>/<packname>/<fileName> the fileName controls where
-- the texture lands in the block drawer, smaller lands further left.
for iter, entry in pairs(entryTable) do
table.insert(entryTableSorted, {name = iter, entry = entry})
end
table.sort(entryTableSorted, function(k1, k2) return k1.name < k2.name end)
for iter, entry in pairs(entryTableSorted) do
if #entries > 2 then
local NextX, nextY = layout.handle(entries, 0, 0, true)
if not textures[entry.entry] then textures[entry.entry] = {} end
if not textures[entry.entry].image then
textures[entry.entry].image = love.graphics.newImage(textures[entry.entry].data)
end
if textures[entry.entry].image:getWidth() + NextX < width then
table.insert(entries, textureEntry(entry.entry))
else
table.insert(superEntry, entries)
entries = {name = "horizontal"}
end
else
table.insert(entries, textureEntry(entry.entry))
if not textures[entry.entry].image then
textures[entry.entry].image = love.graphics.newImage(textures[entry.entry].data)
end
table.insert(entries, textureEntry(entry.entry))
end
if #entries > 1 then table.insert(superEntry, entries) end
if #superEntry > 1 then
return superEntry
else
return entries
end
return entries
end
function ui.blockDrawer(textureTree, w, h, container)
if not textureTree then return {name = "label", text = "Loading textures..."} end
local pointSize = 8
if ui.sidebarScale == 2 then pointSize = 16 end
if not container then container = {name="vertical",
{name = "colorPicker", kind = "colorpicker", granularity = 0.25}}
{name = "colorPicker", kind = "colorpicker",
granularity = 0.25, pointSize = pointSize},
{name = "spacer", height = 5 * ui.sidebarScale},
{name = "bwColorPicker", kind = "bwColorPicker",
pointSize = pointSize}}
end
local textureTreeSorted = {}
for author, tree in pairs(textureTree) do
for pack, subTree in pairs(tree) do
table.insert(textureTreeSorted, {author = author, tree = tree})
end
table.sort(textureTreeSorted, function(k1, k2) return k1.author < k2.author end)
for author, tree in pairs(textureTreeSorted) do
for pack, subTree in pairs(tree.tree) do
container.name = "vertical"
table.insert(container, {name = "label", text = pack})
table.insert(container,
{name = "color",
color = drawing.color,
color = drawing.color or {1, 1, 1},
--[[ HACK! remove the 1, 1, 1.
drawing.color should be set!
]]--
textureRun(subTree, w)
})
end
@ -258,107 +299,111 @@ function ui.blockDrawer(textureTree, w, h, container)
end
function ui.helpDrawer(w, h)
return {name = "label",
text = ui.helptext
}
end
function ui.testDrawer(w, h)
local drawer = {name = "vertical"}
return drawer
end
local selectedTab = "help"
function ui.tabDrawer(textureTree, w, h)
local labels
local helpTab = {name = "label",
text = ui.helptext
}
local hWidth, hHeight = layout.handle(helpTab, 0, 0, true)
local blockTab = ui.blockDrawer(textureTree, w, h)
local bWidth, bHeight = layout.handle(blockTab, 0, 0, true)
labels = {name = "horizontal",
{name = "cursorSelect",
identifier = "help",
kind = "tab",
{name = "label",
text = " [Help] "
}
},
{name = "cursorSelect",
identifier = "blocks",
kind = "tab",
{name = "label",
text = " [Blocks] "
}
function ui.menuDrawer(w, h)
return {name = "horizontal",
{name = "vertical",
{name = "button",
identifier = "save",
uiSelect = "Menu",
buttonColor = "greenTranslucent",
text = "Save world"
},
{name = "cursorSelect",
{name = "spacer",
height = 10,
},
{name = "button",
identifier = "disconnect",
kind = "button",
{name = "label",
text = " Disconnect. "
}
}
}
if selectedTab == "blocks" then
tab = blockTab
elseif selectedTab == "help" then
tab = helpTab
end
local drawer = {
name = "vertical",
{name = "copySize",
{name = "rect",
fill = "fill",
color = { r = .2, g = 0, b = .2 }
uiSelect = "Menu",
buttonColor = "pinkTranslucent",
text = "Disconnect"
},
{name = "horizontal",
labels,
{name = "spacer",
height = 40,
},
{name = "spacer",
height = 25,
},
{name = "button",
identifier = "sideBarScaleTwo",
uiSelect = "Menu",
buttonColor = "grayTranslucent",
text = "Ui scale x2"
},
{name = "spacer",
height = 4,
},
{name = "button",
identifier = "sideBarScaleOne",
uiSelect = "Menu",
buttonColor = "grayTranslucent",
text = "Ui scale x1"
},
},
{name = "copySize",
{name = "rect",
fill = "fill",
color = { r = .1, g = .1, b = .1 }
},
tab
{name = "spacer",
width = 80,
},
{name = "label",
text = ui.helptext,
font = fonts.normallFont
}
}
return drawer, math.max(hWidth, bWidth)
end
function ui.draw(w, h)
window.x, window.y = w, h
color_push()
love.graphics.setCanvas(ui.buffer)
uiState = {}
layout.uiState.GameUI = {}
love.graphics.clear( )
local sidebar, width = ui.tabDrawer(ui.textureTree, w, h)
layout.handle(sidebar, window.x -width, 0)
ui.space = width
if textEnabled then
local chatLayout = ui.chatEntry()
local sizex, sizey = layout.handle(chatLayout, 0, 0, true)
layout.handle(chatLayout, 0, window.y - sizey)
end
local blockDrawer = ui.blockDrawer(ui.textureTree, w, h)
blockMaxX, blockMaxY = layout.handle(blockDrawer, 0, 0, true)
local blockDrawerAdjusted = ui.blockDrawer(ui.textureTree, blockMaxX, blockMaxY)
layout.handle(blockDrawerAdjusted, window.x - blockMaxX, 0)
layout.handle(ui.chatlog(), 25, 90)
color_pop()
love.graphics.setCanvas()
end
function ui.drawMenu(w, h)
window.x, window.y = w, h
layout.uiState.Menu = {}
love.graphics.setCanvas(ui.buffer)
love.graphics.clear()
local menuDrawer = ui.menuDrawer(w, h)
menux, menuy = layout.handle(menuDrawer, 0, 0, true)
layout.handle(menuDrawer, (window.x - menux) /2, (window.y - menuy)/2)
love.graphics.setCanvas()
end
function ui.mousepressed(mousex, mousey)
local textures = gameloop.textures
for i, v in ipairs(uiState) do
if mousex >= v.x and mousex <= v.x2 then
if mousey >= v.y and mousey <= v.y2 then
local textures = levelloop.textures
if menuVisible then
for i, v in ipairs(layout.uiState.Menu) do
if mousex >= v.x and mousex <= v.x2 and --hit testing
mousey >= v.y and mousey <= v.y2 then
if v.kind == "button" then
if v.identifier == "save" then
levelloop.networkSend("saveWorld")
ui.keyreleased("escape")
elseif v.identifier == "disconnect" then
levelloop.remoteCall("playerLeave")
menu.init()
elseif v.identifier == "sideBarScaleTwo" then
ui.sidebarScale = 2
ui.draw(window.x, window.y)
ui.keyreleased("escape")
elseif v.identifier == "sideBarScaleOne" then
ui.sidebarScale = 1
ui.draw(window.x, window.y)
ui.keyreleased("escape")
end
end
return -- Return after the first hit...
end
end
else
for i, v in ipairs(layout.uiState.GameUI) do
if mousex >= v.x and mousex <= v.x2 and
mousey >= v.y and mousey <= v.y2 then
if v.kind == "picker" then
assert(v.identifier, "No identifier in picker!")
drawing.cursorHash = v.identifier
@ -371,19 +416,23 @@ function ui.mousepressed(mousex, mousey)
ui.draw(window.x, window.y)
return
elseif v.kind == "colorpicker" then
local scale = 1.0 / granularity
local red = math.floor((mousey - v.y) / v.pointsize) / scale
local green = math.floor((mousex - v.x) / v.pointsize / (scale + 1)) / scale
local blue = (math.floor((mousex - v.x) / v.pointsize) % (scale + 1)) / scale
print("Picker hit :D")
local scale = ui.sidebarScale / granularity
local red = math.floor((mousey - v.y) / v.pointSize) / scale
local green = math.floor((mousex - v.x) / v.pointSize / (scale + 1)) / scale
local blue = (math.floor((mousex - v.x) / v.pointSize) % (scale + 1)) / scale
print("I think the color is " .. red .. ":" .. green .. ":" .. blue)
drawing.color = {red, green, blue, 1}
ui.draw(window.x, window.y)
return
elseif v.kind == "button" then
if v.identifier == "disconnect" then
gameloop.networkSend(unit("playerLeave"))
menu.init()
end
elseif v.kind == "bwColorPicker" then
local scale = 1.0 / granularity
local saturation = math.floor((mousex - v.x) / v.pointSize) / scale
drawing.color = {saturation, saturation, saturation, 1}
ui.draw(window.x, window.y)
return
end
return
end
end
end

View File

@ -1,60 +0,0 @@
local colorstack = {}
function color_push()
local depth = #colorstack +1
colorstack[depth] = {}
colorstack[depth].r, colorstack[depth].g, colorstack[depth].b,
colorstack[depth].a = love.graphics.getColor()
end
function color_pop()
love.graphics.setColor(colorstack[#colorstack].r, colorstack[#colorstack].g,
colorstack[#colorstack].b, colorstack[#colorstack].a)
table.remove(colorstack, #colorstack)
end
function assert_num(name, count, ...)
local args = { ... }
for i = 1, count do
assert(args[i] ~= nil, "Argument " .. i .. " to " .. name .. " may not be nil and must be an int!")
assert(type(args[i]) == "number", "Argument " .. i .. " to " .. name .. " must be an int!")
assert(args[i] == args[i], "Argument " .. i .. " to " .. name .. " must not be NaN!")
end
end
function assert_num_or_nil(name, count, ...)
local args = { ... }
for i = 1, count do
if (args[i] ~= nil) then
assert(type(args[i]) == "number", "Argument " .. i .. " to " .. name .. " can only be of type int!")
assert(args[i] == args[i], "Argument " .. i .. " to " .. name .. " may not be NaN!")
end
end
end
local recordSeperator = string.char(30)
local unitSeperator = string.char(31)
function unit(...)
local args = { ... }
local string = args[1]
for i=2, #args do
string = string .. unitSeperator .. args[i]
end
return string
end
function record(...)
local args = { ... }
local string = args[1]
for i=2, #args do
string = string .. recordSeperator .. args[i]
end
return string
end

View File

@ -2,11 +2,66 @@ function dofile(fileString)
local chunk = love.filesystem.load(fileString)
chunk()
end
dofile "lua/gameloop.lua"
dofile "lua/utils.lua"
dofile "lua/layout.lua"
dofile "lua/init.lua"
dofile "lua/ui.lua"
dofile "lua/camera.lua"
dofile "lua/physics.lua"
dofile "lua/drawing.lua"
function love.load(args)
if args[1] == "--nwTest" then
local format = require("shared.networkFormat")
format.testEncoder()
elseif args[1] == "--server" then
if not args[2]then
print("Missing server adress!")
love.event.quit()
return
end
local host = args[2]
local port = "11150"
if args[3] then port = args[3] end
local serverloop = require("server.main")
print("Starting server! [" .. host .. "] <" .. port .. ">")
serverloop.init({host, port})
elseif args[1] == "--client" then
if not args[2]then
print("Missing server adress!")
love.event.quit()
return
end
dofile "lua/gameloop.lua"
dofile "lua/layout.lua"
dofile "lua/ui.lua"
dofile "lua/camera.lua"
dofile "lua/physics.lua"
dofile "lua/drawing.lua"
setmetatable(_G, {
__index = function(self, idx)
if rawget(self, idx) == nil then
error("attempted to read implicit global:" .. idx)
end
end
})
PHYSICS_DEBUG = false
NETWORK_DEBUG = true
local host = args[2]
local port = "11150"
if args[3] then port = args[3] end
local avatar = love.image.newImageData("textures/player/nephele/fallback.png")
local avatarHash = love.data.hash("sha512", avatar:getString())
levelloop.init({ adress = host, port = port }, "Fallback player", avatarHash)
elseif args[1] == "--help" then
print(" --server [host] <port>")
print(" Start a local server")
print("")
print(" --help")
print(" print this help")
return
else
constants = require("server.constants")
local menu = require("lua.menuloop")
menu.init()
end
end

2
makeCleintLoveFile.sh Executable file
View File

@ -0,0 +1,2 @@
#!sh
zip -r Poppy.love main.lua conf.lua lua/ shared/ ressources/Poppy.png textures/menu/AddIcon.png textures/menu/serverPlay.png server/ textures/blocks/ textures/menu/serverPlay.png textures/player/

BIN
ressources/Icons/AddIcon Normal file

Binary file not shown.

BIN
ressources/Icons/AddIcon2 Normal file

Binary file not shown.

BIN
ressources/Icons/PlayButton Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
ressources/Textures/mossBig Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
ressources/characters/liz Normal file

Binary file not shown.

6
run
View File

@ -1,11 +1,13 @@
#!/bin/sh
#adjust the next line... not sure how to figure this out from a shellscript, sorry :)
cd ~/proj/poppy
# This mechanism is fairly unreliable, but it is the only "okay" way currently :/
# realpath is also not part of any POSIX standard
cd $(dirname $(realpath "$0"))
if test $? -eq 1; then
alert --stop "cant figure out where this script is" "Ill adjust it!"
exit
fi
uname=`uname -o`
if test "$uname" = "Haiku"
then

1
server

@ -1 +0,0 @@
Subproject commit 3447cbeba95455783a3fa28a26935e8960ff1a42

5
server/License Normal file
View File

@ -0,0 +1,5 @@
Copyright (C) 2022 by Pascal R. G. Abresch <nep@packageloss.eu>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

12
server/commands/chat.lua Normal file
View File

@ -0,0 +1,12 @@
local utils = require("shared.utils")
local constants = require("server.constants")
local rpc = require("server.rpc")
return function(commands)
function commands.chatMessage(clientID, message)
assert(participants[clientID])
print("Chat " .. participants[clientID].name .. ": " .. message)
broadcast(clientID, "chatMessage", message)
end
end

View File

@ -0,0 +1,39 @@
local constants = require("server.constants")
local util = require("shared.utils")
local rpc = require("server.rpc")
require("server.compat")
return function(commands)
function commands.moveUpdate(clientID, x, y, speedx, speedy)
if not participants[clientID] then rpc.print("moveUpdate: invalid clientid [" .. clientID .. "]") return end
participants[clientID].x = x
participants[clientID].y = y
participants[clientID].speedx = speedx
participants[clientID].speedy = speedy
boradcast(clientID, "moveUpdate", x, y, speedx, speedy)
end
function commands.moveFlying(clientID, flying)
if not participants[clientID] then rpc.print("moveFlying: invalid clientid [" .. clientID .. "]") return end
broadcast(clientID, "moveFlying", flying)
end
function commands.deletePhysics(clientID, x, y, width, height)
if not WORLD_LOCK then
if not participants[clientID] then rpc.print("deletePhysics: invalid clientid [" .. clientID .. "]") return end
broadcast(clientID, "deletePhysics", x, y, width, height)
end
end
function commands.drawPhysics(clientID, x, y, width, height)
print("got physX")
if not WORLD_LOCK then
if not participants[clientID] then rpc.print("drawPhysics: invalid clientid [" .. clientID .. "]") return end
broadcast(clientID, "drawPhysics", x, y, width, height)
print("got physX broadcasting")
end
end
end

View File

@ -0,0 +1,9 @@
local util = require("shared.utils")
return function(commands)
function commands.playerLeave(clientID)
print("Old client disconnected. id: [" .. clientID .. "]")
participants[clientID] = nil
broadcast(clientID, "playerLeave")
end
end

View File

@ -0,0 +1,47 @@
local util = require("shared.utils")
local constants = require("server.constants")
local rpc = require("server.rpc")
local os = require("os")
return function(commands)
function commands.listSaves(clientID, args)
-- local requestID = args
-- files = love.filesystem.getDirectoryItems( dir )
-- ChunkedReply("SaveData", files)
end
function commands.saveWorld(clientID, savename)
local handle = io.popen("date -u --rfc-3339=seconds")
local datetime = handle:read("*a")
handle:close()
datetime = datetime:sub(1, -8)
datetime = datetime:gsub("%s+", "_")
datetime = datetime:gsub(":+", "")
if savename then
savename = args .. "/" .. datetime
else
savename = datetime
end
local directory = "Saves/" .. savename
local result = love.filesystem.createDirectory(directory)
-- TODO check this result...
if result then
levelloop.Canvas.fg:newImageData():encode("png", directory .. "/fg.png")
levelloop.Canvas.bg:newImageData():encode("png", directory .. "/bg.png")
print("saved world. " .. savename)
broadcast(0, "saveWorld", true, savename)
return
else
print("Failed to save world!")
remoteCallTarget(clientID, 0, "saveWorld", false)
end
end
function commands.loadWorld(clientID, savename)
end
function commands.saveWorldAndLoad(clientID, args)
end
end

4
server/compat.lua Normal file
View File

@ -0,0 +1,4 @@
table.unpack = table.unpack or unpack
string.pack = string.pack or love.data.pack
string.unpack = string.unpack or love.data.unpack
string.packsize = string.packsize or love.data.getPackedSize

21
server/conf.lua Normal file
View File

@ -0,0 +1,21 @@
love.conf = function(conf)
conf.version = "11.0"
conf.modules.font = false
conf.modules.mouse = false
--conf.modules.image
--conf.modules.system
conf.modules.audio = false
conf.modules.touch = false
conf.modules.joystick = false
conf.modules.keyboard = false
--conf.modules.timer
--conf.modules.graphics
conf.modules.window = false
--conf.modules.math
--conf.modules.data
--conf.modules.event
conf.modules.sound = false
--conf.modules.thread
conf.modules.video = false
conf.modules.physics = false
end

8
server/constants.lua Normal file
View File

@ -0,0 +1,8 @@
return {
protocolVersion = "PYY\x03",
world = {
x = 768,
y = 768,
id = "NYAAA"
}
}

127
server/main.lua Normal file
View File

@ -0,0 +1,127 @@
local socket = require("socket")
local fonts = require("shared.fonts")
local constants = require("server.constants")
local rpc = require("server.rpc")
local util = require("shared.utils")
protocolVersion = constants.protocolVersion
local commands = {}
require("server.commands.players")(commands)
require("server.commands.physics")(commands)
require("server.commands.chat")(commands)
require("server.commands.saving")(commands)
require("shared.commands")(commands)
local format = require("shared.networkFormat")
local errorHandler = require("shared.error")
local unpriviligedCommands = {}
require("server.unpriviliged_commands.init")(unpriviligedCommands)
server = assert(socket.udp())
local ip, port = nil, nil
participants = {}
levelloop = {}
levelloop.textures = {}
server:settimeout(0)
if not server or not socket then error("failed to listen on socket") end
local function remoteCallTarget(target, clientID, name, ...)
local request = "PPY\x03" .. format.encodeFunction(name, clientID, ...)
server:sendto(request, target.ip, target.port)
end
function broadcast(clientID, name, ...)
for i, player in pairs(participants) do
if i ~= clientID then
remoteCallTarget(player, clientID, name, ...)
end
end
end
function loadTextures(directory, container)
if not container then container = {} end
local entries = love.filesystem.getDirectoryItems(directory)
if #entries == 0 then return container end
for i, file in pairs(entries) do
local path = directory .."/".. file
local entry = love.filesystem.getInfo(path)
if entry.type == "directory" and file ~= ".git" then
container[file] = {}
container[file] = loadTextures(path, container[file])
elseif entry.type == "file" or entry.type == "symlink" then
local status, imageData = pcall(love.image.newImageData, path);
if status then
local hash = love.data.hash("sha512", imageData:getString())
levelloop.textures[hash] = {data = imageData}
container[file] = hash
end
end
end
return container
end
love.window.minimize( )
love.window.setTitle( "Poppy server" )
function mainloop(dt)
local data, ip, port
data, ip, port = server:receivefrom()
if not data then
if ip == "timeout" then return else
print(ip)
end
end
local protocolVersion, index = string.unpack("<c4", data, index)
if protocolVersion ~= "PPY\x03" then
print("Dropping packet with invalid protocol " .. protocolVersion)
else
local DecoderType, index = string.unpack("<I1", data, index)
if (DecoderType == 0x81) then
name, clientID, args = format.decodeFunction(data, index)
if clientID == 0 then
unpriviligedCommands[name](ip, port, table.unpack(args))
else
commands[name](clientID, table.unpack(args))
end
end
end
end
function levelloop.init(host)
love.errorhandler = errorHandler.redscreen
status, value = server:setsockname(host[1], host[2])
if not status then
error("Server could not be started :( " .. value)
end
local ip, port =server:getsockname()
print("Server startup. listening on " .. ip .. " [".. port .."]")
loadTextures("textures/blocks", levelloop.textures)
levelloop.world = constants.world
levelloop.Canvas = {}
levelloop.Canvas.fg = love.graphics.newCanvas(levelloop.world.x, levelloop.world.y)
levelloop.Canvas.bg = love.graphics.newCanvas(levelloop.world.x, levelloop.world.y)
print("Startup!")
SERVER = true
CLIENT = false
love.update = mainloop
love.draw = function()
love.graphics.clear(.2, 0, .2)
love.graphics.print("Poppy server is running.", fonts.normalFont, 20, 20)
love.graphics.print("listening on " .. ip .. " [".. port .."]" , fonts.normalFont, 20, 50)
return end
love.keyreleased = function() return end
love.keypressed = function() return end
love.mousepressed = function() return end
end
return levelloop

13
server/networkBuffer.lua Normal file
View File

@ -0,0 +1,13 @@
local nwBuffer = {}
nwBuffer.replyBuffers = {}
-- This implementation requires the client to supply a requestID, so that it later can
-- tell if the transfer suceeded or not, and re-request data out of a chunked reply.
nwbuffer.commands.requestChunk(clientID, args)
local chunkNumber = args
if replybuffers[clientID .. "_" .. args] then
reply(clientID, "chunk", args, chunk[clientID .. "_" .. args])
end
end
return nwBuffer

View File

View File

@ -0,0 +1,40 @@
local utils = require("shared.utils")
local constants = require("server.constants")
local format = require("shared.networkFormat")
local rpc = require("server.rpc")
local clientCount = 0
local function remoteCallTo(ip, port, name, ...)
local request = "PPY\x03" .. format.encodeFunction(name, 0, ...)
server:sendto(request, ip, port)
print("send request to " .. ip .. " " .. port .. "[" .. request .. "]")
end
return function(commands)
function commands.init(ip, port, nickname, avatar)
clientCount = clientCount +1
participants[clientCount] = { ip = ip, port = port, name = nickname, avatar = avatar }
print("New client connected. Granting id: [" .. clientCount .. "] IP: " .. ip .. " Port:" .. port)
for i, client in pairs(participants) do
if i ~= clientCount then
-- to Existing client
remoteCallTo(client.ip, client.port, "playerJoin", clientCount, nickname, avatar)
-- to New client
remoteCallTo(ip, port, "playerJoin", i, client.name, client.avatar)
end
end
WORLD_LOCK = true
-- The world is now locked
-- Client response
remoteCallTo(ip, port, "init", clientCount, nickname, avatar, constants.world.id,
constants.world.x, constants.world.y)
-- Send World
local fg = love.data.compress("string", "lz4", levelloop.Canvas.fg:newImageData())
local bg = love.data.compress("string", "lz4", levelloop.Canvas.bg:newImageData())
--network.prepareChunk(fg
WORLD_LOCK = false
end
end

23
shared/asserts.lua Normal file
View File

@ -0,0 +1,23 @@
local asserts = {}
function asserts.number(name, count, ...)
local args = { ... }
for i = 1, count do
assert(args[i] ~= nil, "Argument " .. i .. " to " .. name .. " may not be nil and must be an int!")
assert(type(args[i]) == "number", "Argument " .. i .. " to " .. name .. " must be an int!")
assert(args[i] == args[i], "Argument " .. i .. " to " .. name .. " must not be NaN!")
end
end
function asserts.numberOrNil(name, count, ...)
local args = { ... }
for i = 1, count do
if (args[i] ~= nil) then
assert(type(args[i]) == "number", "Argument " .. i .. " to " .. name .. " can only be of type int!")
assert(args[i] == args[i], "Argument " .. i .. " to " .. name .. " may not be NaN!")
end
end
end
return asserts

31
shared/catalog.lua Normal file
View File

@ -0,0 +1,31 @@
local catalog = {}
catalog.firstNames = { "Herbert", "Thorben", "Martin", "Heinrich", "Dietrich",
"Markus", "Florian", "Helmut", "Willhelm", "Fritz", "Gustav", "Konrad",
"Berta", "Charlotte", "Hildegard", "Lieselotte", "Gudrun", "Giesela",
"Margarete", "Antonia", "Friederike", "Clotilde", "Marlies", "Hedwig",
"Agathe" }
catalog.surNames = { "Müller", "Schmidt", "Meier", "Bauer", "Werner",
"Schumacher", "Bergmann", "Eisenhauer", "Heisenberg" }
catalog.pastelColors = {
{234/255, 155/255, 254/255, .6}, --pinkish
{156/255, 154/255, 251/255, .6}, --blueish
{242/255, 228/255, 148/255, .6}, --orange yellowish
{255/255, 156/255, 182/255, .6}, --magenta/orange
{145/255, 237/255, 216/255, .6} --turqoise
}
catalog.buttonColors = {
blue = { {50/255, 65/255, 98/255}, {125/255, 151/255, 208/255}, {1, 1, 1}},
pink = { {208/255, 37/255, 134/255}, {241/255, 109/255, 184/255}, {1, 1, 1}},
green = {{30/255,183/255,127/255},{109/255,241/255,193/255}, {1,1,1}},
blueTranslucent = { {50/255, 65/255, 98/255, .7}, {125/255, 151/255, 208/255, .7}, {1, 1, 1, .9}},
pinkTranslucent = { {208/255, 37/255, 134/255, .7}, {241/255, 109/255, 184/255, .7}, {1, 1, 1, .9}},
greenTranslucent = {{30/255,183/255,127/255, .7},{109/255,241/255,193/255, .7}, {1,1,1, .9}},
grayTranslucent = {{.1,.1,.1, .7}, {1,1,1, .7}, {1,1,1, .9}}
}
catalog.default = {
avatar = "textures/player/noriko/liz.png",
}
return catalog

108
shared/commands.lua Normal file
View File

@ -0,0 +1,108 @@
local player = require("lua.player")
local utils = require("shared.utils")
return function(commands)
commands.moveUpdate = function(clientID, x, y, speedx, speedy)
if SERVER then
broadcast(clientID, "moveUpdate", x, y, speedx, speedy)
end
if not participants[clientID] then return end
participants[clientID].x = x
participants[clientID].y = y
if not participants[clientID].speed then participants[clientID].speed = {} end
participants[clientID].speed.x = speedx
participants[clientID].speed.y = speedy
end
commands.moveFlying = function(clientID, flying)
if SERVER then
broadcast(clientID, "moveFlying", flying)
end
if not participants[clientID] then return end
participants[clientID].flying = flying
end
commands.drawTexture = function(clientID, x, y, layer, hash, color)
if SERVER then
color.nwType = "color"
broadcast(clientID, "drawTexture", x, y, layer, hash, color)
end
if not participants[clientID] then return end
if not levelloop.textures[hash] then error("Failed to find hash!") return end
if not levelloop.textures[hash].image then
levelloop.textures[hash].image = love.graphics.newImage(levelloop.textures[hash].data)
end
love.graphics.setCanvas(levelloop.Canvas[layer])
love.graphics.setBlendMode("alpha")
utils.color_push()
love.graphics.setColor(color)
love.graphics.draw(levelloop.textures[hash].image, x, y)
utils.color_pop()
love.graphics.setCanvas()
end
commands.deleteTexture = function(clientID, x, y, width, height, layer)
if SERVER then
broadcast(clientID, "deleteTexture", x, y, width, height, layer)
end
if not participants[clientID] then return end
love.graphics.setCanvas(levelloop.Canvas[layer])
love.graphics.setBlendMode("replace")
utils.color_push()
love.graphics.setColor(0,0,0,0)
love.graphics.rectangle("fill", x, y, width, height)
utils.color_pop()
love.graphics.setBlendMode("alpha")
love.graphics.setCanvas()
end
commands.fillCanvas = function(clientID, layer, hash, color)
if SERVER then
color.nwType = "color"
broadcast(clientID, "fillCanvas", layer, hash, color)
end
if not participants[clientID] then return end
love.graphics.setCanvas(levelloop.Canvas[layer])
love.graphics.clear()
if not levelloop.textures[hash] then error("Failed to find hash!") return end
if not levelloop.textures[hash].image then
levelloop.textures[hash].image = love.graphics.newImage(levelloop.textures[hash].data)
end
utils.color_push()
love.graphics.setColor(color)
if not levelloop.textures[hash].image then
love.graphics.newImage(levelloop.textures[hash].data)
end
for x = 0, levelloop.world.x -levelloop.textures[hash].image:getPixelWidth(),
levelloop.textures[hash].image:getPixelWidth() do
for y = 0, levelloop.world.y -levelloop.textures[hash].image:getPixelHeight(),
levelloop.textures[hash].image:getPixelHeight() do
love.graphics.draw(levelloop.textures[hash].image, x, y)
end
end
utils.color_pop()
love.graphics.setCanvas()
end
commands.clearCanvas = function(clientID)
if SERVER then
broadcast(clientID, "clearCanvas")
end
love.graphics.setCanvas(levelloop.Canvas.fg)
love.graphics.clear()
love.graphics.setCanvas(levelloop.Canvas.bg)
love.graphics.clear()
love.graphics.setCanvas(levelloop.Canvas.dbg)
love.graphics.clear()
love.graphics.setCanvas()
levelloop.Canvas.physics = {}
end
end

43
shared/error.lua Normal file
View File

@ -0,0 +1,43 @@
local errorHandler = {}
function errorHandler.redscreen(msg)
local scale = 0.5
local errorfont
if love.filesystem.getInfo("fonts/font.ttf") then
errorfont = love.graphics.newFont("fonts/font.ttf", 45 * scale)
else
errorfont = love.graphics.newFont(45 * scale)
end
love.graphics.reset()
love.graphics.origin()
love.graphics.clear(.4, 0, 0)
love.graphics.setColor(1, 1, 1)
local message = love.graphics.newText(errorfont, "Poppy has encountered a critical fault")
local message2 = love.graphics.newText(errorfont, "Press q to exit")
local message3 = love.graphics.newText(errorfont, msg)
love.graphics.draw(message, 40, 40 * scale, 0, 1, 1)
love.graphics.draw(message2, 40, 90 * scale, 0, 1, 1)
love.graphics.draw(message3, 40, 140 * scale, 0, 1, 1)
print(msg)
local indent = 0
local messageline
for line in debug.traceback():gmatch("([^\n]*)\n?") do
print(line)
messageline = love.graphics.newText(errorfont, line)
love.graphics.draw(messageline, 40, (250 * scale) + indent, 0, 1, 1)
indent = indent + 50 * scale
end
love.graphics.present()
return function()
love.event.pump()
for event, action in love.event.poll() do
if event == "quit" then return 1 end
if event == "keypressed" then
if action == "q" then
love.event.quit( 1 )
end
end
end
love.timer.sleep(0.1)
end
end
return errorHandler

15
shared/fonts.lua Normal file
View File

@ -0,0 +1,15 @@
local fonts = {}
if love.filesystem.getInfo("fonts/font.ttf") then
fonts.smallFont = love.graphics.newFont("fonts/font.ttf", 14)
fonts.normalFont = love.graphics.newFont("fonts/font.ttf", 20)
fonts.headerFont = love.graphics.newFont("fonts/font.ttf", 25)
fonts.bigFont = love.graphics.newFont("fonts/font.ttf", 45)
else
fonts.smallFont = love.graphics.newFont(14)
fonts.normalFont = love.graphics.newFont(20)
fonts.headerFont = love.graphics.newFont(25)
fonts.bigFont = love.graphics.newFont(45)
end
return fonts

7
shared/icon.lua Normal file
View File

@ -0,0 +1,7 @@
local icon = {
logo = love.graphics.newImage("ressources/Poppy.png"),
joinServer = love.graphics.newImage("textures/menu/serverPlay.png"),
startServer = love.graphics.newImage("textures/menu/StartServer.png"),
addServer = love.graphics.newImage("textures/menu/AddIcon.png")
}
return icon

31
shared/log.lua Normal file
View File

@ -0,0 +1,31 @@
local log = {}
log.color = {
default = 1,
blue = 2,
pink = 3,
orange = 4
}
log.kind = {
warning = 2,
failure = 3,
info = 4,
}
log.log = function(message, level, kind, source, color)
if not color then
if kind = log.kind.failure then
color = log.color.red
elseif kind = log.kind.warning then
color = log.color.orange
elseif kind = log
color = log.color.default
end
table.insert(log.actuallog, {message = message, level = level, source = source
end
log.actuallog = { { level = TRACE, message = "Log initialized", color = BLUE, source = "log.actallog Init"}
log.level = { INFO = 1, DEBUG = 2, TRACE = 3

154
shared/networkFormat.lua Normal file
View File

@ -0,0 +1,154 @@
table.unpack = table.unpack or unpack
string.pack = function(...) return love.data.pack("string", ...) end
string.unpack = love.data.unpack
string.packsize = string.packsize or love.data.getPackedSize
-- Lua data types: nil, boolean, number, string, function, userdata, thread, and table.
-- Not representable here: function, userdata, thread.
format = {
--[[ Poppy specific
0x81 = "remoteCall",
-- lua specific
0x02 = "shortString",
0x03 = "longString",
0x04 = "nil",
0x05 = "true",
0x06 = "false",
0x07 = "number",
-- love2d/poppy specific
0x48 = "colorAlpha",
0x49 = "color"]]--
}
format.decodeFunction = function(encoded, index)
local nameLength, index = string.unpack("<I1", encoded, index)
local name, index = string.unpack("<c".. nameLength, encoded, index)
if name == "init" then DEBUG = true end
local argNumber, index = string.unpack("<I1", encoded, index)
local clientID, index = string.unpack("<I2", encoded, index)
local arguments = {}
for i = 1, argNumber do
local argtype = ""
argType, index = string.unpack("<I1", encoded, index)
if argType == 0x02 then
local stringLength, nwString
stringLength, index = string.unpack("<I1", encoded, index)
nwString, index = string.unpack("<c"..stringLength, encoded, index)
arguments[i] = nwString
elseif argType == 0x03 then
local stringLength, nwString
stringLength, index = string.unpack("<I2", encoded, index)
nwString, index = string.unpack("<c"..stringLength, encoded, index)
arguments[i] = string
elseif argType == 0x04 then
arguments[i] = nil
elseif argType == 0x05 then
arguments[i] = true
elseif argType == 0x06 then
arguments[i] = false
elseif argType == 0x07 then
local number
number, index = string.unpack("<n", encoded, index)
arguments[i] = number
elseif argType == 0x48 then
local color = {}
color[1], color[2], color[3], color[4], index = string.unpack("<ffff", encoded, index)
arguments[i] = color
elseif argType == 0x49 then
local color = {nwType = "color"}
color[1], color[2], color[3], index = string.unpack("<fff", encoded, index)
arguments[i] = color
else
error("failed to decode argument " .. i .. " index is at " ..index.. " len is " .. encoded:len() .. "value is " .. argType)
end
end
return name, clientID, arguments
end
format.encodeFunction = function(name, clientID, ...)
assert(name and clientID)
-- Format
-- byte1 0x81 to indicate a function
-- byte2 length of the name of the function
-- byte3-byteN name of the function
-- byteN+1 number of arguments to the function
-- byteN+2 type of argument
-- byteN+3-byteM argument specific syntax
-- byteM+1 type of next argument
-- and so on
local encoded = "\x81"
encoded = encoded .. string.pack("<I1", name:len())
encoded = encoded .. name .. string.pack("<I1", select("#", ...))
-- can't use ipairs, it does stop on nil
-- can't use pairs, it ignores nil :D
encoded = encoded .. string.pack("<I2", clientID)
local args = { ... }
for i=1, select("#", ...) do
local value = args[i]
if type(value) == "function" or type(value) == "CFunction" or type(value) == "userdata"
or type(value) == "thread" then
error("unencodeable data passed! of type: " .. type(value))
elseif type(value) == "nil" then
encoded = encoded .. "\x04"
elseif type(value) == "boolean" then
if value then
encoded = encoded .. "\x05"
else
encoded = encoded .. "\x06"
end
elseif type(value) == "string" then
if value:len() > 255 then
encoded = encoded .. "\x03" .. string.pack("<I2", value:len()) .. value
else
encoded = encoded .. "\x02" .. string.pack("<I1", value:len()) .. value
end
elseif type(value) == "number" then
encoded = encoded .. "\x07" .. string.pack("<n", value)
elseif type(value) == "table" then
if value.nwType == "color" and #value == 3 then
encoded = encoded .. "\x49" .. string.pack("<fff", value[1], value[2], value[3])
elseif value.nwType == "color" and #value == 4 then
encoded = encoded .. "\x48" ..
string.pack("<ffff", value[1], value[2], value[3], value[4])
else
error("Got table but don't know how to encode it")
end
else
error("unimplemented type for encoding!")
end
end
return encoded
end
function format.testEncoder()
local testCases = {
Jeremeeeey = { true, true, nil, false, 69, "Jeremy stop that >:(" },
buildBlocks = { 32, 32, 64, 64, "longHashValueISwear" },
colorWorld = { {nwType = "color", 0.4, 0.4, 0.4 }, {nwType = "color", 0.4, 0.4, 0.4, 0.5}},
slangvariants = { "oy mate", "how ya doing", "sup brooo", "heya", "hey there ma dude" }
}
print("Running encoder tests...")
for name, value in pairs(testCases) do
local encoded = format.encodeFunction(name, 3, table.unpack(value))
local name, _, decodedArgs = format.decodeFunction(encoded)
local sucess = true
for iter= 1, #value do
local var = value[iter]
if var ~= decodedArgs[iter] and not type(var) == table then
sucess = false
end
end
if sucess then
print(name .. " suceeded!")
else
print(name .. " failed.")
end
end
end
return format

22
shared/utils.lua Normal file
View File

@ -0,0 +1,22 @@
local utils = {}
utils.colorstack = {}
-- Colors
function utils.color_push()
local colorstack = utils.colorstack
local depth = #utils.colorstack +1
colorstack[depth] = {}
colorstack[depth].r, colorstack[depth].g, colorstack[depth].b,
colorstack[depth].a = love.graphics.getColor()
end
function utils.color_pop()
local colorstack = utils.colorstack
love.graphics.setColor(colorstack[#colorstack].r, colorstack[#colorstack].g,
colorstack[#colorstack].b, colorstack[#colorstack].a)
table.remove(colorstack, #colorstack)
end
return utils

BIN
sound/Bird.ogg Normal file

Binary file not shown.

BIN
sound/Bird_2.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Some files were not shown because too many files have changed in this diff Show More