You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
8.1 KiB
278 lines
8.1 KiB
4 years ago
|
|
||
|
#include "../MMO_Server/MMO_Common.h"
|
||
|
|
||
|
#define OLC_PGEX_TRANSFORMEDVIEW
|
||
|
#include "olcPGEX_TransformedView.h"
|
||
|
|
||
|
#include <unordered_map>
|
||
|
|
||
|
class MMOGame : public olc::PixelGameEngine, olc::net::client_interface<GameMsg>
|
||
|
{
|
||
|
public:
|
||
|
MMOGame()
|
||
|
{
|
||
|
sAppName = "MMO Client";
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
olc::TileTransformedView tv;
|
||
|
|
||
|
std::string sWorldMap =
|
||
|
"################################"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..........####...####.........#"
|
||
|
"#..........#.........#.........#"
|
||
|
"#..........#.........#.........#"
|
||
|
"#..........#.........#.........#"
|
||
|
"#..........##############......#"
|
||
|
"#..............................#"
|
||
|
"#..................#.#.#.#.....#"
|
||
|
"#..............................#"
|
||
|
"#..................#.#.#.#.....#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"#..............................#"
|
||
|
"################################";
|
||
|
|
||
|
olc::vi2d vWorldSize = { 32, 32 };
|
||
|
|
||
|
private:
|
||
|
std::unordered_map<uint32_t, sPlayerDescription> mapObjects;
|
||
|
uint32_t nPlayerID = 0;
|
||
|
sPlayerDescription descPlayer;
|
||
|
|
||
|
bool bWaitingForConnection = true;
|
||
|
|
||
|
public:
|
||
|
bool OnUserCreate() override
|
||
|
{
|
||
|
tv = olc::TileTransformedView({ ScreenWidth(), ScreenHeight() }, { 8, 8 });
|
||
|
|
||
|
//mapObjects[0].nUniqueID = 0;
|
||
|
//mapObjects[0].vPos = { 3.0f, 3.0f };
|
||
|
|
||
|
if (Connect("127.0.0.1", 60000))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool OnUserUpdate(float fElapsedTime) override
|
||
|
{
|
||
|
// Check for incoming network messages
|
||
|
if (IsConnected())
|
||
|
{
|
||
|
while (!Incoming().empty())
|
||
|
{
|
||
|
auto msg = Incoming().pop_front().msg;
|
||
|
|
||
|
switch (msg.header.id)
|
||
|
{
|
||
|
case(GameMsg::Client_Accepted):
|
||
|
{
|
||
|
std::cout << "Server accepted client - you're in!\n";
|
||
|
olc::net::message<GameMsg> msg;
|
||
|
msg.header.id = GameMsg::Client_RegisterWithServer;
|
||
|
descPlayer.vPos = { 3.0f, 3.0f };
|
||
|
msg << descPlayer;
|
||
|
Send(msg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case(GameMsg::Client_AssignID):
|
||
|
{
|
||
|
// Server is assigning us OUR id
|
||
|
msg >> nPlayerID;
|
||
|
std::cout << "Assigned Client ID = " << nPlayerID << "\n";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case(GameMsg::Game_AddPlayer):
|
||
|
{
|
||
|
sPlayerDescription desc;
|
||
|
msg >> desc;
|
||
|
mapObjects.insert_or_assign(desc.nUniqueID, desc);
|
||
|
|
||
|
if (desc.nUniqueID == nPlayerID)
|
||
|
{
|
||
|
// Now we exist in game world
|
||
|
bWaitingForConnection = false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case(GameMsg::Game_RemovePlayer):
|
||
|
{
|
||
|
uint32_t nRemovalID = 0;
|
||
|
msg >> nRemovalID;
|
||
|
mapObjects.erase(nRemovalID);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case(GameMsg::Game_UpdatePlayer):
|
||
|
{
|
||
|
sPlayerDescription desc;
|
||
|
msg >> desc;
|
||
|
mapObjects.insert_or_assign(desc.nUniqueID, desc);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bWaitingForConnection)
|
||
|
{
|
||
|
Clear(olc::DARK_BLUE);
|
||
|
DrawString({ 10,10 }, "Waiting To Connect...", olc::WHITE);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Control of Player Object
|
||
|
mapObjects[nPlayerID].vVel = { 0.0f, 0.0f };
|
||
|
if (GetKey(olc::Key::W).bHeld) mapObjects[nPlayerID].vVel += { 0.0f, -1.0f };
|
||
|
if (GetKey(olc::Key::S).bHeld) mapObjects[nPlayerID].vVel += { 0.0f, +1.0f };
|
||
|
if (GetKey(olc::Key::A).bHeld) mapObjects[nPlayerID].vVel += { -1.0f, 0.0f };
|
||
|
if (GetKey(olc::Key::D).bHeld) mapObjects[nPlayerID].vVel += { +1.0f, 0.0f };
|
||
|
|
||
|
if (mapObjects[nPlayerID].vVel.mag2() > 0)
|
||
|
mapObjects[nPlayerID].vVel = mapObjects[nPlayerID].vVel.norm() * 4.0f;
|
||
|
|
||
|
// Update objects locally
|
||
|
for (auto& object : mapObjects)
|
||
|
{
|
||
|
// Where will object be worst case?
|
||
|
olc::vf2d vPotentialPosition = object.second.vPos + object.second.vVel * fElapsedTime;
|
||
|
|
||
|
// Extract region of world cells that could have collision this frame
|
||
|
olc::vi2d vCurrentCell = object.second.vPos.floor();
|
||
|
olc::vi2d vTargetCell = vPotentialPosition;
|
||
|
olc::vi2d vAreaTL = (vCurrentCell.min(vTargetCell) - olc::vi2d(1, 1)).max({ 0,0 });
|
||
|
olc::vi2d vAreaBR = (vCurrentCell.max(vTargetCell) + olc::vi2d(1, 1)).min(vWorldSize);
|
||
|
|
||
|
// Iterate through each cell in test area
|
||
|
olc::vi2d vCell;
|
||
|
for (vCell.y = vAreaTL.y; vCell.y <= vAreaBR.y; vCell.y++)
|
||
|
{
|
||
|
for (vCell.x = vAreaTL.x; vCell.x <= vAreaBR.x; vCell.x++)
|
||
|
{
|
||
|
// Check if the cell is actually solid...
|
||
|
// olc::vf2d vCellMiddle = vCell.floor();
|
||
|
if (sWorldMap[vCell.y * vWorldSize.x + vCell.x] == '#')
|
||
|
{
|
||
|
// ...it is! So work out nearest point to future player position, around perimeter
|
||
|
// of cell rectangle. We can test the distance to this point to see if we have
|
||
|
// collided.
|
||
|
|
||
|
olc::vf2d vNearestPoint;
|
||
|
// Inspired by this (very clever btw)
|
||
|
// https://stackoverflow.com/questions/45370692/circle-rectangle-collision-response
|
||
|
vNearestPoint.x = std::max(float(vCell.x), std::min(vPotentialPosition.x, float(vCell.x + 1)));
|
||
|
vNearestPoint.y = std::max(float(vCell.y), std::min(vPotentialPosition.y, float(vCell.y + 1)));
|
||
|
|
||
|
// But modified to work :P
|
||
|
olc::vf2d vRayToNearest = vNearestPoint - vPotentialPosition;
|
||
|
float fOverlap = object.second.fRadius - vRayToNearest.mag();
|
||
|
if (std::isnan(fOverlap)) fOverlap = 0;// Thanks Dandistine!
|
||
|
|
||
|
// If overlap is positive, then a collision has occurred, so we displace backwards by the
|
||
|
// overlap amount. The potential position is then tested against other tiles in the area
|
||
|
// therefore "statically" resolving the collision
|
||
|
if (fOverlap > 0)
|
||
|
{
|
||
|
// Statically resolve the collision
|
||
|
vPotentialPosition = vPotentialPosition - vRayToNearest.norm() * fOverlap;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set the objects new position to the allowed potential position
|
||
|
object.second.vPos = vPotentialPosition;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Handle Pan & Zoom
|
||
|
if (GetMouse(2).bPressed) tv.StartPan(GetMousePos());
|
||
|
if (GetMouse(2).bHeld) tv.UpdatePan(GetMousePos());
|
||
|
if (GetMouse(2).bReleased) tv.EndPan(GetMousePos());
|
||
|
if (GetMouseWheel() > 0) tv.ZoomAtScreenPos(1.5f, GetMousePos());
|
||
|
if (GetMouseWheel() < 0) tv.ZoomAtScreenPos(0.75f, GetMousePos());
|
||
|
|
||
|
// Clear World
|
||
|
Clear(olc::BLACK);
|
||
|
|
||
|
// Draw World
|
||
|
olc::vi2d vTL = tv.GetTopLeftTile().max({ 0,0 });
|
||
|
olc::vi2d vBR = tv.GetBottomRightTile().min(vWorldSize);
|
||
|
olc::vi2d vTile;
|
||
|
for (vTile.y = vTL.y; vTile.y < vBR.y; vTile.y++)
|
||
|
for (vTile.x = vTL.x; vTile.x < vBR.x; vTile.x++)
|
||
|
{
|
||
|
if (sWorldMap[vTile.y * vWorldSize.x + vTile.x] == '#')
|
||
|
{
|
||
|
tv.DrawRect(vTile, { 1.0f, 1.0f });
|
||
|
tv.DrawRect(olc::vf2d(vTile) + olc::vf2d(0.1f, 0.1f), { 0.8f, 0.8f });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw World Objects
|
||
|
for (auto& object : mapObjects)
|
||
|
{
|
||
|
// Draw Boundary
|
||
|
tv.DrawCircle(object.second.vPos, object.second.fRadius);
|
||
|
|
||
|
// Draw Velocity
|
||
|
if (object.second.vVel.mag2() > 0)
|
||
|
tv.DrawLine(object.second.vPos, object.second.vPos + object.second.vVel.norm() * object.second.fRadius, olc::MAGENTA);
|
||
|
|
||
|
// Draw Name
|
||
|
olc::vi2d vNameSize = GetTextSizeProp("ID: " + std::to_string(object.first));
|
||
|
tv.DrawStringPropDecal(object.second.vPos - olc::vf2d{ vNameSize.x * 0.5f * 0.25f * 0.125f, -object.second.fRadius * 1.25f }, "ID: " + std::to_string(object.first), olc::BLUE, { 0.25f, 0.25f });
|
||
|
}
|
||
|
|
||
|
// Send player description
|
||
|
olc::net::message<GameMsg> msg;
|
||
|
msg.header.id = GameMsg::Game_UpdatePlayer;
|
||
|
msg << mapObjects[nPlayerID];
|
||
|
Send(msg);
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
int main()
|
||
|
{
|
||
|
MMOGame demo;
|
||
|
if (demo.Construct(480, 480, 1, 1))
|
||
|
demo.Start();
|
||
|
return 0;
|
||
|
}
|