This is the code for the C++ client.cpp
#include "pandaFramework.h"
#include "windowFramework.h"
#include "nodePath.h"
#include "queuedConnectionManager.h"
#include "queuedConnectionReader.h"
#include "connectionWriter.h"
#include "netDatagram.h"
#include "datagram.h"
#include "datagramIterator.h"
#include "genericAsyncTask.h"
#include "asyncTaskManager.h"
#include "keyboardButton.h"
#include "mouseWatcher.h"
#include "clockObject.h"
#include "ambientLight.h"
#include "directionalLight.h"
#include "cardMaker.h"
#include "lineSegs.h"
#include "animControlCollection.h"
#include "auto_bind.h"
#include "partBundle.h"
#include "partBundleNode.h"
#include "load_prc_file.h"
#include "pandaSystem.h"
#include "pnotify.h"
#include <cmath>
#include <map>
#include <string>
#include <memory>
static NotifyCategory* crCat() {
static NotifyCategory* c = Notify::ptr()->get_category(":ClientRepository");
return c;
}
static NotifyCategory* actorCat() {
static NotifyCategory* c = Notify::ptr()->get_category(":Actor");
return c;
}
static NotifyCategory* appCat() {
static NotifyCategory* c = Notify::ptr()->get_category(":App");
return c;
}
// Values from direct/distributed/MsgTypesCMU.py
static const uint16_t CLIENT_DISCONNECT_CMU = 9008;
static const uint16_t CLIENT_OBJECT_GENERATE_CMU = 9002;
static const uint16_t CLIENT_OBJECT_UPDATE_FIELD = 120;
static const uint16_t CLIENT_SET_INTEREST_CMU = 9009;
static const uint16_t SET_DOID_RANGE_CMU = 9001;
static const uint16_t OBJECT_GENERATE_CMU = 9003;
static const uint16_t OBJECT_DISABLE_CMU = 9005;
static const uint16_t OBJECT_DELETE_CMU = 9006;
static const uint16_t OBJECT_UPDATE_FIELD_CMU = 9004;
static const uint16_t OBJECT_SET_ZONE_CMU = 9010;
static const uint16_t REQUEST_GENERATES_CMU = 9007;
static const uint32_t ZONE_ID = 1;
static const std::string RALPH_MODEL = "ralph";
static const std::string RALPH_WALK = "ralph-walk";
static const uint16_t FIELD_SET_X = 8;
static const uint16_t FIELD_SET_Y = 9;
static const uint16_t FIELD_SET_Z = 10;
static const uint16_t FIELD_SET_H = 11;
static const uint16_t FIELD_SET_P = 12;
static const uint16_t FIELD_SET_R = 13;
static const uint16_t FIELD_SET_SM_POS = 34;
static const uint16_t FIELD_SET_SM_HPR = 35;
static const uint16_t FIELD_SET_SM_XYzh = 37;
static const uint16_t FIELD_LOOP = 43;
static const uint16_t FIELD_POSE = 44;
// Position encoding: int16 = float * 10
static const float POS_DIVISOR = 10.0f;
// Movement
static const float MOVE_SPEED = 5.0f;
static const float TURN_SPEED = 60.0f;
class ClientRepository;
class Actor {
public:
Actor() = default;
~Actor() {
if (!_cleaned) cleanup();
}
Actor(const Actor&) = delete;
Actor& operator=(const Actor&) = delete;
bool load(WindowFramework* window, PandaFramework* framework,
const std::string& modelPath,
const std::string& animPath,
const std::string& animAlias = "walk") {
_window = window;
_framework = framework;
_animAlias = animAlias;
_cleaned = false;
_modelNP = window->load_model(framework->get_models(), modelPath);
if (_modelNP.is_empty()) {
actorCat()->error() << "model '" << modelPath << "' not found on model-path\n";
return false;
}
_animNP = window->load_model(_modelNP, animPath);
if (_animNP.is_empty()) {
actorCat()->warning() << "anim '" << animPath << "' not found — actor will be static\n";
return true;
}
auto_bind(_modelNP.node(), _anims, ~0);
if (_anims.get_num_anims() > 0 && !_anims.find_anim(animAlias))
_anims.store_anim(_anims.get_anim(0), animAlias);
actorCat()->info() << "loaded '" << modelPath << "' with "
<< _anims.get_num_anims() << " animation(s)\n";
return true;
}
void loop(const std::string& animName, bool restart = true) {
if (_anims.get_num_anims() > 0) _anims.loop(animName, restart);
}
void stop(const std::string& animName = "") {
if (_anims.get_num_anims() == 0) return;
animName.empty() ? _anims.stop_all() : _anims.stop(animName);
}
void pose(const std::string& animName, int frame) {
if (_anims.get_num_anims() > 0) _anims.pose(animName, frame);
}
// mirrors Actor.cleanup()
void cleanup() {
if (_cleaned) return;
_cleaned = true;
stop(); // 1. stop(None)
_anims = AnimControlCollection(); // 2. clearPythonData()
if (!_animNP.is_empty()) { // 3. flush()
_animNP.detach_node();
_animNP = NodePath();
}
if (!_modelNP.is_empty()) { // 4. removeNode()
_modelNP.remove_node();
_modelNP = NodePath();
}
}
void reparentTo(NodePath parent) { if (!_modelNP.is_empty()) _modelNP.reparent_to(parent); }
void setPos(float x, float y, float z) { if (!_modelNP.is_empty()) _modelNP.set_pos(x, y, z); }
void setH(float h) { if (!_modelNP.is_empty()) _modelNP.set_h(h); }
void setColorScale(const LColor& c) { if (!_modelNP.is_empty()) _modelNP.set_color_scale(c); }
NodePath getNodePath() const { return _modelNP; }
bool isEmpty() const { return _modelNP.is_empty(); }
bool hasBoundAnims() const { return _anims.get_num_anims() > 0; }
private:
WindowFramework* _window = nullptr;
PandaFramework* _framework = nullptr;
NodePath _modelNP;
NodePath _animNP;
AnimControlCollection _anims;
std::string _animAlias;
bool _cleaned = false;
};
class DistributedSmoothActor {
public:
uint32_t doId = 0;
float x = 0, y = 0, h = 0;
bool isLocalAvatar = false;
ClientRepository* cr = nullptr;
DistributedSmoothActor() = default;
NodePath getNode() const { return _actor.getNodePath(); }
void generate(WindowFramework* window, PandaFramework* framework);
void announceGenerate();
void disable();
void setX(float v) { x = v; NodePath np = _actor.getNodePath(); if (!np.is_empty()) np.set_x(v); }
void setY(float v) { y = v; NodePath np = _actor.getNodePath(); if (!np.is_empty()) np.set_y(v); }
void setH(float v) { h = v; NodePath np = _actor.getNodePath(); if (!np.is_empty()) np.set_h(v); }
void dSetX(float v);
void dSetY(float v);
void dSetH(float v);
void startWalk();
void stopWalk();
private:
WindowFramework* _window = nullptr;
PandaFramework* _framework = nullptr;
Actor _actor;
bool _isMoving = false;
NodePath buildFallbackGeometry();
};
class ClientRepository {
public:
static const uint16_t CLASS_ID = 5; // DistributedSmoothActor class number
DistributedSmoothActor* myAvatar = nullptr;
ClientRepository(WindowFramework* window, PandaFramework* framework)
: _window(window), _framework(framework) {
_reader = new QueuedConnectionReader(&_manager, 0);
_writer = new ConnectionWriter(&_manager, 1);
// CMU ServerRepository uses 2-byte TCP framing
_reader->set_tcp_header_size(2);
_writer->set_tcp_header_size(2);
}
~ClientRepository() {
delete _reader;
delete _writer;
}
void connect(const std::string& host, int port) {
_conn = _manager.open_TCP_client_connection(host, port, 3000);
if (_conn) {
_reader->add_connection(_conn);
_connected = true;
crCat()->info() << "connected to " << host << ":" << port << "\n";
} else {
crCat()->error() << "failed to connect to " << host << ":" << port
<< " — is server.py running?\n";
}
}
bool isConnected() const { return _connected; }
uint32_t getMyDoId() const { return _myDoId; }
// DC: setSmXYZH(int16/10 x, int16/10 y, int16/10 z, int16/10 h, uint16 t)
void sendPosUpdate(float x, float y, float h) {
if (!_connected || _myDoId == 0) return;
uint16_t ts = static_cast<uint16_t>(
static_cast<uint32_t>(
ClockObject::get_global_clock()->get_real_time() * 100.0) & 0xFFFFu);
Datagram dg;
dg.add_uint16(CLIENT_OBJECT_UPDATE_FIELD);
dg.add_uint32(_myDoId);
dg.add_uint16(FIELD_SET_SM_XYzh);
_packPos(dg, x); _packPos(dg, y); _packPos(dg, 0.0f); _packPos(dg, h);
dg.add_uint16(ts);
_writer->send(dg, _conn);
}
void sendLoop(const std::string& animName) {
if (!_connected || _myDoId == 0) return;
Datagram dg;
dg.add_uint16(CLIENT_OBJECT_UPDATE_FIELD);
dg.add_uint32(_myDoId);
dg.add_uint16(FIELD_LOOP);
uint16_t len = static_cast<uint16_t>(animName.size());
dg.add_uint16(len);
dg.append_data(animName.data(), len);
_writer->send(dg, _conn);
}
void sendPose(const std::string& animName, int frame) {
if (!_connected || _myDoId == 0) return;
Datagram dg;
dg.add_uint16(CLIENT_OBJECT_UPDATE_FIELD);
dg.add_uint32(_myDoId);
dg.add_uint16(FIELD_POSE);
uint16_t len = static_cast<uint16_t>(animName.size());
dg.add_uint16(len);
dg.append_data(animName.data(), len);
dg.add_int16(static_cast<int16_t>(frame));
_writer->send(dg, _conn);
}
void sendSetLocation(uint32_t doId, uint32_t zoneId) {
Datagram dg;
dg.add_uint16(OBJECT_SET_ZONE_CMU);
dg.add_uint32(doId);
dg.add_uint32(zoneId);
_writer->send(dg, _conn);
}
void poll() {
if (!_connected) return;
while (_manager.reset_connection_available()) {
PT(Connection) lost;
_manager.get_reset_connection(lost);
if (lost == _conn) lostConnection();
_manager.close_connection(lost);
}
while (_reader->data_available()) {
NetDatagram ndg;
if (!_reader->get_data(ndg)) continue;
if (ndg.get_length() < 2) continue;
DatagramIterator dgi(ndg);
handleDatagram(dgi);
}
}
void sendDisconnect() {
if (!_connected)
return;
if (_myDoId != 0) {
Datagram dgDis;
dgDis.add_uint16(OBJECT_DISABLE_CMU);
dgDis.add_uint32(_myDoId);
_writer->send(dgDis, _conn);
}
Datagram dg;
dg.add_uint16(CLIENT_DISCONNECT_CMU);
_writer->send(dg, _conn);
_manager.close_connection(_conn);
_connected = false;
}
private:
WindowFramework* _window;
PandaFramework* _framework;
QueuedConnectionManager _manager;
QueuedConnectionReader* _reader;
ConnectionWriter* _writer;
PT(Connection) _conn;
bool _connected = false;
uint32_t _doIdBase = 0; // first id in our assigned range
uint32_t _doIdLast = 0; // one past the last id (exclusive)
uint32_t _myDoId = 0; // doId of our spawned avatar
std::map<uint32_t, std::unique_ptr<DistributedSmoothActor>> _avatars;
bool isLocalId(uint32_t doId) const {
return _doIdLast > 0 && doId >= _doIdBase && doId < _doIdLast;
}
static void _packPos(Datagram& dg, float v) { dg.add_int16(static_cast<int16_t>(v * POS_DIVISOR)); }
static float _unpackPos(DatagramIterator& dgi) { return dgi.get_int16() / POS_DIVISOR; }
void onConnected() {
crCat()->info() << "doIdBase=" << _doIdBase << " doIdLast=" << _doIdLast << "\n";
sendSetInterest(ZONE_ID);
createDistributedObject();
}
void sendSetInterest(uint32_t zoneId) {
Datagram dg;
dg.add_uint16(CLIENT_SET_INTEREST_CMU);
dg.add_uint32(zoneId);
_writer->send(dg, _conn);
}
// Send CLIENT_OBJECT_GENERATE_CMU and spawn our avatar locally.
// Wire: [uint32 zoneId][uint16 classId][uint32 doId][uint16 numOther=0]
// The server broadcasts OBJECT_GENERATE_CMU to other clients but NOT back
void createDistributedObject() {
if (!_connected) return;
_myDoId = _doIdBase;
Datagram dg;
dg.add_uint16(CLIENT_OBJECT_GENERATE_CMU);
dg.add_uint32(ZONE_ID);
dg.add_uint16(CLASS_ID);
dg.add_uint32(_myDoId);
dg.add_uint16(0); // numOther = 0
_writer->send(dg, _conn);
if (_avatars.count(_myDoId) == 0) {
auto actor = std::make_unique<DistributedSmoothActor>();
actor->doId = _myDoId;
actor->cr = this;
actor->isLocalAvatar = true;
actor->generate(_window, _framework);
actor->announceGenerate();
myAvatar = actor.get();
_avatars[_myDoId] = std::move(actor);
crCat()->info() << "local avatar spawned doId=" << _myDoId << "\n";
}
}
void lostConnection() {
crCat()->warning() << "lost connection to server\n";
_connected = false;
}
void handleDatagram(DatagramIterator& dgi) {
const uint16_t msgType = dgi.get_uint16();
switch (msgType) {
case SET_DOID_RANGE_CMU: _onSetDoIdRange(dgi); break;
case OBJECT_GENERATE_CMU: _onObjectGenerate(dgi); break;
case OBJECT_UPDATE_FIELD_CMU: _onObjectUpdate(dgi); break;
case OBJECT_DISABLE_CMU: _onObjectDisable(dgi); break;
case OBJECT_DELETE_CMU: _onObjectDisable(dgi); break;
case REQUEST_GENERATES_CMU: _resendGenerates(); break;
default: break;
}
}
// Wire: [uint32 doIdBase] [uint32 rangeCount]
void _onSetDoIdRange(DatagramIterator& dgi) {
_doIdBase = dgi.get_uint32();
_doIdLast = _doIdBase + dgi.get_uint32(); // second field is a COUNT
onConnected();
}
// Wire: [uint32 ownerBase] [uint32 zoneId] [uint16 classId] [uint32 doId]
// [uint16 numOther] [fieldId + value] × numOther
// DistributedSmoothActor has zero required fields; all arrive in numOther.
void _onObjectGenerate(DatagramIterator& dgi) {
uint32_t ownerBase = dgi.get_uint32();
uint32_t zoneId = dgi.get_uint32();
uint16_t classId = dgi.get_uint16();
uint32_t doId = dgi.get_uint32();
if (classId != CLASS_ID) return;
if (_avatars.count(doId)) return;
float x = 0.0f, y = 0.0f, h = 0.0f;
if (dgi.get_remaining_size() >= 2) {
uint16_t numOther = dgi.get_uint16();
for (uint16_t i = 0; i < numOther && dgi.get_remaining_size() >= 4; ++i) {
uint16_t fid = dgi.get_uint16();
if (fid == FIELD_SET_X) { x = _unpackPos(dgi); }
else if (fid == FIELD_SET_Y) { y = _unpackPos(dgi); }
else if (fid == FIELD_SET_H) { h = _unpackPos(dgi); }
else if (fid == FIELD_SET_Z ||
fid == FIELD_SET_P ||
fid == FIELD_SET_R) { _unpackPos(dgi); }
else break;
}
}
bool isOurs = isLocalId(doId) || (ownerBase == _doIdBase);
if (isOurs) _myDoId = doId;
auto actor = std::make_unique<DistributedSmoothActor>();
actor->doId = doId;
actor->cr = this;
actor->isLocalAvatar = isOurs;
actor->generate(_window, _framework);
actor->setX(x); actor->setY(y); actor->setH(h);
actor->announceGenerate();
if (isOurs) {
myAvatar = actor.get();
crCat()->info() << "our avatar spawned doId=" << doId << "\n";
} else {
crCat()->info() << "remote avatar spawned doId=" << doId << "\n";
}
_avatars[doId] = std::move(actor);
}
// Wire: [uint32 doId] repeated until end of datagram
void _onObjectDisable(DatagramIterator& dgi) {
while (dgi.get_remaining_size() >= 4) {
uint32_t doId = dgi.get_uint32();
auto it = _avatars.find(doId);
if (it != _avatars.end()) {
it->second->disable();
if (it->second.get() == myAvatar) myAvatar = nullptr;
_avatars.erase(it);
crCat()->info() << "avatar removed doId=" << doId << "\n";
}
}
}
// Wire: [uint32 senderId] [uint32 doId] [uint16 fieldId] [payload]
void _onObjectUpdate(DatagramIterator& dgi) {
(void)dgi.get_uint32(); // senderId — CMU prefix, must consume
uint32_t doId = dgi.get_uint32();
uint16_t fieldId = dgi.get_uint16();
if (doId == _myDoId) return; // ignore echoes of our own updates
auto it = _avatars.find(doId);
if (it == _avatars.end()) return;
DistributedSmoothActor* actor = it->second.get();
if (fieldId == FIELD_SET_X) {
actor->setX(_unpackPos(dgi));
} else if (fieldId == FIELD_SET_Y) {
actor->setY(_unpackPos(dgi));
} else if (fieldId == FIELD_SET_H) {
actor->setH(_unpackPos(dgi));
} else if (fieldId == FIELD_SET_SM_XYzh) {
// x, y, z, h (int16/10 each) + t (uint16 timestamp)
float x = _unpackPos(dgi), y = _unpackPos(dgi);
_unpackPos(dgi); // z — discard
float h = _unpackPos(dgi);
dgi.get_uint16(); // timestamp — discard
actor->setX(x); actor->setY(y); actor->setH(h);
} else if (fieldId == FIELD_SET_SM_POS) {
// x, y, z (int16/10 each) + t (uint16 timestamp)
float x = _unpackPos(dgi), y = _unpackPos(dgi);
_unpackPos(dgi); dgi.get_uint16();
actor->setX(x); actor->setY(y);
} else if (fieldId == FIELD_SET_SM_HPR) {
// h, p, r (int16/10 each) + t (uint16 timestamp)
float h = _unpackPos(dgi);
_unpackPos(dgi); _unpackPos(dgi); dgi.get_uint16();
actor->setH(h);
} else if (fieldId == FIELD_LOOP) {
uint16_t len = dgi.get_uint16(); dgi.skip_bytes(len);
actor->startWalk();
} else if (fieldId == FIELD_POSE) {
uint16_t len = dgi.get_uint16(); dgi.skip_bytes(len);
dgi.get_int16(); // frame
actor->stopWalk();
}
}
// Re-broadcast our objects so a newly joined client can discover us.
// Called in response to REQUEST_GENERATES_CMU (9007).
void _resendGenerates() {
for (auto& kv : _avatars) {
if (!isLocalId(kv.first)) continue;
Datagram dg;
dg.add_uint16(CLIENT_OBJECT_GENERATE_CMU);
dg.add_uint32(ZONE_ID);
dg.add_uint16(CLASS_ID);
dg.add_uint32(kv.first);
dg.add_uint16(0);
_writer->send(dg, _conn);
}
}
};
NodePath DistributedSmoothActor::buildFallbackGeometry() {
LColor col = isLocalAvatar ? LColor(0.4f, 0.6f, 1.0f, 1.0f)
: LColor(1.0f, 0.6f, 0.4f, 1.0f);
LineSegs ls("fallback");
ls.set_color(col); ls.set_thickness(4.0f);
ls.move_to(0, 0, 0); ls.draw_to(0, 0, 3);
ls.move_to(-1, 0, 1.5f); ls.draw_to(1, 0, 1.5f);
ls.move_to(0, -1, 1.5f); ls.draw_to(0, 1, 1.5f);
ls.move_to(0, 0, 3); ls.draw_to( 0.3f, 0.5f, 2.5f);
ls.move_to(0, 0, 3); ls.draw_to(-0.3f, 0.5f, 2.5f);
return NodePath(ls.create());
}
void DistributedSmoothActor::generate(WindowFramework* window, PandaFramework* framework) {
_window = window;
_framework = framework;
}
void DistributedSmoothActor::announceGenerate() {
LColor col = isLocalAvatar ? LColor(0.4f, 0.6f, 1.0f, 1.0f)
: LColor(1.0f, 0.6f, 0.4f, 1.0f);
bool loaded = _actor.load(_window, _framework, RALPH_MODEL, RALPH_WALK, "walk");
NodePath np;
if (loaded && !_actor.isEmpty()) {
_actor.setColorScale(col);
if (_actor.hasBoundAnims()) _actor.pose("walk", 5);
np = _actor.getNodePath();
} else {
actorCat()->warning() << "using fallback geometry for doId=" << doId << "\n";
np = buildFallbackGeometry();
}
np.reparent_to(_window->get_render());
np.set_pos(x, y, 0.0f);
np.set_h(h);
}
void DistributedSmoothActor::disable() {
_actor.cleanup();
}
void DistributedSmoothActor::dSetX(float v) { x = v; NodePath np = _actor.getNodePath(); if (!np.is_empty()) np.set_x(v); }
void DistributedSmoothActor::dSetY(float v) { y = v; NodePath np = _actor.getNodePath(); if (!np.is_empty()) np.set_y(v); }
void DistributedSmoothActor::dSetH(float v) { h = v; NodePath np = _actor.getNodePath(); if (!np.is_empty()) np.set_h(v); }
void DistributedSmoothActor::startWalk() {
if (_isMoving) return;
_isMoving = true;
_actor.loop("walk");
if (isLocalAvatar && cr) cr->sendLoop("walk");
}
void DistributedSmoothActor::stopWalk() {
if (!_isMoving) return;
_isMoving = false;
_actor.stop("walk");
_actor.pose("walk", 5);
if (isLocalAvatar && cr) cr->sendPose("walk", 5);
}
static PandaFramework* gFramework = nullptr;
static WindowFramework* gWindow = nullptr;
static ClientRepository* gCR = nullptr;
static MouseWatcher* gMouseWatcher = nullptr;
static void setupScene() {
NodePath mouseNP = gWindow->get_mouse();
if (!mouseNP.is_empty())
gMouseWatcher = DCAST(MouseWatcher, mouseNP.node());
NodePath cam = gWindow->get_camera_group();
cam.wrt_reparent_to(gWindow->get_render());
cam.set_pos(0.0f, -50.0f, 35.0f);
cam.look_at(LPoint3f(0.0f, 0.0f, 0.0f));
PT(AmbientLight) al = new AmbientLight("al");
al->set_color(LColor(0.5f, 0.5f, 0.5f, 1.0f));
gWindow->get_render().set_light(gWindow->get_render().attach_new_node(al));
PT(DirectionalLight) dl = new DirectionalLight("dl");
dl->set_color(LColor(0.8f, 0.8f, 0.5f, 1.0f));
NodePath dlnp = gWindow->get_render().attach_new_node(dl);
dlnp.set_hpr(45.0f, -45.0f, 0.0f);
gWindow->get_render().set_light(dlnp);
CardMaker cm("ground");
cm.set_frame(-50.0f, 50.0f, -50.0f, 50.0f);
NodePath ground = gWindow->get_render().attach_new_node(cm.generate());
ground.set_p(-90.0f);
ground.set_color(0.25f, 0.45f, 0.25f, 1.0f);
}
AsyncTask::DoneStatus taskNetworkPoll(GenericAsyncTask*, void*) {
gCR->poll();
return AsyncTask::DS_cont;
}
AsyncTask::DoneStatus taskMovement(GenericAsyncTask*, void*) {
DistributedSmoothActor* av = gCR->myAvatar;
if (!av || !gMouseWatcher) return AsyncTask::DS_cont;
float dt = static_cast<float>(ClockObject::get_global_clock()->get_dt());
bool moving = false;
if (gMouseWatcher->is_button_down(KeyboardButton::left()) ||
gMouseWatcher->is_button_down(KeyboardButton::ascii_key('a'))) {
av->dSetH(av->h + TURN_SPEED * dt); moving = true;
}
if (gMouseWatcher->is_button_down(KeyboardButton::right()) ||
gMouseWatcher->is_button_down(KeyboardButton::ascii_key('d'))) {
av->dSetH(av->h - TURN_SPEED * dt); moving = true;
}
if (gMouseWatcher->is_button_down(KeyboardButton::up()) ||
gMouseWatcher->is_button_down(KeyboardButton::ascii_key('w'))) {
float rad = av->h * static_cast<float>(MathNumbers::pi) / 180.0f;
av->dSetX(av->x - sinf(rad) * MOVE_SPEED * dt);
av->dSetY(av->y + cosf(rad) * MOVE_SPEED * dt);
moving = true;
}
if (gMouseWatcher->is_button_down(KeyboardButton::down()) ||
gMouseWatcher->is_button_down(KeyboardButton::ascii_key('s'))) {
float rad = av->h * static_cast<float>(MathNumbers::pi) / 180.0f;
av->dSetX(av->x + sinf(rad) * MOVE_SPEED * dt);
av->dSetY(av->y - cosf(rad) * MOVE_SPEED * dt);
moving = true;
}
if (moving) {
av->startWalk();
gCR->sendPosUpdate(av->x, av->y, av->h);
} else {
av->stopWalk();
}
return AsyncTask::DS_cont;
}
int main(int argc, char* argv[]) {
const std::string host = "127.0.0.1";
const int port = 4400;
load_prc_file_data("", "model-path /c/PATH_TO_YOUR_MODELS/models");
PandaFramework framework;
framework.open_framework(argc, argv);
framework.set_window_title("SimpleAvatar C++ Client");
gFramework = &framework;
gWindow = framework.open_window();
if (!gWindow) {
appCat()->error() << "could not open window\n";
return 1;
}
gWindow->enable_keyboard();
setupScene();
ClientRepository cr(gWindow, gFramework);
gCR = &cr;
cr.connect(host, port);
PT(GenericAsyncTask) netTask = new GenericAsyncTask("netPoll", taskNetworkPoll, nullptr);
netTask->set_sort(-40);
AsyncTaskManager::get_global_ptr()->add(netTask);
PT(GenericAsyncTask) moveTask = new GenericAsyncTask("moveTask", taskMovement, nullptr);
moveTask->set_sort(0);
AsyncTaskManager::get_global_ptr()->add(moveTask);
appCat()->info() << "Panda3D " << PandaSystem::get_version_string() << " — WASD / arrow keys to move\n";
framework.main_loop();
cr.sendDisconnect();
framework.close_framework();
return 0;
}
Obviously this is POC that distributed network can be used in C++ with a python host server.
YOU HAVE TO USE YOUR OWN PATHs FOR THE MODELS
For the server i used 06-simple-avatar