Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/Battlescape/BattlescapeGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2168,8 +2168,39 @@ void BattlescapeGame::endTurn()
}
}

// In PvP both combat factions are controlled by network players. The
// remote-player hand-off is handled by the co-op protocol, so entering the
// vanilla HOSTILE phase here would incorrectly give the alien AI a turn.
// Advance PLAYER as if HOSTILE had already completed, preserving the normal
// NEUTRAL -> PLAYER boundary (including its turn increment and TU refresh).
const int coopGamemode = getCoopMod()->getCoopGamemode();
if (getCoopMod()->getCoopStatic()
&& (coopGamemode == 2 || coopGamemode == 3)
&& _save->getSide() == FACTION_PLAYER)
{
_save->setSideCoop(FACTION_HOSTILE);
}

_save->endTurn();

// PvP has no vanilla HOSTILE phase, so the opposing network seat does not
// pass through the place where that faction would normally receive its
// round-start TU/energy. Refresh both human seats together at the real
// NEUTRAL -> PLAYER round boundary. The host can then carry the current
// value (full minus any later reaction-fire cost) in PlayerTurnYour.
if (getCoopMod()->getCoopStatic()
&& (coopGamemode == 2 || coopGamemode == 3)
&& _save->getSide() == FACTION_PLAYER)
{
for (BattleUnit* unit : *_save->getUnits())
{
if (!unit->isOut() && unit->getFaction() != FACTION_NEUTRAL)
{
unit->resetTimeUnitsAndEnergy();
}
}
}

t = _save->getTileEngine()->checkForTerrainExplosions();
if (t)
{
Expand Down Expand Up @@ -2279,7 +2310,12 @@ void BattlescapeGame::endTurn()

bool battleComplete = (!killingAllAliensIsNotEnough && tally.liveAliens == 0) || tally.liveSoldiers == 0;

if ((_save->getSide() != FACTION_NEUTRAL || battleComplete) && _endTurnRequested)
const int nextTurnGamemode = getCoopMod()->getCoopGamemode();
const bool pvpNeutralScreen = getCoopMod()->getCoopStatic()
&& (nextTurnGamemode == 2 || nextTurnGamemode == 3)
&& _save->getSide() == FACTION_NEUTRAL;
if ((_save->getSide() != FACTION_NEUTRAL || pvpNeutralScreen || battleComplete)
&& _endTurnRequested)
{
_parentState->getGame()->pushState(new NextTurnState(_save, _parentState));
}
Expand Down
24 changes: 22 additions & 2 deletions src/Battlescape/NextTurnState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,15 @@ void NextTurnState::close()
{

// coop
if (_battleGame->getSide() == FACTION_HOSTILE && _game->getCoopMod()->getCoopStatic() == true && _battleGame->getTurn() >= 1 && _game->getCoopMod()->getHost() == true && _battleGame->isPreview() == false)
const int pvpScreenGamemode = _game->getCoopMod()->getCoopGamemode();
const bool pvpTurnScreen = (pvpScreenGamemode == 2 || pvpScreenGamemode == 3)
&& (_battleGame->getSide() == FACTION_NEUTRAL
|| _battleGame->getSide() == FACTION_PLAYER);
if ((_battleGame->getSide() == FACTION_HOSTILE || pvpTurnScreen)
&& _game->getCoopMod()->getCoopStatic() == true
&& _battleGame->getTurn() >= 1
&& _game->getCoopMod()->getHost() == true
&& _battleGame->isPreview() == false)
{

Json::Value root;
Expand Down Expand Up @@ -622,7 +630,19 @@ void NextTurnState::close()
// Abort is a separate, explicitly host-announced vote (finishBattle(true), which
// is NOT gated here and still crosses on the client).
const bool coopHostAuthoritativeEnd = connectionTCP::parallelTurnActive() && !_game->getCoopMod()->getHost();
if (!coopHostAuthoritativeEnd && ((!killingAllAliensIsNotEnough && tally.liveAliens == 0) || tally.liveSoldiers == 0))
// PvP has its own seat-based elimination check in
// BattlescapeState::btnEndTurnClick. The ordinary mission tally cannot be
// used here: each PvP process presents its locally controlled side as the
// player faction, so after the alien half of a round tallyUnits() may report
// zero soldiers or zero aliens even though both network seats still have
// living units. That used to end a resumed gm2/gm3 battle as soon as the
// Next Turn screen was closed. Campaign and Custom Battle PvP now share the
// same authoritative seat-based end path.
const int coopGamemode = _game->getCoopMod()->getCoopGamemode();
const bool coopPvpBattle = _game->getCoopMod()->getCoopStatic()
&& (coopGamemode == 2 || coopGamemode == 3);
if (!coopHostAuthoritativeEnd && !coopPvpBattle
&& ((!killingAllAliensIsNotEnough && tally.liveAliens == 0) || tally.liveSoldiers == 0))
{
_state->finishBattle(false, tally.liveSoldiers);
}
Expand Down
18 changes: 15 additions & 3 deletions src/CoopMod/HostMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,17 @@ void HostMenu::hostTCPGame(Action* action)

}

connectionTCP::_coopGamemode = 1;
// A resume keeps the mode serialized in the save (campaign and Custom
// Battle alike). Resetting a loaded PvP skirmish to 1 here ran the PVE unit
// conversion and changed both ownership and turn semantics before streaming.
const bool preserveLoadedMode =
(_game->getCoopMod()->inCoopCampaignContext()
&& connectionTCP::session.lobbyMode == 2)
|| connectionTCP::session.customBattleResumePending;
if (!preserveLoadedMode)
{
connectionTCP::_coopGamemode = 1;
}

_game->getCoopMod()->setCoopSession(false);

Expand Down Expand Up @@ -701,7 +711,8 @@ void HostMenu::hostTCPGame(Action* action)

}

if (_game->getCoopMod()->getCoopCampaign() == true)
if (_game->getCoopMod()->getCoopCampaign() == true
|| connectionTCP::session.customBattleResumePending)
{
convert = false;
}
Expand Down Expand Up @@ -765,7 +776,8 @@ void HostMenu::hostTCPGame(Action* action)
_game->getCoopMod()->setServerOwner(true);

// If the player has created a server or joined another player's game, close the ServerList and create the LobbyMenu
if (_game->getCoopMod()->getCoopCampaign() == true)
if (_game->getCoopMod()->getCoopCampaign() == true
|| connectionTCP::session.customBattleResumePending)
{
_game->popState();

Expand Down
79 changes: 75 additions & 4 deletions src/CoopMod/LobbyMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "LobbyMenu.h"
#include "../Engine/Logger.h"
#include "../Savegame/SavedGame.h"
#include "../Savegame/SavedBattleGame.h"
#include "../Engine/Game.h"
#include "../Engine/Action.h"
#include "../Engine/Exception.h"
Expand Down Expand Up @@ -143,7 +144,8 @@ LobbyMenu::LobbyMenu() : _sortable(true)
// reconnect are never mode 2, so they are unaffected.
_resumeToGame = (battleRunning
|| (geoRunning && connectionTCP::session.sessionLocked))
&& connectionTCP::session.lobbyMode != 2;
&& connectionTCP::session.lobbyMode != 2
&& !connectionTCP::session.customBattleResumePending;

connectionTCP::session.markLobbyOpen();

Expand Down Expand Up @@ -244,7 +246,8 @@ LobbyMenu::LobbyMenu() : _sortable(true)
{
if (_game->getCoopMod()->getServerOwner() == true)
{
_btnCancel->setText("BATTLE SETTINGS");
_btnCancel->setText(connectionTCP::session.customBattleResumePending
? "CONTINUE BATTLE" : "BATTLE SETTINGS");
_btnCancel->setVisible(false); // shown once the peer is in (think())
}
else
Expand Down Expand Up @@ -877,7 +880,14 @@ void LobbyMenu::btnCancelClick(Action*)
{
if (startEligible())
{
openBattleSettings();
if (connectionTCP::session.customBattleResumePending)
{
continueCustomBattle();
}
else
{
openBattleSettings();
}
}
return;
}
Expand Down Expand Up @@ -969,6 +979,66 @@ void LobbyMenu::openBattleSettings()
closeLobby();
}

void LobbyMenu::continueCustomBattle()
{
if (!_game->getCoopMod()->getServerOwner()
|| !connectionTCP::session.customBattleResumePending
|| !startEligible()
|| !_game->getSavedGame()
|| !_game->getSavedGame()->getSavedBattle())
{
return;
}

connectionTCP::session.campaignStarted();

Json::Value start;
start["state"] = "custom_battle_continue";
_game->getCoopMod()->sendTCPPacketData(start.toStyledString());

closeLobby();
connectionTCP::session.finishCustomBattleResume();
connectionTCP::session.beginCustomBattleResumeLoad();
_game->getCoopMod()->inventory_battle_window = false;

// HostMenu was opened over the loaded battle's PauseState. Return the host
// to that BattlescapeState before the peer begins loading its snapshot.
int guard = 0;
while (guard++ < 32 && _game->getStates().size() > 1
&& dynamic_cast<BattlescapeState*>(_game->getStates().back()) == nullptr)
{
_game->popState();
}
_game->getCoopMod()->_battleInit = false;
if (_game->getCoopMod()->parallelTurnActive())
{
connectionTCP::resetActionArbiter(true);
_game->getCoopMod()->setPlayerTurn(2);
SavedBattleGame* battle = _game->getSavedGame()->getSavedBattle();
if (battle && battle->getBattleState())
{
battle->getBattleState()->setCurrentTurn(2);
}
}
else if (_game->getCoopMod()->getCoopGamemode() == 2
|| _game->getCoopMod()->getCoopGamemode() == 3)
{
// Campaign-PvP resume derives the active player from the preserved mode:
// gm2 host=XCOM, gm3 host=Alien. isYourTurn itself is process-static and
// is not serialized, so establish that same role before snapshotting.
const int turn = _game->getCoopMod()->getCoopGamemode() == 2 ? 2 : 1;
_game->getCoopMod()->setPlayerTurn(turn);
SavedBattleGame* battle = _game->getSavedGame()->getSavedBattle();
if (battle && battle->getBattleState())
{
battle->getBattleState()->setCurrentTurn(turn);
}
}
_game->pushState(new CoopState(COOP_DLG_WAIT_PLAYERS));

_game->getCoopMod()->streamSkirmishBattleToClient();
}

/**
* Can the custom-battle client open the local EQUIP CRAFT screen safely?
*
Expand Down Expand Up @@ -1424,7 +1494,8 @@ void LobbyMenu::think()
// the selected craft and entered equipment preparation.
if (_game->getCoopMod()->getServerOwner() == true)
{
_btnCancel->setText("BATTLE SETTINGS");
_btnCancel->setText(connectionTCP::session.customBattleResumePending
? "CONTINUE BATTLE" : "BATTLE SETTINGS");
_btnCancel->setVisible(startEligible());
}
else
Expand Down
2 changes: 2 additions & 0 deletions src/CoopMod/LobbyMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class LobbyMenu : public State
void closeLobby();
/// Host pressed BATTLE SETTINGS in the skirmish lobby (mode 0).
void openBattleSettings();
/// Host continues a loaded Custom Battle after both players reached the lobby.
void continueCustomBattle();
/// Is the client's local custom-battle craft ready to be opened safely?
bool canOpenEquipCraft() const;
/// Client pressed EQUIP CRAFT in the skirmish lobby (mode 0).
Expand Down
11 changes: 11 additions & 0 deletions src/CoopMod/TestServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "../Engine/Logger.h"
#include "../Engine/Options.h"
#include "../Engine/State.h"
#include "../Menu/PauseState.h"
#include "../Geoscape/GeoscapeState.h"
#include "../Geoscape/ItemsArrivingState.h"
#include "../Geoscape/GeoscapeCraftState.h"
Expand Down Expand Up @@ -2589,6 +2590,15 @@ bool TestServer::executeShared11(const std::string& cmd, const Json::Value& req,
_game->pushState(new LobbyMenu());
resp["ok"] = true;
}
else if (cmd == "open_pause_coop")
{
// Drive the real Battlescape pause-menu COOP action. In particular, a
// freshly loaded Custom Battle must open HostMenu rather than ServerList.
PauseState* pause = new PauseState(OPT_BATTLESCAPE);
_game->pushState(pause);
pause->btnCoopClick(nullptr);
resp["ok"] = true;
}
else if (cmd == "lobby_set_team")
{
// Host-only: put lobby row <row> (0 = host, 1 = the joined client) on
Expand Down Expand Up @@ -7308,6 +7318,7 @@ std::string TestServer::execute(const std::string& line)
resp["lobbyClosed"] = connectionTCP::session.lobbyClosed;
resp["lobbyFileStatus"] = connectionTCP::LobbyFileStatus;
resp["lobbyMode"] = connectionTCP::session.lobbyMode;
resp["customBattleResumePending"] = connectionTCP::session.customBattleResumePending;
resp["resumeAck"] = connectionTCP::session.resumeAck;
resp["coopSession"] = coop->isCoopSession();
resp["hasSave"] = _game->getSavedGame() != nullptr;
Expand Down
Loading
Loading