diff --git a/README.md b/README.md index d101b1fe..3fdf2e77 100644 --- a/README.md +++ b/README.md @@ -59,5 +59,4 @@ Other client settings are stored in the "settings.ini" file in the game client's ## To do - add move indicator -- test firing on fortress - alternate game modes diff --git a/rsc/data/objects/regular.mtl b/rsc/data/objects/regular.mtl index b1f52f1d..5a6cb8bc 100644 --- a/rsc/data/objects/regular.mtl +++ b/rsc/data/objects/regular.mtl @@ -34,7 +34,7 @@ newmtl plains Ka 0 0 0 Kd 0.157 1 0.157 Ks 0.5 0.5 0.5 -Ke 0 0 0 +Ke 0.157 1 0.157 Ns 96 d 1 @@ -42,7 +42,7 @@ newmtl forest Ka 0 0 0 Kd 0 0.627 0 Ks 0.5 0.5 0.5 -Ke 0 0 0 +Ke 0 0.627 0 Ns 96 d 1 @@ -50,7 +50,7 @@ newmtl mountain Ka 0 0 0 Kd 0.471 0.471 0.471 Ks 0.5 0.5 0.5 -Ke 0 0 0 +Ke 0.471 0.471 0.471 Ns 96 d 1 @@ -58,7 +58,7 @@ newmtl water Ka 0 0 0 Kd 0.157 0.784 1 Ks 0.5 0.5 0.5 -Ke 0 0 0 +Ke 0.157 0.784 1 Ns 96 d 1 @@ -66,15 +66,7 @@ newmtl fortress Ka 0 0 0 Kd 0.549 0.275 0.078 Ks 0.5 0.5 0.5 -Ke 0 0 0 -Ns 96 -d 1 - -newmtl rubble -Ka 0 0 0 -Kd 0.274, 0.137, 0.039 -Ks 0.5 0.5 0.5 -Ke 0 0 0 +Ke 0.549 0.275 0.078 Ns 96 d 1 @@ -82,7 +74,7 @@ newmtl ally Ka 0 0 0 Kd 1 1 1 Ks 0.5 0.5 0.5 -Ke 0 0 0 +Ke 1 1 1 Ns 96 d 1 diff --git a/rsc/data/objects/regular.obj b/rsc/data/objects/regular.obj index b7a9bbc8..151d2d31 100644 --- a/rsc/data/objects/regular.obj +++ b/rsc/data/objects/regular.obj @@ -29,12 +29,11 @@ vt 0.1 0.1 vt 0.9 0.1 vt 0.9 0.9 vt 0.1 0.9 -f 1/1/1 6/6/1 2/2/1 -f 1/1/1 5/5/1 6/6/1 -f 2/2/1 7/7/1 3/3/1 -f 2/2/1 6/6/1 7/7/1 -f 3/3/1 6/6/1 4/4/1 -f 3/3/1 7/7/1 6/6/1 -f 4/4/1 5/5/1 1/1/1 -f 4/4/1 6/6/1 5/5/1 - +f 5/5/2 10/10/2 6/6/2 +f 5/5/2 9/9/2 10/10/2 +f 6/6/2 11/11/2 7/7/2 +f 6/6/2 10/10/2 11/11/2 +f 7/7/2 12/12/2 8/8/2 +f 7/7/2 11/11/2 12/12/2 +f 8/8/2 9/9/2 5/5/2 +f 8/8/2 12/12/2 9/9/2 diff --git a/src/engine/fileSys.cpp b/src/engine/fileSys.cpp index 805f0657..76af2de8 100644 --- a/src/engine/fileSys.cpp +++ b/src/engine/fileSys.cpp @@ -191,7 +191,7 @@ uint8 FileSys::readFace(const char* str, Blueprint& obj, const array 0 && pid < size) - return pid; - if (ushort eid = size + ushort(id); id < 0 && eid < size) - return eid; +ushort FileSys::resolveObjId(int id, ushort ofs, ushort size) { + if (ushort pid = id - ofs; id > 0 && pid <= size) + return pid - 1; + if (id < 0 && -id <= size) + return size - id; return size; } diff --git a/src/engine/fileSys.h b/src/engine/fileSys.h index 3c6a387a..3703951e 100644 --- a/src/engine/fileSys.h +++ b/src/engine/fileSys.h @@ -55,34 +55,7 @@ enum FileType : uint8 { FTYPE_OTH = 0x4, FTYPE_STD = FTYPE_REG | FTYPE_DIR }; - -inline constexpr FileType operator~(FileType a) { - return FileType(~uint8(a)); -} - -inline constexpr FileType operator&(FileType a, FileType b) { - return FileType(uint8(a) & uint8(b)); -} - -inline constexpr FileType operator&=(FileType& a, FileType b) { - return a = FileType(uint8(a) & uint8(b)); -} - -inline constexpr FileType operator^(FileType a, FileType b) { - return FileType(uint8(a) ^ uint8(b)); -} - -inline constexpr FileType operator^=(FileType& a, FileType b) { - return a = FileType(uint8(a) ^ uint8(b)); -} - -inline constexpr FileType operator|(FileType a, FileType b) { - return FileType(uint8(a) | uint8(b)); -} - -inline constexpr FileType operator|=(FileType& a, FileType b) { - return a = FileType(uint8(a) | uint8(b)); -} +ENUM_OPERATIONS(FileType, uint8) // handles all filesystem interactions class FileSys { @@ -133,7 +106,7 @@ class FileSys { private: static uint8 readFace(const char* str, Blueprint& obj, const array& begins); - static ushort resolveObjId(int id, ushort size); + static ushort resolveObjId(int id, ushort ofs, ushort size); static void fillUpObj(uint8& fill, Blueprint& obj); static int setWorkingDir(); diff --git a/src/engine/scene.h b/src/engine/scene.h index 20cb7f11..bc1d40c5 100644 --- a/src/engine/scene.h +++ b/src/engine/scene.h @@ -60,34 +60,7 @@ struct Keyframe { Keyframe(float time, Change change, const vec3& pos = vec3(), const vec3& rot = vec3()); }; - -inline constexpr Keyframe::Change operator~(Keyframe::Change a) { - return Keyframe::Change(~uint8(a)); -} - -inline constexpr Keyframe::Change operator&(Keyframe::Change a, Keyframe::Change b) { - return Keyframe::Change(uint8(a) & uint8(b)); -} - -inline constexpr Keyframe::Change operator&=(Keyframe::Change& a, Keyframe::Change b) { - return a = Keyframe::Change(uint8(a) & uint8(b)); -} - -inline constexpr Keyframe::Change operator^(Keyframe::Change a, Keyframe::Change b) { - return Keyframe::Change(uint8(a) ^ uint8(b)); -} - -inline constexpr Keyframe::Change operator^=(Keyframe::Change& a, Keyframe::Change b) { - return a = Keyframe::Change(uint8(a) ^ uint8(b)); -} - -inline constexpr Keyframe::Change operator|(Keyframe::Change a, Keyframe::Change b) { - return Keyframe::Change(uint8(a) | uint8(b)); -} - -inline constexpr Keyframe::Change operator|=(Keyframe::Change& a, Keyframe::Change b) { - return a = Keyframe::Change(uint8(a) | uint8(b)); -} +ENUM_OPERATIONS(Keyframe::Change, uint8) // a sequence of keyframes applied to an object class Animation { diff --git a/src/prog/game.cpp b/src/prog/game.cpp index 77a57542..c8e4a5c6 100644 --- a/src/prog/game.cpp +++ b/src/prog/game.cpp @@ -1,23 +1,39 @@ #include "engine/world.h" +// FAVORING STATE + +FavorState::FavorState() : + piece(nullptr), + use(false) +{} + // RECORD -Game::Record::Record(Piece* actor, Piece* victim, vec2s actorPos, vec2s victimPos, bool attack, bool swap, bool restore) : +Record::Record(Piece* actor, Piece* swapper, Action action) : actor(actor), - victim(victim), - actorPos(actorPos), - victimPos(victimPos), - attack(attack), - swap(swap), - restore(restore) + swapper(swapper), + action(action) {} -// FAVORING STATE +void Record::update(Piece* doActor, Piece* didSwap, Action didActions) { + actor = doActor; + swapper = didSwap ? didSwap : swapper; + action |= didActions; +} -Game::FavorState::FavorState() : - piece(nullptr), - use(false) -{} +void Record::updateProtectionColors(bool on) const { + updateProtectionColor(actor, on); + updateProtectionColor(swapper, on); +} + +void Record::updateProtectionColor(Piece* pce, bool on) const { + if (pce) + pce->diffuseFactor = pieceProtected(pce) && on ? 0.5f : 1.f; +} + +bool Record::isProtectedMember(Piece* pce) const { + return pce && (actor == pce || swapper == pce) && pieceProtected(pce); +} // GAME @@ -113,20 +129,15 @@ void Game::processCode(Com::Code code, const uint8* data) { case Com::Code::move: pieces.ene(reinterpret_cast(data)[0])->pos = gtop(idToPos(reinterpret_cast(data)[1]), BoardObject::upperPoz); break; - case Com::Code::kill: { - Piece* piece = &pieces[*reinterpret_cast(data)]; - if (isOwnPiece(piece) && favorCount && getTile(ptog(piece->pos))->getType() == Com::Tile::forest) - static_cast(World::state())->updateNegateIcon(true); - piece->disable(); - break; } + case Com::Code::kill: + pieces[*reinterpret_cast(data)].disable(); + break; case Com::Code::breach: (tiles.begin() + *reinterpret_cast(data + 1))->setBreached(data[0]); break; case Com::Code::record: - if (record = Record(&pieces[reinterpret_cast(data)[0]], &pieces[reinterpret_cast(data)[1]], idToPos(reinterpret_cast(data)[2]), idToPos(reinterpret_cast(data)[3]), data[8], data[9], data[10]); record.restore) { - record.actor->pos = gtop(record.actorPos, BoardObject::upperPoz); - record.victim->enable(record.victimPos); - } + eneRec = Record(&pieces[*reinterpret_cast(data + 1)], &pieces[*reinterpret_cast(data + 3)], Record::Action(data[0])); + ownRec = Record(); myTurn = true; prepareTurn(); break; @@ -162,8 +173,8 @@ vector Game::initObjects() { } void Game::uninitObjects() { - setTilesInteract(tiles.begin(), config.boardSize, Tile::Interactivity::off); - setPiecesInteract(pieces.begin(), config.piecesSize, false); + setTilesInteract(tiles.begin(), config.boardSize, Tile::Interactivity::ignore); + setOwnPiecesInteract(false); } void Game::prepareMatch() { @@ -172,9 +183,9 @@ void Game::prepareMatch() { it.setRaycast(myTurn); // interactivity is already reset during setup phase for (Piece* it = pieces.own(); it != pieces.ene(); it++) { it->setRaycast(myTurn); - it->setHgcall(&Program::eventFavorStart); - it->setUlcall(&Program::eventMove); - it->setUrcall(it->canFire() ? &Program::eventFire : nullptr); + it->hgcall = &Program::eventFavorStart; + it->ulcall = &Program::eventMove; + it->urcall = it->canFire() ? &Program::eventFire : nullptr; } for (Piece* it = pieces.ene(); it != pieces.end(); it++) { it->mode |= Object::INFO_SHOW; @@ -292,7 +303,7 @@ void Game::takeOutFortress() { void Game::updateFavorState() { if (!favorState.piece || !favorState.use || !favorCount) World::scene()->effects[0].mode &= ~Object::INFO_SHOW; - else if (Tile* til = getTile(ptog(favorState.piece->pos)); til->getType() < Com::Tile::fortress && til->getType() != Com::Tile::forest) { + else if (Tile* til = getTile(ptog(favorState.piece->pos)); til->getType() < Com::Tile::fortress) { World::scene()->effects[0].pos = vec3(favorState.piece->pos.x, World::scene()->effects[0].pos.y, favorState.piece->pos.z); World::scene()->effects[0].mode |= Object::INFO_SHOW; } @@ -314,6 +325,10 @@ void Game::useFavor() { void Game::pieceMove(Piece* piece, vec2s pos, Piece* occupant) { // check attack, favor, move and survival conditions + if (ownRec.action & Record::ACT_MOVE) { + static_cast(World::state())->message->setText("You've already moved"); + return; + } vec2s spos = ptog(piece->pos); bool attacking = occupant && !isOwnPiece(occupant); Com::Tile favored = pollFavor(); @@ -332,22 +347,25 @@ void Game::pieceMove(Piece* piece, vec2s pos, Piece* occupant) { removePiece(occupant); placePiece(piece, pos); } - record = Record(piece, occupant, spos, pos, true, false, false); + ownRec.update(piece, nullptr, Record::ACT_MOVE | Record::ACT_ATCK); } else if (occupant) { // switch ally placePiece(occupant, spos); placePiece(piece, pos); - record = Record(piece, occupant, spos, pos, false, true, false); + ownRec.update(piece, occupant, Record::ACT_MOVE | Record::ACT_SWAP); } else { // regular move if (Tile* til = getTile(spos); til->isBreachedFortress()) updateFortress(til, false); // restore fortress placePiece(piece, pos); - record = Record(piece, nullptr, spos, pos, false, false, false); + ownRec.update(piece, nullptr, Record::ACT_MOVE); } - if (!checkWin()) - endTurn(); + concludeAction(); } void Game::pieceFire(Piece* killer, vec2s pos, Piece* piece) { + if (ownRec.action & Record::ACT_FIRE) { + static_cast(World::state())->message->setText("You've already fired"); + return; + } vec2s kpos = ptog(killer->pos); Com::Tile favored = pollFavor(); if (!checkFire(killer, kpos, piece, pos)) // check rules @@ -359,15 +377,25 @@ void Game::pieceFire(Piece* killer, vec2s pos, Piece* piece) { if (Tile* til = getTile(pos); til->isUnbreachedFortress()) { // breach fortress updateFortress(til, true); - record = Record(killer, nullptr, kpos, pos, true, false, false); - endTurn(); + ownRec.update(killer, nullptr, Record::ACT_FIRE); } else { // regular fire if (til->isBreachedFortress()) updateFortress(til, false); // restore fortress removePiece(piece); - record = Record(killer, piece, kpos, pos, true, false, false); - if (!checkWin()) + ownRec.update(killer, nullptr, Record::ACT_FIRE); + } + concludeAction(); +} + +void Game::concludeAction() { + if (!checkWin()) { + if (!ownRec.actor->canFire() || (ownRec.action & (Record::ACT_MOVE | Record::ACT_FIRE)) == (Record::ACT_MOVE | Record::ACT_FIRE) || ((ownRec.action & Record::ACT_MOVE) && firstTurn)) endTurn(); + else { + setOwnPiecesInteract(false, true); + ownRec.actor->setRaycast(true); + static_cast(World::state())->message->setText(""); + } } } @@ -381,14 +409,6 @@ void Game::placeDragon(vec2s pos, Piece* occupant) { } } -void Game::negateAttack() { - useFavor(); - record.actor->pos = gtop(record.actorPos, BoardObject::upperPoz); - record.victim->enable(record.victimPos); - record = Record(record.actor, record.victim, record.actorPos, record.victimPos, false, false, true); - endTurn(); -} - void Game::setBgrid() { uint16 i = 0; uint16 boardHeight = config.homeHeight * 2 + 1; @@ -428,14 +448,14 @@ void Game::setPieces(Piece* pieces, OCall ucall, const Material* mat, Object::In } } -void Game::setTilesInteract(Tile* tiles, uint16 num, Tile::Interactivity lvl) { +void Game::setTilesInteract(Tile* tiles, uint16 num, Tile::Interactivity lvl, bool dim) { for (uint16 i = 0; i < num; i++) - tiles[i].setInteractivity(lvl); + tiles[i].setInteractivity(lvl, dim); } -void Game::setPiecesInteract(Piece* pieces, uint16 num, bool on) { +void Game::setPiecesInteract(Piece* pieces, uint16 num, bool on, bool dim) { for (uint16 i = 0; i < num; i++) - pieces[i].setRaycast(on); + pieces[i].setRaycast(on, dim); } void Game::setPiecesVisible(Piece* pieces, bool on) { @@ -455,17 +475,31 @@ Piece* Game::findPiece(vec2s pos) { } bool Game::checkMove(Piece* piece, vec2s pos, Piece* occupant, vec2s dst, bool attacking, Com::Tile favor) { - if (pos == dst) + if (pos == dst) { + static_cast(World::state())->message->setText("Can't swap with yourself"); return false; - - Tile* dtil = getTile(pos); + } + Tile* dtil = getTile(dst); if (attacking) { - if (!checkAttack(piece, occupant, dtil) || firstTurn) + if (firstTurn) { + static_cast(World::state())->message->setText("Can't attack on first turn"); return false; + } if (dtil->isUnbreachedFortress()) return checkMoveBySingle(pos, dst, favor); - } else if (occupant) - return piece->getType() != occupant->getType() ? checkMoveBySingle(pos, dst, favor) : false; + else if (!checkAttack(piece, occupant, dtil)) + return false; + } else if (occupant) { + if (piece->getType() == occupant->getType()) { + static_cast(World::state())->message->setText("Can't swap with a piece of the same type"); + return false; + } + if (favor == Com::Tile::forest) { + useFavor(); + return true; + } + return checkMoveBySingle(pos, dst, favor); + } switch (piece->getType()) { case Com::Piece::spearman: @@ -483,12 +517,16 @@ bool Game::checkMoveByArea(vec2s pos, vec2s dst, Com::Tile favor, uint16 dlim, b useFavor(); dlim++; } - return Dijkstra::travelDist(posToId(pos), dlim, config.homeWidth, config.boardSize, stepable, vmov, movSize)[posToId(dst)] <= dlim; + if (Dijkstra::travelDist(posToId(pos), dlim, config.homeWidth, config.boardSize, stepable, vmov, movSize)[posToId(dst)] > dlim) { + static_cast(World::state())->message->setText("Can't move there"); + return false; + } + return true; } bool Game::spaceAvailible(uint16 pos) { Piece* occ = World::game()->findPiece(World::game()->idToPos(pos)); - return !occ || (World::game()->isOwnPiece(occ) && occ->getType() != Com::Piece::dragon && !occ->canFire()); + return !occ || World::game()->isOwnPiece(occ) || (occ->getType() != Com::Piece::dragon && !occ->canFire()); } bool Game::checkMoveByType(vec2s pos, vec2s dst, Com::Tile favor) { @@ -498,9 +536,6 @@ bool Game::checkMoveByType(vec2s pos, vec2s dst, Com::Tile favor) { bool Game::checkAdjacentTilesByType(uint16 pos, uint16 dst, vector& visited, Com::Tile type) const { visited[pos] = true; - if (pos == dst) - return true; - for (uint16 (*mov)(uint16, uint16) : adjacentFull) if (uint16 ni = mov(pos, config.homeWidth); ni < config.boardSize && tiles[ni].getType() == type && !visited[ni] && checkAdjacentTilesByType(ni, dst, visited, type)) return true; @@ -508,44 +543,64 @@ bool Game::checkAdjacentTilesByType(uint16 pos, uint16 dst, vector& visite } bool Game::checkFire(Piece* killer, vec2s pos, Piece* victim, vec2s dst) { - if (!killer->canFire() || pos == dst || firstTurn) + if (!victim) { + static_cast(World::state())->message->setText("Can't fire at nobody"); + return false; + } + if (firstTurn) { + static_cast(World::state())->message->setText("Can't fire on first turn"); + return false; + } + if (pos == dst) { + static_cast(World::state())->message->setText("Suicide is not yet implemented"); + return false; + } + if (isOwnPiece(victim)) { + static_cast(World::state())->message->setText("Can't fire at an ally"); return false; - if (Tile* dtil = getTile(dst); dtil->getType() != Com::Tile::fortress || dtil->isBreachedFortress()) { + } + if (Tile* dtil = getTile(dst); dtil->isPenetrable()) { // check rules of directly firing at a piece if (!checkAttack(killer, victim, dtil)) return false; - if (victim->getType() == Com::Piece::ranger && dtil->getType() == Com::Tile::mountain) + if (victim->getType() == Com::Piece::ranger && dtil->getType() == Com::Tile::mountain) { + static_cast(World::state())->message->setText("Can't fire at a " + Com::pieceNames[uint8(victim->getType())] + " in the " + Com::tileNames[uint8(dtil->getType())] + 's'); return false; - if (killer->getType() == Com::Piece::crossbowman && (dtil->getType() == Com::Tile::forest || dtil->getType() == Com::Tile::mountain)) + } + if (killer->getType() == Com::Piece::crossbowman && (dtil->getType() == Com::Tile::forest || dtil->getType() == Com::Tile::mountain)) { + static_cast(World::state())->message->setText(firstUpper(Com::pieceNames[uint8(killer->getType())]) + " can't fire at " + (dtil->getType() == Com::Tile::forest ? "a ": "") + Com::tileNames[uint8(dtil->getType())] + (dtil->getType() == Com::Tile::mountain ? "s" : "")); return false; - if (killer->getType() == Com::Piece::catapult && dtil->getType() == Com::Tile::forest) + } + if (killer->getType() == Com::Piece::catapult && dtil->getType() == Com::Tile::forest) { + static_cast(World::state())->message->setText(firstUpper(Com::pieceNames[uint8(killer->getType())]) + " can't fire at a " + Com::tileNames[uint8(dtil->getType())] + 's'); return false; + } } - return checkTilesByDistance(pos, dst, int16(killer->getType()) - int16(Com::Piece::crossbowman) + 1); + if (int16 dist = int16(killer->getType()) - int16(Com::Piece::crossbowman) + 1; !checkTilesByDistance(pos, dst, dist)) { + static_cast(World::state())->message->setText(firstUpper(Com::pieceNames[uint8(killer->getType())]) + " can only fire at a distance of " + to_string(dist)); + return false; + } + return true; } bool Game::checkTilesByDistance(vec2s pos, vec2s dst, int16 dist) { - if (dst.x == pos.x) - return dst.y == pos.y - dist || dst.y == pos.y + dist; - if (dst.y == pos.y) - return dst.x == pos.x - dist || dst.x == pos.x + dist; - return false; + vec2s dlt = dst - pos; + return (!dlt.x || std::abs(dlt.x) == dist) && (!dlt.y || std::abs(dlt.y) == dist); } bool Game::checkAttack(Piece* killer, Piece* victim, Tile* dtil) { - if (dtil->isUnbreachedFortress()) - return true; - if (!victim || isOwnPiece(victim) || (record.restore && victim == record.victim)) - return false; if (killer->getType() == Com::Piece::throne) return true; - - switch (victim->getType()) { - case Com::Piece::warhorse: - return victim != record.actor || !(record.attack || record.swap); - case Com::Piece::elephant: - return dtil->getType() != Com::Tile::plains || killer->getType() == Com::Piece::dragon; - case Com::Piece::dragon: - return dtil->getType() != Com::Tile::forest; + if (eneRec.isProtectedMember(victim)) { + static_cast(World::state())->message->setText(firstUpper(Com::pieceNames[uint8(victim->getType())]) + " is protected"); + return false; + } + if (killer->getType() == Com::Piece::dragon && dtil->getType() == Com::Tile::forest) { + static_cast(World::state())->message->setText(firstUpper(Com::pieceNames[uint8(killer->getType())]) + " can't attack a " + Com::tileNames[uint8(dtil->getType())]); + return false; + } + if (victim->getType() == Com::Piece::elephant && dtil->getType() == Com::Tile::plains && killer->getType() != Com::Piece::dragon) { + static_cast(World::state())->message->setText(firstUpper(Com::pieceNames[uint8(killer->getType())]) + " can't attack an " + Com::pieceNames[uint8(victim->getType())] + " on " + Com::tileNames[uint8(dtil->getType())]); + return false; } return true; } @@ -555,7 +610,7 @@ bool Game::survivalCheck(Piece* piece, vec2s spos, vec2s dpos, bool attacking, b Com::Tile dst = attacking ? getTile(dpos)->getType() : Com::Tile::empty; // dst only relevant when attacking if ((src != Com::Tile::mountain && src != Com::Tile::water && dst != Com::Tile::mountain && dst != Com::Tile::water) || piece->getType() == Com::Piece::dragon) return true; - if ((piece->getType() == Com::Piece::ranger && src != Com::Tile::water && dst == Com::Tile::water) || (piece->getType() == Com::Piece::spearman && src != Com::Tile::mountain && dst == Com::Tile::mountain)) + if ((piece->getType() == Com::Piece::ranger && src == Com::Tile::mountain && dst != Com::Tile::water) || (piece->getType() == Com::Piece::spearman && src == Com::Tile::water && dst != Com::Tile::mountain)) return true; if ((switching && favor == Com::Tile::water) || favor == Com::Tile::mountain) { useFavor(); @@ -592,14 +647,14 @@ bool Game::checkThroneWin(Piece* thrones) { } bool Game::checkFortressWin() { - if (uint16 cnt = config.winFortress) - for (Tile* tit = tiles.ene(); tit != tiles.mid(); tit++) - if (Piece* pit = pieces.own(); tit->getType() == Com::Tile::fortress) - for (uint8 pi = 0; pi < config.capturers.size(); pit += config.pieceAmounts[pi++]) // kill me - if (config.capturers[pi]) - for (uint16 i = 0; i < config.pieceAmounts[pi]; i++) - if (tileId(tit) == posToId(ptog(pit[i].pos)) && !--cnt) - return true; + if (uint16 cnt = config.winFortress) // if there's a fortress quota + for (Tile* tit = tiles.ene(); tit != tiles.mid(); tit++) // iterate enemy tiles + if (Piece* pit = pieces.own(); tit->getType() == Com::Tile::fortress) // if tile is an enemy fortress + for (uint8 pi = 0; pi < Com::pieceMax; pit += config.pieceAmounts[pi++]) // iterate piece types + if (config.capturers[pi]) // if the piece type is a capturer + for (uint16 i = 0; i < config.pieceAmounts[pi]; i++) // iterate pieces of that type + if (tileId(tit) == posToId(ptog(pit[i].pos)) && !--cnt) // if such a piece is on the fortress + return true; // decrement fortress counter and win if 0 return false; } @@ -611,25 +666,23 @@ bool Game::doWin(bool win) { } void Game::prepareTurn() { - setTilesInteract(tiles.ene(), config.numTiles, Tile::Interactivity(myTurn)); - setMidTilesInteract(Tile::Interactivity(myTurn)); - setOwnTilesInteract(Tile::Interactivity(myTurn)); + setTilesInteract(tiles.begin(), config.boardSize, Tile::Interactivity(myTurn)); setPiecesInteract(pieces.begin(), config.piecesSize, myTurn); + (myTurn ? eneRec : ownRec).updateProtectionColors(true); ProgMatch* pm = static_cast(World::state()); pm->message->setText(myTurn ? messageTurnGo : messageTurnWait); pm->updateFavorIcon(myTurn, favorCount, favorTotal); + pm->updateTurnIcon(myTurn); pm->setDragonIcon(myTurn); } void Game::endTurn() { firstTurn = myTurn = false; - static_cast(World::state())->updateNegateIcon(false); prepareTurn(); - netcp->push(Com::Code::record); - netcp->push(vector({ inversePieceId(record.actor), inversePieceId(record.victim), invertId(posToId(record.actorPos)), invertId(posToId(record.victimPos)) })); - netcp->push(vector({ uint8(record.attack), uint8(record.swap), uint8(record.restore) })); + netcp->push(vector({ uint8(Com::Code::record), uint8(ownRec.action) })); + netcp->push(vector({ inversePieceId(ownRec.actor), inversePieceId(ownRec.swapper) })); netcp->sendData(); } diff --git a/src/prog/game.h b/src/prog/game.h index 45de2108..4be6fdec 100644 --- a/src/prog/game.h +++ b/src/prog/game.h @@ -3,15 +3,47 @@ #include "netcp.h" #include "utils/objects.h" +struct FavorState { + Piece* piece; // when dragging piece + bool use; // when holding down lalt + + FavorState(); +}; + +class Record { +public: + enum Action : uint8 { + ACT_NONE = 0, + ACT_MOVE = 1, + ACT_SWAP = 2, + ACT_ATCK = 4, + ACT_FIRE = 8 + }; + + Piece* actor; // the moved piece/the killer + Piece* swapper; // who swapped with acter + Action action; + +public: + Record(Piece* actor = nullptr, Piece* swapper = nullptr, Action action = ACT_NONE); + + void update(Piece* doActor, Piece* didSwap, Action didActions); + void updateProtectionColors(bool on) const; + bool isProtectedMember(Piece* pce) const; +private: + void updateProtectionColor(Piece* pce, bool on) const; + bool pieceProtected(Piece* pce) const; // piece mustn't be nullptr +}; +ENUM_OPERATIONS(Record::Action, uint8) + +inline bool Record::pieceProtected(Piece* pce) const { + return pce->getType() == Com::Piece::warhorse && (action & (ACT_SWAP | ACT_ATCK | ACT_FIRE)); +} + // handles game logic class Game { public: - struct FavorState { - Piece* piece; // when dragging piece - bool use; // when holding down lalt - - FavorState(); - } favorState; // favor state when dragging piece + FavorState favorState; // favor state when dragging piece static const vec2 screenPosUp, screenPosDown; static constexpr char messageTurnGo[] = "Your turn"; @@ -32,14 +64,7 @@ class Game { TileCol tiles; PieceCol pieces; - struct Record { - Piece* actor; // the moved piece/the killer - Piece* victim; // the attacked/restored piece - vec2s actorPos, victimPos; // necessary for restoring - bool attack, swap, restore; - - Record(Piece* actor = nullptr, Piece* victim = nullptr, vec2s actorPos = INT16_MIN, vec2s victimPos = INT16_MIN, bool attack = false, bool swap = false, bool restore = false); - } record; // what happened the previous turn + Record ownRec, eneRec; // what happened the previous turn bool myTurn, firstTurn; uint8 favorCount, favorTotal; @@ -73,9 +98,9 @@ class Game { #endif void uninitObjects(); void prepareMatch(); - void setOwnTilesInteract(Tile::Interactivity lvl); - void setMidTilesInteract(Tile::Interactivity lvl); - void setOwnPiecesInteract(bool on); + void setOwnTilesInteract(Tile::Interactivity lvl, bool dim = false); + void setMidTilesInteract(Tile::Interactivity lvl, bool dim = false); + void setOwnPiecesInteract(bool on, bool dim = false); void setOwnPiecesVisible(bool on); void checkOwnTiles() const; // throws error string on failure void checkMidTiles() const; // ^ @@ -90,7 +115,7 @@ class Game { void pieceMove(Piece* piece, vec2s pos, Piece* occupant); void pieceFire(Piece* killer, vec2s pos, Piece* piece); void placeDragon(vec2s pos, Piece* occupant); - void negateAttack(); + void endTurn(); private: void connect(Netcp* net); @@ -99,8 +124,8 @@ class Game { void setMidTiles(); void setTiles(Tile* tiles, int16 yofs, Object::Info mode); void setPieces(Piece* pieces, OCall ucall, const Material* mat, Object::Info mode); - static void setTilesInteract(Tile* tiles, uint16 num, Tile::Interactivity lvl); - static void setPiecesInteract(Piece* pieces, uint16 num, bool on); + static void setTilesInteract(Tile* tiles, uint16 num, Tile::Interactivity lvl, bool dim = false); + static void setPiecesInteract(Piece* pieces, uint16 num, bool on, bool dim = false); void setPiecesVisible(Piece* pieces, bool on); static vector countTiles(const Tile* tiles, uint16 num, vector cnt); template static void setObjectAddrs(T* data, sizet size, vector& dst, sizet& id); @@ -109,6 +134,7 @@ class Game { Com::Tile pollFavor(); void useFavor(); + void concludeAction(); bool checkMove(Piece* piece, vec2s pos, Piece* occupant, vec2s dst, bool attacking, Com::Tile favor); bool checkMoveBySingle(vec2s pos, vec2s dst, Com::Tile favor); bool checkMoveByArea(vec2s pos, vec2s dst, Com::Tile favor, uint16 dlim, bool (*stepable)(uint16), uint16 (*const* vmov)(uint16, uint16), uint8 movSize); @@ -127,7 +153,6 @@ class Game { bool doWin(bool win); // always returns true void prepareTurn(); - void endTurn(); void placePiece(Piece* piece, vec2s pos); // set the position and check if a favor has been gained void removePiece(Piece* piece); // remove from board void updateFortress(Tile* fort, bool breached); @@ -188,16 +213,16 @@ inline uint8 Game::getFavorTotal() const { return favorTotal; } -inline void Game::setOwnTilesInteract(Tile::Interactivity lvl) { - setTilesInteract(tiles.own(), config.numTiles, lvl); +inline void Game::setOwnTilesInteract(Tile::Interactivity lvl, bool dim) { + setTilesInteract(tiles.own(), config.numTiles, lvl, dim); } -inline void Game::setMidTilesInteract(Tile::Interactivity lvl) { - setTilesInteract(tiles.mid(), config.homeWidth, lvl); +inline void Game::setMidTilesInteract(Tile::Interactivity lvl, bool dim) { + setTilesInteract(tiles.mid(), config.homeWidth, lvl, dim); } -inline void Game::setOwnPiecesInteract(bool on) { - setPiecesInteract(pieces.own(), config.numPieces, on); +inline void Game::setOwnPiecesInteract(bool on, bool dim) { + setPiecesInteract(pieces.own(), config.numPieces, on, dim); } inline void Game::setOwnPiecesVisible(bool on) { diff --git a/src/prog/program.cpp b/src/prog/program.cpp index e7a4e565..d6b5f0be 100644 --- a/src/prog/program.cpp +++ b/src/prog/program.cpp @@ -105,13 +105,20 @@ void Program::eventConfigNew(Button*) { World::scene()->setPopup(ProgState::createPopupMessage("Name taken", &Program::eventClosePopup)); } +void Program::eventUpdateSurvivalSL(Button* but) { + ProgHost* ph = static_cast(state.get()); + uint8 prc = static_cast(but)->getVal(); + ph->confs[ph->curConf].survivalPass = prc; + ph->inSurvivalLE->setText(to_string(prc) + '%'); +} + void Program::eventUpdateConfig(Button*) { ProgHost* ph = static_cast(state.get()); Com::Config& cfg = ph->confs[ph->curConf]; cfg.homeWidth = uint16(sstol(ph->inWidth->getText())); cfg.homeHeight = uint16(sstol(ph->inHeight->getText())); - cfg.survivalPass = uint8(sstoul(ph->inSurvival->getText())); + cfg.survivalPass = uint8(sstoul(ph->inSurvivalLE->getText())); cfg.favorLimit = uint8(sstoul(ph->inFavors->getText())); cfg.dragonDist = uint8(sstoul(ph->inDragonDist->getText())); cfg.dragonDiag = ph->inDragonDiag->on; @@ -134,7 +141,8 @@ void Program::eventUpdateConfig(Button*) { void Program::updateConfigWidgets(ProgHost* ph, const Com::Config& cfg) { ph->inWidth->setText(to_string(cfg.homeWidth)); ph->inHeight->setText(to_string(cfg.homeHeight)); - ph->inSurvival->setText(to_string(cfg.survivalPass) + '%'); + ph->inSurvivalSL->setVal(cfg.survivalPass); + ph->inSurvivalLE->setText(to_string(cfg.survivalPass) + '%'); ph->inFavors->setText(to_string(cfg.favorLimit)); ph->inDragonDist->setText(to_string(cfg.dragonDist)); ph->inDragonDiag->on = cfg.dragonDiag; @@ -201,7 +209,7 @@ void Program::placeTile(Tile* tile, uint8 type) { // place the tile tile->setType(Com::Tile(type)); - tile->setInteractivity(Tile::Interactivity::tiling); + tile->setInteractivity(Tile::Interactivity::interact); ps->incdecIcon(type, false, true); } @@ -243,9 +251,9 @@ void Program::eventMoveTile(BoardObject* obj) { if (Tile* src = static_cast(obj); Tile* dst = dynamic_cast(World::scene()->select)) { Com::Tile desType = dst->getType(); dst->setType(src->getType()); - dst->setInteractivity(Tile::Interactivity::tiling); + dst->setInteractivity(Tile::Interactivity::interact); src->setType(desType); - src->setInteractivity(Tile::Interactivity::tiling); + src->setInteractivity(Tile::Interactivity::interact); } } @@ -264,7 +272,7 @@ void Program::eventClearTile() { if (Tile* til = dynamic_cast(World::scene()->select); til && til->getType() != Com::Tile::empty) { static_cast(state.get())->incdecIcon(uint8(til->getType()), true, true); til->setType(Com::Tile::empty); - til->setInteractivity(Tile::Interactivity::tiling); + til->setInteractivity(Tile::Interactivity::interact); } } @@ -320,6 +328,10 @@ void Program::eventOpenMatch() { World::scene()->addAnimation(Animation(World::scene()->getCamera(), queue({ Keyframe(0.5f, Keyframe::CHG_POS | Keyframe::CHG_LAT, Camera::posMatch, Camera::latMatch) }))); } +void Program::eventEndTurn(Button*) { + game.endTurn(); +} + void Program::eventPlaceDragon(Button*) { vec2s pos; if (Piece* pce; pickBob(pos, pce)) @@ -343,10 +355,6 @@ void Program::eventFire(BoardObject* obj) { game.pieceFire(static_cast(obj), pos, pce); } -void Program::eventNegateAttack(Button*) { - game.negateAttack(); -} - void Program::eventExitGame(Button*) { if (dynamic_cast(state.get())) { World::scene()->addAnimation(Animation(game.getScreen(), queue({ Keyframe(0.5f, Keyframe::CHG_POS, vec3(Game::screenPosUp.x, Game::screenPosUp.y, game.getScreen()->pos.z)) }))); @@ -405,6 +413,14 @@ void Program::eventExit(Button*) { World::winSys()->close(); } +void Program::eventSBNext(Button* but) { + static_cast(but->getParent()->getWidget(but->getID() - 1))->onClick(0, SDL_BUTTON_LEFT); +} + +void Program::eventSBPrev(Button* but) { + static_cast(but->getParent()->getWidget(but->getID() + 1))->onClick(0, SDL_BUTTON_RIGHT); +} + void Program::setState(ProgState* newState) { state.reset(newState); World::scene()->resetLayouts(); diff --git a/src/prog/program.h b/src/prog/program.h index 289a503c..e2322657 100644 --- a/src/prog/program.h +++ b/src/prog/program.h @@ -31,6 +31,7 @@ class Program { void eventConfigCopy(Button* but = nullptr); void eventConfigNewInput(Button* but = nullptr); void eventConfigNew(Button* but = nullptr); + void eventUpdateSurvivalSL(Button* but); void eventUpdateConfig(Button* but = nullptr); void eventUpdateReset(Button* but); void eventExitHost(Button* but = nullptr); @@ -51,11 +52,11 @@ class Program { // game match void eventOpenMatch(); + void eventEndTurn(Button* but = nullptr); void eventPlaceDragon(Button* but = nullptr); void eventFavorStart(BoardObject* obj); void eventMove(BoardObject* obj); void eventFire(BoardObject* obj); - void eventNegateAttack(Button* but = nullptr); void eventExitGame(Button* but = nullptr); // settings @@ -71,7 +72,9 @@ class Program { // other void eventClosePopup(Button* but = nullptr); void eventExit(Button* but = nullptr); - + void eventSBNext(Button* but); + void eventSBPrev(Button* but); + ProgState* getState(); Game* getGame(); diff --git a/src/prog/progs.cpp b/src/prog/progs.cpp index 0cbdc318..6c1a22ae 100644 --- a/src/prog/progs.cpp +++ b/src/prog/progs.cpp @@ -4,11 +4,11 @@ ProgState::Text::Text(string str, int height, int margin) : text(std::move(str)), - length(strLength(text, height, margin)), + length(strLen(text, height, margin)), height(height) {} -int ProgState::Text::strLength(const string& str, int height, int margin) { +int ProgState::Text::strLen(const string& str, int height, int margin) { return World::winSys()->textLength(str, height) + margin * 2; } @@ -149,23 +149,26 @@ Layout* ProgHost::createLayout() { for (sizet i = 0; i < confs.size(); i++) cfgNames[i] = confs[i].name; - Text back("Back", superHeight); - Text cfgt("Configuration: ", lineHeight); - Text copy("Copy"); - Text newc("New"); - Text port("Port:", lineHeight); + Text back("Back"); + Text port("Port:"); vector top0 = { new Label(back.length, back.text, &Program::eventExitHost), - new Label(1.f, "Open", &Program::eventHostServer, nullptr, Label::Alignment::center) + new Label(1.f, "Open", &Program::eventHostServer, nullptr, Label::Alignment::center), + new Label(port.length, port.text), + new LabelEdit(World::winSys()->textLength("00000", lineHeight), to_string(World::sets()->port), &Program::eventUpdatePort, nullptr, nullptr, LabelEdit::TextType::uInt) }; + Text cfgt("Configuration: "); + Text aleft(arrowLeft); + Text aright(arrowRight); + Text copy("Copy"); + Text newc("New"); vector top1 = { new Label(cfgt.length, cfgt.text), + new Label(aleft.length, aleft.text, &Program::eventSBPrev), new SwitchBox(1.f, cfgNames.data(), cfgNames.size(), cfgNames[curConf], &Program::eventSwitchConfig, Label::Alignment::center), + new Label(aright.length, aright.text, &Program::eventSBNext), new Label(copy.length, copy.text, &Program::eventConfigCopyInput), - new Label(newc.length, newc.text, &Program::eventConfigNewInput), - new Widget(superSpacing), - new Label(port.length, port.text), - new LabelEdit(World::winSys()->textLength("00000", lineHeight), to_string(World::sets()->port), &Program::eventUpdatePort, nullptr, nullptr, LabelEdit::TextType::uInt), + new Label(newc.length, newc.text, &Program::eventConfigNewInput) }; if (confs.size() > 1) { Text dele("Del"); @@ -197,7 +200,8 @@ Layout* ProgHost::createLayout() { inHeight = new LabelEdit(1.f, to_string(confs[curConf].homeHeight), &Program::eventUpdateConfig, nullptr, &Program::eventUpdateConfig, LabelEdit::TextType::sInt) }, { new Label(descLength, popBack(txs)), - inSurvival = new LabelEdit(1.f, to_string(confs[curConf].survivalPass) + '%', &Program::eventUpdateConfig, nullptr, &Program::eventUpdateConfig) + inSurvivalSL = new Slider(1.f, confs[curConf].survivalPass, 0, 100, &Program::eventUpdateSurvivalSL), + inSurvivalLE = new LabelEdit(Text::strLen("100%"), to_string(confs[curConf].survivalPass) + '%', &Program::eventUpdateConfig, nullptr, &Program::eventUpdateConfig) }, { new Label(descLength, popBack(txs)), inFavors = new LabelEdit(1.f, to_string(confs[curConf].favorLimit), &Program::eventUpdateConfig, nullptr, &Program::eventUpdateConfig, LabelEdit::TextType::uInt) @@ -283,9 +287,12 @@ Layout* ProgHost::createLayout() { setLines(menu, lines5, id); setLines(menu, lineR, id); + vector topb = { + new Layout(lineHeight, std::move(top0), false), + new Layout(lineHeight, std::move(top1), false) + }; vector cont = { - new Layout(superHeight, std::move(top0), false), - new Layout(lineHeight, std::move(top1), false), + new Layout(lineHeight * 2 + Layout::defaultItemSpacing, std::move(topb)), new ScrollArea(1.f, std::move(menu)) }; return new Layout(1.f, std::move(cont), true, superSpacing); @@ -297,7 +304,7 @@ void ProgHost::setLines(vector& menu, vector>& lines, s } void ProgHost::setTitle(vector& menu, string&& title, sizet& id) { - int tlen = Text::strLength(title); + int tlen = Text::strLen(title); vector line = { new Label(tlen, std::move(title)), new Widget() @@ -310,7 +317,7 @@ void ProgHost::setTitle(vector& menu, string&& title, sizet& id) { ProgSetup::ProgSetup() : enemyReady(false), selected(0), - lastHold(nullptr), + lastHold(INT16_MIN), lastButton(0) {} @@ -324,9 +331,10 @@ void ProgSetup::eventWheel(int ymov) { } void ProgSetup::eventDrag(uint32 mStat) { - BoardObject* curHold = dynamic_cast(World::scene()->select); uint8 curButton = mStat & SDL_BUTTON_LMASK ? SDL_BUTTON_LEFT : mStat & SDL_BUTTON_RMASK ? SDL_BUTTON_RIGHT : 0; - if ((curHold && curHold != lastHold) || (curButton && curButton != lastButton)) { + BoardObject* bo = dynamic_cast(World::scene()->select); + vec2s curHold = bo ? World::game()->ptog(bo->pos) : INT16_MIN; + if (bo && curButton && (curHold != lastHold || curButton != lastButton)) { if (stage <= Stage::middles) curButton == SDL_BUTTON_LEFT ? World::program()->eventPlaceTileH() : World::program()->eventClearTile(); else if (stage == Stage::pieces) @@ -337,32 +345,32 @@ void ProgSetup::eventDrag(uint32 mStat) { } void ProgSetup::eventUndrag() { - lastHold = nullptr; + lastHold = INT16_MIN; } bool ProgSetup::setStage(ProgSetup::Stage stg) { switch (stage = stg) { case Stage::tiles: - World::game()->setOwnTilesInteract(Tile::Interactivity::tiling); - World::game()->setMidTilesInteract(Tile::Interactivity::off); + World::game()->setOwnTilesInteract(Tile::Interactivity::interact); + World::game()->setMidTilesInteract(Tile::Interactivity::ignore, true); World::game()->setOwnPiecesVisible(false); counters = World::game()->countOwnTiles(); break; case Stage::middles: - World::game()->setOwnTilesInteract(Tile::Interactivity::off); - World::game()->setMidTilesInteract(Tile::Interactivity::tiling); + World::game()->setOwnTilesInteract(Tile::Interactivity::ignore, true); + World::game()->setMidTilesInteract(Tile::Interactivity::interact); World::game()->setOwnPiecesVisible(false); counters = World::game()->countMidTiles(); break; case Stage::pieces: - World::game()->setOwnTilesInteract(Tile::Interactivity::on); - World::game()->setMidTilesInteract(Tile::Interactivity::off); + World::game()->setOwnTilesInteract(Tile::Interactivity::recognize); + World::game()->setMidTilesInteract(Tile::Interactivity::ignore, true); World::game()->setOwnPiecesVisible(true); counters = World::game()->countOwnPieces(); break; case Stage::ready: - World::game()->setOwnTilesInteract(Tile::Interactivity::off); - World::game()->setMidTilesInteract(Tile::Interactivity::off); + World::game()->setOwnTilesInteract(Tile::Interactivity::ignore); + World::game()->setMidTilesInteract(Tile::Interactivity::ignore); World::game()->setOwnPiecesInteract(false); counters.clear(); return true; @@ -471,14 +479,9 @@ void ProgMatch::updateFavorIcon(bool on, uint8 cnt, uint8 tot) { favorIcon->setText("FF: " + to_string(cnt) + '/' + to_string(tot)); } -void ProgMatch::updateNegateIcon(bool on) { - if (on) { - negateIcon->setLcall(&Program::eventNegateAttack); - negateIcon->setDim(1.f); - } else { - negateIcon->setLcall(nullptr); - negateIcon->setDim(0.5f); - } +void ProgMatch::updateTurnIcon(bool on) { + turnIcon->setLcall(on ? &Program::eventEndTurn : nullptr); + turnIcon->setDim(on ? 1.f : 0.5f); } void ProgMatch::setDragonIcon(bool on) { @@ -500,14 +503,14 @@ void ProgMatch::deleteDragonIcon() { Layout* ProgMatch::createLayout() { // sidebar - int sideLength = Text::strLength("FF: 00/00"); + int sideLength = Text::strLen("FF: 00/00"); vector left = { new Label(lineHeight, "Exit", &Program::eventExitGame), new Widget(0), favorIcon = new Label(lineHeight, "FF: 0/0"), // text is updated after the icon has a parent - negateIcon = new Label(lineHeight, "Negate") + turnIcon = new Label(lineHeight, "End turn") }; - updateNegateIcon(false); + updateTurnIcon(World::game()->getMyTurn()); if (World::game()->ptog(World::game()->getOwnPieces(Com::Piece::dragon)->pos).hasNot(INT16_MIN)) dragonIcon = nullptr; @@ -568,6 +571,8 @@ Layout* ProgSettings::createLayout() { vector samples = { "0", "1", "2", "4" }; // setting buttons, labels and action fields for labels + Text aleft(arrowLeft); + Text aright(arrowRight); Text aptx("Apply", lineHeight); vector txs = { "Screen", @@ -584,26 +589,36 @@ Layout* ProgSettings::createLayout() { vector lx[] = { { new Label(descLength, popBack(txs)), - screen = new SwitchBox(1.f, Settings::screenNames.data(), Settings::screenNames.size(), Settings::screenNames[uint8(World::sets()->screen)]) + new Label(aleft.length, aleft.text, &Program::eventSBPrev), + screen = new SwitchBox(1.f, Settings::screenNames.data(), Settings::screenNames.size(), Settings::screenNames[uint8(World::sets()->screen)]), + new Label(aright.length, aright.text, &Program::eventSBNext) }, { new Label(descLength, popBack(txs)), - winSize = new SwitchBox(1.f, winsiz.data(), winsiz.size(), World::sets()->size.toString(rv2iSeparator)) + new Label(aleft.length, aleft.text, &Program::eventSBPrev), + winSize = new SwitchBox(1.f, winsiz.data(), winsiz.size(), World::sets()->size.toString(rv2iSeparator)), + new Label(aright.length, aright.text, &Program::eventSBNext) }, { new Label(descLength, popBack(txs)), - dspMode = new SwitchBox(1.f, dmodes.data(), dmodes.size(), dispToFstr(World::sets()->mode)) + new Label(aleft.length, aleft.text, &Program::eventSBPrev), + dspMode = new SwitchBox(1.f, dmodes.data(), dmodes.size(), dispToFstr(World::sets()->mode)), + new Label(aright.length, aright.text, &Program::eventSBNext) }, { new Label(descLength, popBack(txs)), - new SwitchBox(1.f, Settings::vsyncNames.data(), Settings::vsyncNames.size(), Settings::vsyncNames[uint8(int8(World::sets()->vsync)+1)], &Program::eventSetVsync) + new Label(aleft.length, aleft.text, &Program::eventSBPrev), + new SwitchBox(1.f, Settings::vsyncNames.data(), Settings::vsyncNames.size(), Settings::vsyncNames[uint8(int8(World::sets()->vsync)+1)], &Program::eventSetVsync), + new Label(aright.length, aright.text, &Program::eventSBNext) }, { new Label(descLength, popBack(txs)), - msample = new SwitchBox(1.f, samples.data(), samples.size(), to_string(World::sets()->samples)) + new Label(aleft.length, aleft.text, &Program::eventSBPrev), + msample = new SwitchBox(1.f, samples.data(), samples.size(), to_string(World::sets()->samples)), + new Label(aright.length, aright.text, &Program::eventSBNext) }, { new Label(descLength, popBack(txs)), new SwitchBox(1.f, Settings::smoothNames.data(), Settings::smoothNames.size(), Settings::smoothNames[uint8(World::sets()->smooth)], &Program::eventSetSmooth) }, { new Label(descLength, popBack(txs)), new Slider(1.f, int(World::sets()->gamma * gammaStepFactor), 0, int(Settings::gammaMax * gammaStepFactor), &Program::eventSetGammaSL), - new LabelEdit(Text::strLength("0.0"), trimZero(to_string(World::sets()->gamma)), &Program::eventSetGammaLE) + new LabelEdit(Text::strLen("0.0"), trimZero(to_string(World::sets()->gamma)), &Program::eventSetGammaLE) }, }; vector lns(lnc + 2); for (sizet i = 0; i < lnc; i++) diff --git a/src/prog/progs.h b/src/prog/progs.h index f5ccd914..24185e1a 100644 --- a/src/prog/progs.h +++ b/src/prog/progs.h @@ -10,6 +10,8 @@ class ProgState { static constexpr int superHeight = 40; static constexpr int superSpacing = 10; static constexpr int iconSize = 64; + static constexpr char arrowLeft[] = "<"; + static constexpr char arrowRight[] = ">"; struct Text { string text; @@ -17,7 +19,7 @@ class ProgState { Text(string str, int height = lineHeight, int margin = Label::defaultTextMargin); - static int strLength(const string& str, int height = lineHeight, int margin = Label::defaultTextMargin); + static int strLen(const string& str, int height = lineHeight, int margin = Label::defaultTextMargin); }; template static int findMaxLength(T pos, T end, int height = lineHeight, int margin = Label::defaultTextMargin); static int findMaxLength(const vector*>& lists, int height = lineHeight, int margin = Label::defaultTextMargin); @@ -44,7 +46,7 @@ template int ProgState::findMaxLength(T pos, T end, int height, int margin) { int width = 0; for (; pos != end; pos++) - if (int len = Text::strLength(*pos, height, margin); len > width) + if (int len = Text::strLen(*pos, height, margin); len > width) width = len; return width; } @@ -65,7 +67,8 @@ class ProgHost : public ProgState { LabelEdit* inWidth; LabelEdit* inHeight; - LabelEdit* inSurvival; + Slider* inSurvivalSL; + LabelEdit* inSurvivalLE; LabelEdit* inFavors; LabelEdit* inDragonDist; CheckBox* inDragonDiag; @@ -126,8 +129,8 @@ class ProgSetup : public ProgState { Stage stage; vector counters; Layout* icons; - BoardObject* lastHold; // last object that the cursor was dragged over - uint8 lastButton; // last button that was used on lastHold (0 for none) + vec2s lastHold; // position of last object that the cursor was dragged over + uint8 lastButton; // last button that was used on lastHold (0 for none) public: ProgSetup(); @@ -181,7 +184,7 @@ class ProgMatch : public ProgState { Label* message; private: Label* favorIcon; - Label* negateIcon; + Label* turnIcon; Layout* dragonIcon; // has to be nullptr if dragon can't be placed anymore public: @@ -189,7 +192,7 @@ class ProgMatch : public ProgState { virtual void eventEscape() override; void updateFavorIcon(bool on, uint8 cnt, uint8 tot); - void updateNegateIcon(bool on); + void updateTurnIcon(bool on); void setDragonIcon(bool on); void deleteDragonIcon(); diff --git a/src/server/server.cpp b/src/server/server.cpp index 0f755dc1..70e3378b 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -25,7 +25,7 @@ Config::Config(string name) : survivalPass(randomLimit / 2), favorLimit(4), dragonDist(4), - dragonDiag(false), + dragonDiag(true), tileAmounts({ 11, 10, 7, 7, 1 }), middleAmounts({ 1, 1, 1, 1 }), pieceAmounts({ 2, 2, 2, 1, 1, 2, 1, 2, 1, 1 }), @@ -93,7 +93,7 @@ uint16 Config::floorAmounts(uint16 total, uint16* amts, uint16 limit, sizet ei, return total; } -uint8* Config::toComData(uint8* data) const { +void Config::toComData(uint8* data) const { data[0] = uint8(Code::setup); // leave second byte free for first turn indicator uint16* sp = reinterpret_cast(data + 2); @@ -116,7 +116,6 @@ uint8* Config::toComData(uint8* data) const { bp = std::copy(capturers.begin(), capturers.end(), reinterpret_cast(sp)); *bp++ = shiftLeft; *bp++ = shiftNear; - return data; } void Config::fromComData(const uint8* data) { @@ -167,7 +166,7 @@ uint16 Config::dataSize(Code code) const { case Code::breach: return sizeof(Code) + sizeof(bool) + sizeof(uint16); case Code::record: - return sizeof(Code) + sizeof(uint16) * 4 + sizeof(bool) * 3; + return sizeof(Code) + sizeof(uint8) + sizeof(uint16) * 2; case Code::win: return sizeof(Code) + sizeof(bool); } diff --git a/src/server/server.h b/src/server/server.h index 718ea1b6..78cd0caf 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -150,7 +150,7 @@ class Config { void updateValues(); Config& checkValues(); - uint8* toComData(uint8* data) const; + void toComData(uint8* data) const; void fromComData(const uint8* data); uint16 dataSize(Code code) const; uint16 tileCompressionEnd() const; diff --git a/src/utils/objects.cpp b/src/utils/objects.cpp index 55663db5..a2ee953f 100644 --- a/src/utils/objects.cpp +++ b/src/utils/objects.cpp @@ -62,8 +62,8 @@ Object::Object(const vec3& pos, const vec3& rot, const vec3& scl, const Blueprin void Object::draw() const { if (mode & INFO_SHOW) { mat->updateColor(); - updateTexture(tex, mode); - updateTransform(pos, rot, scl); + updateTexture(); + updateTransform(); bpr->draw(!(mode & INFO_LINES) ? GL_TRIANGLES : GL_LINES); } } @@ -89,32 +89,34 @@ void Object::updateTexture(const Texture* tex, Info mode) { const vec4 BoardObject::moveIconColor(0.9f, 0.9f, 0.9f, 0.9f); const vec4 BoardObject::fireIconColor(1.f, 0.1f, 0.1f, 0.9f); +const vec4 BoardObject::emissionSelect(0.1f, 0.1f, 0.1f, 1.f); BoardObject::BoardObject(const vec3& pos, float size, OCall hgcall, OCall ulcall, OCall urcall, const Material* mat, const Texture* tex, Info mode) : Object(pos, vec3(0.f), vec3(size, 1.f, size), World::scene()->blueprint("plane"), mat, tex, mode), - dragState(DragState::none), + diffuseFactor(1.f), + emissionFactor(0.f), hgcall(hgcall), ulcall(ulcall), - urcall(urcall) + urcall(urcall), + dragState(DragState::none) {} void BoardObject::draw() const { - if (dragState != DragState::move) - Object::draw(); + if (dragState != DragState::move && (mode & INFO_SHOW)) { + Material::updateColor(mat->ambient, mat->diffuse * vec4(diffuseFactor, diffuseFactor, diffuseFactor, 1.f), mat->specular, mat->emission * (World::scene()->select != this ? vec4(emissionFactor, emissionFactor, emissionFactor, 1.f) : emissionSelect), mat->shine); + updateTexture(); + updateTransform(); + bpr->draw(); + } } void BoardObject::drawTop() const { - if (dragState != DragState::none) { + if (dragState != DragState::none && (mode & INFO_SHOW)) { vec3 ray = World::scene()->pickerRay(mousePos()); - if (dragState == DragState::move) { - mat->updateColorDiffuse(mat->diffuse * moveIconColor); - updateTexture(tex, mode); - } else { - mat->updateColorDiffuse(fireIconColor); - updateTexture(World::winSys()->texture("crosshair"), mode); - } + Material::updateColor(mat->ambient, dragState == DragState::move ? mat->diffuse * moveIconColor : fireIconColor, mat->specular, mat->emission * vec4(emissionFactor, emissionFactor, emissionFactor, 1.f), mat->shine); + updateTexture(dragState == DragState::move ? tex : World::winSys()->texture("crosshair"), mode); updateTransform(World::scene()->getCamera()->pos + ray * (pos.y - upperPoz * 2.f - World::scene()->getCamera()->pos.y / ray.y), rot, scl); - bpr->draw(GL_TRIANGLES); + bpr->draw(); } } @@ -134,6 +136,11 @@ void BoardObject::onUndrag(uint8 mBut) { } } +void BoardObject::setRaycast(bool on, bool dim) { + on ? mode |= INFO_RAYCAST : mode &= ~INFO_RAYCAST; + diffuseFactor = on || !dim ? 1.f : 0.6f; +} + // TILE Tile::Tile(const vec3& pos, float size, Com::Tile type, OCall hgcall, OCall ulcall, OCall urcall, Info mode) : @@ -152,12 +159,12 @@ void Tile::setType(Com::Tile newType) { void Tile::setBreached(bool yes) { breached = yes; - mat = World::scene()->material(breached ? "rubble" : Com::tileNames[uint8(type)]); + diffuseFactor = breached ? 0.6f : 1.f; } -void Tile::setInteractivity(Interactivity lvl) { - setRaycast(lvl != Interactivity::off); - setUlcall(lvl == Interactivity::tiling && type != Com::Tile::empty ? &Program::eventMoveTile : nullptr); +void Tile::setInteractivity(Interactivity lvl, bool dim) { + setRaycast(lvl != Interactivity::ignore, dim); + ulcall = lvl == Interactivity::interact && type != Com::Tile::empty ? &Program::eventMoveTile : nullptr; } // TILE COL diff --git a/src/utils/objects.h b/src/utils/objects.h index 8cae8d5e..d8db21cc 100644 --- a/src/utils/objects.h +++ b/src/utils/objects.h @@ -12,8 +12,6 @@ struct Material { Material(const vec4& ambient = vec4(0.f, 0.f, 0.f, 1.f), const vec4& diffuse = vec4(0.f, 0.f, 0.f, 1.f), const vec4& specular = vec4(0.f, 0.f, 0.f, 1.f), const vec4& emission = vec4(0.f, 0.f, 0.f, 1.f), float shine = 0.f); void updateColor() const; - void updateColorDiffuse(const vec4& difu) const; - void updateColorEmission(const vec4& emis) const; static void updateColor(const vec4& ambient, const vec4& diffuse, const vec4& specular, const vec4& emission, float shine); }; @@ -21,14 +19,6 @@ inline void Material::updateColor() const { updateColor(ambient, diffuse, specular, emission, shine); } -inline void Material::updateColorDiffuse(const vec4& difu) const { - updateColor(ambient, difu, specular, emission, shine); -} - -inline void Material::updateColorEmission(const vec4& emis) const { - updateColor(ambient, diffuse, specular, emis, shine); -} - // indices of vertex, uv, normal for a point struct Vertex { static constexpr uint8 size = 3; @@ -80,7 +70,7 @@ struct Blueprint { Blueprint() = default; Blueprint(vector verts, vector tuvs, vector norms, vector elems); - void draw(GLenum mode) const; + void draw(GLenum mode = GL_TRIANGLES) const; bool empty() const; }; @@ -135,51 +125,39 @@ class Object : public Interactable { virtual void draw() const; protected: + void updateTransform() const; static void updateTransform(const vec3& pos, const vec3& rot, const vec3& scl); + void updateTexture() const; static void updateTexture(const Texture* tex, Info mode); }; +ENUM_OPERATIONS(Object::Info, uint8) -inline constexpr Object::Info operator~(Object::Info a) { - return Object::Info(~uint8(a)); -} - -inline constexpr Object::Info operator&(Object::Info a, Object::Info b) { - return Object::Info(uint8(a) & uint8(b)); -} - -inline constexpr Object::Info operator&=(Object::Info& a, Object::Info b) { - return a = Object::Info(uint8(a) & uint8(b)); -} - -inline constexpr Object::Info operator^(Object::Info a, Object::Info b) { - return Object::Info(uint8(a) ^ uint8(b)); -} - -inline constexpr Object::Info operator^=(Object::Info& a, Object::Info b) { - return a = Object::Info(uint8(a) ^ uint8(b)); -} - -inline constexpr Object::Info operator|(Object::Info a, Object::Info b) { - return Object::Info(uint8(a) | uint8(b)); +inline void Object::updateTransform() const { + updateTransform(pos, rot, scl); } -inline constexpr Object::Info operator|=(Object::Info& a, Object::Info b) { - return a = Object::Info(uint8(a) | uint8(b)); +inline void Object::updateTexture() const { + updateTexture(tex, mode); } // square object on a single plane class BoardObject : public Object { public: static constexpr float upperPoz = 0.001f; + + float diffuseFactor; + float emissionFactor; + OCall hgcall, ulcall, urcall; + private: static const vec4 moveIconColor, fireIconColor; + static const vec4 emissionSelect; enum class DragState : uint8 { none, move, fire } dragState; - OCall hgcall, ulcall, urcall; public: DCLASS_CONSTRUCT(BoardObject, Object) @@ -190,35 +168,16 @@ class BoardObject : public Object { virtual void onHold(vec2i mPos, uint8 mBut) override; virtual void onUndrag(uint8 mBut) override; - void setRaycast(bool on); - void setHgcall(OCall call); - void setUlcall(OCall call); - void setUrcall(OCall call); + void setRaycast(bool on, bool dim = false); }; -inline void BoardObject::setRaycast(bool on) { - on ? mode |= INFO_RAYCAST : mode &= ~INFO_RAYCAST; -} - -inline void BoardObject::setHgcall(OCall call) { - hgcall = call; -} - -inline void BoardObject::setUlcall(OCall call) { - ulcall = call; -} - -inline void BoardObject::setUrcall(OCall call) { - urcall = call; -} - // piece of terrain class Tile : public BoardObject { public: enum class Interactivity : uint8 { - off, - on, - tiling + ignore, + recognize, + interact }; private: @@ -235,9 +194,10 @@ class Tile : public BoardObject { void setType(Com::Tile newType); bool isBreachedFortress() const; bool isUnbreachedFortress() const; + bool isPenetrable() const; bool getBreached() const; void setBreached(bool yes); - void setInteractivity(Interactivity lvl); + void setInteractivity(Interactivity lvl, bool dim = false); private: static Info getModeShow(Info mode, Com::Tile type); }; @@ -254,6 +214,10 @@ inline bool Tile::isUnbreachedFortress() const { return type == Com::Tile::fortress && !breached; } +inline bool Tile::isPenetrable() const { + return type != Com::Tile::fortress || breached; +} + inline bool Tile::getBreached() const { return breached; } diff --git a/src/utils/text.h b/src/utils/text.h index 3c7e6ad0..acb17b6d 100644 --- a/src/utils/text.h +++ b/src/utils/text.h @@ -81,11 +81,7 @@ inline int strncicmp(const string& a, const string& b, sizet n) { // case insens return strncasecmp(a.c_str(), b.c_str(), n); #endif } -#ifdef _WIN32 -inline bool isDriveLetter(const string& path) { - return path.length() >= 2 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':' && std::all_of(path.begin() + 2, path.end(), [](char c) -> bool { return c == dsep; }); -} -#endif + inline bool isSpace(char c) { return (c > '\0' && c <= ' ') || c == 0x7F; } diff --git a/src/utils/utils.h b/src/utils/utils.h index b089f015..8fd2cf52 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -51,6 +51,29 @@ inline vec2i mousePos() { return p; } +#define ENUM_OPERATIONS(EType, IType) \ + inline constexpr EType operator~(EType a) { \ + return EType(~IType(a)); \ + } \ + inline constexpr EType operator&(EType a, EType b) { \ + return EType(IType(a) & IType(b)); \ + } \ + inline constexpr EType operator&=(EType& a, EType b) { \ + return a = EType(IType(a) & IType(b)); \ + } \ + inline constexpr EType operator^(EType a, EType b) { \ + return EType(IType(a) ^ IType(b)); \ + } \ + inline constexpr EType operator^=(EType& a, EType b) { \ + return a = EType(IType(a) ^ IType(b)); \ + } \ + inline constexpr EType operator|(EType a, EType b) { \ + return EType(IType(a) | IType(b)); \ + } \ + inline constexpr EType operator|=(EType& a, EType b) { \ + return a = EType(IType(a) | IType(b)); \ + } + // SDL_Rect wrapper struct Rect : SDL_Rect { diff --git a/src/utils/widgets.cpp b/src/utils/widgets.cpp index d38e6247..2cf2a6e2 100644 --- a/src/utils/widgets.cpp +++ b/src/utils/widgets.cpp @@ -215,7 +215,7 @@ Label::~Label() { Rect Label::draw() const { Rect frm = Picture::draw(); if (textTex.valid()) - drawTexture(&textTex, textRect(), frm); + drawTexture(&textTex, textRect(), frm, colorTexture * dimFactor); return frm; } @@ -315,7 +315,7 @@ void Draglet::onUndrag(uint8 mBut) { SwitchBox::SwitchBox(Size relSize, const string* opts, sizet ocnt, string curOption, BCall call, Alignment alignment, const Texture* bgTex, const vec4& color, bool showColor, int textMargin, int bgMargin, Layout* parent, sizet id) : Label(relSize, std::move(curOption), call, call, alignment, bgTex, color, showColor, textMargin, bgMargin, parent, id), options(opts, opts + ocnt), - curOpt(sizet(std::find(options.begin(), options.end(), curOption) - options.begin())) + curOpt(sizet(std::find(options.begin(), options.end(), text) - options.begin())) { if (curOpt >= options.size()) curOpt = 0; @@ -323,9 +323,9 @@ SwitchBox::SwitchBox(Size relSize, const string* opts, sizet ocnt, string curOpt void SwitchBox::onClick(vec2i mPos, uint8 mBut) { if (mBut == SDL_BUTTON_LEFT) - shiftOption(true); + shiftOption(1); else if (mBut == SDL_BUTTON_RIGHT) - shiftOption(false); + shiftOption(-1); Button::onClick(mPos, mBut); } @@ -333,9 +333,8 @@ bool SwitchBox::selectable() const { return true; } -void SwitchBox::shiftOption(bool fwd) { - if (curOpt += btom(fwd); curOpt >= options.size()) - curOpt = fwd ? 0 : options.size() - 1; +void SwitchBox::shiftOption(long mov) { + curOpt = cycle(curOpt, options.size(), mov); setText(options[curOpt]); } diff --git a/src/utils/widgets.h b/src/utils/widgets.h index 50f19ae2..63862b45 100644 --- a/src/utils/widgets.h +++ b/src/utils/widgets.h @@ -291,7 +291,7 @@ class SwitchBox : public Label { virtual bool selectable() const override; sizet getCurOpt() const; private: - void shiftOption(bool fwd); + void shiftOption(long mov); }; inline sizet SwitchBox::getCurOpt() const {