Files
Cockatrice/common/server/game/server_player.cpp
ebbit1q 3cff55b0bb add abstract player in expectance of draft players (#6210)
* add abstract player in expectance of draft players
2025-10-07 15:09:30 +02:00

603 lines
19 KiB
C++

#include "server_player.h"
#include "../../color.h"
#include "../../deck_list.h"
#include "../../deck_list_card_node.h"
#include "../../trice_limits.h"
#include "../server.h"
#include "../server_database_interface.h"
#include "../server_room.h"
#include "pb/command_change_zone_properties.pb.h"
#include "pb/command_create_counter.pb.h"
#include "pb/command_deck_select.pb.h"
#include "pb/command_del_counter.pb.h"
#include "pb/command_draw_cards.pb.h"
#include "pb/command_inc_counter.pb.h"
#include "pb/command_move_card.pb.h"
#include "pb/command_mulligan.pb.h"
#include "pb/command_reverse_turn.pb.h"
#include "pb/command_set_active_phase.pb.h"
#include "pb/command_set_counter.pb.h"
#include "pb/command_set_sideboard_lock.pb.h"
#include "pb/command_set_sideboard_plan.pb.h"
#include "pb/command_shuffle.pb.h"
#include "pb/command_undo_draw.pb.h"
#include "pb/context_deck_select.pb.h"
#include "pb/context_mulligan.pb.h"
#include "pb/context_set_sideboard_lock.pb.h"
#include "pb/context_undo_draw.pb.h"
#include "pb/event_create_counter.pb.h"
#include "pb/event_del_counter.pb.h"
#include "pb/event_draw_cards.pb.h"
#include "pb/event_player_properties_changed.pb.h"
#include "pb/event_reverse_turn.pb.h"
#include "pb/event_set_counter.pb.h"
#include "pb/event_shuffle.pb.h"
#include "pb/response_deck_download.pb.h"
#include "pb/serverinfo_player.pb.h"
#include "server_card.h"
#include "server_cardzone.h"
#include "server_counter.h"
#include "server_game.h"
#include "server_move_card_struct.h"
Server_Player::Server_Player(Server_Game *_game,
int _playerId,
const ServerInfo_User &_userInfo,
bool _judge,
Server_AbstractUserInterface *_userInterface)
: Server_AbstractPlayer(_game, _playerId, _userInfo, _judge, _userInterface)
{
}
Server_Player::~Server_Player() = default;
int Server_Player::newCounterId() const
{
int id = 0;
QMapIterator<int, Server_Counter *> i(counters);
while (i.hasNext()) {
Server_Counter *c = i.next().value();
if (c->getId() > id) {
id = c->getId();
}
}
return id + 1;
}
void Server_Player::setupZones()
{
Server_AbstractPlayer::setupZones();
// This may need to be customized according to the game rules.
// ------------------------------------------------------------------
// Create zones
auto *deckZone = new Server_CardZone(this, "deck", false, ServerInfo_Zone::HiddenZone);
addZone(deckZone);
auto *sbZone = new Server_CardZone(this, "sb", false, ServerInfo_Zone::HiddenZone);
addZone(sbZone);
addZone(new Server_CardZone(this, "table", true, ServerInfo_Zone::PublicZone));
addZone(new Server_CardZone(this, "hand", false, ServerInfo_Zone::PrivateZone));
addZone(new Server_CardZone(this, "stack", false, ServerInfo_Zone::PublicZone));
addZone(new Server_CardZone(this, "grave", false, ServerInfo_Zone::PublicZone));
addZone(new Server_CardZone(this, "rfg", false, ServerInfo_Zone::PublicZone));
addCounter(new Server_Counter(0, "life", makeColor(255, 255, 255), 25, game->getStartingLifeTotal()));
addCounter(new Server_Counter(1, "w", makeColor(255, 255, 150), 20, 0));
addCounter(new Server_Counter(2, "u", makeColor(150, 150, 255), 20, 0));
addCounter(new Server_Counter(3, "b", makeColor(150, 150, 150), 20, 0));
addCounter(new Server_Counter(4, "r", makeColor(250, 150, 150), 20, 0));
addCounter(new Server_Counter(5, "g", makeColor(150, 255, 150), 20, 0));
addCounter(new Server_Counter(6, "x", makeColor(255, 255, 255), 20, 0));
addCounter(new Server_Counter(7, "storm", makeColor(255, 150, 30), 20, 0));
// ------------------------------------------------------------------
// Assign card ids and create deck from deck list
InnerDecklistNode *listRoot = deck->getRoot();
for (int i = 0; i < listRoot->size(); ++i) {
auto *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
Server_CardZone *z;
if (currentZone->getName() == DECK_ZONE_MAIN) {
z = deckZone;
} else if (currentZone->getName() == DECK_ZONE_SIDE) {
z = sbZone;
} else {
continue;
}
for (int j = 0; j < currentZone->size(); ++j) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard) {
continue;
}
for (int k = 0; k < currentCard->getNumber(); ++k) {
z->insertCard(new Server_Card(currentCard->toCardRef(), nextCardId++, 0, 0, z), -1, 0);
}
}
}
const QList<MoveCard_ToZone> &sideboardPlan = deck->getCurrentSideboardPlan();
for (const auto &m : sideboardPlan) {
const QString startZone = nameFromStdString(m.start_zone());
const QString targetZone = nameFromStdString(m.target_zone());
Server_CardZone *start, *target;
if (startZone == DECK_ZONE_MAIN) {
start = deckZone;
} else if (startZone == DECK_ZONE_SIDE) {
start = sbZone;
} else {
continue;
}
if (targetZone == DECK_ZONE_MAIN) {
target = deckZone;
} else if (targetZone == DECK_ZONE_SIDE) {
target = sbZone;
} else {
continue;
}
for (int j = 0; j < start->getCards().size(); ++j) {
if (start->getCards()[j]->getName() == nameFromStdString(m.card_name())) {
Server_Card *card = start->getCard(j, nullptr, true);
target->insertCard(card, -1, 0);
break;
}
}
}
deckZone->shuffle();
}
void Server_Player::clearZones()
{
Server_AbstractPlayer::clearZones();
for (Server_Counter *counter : counters) {
delete counter;
}
counters.clear();
lastDrawList.clear();
}
void Server_Player::addCounter(Server_Counter *counter)
{
counters.insert(counter->getId(), counter);
}
Response::ResponseCode Server_Player::drawCards(GameEventStorage &ges, int number)
{
Server_CardZone *deckZone = zones.value("deck");
Server_CardZone *handZone = zones.value("hand");
if (deckZone->getCards().size() < number) {
number = deckZone->getCards().size();
}
Event_DrawCards eventOthers;
eventOthers.set_number(number);
Event_DrawCards eventPrivate(eventOthers);
for (int i = 0; i < number; ++i) {
Server_Card *card = deckZone->getCard(0, nullptr, true);
handZone->insertCard(card, -1, 0);
lastDrawList.append(card->getId());
ServerInfo_Card *cardInfo = eventPrivate.add_cards();
cardInfo->set_id(card->getId());
cardInfo->set_name(card->getName().toStdString());
cardInfo->set_provider_id(card->getProviderId().toStdString());
}
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
if (number > 0) {
revealTopCardIfNeeded(deckZone, ges);
int currentKnownCards = deckZone->getCardsBeingLookedAt();
deckZone->setCardsBeingLookedAt(currentKnownCards - number);
}
return Response::RespOk;
}
void Server_Player::onCardBeingMoved(GameEventStorage &ges,
const MoveCardStruct &cardStruct,
Server_CardZone *startzone,
Server_CardZone *targetzone,
bool undoingDraw)
{
Server_AbstractPlayer::onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
Server_Card *card = cardStruct.card;
// "Undo draw" should only remain valid if the just-drawn card stays within the user's hand (e.g., they only
// reorder their hand). If a just-drawn card leaves the hand then remove cards before it from the list
// (Ignore the case where the card is currently being un-drawn.)
if (startzone->getName() == "hand" && targetzone->getName() != "hand" && !undoingDraw) {
int index = lastDrawList.lastIndexOf(card->getId());
if (index != -1) {
lastDrawList.erase(lastDrawList.begin(), lastDrawList.begin() + index);
}
}
}
Response::ResponseCode
Server_Player::cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &rc, GameEventStorage &ges)
{
if (game->getGameStarted()) {
return Response::RespContextError;
}
DeckList *newDeck;
if (cmd.has_deck_id()) {
try {
newDeck = game->getRoom()->getServer()->getDatabaseInterface()->getDeckFromDatabase(cmd.deck_id(),
userInfo->id());
} catch (Response::ResponseCode &r) {
return r;
}
} else {
newDeck = new DeckList(fileFromStdString(cmd.deck()));
}
if (!newDeck) {
return Response::RespInternalError;
}
delete deck;
deck = newDeck;
sideboardLocked = true;
Event_PlayerPropertiesChanged event;
event.mutable_player_properties()->set_sideboard_locked(true);
event.mutable_player_properties()->set_deck_hash(deck->getDeckHash().toStdString());
ges.enqueueGameEvent(event, playerId);
Context_DeckSelect context;
context.set_deck_hash(deck->getDeckHash().toStdString());
context.set_sideboard_size(deck->getSideboardSize());
if (game->getShareDecklistsOnLoad()) {
context.set_deck_list(deck->writeToString_Native().toStdString());
}
ges.setGameEventContext(context);
auto *re = new Response_DeckDownload;
re->set_deck(deck->writeToString_Native().toStdString());
rc.setResponseExtension(re);
return Response::RespOk;
}
Response::ResponseCode Server_Player::cmdSetSideboardPlan(const Command_SetSideboardPlan &cmd,
ResponseContainer & /*rc*/,
GameEventStorage & /*ges*/)
{
if (readyStart) {
return Response::RespContextError;
}
if (!deck) {
return Response::RespContextError;
}
if (sideboardLocked) {
return Response::RespContextError;
}
QList<MoveCard_ToZone> sideboardPlan;
for (int i = 0; i < cmd.move_list_size(); ++i) {
sideboardPlan.append(cmd.move_list(i));
}
deck->setCurrentSideboardPlan(sideboardPlan);
return Response::RespOk;
}
Response::ResponseCode Server_Player::cmdSetSideboardLock(const Command_SetSideboardLock &cmd,
ResponseContainer & /*rc*/,
GameEventStorage &ges)
{
if (readyStart) {
return Response::RespContextError;
}
if (!deck) {
return Response::RespContextError;
}
if (sideboardLocked == cmd.locked()) {
return Response::RespContextError;
}
sideboardLocked = cmd.locked();
if (sideboardLocked) {
deck->setCurrentSideboardPlan(QList<MoveCard_ToZone>());
}
Event_PlayerPropertiesChanged event;
event.mutable_player_properties()->set_sideboard_locked(sideboardLocked);
ges.enqueueGameEvent(event, playerId);
ges.setGameEventContext(Context_SetSideboardLock());
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdShuffle(const Command_Shuffle &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
if (cmd.has_zone_name() && cmd.zone_name() != "deck") {
return Response::RespFunctionNotAllowed;
}
Server_CardZone *zone = zones.value("deck");
if (!zone) {
return Response::RespNameNotFound;
}
zone->shuffle(cmd.start(), cmd.end());
Event_Shuffle event;
event.set_zone_name(zone->getName().toStdString());
event.set_start(cmd.start());
event.set_end(cmd.end());
ges.enqueueGameEvent(event, playerId);
revealTopCardIfNeeded(zone, ges);
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdMulligan(const Command_Mulligan &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
Server_CardZone *hand = zones.value("hand");
Server_CardZone *_deck = zones.value("deck");
int number = cmd.number();
if (!hand->getCards().isEmpty()) {
auto cardsToMove = QList<const CardToMove *>();
for (auto &card : hand->getCards()) {
auto *cardToMove = new CardToMove;
cardToMove->set_card_id(card->getId());
cardsToMove.append(cardToMove);
}
moveCard(ges, hand, cardsToMove, _deck, -1, 0, false);
qDeleteAll(cardsToMove);
}
_deck->shuffle();
ges.enqueueGameEvent(Event_Shuffle(), playerId);
drawCards(ges, number);
Context_Mulligan context;
context.set_number(static_cast<google::protobuf::uint32>(hand->getCards().size()));
ges.setGameEventContext(context);
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdDrawCards(const Command_DrawCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
return drawCards(ges, cmd.number());
}
Response::ResponseCode
Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
if (lastDrawList.isEmpty()) {
return Response::RespContextError;
}
Response::ResponseCode retVal;
auto *cardToMove = new CardToMove;
cardToMove->set_card_id(lastDrawList.takeLast());
retVal = moveCard(ges, zones.value("hand"), QList<const CardToMove *>() << cardToMove, zones.value("deck"), 0, 0,
false, true);
delete cardToMove;
return retVal;
}
Response::ResponseCode
Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
Server_Counter *c = counters.value(cmd.counter_id(), 0);
if (!c) {
return Response::RespNameNotFound;
}
c->setCount(c->getCount() + cmd.delta());
Event_SetCounter event;
event.set_counter_id(c->getId());
event.set_value(c->getCount());
ges.enqueueGameEvent(event, playerId);
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdCreateCounter(const Command_CreateCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
auto *c = new Server_Counter(newCounterId(), nameFromStdString(cmd.counter_name()), cmd.counter_color(),
cmd.radius(), cmd.value());
addCounter(c);
Event_CreateCounter event;
ServerInfo_Counter *counterInfo = event.mutable_counter_info();
counterInfo->set_id(c->getId());
counterInfo->set_name(c->getName().toStdString());
counterInfo->mutable_counter_color()->CopyFrom(cmd.counter_color());
counterInfo->set_radius(c->getRadius());
counterInfo->set_count(c->getCount());
ges.enqueueGameEvent(event, playerId);
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
Server_Counter *c = counters.value(cmd.counter_id(), 0);
if (!c) {
return Response::RespNameNotFound;
}
c->setCount(cmd.value());
Event_SetCounter event;
event.set_counter_id(c->getId());
event.set_value(c->getCount());
ges.enqueueGameEvent(event, playerId);
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded) {
return Response::RespContextError;
}
Server_Counter *counter = counters.value(cmd.counter_id(), 0);
if (!counter) {
return Response::RespNameNotFound;
}
counters.remove(cmd.counter_id());
delete counter;
Event_DelCounter event;
event.set_counter_id(cmd.counter_id());
ges.enqueueGameEvent(event, playerId);
return Response::RespOk;
}
Response::ResponseCode
Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (conceded && !judge) {
return Response::RespContextError;
}
game->nextTurn();
return Response::RespOk;
}
Response::ResponseCode Server_Player::cmdSetActivePhase(const Command_SetActivePhase &cmd,
ResponseContainer & /*rc*/,
GameEventStorage & /*ges*/)
{
if (!game->getGameStarted()) {
return Response::RespGameNotStarted;
}
if (!judge) {
if (conceded) {
return Response::RespContextError;
}
if (game->getActivePlayer() != playerId) {
return Response::RespContextError;
}
}
game->setActivePhase(cmd.phase());
return Response::RespOk;
}
Response::ResponseCode Server_Player::cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd,
ResponseContainer &rc,
GameEventStorage &ges)
{
auto ret = Server_AbstractPlayer::cmdChangeZoneProperties(cmd, rc, ges);
Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name()));
if (!zone) {
return Response::RespNameNotFound;
}
revealTopCardIfNeeded(zone, ges);
return ret;
}
Response::ResponseCode
Server_Player::cmdReverseTurn(const Command_ReverseTurn &cmd, ResponseContainer &rc, GameEventStorage &ges)
{
if (!judge && conceded) {
return Response::RespContextError;
}
return Server_AbstractParticipant::cmdReverseTurn(cmd, rc, ges);
}
void Server_Player::getInfo(ServerInfo_Player *info,
Server_AbstractParticipant *recipient,
bool omniscient,
bool withUserInfo)
{
Server_AbstractPlayer::getInfo(info, recipient, omniscient, withUserInfo);
for (Server_Counter *counter : counters) {
counter->getInfo(info->add_counter_list());
}
}