Online multiplayer!
This commit is contained in:
parent
ae75f23025
commit
693584ae98
22
Makefile
22
Makefile
|
@ -2,7 +2,7 @@ mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
|||
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))
|
||||
|
||||
love:
|
||||
zip -9r bin/horsehorse.love ./*
|
||||
zip -9r bin/horseseahorse.love ./*
|
||||
|
||||
win32: love
|
||||
ifeq (,$(wildcard bin/love-win32.zip))
|
||||
|
@ -10,18 +10,18 @@ ifeq (,$(wildcard bin/love-win32.zip))
|
|||
https://github.com/love2d/love/releases/download/11.3/love-11.3-win32.zip
|
||||
endif
|
||||
unzip -d bin/ bin/love-win32.zip
|
||||
mv bin/love-*-win32 bin/horsehorse-win32
|
||||
rm bin/horsehorse-win32/changes.txt
|
||||
rm bin/horsehorse-win32/readme.txt
|
||||
rm bin/horsehorse-win32/lovec.exe
|
||||
cat bin/horsehorse.love >> bin/horsehorse-win32/love.exe
|
||||
mv bin/horsehorse-win32/love.exe bin/horsehorse-win32/HorseHorse.exe
|
||||
cp lib/bin-license.txt bin/horsehorse-win32/license.txt
|
||||
zip -9jr bin/horsehorse-win32.zip bin/horsehorse-win32
|
||||
rm -rf bin/horsehorse-win32
|
||||
mv bin/love-*-win32 bin/horseseahorse-win32
|
||||
rm bin/horseseahorse-win32/changes.txt
|
||||
rm bin/horseseahorse-win32/readme.txt
|
||||
rm bin/horseseahorse-win32/lovec.exe
|
||||
cat bin/horseseahorse.love >> bin/horseseahorse-win32/love.exe
|
||||
mv bin/horseseahorse-win32/love.exe bin/horseseahorse-win32/HorseHorse.exe
|
||||
cp lib/bin-license.txt bin/horseseahorse-win32/license.txt
|
||||
zip -9jr bin/horseseahorse-win32.zip bin/horseseahorse-win32
|
||||
rm -rf bin/horseseahorse-win32
|
||||
|
||||
test: love
|
||||
love bin/horsehorse.love
|
||||
love bin/horseseahorse.love
|
||||
|
||||
clean:
|
||||
rm -rf ./bin/*
|
||||
|
|
34
README.txt
34
README.txt
|
@ -1,20 +1,38 @@
|
|||
HORSEHORSE
|
||||
HORSESEAHORSE
|
||||
================================================================================
|
||||
HorseHorse is an tank-control duel-ish game, where valiant non-horses are pitted
|
||||
against one another.
|
||||
HorseSeaHorse is an tank-control duel-ish game, where valiant non-horses are
|
||||
pitted against one another.
|
||||
Made for Librejam 2020-02, with the theme "SUBNAUTICAL."
|
||||
|
||||
|
||||
RUNNING
|
||||
--------------------------------------------------
|
||||
LINUX/UNIX/HAIKU
|
||||
--------------------
|
||||
Make sure love2d is installed with your package manager, and then:
|
||||
$ make test
|
||||
UNIX/Linux/Haiku
|
||||
----------------------------------------
|
||||
For UNIX/Linux/Haiku, if you have the "horseseahorse.love" file and love2d
|
||||
installed through your package manager, then run:
|
||||
$ love horseseahorse.love
|
||||
|
||||
WINDOWS
|
||||
----------------------------------------
|
||||
If you have "horseseahorse.exe", then just run it or whatever idk.
|
||||
|
||||
|
||||
"BUILDING"
|
||||
--------------------------------------------------
|
||||
LOVE FILE
|
||||
----------------------------------------
|
||||
$ mkdir bin/
|
||||
$ make
|
||||
|
||||
WINDOWS BINARY
|
||||
----------------------------------------
|
||||
$ mkdir bin/
|
||||
$ make win32
|
||||
|
||||
|
||||
BORING STUFF
|
||||
--------------------------------------------------
|
||||
Jaidyn Ann <jadedctrl@teknik.io>
|
||||
Code under GPLv3 (see COPYING.txt)
|
||||
Art assets all under the CC-BY 3.0
|
||||
Art assets all under the CC-BY 4.0
|
||||
|
|
2
conf.lua
2
conf.lua
|
@ -1,6 +1,6 @@
|
|||
function love.conf(t)
|
||||
t.window.resizable = true
|
||||
t.window.title = "HorseHorse"
|
||||
t.window.title = "HorseSeaHorse"
|
||||
t.window.vsync = 0
|
||||
t.window.width = 800
|
||||
t.window.height = 800
|
||||
|
|
327
main.lua
327
main.lua
|
@ -108,10 +108,7 @@ end
|
|||
|
||||
-- IN-GAME
|
||||
----------------------------------------
|
||||
Game = class("Game")
|
||||
|
||||
function game_load(lobbiests)
|
||||
game = Game:new(lobbiests)
|
||||
function game_load(game)
|
||||
game:install()
|
||||
end
|
||||
|
||||
|
@ -120,12 +117,12 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- Fighter player class
|
||||
----------------------------------------
|
||||
Fighter = class('Fighter')
|
||||
Player = class('Fighter')
|
||||
|
||||
function Fighter:initialize(game, x, y, character, swordType, swordSide)
|
||||
function Player:initialize(game, x, y, character, swordType, name)
|
||||
self.game = game
|
||||
self.name = name
|
||||
self.swordType = swordType or 'normal'
|
||||
self.swordSide = swordSide or 'top'
|
||||
self.character = character or math.random(1, table.maxn(CHARACTERS))
|
||||
|
||||
self.directionals = {}
|
||||
|
@ -134,10 +131,12 @@ function Fighter:initialize(game, x, y, character, swordType, swordSide)
|
|||
self:initBody(x, y)
|
||||
self:initSword()
|
||||
self:initShield()
|
||||
|
||||
self.id = math.random(1, 20000)
|
||||
end
|
||||
|
||||
|
||||
function Fighter:update(dt)
|
||||
function Player:update(dt)
|
||||
local dir = self.directionals
|
||||
|
||||
self:movement()
|
||||
|
@ -148,7 +147,7 @@ function Fighter:update(dt)
|
|||
end
|
||||
|
||||
|
||||
function Fighter:draw()
|
||||
function Player:draw()
|
||||
local x,y = self.body:getWorldPoints(self.body.shape:getPoints())
|
||||
|
||||
love.graphics.draw(CHARACTERS[self.character], x, y, self.body:getAngle(),
|
||||
|
@ -156,13 +155,56 @@ function Fighter:draw()
|
|||
end
|
||||
|
||||
|
||||
function Fighter:movement()
|
||||
function Player:movement()
|
||||
end
|
||||
|
||||
|
||||
function Fighter:initBody(x, y)
|
||||
function Player:toTable()
|
||||
local bx1,by1, bx2,by2, bx3,by3, bx4,by4 = self.body.shape:getPoints()
|
||||
local bodyVertices = {bx1,by1, bx2,by2, bx3,by3, bx4,by4}
|
||||
|
||||
local sx1,sy1, sx2,sy2, sx3,sy3, sx4,sy4 = self.sword.shape:getPoints()
|
||||
local swordVertices = {sx1,sy1, sy2,sy2, sx3,by3, sx4,by4}
|
||||
|
||||
local mx1,my1, mx2,my2, mx3,my3, mx4,my4 = self.shield.shape:getPoints()
|
||||
local shieldVertices = {mx1,my1, my2,my2, mx3,by3, mx4,by4}
|
||||
|
||||
return {["bodyBox"] = bodyVertices, ["bodyAngle"] = self.body:getAngle(),
|
||||
["swordBox"] = swordVertices, ["swordAngle"] = self.sword:getAngle(),
|
||||
["shieldBox"] = shieldVertices, ["shieldAngle"] = self.shield:getAngle(),
|
||||
["bodyX"] = self.body:getX(), ["bodyY"] = self.body:getY(),
|
||||
["swordX"] = self.sword:getX(), ["swordY"] = self.sword:getY(),
|
||||
["shieldX"] = self.shield:getX(), ["shieldY"] = self.shield:getY(),
|
||||
["character"] = self.character, ["name"] = self.name,
|
||||
["id"] = self.id}
|
||||
end
|
||||
|
||||
|
||||
function Player:applyTable(pTable)
|
||||
self.body.shape = love.physics.newPolygonShape(pTable["bodyBox"])
|
||||
self.body:setAngle(pTable["bodyAngle"])
|
||||
self.body:setX(pTable["bodyX"])
|
||||
self.body:setY(pTable["bodyY"])
|
||||
|
||||
self.sword.shape = love.physics.newPolygonShape(pTable["swordBox"])
|
||||
self.sword:setAngle(pTable["swordAngle"])
|
||||
self.sword:setX(pTable["swordX"])
|
||||
self.sword:setY(pTable["swordY"])
|
||||
|
||||
self.shield.shape = love.physics.newPolygonShape(pTable["shieldBox"])
|
||||
self.shield:setAngle(pTable["shieldAngle"])
|
||||
self.shield:setX(pTable["shieldX"])
|
||||
self.shield:setY(pTable["shieldY"])
|
||||
|
||||
self.character = pTable["character"]
|
||||
self.id = pTable["id"]
|
||||
self.name = pTable["name"]
|
||||
end
|
||||
|
||||
|
||||
function Player:initBody(x, y)
|
||||
self.body = self.game.world:newRectangleCollider(x, y, 16, 16);
|
||||
self.body:setCollisionClass('Fighter')
|
||||
self.body:setCollisionClass('Player')
|
||||
self.body:setObject(self)
|
||||
self.body:setAngularDamping(2)
|
||||
self.body:setLinearDamping(.5)
|
||||
|
@ -170,14 +212,14 @@ function Fighter:initBody(x, y)
|
|||
end
|
||||
|
||||
|
||||
function Fighter:initShield()
|
||||
function Player:initShield()
|
||||
self.shield = self.game.world:newRectangleCollider(0, 0, 20, 5);
|
||||
self.shield:setCollisionClass('Shield')
|
||||
self.shield:setObject(self)
|
||||
end
|
||||
|
||||
|
||||
function Fighter:initSword()
|
||||
function Player:initSword()
|
||||
self.swordLength = SWORDLENGTH
|
||||
|
||||
if (self.swordType == 'normal') then
|
||||
|
@ -191,7 +233,7 @@ function Fighter:initSword()
|
|||
end
|
||||
|
||||
|
||||
function Fighter:glueSwordAndShield()
|
||||
function Player:glueSwordAndShield()
|
||||
local x, y = self.body:getPosition()
|
||||
local angle = self.body:getAngle()
|
||||
|
||||
|
@ -214,9 +256,9 @@ function Fighter:glueSwordAndShield()
|
|||
end
|
||||
|
||||
|
||||
function Fighter:makePostSolve()
|
||||
function Player:makePostSolve()
|
||||
return function(col1, col2, contact)
|
||||
if (col1.collision_class == "Fighter"
|
||||
if (col1.collision_class == "Player"
|
||||
and col2.collision_class == "Sword")
|
||||
then
|
||||
-- print(col2.shape)
|
||||
|
@ -226,7 +268,7 @@ function Fighter:makePostSolve()
|
|||
end
|
||||
|
||||
|
||||
function Fighter:makeSwordPostSolve()
|
||||
function Player:makeSwordPostSolve()
|
||||
return function(col1, col2, contact)
|
||||
if (col1.collision_class == "Sword"
|
||||
and col2.collision_class == "Shield")
|
||||
|
@ -239,11 +281,11 @@ end
|
|||
|
||||
-- LocalPlayer for local players (ofc)
|
||||
----------------------------------------
|
||||
LocalPlayer = class("LocalPlayer", Fighter)
|
||||
LocalPlayer = class("LocalPlayer", Player)
|
||||
|
||||
function LocalPlayer:initialize(game, x, y, keymap, character, swordType, swordSide)
|
||||
function LocalPlayer:initialize(game, x, y, keymap, character, swordType, name)
|
||||
self.keymap = keymap or KEYMAPS[1]
|
||||
Fighter.initialize(self, game, x, y, character, swordType, swordSide)
|
||||
Player.initialize(self, game, x, y, character, swordType, name)
|
||||
end
|
||||
|
||||
|
||||
|
@ -308,41 +350,49 @@ function LocalPlayer:keyreleased(key)
|
|||
end
|
||||
|
||||
|
||||
-- LocalBot andddd for bots too
|
||||
-- NetPlayer for network players
|
||||
----------------------------------------
|
||||
LocalBot = class("LocalBot", Fighter)
|
||||
NetPlayer = class("NetPlayer", Player)
|
||||
|
||||
function LocalBot:initialize(x, y, character, swordType, swordSide)
|
||||
Fighter.initialize(self, x, y, character, swordType, swordSide)
|
||||
function NetPlayer:initialize(ptable, game)
|
||||
Player.initialize(self, game, 0, 0, 1, 'normal', "sldkfj")
|
||||
|
||||
self:applyTable(ptable)
|
||||
end
|
||||
|
||||
|
||||
-- HostPlayer for from-server players
|
||||
----------------------------------------
|
||||
HostPlayer = class("HostPlayer", NetPlayer)
|
||||
|
||||
function HostPlayer:intialize(ptable, game)
|
||||
NetPlayer.initialize(self, ptable, game)
|
||||
end
|
||||
|
||||
|
||||
-- ClientPlayer for from-client players
|
||||
----------------------------------------
|
||||
ClientPlayer = class("ClientPlayer", NetPlayer)
|
||||
|
||||
function ClientPlayer:intialize(ptable, game)
|
||||
NetPlayer.initialize(self, ptable, game)
|
||||
end
|
||||
|
||||
|
||||
-- GAME superclass for matches
|
||||
----------------------------------------
|
||||
Game = class("Game")
|
||||
|
||||
function Game:initialize(lobbiests)
|
||||
self.world = wind.newWorld(0, 0, true)
|
||||
self.world:addCollisionClass('Fighter')
|
||||
self.world:addCollisionClass('Player')
|
||||
self.world:addCollisionClass('Shield')
|
||||
self.world:addCollisionClass('Sword')
|
||||
|
||||
self.remotePlayers = {}
|
||||
self.localPlayers = {}
|
||||
self.remotePlayersN = 0
|
||||
self.localPlayersN = 0
|
||||
|
||||
for k,lobbiest in pairs(lobbiests) do
|
||||
if (lobbiest.class.name == "LocalLobbiest") then
|
||||
local i = self.localPlayersN + 1
|
||||
self.localPlayers[i] = LocalPlayer:new(self, 0 + i * 50, 0 + i * 50,
|
||||
KEYMAPS[i], lobbiest.character)
|
||||
self.localPlayersN = i
|
||||
end
|
||||
end
|
||||
|
||||
self.localFighters = self.localPlayers
|
||||
|
||||
self.fighters = self.localFighters
|
||||
|
||||
self:addLobbiests(lobbiests)
|
||||
end
|
||||
|
||||
|
||||
|
@ -358,24 +408,22 @@ end
|
|||
function Game:update(dt)
|
||||
self.world:update(dt)
|
||||
|
||||
for i, fighter in pairs(self.fighters) do
|
||||
fighter:update(dt)
|
||||
for k,player in pairs(self:players()) do
|
||||
player:update(dt)
|
||||
end
|
||||
-- local x, y = player.body:getPosition()
|
||||
-- camera:follow(x, y)
|
||||
end
|
||||
|
||||
|
||||
function Game:draw()
|
||||
self.world:draw()
|
||||
for i, fighter in pairs(self.fighters) do
|
||||
fighter:draw()
|
||||
for k,player in pairs(self:players()) do
|
||||
player:draw()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Game:keypressed(key)
|
||||
local dir = self.localFighters[1].directionals
|
||||
local dir = self.localPlayers[1].directionals
|
||||
|
||||
-- if a player presses the left key, then holds the right key, they should
|
||||
-- go right until they let go, then they should go left.
|
||||
|
@ -384,8 +432,16 @@ function Game:keypressed(key)
|
|||
elseif (key == "-" and camera.scale > .5) then
|
||||
camera.scale = camera.scale - .5
|
||||
|
||||
elseif (key == "t") then
|
||||
local chatbox = TextBox:new(10,770, 2, 99, nil, nil,
|
||||
function (text)
|
||||
self:sendChat(text)
|
||||
self:install()
|
||||
end)
|
||||
chatbox:install(false, drawFunction, nil, false)
|
||||
|
||||
elseif (key == "escape") then
|
||||
pause_load()
|
||||
menu_load(makeMainMenu())
|
||||
else
|
||||
for i, player in pairs(self.localPlayers) do
|
||||
player:keypressed(key)
|
||||
|
@ -401,6 +457,162 @@ function Game:keyreleased (key)
|
|||
end
|
||||
|
||||
|
||||
function Game:players()
|
||||
return self.localPlayers
|
||||
end
|
||||
|
||||
|
||||
function Game:sendChat(message)
|
||||
local author = "AGhost"
|
||||
if (self.localPlayersN > 0) then
|
||||
author = self.localPlayers[1].name
|
||||
end
|
||||
logMsg(author, message)
|
||||
return author,message
|
||||
end
|
||||
|
||||
|
||||
function Game:addLobbiests(localLobbiests)
|
||||
for k,lobbiest in pairs(localLobbiests) do
|
||||
local i = self.localPlayersN + 1
|
||||
self.localPlayers[i] = LocalPlayer:new(self, 0 + i * 50, 0 + i * 50,
|
||||
KEYMAPS[i], lobbiest.character, lobbiest.swordType, lobbiest.name)
|
||||
self.localPlayersN = i
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- NETGAME superclass for online matches
|
||||
----------------------------------------
|
||||
NetGame = class("NetGame", Game)
|
||||
|
||||
function NetGame:initialize(localLobbiests, sock)
|
||||
self.remotePlayers = {}
|
||||
self.remotePlayersN = 0
|
||||
self.sock = sock
|
||||
|
||||
Game.initialize(self, localLobbiests)
|
||||
self:sockCallbacks()
|
||||
end
|
||||
|
||||
|
||||
function NetGame:sockCallbacks()
|
||||
self.sock:on("newPlayers",
|
||||
function(playerTables, client)
|
||||
self:addNewPlayers(playerTables, client)
|
||||
end)
|
||||
self.sock:on("playerPing",
|
||||
function(playerTables, client)
|
||||
self:receivePlayers(playerTables, client)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function NetGame:update(dt)
|
||||
Game.update(self, dt)
|
||||
self:sendPlayers()
|
||||
self.sock:update()
|
||||
end
|
||||
|
||||
|
||||
function NetGame:players()
|
||||
local players = {}
|
||||
table.foreach(self.localPlayers,
|
||||
function(k, v) table.insert(players, v) end)
|
||||
table.foreach(self.remotePlayers,
|
||||
function(k, v) table.insert(players, v) end)
|
||||
return players
|
||||
end
|
||||
|
||||
|
||||
function NetGame:receivePlayers(playerTables, client)
|
||||
for i,ptable in pairs(playerTables) do
|
||||
local id = ptable["id"]
|
||||
if (self.remotePlayers[id] == nil) then
|
||||
self:addNewPlayer(ptable)
|
||||
end
|
||||
self.remotePlayers[id]:applyTable(ptable)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- HOSTGAME for server matches
|
||||
----------------------------------------
|
||||
HostGame = class("HostGame", NetGame)
|
||||
|
||||
function HostGame:initialize(localLobbiests, server)
|
||||
NetGame.initialize(self, localLobbiests, server)
|
||||
|
||||
self.sock:sendToAll("newGame", nil)
|
||||
end
|
||||
|
||||
|
||||
function HostGame:sockCallbacks()
|
||||
NetGame.sockCallbacks(self)
|
||||
end
|
||||
|
||||
|
||||
function HostGame:sendChat(message)
|
||||
local author,text = Game.sendChat(self, message)
|
||||
self.sock:sendToAll("chat", {["author"] = author, ["text"] = text})
|
||||
end
|
||||
|
||||
|
||||
function HostGame:addNewPlayer(ptable)
|
||||
self.remotePlayersN = self.remotePlayersN + 1
|
||||
self.remotePlayers[ptable["id"]] = ClientPlayer:new(ptable, self)
|
||||
end
|
||||
|
||||
|
||||
function HostGame:sendPlayers()
|
||||
local ptables = {}
|
||||
|
||||
for i,player in pairs(self.localPlayers) do
|
||||
table.insert(ptables, player:toTable())
|
||||
end
|
||||
|
||||
self.sock:sendToAll("playerPing", ptables)
|
||||
end
|
||||
|
||||
|
||||
function HostGame:receivePlayers(playerTables, client)
|
||||
NetGame.receivePlayers(self, playerTables)
|
||||
self.sock:sendToAllBut(client, "playerPing", playerTables)
|
||||
end
|
||||
|
||||
|
||||
-- CLIENTGAME for client-side matches
|
||||
----------------------------------------
|
||||
ClientGame = class("ClientGame", NetGame)
|
||||
|
||||
function ClientGame:initialize(localLobbiests, client)
|
||||
NetGame.initialize(self, localLobbiests, client)
|
||||
end
|
||||
|
||||
|
||||
function ClientGame:sendChat(message)
|
||||
local author,text = Game.sendChat(self, message)
|
||||
self.sock:send("chat", {["author"] = author, ["text"] = text})
|
||||
end
|
||||
|
||||
|
||||
function ClientGame:sendPlayers()
|
||||
local ptables = {}
|
||||
|
||||
for i,player in pairs(self.localPlayers) do
|
||||
table.insert(ptables, player:toTable())
|
||||
end
|
||||
|
||||
self.sock:send("playerPing", ptables)
|
||||
end
|
||||
|
||||
|
||||
function ClientGame:addNewPlayer(ptable)
|
||||
self.remotePlayersN = self.remotePlayersN + 1
|
||||
self.remotePlayers[ptable["id"]] = HostPlayer:new(ptable, self)
|
||||
end
|
||||
|
||||
|
||||
-- LOBBY superclass for pre-matches
|
||||
----------------------------------------
|
||||
Lobby = class("Lobby")
|
||||
|
@ -566,7 +778,7 @@ end
|
|||
|
||||
function LocalLobby:keypressed(key)
|
||||
if (key == "return" and self:lobbiestsN() > 1) then
|
||||
game_load(self:lobbiests())
|
||||
game_load(Game:new(self:lobbiests()))
|
||||
else
|
||||
Lobby.keypressed(self, key)
|
||||
end
|
||||
|
@ -634,6 +846,7 @@ function HostLobby:sockCallbacks()
|
|||
self.sock:on("connect",
|
||||
function (data, client)
|
||||
-- self:sendLobbiests()
|
||||
-- st
|
||||
end)
|
||||
|
||||
self.sock:on("disconnect",
|
||||
|
@ -643,6 +856,15 @@ function HostLobby:sockCallbacks()
|
|||
end
|
||||
|
||||
|
||||
function HostLobby:keypressed(key)
|
||||
if (key == "return" and self:lobbiestsN() > 1) then
|
||||
game_load(HostGame:new(self.localLobbiests, self.sock))
|
||||
else
|
||||
Lobby.keypressed(self, key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function HostLobby:toMainMenu()
|
||||
-- self.sock:destroy()
|
||||
-- Lobby.toMainMenu(self)
|
||||
|
@ -753,6 +975,11 @@ function ClientLobby:sockCallbacks()
|
|||
function (ignoredData)
|
||||
self.status = "Disconnected"
|
||||
end)
|
||||
|
||||
self.sock:on("newGame",
|
||||
function (data, client)
|
||||
game_load(ClientGame:new(self.localLobbiests, self.sock))
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
|
|
Ŝarĝante…
Reference in New Issue