Files
SLikeNet/DependentExtensions/IrrlichtDemo/slikenetstuff.cpp
2025-11-24 14:19:51 +05:30

529 lines
18 KiB
C++

/*
* Original work: Copyright (c) 2014, Oculus VR, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* RakNet License.txt file in the licenses directory of this source tree. An additional grant
* of patent rights can be found in the RakNet Patents.txt file in the same directory.
*
*
* Modified work: Copyright (c) 2016-2020, SLikeSoft UG (haftungsbeschränkt)
*
* This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
* license found in the license.txt file in the root directory of this source tree.
*/
#include "slikenetstuff.h"
#include "slikenet/NetworkIDManager.h"
#include "CDemo.h"
#include "slikenet/time.h"
#include "slikenet/GetTime.h"
#include "slikenet/SocketLayer.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"
using namespace SLNet;
RakPeerInterface *rakPeer;
NetworkIDManager *networkIDManager;
ReplicaManager3Irrlicht *irrlichtReplicaManager3;
NatPunchthroughClient *natPunchthroughClient;
CloudClient *cloudClient;
SLNet::FullyConnectedMesh2 *fullyConnectedMesh2;
PlayerReplica *playerReplica;
/*
class DebugBoxSceneNode : public scene::ISceneNode
{
public:
DebugBoxSceneNode(scene::ISceneNode* parent,
scene::ISceneManager* mgr,
s32 id = -1);
virtual const core::aabbox3d<f32>& getBoundingBox() const;
virtual void OnRegisterSceneNode();
virtual void render();
CDemo *demo;
};
DebugBoxSceneNode::DebugBoxSceneNode(
scene::ISceneNode* parent,
scene::ISceneManager* mgr,
s32 id)
: scene::ISceneNode(parent, mgr, id)
{
#ifdef _DEBUG
setDebugName("DebugBoxSceneNode");
#endif
setAutomaticCulling(scene::EAC_OFF);
}
const core::aabbox3d<f32>& DebugBoxSceneNode::getBoundingBox() const
{
return demo->GetSyndeyBoundingBox();
}
void DebugBoxSceneNode::OnRegisterSceneNode()
{
if (IsVisible)
demo->GetSceneManager()->registerNodeForRendering(this, scene::ESNRP_SOLID);
}
void DebugBoxSceneNode::render()
{
if (DebugDataVisible)
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
video::SMaterial m;
m.Lighting = false;
demo->GetDevice()->getVideoDriver()->setMaterial(m);
demo->GetDevice()->getVideoDriver()->draw3DBox(demo->GetSyndeyBoundingBox());
}
}
*/
DataStructures::List<PlayerReplica*> PlayerReplica::playerList;
// Take this many milliseconds to move the visible position to the real position
static const float INTERP_TIME_MS=100.0f;
void InstantiateRakNetClasses(void)
{
static const int MAX_PLAYERS=32;
static const unsigned short TCP_PORT=0;
static const SLNet::TimeMS UDP_SLEEP_TIMER=30;
// Basis of all UDP communications
rakPeer= SLNet::RakPeerInterface::GetInstance();
// Using fixed port so we can use AdvertiseSystem and connect on the LAN if the server is not available.
SLNet::SocketDescriptor sd(1234,0);
sd.socketFamily=AF_INET; // Only IPV4 supports broadcast on 255.255.255.255
while (IRNS2_Berkley::IsPortInUse(sd.port, sd.hostAddress, sd.socketFamily, SOCK_DGRAM)==true)
sd.port++;
// +1 is for the connection to the NAT punchthrough server
SLNET_VERIFY(rakPeer->Startup(MAX_PLAYERS+1,&sd,1) == SLNet::RAKNET_STARTED);
rakPeer->SetMaximumIncomingConnections(MAX_PLAYERS);
// Fast disconnect for easier testing of host migration
rakPeer->SetTimeoutTime(5000,UNASSIGNED_SYSTEM_ADDRESS);
// ReplicaManager3 replies on NetworkIDManager. It assigns numbers to objects so they can be looked up over the network
// It's a class in case you wanted to have multiple worlds, then you could have multiple instances of NetworkIDManager
networkIDManager=new NetworkIDManager;
// Automatically sends around new / deleted / changed game objects
irrlichtReplicaManager3=new ReplicaManager3Irrlicht;
irrlichtReplicaManager3->SetNetworkIDManager(networkIDManager);
rakPeer->AttachPlugin(irrlichtReplicaManager3);
// Automatically destroy connections, but don't create them so we have more control over when a system is considered ready to play
irrlichtReplicaManager3->SetAutoManageConnections(false,true);
// Create and register the network object that represents the player
playerReplica = new PlayerReplica;
irrlichtReplicaManager3->Reference(playerReplica);
// Lets you connect through routers
natPunchthroughClient=new NatPunchthroughClient;
rakPeer->AttachPlugin(natPunchthroughClient);
// Uploads game instance, basically client half of a directory server
// Server code is in NATCompleteServer sample
cloudClient=new CloudClient;
rakPeer->AttachPlugin(cloudClient);
fullyConnectedMesh2=new FullyConnectedMesh2;
fullyConnectedMesh2->SetAutoparticipateConnections(false);
fullyConnectedMesh2->SetConnectOnNewRemoteConnection(false, "");
rakPeer->AttachPlugin(fullyConnectedMesh2);
// Connect to the NAT punchthrough server
SLNET_VERIFY(rakPeer->Connect(DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_IP, DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_PORT,0,0) == CONNECTION_ATTEMPT_STARTED);
// Advertise ourselves on the lAN if the NAT punchthrough server is not available
//for (int i=0; i < 8; i++)
// rakPeer->AdvertiseSystem("255.255.255.255", 1234+i, 0,0,0);
}
void DeinitializeRakNetClasses(void)
{
// Shutdown so the server knows we stopped
rakPeer->Shutdown(100,0);
SLNet::RakPeerInterface::DestroyInstance(rakPeer);
delete networkIDManager;
delete irrlichtReplicaManager3;
delete natPunchthroughClient;
delete cloudClient;
delete fullyConnectedMesh2;
// ReplicaManager3 deletes all referenced objects, including this one
//playerReplica->PreDestruction(0);
//delete playerReplica;
}
BaseIrrlichtReplica::BaseIrrlichtReplica()
{
}
BaseIrrlichtReplica::~BaseIrrlichtReplica()
{
}
void BaseIrrlichtReplica::SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
{
// unused parameters
(void)destinationConnection;
constructionBitstream->Write(position);
}
bool BaseIrrlichtReplica::DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
{
// unused parameters
(void)sourceConnection;
constructionBitstream->Read(position);
return true;
}
RM3SerializationResult BaseIrrlichtReplica::Serialize(SLNet::SerializeParameters *serializeParameters)
{
// unused parameters
(void)serializeParameters;
return RM3SR_BROADCAST_IDENTICALLY;
}
void BaseIrrlichtReplica::Deserialize(SLNet::DeserializeParameters *deserializeParameters)
{
// unused parameters
(void)deserializeParameters;
}
void BaseIrrlichtReplica::Update(SLNet::TimeMS curTime)
{
// unused parameters
(void)curTime;
}
PlayerReplica::PlayerReplica()
{
model=0;
rotationDeltaPerMS=0.0f;
isMoving=false;
deathTimeout=0;
lastUpdate= SLNet::GetTimeMS();
playerList.Push(this,_FILE_AND_LINE_);
}
PlayerReplica::~PlayerReplica()
{
unsigned int index = playerList.GetIndexOf(this);
if (index != (unsigned int) -1)
playerList.RemoveAtIndexFast(index);
}
void PlayerReplica::WriteAllocationID(SLNet::Connection_RM3 *destinationConnection, SLNet::BitStream *allocationIdBitstream) const
{
// unused parameters
(void)destinationConnection;
allocationIdBitstream->Write(SLNet::RakString("PlayerReplica"));
}
void PlayerReplica::SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
{
BaseIrrlichtReplica::SerializeConstruction(constructionBitstream, destinationConnection);
constructionBitstream->Write(rotationAroundYAxis);
constructionBitstream->Write(playerName);
constructionBitstream->Write(IsDead());
}
bool PlayerReplica::DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
{
if (!BaseIrrlichtReplica::DeserializeConstruction(constructionBitstream, sourceConnection))
return false;
constructionBitstream->Read(rotationAroundYAxis);
constructionBitstream->Read(playerName);
constructionBitstream->Read(isDead);
return true;
}
void PlayerReplica::PostDeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
{
// unused parameters
(void)constructionBitstream;
(void)destinationConnection;
// Object was remotely created and all data loaded. Now we can make the object visible
scene::IAnimatedMesh* mesh = 0;
scene::ISceneManager *sm = demo->GetSceneManager();
mesh = sm->getMesh(IRRLICHT_MEDIA_PATH "sydney.md2");
model = sm->addAnimatedMeshSceneNode(mesh, 0);
// DebugBoxSceneNode * debugBox = new DebugBoxSceneNode(model,sm);
// debugBox->demo=demo;
// debugBox->setDebugDataVisible(true);
model->setPosition(position);
model->setRotation(core::vector3df(0, rotationAroundYAxis, 0));
model->setScale(core::vector3df(2,2,2));
model->setMD2Animation(scene::EMAT_STAND);
curAnim=scene::EMAT_STAND;
model->setMaterialTexture(0, demo->GetDevice()->getVideoDriver()->getTexture(IRRLICHT_MEDIA_PATH "sydney.bmp"));
model->setMaterialFlag(video::EMF_LIGHTING, true);
model->addShadowVolumeSceneNode();
model->setAutomaticCulling ( scene::EAC_BOX );
model->setVisible(true);
model->setAnimationEndCallback(this);
wchar_t playerNameWChar[1024];
mbstowcs_s(nullptr, playerNameWChar, playerName.C_String(), 1023);
scene::IBillboardSceneNode *bb = sm->addBillboardTextSceneNode(0, playerNameWChar, model);
bb->setSize(core::dimension2df(40,20));
bb->setPosition(core::vector3df(0,model->getBoundingBox().MaxEdge.Y+bb->getBoundingBox().MaxEdge.Y-bb->getBoundingBox().MinEdge.Y+5.0f,0));
bb->setColor(video::SColor(255,255,128,128), video::SColor(255,255,128,128));
}
void PlayerReplica::PreDestruction(SLNet::Connection_RM3 *sourceConnection)
{
// unused parameters
(void)sourceConnection;
if (model)
model->remove();
}
RM3SerializationResult PlayerReplica::Serialize(SLNet::SerializeParameters *serializeParameters)
{
BaseIrrlichtReplica::Serialize(serializeParameters);
serializeParameters->outputBitstream[0].Write(position);
serializeParameters->outputBitstream[0].Write(rotationAroundYAxis);
serializeParameters->outputBitstream[0].Write(isMoving);
serializeParameters->outputBitstream[0].Write(IsDead());
return RM3SR_BROADCAST_IDENTICALLY;
}
void PlayerReplica::Deserialize(SLNet::DeserializeParameters *deserializeParameters)
{
BaseIrrlichtReplica::Deserialize(deserializeParameters);
deserializeParameters->serializationBitstream[0].Read(position);
deserializeParameters->serializationBitstream[0].Read(rotationAroundYAxis);
deserializeParameters->serializationBitstream[0].Read(isMoving);
bool wasDead=isDead;
deserializeParameters->serializationBitstream[0].Read(isDead);
if (isDead==true && wasDead==false)
{
demo->PlayDeathSound(position);
}
core::vector3df positionOffset;
positionOffset=position-model->getPosition();
positionDeltaPerMS = positionOffset / INTERP_TIME_MS;
float rotationOffset;
rotationOffset=GetRotationDifference(rotationAroundYAxis,model->getRotation().Y);
rotationDeltaPerMS = rotationOffset / INTERP_TIME_MS;
interpEndTime = SLNet::GetTimeMS() + (SLNet::TimeMS) INTERP_TIME_MS;
}
void PlayerReplica::Update(SLNet::TimeMS curTime)
{
// Is a locally created object?
if (creatingSystemGUID==rakPeer->GetGuidFromSystemAddress(SLNet::UNASSIGNED_SYSTEM_ADDRESS))
{
// Local player has no mesh to interpolate
// Input our camera position as our player position
playerReplica->position=demo->GetSceneManager()->getActiveCamera()->getPosition()-irr::core::vector3df(0,CAMERA_HEIGHT,0);
playerReplica->rotationAroundYAxis=demo->GetSceneManager()->getActiveCamera()->getRotation().Y-90.0f;
isMoving=demo->IsMovementKeyDown();
// Ack, makes the screen messed up and the mouse move off the window
// Find another way to keep the dead player from moving
// demo->EnableInput(IsDead()==false);
return;
}
// Update interpolation
SLNet::TimeMS elapsed = curTime-lastUpdate;
if (elapsed<=1)
return;
if (elapsed>100)
elapsed=100;
lastUpdate=curTime;
irr::core::vector3df curPositionDelta = position-model->getPosition();
irr::core::vector3df interpThisTick = positionDeltaPerMS*(float) elapsed;
if (curTime < interpEndTime && interpThisTick.getLengthSQ() < curPositionDelta.getLengthSQ())
{
model->setPosition(model->getPosition()+positionDeltaPerMS*(float) elapsed);
}
else
{
model->setPosition(position);
}
float curRotationDelta = GetRotationDifference(rotationAroundYAxis,model->getRotation().Y);
float interpThisTickRotation = rotationDeltaPerMS*(float)elapsed;
if (curTime < interpEndTime && fabs(interpThisTickRotation) < fabs(curRotationDelta))
{
model->setRotation(model->getRotation()+core::vector3df(0,interpThisTickRotation,0));
}
else
{
model->setRotation(core::vector3df(0,rotationAroundYAxis,0));
}
if (isDead)
{
UpdateAnimation(scene::EMAT_DEATH_FALLBACK);
model->setLoopMode(false);
}
else if (curAnim!=scene::EMAT_ATTACK)
{
if (isMoving)
{
UpdateAnimation(scene::EMAT_RUN);
model->setLoopMode(true);
}
else
{
UpdateAnimation(scene::EMAT_STAND);
model->setLoopMode(true);
}
}
}
void PlayerReplica::UpdateAnimation(irr::scene::EMD2_ANIMATION_TYPE anim)
{
if (anim!=curAnim)
model->setMD2Animation(anim);
curAnim=anim;
}
float PlayerReplica::GetRotationDifference(float r1, float r2)
{
float diff = r1-r2;
while (diff>180.0f)
diff-=360.0f;
while (diff<-180.0f)
diff+=360.0f;
return diff;
}
void PlayerReplica::OnAnimationEnd(scene::IAnimatedMeshSceneNode* node)
{
// unused parameters
(void)node;
if (curAnim==scene::EMAT_ATTACK)
{
if (isMoving)
{
UpdateAnimation(scene::EMAT_RUN);
model->setLoopMode(true);
}
else
{
UpdateAnimation(scene::EMAT_STAND);
model->setLoopMode(true);
}
}
}
void PlayerReplica::PlayAttackAnimation(void)
{
if (isDead==false)
{
UpdateAnimation(scene::EMAT_ATTACK);
model->setLoopMode(false);
}
}
bool PlayerReplica::IsDead(void) const
{
return deathTimeout > SLNet::GetTimeMS();
}
BallReplica::BallReplica()
{
creationTime= SLNet::GetTimeMS();
}
BallReplica::~BallReplica()
{
}
void BallReplica::WriteAllocationID(SLNet::Connection_RM3 *destinationConnection, SLNet::BitStream *allocationIdBitstream) const
{
// unused parameters
(void)destinationConnection;
allocationIdBitstream->Write(SLNet::RakString("BallReplica"));
}
void BallReplica::SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
{
BaseIrrlichtReplica::SerializeConstruction(constructionBitstream, destinationConnection);
constructionBitstream->Write(shotDirection);
}
bool BallReplica::DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection)
{
if (!BaseIrrlichtReplica::DeserializeConstruction(constructionBitstream, sourceConnection))
return false;
constructionBitstream->Read(shotDirection);
return true;
}
void BallReplica::PostDeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection)
{
// unused parameters
(void)constructionBitstream;
(void)destinationConnection;
// Shot visible effect and BallReplica classes are not linked, but they update the same way, such that
// they are in the same spot all the time
demo->shootFromOrigin(position, shotDirection);
// Find the owner of this ball, and make them play the attack animation
unsigned int idx;
for (idx=0; idx < PlayerReplica::playerList.Size(); idx++)
{
if (PlayerReplica::playerList[idx]->creatingSystemGUID==creatingSystemGUID)
{
PlayerReplica::playerList[idx]->PlayAttackAnimation();
break;
}
}
}
void BallReplica::PreDestruction(SLNet::Connection_RM3 *sourceConnection)
{
// unused parameters
(void)sourceConnection;
// The system that shot this ball destroyed it, or disconnected
// Technically we should clear out the node visible effect too, but it's not important for now
}
RM3SerializationResult BallReplica::Serialize(SLNet::SerializeParameters *serializeParameters)
{
BaseIrrlichtReplica::Serialize(serializeParameters);
return RM3SR_BROADCAST_IDENTICALLY;
}
void BallReplica::Deserialize(SLNet::DeserializeParameters *deserializeParameters)
{
BaseIrrlichtReplica::Deserialize(deserializeParameters);
}
void BallReplica::Update(SLNet::TimeMS curTime)
{
// Is a locally created object?
if (creatingSystemGUID==rakPeer->GetGuidFromSystemAddress(SLNet::UNASSIGNED_SYSTEM_ADDRESS))
{
// Destroy if shot expired
if (curTime > shotLifetime)
{
// Destroy on network
BroadcastDestruction();
delete this;
return;
}
}
// Keep at the same position as the visible effect
// Deterministic, so no need to actually transmit position
// The variable position is the origin that the ball was created at. For the player, it is their actual position
SLNet::TimeMS elapsedTime;
// Due to ping variances and timestamp miscalculations, it's possible with very low pings to get a slightly negative time, so we have to check
if (curTime>=creationTime)
elapsedTime = curTime - creationTime;
else
elapsedTime=0;
irr::core::vector3df updatedPosition = position + shotDirection * (float) elapsedTime * SHOT_SPEED;
// See if the bullet hit us
if (creatingSystemGUID!=rakPeer->GetGuidFromSystemAddress(SLNet::UNASSIGNED_SYSTEM_ADDRESS))
{
if (playerReplica->IsDead()==false)
{
//float playerHalfHeight=demo->GetSyndeyBoundingBox().getExtent().Y/2;
irr::core::vector3df positionRelativeToCharacter = updatedPosition-playerReplica->position;//+core::vector3df(0,playerHalfHeight,0);
if (demo->GetSyndeyBoundingBox().isPointInside(positionRelativeToCharacter))
//if ((playerReplica->position+core::vector3df(0,playerHalfHeight,0)-updatedPosition).getLengthSQ() < BALL_DIAMETER*BALL_DIAMETER/4.0f)
{
// We're dead for 3 seconds
playerReplica->deathTimeout=curTime+3000;
}
}
}
}
SLNet::Replica3 *Connection_RM3Irrlicht::AllocReplica(SLNet::BitStream *allocationId, ReplicaManager3 *replicaManager3)
{
// unused parameters
(void)replicaManager3;
SLNet::RakString typeName; allocationId->Read(typeName);
if (typeName=="PlayerReplica") {BaseIrrlichtReplica *r = new PlayerReplica; r->demo=demo; return r;}
if (typeName=="BallReplica") {BaseIrrlichtReplica *r = new BallReplica; r->demo=demo; return r;}
return 0;
}