Took 1 hour 10 minutes
This commit is contained in:
Lukas Brübach 2026-05-28 08:58:16 +02:00
parent 7f30728f87
commit 2727afe386
2 changed files with 267 additions and 7 deletions

View file

@ -7,6 +7,7 @@
#include "server_move_card_struct.h" #include "server_move_card_struct.h"
#include <QDebug> #include <QDebug>
#include <QLoggingCategory>
#include <QRegularExpression> #include <QRegularExpression>
#include <algorithm> #include <algorithm>
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
@ -52,6 +53,8 @@
#include <limits> #include <limits>
#include <ranges> #include <ranges>
Q_LOGGING_CATEGORY(arrowLog, "cockatrice.server.arrows")
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
int _playerId, int _playerId,
const ServerInfo_User &_userInfo, const ServerInfo_User &_userInfo,
@ -84,11 +87,23 @@ int Server_AbstractPlayer::newCardId()
int Server_AbstractPlayer::newArrowId() const int Server_AbstractPlayer::newArrowId() const
{ {
int id = 0; int id = 0;
qCDebug(arrowLog) << "[ArrowId] Calculating next arrow id for player" << playerId
<< "- existing arrow count:" << arrows.size();
for (Server_Arrow *a : arrows) { for (Server_Arrow *a : arrows) {
qCDebug(arrowLog) << "[ArrowId] Existing arrow:"
<< "id=" << a->getId()
<< "startCard=" << (a->getStartCard() ? a->getStartCard()->getId() : -1)
<< "target=" << a->getTargetItem();
if (a->getId() > id) { if (a->getId() > id) {
id = a->getId(); id = a->getId();
} }
} }
qCDebug(arrowLog) << "[ArrowId] Next arrow id for player" << playerId << "will be" << (id + 1);
return id + 1; return id + 1;
} }
@ -117,23 +132,68 @@ void Server_AbstractPlayer::addZone(Server_CardZone *zone)
void Server_AbstractPlayer::addArrow(Server_Arrow *arrow) void Server_AbstractPlayer::addArrow(Server_Arrow *arrow)
{ {
qCDebug(arrowLog) << "[ArrowAdd]"
<< "player=" << playerId << "arrowId=" << arrow->getId()
<< "startCard=" << (arrow->getStartCard() ? arrow->getStartCard()->getId() : -1)
<< "targetItem=" << arrow->getTargetItem() << "existingArrowCount=" << arrows.size();
arrows.insert(arrow->getId(), arrow); arrows.insert(arrow->getId(), arrow);
qCDebug(arrowLog) << "[ArrowAdd] Complete:"
<< "newArrowCount=" << arrows.size();
} }
void Server_AbstractPlayer::updateArrowId(int id) void Server_AbstractPlayer::updateArrowId(int id)
{ {
qCDebug(arrowLog) << "[ArrowUpdateId]"
<< "player=" << playerId << "oldId=" << id;
auto *arrow = arrows.take(id); auto *arrow = arrows.take(id);
if (!arrow) {
qCWarning(arrowLog) << "[ArrowUpdateId] FAILED:"
<< "could not find arrow with id" << id;
return;
}
qCDebug(arrowLog) << "[ArrowUpdateId]"
<< "newId=" << arrow->getId();
arrows.insert(arrow->getId(), arrow); arrows.insert(arrow->getId(), arrow);
qCDebug(arrowLog) << "[ArrowUpdateId] Complete";
} }
bool Server_AbstractPlayer::deleteArrow(int arrowId) bool Server_AbstractPlayer::deleteArrow(int arrowId)
{ {
Server_Arrow *arrow = arrows.value(arrowId, 0); qCDebug(arrowLog) << "[ArrowDelete]"
<< "player=" << playerId << "requestedArrowId=" << arrowId;
Server_Arrow *arrow = arrows.value(arrowId, nullptr);
if (!arrow) { if (!arrow) {
qCWarning(arrowLog) << "[ArrowDelete] FAILED:"
<< "arrow not found"
<< "arrowId=" << arrowId;
return false; return false;
} }
qCDebug(arrowLog) << "[ArrowDelete]"
<< "startCard=" << (arrow->getStartCard() ? arrow->getStartCard()->getId() : -1)
<< "targetItem=" << arrow->getTargetItem();
arrows.remove(arrowId); arrows.remove(arrowId);
qCDebug(arrowLog) << "[ArrowDelete]"
<< "removedFromMap"
<< "remainingArrowCount=" << arrows.size();
delete arrow; delete arrow;
qCDebug(arrowLog) << "[ArrowDelete] Complete:"
<< "deletedArrowId=" << arrowId;
return true; return true;
} }
@ -388,15 +448,24 @@ void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges,
} }
if (startzone != targetzone) { if (startzone != targetzone) {
qCDebug(arrowLog) << "[ArrowCleanupOnMove]"
<< "movingCard=" << card->getId() << "fromZone=" << startzone->getName()
<< "toZone=" << targetzone->getName();
// Delete all arrows from and to the card // Delete all arrows from and to the card
for (auto *player : game->getPlayers().values()) { for (auto *player : game->getPlayers().values()) {
QList<int> arrowsToDelete; QList<int> arrowsToDelete;
for (Server_Arrow *arrow : player->getArrows()) { for (Server_Arrow *arrow : player->getArrows()) {
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) { if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) {
qCDebug(arrowLog) << "[ArrowCleanupOnMove]"
<< "player=" << player->getPlayerId()
<< "schedulingArrowDelete=" << arrow->getId() << "because card" << card->getId()
<< "is involved";
arrowsToDelete.append(arrow->getId()); arrowsToDelete.append(arrow->getId());
} }
} }
for (int j : arrowsToDelete) { for (int j : arrowsToDelete) {
qCDebug(arrowLog) << "[ArrowCleanupOnMove]"
<< "deletingArrowId=" << j;
Event_DeleteArrow event; Event_DeleteArrow event;
event.set_arrow_id(j); event.set_arrow_id(j);
ges.enqueueGameEvent(event, player->getPlayerId()); ges.enqueueGameEvent(event, player->getPlayerId());
@ -1120,50 +1189,117 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo
// Copy Arrows // Copy Arrows
for (auto *player : game->getPlayers().values()) { for (auto *player : game->getPlayers().values()) {
QList<int> changedArrowIds; QList<int> changedArrowIds;
for (Server_Arrow *arrow : player->getArrows()) { for (Server_Arrow *arrow : player->getArrows()) {
qCDebug(arrowLog) << "[ArrowTransformMigration] Inspecting arrow"
<< "player=" << player->getPlayerId() << "arrowId=" << arrow->getId();
bool sendGameEvent = false; bool sendGameEvent = false;
const auto *startCard = arrow->getStartCard(); const auto *startCard = arrow->getStartCard();
if (startCard == targetCard) { if (startCard == targetCard) {
qCDebug(arrowLog) << "[ArrowTransformMigration] Replacing start card"
<< "arrowId=" << arrow->getId() << "oldStartCard=" << targetCard->getId()
<< "newStartCard=" << card->getId();
sendGameEvent = true; sendGameEvent = true;
arrow->setStartCard(card); arrow->setStartCard(card);
startCard = card; startCard = card;
} }
const auto *targetItem = arrow->getTargetItem(); const auto *targetItem = arrow->getTargetItem();
if (targetItem == targetCard) { if (targetItem == targetCard) {
qCDebug(arrowLog) << "[ArrowTransformMigration] Replacing target item"
<< "arrowId=" << arrow->getId() << "oldTargetCard=" << targetCard->getId()
<< "newTargetCard=" << card->getId();
sendGameEvent = true; sendGameEvent = true;
arrow->setTargetItem(card); arrow->setTargetItem(card);
targetItem = card; targetItem = card;
} }
if (sendGameEvent) { if (sendGameEvent) {
const int oldId = arrow->getId(); const int oldId = arrow->getId();
qCDebug(arrowLog) << "[ArrowTransformMigration] Migrating arrow"
<< "player=" << player->getPlayerId() << "oldArrowId=" << oldId;
changedArrowIds.append(oldId); changedArrowIds.append(oldId);
Event_DeleteArrow deleteEvent; Event_DeleteArrow deleteEvent;
deleteEvent.set_arrow_id(oldId); deleteEvent.set_arrow_id(oldId);
qCDebug(arrowLog) << "[ArrowTransformMigration] Enqueue delete event"
<< "arrowId=" << oldId;
ges.enqueueGameEvent(deleteEvent, player->getPlayerId()); ges.enqueueGameEvent(deleteEvent, player->getPlayerId());
Event_CreateArrow createEvent; Event_CreateArrow createEvent;
ServerInfo_Arrow *arrowInfo = createEvent.mutable_arrow_info(); ServerInfo_Arrow *arrowInfo = createEvent.mutable_arrow_info();
const int newId = player->newArrowId(); const int newId = player->newArrowId();
qCDebug(arrowLog) << "[ArrowTransformMigration] Assigning new arrow id"
<< "oldId=" << oldId << "newId=" << newId;
arrow->setId(newId); arrow->setId(newId);
arrowInfo->set_id(newId); arrowInfo->set_id(newId);
arrowInfo->set_start_player_id(player->getPlayerId()); arrowInfo->set_start_player_id(player->getPlayerId());
arrowInfo->set_start_zone(startCard->getZone()->getName().toStdString()); arrowInfo->set_start_zone(startCard->getZone()->getName().toStdString());
arrowInfo->set_start_card_id(startCard->getId()); arrowInfo->set_start_card_id(startCard->getId());
const auto *arrowTargetPlayer = qobject_cast<const Server_AbstractPlayer *>(targetItem); const auto *arrowTargetPlayer = qobject_cast<const Server_AbstractPlayer *>(targetItem);
if (arrowTargetPlayer != nullptr) { if (arrowTargetPlayer != nullptr) {
qCDebug(arrowLog) << "[ArrowTransformMigration] Target is player"
<< "targetPlayer=" << arrowTargetPlayer->getPlayerId();
arrowInfo->set_target_player_id(arrowTargetPlayer->getPlayerId()); arrowInfo->set_target_player_id(arrowTargetPlayer->getPlayerId());
} else { } else {
const auto *arrowTargetCard = qobject_cast<const Server_Card *>(targetItem); const auto *arrowTargetCard = qobject_cast<const Server_Card *>(targetItem);
qCDebug(arrowLog)
<< "[ArrowTransformMigration] Target is card"
<< "targetPlayer=" << arrowTargetCard->getZone()->getPlayer()->getPlayerId()
<< "targetZone=" << arrowTargetCard->getZone()->getName()
<< "targetCard=" << arrowTargetCard->getId();
arrowInfo->set_target_player_id(arrowTargetCard->getZone()->getPlayer()->getPlayerId()); arrowInfo->set_target_player_id(arrowTargetCard->getZone()->getPlayer()->getPlayerId());
arrowInfo->set_target_zone(arrowTargetCard->getZone()->getName().toStdString()); arrowInfo->set_target_zone(arrowTargetCard->getZone()->getName().toStdString());
arrowInfo->set_target_card_id(arrowTargetCard->getId()); arrowInfo->set_target_card_id(arrowTargetCard->getId());
} }
arrowInfo->mutable_arrow_color()->CopyFrom(arrow->getColor()); arrowInfo->mutable_arrow_color()->CopyFrom(arrow->getColor());
qCDebug(arrowLog) << "[ArrowTransformMigration] Enqueue create event"
<< "newArrowId=" << newId;
ges.enqueueGameEvent(createEvent, player->getPlayerId()); ges.enqueueGameEvent(createEvent, player->getPlayerId());
qCDebug(arrowLog) << "[ArrowTransformMigration] Migration complete"
<< "oldArrowId=" << oldId << "newArrowId=" << newId;
} }
} }
for (int id : changedArrowIds) { for (int id : changedArrowIds) {
qCDebug(arrowLog) << "[ArrowTransformMigration] Updating arrow map"
<< "oldId=" << id;
player->updateArrowId(id); player->updateArrowId(id);
} }
} }
@ -1261,6 +1397,11 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo
for (Server_Arrow *temp : arrows) { for (Server_Arrow *temp : arrows) {
if ((temp->getStartCard() == startCard) && (temp->getTargetItem() == targetItem)) { if ((temp->getStartCard() == startCard) && (temp->getTargetItem() == targetItem)) {
qCWarning(arrowLog) << "[ArrowCreate] Duplicate rejected:"
<< "player=" << playerId << "existingArrowId=" << temp->getId()
<< "startCard=" << startCard->getId() << "targetItem=" << targetItem;
return Response::RespContextError; return Response::RespContextError;
} }
} }
@ -1270,6 +1411,10 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo
auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase); auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase);
addArrow(arrow); addArrow(arrow);
qCDebug(arrowLog) << "[ArrowCreate]"
<< "requestingPlayer=" << playerId << "startPlayer=" << startPlayer->getPlayerId()
<< "targetPlayer=" << targetPlayer->getPlayerId() << "startZone=" << startZoneName
<< "startCard=" << startCard->getId() << "targetIsPlayer=" << playerTarget;
Event_CreateArrow event; Event_CreateArrow event;
ServerInfo_Arrow *arrowInfo = event.mutable_arrow_info(); ServerInfo_Arrow *arrowInfo = event.mutable_arrow_info();
arrowInfo->set_id(arrow->getId()); arrowInfo->set_id(arrow->getId());
@ -1290,21 +1435,53 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo
Response::ResponseCode Response::ResponseCode
Server_AbstractPlayer::cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) Server_AbstractPlayer::cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{ {
qCDebug(arrowLog) << "[ArrowDeleteCommand] Request received"
<< "requestingPlayer=" << playerId << "arrowId=" << cmd.arrow_id();
if (!game->getGameStarted()) { if (!game->getGameStarted()) {
qCWarning(arrowLog) << "[ArrowDeleteCommand] Rejected:"
<< "game not started"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
return Response::RespGameNotStarted; return Response::RespGameNotStarted;
} }
if (conceded) { if (conceded) {
qCWarning(arrowLog) << "[ArrowDeleteCommand] Rejected:"
<< "player already conceded"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
return Response::RespContextError; return Response::RespContextError;
} }
qCDebug(arrowLog) << "[ArrowDeleteCommand] Attempting delete"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
if (!deleteArrow(cmd.arrow_id())) { if (!deleteArrow(cmd.arrow_id())) {
qCWarning(arrowLog) << "[ArrowDeleteCommand] Delete failed:"
<< "arrow not found"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
return Response::RespNameNotFound; return Response::RespNameNotFound;
} }
qCDebug(arrowLog) << "[ArrowDeleteCommand] Delete successful"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
Event_DeleteArrow event; Event_DeleteArrow event;
event.set_arrow_id(cmd.arrow_id()); event.set_arrow_id(cmd.arrow_id());
qCDebug(arrowLog) << "[ArrowDeleteCommand] Enqueueing delete event"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
ges.enqueueGameEvent(event, playerId); ges.enqueueGameEvent(event, playerId);
qCDebug(arrowLog) << "[ArrowDeleteCommand] Complete"
<< "player=" << playerId << "arrowId=" << cmd.arrow_id();
return Response::RespOk; return Response::RespOk;
} }

View file

@ -32,6 +32,7 @@
#include "server_spectator.h" #include "server_spectator.h"
#include <QDebug> #include <QDebug>
#include <QLoggingCategory>
#include <QRegularExpression> #include <QRegularExpression>
#include <QTimer> #include <QTimer>
#include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.h>
@ -53,6 +54,8 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h> #include <libcockatrice/protocol/pb/game_replay.pb.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
Q_LOGGING_CATEGORY(arrowServerLog, "cockatrice.server.game_arrows")
Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, Server_Game::Server_Game(const ServerInfo_User &_creatorInfo,
int _gameId, int _gameId,
const QString &_description, const QString &_description,
@ -599,35 +602,85 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Abst
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
// Remove all arrows of other players pointing to the player being removed or to one of his cards. qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] START"
// Also remove all arrows starting at one of his cards. This is necessary since players can create << "targetPlayer=" << player->getPlayerId();
// arrows that start at another person's cards.
for (Server_AbstractPlayer *anyPlayer : getPlayers().values()) { for (Server_AbstractPlayer *anyPlayer : getPlayers().values()) {
QList<Server_Arrow *> toDelete; QList<Server_Arrow *> toDelete;
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] Scanning player"
<< "player=" << anyPlayer->getPlayerId()
<< "arrowCount=" << anyPlayer->getArrows().size();
for (auto *arrow : anyPlayer->getArrows().values()) { for (auto *arrow : anyPlayer->getArrows().values()) {
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] Inspect arrow"
<< "arrowId=" << arrow->getId() << "ownerPlayer=" << anyPlayer->getPlayerId();
auto *targetCard = qobject_cast<Server_Card *>(arrow->getTargetItem()); auto *targetCard = qobject_cast<Server_Card *>(arrow->getTargetItem());
bool marked = false;
if (targetCard) { if (targetCard) {
if (targetCard->getZone() != nullptr && targetCard->getZone()->getPlayer() == player) { if (targetCard->getZone() != nullptr && targetCard->getZone()->getPlayer() == player) {
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] MATCH targetCard belongs to player"
<< "arrowId=" << arrow->getId() << "targetCard=" << targetCard->getId();
toDelete.append(arrow); toDelete.append(arrow);
marked = true;
} }
} else if (arrow->getTargetItem() == player) { } else if (arrow->getTargetItem() == player) {
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] MATCH target is player"
<< "arrowId=" << arrow->getId();
toDelete.append(arrow); toDelete.append(arrow);
marked = true;
} }
// Don't use else here! It has to happen regardless of whether targetCard == 0. // IMPORTANT: always evaluated
if (arrow->getStartCard()->getZone() != nullptr && if (arrow->getStartCard()->getZone() != nullptr &&
arrow->getStartCard()->getZone()->getPlayer() == player) { arrow->getStartCard()->getZone()->getPlayer() == player) {
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] MATCH startCard belongs to player"
<< "arrowId=" << arrow->getId()
<< "startCard=" << arrow->getStartCard()->getId();
toDelete.append(arrow); toDelete.append(arrow);
marked = true;
}
if (!marked) {
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] KEEP"
<< "arrowId=" << arrow->getId();
} }
} }
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] Deletion batch ready"
<< "player=" << anyPlayer->getPlayerId() << "count=" << toDelete.size();
for (auto *arrow : toDelete) { for (auto *arrow : toDelete) {
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] DELETING"
<< "arrowId=" << arrow->getId() << "fromPlayer=" << anyPlayer->getPlayerId();
Event_DeleteArrow event; Event_DeleteArrow event;
event.set_arrow_id(arrow->getId()); event.set_arrow_id(arrow->getId());
ges.enqueueGameEvent(event, anyPlayer->getPlayerId()); ges.enqueueGameEvent(event, anyPlayer->getPlayerId());
anyPlayer->deleteArrow(arrow->getId()); anyPlayer->deleteArrow(arrow->getId());
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] Deleted"
<< "arrowId=" << arrow->getId();
} }
} }
qCDebug(arrowServerLog) << "[ArrowPlayerCleanup] END";
} }
void Server_Game::unattachCards(GameEventStorage &ges, Server_AbstractPlayer *player) void Server_Game::unattachCards(GameEventStorage &ges, Server_AbstractPlayer *player)
@ -701,17 +754,47 @@ void Server_Game::removeArrows(int newPhase, bool force)
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
qCDebug(arrowServerLog) << "[ArrowPhaseCleanup] START"
<< "newPhase=" << newPhase << "force=" << force << "playerCount=" << getPlayers().size();
for (auto *anyPlayer : getPlayers().values()) { for (auto *anyPlayer : getPlayers().values()) {
for (auto *arrowToDelete : anyPlayer->getArrows().values()) { // values creates a copy
if (force || arrowToDelete->checkPhaseDeletion(newPhase)) { qCDebug(arrowServerLog) << "[ArrowPhaseCleanup] Player scan"
<< "player=" << anyPlayer->getPlayerId()
<< "arrowCount=" << anyPlayer->getArrows().size();
for (auto *arrowToDelete : anyPlayer->getArrows().values()) {
qCDebug(arrowServerLog) << "[ArrowPhaseCleanup] Inspect arrow"
<< "player=" << anyPlayer->getPlayerId() << "arrowId=" << arrowToDelete->getId()
<< "startCard="
<< (arrowToDelete->getStartCard() ? arrowToDelete->getStartCard()->getId() : -1)
<< "targetItem=" << arrowToDelete->getTargetItem()
<< "currentPhaseOk=" << arrowToDelete->checkPhaseDeletion(newPhase);
const bool shouldDelete = (force || arrowToDelete->checkPhaseDeletion(newPhase));
if (shouldDelete) {
qCDebug(arrowServerLog) << "[ArrowPhaseCleanup] DELETING"
<< "player=" << anyPlayer->getPlayerId() << "arrowId=" << arrowToDelete->getId()
<< "reason=" << (force ? "FORCE" : "PHASE");
Event_DeleteArrow event; Event_DeleteArrow event;
event.set_arrow_id(arrowToDelete->getId()); event.set_arrow_id(arrowToDelete->getId());
sendGameEventContainer(prepareGameEvent(event, anyPlayer->getPlayerId())); sendGameEventContainer(prepareGameEvent(event, anyPlayer->getPlayerId()));
anyPlayer->deleteArrow(arrowToDelete->getId()); anyPlayer->deleteArrow(arrowToDelete->getId());
qCDebug(arrowServerLog) << "[ArrowPhaseCleanup] Deleted"
<< "player=" << anyPlayer->getPlayerId()
<< "arrowId=" << arrowToDelete->getId();
} }
} }
} }
qCDebug(arrowServerLog) << "[ArrowPhaseCleanup] END";
} }
void Server_Game::nextTurn() void Server_Game::nextTurn()