Skip to content

Commit

Permalink
Multiplayer
Browse files Browse the repository at this point in the history
  • Loading branch information
deathkiller committed Dec 15, 2024
1 parent 83d19d0 commit bdf0ebd
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 43 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,18 @@ jobs:
* ) exit 1 ;;
esac
echo '${{ secrets.ANDROID_KEYSTORE_FILE }}' | base64 --decode > ./_keystore.jks
mkdir ./_package/
$ANDROID_HOME/build-tools/34.0.0/zipalign -p 4 "./_build/android/app/build/outputs/apk/$buildDir/$filename" "./_package/Jazz2.apk"
$ANDROID_HOME/build-tools/34.0.0/apksigner sign --ks-key-alias Main --ks "./_keystore.jks" --ks-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" --key-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" "./_package/Jazz2.apk"
cp -f ./LICENSE ./_package/LICENSE
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
echo 'Pull requests are not signed!'
else
echo 'Signing APK file...'
echo '${{ secrets.ANDROID_KEYSTORE_FILE }}' | base64 --decode > ./_keystore.jks
$ANDROID_HOME/build-tools/34.0.0/apksigner sign --ks-key-alias Main --ks "./_keystore.jks" --ks-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" --key-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" "./_package/Jazz2.apk"
fi
- name: 'Upload Package'
uses: actions/upload-artifact@v4
with:
Expand Down
7 changes: 6 additions & 1 deletion Sources/Jazz2/Actors/Multiplayer/RemotePlayerOnServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ namespace Jazz2::Actors::Multiplayer
{
LevelExitingState lastState = _levelExiting;
bool success = PlayerOnServer::OnLevelChanging(initiator, exitType);

if (lastState == LevelExitingState::None) {
// Level changing just started, send the request to the player as WarpIn packet
static_cast<Jazz2::Multiplayer::MultiLevelHandler*>(_levelHandler)->HandlePlayerLevelChanging(this, exitType);
Expand All @@ -91,6 +91,11 @@ namespace Jazz2::Actors::Multiplayer

void RemotePlayerOnServer::SyncWithServer(const Vector2f& pos, const Vector2f& speed, bool isVisible, bool isFacingLeft, bool isActivelyPushing)
{
if (_health <= 0) {
// Don't sync dead players to avoid cheating
return;
}

Clock& c = nCine::clock();
std::int64_t now = c.now() * 1000 / c.frequency();

Expand Down
20 changes: 19 additions & 1 deletion Sources/Jazz2/Actors/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2688,9 +2688,11 @@ namespace Jazz2::Actors
_levelHandler->RollbackToCheckpoint(this);
} else {
// Respawn is delayed
_controllable = false;
_renderer.setDrawEnabled(false);

// TODO: Turn off collisions
SetState(ActorState::IsInvulnerable, true);
SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors, false);
}
} else {
_lives = 0;
Expand Down Expand Up @@ -3364,6 +3366,22 @@ namespace Jazz2::Actors
dest.Write(_weaponUpgradesCheckpoint, sizeof(_weaponUpgradesCheckpoint));
}

void Player::Respawn(const Vector2f& pos)
{
if ((GetState() & (ActorState::IsInvulnerable | ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors)) != ActorState::IsInvulnerable) {
return;
}

_health = _maxHealth;

MoveInstantly(pos, MoveType::Absolute | MoveType::Force);

_controllable = true;
_renderer.setDrawEnabled(true);
SetState(ActorState::IsInvulnerable, false);
SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors, true);
}

void Player::WarpToPosition(const Vector2f& pos, WarpFlags flags)
{
if ((flags & WarpFlags::Fast) == WarpFlags::Fast) {
Expand Down
1 change: 1 addition & 0 deletions Sources/Jazz2/Actors/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ namespace Jazz2::Actors
void InitializeFromStream(ILevelHandler* levelHandler, Stream& src, std::uint16_t version);
void SerializeResumableToStream(Stream& dest);

void Respawn(const Vector2f& pos);
virtual void WarpToPosition(const Vector2f& pos, WarpFlags flags);
void WarpToCheckpoint();
Modifier GetModifier() const;
Expand Down
3 changes: 3 additions & 0 deletions Sources/Jazz2/IRootController.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ namespace Jazz2
#if defined(WITH_MULTIPLAYER)
virtual bool ConnectToServer(const StringView address, std::uint16_t port) = 0;
virtual bool CreateServer(LevelInitialization&& levelInit, std::uint16_t port) = 0;

virtual StringView GetServerName() const = 0;
virtual void SetServerName(StringView value) = 0;
#endif

virtual Flags GetFlags() const = 0;
Expand Down
7 changes: 7 additions & 0 deletions Sources/Jazz2/Multiplayer/INetworkHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@
#include "Reason.h"
#include "../../Common.h"

#include <Containers/StringView.h>

using namespace Death::Containers;

namespace Jazz2::Multiplayer
{
class INetworkHandler
{
public:
virtual StringView GetServerName() const = 0;
virtual void SetServerName(StringView value) = 0;

virtual ConnectionResult OnPeerConnected(const Peer& peer, std::uint32_t clientData) = 0;
virtual void OnPeerDisconnected(const Peer& peer, Reason reason) = 0;
virtual void OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t* data, std::size_t dataLength) = 0;
Expand Down
165 changes: 161 additions & 4 deletions Sources/Jazz2/Multiplayer/MultiLevelHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@

#include <Utf8.h>
#include <Containers/StaticArray.h>
#include <Containers/StringConcatenable.h>
#include <Containers/StringUtils.h>
#include <IO/MemoryStream.h>
#include <IO/Compression/DeflateStream.h>

Expand All @@ -56,7 +58,7 @@ namespace Jazz2::Multiplayer
{
MultiLevelHandler::MultiLevelHandler(IRootController* root, NetworkManager* networkManager)
: LevelHandler(root), _gameMode(MultiplayerGameMode::Unknown), _networkManager(networkManager), _updateTimeLeft(1.0f),
_initialUpdateSent(false), _lastSpawnedActorId(-1), _seqNum(0), _seqNumWarped(0), _suppressRemoting(false),
_initialUpdateSent(false), _enableSpawning(true), _lastSpawnedActorId(-1), _seqNum(0), _seqNumWarped(0), _suppressRemoting(false),
_ignorePackets(false)
#if defined(DEATH_DEBUG) && defined(WITH_IMGUI)
, _plotIndex(0), _actorsMaxCount(0.0f), _actorsCount{}, _remoteActorsCount{}, _remotingActorsCount{},
Expand Down Expand Up @@ -427,6 +429,103 @@ namespace Jazz2::Multiplayer
}

if (_isServer) {
if (line.hasPrefix('/')) {
if (line.hasPrefix("/ban "_s)) {
// TODO: Implement /ban
} else if (line == "/info"_s) {
char infoBuffer[128];
formatString(infoBuffer, sizeof(infoBuffer), "Current level: %s/%s (\f[w:80]\f[c:#707070]%s\f[/c]\f[/w])", _episodeName.data(), _levelFileName.data(), GameModeToString(_gameMode).data());
_console->WriteLine(UI::MessageLevel::Info, infoBuffer);
formatString(infoBuffer, sizeof(infoBuffer), "Players: \f[w:80]\f[c:#707070]%zu\f[/c]\f[/w]/%zu", _peerDesc.size() + 1, NetworkManager::MaxPeerCount);
_console->WriteLine(UI::MessageLevel::Info, infoBuffer);
formatString(infoBuffer, sizeof(infoBuffer), "Server load: %i ms", (std::int32_t)(theApplication().GetFrameTimer().GetLastFrameDuration() * 1000.0f));
_console->WriteLine(UI::MessageLevel::Info, infoBuffer);
return true;
} else if (line.hasPrefix("/kick "_s)) {
// TODO: Implement /kick
} else if (line.hasPrefix("/set "_s)) {
auto [variableName, _, value] = line.exceptPrefix("/set "_s).trimmedPrefix().partition(' ');
if (variableName == "mode"_s) {
auto gameModeString = StringUtils::lowercase(value.trimmed());

MultiplayerGameMode gameMode;
if (gameModeString == "battle"_s || gameModeString == "b"_s) {
gameMode = MultiplayerGameMode::Battle;
} else if (gameModeString == "teambattle"_s || gameModeString == "tb"_s) {
gameMode = MultiplayerGameMode::TeamBattle;
} else if (gameModeString == "capturetheflag"_s || gameModeString == "ctf"_s) {
gameMode = MultiplayerGameMode::CaptureTheFlag;
} else if (gameModeString == "race"_s || gameModeString == "r"_s) {
gameMode = MultiplayerGameMode::Race;
} else if (gameModeString == "teamrace"_s || gameModeString == "tr"_s) {
gameMode = MultiplayerGameMode::TeamRace;
} else if (gameModeString == "treasurehunt"_s || gameModeString == "th"_s) {
gameMode = MultiplayerGameMode::TreasureHunt;
} else if (gameModeString == "cooperation"_s || gameModeString == "coop"_s || gameModeString == "c"_s) {
gameMode = MultiplayerGameMode::Cooperation;
} else {
return false;
}

if (SetGameMode(gameMode)) {
char infoBuffer[128];
formatString(infoBuffer, sizeof(infoBuffer), "Game mode set to \f[w:80]\f[c:#707070]%s\f[/c]\f[/w]", GameModeToString(_gameMode).data());
_console->WriteLine(UI::MessageLevel::Info, infoBuffer);
return true;
}
} else if (variableName == "level"_s) {
// TODO: Implement /set level
} else if (variableName == "name"_s) {
auto name = value.trimmed();
_root->SetServerName(name);

name = _root->GetServerName();
char infoBuffer[128];
if (!name.empty()) {
formatString(infoBuffer, sizeof(infoBuffer), "Server name set to \f[w:80]\f[c:#707070]%s\f[/c]\f[/w]", String::nullTerminatedView(name).data());
} else {
formatString(infoBuffer, sizeof(infoBuffer), "Server visibility to \f[w:80]\f[c:#707070]hidden\f[/c]\f[/w]");
}
_console->WriteLine(UI::MessageLevel::Info, infoBuffer);
return true;
} else if (variableName == "spawning"_s) {
auto boolValue = StringUtils::lowercase(value.trimmed());
if (boolValue == "false"_s || boolValue == "off"_s || boolValue == "0"_s) {
_enableSpawning = false;
} else if (boolValue == "true"_s || boolValue == "on"_s || boolValue == "1"_s) {
_enableSpawning = true;
} else {
return false;
}

char infoBuffer[128];
formatString(infoBuffer, sizeof(infoBuffer), "Spawning set to \f[w:80]\f[c:#707070]%s\f[/c]\f[/w]", _enableSpawning ? "Enabled" : "Disabled");
_console->WriteLine(UI::MessageLevel::Info, infoBuffer);
return true;
}
} else if (line.hasPrefix("/alert "_s)) {
StringView message = line.exceptPrefix("/alert "_s).trimmed();
if (!message.empty()) {
for (const auto& [otherPeer, otherPeerDesc] : _peerDesc) {
if (otherPeerDesc.State == PeerState::Unknown) {
continue;
}

MemoryStream packet(5 + message.size());
packet.WriteValue<std::uint8_t>((std::uint8_t)ServerPacketType::ShowAlert);
packet.WriteVariableUint32((std::uint32_t)message.size());
packet.Write(message.data(), (std::uint32_t)message.size());

// TODO: If it fail, it will release the packet which is wrong
_networkManager->SendToPeer(otherPeer, NetworkChannel::Main, packet);
}
}
}

return false;
}

// Chat message
for (const auto& [otherPeer, otherPeerDesc] : _peerDesc) {
if (otherPeerDesc.State == PeerState::Unknown) {
continue;
Expand All @@ -443,6 +542,12 @@ namespace Jazz2::Multiplayer
_networkManager->SendToPeer(otherPeer, NetworkChannel::Main, packet);
}
} else {
if (line.hasPrefix('/')) {
// Command are allowed only on server
return false;
}

// Chat message
MemoryStream packet(6 + line.size());
packet.WriteValue<std::uint8_t>((std::uint8_t)ClientPacketType::ChatMessage);
packet.WriteVariableUint32(_lastSpawnedActorId);
Expand Down Expand Up @@ -633,8 +738,31 @@ namespace Jazz2::Multiplayer

bool MultiLevelHandler::HandlePlayerDied(Actors::Player* player, Actors::ActorBase* collider)
{
// TODO: Remove this override
return LevelHandler::HandlePlayerDied(player, collider);
#if defined(WITH_ANGELSCRIPT)
if (_scripts != nullptr) {
_scripts->OnPlayerDied(player, collider);
}
#endif

// TODO
//return LevelHandler::HandlePlayerDied(player, collider);

if (_isServer && _enableSpawning) {
for (const auto& [peer, peerDesc] : _peerDesc) {
if (peerDesc.Player == player) {
MemoryStream packet(13);
packet.WriteValue<std::uint8_t>((std::uint8_t)ServerPacketType::PlayerRespawn);
packet.WriteVariableUint32(player->_playerIndex);
packet.WriteValue<std::int32_t>((std::int32_t)(player->_checkpointPos.X * 512.0f));
packet.WriteValue<std::int32_t>((std::int32_t)(player->_checkpointPos.Y * 512.0f));
_networkManager->SendToPeer(peer, NetworkChannel::Main, packet);
break;
}
}
return true;
} else {
return false;
}
}

void MultiLevelHandler::HandlePlayerLevelChanging(Actors::Player* player, ExitType exitType)
Expand Down Expand Up @@ -1615,6 +1743,21 @@ namespace Jazz2::Multiplayer
}, NCINE_CURRENT_FUNCTION);
return true;
}
case ServerPacketType::PlayerRespawn: {
MemoryStream packet(data + 1, dataLength - 1);
std::uint32_t playerIndex = packet.ReadVariableUint32();
if (_lastSpawnedActorId != playerIndex) {
LOGD("[MP] ServerPacketType::PlayerRespawn - received playerIndex %u instead of %u", playerIndex, _lastSpawnedActorId);
return true;
}

LOGD("[MP] ServerPacketType::PlayerRespawn - playerIndex: %u", playerIndex);

float posX = packet.ReadValue<std::int32_t>() / 512.0f;
float posY = packet.ReadValue<std::int32_t>() / 512.0f;
_players[0]->Respawn(Vector2f(posX, posY));
return true;
}
case ServerPacketType::PlayerMoveInstantly: {
MemoryStream packet(data + 1, dataLength - 1);
std::uint32_t playerIndex = packet.ReadVariableUint32();
Expand Down Expand Up @@ -1651,7 +1794,7 @@ namespace Jazz2::Multiplayer
MemoryStream packet(data + 1, dataLength - 1);
std::uint32_t playerIndex = packet.ReadVariableUint32();
if (_lastSpawnedActorId != playerIndex) {
LOGD("[MP] ServerPacketType::PlayerChangeWeapon - received playerIndex %u instead of %u", playerIndex, _lastSpawnedActorId);
LOGD("[MP] ServerPacketType::PlayerEmitWeaponFlare - received playerIndex %u instead of %u", playerIndex, _lastSpawnedActorId);
return true;
}

Expand Down Expand Up @@ -2176,6 +2319,20 @@ namespace Jazz2::Multiplayer
runtime_cast<Actors::Solid::PinballPaddle*>(actor) || runtime_cast<Actors::Solid::SpikeBall*>(actor));
}

StringView MultiLevelHandler::GameModeToString(MultiplayerGameMode mode)
{
switch (mode) {
case MultiplayerGameMode::Battle: return _("Battle");
case MultiplayerGameMode::TeamBattle: return _("Team Battle");
case MultiplayerGameMode::CaptureTheFlag: return _("Capture The Flag");
case MultiplayerGameMode::Race: return _("Race");
case MultiplayerGameMode::TeamRace: return _("Team Race");
case MultiplayerGameMode::TreasureHunt: return _("Treasure Hunt");
case MultiplayerGameMode::Cooperation: return _("Cooperation");
default: return _("Unknown");
}
}

/*void MultiLevelHandler::UpdatePlayerLocalPos(Actors::Player* player, PlayerState& playerState, float timeMult)
{
if (playerState.WarpTimeLeft > 0.0f || !player->_controllable || !player->GetState(Actors::ActorState::CollideWithTileset)) {
Expand Down
4 changes: 3 additions & 1 deletion Sources/Jazz2/Multiplayer/MultiLevelHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,10 @@ namespace Jazz2::Multiplayer

NetworkManager* _networkManager;
MultiplayerGameMode _gameMode;
bool _isServer;
float _updateTimeLeft;
bool _isServer;
bool _initialUpdateSent;
bool _enableSpawning;
HashMap<Peer, PeerDesc> _peerDesc; // Server: Per peer description
HashMap<std::uint8_t, PlayerState> _playerStates; // Server: Per (remote) player state
HashMap<std::uint32_t, std::shared_ptr<Actors::ActorBase>> _remoteActors; // Client: Actor ID -> Remote Actor created by server
Expand All @@ -184,6 +185,7 @@ namespace Jazz2::Multiplayer
void ApplyGameModeToPlayer(MultiplayerGameMode gameMode, Actors::Player* player);

static bool ActorShouldBeMirrored(Actors::ActorBase* actor);
static StringView GameModeToString(MultiplayerGameMode mode);

#if defined(DEATH_DEBUG) && defined(WITH_IMGUI)
static constexpr std::int32_t PlotValueCount = 512;
Expand Down
4 changes: 2 additions & 2 deletions Sources/Jazz2/Multiplayer/NetworkManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace Jazz2::Multiplayer

_state = NetworkState::Connecting;

ENetAddress addr = { };
ENetAddress addr = {};
enet_address_set_host(&addr, String::nullTerminatedView(address).data());
addr.port = port;

Expand All @@ -80,7 +80,7 @@ namespace Jazz2::Multiplayer
return false;
}

ENetAddress addr = { };
ENetAddress addr = {};
addr.host = ENET_HOST_ANY;
addr.port = port;

Expand Down
3 changes: 2 additions & 1 deletion Sources/Jazz2/Multiplayer/NetworkManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace Jazz2::Multiplayer
friend class ServerDiscovery;

public:
static constexpr std::size_t MaxPeerCount = 128;

NetworkManager();
~NetworkManager();

Expand All @@ -62,7 +64,6 @@ namespace Jazz2::Multiplayer
void KickClient(const Peer& peer, Reason reason);

private:
static constexpr std::size_t MaxPeerCount = 64;
static constexpr std::uint32_t ProcessingIntervalMs = 4;

_ENetHost* _host;
Expand Down
1 change: 1 addition & 0 deletions Sources/Jazz2/Multiplayer/PacketTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace Jazz2::Multiplayer
DestroyRemoteActor,
UpdateAllActors,

PlayerRespawn,
PlayerMoveInstantly,
PlayerAckWarped, // TODO
PlayerActivateForce, // TODO
Expand Down
Loading

0 comments on commit bdf0ebd

Please sign in to comment.