Compare commits
2 Commits
cc6c92a005
...
ebeae76cb0
| Author | SHA1 | Date | |
|---|---|---|---|
| ebeae76cb0 | |||
| 2cc96422a2 |
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Game
|
||||||
|
|
||||||
|
This is a little test bench for a multiplayer game. Not sure if it'll scale to MMO, but it's a great
|
||||||
|
practice run for learning how to build one. Uses C++ with Raylib for the client, and Go for the server.
|
||||||
|
|
||||||
|
## Client
|
||||||
|
|
||||||
|
A dead-simple client using C++ with Raylib. The goal is to compile it for Windows, Linux, and macOS.
|
||||||
|
|
||||||
|
## Server
|
||||||
|
|
||||||
|
Bare-bones UDP server using Go. Since speed is a primary concern, we'll likely leverage gnet at some point.
|
||||||
@ -20,4 +20,7 @@ clean:
|
|||||||
run: $(TARGET)
|
run: $(TARGET)
|
||||||
./$(TARGET)
|
./$(TARGET)
|
||||||
|
|
||||||
.PHONY: all clean run
|
packets:
|
||||||
|
cd .. && lua generate_packets.lua
|
||||||
|
|
||||||
|
.PHONY: all clean run packets
|
||||||
@ -31,9 +31,9 @@ void NetworkManager::startReceive() {
|
|||||||
|
|
||||||
void NetworkManager::processMessage(const uint8_t* data, std::size_t size) {
|
void NetworkManager::processMessage(const uint8_t* data, std::size_t size) {
|
||||||
if (size == 0) return;
|
if (size == 0) return;
|
||||||
|
|
||||||
auto msgType = static_cast<MessageType>(data[0]);
|
auto msgType = static_cast<MessageType>(data[0]);
|
||||||
|
|
||||||
switch (msgType) {
|
switch (msgType) {
|
||||||
case MessageType::Spawn:
|
case MessageType::Spawn:
|
||||||
handleSpawn(data, size);
|
handleSpawn(data, size);
|
||||||
@ -61,19 +61,19 @@ void NetworkManager::processMessage(const uint8_t* data, std::size_t size) {
|
|||||||
void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) {
|
void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) {
|
||||||
// Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
|
// Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
|
||||||
if (size < 18) return;
|
if (size < 18) return;
|
||||||
|
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
std::memcpy(&id, &data[1], sizeof(id));
|
std::memcpy(&id, &data[1], sizeof(id));
|
||||||
std::memcpy(&x, &data[5], sizeof(x));
|
std::memcpy(&x, &data[5], sizeof(x));
|
||||||
std::memcpy(&y, &data[9], sizeof(y));
|
std::memcpy(&y, &data[9], sizeof(y));
|
||||||
std::memcpy(&z, &data[13], sizeof(z));
|
std::memcpy(&z, &data[13], sizeof(z));
|
||||||
|
|
||||||
uint8_t colorLen = data[17];
|
uint8_t colorLen = data[17];
|
||||||
if (size >= 18 + colorLen) {
|
if (size >= 18 + colorLen) {
|
||||||
playerColor = std::string(reinterpret_cast<const char*>(&data[18]), colorLen);
|
playerColor = std::string(reinterpret_cast<const char*>(&data[18]), colorLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
playerID = id;
|
playerID = id;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(positionMutex);
|
std::lock_guard lock(positionMutex);
|
||||||
@ -85,14 +85,14 @@ void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) {
|
|||||||
|
|
||||||
void NetworkManager::handleUpdate(const uint8_t* data, std::size_t size) {
|
void NetworkManager::handleUpdate(const uint8_t* data, std::size_t size) {
|
||||||
if (size < 17) return;
|
if (size < 17) return;
|
||||||
|
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
std::memcpy(&id, &data[1], sizeof(id));
|
std::memcpy(&id, &data[1], sizeof(id));
|
||||||
std::memcpy(&x, &data[5], sizeof(x));
|
std::memcpy(&x, &data[5], sizeof(x));
|
||||||
std::memcpy(&y, &data[9], sizeof(y));
|
std::memcpy(&y, &data[9], sizeof(y));
|
||||||
std::memcpy(&z, &data[13], sizeof(z));
|
std::memcpy(&z, &data[13], sizeof(z));
|
||||||
|
|
||||||
if (id == playerID) {
|
if (id == playerID) {
|
||||||
std::lock_guard lock(positionMutex);
|
std::lock_guard lock(positionMutex);
|
||||||
serverPosition = {x, y, z};
|
serverPosition = {x, y, z};
|
||||||
@ -108,20 +108,20 @@ void NetworkManager::handleUpdate(const uint8_t* data, std::size_t size) {
|
|||||||
void NetworkManager::handlePlayerJoined(const uint8_t* data, std::size_t size) {
|
void NetworkManager::handlePlayerJoined(const uint8_t* data, std::size_t size) {
|
||||||
// Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
|
// Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
|
||||||
if (size < 18) return;
|
if (size < 18) return;
|
||||||
|
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
std::memcpy(&id, &data[1], sizeof(id));
|
std::memcpy(&id, &data[1], sizeof(id));
|
||||||
std::memcpy(&x, &data[5], sizeof(x));
|
std::memcpy(&x, &data[5], sizeof(x));
|
||||||
std::memcpy(&y, &data[9], sizeof(y));
|
std::memcpy(&y, &data[9], sizeof(y));
|
||||||
std::memcpy(&z, &data[13], sizeof(z));
|
std::memcpy(&z, &data[13], sizeof(z));
|
||||||
|
|
||||||
uint8_t colorLen = data[17];
|
uint8_t colorLen = data[17];
|
||||||
std::string color = "red";
|
std::string color = "red";
|
||||||
if (size >= 18 + colorLen) {
|
if (size >= 18 + colorLen) {
|
||||||
color = std::string(reinterpret_cast<const char*>(&data[18]), colorLen);
|
color = std::string(reinterpret_cast<const char*>(&data[18]), colorLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != playerID) {
|
if (id != playerID) {
|
||||||
std::lock_guard lock(remotePlayersMutex);
|
std::lock_guard lock(remotePlayersMutex);
|
||||||
remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())};
|
remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())};
|
||||||
@ -131,10 +131,10 @@ void NetworkManager::handlePlayerJoined(const uint8_t* data, std::size_t size) {
|
|||||||
|
|
||||||
void NetworkManager::handlePlayerLeft(const uint8_t* data, std::size_t size) {
|
void NetworkManager::handlePlayerLeft(const uint8_t* data, std::size_t size) {
|
||||||
if (size < 5) return;
|
if (size < 5) return;
|
||||||
|
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
std::memcpy(&id, &data[1], sizeof(id));
|
std::memcpy(&id, &data[1], sizeof(id));
|
||||||
|
|
||||||
std::lock_guard lock(remotePlayersMutex);
|
std::lock_guard lock(remotePlayersMutex);
|
||||||
remotePlayers.erase(id);
|
remotePlayers.erase(id);
|
||||||
std::cout << "Player " << id << " left\n";
|
std::cout << "Player " << id << " left\n";
|
||||||
@ -142,13 +142,13 @@ void NetworkManager::handlePlayerLeft(const uint8_t* data, std::size_t size) {
|
|||||||
|
|
||||||
void NetworkManager::handlePlayerList(const uint8_t* data, std::size_t size) {
|
void NetworkManager::handlePlayerList(const uint8_t* data, std::size_t size) {
|
||||||
if (size < 2) return;
|
if (size < 2) return;
|
||||||
|
|
||||||
uint8_t count = data[1];
|
uint8_t count = data[1];
|
||||||
size_t offset = 2;
|
size_t offset = 2;
|
||||||
|
|
||||||
std::lock_guard lock(remotePlayersMutex);
|
std::lock_guard lock(remotePlayersMutex);
|
||||||
remotePlayers.clear();
|
remotePlayers.clear();
|
||||||
|
|
||||||
for (uint8_t i = 0; i < count && offset + 17 < size; i++) {
|
for (uint8_t i = 0; i < count && offset + 17 < size; i++) {
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
@ -156,21 +156,21 @@ void NetworkManager::handlePlayerList(const uint8_t* data, std::size_t size) {
|
|||||||
std::memcpy(&x, &data[offset + 4], sizeof(x));
|
std::memcpy(&x, &data[offset + 4], sizeof(x));
|
||||||
std::memcpy(&y, &data[offset + 8], sizeof(y));
|
std::memcpy(&y, &data[offset + 8], sizeof(y));
|
||||||
std::memcpy(&z, &data[offset + 12], sizeof(z));
|
std::memcpy(&z, &data[offset + 12], sizeof(z));
|
||||||
|
|
||||||
uint8_t colorLen = data[offset + 16];
|
uint8_t colorLen = data[offset + 16];
|
||||||
std::string color = "red";
|
std::string color = "red";
|
||||||
|
|
||||||
if (offset + 17 + colorLen <= size) {
|
if (offset + 17 + colorLen <= size) {
|
||||||
color = std::string(reinterpret_cast<const char*>(&data[offset + 17]), colorLen);
|
color = std::string(reinterpret_cast<const char*>(&data[offset + 17]), colorLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += 17 + colorLen;
|
offset += 17 + colorLen;
|
||||||
|
|
||||||
if (id != playerID) {
|
if (id != playerID) {
|
||||||
remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())};
|
remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Received list of " << (int)count << " players\n";
|
std::cout << "Received list of " << (int)count << " players\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,30 +181,30 @@ void NetworkManager::sendLogin() {
|
|||||||
|
|
||||||
void NetworkManager::sendMove(float dx, float dy, float dz) {
|
void NetworkManager::sendMove(float dx, float dy, float dz) {
|
||||||
if (!connected) return;
|
if (!connected) return;
|
||||||
|
|
||||||
std::array<uint8_t, 17> msg{};
|
std::array<uint8_t, 17> msg{};
|
||||||
msg[0] = static_cast<uint8_t>(MessageType::Move);
|
msg[0] = static_cast<uint8_t>(MessageType::Move);
|
||||||
|
|
||||||
uint32_t id = playerID;
|
uint32_t id = playerID;
|
||||||
std::memcpy(&msg[1], &id, sizeof(id));
|
std::memcpy(&msg[1], &id, sizeof(id));
|
||||||
std::memcpy(&msg[5], &dx, sizeof(dx));
|
std::memcpy(&msg[5], &dx, sizeof(dx));
|
||||||
std::memcpy(&msg[9], &dy, sizeof(dy));
|
std::memcpy(&msg[9], &dy, sizeof(dy));
|
||||||
std::memcpy(&msg[13], &dz, sizeof(dz));
|
std::memcpy(&msg[13], &dz, sizeof(dz));
|
||||||
|
|
||||||
socket.send_to(buffer(msg), serverEndpoint);
|
socket.send_to(buffer(msg), serverEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkManager::sendColorChange(const std::string& newColor) {
|
void NetworkManager::sendColorChange(const std::string& newColor) {
|
||||||
if (!connected) return;
|
if (!connected) return;
|
||||||
|
|
||||||
std::vector<uint8_t> msg(6 + newColor.size());
|
std::vector<uint8_t> msg(6 + newColor.size());
|
||||||
msg[0] = static_cast<uint8_t>(MessageType::ChangeColor);
|
msg[0] = static_cast<uint8_t>(MessageType::ChangeColor);
|
||||||
|
|
||||||
uint32_t id = playerID;
|
uint32_t id = playerID;
|
||||||
std::memcpy(&msg[1], &id, sizeof(id));
|
std::memcpy(&msg[1], &id, sizeof(id));
|
||||||
msg[5] = static_cast<uint8_t>(newColor.size());
|
msg[5] = static_cast<uint8_t>(newColor.size());
|
||||||
std::memcpy(&msg[6], newColor.data(), newColor.size());
|
std::memcpy(&msg[6], newColor.data(), newColor.size());
|
||||||
|
|
||||||
socket.send_to(buffer(msg), serverEndpoint);
|
socket.send_to(buffer(msg), serverEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,14 +216,14 @@ Vector3 NetworkManager::getPosition() {
|
|||||||
void NetworkManager::handleColorChanged(const uint8_t* data, std::size_t size) {
|
void NetworkManager::handleColorChanged(const uint8_t* data, std::size_t size) {
|
||||||
// Message format: [type(1)][id(4)][colorLen(1)][color(colorLen)]
|
// Message format: [type(1)][id(4)][colorLen(1)][color(colorLen)]
|
||||||
if (size < 6) return;
|
if (size < 6) return;
|
||||||
|
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
std::memcpy(&id, &data[1], sizeof(id));
|
std::memcpy(&id, &data[1], sizeof(id));
|
||||||
|
|
||||||
uint8_t colorLen = data[5];
|
uint8_t colorLen = data[5];
|
||||||
if (size >= 6 + colorLen) {
|
if (size >= 6 + colorLen) {
|
||||||
std::string newColor(reinterpret_cast<const char*>(&data[6]), colorLen);
|
std::string newColor(reinterpret_cast<const char*>(&data[6]), colorLen);
|
||||||
|
|
||||||
if (id == playerID) {
|
if (id == playerID) {
|
||||||
playerColor = newColor;
|
playerColor = newColor;
|
||||||
std::cout << "Your color changed to " << newColor << "\n";
|
std::cout << "Your color changed to " << newColor << "\n";
|
||||||
@ -240,4 +240,4 @@ void NetworkManager::handleColorChanged(const uint8_t* data, std::size_t size) {
|
|||||||
std::unordered_map<uint32_t, RemotePlayer> NetworkManager::getRemotePlayers() {
|
std::unordered_map<uint32_t, RemotePlayer> NetworkManager::getRemotePlayers() {
|
||||||
std::lock_guard lock(remotePlayersMutex);
|
std::lock_guard lock(remotePlayersMutex);
|
||||||
return remotePlayers;
|
return remotePlayers;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,37 +36,37 @@ class NetworkManager {
|
|||||||
public:
|
public:
|
||||||
NetworkManager();
|
NetworkManager();
|
||||||
~NetworkManager();
|
~NetworkManager();
|
||||||
|
|
||||||
void sendLogin();
|
void sendLogin();
|
||||||
void sendMove(float dx, float dy, float dz);
|
void sendMove(float dx, float dy, float dz);
|
||||||
void sendColorChange(const std::string& newColor);
|
void sendColorChange(const std::string& newColor);
|
||||||
|
|
||||||
Vector3 getPosition();
|
Vector3 getPosition();
|
||||||
bool isConnected() const { return connected; }
|
bool isConnected() const { return connected; }
|
||||||
uint32_t getPlayerID() const { return playerID; }
|
uint32_t getPlayerID() const { return playerID; }
|
||||||
std::string getPlayerColor() const { return playerColor; }
|
std::string getPlayerColor() const { return playerColor; }
|
||||||
|
|
||||||
std::unordered_map<uint32_t, RemotePlayer> getRemotePlayers();
|
std::unordered_map<uint32_t, RemotePlayer> getRemotePlayers();
|
||||||
|
|
||||||
// Available colors for cycling
|
// Available colors for cycling
|
||||||
static const std::vector<std::string> AVAILABLE_COLORS;
|
static const std::vector<std::string> AVAILABLE_COLORS;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
io_context ioContext;
|
io_context ioContext;
|
||||||
udp::socket socket{ioContext};
|
udp::socket socket{ioContext};
|
||||||
udp::endpoint serverEndpoint;
|
udp::endpoint serverEndpoint;
|
||||||
std::thread ioThread;
|
std::thread ioThread;
|
||||||
std::array<uint8_t, 1024> recvBuffer;
|
std::array<uint8_t, 1024> recvBuffer;
|
||||||
|
|
||||||
std::atomic<uint32_t> playerID{0};
|
std::atomic<uint32_t> playerID{0};
|
||||||
std::string playerColor{"red"};
|
std::string playerColor{"red"};
|
||||||
std::mutex positionMutex;
|
std::mutex positionMutex;
|
||||||
Vector3 serverPosition{0, 0, 0};
|
Vector3 serverPosition{0, 0, 0};
|
||||||
std::atomic<bool> connected{false};
|
std::atomic<bool> connected{false};
|
||||||
|
|
||||||
std::mutex remotePlayersMutex;
|
std::mutex remotePlayersMutex;
|
||||||
std::unordered_map<uint32_t, RemotePlayer> remotePlayers;
|
std::unordered_map<uint32_t, RemotePlayer> remotePlayers;
|
||||||
|
|
||||||
void startReceive();
|
void startReceive();
|
||||||
void processMessage(const uint8_t* data, std::size_t size);
|
void processMessage(const uint8_t* data, std::size_t size);
|
||||||
void handleSpawn(const uint8_t* data, std::size_t size);
|
void handleSpawn(const uint8_t* data, std::size_t size);
|
||||||
@ -75,4 +75,4 @@ private:
|
|||||||
void handlePlayerLeft(const uint8_t* data, std::size_t size);
|
void handlePlayerLeft(const uint8_t* data, std::size_t size);
|
||||||
void handlePlayerList(const uint8_t* data, std::size_t size);
|
void handlePlayerList(const uint8_t* data, std::size_t size);
|
||||||
void handleColorChanged(const uint8_t* data, std::size_t size);
|
void handleColorChanged(const uint8_t* data, std::size_t size);
|
||||||
};
|
};
|
||||||
|
|||||||
198
generate_packets.php
Executable file
198
generate_packets.php
Executable file
@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate packet definitions for C++ client and Go server from packets.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load packet definitions from JSON file
|
||||||
|
*/
|
||||||
|
function load_packet_definitions($filename = 'packets.json') {
|
||||||
|
if (!file_exists($filename)) {
|
||||||
|
throw new Exception("Could not open $filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($filename);
|
||||||
|
$packets = json_decode($content, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new Exception("JSON decode error: " . json_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $packets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert PascalCase to SNAKE_CASE
|
||||||
|
*/
|
||||||
|
function to_snake_case($str) {
|
||||||
|
$result = preg_replace('/([A-Z])/', '_$1', $str);
|
||||||
|
return strtoupper(ltrim($result, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate C++ header file with packet definitions
|
||||||
|
*/
|
||||||
|
function generate_cpp_header($packets) {
|
||||||
|
$header = <<<CPP
|
||||||
|
// Auto-generated packet definitions from packets.json
|
||||||
|
// DO NOT EDIT MANUALLY
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
CPP;
|
||||||
|
|
||||||
|
// Generate enum
|
||||||
|
$header .= "enum class MessageType : uint8_t {\n";
|
||||||
|
|
||||||
|
// Sort keys for consistent output
|
||||||
|
$names = array_keys($packets['opcodes']);
|
||||||
|
sort($names);
|
||||||
|
|
||||||
|
foreach ($names as $i => $name) {
|
||||||
|
$packet = $packets['opcodes'][$name];
|
||||||
|
$header .= sprintf(" %s = %s", $name, $packet['id']);
|
||||||
|
if ($i < count($names) - 1) {
|
||||||
|
$header .= ",";
|
||||||
|
}
|
||||||
|
$header .= "\n";
|
||||||
|
}
|
||||||
|
$header .= "};\n\n";
|
||||||
|
|
||||||
|
// Generate packet structures
|
||||||
|
$header .= "// Packet structure definitions\n";
|
||||||
|
$header .= "namespace Packets {\n\n";
|
||||||
|
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$packet = $packets['opcodes'][$name];
|
||||||
|
if (!empty($packet['fields'])) {
|
||||||
|
$header .= sprintf("struct %s {\n", $name);
|
||||||
|
$header .= sprintf(" static constexpr MessageType TYPE = MessageType::%s;\n", $name);
|
||||||
|
|
||||||
|
foreach ($packet['fields'] as $field) {
|
||||||
|
$cpp_type = '';
|
||||||
|
|
||||||
|
switch ($field['type']) {
|
||||||
|
case 'string':
|
||||||
|
$cpp_type = 'std::string';
|
||||||
|
break;
|
||||||
|
case 'uint8':
|
||||||
|
$cpp_type = 'uint8_t';
|
||||||
|
break;
|
||||||
|
case 'uint32':
|
||||||
|
$cpp_type = 'uint32_t';
|
||||||
|
break;
|
||||||
|
case 'float32':
|
||||||
|
$cpp_type = 'float';
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
$header .= sprintf(" // Array field: %s - requires custom handling\n", $field['name']);
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header .= sprintf(" %s %s;\n", $cpp_type, $field['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$header .= "};\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$header .= "} // namespace Packets\n";
|
||||||
|
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Go file with packet definitions
|
||||||
|
*/
|
||||||
|
function generate_go_file($packets) {
|
||||||
|
$go_file = <<<GO
|
||||||
|
// Auto-generated packet definitions from packets.json
|
||||||
|
// DO NOT EDIT MANUALLY
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Message type constants
|
||||||
|
const (
|
||||||
|
GO;
|
||||||
|
$go_file .= "\n";
|
||||||
|
|
||||||
|
// Sort keys for consistent output
|
||||||
|
$names = array_keys($packets['opcodes']);
|
||||||
|
sort($names);
|
||||||
|
|
||||||
|
// Generate constants
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$packet = $packets['opcodes'][$name];
|
||||||
|
$const_name = 'MSG_' . to_snake_case($name);
|
||||||
|
$go_file .= sprintf("\t%s = %s\n", $const_name, $packet['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$go_file .= ")\n\n";
|
||||||
|
|
||||||
|
// Generate packet structures
|
||||||
|
$go_file .= "// Packet structure definitions\n\n";
|
||||||
|
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$packet = $packets['opcodes'][$name];
|
||||||
|
if (!empty($packet['fields'])) {
|
||||||
|
$struct_name = "Packet $name";
|
||||||
|
$go_file .= sprintf("type %s struct {\n", $struct_name);
|
||||||
|
|
||||||
|
foreach ($packet['fields'] as $field) {
|
||||||
|
$field_name = ucfirst($field['name']);
|
||||||
|
$go_type = '';
|
||||||
|
|
||||||
|
switch ($field['type']) {
|
||||||
|
case 'string':
|
||||||
|
$go_type = 'string';
|
||||||
|
break;
|
||||||
|
case 'uint8':
|
||||||
|
$go_type = 'uint8';
|
||||||
|
break;
|
||||||
|
case 'uint32':
|
||||||
|
$go_type = 'uint32';
|
||||||
|
break;
|
||||||
|
case 'float32':
|
||||||
|
$go_type = 'float32';
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
$go_file .= sprintf("\t// Array field: %s - requires custom handling\n", $field_name);
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$go_file .= sprintf("\t%s %s\n", $field_name, $go_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
$go_file .= "}\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $go_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load packet definitions
|
||||||
|
$packets = load_packet_definitions();
|
||||||
|
|
||||||
|
// Generate C++ header
|
||||||
|
$cpp_header = generate_cpp_header($packets);
|
||||||
|
$cpp_path = 'client/net/PacketDefinitions.hpp';
|
||||||
|
file_put_contents($cpp_path, $cpp_header);
|
||||||
|
echo "Generated $cpp_path\n";
|
||||||
|
|
||||||
|
// Generate Go file
|
||||||
|
$go_file = generate_go_file($packets);
|
||||||
|
$go_path = 'server/packet_definitions.go';
|
||||||
|
file_put_contents($go_path, $go_file);
|
||||||
|
echo "Generated $go_path\n";
|
||||||
|
|
||||||
|
echo sprintf("Successfully generated packet definitions for protocol version %s\n", $packets['version']);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage() . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
96
packets.json
Normal file
96
packets.json
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"opcodes": {
|
||||||
|
"Login": {
|
||||||
|
"id": "0x01",
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"Position": {
|
||||||
|
"id": "0x02",
|
||||||
|
"fields": [
|
||||||
|
{"name": "x", "type": "float32"},
|
||||||
|
{"name": "y", "type": "float32"},
|
||||||
|
{"name": "z", "type": "float32"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Spawn": {
|
||||||
|
"id": "0x03",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "x", "type": "float32"},
|
||||||
|
{"name": "y", "type": "float32"},
|
||||||
|
{"name": "z", "type": "float32"},
|
||||||
|
{"name": "color", "type": "string"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Move": {
|
||||||
|
"id": "0x04",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "dx", "type": "float32"},
|
||||||
|
{"name": "dy", "type": "float32"},
|
||||||
|
{"name": "dz", "type": "float32"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Update": {
|
||||||
|
"id": "0x05",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "x", "type": "float32"},
|
||||||
|
{"name": "y", "type": "float32"},
|
||||||
|
{"name": "z", "type": "float32"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PlayerJoined": {
|
||||||
|
"id": "0x06",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "x", "type": "float32"},
|
||||||
|
{"name": "y", "type": "float32"},
|
||||||
|
{"name": "z", "type": "float32"},
|
||||||
|
{"name": "color", "type": "string"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PlayerLeft": {
|
||||||
|
"id": "0x07",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PlayerList": {
|
||||||
|
"id": "0x08",
|
||||||
|
"fields": [
|
||||||
|
{"name": "count", "type": "uint8"},
|
||||||
|
{"name": "players", "type": "array", "itemType": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "x", "type": "float32"},
|
||||||
|
{"name": "y", "type": "float32"},
|
||||||
|
{"name": "z", "type": "float32"},
|
||||||
|
{"name": "color", "type": "string"}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ChangeColor": {
|
||||||
|
"id": "0x09",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "color", "type": "string"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ColorChanged": {
|
||||||
|
"id": "0x0A",
|
||||||
|
"fields": [
|
||||||
|
{"name": "playerId", "type": "uint32"},
|
||||||
|
{"name": "color", "type": "string"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"uint8": {"size": 1, "encoding": "little-endian"},
|
||||||
|
"uint32": {"size": 4, "encoding": "little-endian"},
|
||||||
|
"float32": {"size": 4, "encoding": "little-endian"},
|
||||||
|
"string": {"encoding": "length-prefixed", "lengthType": "uint8"}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user