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 #!sh
#adjust the next line... not sure how to figure this out from a shellscript, sorry :) # This mechanism is fairly unreliable, but it is the only "okay" way currently :/
cd ~/proj/poppy-client-loeve # 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 if test $? -eq 1; then
alert --stop "cant figure out where this script is" "Ill adjust it!" alert --stop "cant figure out where this script is" "Ill adjust it!"
exit exit
fi fi
touch log
for i in lua/*.lua; do for i in lua/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i; addattr -t mime BEOS:TYPE text/x-source-code $i;
done done
for i in server/*.lua; do for i in server/*.lua; do
echo "$i" >> log
addattr -t mime BEOS:TYPE text/x-source-code $i; addattr -t mime BEOS:TYPE text/x-source-code $i;
done 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\ This is the Poppy game\
A server is required to play the game, if you do not have one running then\ 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.\ 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,\ 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 * Loading textures
* drawing on canvases * drawing on canvases
* physics calculation for collison and movement * physics calculation for collison and movement
* drawing physics collisions (on the physics layer) * drawing physics collisions (on the physics layer) (debug)
* Basic networking * Basic networking
* Get clientID * Get clientID
* Draw all players * 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 * Send disconnect on client error
* Chat support * Chat support
* Retrieve initial game state (partial)
* Get users name
* Get users avatar
* Get world size
* Drawing * Drawing
* Draw with custom color * Draw with custom color
* Re-do base textures as gray scale to color in arbitrarily * 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) ## Not implemented (rough goals anyhow)
* UI to connect to specific server * Remember visited servers (save the list)
* Reconnect to server * Add new servers to the list by user input
* connect to other server during a session * Add new servers at runtime via DNS-SD mDNS discovery
* Open second window? Open second instance instead? (does love2d allow that?)
* Saving the game (server stuff?) * Saving the map
* Loading the game/map (server stuff?) * 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) * confirmation before clearing canvas (that's quite destructive :D)
* Make a way to add dialogs and stuff * 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 * Networking support
* Figure out why and how physics is tied to framerate, why does jump height differ? * 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) * 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 * Tell the user if the server doesn't support the networking version requested

View File

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

View File

@ -2,14 +2,16 @@ local CAM_SPEED = 10
camera = {} camera = {}
camera.target = 0 camera.target = 0
camera.offset = { x = 0, y = 300 } camera.offset = { x = 0, y = 0 }
local fullscreen = false local fullscreen = false
local altPressed, enterPressed = false, false local altPressed, enterPressed = false, false
function camera.keypressed(key, _) function camera.keypressed(key, _)
if key == "return" then enterPressed = true end if key == "return" then enterPressed = true end
if key == "lalt" then altPressed = 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 fullscreen = not fullscreen
love.window.setFullscreen(fullscreen, "desktop") love.window.setFullscreen(fullscreen, "desktop")
end end
@ -93,7 +95,7 @@ end
function camera.setplayer() function camera.setplayer()
if camera.target == 0 then return end if camera.target == 0 then return end
player = players[camera.target] player = participants[camera.target]
camera.offsetclamp() camera.offsetclamp()
local perfectx = math.floor(player.x * -1) + ((window.x -300)/2 -((player.avatar:getWidth() /2) / scale)) / scale + camera.offset.x 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 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() function camera.update()
if camera.target == 0 then return end if camera.target == 0 then return end
player = players[camera.target] player = participants[camera.target]
--[[ Update camera to track player ]]-- --[[ 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 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 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) return num - (num % thing)
end end
local utils = require("shared.utils")
function drawing.clearAllCanvases() function drawing.clearAllCanvases()
love.graphics.setCanvas(gameloop.buffer.pixel.fg) love.graphics.setCanvas(levelloop.Canvas.fg)
love.graphics.clear() love.graphics.clear()
love.graphics.setCanvas(gameloop.buffer.pixel.bg) love.graphics.setCanvas(levelloop.Canvas.bg)
love.graphics.clear() love.graphics.clear()
love.graphics.setCanvas(gameloop.buffer.pixel.dbg) love.graphics.setCanvas(levelloop.Canvas.dbg)
love.graphics.clear() love.graphics.clear()
love.graphics.setCanvas() love.graphics.setCanvas()
gameloop.buffer.physics = {} levelloop.Canvas.physics = {}
end end
function drawing.clearAllCanvasesNetwork() function drawing.clearAllCanvasesNetwork()
gameloop.networkSend("clearCanvas") levelloop.remoteCall("clearCanvas")
drawing.clearAllCanvases() drawing.clearAllCanvases()
end end
function drawing.fillCanvas(layer, hash) function drawing.fillCanvas(layer, hash, color)
love.graphics.setCanvas(gameloop.buffer.pixel[layer]) love.graphics.setCanvas(levelloop.Canvas[layer])
love.graphics.clear() love.graphics.clear()
for x = 0, gameloop.world.x -gameloop.textures[hash].image:getPixelWidth(), utils.color_push()
gameloop.textures[hash].image:getPixelWidth() do love.graphics.setColor(color)
for y = 0, gameloop.world.y -gameloop.textures[hash].image:getPixelHeight(), for x = 0, levelloop.world.x -levelloop.textures[hash].image:getPixelWidth(),
gameloop.textures[hash].image:getPixelHeight() do levelloop.textures[hash].image:getPixelWidth() do
love.graphics.draw(gameloop.textures[hash].image, x, y) for y = 0, levelloop.world.y -levelloop.textures[hash].image:getPixelHeight(),
end levelloop.textures[hash].image:getPixelHeight() do
love.graphics.draw(levelloop.textures[hash].image, x, y)
end end
love.graphics.setCanvas() end
utils.color_pop()
love.graphics.setCanvas()
end end
function drawing.fillCanvasNetwork(layer, hash) function drawing.fillCanvasNetwork(layer, hash, color)
gameloop.networkSend(unit("fillCanvas", layer, love.data.encode("string", "base64", hash))) color.nwType = "color"
drawing.fillCanvas(layer, hash) levelloop.remoteCall("fillCanvas", layer, hash, color)
drawing.fillCanvas(layer, hash, color)
end end
function drawing.keyreleased(key, _) function drawing.keyreleased(key, _)
assert(drawing.color)
if key == "c" then if key == "c" then
drawing.clearAllCanvasesNetwork() drawing.clearAllCanvasesNetwork()
end end
if key == "f" then if key == "f" then
drawing.fillCanvasNetwork("fg", drawing.cursorHash) drawing.fillCanvasNetwork("fg", drawing.cursorHash, drawing.color)
end end
if key == "b" then if key == "b" then
drawing.fillCanvasNetwork("bg", drawing.cursorHash) drawing.fillCanvasNetwork("bg", drawing.cursorHash, drawing.color)
end end
end end
local drawmouse = {} local drawmouse = {}
function drawing.input(dt) function drawing.input(dt)
local textures = gameloop.textures local textures = levelloop.textures
local cursor = drawing.cursor local cursor = drawing.cursor
if not cursor or not cursor.data then return end if not cursor or not cursor.data then return end
assert(cursor, "Failed to find cursor") assert(cursor, "Failed to find cursor")
@ -73,8 +79,8 @@ function drawing.input(dt)
end end
mousex = mousex / scale - view.x mousex = mousex / scale - view.x
mousey = mousey / scale - view.y mousey = mousey / scale - view.y
if mousex > gameloop.world.x or if mousex > levelloop.world.x or
mousey > gameloop.world.y or mousey > levelloop.world.y or
mousex < 0 or mousex < 0 or
mousey < 0 then mousey < 0 then
return return
@ -99,18 +105,17 @@ function drawing.input(dt)
if not (thisx == thisx) or not (thisy == thisy) then return end if not (thisx == thisx) or not (thisy == thisy) then return end
local ctrlDown = love.keyboard.isDown("lctrl") or love.keyboard.isDown("rctrl") local ctrlDown = love.keyboard.isDown("lctrl") or love.keyboard.isDown("rctrl")
love.graphics.setBlendMode("replace")
if ctrlDown then if ctrlDown then
love.graphics.setCanvas(gameloop.buffer.pixel.bg) love.graphics.setCanvas(levelloop.Canvas.bg)
else else
love.graphics.setCanvas(gameloop.buffer.pixel.fg) love.graphics.setCanvas(levelloop.Canvas.fg)
end end
if love.mouse.isDown(1) then -- only delete if love.mouse.isDown(1) then -- only delete
color_push() utils.color_push()
love.graphics.setColor(drawing.color) love.graphics.setColor(drawing.color)
love.graphics.draw(cursor.image, thisx, thisy) love.graphics.draw(cursor.image, thisx, thisy)
color_pop() utils.color_pop()
if ctrlDown then if ctrlDown then
drawing.draw_texture(drawing.cursorHash, thisx, thisy, "bg", drawing.color) drawing.draw_texture(drawing.cursorHash, thisx, thisy, "bg", drawing.color)
else else
@ -120,7 +125,8 @@ function drawing.input(dt)
cursor.image:getPixelHeight()) cursor.image:getPixelHeight())
end end
else else
color_push() love.graphics.setBlendMode("replace")
utils.color_push()
love.graphics.setColor(0,0,0,0) love.graphics.setColor(0,0,0,0)
love.graphics.rectangle("fill", thisx, thisy, love.graphics.rectangle("fill", thisx, thisy,
cursor.image:getPixelWidth(), cursor.image:getPixelWidth(),
@ -137,9 +143,9 @@ function drawing.input(dt)
cursor.image:getPixelWidth(), cursor.image:getPixelWidth(),
cursor.image:getPixelHeight()) cursor.image:getPixelHeight())
end end
color_pop() utils.color_pop()
love.graphics.setBlendMode("alpha")
end end
love.graphics.setBlendMode("alpha")
love.graphics.setCanvas() love.graphics.setCanvas()
end end
drawmouse.lastX = mousex 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 if not color[4] then color[4] = 1 end
local last = drawing.lastTexture local last = drawing.lastTexture
if hash ~= last.hash or x ~= last.x or y ~= last.y or layer ~= last.layer then if hash ~= last.hash or x ~= last.x or y ~= last.y or layer ~= last.layer then
gameloop.networkSend(unit("drawTexture", x, y, color.nwType = "color"
layer, love.data.encode("string", "base64", hash), color[1], color[2], color[3], color[4])) levelloop.remoteCall("drawTexture", x, y, layer, hash, color)
end end
last.hash, last.x, last.y, last.layer = hash, x, y, layer last.hash, last.x, last.y, last.layer = hash, x, y, layer
end end
@ -168,7 +174,7 @@ function drawing.del_texture(x, y, width, height, layer)
local last = drawing.lastTextureDel local last = drawing.lastTextureDel
if x ~= last.x or y ~= last.y or width ~= last.width if x ~= last.x or y ~= last.y or width ~= last.width
or height ~= last.height or layer ~= last.layer then 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 last.x, last.y, last.width, last.height, last.layer = x, y, width, height, layer
end end
end end
@ -177,7 +183,7 @@ end
local lastDWP = { x = 0, y = 0, width = 0, height = 0} local lastDWP = { x = 0, y = 0, width = 0, height = 0}
function drawing.draw_solid(x, y, width, height) 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 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) physics.draw_solid(x, y, width, height)
lastDWP.x, lastDWP.y, lastDWP.width, lastDWP.height = x, y, width, height lastDWP.x, lastDWP.y, lastDWP.width, lastDWP.height = x, y, width, height
end end
@ -187,14 +193,15 @@ end
local lastRMP = { x = 0, y = 0, width = 0, height = 0} local lastRMP = { x = 0, y = 0, width = 0, height = 0}
function drawing.remove_solid(x, y, width, height) 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 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) physics.remove_solid(x, y, width, height)
lastRMP.x, lastRMP.y, lastRMP.width, lastRMP.height = x, y, width, height lastRMP.x, lastRMP.y, lastRMP.width, lastRMP.height = x, y, width, height
end end
end end
function drawing.init() 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), local texture = {data = love.image.newImageData(path),
image = love.graphics.newImage(path)} image = love.graphics.newImage(path)}
drawing.color = {1, 1, 1, 1} drawing.color = {1, 1, 1, 1}
@ -203,7 +210,6 @@ function drawing.init()
drawing.cursor = textures[drawing.cursorHash] drawing.cursor = textures[drawing.cursorHash]
texture, path = nil, nil texture, path = nil, nil
local clientChannel = love.thread.getChannel ( "clientChannel" );
mouse.lastpos = {} mouse.lastpos = {}
mouse.lastpos.x = 0 mouse.lastpos.x = 0

View File

@ -1,26 +1,44 @@
gameloop = {} levelloop = {}
gameloop.world = nil levelloop.CLIENT = true
gameloop.textures = {} levelloop.world = nil
local rpc = require("server.rpc") levelloop.textures = {}
local v2_message = require("lua.network") levelloop.avatars = {}
local socket = require "socket" commands = {}
local util = require("server.utils") 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() 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) local layerTransform = love.math.newTransform(math.floor(view.x) * scale, math.floor(view.y) * scale, 0, scale, scale)
-- Draw background -- Draw background
if drawBackground then if drawBackground then
color_push() utils.color_push()
love.graphics.setColor(0.05, 0.05, 0.05) 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.setColor(1, 1, 1, 1)
love.graphics.draw(gameloop.buffer.pixel.bg, layerTransform) love.graphics.draw(levelloop.Canvas.bg, layerTransform)
color_pop() utils.color_pop()
end end
-- draw player -- draw player
for i, player in pairs(players) do for i, player in pairs(participants) do
local playerTransform = love.math.newTransform( local playerTransform = love.math.newTransform(
math.floor(player.x) * scale + math.floor(view.x) * scale, math.floor(player.x) * scale + math.floor(view.x) * scale,
math.floor(player.y) * scale + math.floor(view.y) * scale, math.floor(player.y) * scale + math.floor(view.y) * scale,
@ -29,7 +47,7 @@ local function normalDraw()
love.graphics.draw(player.avatar, playerTransform) love.graphics.draw(player.avatar, playerTransform)
if player.name then if player.name then
if not player.label 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 end
local labelTransform = love.math.newTransform( local labelTransform = love.math.newTransform(
math.floor(player.x - (player.label:getWidth() / 2) + math.floor(player.x - (player.label:getWidth() / 2) +
@ -41,7 +59,7 @@ local function normalDraw()
end end
-- Draw foreground -- Draw foreground
love.graphics.draw(gameloop.buffer.pixel.fg, layerTransform) love.graphics.draw(levelloop.Canvas.fg, layerTransform)
-- Draw UI -- Draw UI
love.graphics.draw(ui.buffer) love.graphics.draw(ui.buffer)
@ -51,184 +69,156 @@ end
local function debugDraw() local function debugDraw()
normalDraw() normalDraw()
local layerTransform = love.math.newTransform(math.floor(view.x) * scale, math.floor(view.y) * scale, 0, scale, scale) 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.setCanvas(levelloop.Canvas.dbg2)
love.graphics.draw(gameloop.buffer.pixel.dbg2, layerTransform)
love.graphics.setCanvas(gameloop.buffer.pixel.dbg2)
love.graphics.clear() love.graphics.clear()
love.graphics.setCanvas() love.graphics.setCanvas()
end end
local function normalNetworkSend(args) local function remoteCall(name, ...)
if clientID == "0" then print("Invalid clientID?") end local request = protocolVersion .. format.encodeFunction(name, levelloop.clientID or 0, ...)
local request = "poppyV002" ..US.. clientID ..US.. args levelloop.client:send(request)
gameloop.client:send(request)
end end
local function debugNetworkSend(args) local function networkSync()
assert(clientID ~= 0) while true do
local request = "poppyV002" ..US.. clientID ..US.. args local message, fault = levelloop.client:receive()
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()
if not message then if not message then
if errorString == "timeout" then return else if fault == "timeout" then
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()
return return
else
error(fault)
end end
end end
local response = rpc.validate(message) local protocolVersion, index = string.unpack("<c4", message, 1)
if not response.errorMsg then if protocolVersion ~= "PPY\x03" then
rpc.execute(v2_message, response.clientID, response.record) print("Dropping packet with invalid protocol " .. protocolVersion)
message = nil
else 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 end
end end
local function errorhandlerNetwork(msg) local function errorhandlerNetwork(msg)
gameloop.networkSend(unit("playerLeave")) levelloop.remoteCall("playerLeave")
return errorhandler(msg) return errorHandler.redscreen(msg)
end end
function errorhandler(msg) function levelloop.mousepressed(mousex, mousey)
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)
ui.mousepressed(mousex, mousey) ui.mousepressed(mousex, mousey)
end end
function gameloop.init(server, nickname) function levelloop.loadAvatars()
love.errorhandler = errorhandler local container = {}
assert(server, "Gameloop called without server to connect to?") local entries = love.filesystem.getDirectoryItems("textures/player")
assert(nickname, "Gameloop called without nickname?") if #entries == 0 then return error("no avatars found") end
gameloop.server = server 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 scale = 1
love.graphics.setDefaultFilter("nearest", "nearest", 0) love.graphics.setDefaultFilter("nearest", "nearest", 0)
layer = true layer = true
grid = 16 grid = 16
color = {} color = {}
mouse = {} mouse = {}
window = {} window = {}
window.x, window.y = love.graphics.getDimensions() window.x, window.y = love.graphics.getDimensions()
players = {} participants = {}
textures = {} textures = {}
view = {} view = {}
view.x = 0 view.x = 0
view.y = 0 view.y = 0
clientID = "0" clientID = "0"
-- Must be done now because it has to be ready before "init" returns from the server
gameloop.client = socket.udp() -- which is /before/ the game world is loaded
gameloop.client:setpeername(server.adress, server.port) levelloop.loadAvatars()
gameloop.client:settimeout(0)
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) print("Connecting to [" .. server.adress .."]:" .. server.port)
local request = "poppyV002" ..US.. "0" ..US.. unit("init", nickname) remoteCall("init", nickname, avatarHash)
gameloop.client:send(request)
if NETWORK_DEBUG then print("=> " .. util.pprint(request)) end
if not NETWORK_DEBUG then levelloop.remoteCall = remoteCall
gameloop.networkSend = normalNetworkSend levelloop.networkSync = networkSync
gameloop.networkSync = normalNetworkSync
else
gameloop.networkSend = debugNetworkSend
gameloop.networkSync = debugNetworkSync
end
love.update = function(dt) love.update = function(dt)
gameloop.networkSync(dt) networkSync(dt)
love.timer.sleep((1/60) -dt) love.timer.sleep((1/60) -dt)
end 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.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
end end
function gameloop.normalVisible(dt) function levelloop.normalVisible(dt)
gameloop.networkSync(dt) networkSync(dt)
camera.input(dt) camera.input(dt)
physics.input(dt) physics.input(dt)
drawing.input(dt) drawing.input(dt)
@ -238,20 +228,20 @@ function gameloop.normalVisible(dt)
end end
function gameloop.reducedVisible(dt) function levelloop.reducedVisible(dt)
gameloop.networkSync(dt) networkSync(dt)
physics.update(dt) physics.update(dt)
love.timer.sleep((1/60) -dt) love.timer.sleep((1/60) -dt)
end end
function gameloop.keypressed(key, _) function levelloop.keypressed(key, _)
physics.keypressed(key, _) physics.keypressed(key, _)
camera.keypressed(key, _) camera.keypressed(key, _)
end end
function gameloop.keyreleased(key, _) function levelloop.keyreleased(key, _)
if key == "r" then if key == "r" then
drawBackground = not drawBackground drawBackground = not drawBackground
end end
@ -261,53 +251,52 @@ function gameloop.keyreleased(key, _)
end end
function gameloop.loadworld(world) function levelloop.loadworld(world)
-- Game connected! -- Game connected!
love.errorhandler = errorhandlerNetwork --love.errorhandler = errorhandlerNetwork
local buffer = {} print("make canvas!")
buffer.pixel = {} levelloop.Canvas.fg = love.graphics.newCanvas(world.x, world.y)
buffer.pixel.fg = love.graphics.newCanvas(world.x, world.y) levelloop.Canvas.bg = love.graphics.newCanvas(world.x, world.y)
buffer.pixel.bg = love.graphics.newCanvas(world.x, world.y)
if PHYSICS_DEBUG then if PHYSICS_DEBUG then
buffer.pixel.dbg = love.graphics.newCanvas(world.x, world.y) levelloop.Canvas.dbg = love.graphics.newCanvas(world.x, world.y)
buffer.pixel.dbg2 = love.graphics.newCanvas(world.x, world.y) levelloop.Canvas.dbg2 = love.graphics.newCanvas(world.x, world.y)
end end
buffer.physics = {} levelloop.Canvas.physics = {}
gameloop.buffer = buffer levelloop.world = world
ui.init() ui.init()
drawing.init() drawing.init()
love.update = gameloop.normalVisible love.update = levelloop.normalVisible
love.visible = function(visible) love.visible = function(visible)
if visible then if visible then
love.update = gameloop.normalVisible love.update = levelloop.normalVisible
else else
love.update = gameloop.reducedVisible love.update = levelloop.reducedVisible
end end
end end
drawBackground = true drawBackground = true
love.keyreleased = gameloop.keyreleased love.keyreleased = levelloop.keyreleased
love.keypressed = gameloop.keypressed love.keypressed = levelloop.keypressed
love.mousepressed = gameloop.mousepressed love.mousepressed = levelloop.mousepressed
if not PHYSICS_DEBUG then if not PHYSICS_DEBUG then
love.draw = normalDraw love.draw = normalDraw
else else
love.draw = debugDraw love.draw = debugDraw
end end
love.resize = function() love.resize = function()
normalDraw()
ui.resize() ui.resize()
camera.resize() camera.resize()
end end
love.quit = function() love.quit = function()
gameloop.networkSend(unit("playerLeave")) levelloop.remoteCall("playerLeave")
end end
gameloop.world = world levelloop.world = world
end 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 = {} local layout = {}
uiState = {} 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) --print(string)
end end
function layout.canvasAndColor(container, x, y, simulate) 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 if simulate then return layout.handle(container[1], x, y, simulate) end
color_push() utils.color_push()
love.graphics.setCanvas(container.canvas) love.graphics.setCanvas(container.canvas)
x, y = layout.handle(container[1], x, y, simulate) x, y = layout.handle(container[1], x, y, simulate)
love.graphics.setCanvas() love.graphics.setCanvas()
color_pop() utils.color_pop()
return x, y return x, y
end end
function layout.rotate(container, x, y, simulate) function layout.rotate(container, x, y, simulate)
assert_num("layout.rotate", 2, x, y) asserts.number("layout.rotate", 2, x, y)
assert_num("layout.rotate (container)", container.rotation) asserts.number("layout.rotate (container)", container.rotation)
local transform = love.math.newTransform(0, 0, container.rotation) local transform = love.math.newTransform(0, 0, container.rotation)
local noTransform = love.math.newTransform(0, 0, 0) local noTransform = love.math.newTransform(0, 0, 0)
love.graphics.replaceTransform(transform) love.graphics.replaceTransform(transform)
@ -30,7 +41,8 @@ end
function layout.frame(container, x, y, simulate) 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) x2, y2 = layout.handle(container[1], x +1, y +1, simulate)
if not simulate then if not simulate then
love.graphics.rectangle("line", x, y, x2 -x, y2 -y) love.graphics.rectangle("line", x, y, x2 -x, y2 -y)
@ -41,31 +53,22 @@ end
function layout.cursorSelect(container, x, y, simulate) function layout.cursorSelect(container, x, y, simulate)
x2, y2 = layout.handle(container[1], x, y, simulate) x2, y2 = layout.handle(container[1], x, y, simulate)
assert(container.uiSelect)
if not simulate then 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 }) x=x, x2=x2, y=y, y2=y2 })
end end
return x2, y2 return x2, y2
end 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) function layout.spacer(container, x, y, simulate)
assert(container.width or container.height, "No arguments for layout.spacer!") assert(container.width or container.height, "No arguments for layout.spacer!")
assert(container.width == container.width, assert(container.width == container.width,
"Argument container.width to layout.spacer must not be NaN!") "Argument container.width to layout.spacer must not be NaN!")
assert(container.height == container.height, assert(container.height == container.height,
"Argument container.height to layout.spacer must not be NaN!") "Argument container.height to layout.spacer must not be NaN!")
local width = container.width or 0 local width = container.width or 0
local height = container.height or 0 local height = container.height or 0
if container[1] then if container[1] then
@ -82,12 +85,12 @@ function layout.drawTexture(container, x, y, simulate)
if not simulate then if not simulate then
love.graphics.draw(texture, x, y, 0, scale, scale) love.graphics.draw(texture, x, y, 0, scale, scale)
end end
return x + texture:getWidth()* scale, y + texture:getHeight() * scale return x + (texture:getWidth() * scale), y + texture:getHeight() * scale
end end
function layout.drawHash(container, x, y, simulate) 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 local scale = container.scale or 1
if not texture.image then if not texture.image then
texture.image = love.graphics.newImage(texture.data) texture.image = love.graphics.newImage(texture.data)
@ -99,42 +102,80 @@ function layout.drawHash(container, x, y, simulate)
end 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) function layout.colorPicker(container, x, y, simulate)
assert(x and y) asserts.number("layout.colorPicker", 2, x, y)
local startx, starty = x, y local startx, starty = x, y
granularity = container.granularity or 0.2 granularity = container.granularity or 0.2
local points = {} local points = {}
local myx, myy = x, y local myx, myy = x, y
local endx = 0 local endx = 0
local pointsize = 16 local pointSize = container.pointSize or 8
if not simulate then if not simulate then
color_push() utils.color_push()
love.graphics.setPointSize(pointsize) love.graphics.setPointSize(pointSize)
for r=0, 1.0, granularity do for r=0, 1.0, granularity do
for g=0, 1.0, granularity do for g=0, 1.0, granularity do
for b=0, 1.0, granularity do for b=0, 1.0, granularity do
love.graphics.setColor(r, g, b, 1) love.graphics.setColor(r, g, b, 1)
myx=myx+16 myx=myx+pointSize
love.graphics.points(myx-4.5, myy+4.5) love.graphics.points(myx-(pointSize/2) -.5, myy +(pointSize/2))
end end
end end
myy = myy + 16 myy = myy + pointSize
if myx > endx then endx = myx end if myx > endx then endx = myx end
myx = x myx = x
end end
color_pop() utils.color_pop()
local iterationCount = (1.0 / granularity) + 1 local iterationCount = (1.0 / granularity) + 1
maxx = x + pointsize * iterationCount * iterationCount maxx = x + pointSize * iterationCount * iterationCount
table.insert(uiState, { granularity = granularity, pointsize = pointsize, table.insert(layout.uiState.GameUI, { granularity = granularity, pointSize = pointSize,
kind=container.kind, kind=container.kind,
x=x, x2=maxx, y=y, y2=myy }) x=x, x2=maxx, y=y, y2=myy })
else else
local iterationCount = (1.0 / granularity) + 1 local iterationCount = (1.0 / granularity) + 1
maxx = x + pointsize * iterationCount * iterationCount maxx = x + pointSize * iterationCount * iterationCount
maxy = y + pointsize * iterationCount maxy = y + pointSize * iterationCount
return maxx, maxy
end end
return maxx, myy return maxx, myy
end end
@ -142,34 +183,64 @@ end
function layout.color(container, x, y, simulate) function layout.color(container, x, y, simulate)
if simulate then return layout.handle(container[1], x, y, simulate) end 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) assert(#container.color == 3 or #container.color == 4)
color_push() utils.color_push()
love.graphics.setColor(container.color) love.graphics.setColor(container.color)
x, y = layout.handle(container[1], x, y, simulate) x, y = layout.handle(container[1], x, y, simulate)
color_pop() utils.color_pop()
return x, y return x, y
end end
function layout.rect(container, x, y, simulate) function layout.rect(container, x, y, simulate)
assert_num("layout.rect", 2, x, y) asserts.number("layout.rect", 2, x, y)
assert_num("layout.rect (container)", 2, container.width, container.height) asserts.number("layout.rect (container)", 2, container.width, container.height)
if not simulate then
if container.color then if simulate then return x + container.width, y + container.height end
color_push()
love.graphics.setColor(container.color.r, container.color.g, container.color.b, container.color.a or 1) 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) 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 end
return x + container.width, y + container.height return x + container.width, y + container.height
end 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) 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) winx, winy = layout.handle(container[1], x, y, simulate)
width = winx -x width = winx -x
height = winy -y height = winy -y
@ -180,11 +251,33 @@ function layout.overlayRect(container, x, y, simulate)
end 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) 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 ) 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 if not simulate then
love.graphics.draw(text, x, y) love.graphics.draw(text, x, y)
end end
@ -214,7 +307,40 @@ function layout.horizontal(container, x, y, simulate)
if ret.y > bigy then bigy = ret.y end if ret.y > bigy then bigy = ret.y end
x = ret.x x = ret.x
end 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 end
@ -247,13 +373,22 @@ function layout.vertical(container, x, y, simulate)
end end
function layout.skip(container, x, y, simulate)
return x, y
end
local depth = -1 local depth = -1
function layout.handle(container, x, y, simulate) 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") assert(container, "layout.handle called without container")
local name = container["name"] local name = container["name"]
if not name then return end
assert(name, "layout.handle container without name") assert(name, "layout.handle container without name")
assert(layout[name], "Unknown layout function " .. name .. " called") assert(layout[name], "Unknown layout function " .. name .. " called")
depth = depth +1 depth = depth +1
local depthCursor = "" local depthCursor = ""
for i=0, depth do for i=0, depth do
@ -261,22 +396,24 @@ function layout.handle(container, x, y, simulate)
end end
if depth == 0 then if depth == 0 then
if simulate then if simulate then
debugPrint("Starting simulation") layout.debugPrint("Starting simulation")
else else
debugPrint("Starting rendering") layout.debugPrint("Starting rendering")
end end
end end
debugPrint(depthCursor.. ">" .. container.name) layout.debugPrint(depthCursor.. ">" .. container.name)
x, y = layout[name](container, x, y, simulate) x, y = layout[name](container, x, y, simulate)
assert_num("layout.handle" .. container.name .. " is not returning properly!", 2, x, y) asserts.number("layout.handle" .. container.name .. " is not returning properly!", 2, x, y)
debugPrint(depthCursor.."<" .. container.name) layout.debugPrint(depthCursor.."<" .. container.name)
depth = depth -1 depth = depth -1
if depth == -1 then if depth == -1 then
if simulate then if simulate then
debugPrint("simulation ended\n") layout.debugPrint("simulation ended\n")
else else
debugPrint("rendering ended\n") layout.debugPrint("rendering ended\n")
end end
end end
return x, y return x, y
end end
return layout

View File

@ -1,9 +1,16 @@
menu = {} menu = {}
menu.serverlist = { menu.serverlist = {
{description = "A localhost server [ipv4]", host = "127.0.0.1", port = "11150"}, {description = "A localhost server", host = "::1", port = "11150"},
{description = "A localhost server [ipv6]", 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 local altPressed, enterPressed = false, false
function menu.keyreleased(key, _) function menu.keyreleased(key, _)
if key == "lalt" then if key == "lalt" then
@ -25,11 +32,6 @@ function menu.keypressed(key, _)
end end
function menu.update(dt)
love.timer.sleep((1/60) -dt)
end
function menu.resize() function menu.resize()
menu.width, menu.height = love.window.getMode() menu.width, menu.height = love.window.getMode()
menu.Canvas = love.graphics.newCanvas(width, height) menu.Canvas = love.graphics.newCanvas(width, height)
@ -40,20 +42,50 @@ function menu.resize()
end end
local firstClient = true
function menu.mousepressed(mousex, mousey) function menu.mousepressed(mousex, mousey)
local textures = gameloop.textures for i, v in ipairs(layout.uiState.MainMenu) do
for i, v in ipairs(uiState) do if mousex >= v.x and mousex <= v.x2 and mousey >= v.y and mousey <= v.y2 then
if mousex >= v.x and mousex <= v.x2 then if v.kind == "localServer" then
if mousey >= v.y and mousey <= v.y2 then menuMusic[menuMusic.trac]:stop()
if v.kind == "server" then love.graphics.reset()
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" } love.graphics.origin()
local lastNames = { "Müller", "Schmidt", "Meier", "Bauer", "Werner", "Schumacher", "Bergmann", "Eisenhauer", "Heisenberg" } love.graphics.clear(.6, 0, .6)
local fullName = names[love.math.random(1, #names)] .. " " .. lastNames[love.math.random(1, #lastNames)] love.graphics.setColor(1, 1, 1)
local serverID = v.identifier love.graphics.present()
local serverEntry = menu.serverlist[serverID] local serverloop = require("server.main")
gameloop.init({ adress = serverEntry.host, port = serverEntry.port }, fullName) serverloop.init(v.identifier)
uiState = {} 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 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 end
end end
@ -61,68 +93,245 @@ end
menu.serverentry = function(id, description, host, port) menu.serverentry = function(id, description, host, port)
return {name = "horizontal", return {name = "cursorSelect", kind = "server", identifier = id, uiSelect = "MainMenu",
{name = "label", text = description }, {name = "horizontal",
{name = "spacer", width = 20 }, {name = "drawTexture", texture = icon.joinServer},
{name = "label", text = "[".. host .."]"}, {name = "spacer", width = 10 },
{name = "label", text = ":".. port }, {name = "labelWShadow", text = description },
{name = "spacer", width = 25 }, {name = "spacer", width = 20 },
{name = "cursorSelect", kind = "server", identifier = id, {name = "labelWShadow", text = "host: ".. host, font = fonts.smallFont},
{name = "drawTexture", texture = love.graphics.newImage("textures/menu/serverPlay.png")}}} {name = "spacer", width = 10 },
{name = "labelWShadow", text = "port:".. port, font = fonts.smallFont},
}
}
end end
menu.layoutServerList = function() menu.layoutServerList = function()
local list = {name = "vertical"} local list = {}
for i, v in ipairs(menu.serverlist) do for i, v in ipairs(menu.serverlist) do
table.insert(list, menu.serverentry(i, v.description, v.host, v.port)) table.insert(list, menu.serverentry(i, v.description, v.host, v.port))
end end
return list return unpack(list)
end end
menu.layout = {name ="vertical", menu.layoutLocalServer = function()
{name = "spacer", height = 20}, return {name = "vertical",
{name = "horizontal", {name = "cursorSelect", kind = "localServer",
{name = "spacer", width = 20}, identifier = {"::1", "11150"}, uiSelect = "MainMenu",
{name = "drawTexture", texture = love.graphics.newImage("ressources/Poppy.png")}, {name = "horizontal",
{name = "label", text = "Welcome to poppy!"}, {name = "spacer", width = 30},
}, {name = "drawTexture",
{name = "horizontal", texture = icon.startServer
{name = "spacer", width = 60}, },
{name = "vertical", {name = "spacer", width = 5},
{name = "spacer", height = 60}, {name = "labelWShadow", text = "Create a local server"},
{name = "label", text = "Pick a server to join!"}, }
{name = "spacer", height = 20}, },
menu.layoutServerList() {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() function drawMenu(message)
color_push() utils.color_push()
love.graphics.setCanvas(menu.Canvas) love.graphics.setCanvas(menu.Canvas)
uiState = {} layout.uiState.MainMenu = {}
love.graphics.clear( ) 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() love.graphics.setCanvas()
color_pop() utils.color_pop()
end 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) love.graphics.draw(menu.Canvas)
layout.handle(debugAudio(), 600, 300)
end 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() 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) menu.Canvas = love.graphics.newCanvas(menu.width, menu.height)
drawMenu() drawMenu()
love.draw = menu.draw love.draw = menu.draw
love.update = menu.update
love.keyreleased = menu.keyreleased love.keyreleased = menu.keyreleased
love.keypressed = menu.keypressed love.keypressed = menu.keypressed
love.mousepressed = menu.mousepressed love.mousepressed = menu.mousepressed
end end
return menu 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 = {} physics = {}
local asserts = require("shared.asserts")
local utils = require("shared.utils")
function physics.keyreleased_debug(key, _) function physics.keyreleased_debug(key, _)
if key == "kp4" then if key == "kp4" then
player.x = player.x -1 player.x = player.x -1
@ -17,30 +20,30 @@ end
function physics.remove_solid(x, y, sizex, sizey) 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_x=x, x + sizex-1 do
for coord_y=y, y + sizey-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 end
end end
function physics.draw_solid(x, y, sizex, sizey) 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_x=x, x + sizex-1 do
for coord_y=y, y + sizey-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 end
end end
function draw_colision(x, y, x2, y2, pixelx, pixely) function draw_colision(x, y, x2, y2, pixelx, pixely)
color_push() utils.color_push()
love.graphics.setLineWidth(1) love.graphics.setLineWidth(1)
love.graphics.setDefaultFilter("nearest", "nearest", 0) love.graphics.setDefaultFilter("nearest", "nearest", 0)
love.graphics.setCanvas(gameloop.buffer.pixel.dbg2) love.graphics.setCanvas(levelloop.Canvas.dbg2)
if pixelx then if pixelx then
love.graphics.setColor(.6, 0, .6, 1) love.graphics.setColor(.6, 0, .6, 1)
love.graphics.rectangle("fill", x, y, x2, y2) 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) love.graphics.rectangle("fill", x, y, x2, y2)
end end
love.graphics.setCanvas() love.graphics.setCanvas()
color_pop() utils.color_pop()
end end
function physics.is_blocked_x(x, y, sizex) function physics.is_blocked_x(x, y, sizex)
x, y = math.floor(x), math.floor(y) 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 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 if PHYSICS_DEBUG then draw_colision(x, y, sizex, 1, coord_x, y) end
return true return true
end end
@ -71,9 +74,9 @@ end
function physics.is_blocked_y(x, y, sizey) function physics.is_blocked_y(x, y, sizey)
x, y = math.floor(x), math.floor(y) 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 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 if PHYSICS_DEBUG then draw_colision(x, y, 1, sizey, x, coord_y) end
return true return true
end end
@ -85,16 +88,14 @@ end
function physics.keypressed( key, code, isrepeat) function physics.keypressed( key, code, isrepeat)
if isrepeat then return end if isrepeat then return end
if camera.target == 0 then return end if camera.target == 0 then return end
player = players[camera.target] local player = participants[levelloop.clientID]
if key == "p" then if key == "p" then
player.flying = not player.flying player.flying = not player.flying
levelloop.remoteCall("moveFlying", player.flying)
if player.flying then if player.flying then
player.speed.y = 0 player.speed.y = 0
gameloop.networkSend(unit("moveFlying", "true"))
else
gameloop.networkSend(unit("moveFlying", "false"))
end end
end end
end end
@ -102,7 +103,7 @@ end
function physics.input(dt) function physics.input(dt)
if camera.target == 0 then return end if camera.target == 0 then return end
player = players[camera.target] local player = participants[levelloop.clientID]
local speedModified = dt * 20 local speedModified = dt * 20
if love.keyboard.isDown("rshift") or love.keyboard.isDown("lshift") if love.keyboard.isDown("rshift") or love.keyboard.isDown("lshift")
then then
@ -124,7 +125,7 @@ function physics.input(dt)
local posx = player.x + player.avatar:getWidth() local posx = player.x + player.avatar:getWidth()
local posy = player.y + player.avatar:getHeight() local posy = player.y + player.avatar:getHeight()
if physics.is_blocked_x(posx - player.avatar:getWidth(), posy, player.avatar:getWidth()) or 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 player.speed.y = -25
end end
end end
@ -132,16 +133,16 @@ end
function physics.cap_world(width, height, newx, newy, new_sx, new_sy) function physics.cap_world(width, height, newx, newy, new_sx, new_sy)
if newx > gameloop.world.x - width then if newx > levelloop.world.x - width then
newx = gameloop.world.x - width newx = levelloop.world.x - width
new_sx = 0 new_sx = 0
end end
if newx < 0 then if newx < 0 then
newx = 0 newx = 0
new_sx = 0 new_sx = 0
end end
if newy > gameloop.world.y - height then if newy > levelloop.world.y - height then
newy = gameloop.world.y - height newy = levelloop.world.y - height
new_sy = 0 new_sy = 0
end end
if newy < 0 then 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 return newx, newy, new_sx, new_sy
end 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 function physics.updatePlayer(dt, player, localplayer)
local new_sx = player.speed.x - player.speed.x / 16 local new_sx, new_sy
local new_sy if player.flying then
if player.flying then new_sy = player.speed.y - player.speed.y / 6
new_sy = player.speed.y - player.speed.y / 16 new_sx = player.speed.x - player.speed.x / 6
else else
new_sy = player.speed.y - player.speed.y / 6 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 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 intermediateY > futureY then -- UP
if not new_sy then new_sy = 0 end if physics.is_blocked_x(intermediateX, intermediateY, player.avatar:getWidth()) then
--falling intermediateY = currentY
if not player.flying then blocked_y = true
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)
end 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 return
end end
do -- SCOPE if blocked_x and blocked_y then
local blocked_x, blocked_y = false, false return
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
end end
-- cap low speed
if new_sx > -.1 and new_sx < .1 then if not blocked_x and not blocked_y then
new_sx = 0 player.x = intermediateX
end player.y = intermediateY
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)
end end
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 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) function physics.send_update(player, dt)
if last.packet > 0.03 then if last.packet > 0.03 then
if last.x ~= player.x or last.y ~= player.y 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.packet = 0
last.x, last.y = player.x, player.y last.x, last.y = player.x, player.y
last.speedx, last.speedy = player.speedx, player.speedy last.speedx, last.speedy = player.speedx, player.speedy

View File

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

View File

@ -1,40 +1,83 @@
ui = {} ui = {}
ui.textureTree = nil 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) function ui.update_scale(p_scale)
scale = p_scale scale = p_scale
end 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 ui.helptext = [[[P] toggle fly
[A/D] Move {A/D} Move
[Hold Shift] Move faster {Shift} Move faster
[Space] Jump [Space] Jump
[A/S/DW] Move (flying) {A/S/DW} Move (flying)
[C] Clear canvas [C] Clear canvas
[F] Fill foreground [F] Fill foreground
[G] Fill background [G] Fill background
[Mouse1] Draw {Mouse1} Draw
[Mouse2] Delete {Mouse2} Delete
[Hold ctrl] Draw on Background {ctrl} Background draw
[+/-/PGUP/PGDWN] Zoom canvas [PGUP/PGDWN] Zoom canvas
[Cursor keys] Move camera {Cursor keys} Move camera
]] ]]
local menuVisible = false
local textEnabled = false local textEnabled = false
local textEntry = "" local textEntry = ""
function ui.keyreleased(key, _) 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 if key == "return" and not love.keyboard.isDown("lalt") then
textEnabled = true textEnabled = true
love.keyboard.setTextInput(true) love.keyboard.setTextInput(true)
@ -44,14 +87,14 @@ function ui.keyreleased(key, _)
love.visible = function(visible) love.visible = function(visible)
if visible then if visible then
love.update = function(dt) love.update = function(dt)
gameloop.networkSync(dt) levelloop.networkSync(dt)
drawing.input(dt) drawing.input(dt)
physics.update(dt) physics.update(dt)
camera.update(dt) camera.update(dt)
end end
else else
love.update = function(dt) love.update = function(dt)
gameloop.networkSync(dt) levelloop.networkSync(dt)
physics.update(dt) physics.update(dt)
end end
end end
@ -61,14 +104,14 @@ function ui.keyreleased(key, _)
if key == "return" and not love.keyboard.isDown("lalt") then if key == "return" and not love.keyboard.isDown("lalt") then
textEnabled = false textEnabled = false
if textEntry ~= "" then if textEntry ~= "" then
gameloop.networkSend(unit("chatMessage", textEntry)) levelloop.remoteCall("chatMessage", textEntry)
textEntry = "" textEntry = ""
end end
ui.draw(window.x, window.y) ui.draw(window.x, window.y)
love.keyboard.setTextInput(false) love.keyboard.setTextInput(false)
love.keyreleased = gameloop.keyreleased love.keyreleased = levelloop.keyreleased
love.keypressed = gameloop.keypressed love.keypressed = levelloop.keypressed
love.update = gameloop.normalVisible love.update = levelloop.normalVisible
elseif key == "backspace" then elseif key == "backspace" then
textEntry = string.sub(textEntry, 0, #textEntry -1) textEntry = string.sub(textEntry, 0, #textEntry -1)
ui.draw(window.x, window.y) ui.draw(window.x, window.y)
@ -76,8 +119,8 @@ function ui.keyreleased(key, _)
end end
love.keypressed = function(key, _) return end love.keypressed = function(key, _) return end
else else
love.keyreleased = gameloop.keyreleased love.keyreleased = levelloop.keyreleased
love.keypressed = gameloop.keypressed love.keypressed = levelloop.keypressed
end end
end end
@ -97,11 +140,11 @@ function ui.chatEntry()
fill = "fill", fill = "fill",
color = { r = .1, g = .1, b = .1, .7 } color = { r = .1, g = .1, b = .1, .7 }
}, },
{name = "frame", {name = "frame",
{name = "label", {name = "label",
text = passedEntry text = passedEntry
}
} }
}
} }
return chatEntry return chatEntry
end end
@ -125,22 +168,22 @@ function ui.addChatEntry(user, message)
end end
local entry = {name = "rotate", local entry = {name = "rotate",
rotation = 0, -- OKAY DIEGO >:( rotation = 0, -- OKAY DIEGO >:(
{name = "copySize", {name = "copySize",
{name = "rect", {name = "rect",
fill = "fill", fill = "fill",
color = { r = .1, g = .1, b = .1, a = .6 } color = { r = .1, g = .1, b = .1, a = .6 }
},
{name = "vertical",
{name = "label",
text = chatText,
font = fonts.smallFont
}, },
{name = "vertical", {name = "spacer",
{name = "label", height = 2
text = chatText,
font = ui.smallFont
},
{name = "spacer",
height = 2
}
} }
} }
} }
}
table.insert(chatentries, entry) table.insert(chatentries, entry)
ui.draw(window.x, window.y) ui.draw(window.x, window.y)
end end
@ -153,18 +196,16 @@ function ui.loadTextures(directory, container)
for i, file in pairs(entries) do for i, file in pairs(entries) do
local path = directory .."/".. file local path = directory .."/".. file
local entry = love.filesystem.getInfo(path) local entry = love.filesystem.getInfo(path)
if entry.type == "directory" then if entry.type == "directory" and file ~= ".git" then
container[file] = {} container[file] = {}
container[file] = ui.loadTextures(path, container[file]) container[file] = ui.loadTextures(path, container[file])
elseif entry.type == "file" or entry.type == "symlink" then elseif entry.type == "file" or entry.type == "symlink" then
if file ~= "license.txt" then local status, imageData = pcall(love.image.newImageData, path);
local imageData = love.image.newImageData(path) if status then
local hash = love.data.hash("sha512", imageData:getString()) local hash = love.data.hash("sha512", imageData:getString())
gameloop.textures[hash] = {data = imageData} levelloop.textures[hash] = {data = imageData}
container[file] = hash container[file] = hash
end end
elseif entry.type == "other" then
error("Can't figure out what " .. path .. " is!")
end end
end end
return container return container
@ -172,31 +213,29 @@ end
function textureEntry(hash) function textureEntry(hash)
local texture = gameloop.textures[hash] local texture = levelloop.textures[hash]
assert(texture, "No texture found for textureentry?") assert(texture, "No texture found for textureentry?")
assert(texture.data, "No imagedata found for textureentry?") assert(texture.data, "No imagedata found for textureentry?")
if not texture.image then if not texture.image then
texture.image = love.graphics.newImage(texture.data) texture.image = love.graphics.newImage(texture.data)
end end
local width = texture.image:getWidth() local width = texture.image:getWidth()
local scale = 1 local height = texture.image:getHeight()
if width <= 16 then scale = 2 end if drawing and drawing.cursorHash and hash == drawing.cursorHash then
if width <= 8 then scale = 4 end return {name = "overlayRect",
if drawing and drawing.cursor and hash == drawing.cursor then
return{name = "overlayRect",
fill = "line", fill = "line",
{name = "drawHash", {name = "drawHash",
hash = hash, hash = hash,
scale = scale scale = ui.sidebarScale
} }
} }
else else
return{name = "cursorSelect", return {name = "cursorSelect", uiSelect = "GameUI",
identifier = hash, identifier = hash,
kind = "picker", kind = "picker",
{name = "drawHash", {name = "drawHash",
hash = hash, hash = hash,
scale = scale scale = ui.sidebarScale
} }
} }
end end
@ -204,52 +243,54 @@ end
function textureRun(entryTable, width) function textureRun(entryTable, width)
local textures = gameloop.textures local textures = levelloop.textures
local superEntry = {name = "vertical"} local entries = {name = "naiveGrid", width = width - 20}
local entries = {name = "horizontal"}
local entryTableSorted = {} 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 for iter, entry in pairs(entryTable) do
table.insert(entryTableSorted, {name = iter, entry = entry}) table.insert(entryTableSorted, {name = iter, entry = entry})
end end
table.sort(entryTableSorted, function(k1, k2) return k1.name < k2.name end) table.sort(entryTableSorted, function(k1, k2) return k1.name < k2.name end)
for iter, entry in pairs(entryTableSorted) do for iter, entry in pairs(entryTableSorted) do
if #entries > 2 then if not textures[entry.entry].image then
local NextX, nextY = layout.handle(entries, 0, 0, true) textures[entry.entry].image = love.graphics.newImage(textures[entry.entry].data)
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))
end end
table.insert(entries, textureEntry(entry.entry))
end end
if #entries > 1 then table.insert(superEntry, entries) end return entries
if #superEntry > 1 then
return superEntry
else
return entries
end
end end
function ui.blockDrawer(textureTree, w, h, container) function ui.blockDrawer(textureTree, w, h, container)
if not textureTree then return {name = "label", text = "Loading textures..."} end 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", 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 end
local textureTreeSorted = {}
for author, tree in pairs(textureTree) do 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" container.name = "vertical"
table.insert(container, {name = "label", text = pack}) table.insert(container, {name = "label", text = pack})
table.insert(container, table.insert(container,
{name = "color", {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) textureRun(subTree, w)
}) })
end end
@ -258,107 +299,111 @@ function ui.blockDrawer(textureTree, w, h, container)
end end
function ui.helpDrawer(w, h) function ui.menuDrawer(w, h)
return {name = "label", return {name = "horizontal",
text = ui.helptext {name = "vertical",
} {name = "button",
end identifier = "save",
uiSelect = "Menu",
buttonColor = "greenTranslucent",
function ui.testDrawer(w, h) text = "Save world"
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] "
}
}, },
{name = "cursorSelect", {name = "spacer",
height = 10,
},
{name = "button",
identifier = "disconnect", identifier = "disconnect",
kind = "button", uiSelect = "Menu",
{name = "label", buttonColor = "pinkTranslucent",
text = " Disconnect. " 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 }
}, },
{name = "horizontal", {name = "spacer",
labels, height = 25,
{name = "spacer", },
height = 40, {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 = "spacer",
{name = "rect", width = 80,
fill = "fill", },
color = { r = .1, g = .1, b = .1 } {name = "label",
}, text = ui.helptext,
tab font = fonts.normallFont
} }
} }
return drawer, math.max(hWidth, bWidth)
end end
function ui.draw(w, h) function ui.draw(w, h)
window.x, window.y = w, h window.x, window.y = w, h
color_push()
love.graphics.setCanvas(ui.buffer) love.graphics.setCanvas(ui.buffer)
uiState = {} layout.uiState.GameUI = {}
love.graphics.clear( ) love.graphics.clear( )
local sidebar, width = ui.tabDrawer(ui.textureTree, w, h) local blockDrawer = ui.blockDrawer(ui.textureTree, w, h)
layout.handle(sidebar, window.x -width, 0) blockMaxX, blockMaxY = layout.handle(blockDrawer, 0, 0, true)
ui.space = width local blockDrawerAdjusted = ui.blockDrawer(ui.textureTree, blockMaxX, blockMaxY)
if textEnabled then layout.handle(blockDrawerAdjusted, window.x - blockMaxX, 0)
local chatLayout = ui.chatEntry()
local sizex, sizey = layout.handle(chatLayout, 0, 0, true)
layout.handle(chatLayout, 0, window.y - sizey)
end
layout.handle(ui.chatlog(), 25, 90) 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() love.graphics.setCanvas()
end end
function ui.mousepressed(mousex, mousey) function ui.mousepressed(mousex, mousey)
local textures = gameloop.textures local textures = levelloop.textures
for i, v in ipairs(uiState) do if menuVisible then
if mousex >= v.x and mousex <= v.x2 then for i, v in ipairs(layout.uiState.Menu) do
if mousey >= v.y and mousey <= v.y2 then 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 if v.kind == "picker" then
assert(v.identifier, "No identifier in picker!") assert(v.identifier, "No identifier in picker!")
drawing.cursorHash = v.identifier drawing.cursorHash = v.identifier
@ -371,19 +416,23 @@ function ui.mousepressed(mousex, mousey)
ui.draw(window.x, window.y) ui.draw(window.x, window.y)
return return
elseif v.kind == "colorpicker" then elseif v.kind == "colorpicker" then
local scale = 1.0 / granularity print("Picker hit :D")
local red = math.floor((mousey - v.y) / v.pointsize) / scale local scale = ui.sidebarScale / granularity
local green = math.floor((mousex - v.x) / v.pointsize / (scale + 1)) / scale local red = math.floor((mousey - v.y) / v.pointSize) / scale
local blue = (math.floor((mousex - v.x) / v.pointsize) % (scale + 1)) / 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} drawing.color = {red, green, blue, 1}
ui.draw(window.x, window.y) ui.draw(window.x, window.y)
return return
elseif v.kind == "button" then elseif v.kind == "bwColorPicker" then
if v.identifier == "disconnect" then local scale = 1.0 / granularity
gameloop.networkSend(unit("playerLeave")) local saturation = math.floor((mousex - v.x) / v.pointSize) / scale
menu.init() drawing.color = {saturation, saturation, saturation, 1}
end ui.draw(window.x, window.y)
return
end end
return
end end
end 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) local chunk = love.filesystem.load(fileString)
chunk() chunk()
end end
dofile "lua/gameloop.lua"
dofile "lua/utils.lua"
dofile "lua/layout.lua" function love.load(args)
dofile "lua/init.lua" if args[1] == "--nwTest" then
dofile "lua/ui.lua" local format = require("shared.networkFormat")
dofile "lua/camera.lua" format.testEncoder()
dofile "lua/physics.lua" elseif args[1] == "--server" then
dofile "lua/drawing.lua" 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 #!/bin/sh
#adjust the next line... not sure how to figure this out from a shellscript, sorry :) # This mechanism is fairly unreliable, but it is the only "okay" way currently :/
cd ~/proj/poppy # realpath is also not part of any POSIX standard
cd $(dirname $(realpath "$0"))
if test $? -eq 1; then if test $? -eq 1; then
alert --stop "cant figure out where this script is" "Ill adjust it!" alert --stop "cant figure out where this script is" "Ill adjust it!"
exit exit
fi fi
uname=`uname -o` uname=`uname -o`
if test "$uname" = "Haiku" if test "$uname" = "Haiku"
then 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