mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-25 12:14:46 +02:00
commit
194b3389f9
35
.github/workflows/github.yml
vendored
35
.github/workflows/github.yml
vendored
@ -201,8 +201,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: |
|
run: |
|
||||||
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC13=1; fi
|
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC12=1; fi
|
||||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC13:+-DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13}
|
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC12:+-DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
@ -384,3 +384,34 @@ jobs:
|
|||||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
|
name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.ANDROID_APK_PATH }}
|
${{ env.ANDROID_APK_PATH }}
|
||||||
|
|
||||||
|
|
||||||
|
deploy-src:
|
||||||
|
if: always() && github.ref == 'refs/heads/master'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Build Number
|
||||||
|
run: |
|
||||||
|
source '${{github.workspace}}/CI/get_package_name.sh'
|
||||||
|
echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create source code archive (including submodules)
|
||||||
|
run: |
|
||||||
|
git archive HEAD -o "release.tar" --worktree-attributes -v
|
||||||
|
git submodule update --init --recursive
|
||||||
|
git submodule --quiet foreach 'cd "$toplevel"; tar -rvf "release.tar" "$sm_path"'
|
||||||
|
gzip release.tar
|
||||||
|
|
||||||
|
- name: Upload source code archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
|
||||||
|
path: |
|
||||||
|
./release.tar.gz
|
||||||
|
@ -77,6 +77,7 @@ AIGateway::AIGateway()
|
|||||||
destinationTeleport = ObjectInstanceID();
|
destinationTeleport = ObjectInstanceID();
|
||||||
destinationTeleportPos = int3(-1);
|
destinationTeleportPos = int3(-1);
|
||||||
nullkiller.reset(new Nullkiller());
|
nullkiller.reset(new Nullkiller());
|
||||||
|
announcedCheatingProblem = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AIGateway::~AIGateway()
|
AIGateway::~AIGateway()
|
||||||
@ -828,7 +829,14 @@ void AIGateway::makeTurn()
|
|||||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||||
setThreadName("AIGateway::makeTurn");
|
setThreadName("AIGateway::makeTurn");
|
||||||
|
|
||||||
cb->sendMessage("vcmieagles");
|
if(cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
|
||||||
|
cb->sendMessage("vcmieagles");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!announcedCheatingProblem)
|
||||||
|
cb->sendMessage("Nullkiller AI currently requires the ability to cheat in order to function correctly! Please enable!");
|
||||||
|
announcedCheatingProblem = true;
|
||||||
|
}
|
||||||
|
|
||||||
retrieveVisitableObjs();
|
retrieveVisitableObjs();
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ public:
|
|||||||
std::unique_ptr<boost::thread> makingTurn;
|
std::unique_ptr<boost::thread> makingTurn;
|
||||||
private:
|
private:
|
||||||
boost::mutex turnInterruptionMutex;
|
boost::mutex turnInterruptionMutex;
|
||||||
|
bool announcedCheatingProblem;
|
||||||
public:
|
public:
|
||||||
ObjectInstanceID selectedObject;
|
ObjectInstanceID selectedObject;
|
||||||
|
|
||||||
|
@ -334,13 +334,13 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
|||||||
|
|
||||||
if(!upgrade.upgradeValue
|
if(!upgrade.upgradeValue
|
||||||
&& armyToGetOrBuy.upgradeValue > 20000
|
&& armyToGetOrBuy.upgradeValue > 20000
|
||||||
&& ai->heroManager->canRecruitHero(town)
|
&& ai->heroManager->canRecruitHero(upgrader)
|
||||||
&& path.turn() < ai->settings->getScoutHeroTurnDistanceLimit())
|
&& path.turn() < ai->settings->getScoutHeroTurnDistanceLimit())
|
||||||
{
|
{
|
||||||
for(auto hero : cb->getAvailableHeroes(town))
|
for(auto hero : cb->getAvailableHeroes(upgrader))
|
||||||
{
|
{
|
||||||
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanBuy(hero, town)
|
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanBuy(hero, upgrader)
|
||||||
+ ai->armyManager->howManyReinforcementsCanGet(hero, town);
|
+ ai->armyManager->howManyReinforcementsCanGet(hero, upgrader);
|
||||||
|
|
||||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||||
&& ai->getFreeGold() >20000
|
&& ai->getFreeGold() >20000
|
||||||
@ -348,7 +348,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
|||||||
{
|
{
|
||||||
Composition recruitHero;
|
Composition recruitHero;
|
||||||
|
|
||||||
recruitHero.addNext(ArmyUpgrade(path.targetHero, town, armyToGetOrBuy)).addNext(RecruitHero(town, hero));
|
recruitHero.addNext(ArmyUpgrade(path.targetHero, town, armyToGetOrBuy)).addNext(RecruitHero(upgrader, hero));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,7 +354,7 @@ void Nullkiller::makeTurn()
|
|||||||
decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
|
decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
|
||||||
decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
|
decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
|
||||||
|
|
||||||
if(cb->getDate(Date::DAY) == 1)
|
if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty())
|
||||||
{
|
{
|
||||||
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
||||||
}
|
}
|
||||||
|
@ -365,7 +365,7 @@ if(MINGW OR MSVC)
|
|||||||
# Prevent compiler issues when building Debug
|
# Prevent compiler issues when building Debug
|
||||||
# Assembler might fail with "too many sections"
|
# Assembler might fail with "too many sections"
|
||||||
# With big-obj or 64-bit build will take hours
|
# With big-obj or 64-bit build will take hours
|
||||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Og")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Og")
|
||||||
endif()
|
endif()
|
||||||
endif(MINGW)
|
endif(MINGW)
|
||||||
@ -400,9 +400,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR NOT WIN32)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=array-bounds") # false positives in boost::multiarray during release build, keep as warning-only
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=array-bounds") # false positives in boost::multiarray during release build, keep as warning-only
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32)
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
# For gcc 14+ we can use -fhardened instead
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection -fcf-protection=full")
|
# For gcc 14+ we can use -fhardened instead
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection -fcf-protection=full")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Fix string inspection with lldb
|
# Fix string inspection with lldb
|
||||||
@ -740,7 +742,7 @@ if(WIN32)
|
|||||||
"${CMAKE_FIND_ROOT_PATH}/bin/*.dll")
|
"${CMAKE_FIND_ROOT_PATH}/bin/*.dll")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
# Copy debug versions of libraries if build type is debug
|
# Copy debug versions of libraries if build type is debug
|
||||||
set(debug_postfix d)
|
set(debug_postfix d)
|
||||||
endif()
|
endif()
|
||||||
|
53
ChangeLog.md
53
ChangeLog.md
@ -2,12 +2,61 @@
|
|||||||
|
|
||||||
### Stability
|
### Stability
|
||||||
* Fixed possible crash on accessing faction description
|
* Fixed possible crash on accessing faction description
|
||||||
* Fixed possible thread race on leaving to main menu
|
* Fixed possible thread race on exit to main menu
|
||||||
* Game will now show error message instead of silent crash on corrupted H3 data
|
* Game will now show error message instead of silent crash on corrupted H3 data
|
||||||
* Fixed possible crash on double-deletion of quest artifacts placed by RMG
|
* Fixed possible crash on double-deletion of quest artifacts placed by RMG
|
||||||
|
* Fixed crash on loading save made in version 1.4 with removed from map Quest Guards
|
||||||
|
* Added workaround for crash on accessing Altar of Sacrifice on saves made in 1.4
|
||||||
|
* Fixed possible crash on map restart request
|
||||||
|
* Fixed crash on attempt to open scenario list with no save or map selected
|
||||||
|
* Fixed crash on host resolving error when connecting to online lobby
|
||||||
|
* If json file specified in mod.json is missing, vcmi will now only log an error instead of crashing
|
||||||
|
|
||||||
### Interface
|
### Interface
|
||||||
* Fixed possible freeze on attempt to move hero when hero has non-zero movement points but not enough to reach first tile in path
|
* Added retaliation damage and kills preview when hovering over units that can be attacked in melee during combat
|
||||||
|
* Clicking on combat log would now open a window with full combat log history
|
||||||
|
* Removed message length limit in text input fields, such as global lobby chat
|
||||||
|
* Tapping on already active text input field will display on-screen keyboard on systems with one
|
||||||
|
* Fixed possible freeze when trying to move hero if hero has non-zero movement points but not enough to reach first tile in path
|
||||||
|
* Fixed selection of the wrong reward in dialogs such as the level-up window when double-clicking on it
|
||||||
|
* Fixed launch of wrong map or save when double-clicking in scenario list screen
|
||||||
|
* Right-clicking on a hero in a tavern will now select that hero as well, in line with H3
|
||||||
|
* Fixed slow map list parsing when hota map format is enabled
|
||||||
|
* MacOS and iOS can now use either Ctrl or Cmd key for all keyboard shortcuts
|
||||||
|
* Small windows no longer dim the entire screen by default
|
||||||
|
|
||||||
|
### Mechanics
|
||||||
|
* Recruiting a hero will now immediately reveal the fog of war around him
|
||||||
|
* When both a visiting hero and a garrisoned hero are in town, the garrisoned hero will visit town buildings first.
|
||||||
|
|
||||||
|
### Multiplayer
|
||||||
|
* Fixed in-game chat text not being visible after switching from achannel with a long history
|
||||||
|
* Fixed lag when switching to channel with long history
|
||||||
|
* Game now automatically scrolls in-game chat on new messages
|
||||||
|
* Game will now only plays chat sound for active channel and for private channels
|
||||||
|
* Cheats are now disabled by default in multiplayer
|
||||||
|
* Game will now show status of cheats and battle replays on map start
|
||||||
|
* It is possible to change cheats or battle replay on game loading
|
||||||
|
* It is now possible to join rooms hosted by different hotfix versions, e.g. 1.5.1 can join 1.5.0 games
|
||||||
|
* Fixed game rooms remaining visible in the lobby even after they have been closed
|
||||||
|
* Fixed possible lag when there is a player in lobby with a very slow (or dying) connection
|
||||||
|
* Game will show correctly if player has been invited into a room
|
||||||
|
* Fixed overflow in invite window when there are more than 8 players in the lobby
|
||||||
|
|
||||||
|
### Random Maps Generator
|
||||||
|
* Generator will now prefer to place roads away from zone borders
|
||||||
|
|
||||||
|
### AI
|
||||||
|
* Fixed possible crash when Nullkiller AI tries to upgrade army
|
||||||
|
* Nullkiller AI will now recruit new heroes if he left with 0 heroes
|
||||||
|
* AI in combat now knows when an enemy unit has used all of its retaliations.
|
||||||
|
|
||||||
|
### Map Editor
|
||||||
|
* Fixed setting up hero types of heroes in Prisons placed in map editor
|
||||||
|
* Fixed crash on setting up Seer Hut in map editor
|
||||||
|
* Added text auto-completion hints for army widget
|
||||||
|
* Editor will now automatically add .vmap extensions when saving map
|
||||||
|
* Fixed text size in map validation window
|
||||||
|
|
||||||
# 1.4.5 -> 1.5.0
|
# 1.4.5 -> 1.5.0
|
||||||
|
|
||||||
|
@ -252,6 +252,13 @@
|
|||||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d damage",
|
"vcmi.battleWindow.damageEstimation.damage.1" : "%d damage",
|
||||||
"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
|
"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
|
||||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
|
"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
|
||||||
|
|
||||||
|
"vcmi.battleWindow.damageRetaliation.will" : "Will retaliate ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.may" : "May retaliate ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.never" : "Will not retaliate.",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
|
||||||
|
|
||||||
"vcmi.battleWindow.killed" : "Killed",
|
"vcmi.battleWindow.killed" : "Killed",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s were killed by accurate shots!",
|
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s were killed by accurate shots!",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s was killed with an accurate shot!",
|
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s was killed with an accurate shot!",
|
||||||
|
@ -252,6 +252,13 @@
|
|||||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d de dano",
|
"vcmi.battleWindow.damageEstimation.damage.1" : "%d de dano",
|
||||||
"vcmi.battleWindow.damageEstimation.kills" : "%d morrerão",
|
"vcmi.battleWindow.damageEstimation.kills" : "%d morrerão",
|
||||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d morrerá",
|
"vcmi.battleWindow.damageEstimation.kills.1" : "%d morrerá",
|
||||||
|
|
||||||
|
"vcmi.battleWindow.damageRetaliation.will" : "Contra-atacará ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.may" : "Pode contra-atacar ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.never" : "Não contra-atacará.",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
|
||||||
|
|
||||||
"vcmi.battleWindow.killed" : "Eliminados",
|
"vcmi.battleWindow.killed" : "Eliminados",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s morreram por tiros precisos!",
|
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s morreram por tiros precisos!",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s morreu com um tiro preciso!",
|
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s morreu com um tiro preciso!",
|
||||||
|
@ -180,16 +180,19 @@
|
|||||||
"vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.",
|
"vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.",
|
||||||
|
|
||||||
"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona cualquier tecla para empezar la batalla inmediatamente",
|
"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona cualquier tecla para empezar la batalla inmediatamente",
|
||||||
"vcmi.battleWindow.damageEstimation.melee" : "Atacar %CREATURE (%DAÑO).",
|
"vcmi.battleWindow.damageEstimation.melee" : "Atacar %CREATURE (%DAMAGE).",
|
||||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "Atacar %CREATURE (%DAÑO, %BAJAS).",
|
"vcmi.battleWindow.damageEstimation.meleeKills" : "Atacar %CREATURE (%DAMAGE, %KILLS).",
|
||||||
"vcmi.battleWindow.damageEstimation.ranged" : "Disparar a %CREATURE (%DISPAROS, %DAÑO).",
|
"vcmi.battleWindow.damageEstimation.ranged" : "Disparar a %CREATURE (%SHOTS, %DAMAGE).",
|
||||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparar a %CREATURE (%DISPAROS, %DAÑO, %BAJAS).",
|
"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparar a %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||||
"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
|
"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
|
||||||
"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparo restante",
|
"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparo restante",
|
||||||
"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
|
"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
|
||||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
|
"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
|
||||||
"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
|
"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
|
||||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá",
|
"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.will" : "Contratacará ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.may" : "Puede contratacar ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.never" : "No contratacará.",
|
||||||
"vcmi.battleWindow.killed" : "Eliminados",
|
"vcmi.battleWindow.killed" : "Eliminados",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s han sido eliminados por disparos certeros",
|
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s han sido eliminados por disparos certeros",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s ha sido eliminado por un disparo certero",
|
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s ha sido eliminado por un disparo certero",
|
||||||
@ -352,16 +355,16 @@
|
|||||||
|
|
||||||
"core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque",
|
"core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque",
|
||||||
"core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces",
|
"core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces",
|
||||||
"core.bonus.ADDITIONAL_RETALIATION.name": "Contraataques adicionales",
|
"core.bonus.ADDITIONAL_RETALIATION.name": "Contrataques adicionales",
|
||||||
"core.bonus.ADDITIONAL_RETALIATION.description": "Puede contraatacar ${val} veces adicionales",
|
"core.bonus.ADDITIONAL_RETALIATION.description": "Puede contratacar ${val} veces adicionales",
|
||||||
"core.bonus.AIR_IMMUNITY.name": "Inmunidad al Aire",
|
"core.bonus.AIR_IMMUNITY.name": "Inmunidad al Aire",
|
||||||
"core.bonus.AIR_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de Aire",
|
"core.bonus.AIR_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de Aire",
|
||||||
"core.bonus.ATTACKS_ALL_ADJACENT.name": "Ataque en todas las direcciones",
|
"core.bonus.ATTACKS_ALL_ADJACENT.name": "Ataque en todas las direcciones",
|
||||||
"core.bonus.ATTACKS_ALL_ADJACENT.description": "Ataca a todos los enemigos adyacentes",
|
"core.bonus.ATTACKS_ALL_ADJACENT.description": "Ataca a todos los enemigos adyacentes",
|
||||||
"core.bonus.BLOCKS_RETALIATION.name": "Sin contraataque",
|
"core.bonus.BLOCKS_RETALIATION.name": "Evita contrataque",
|
||||||
"core.bonus.BLOCKS_RETALIATION.description": "El enemigo no puede contraatacar",
|
"core.bonus.BLOCKS_RETALIATION.description": "El enemigo no puede contratacar",
|
||||||
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Sin contraataque a distancia",
|
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Evita contrataque a distancia",
|
||||||
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "El enemigo no puede contraatacar disparando",
|
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "El enemigo no puede contratacar disparando",
|
||||||
"core.bonus.CATAPULT.name": "Catapulta",
|
"core.bonus.CATAPULT.name": "Catapulta",
|
||||||
"core.bonus.CATAPULT.description": "Ataca a las paredes de asedio",
|
"core.bonus.CATAPULT.description": "Ataca a las paredes de asedio",
|
||||||
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reducir coste del conjuro (${val})",
|
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reducir coste del conjuro (${val})",
|
||||||
@ -397,7 +400,7 @@
|
|||||||
"core.bonus.FIRE_SHIELD.name": "Escudo de Fuego (${val}%)",
|
"core.bonus.FIRE_SHIELD.name": "Escudo de Fuego (${val}%)",
|
||||||
"core.bonus.FIRE_SHIELD.description": "Refleja una parte del daño cuerpo a cuerpo",
|
"core.bonus.FIRE_SHIELD.description": "Refleja una parte del daño cuerpo a cuerpo",
|
||||||
"core.bonus.FIRST_STRIKE.name": "Primer Ataque",
|
"core.bonus.FIRST_STRIKE.name": "Primer Ataque",
|
||||||
"core.bonus.FIRST_STRIKE.description": "Esta criatura ataca primero en lugar de contraatacar",
|
"core.bonus.FIRST_STRIKE.description": "Esta criatura ataca primero en lugar de contratacar",
|
||||||
"core.bonus.FEAR.name": "Miedo",
|
"core.bonus.FEAR.name": "Miedo",
|
||||||
"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
|
"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
|
||||||
"core.bonus.FEARLESS.name": "Inmune al miedo",
|
"core.bonus.FEARLESS.name": "Inmune al miedo",
|
||||||
@ -450,8 +453,8 @@
|
|||||||
"core.bonus.NON_LIVING.description": "Inmunidad a muchos efectos",
|
"core.bonus.NON_LIVING.description": "Inmunidad a muchos efectos",
|
||||||
"core.bonus.RANDOM_SPELLCASTER.name": "Lanzador de hechizos aleatorio",
|
"core.bonus.RANDOM_SPELLCASTER.name": "Lanzador de hechizos aleatorio",
|
||||||
"core.bonus.RANDOM_SPELLCASTER.description": "Puede lanzar hechizos aleatorios",
|
"core.bonus.RANDOM_SPELLCASTER.description": "Puede lanzar hechizos aleatorios",
|
||||||
"core.bonus.RANGED_RETALIATION.name": "Contraataque a distancia",
|
"core.bonus.RANGED_RETALIATION.name": "Contrataque a distancia",
|
||||||
"core.bonus.RANGED_RETALIATION.description": "Puede realizar un contraataque a distancia",
|
"core.bonus.RANGED_RETALIATION.description": "Puede realizar un contrataque a distancia",
|
||||||
"core.bonus.RECEPTIVE.name": "Receptivo",
|
"core.bonus.RECEPTIVE.name": "Receptivo",
|
||||||
"core.bonus.RECEPTIVE.description": "No tiene inmunidad a hechizos amistosos",
|
"core.bonus.RECEPTIVE.description": "No tiene inmunidad a hechizos amistosos",
|
||||||
"core.bonus.REBIRTH.name": "Renacimiento (${val}%)",
|
"core.bonus.REBIRTH.name": "Renacimiento (${val}%)",
|
||||||
@ -492,8 +495,8 @@
|
|||||||
"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
|
"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
|
||||||
"core.bonus.UNDEAD.name": "No muerto",
|
"core.bonus.UNDEAD.name": "No muerto",
|
||||||
"core.bonus.UNDEAD.description": "La criatura es un no muerto",
|
"core.bonus.UNDEAD.description": "La criatura es un no muerto",
|
||||||
"core.bonus.UNLIMITED_RETALIATIONS.name": "Contraataques ilimitados",
|
"core.bonus.UNLIMITED_RETALIATIONS.name": "Contrataques ilimitados",
|
||||||
"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de contraataques",
|
"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de contrataques",
|
||||||
"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
|
"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
|
||||||
"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
|
"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
|
||||||
"core.bonus.WIDE_BREATH.name": "Aliento amplio",
|
"core.bonus.WIDE_BREATH.name": "Aliento amplio",
|
||||||
|
@ -241,11 +241,19 @@
|
|||||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "Стріляти в %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
"vcmi.battleWindow.damageEstimation.rangedKills" : "Стріляти в %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||||
"vcmi.battleWindow.damageEstimation.shots" : "%d пострілів залишилось",
|
"vcmi.battleWindow.damageEstimation.shots" : "%d пострілів залишилось",
|
||||||
"vcmi.battleWindow.damageEstimation.shots.1" : "%d постріл залишився",
|
"vcmi.battleWindow.damageEstimation.shots.1" : "%d постріл залишився",
|
||||||
"vcmi.battleWindow.damageEstimation.damage" : "%d одиниць пошкоджень",
|
"vcmi.battleWindow.damageEstimation.damage" : "%d пошкоджень",
|
||||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d одиниця пошкодження",
|
"vcmi.battleWindow.damageEstimation.damage.1" : "%d пошкодження",
|
||||||
"vcmi.battleWindow.damageEstimation.kills" : "%d загинуть",
|
"vcmi.battleWindow.damageEstimation.kills" : "%d загинуть",
|
||||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d загине",
|
"vcmi.battleWindow.damageEstimation.kills.1" : "%d загине",
|
||||||
|
|
||||||
|
"vcmi.battleWindow.damageRetaliation.will" : "Буде відповідати ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.may" : "Може відповісти ",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.never" : "Не буде відповідати.",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
|
||||||
|
|
||||||
"vcmi.battleWindow.killed" : "Загинуло",
|
"vcmi.battleWindow.killed" : "Загинуло",
|
||||||
|
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s було вбито влучними пострілами!",
|
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s було вбито влучними пострілами!",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s було вбито влучним пострілом!",
|
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s було вбито влучним пострілом!",
|
||||||
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s було вбито влучними пострілами!",
|
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s було вбито влучними пострілами!",
|
||||||
|
@ -10,7 +10,7 @@ android {
|
|||||||
applicationId "is.xyz.vcmi"
|
applicationId "is.xyz.vcmi"
|
||||||
minSdk 19
|
minSdk 19
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 1511
|
versionCode 1513
|
||||||
versionName "1.5.1"
|
versionName "1.5.1"
|
||||||
setProperty("archivesBaseName", "vcmi")
|
setProperty("archivesBaseName", "vcmi")
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "CFocusableHelper.h"
|
#include "CFocusableHelper.h"
|
||||||
#include "../Global.h"
|
#include "widgets/CTextInput.h"
|
||||||
#include "widgets/TextControls.h"
|
|
||||||
|
|
||||||
void removeFocusFromActiveInput()
|
void removeFocusFromActiveInput()
|
||||||
{
|
{
|
||||||
if(CFocusable::inputWithFocus == nullptr)
|
if(CFocusable::inputWithFocus != nullptr)
|
||||||
return;
|
CFocusable::inputWithFocus->removeFocus();
|
||||||
CFocusable::inputWithFocus->focus = false;
|
|
||||||
CFocusable::inputWithFocus->redraw();
|
|
||||||
CFocusable::inputWithFocus = nullptr;
|
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ set(client_SRCS
|
|||||||
widgets/CGarrisonInt.cpp
|
widgets/CGarrisonInt.cpp
|
||||||
widgets/CreatureCostBox.cpp
|
widgets/CreatureCostBox.cpp
|
||||||
widgets/ComboBox.cpp
|
widgets/ComboBox.cpp
|
||||||
|
widgets/CTextInput.cpp
|
||||||
widgets/GraphicalPrimitiveCanvas.cpp
|
widgets/GraphicalPrimitiveCanvas.cpp
|
||||||
widgets/Images.cpp
|
widgets/Images.cpp
|
||||||
widgets/MiscWidgets.cpp
|
widgets/MiscWidgets.cpp
|
||||||
@ -293,6 +294,7 @@ set(client_HEADERS
|
|||||||
globalLobby/GlobalLobbyLoginWindow.h
|
globalLobby/GlobalLobbyLoginWindow.h
|
||||||
globalLobby/GlobalLobbyRoomWindow.h
|
globalLobby/GlobalLobbyRoomWindow.h
|
||||||
globalLobby/GlobalLobbyServerSetup.h
|
globalLobby/GlobalLobbyServerSetup.h
|
||||||
|
globalLobby/GlobalLobbyObserver.h
|
||||||
globalLobby/GlobalLobbyWidget.h
|
globalLobby/GlobalLobbyWidget.h
|
||||||
globalLobby/GlobalLobbyWindow.h
|
globalLobby/GlobalLobbyWindow.h
|
||||||
|
|
||||||
@ -303,6 +305,7 @@ set(client_HEADERS
|
|||||||
widgets/CGarrisonInt.h
|
widgets/CGarrisonInt.h
|
||||||
widgets/CreatureCostBox.h
|
widgets/CreatureCostBox.h
|
||||||
widgets/ComboBox.h
|
widgets/ComboBox.h
|
||||||
|
widgets/CTextInput.h
|
||||||
widgets/GraphicalPrimitiveCanvas.h
|
widgets/GraphicalPrimitiveCanvas.h
|
||||||
widgets/Images.h
|
widgets/Images.h
|
||||||
widgets/MiscWidgets.h
|
widgets/MiscWidgets.h
|
||||||
|
@ -919,7 +919,6 @@ void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> &
|
|||||||
|
|
||||||
if(getState() == EClientState::DISCONNECTING)
|
if(getState() == EClientState::DISCONNECTING)
|
||||||
{
|
{
|
||||||
assert(networkConnection == nullptr);
|
|
||||||
// Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction
|
// Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction
|
||||||
logNetwork->info("Successfully closed connection to server!");
|
logNetwork->info("Successfully closed connection to server!");
|
||||||
return;
|
return;
|
||||||
|
@ -184,7 +184,7 @@ void AdventureMapInterface::dim(Canvas & to)
|
|||||||
}
|
}
|
||||||
for (auto window : GH.windows().findWindows<CIntObject>())
|
for (auto window : GH.windows().findWindows<CIntObject>())
|
||||||
{
|
{
|
||||||
if (!std::dynamic_pointer_cast<AdventureMapInterface>(window) && !std::dynamic_pointer_cast<RadialMenu>(window) && !window->isPopupWindow() && (settings["adventure"]["backgroundDimSmallWindows"].Bool() || isBigWindow(window)))
|
if (!std::dynamic_pointer_cast<AdventureMapInterface>(window) && !std::dynamic_pointer_cast<RadialMenu>(window) && !window->isPopupWindow() && (settings["adventure"]["backgroundDimSmallWindows"].Bool() || isBigWindow(window) || shortcuts->getState() == EAdventureState::HOTSEAT_WAIT))
|
||||||
{
|
{
|
||||||
Rect targetRect(0, 0, GH.screenDimensions().x, GH.screenDimensions().y);
|
Rect targetRect(0, 0, GH.screenDimensions().x, GH.screenDimensions().y);
|
||||||
ColorRGBA colorToFill(0, 0, 0, std::clamp<int>(backgroundDimLevel, 0, 255));
|
ColorRGBA colorToFill(0, 0, 0, std::clamp<int>(backgroundDimLevel, 0, 255));
|
||||||
@ -564,7 +564,7 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(GH.isKeyboardCtrlDown()) //normal click behaviour (as no hero selected)
|
if(GH.isKeyboardCmdDown()) //normal click behaviour (as no hero selected)
|
||||||
{
|
{
|
||||||
if(canSelect)
|
if(canSelect)
|
||||||
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
|
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
|
||||||
|
@ -47,6 +47,11 @@ void AdventureMapShortcuts::setState(EAdventureState newState)
|
|||||||
state = newState;
|
state = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EAdventureState AdventureMapShortcuts::getState()
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
void AdventureMapShortcuts::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
|
void AdventureMapShortcuts::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
|
||||||
{
|
{
|
||||||
mapLevel = newMapLevel;
|
mapLevel = newMapLevel;
|
||||||
|
@ -89,5 +89,6 @@ public:
|
|||||||
bool optionMapViewActive();
|
bool optionMapViewActive();
|
||||||
|
|
||||||
void setState(EAdventureState newState);
|
void setState(EAdventureState newState);
|
||||||
|
EAdventureState getState();
|
||||||
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
|
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
|
||||||
};
|
};
|
||||||
|
@ -266,7 +266,13 @@ void CInGameConsole::startEnteringText()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if(isEnteringText())
|
if(isEnteringText())
|
||||||
|
{
|
||||||
|
// force-reset text input to re-show on-screen keyboard
|
||||||
|
GH.statusbar()->setEnteringMode(false);
|
||||||
|
GH.statusbar()->setEnteringMode(true);
|
||||||
|
GH.statusbar()->setEnteredText(enteredText);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(currentStatusBar.expired());//effectively, nullptr check
|
assert(currentStatusBar.expired());//effectively, nullptr check
|
||||||
|
|
||||||
|
@ -114,6 +114,22 @@ static std::string formatRangedAttack(const DamageEstimation & estimation, const
|
|||||||
return formatAttack(estimation, creatureName, baseTextID, shotsLeft);
|
return formatAttack(estimation, creatureName, baseTextID, shotsLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string formatRetaliation(const DamageEstimation & estimation, bool mayBeKilled)
|
||||||
|
{
|
||||||
|
if (estimation.damage.max == 0)
|
||||||
|
return CGI->generaltexth->translate("vcmi.battleWindow.damageRetaliation.never");
|
||||||
|
|
||||||
|
std::string baseTextID = estimation.kills.max == 0 ?
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damage" :
|
||||||
|
"vcmi.battleWindow.damageRetaliation.damageKills";
|
||||||
|
|
||||||
|
std::string prefixTextID = mayBeKilled ?
|
||||||
|
"vcmi.battleWindow.damageRetaliation.may" :
|
||||||
|
"vcmi.battleWindow.damageRetaliation.will";
|
||||||
|
|
||||||
|
return CGI->generaltexth->translate(prefixTextID) + formatAttack(estimation, "", baseTextID, 0);
|
||||||
|
}
|
||||||
|
|
||||||
BattleActionsController::BattleActionsController(BattleInterface & owner):
|
BattleActionsController::BattleActionsController(BattleInterface & owner):
|
||||||
owner(owner),
|
owner(owner),
|
||||||
selectedStack(nullptr),
|
selectedStack(nullptr),
|
||||||
@ -484,21 +500,23 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
|||||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||||
{
|
{
|
||||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
|
DamageEstimation retaliation;
|
||||||
|
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex, &retaliation);
|
||||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||||
|
bool enemyMayBeKilled = estimation.kills.max == targetStack->getCount();
|
||||||
|
|
||||||
return formatMeleeAttack(estimation, targetStack->getName());
|
return formatMeleeAttack(estimation, targetStack->getName()) + "\n" + formatRetaliation(retaliation, enemyMayBeKilled);
|
||||||
}
|
}
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::SHOOT:
|
case PossiblePlayerBattleAction::SHOOT:
|
||||||
{
|
{
|
||||||
const auto * shooter = owner.stacksController->getActiveStack();
|
const auto * shooter = owner.stacksController->getActiveStack();
|
||||||
|
|
||||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
|
DamageEstimation retaliation;
|
||||||
|
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition(), &retaliation);
|
||||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||||
|
|
||||||
return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available());
|
return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleInterface::openingPlaying()
|
bool BattleInterface::openingPlaying() const
|
||||||
{
|
{
|
||||||
return battleOpeningDelayActive;
|
return battleOpeningDelayActive;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ public:
|
|||||||
std::shared_ptr<BattleHero> attackingHero;
|
std::shared_ptr<BattleHero> attackingHero;
|
||||||
std::shared_ptr<BattleHero> defendingHero;
|
std::shared_ptr<BattleHero> defendingHero;
|
||||||
|
|
||||||
bool openingPlaying();
|
bool openingPlaying() const;
|
||||||
void openingEnd();
|
void openingEnd();
|
||||||
|
|
||||||
bool makingTurn() const;
|
bool makingTurn() const;
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include "../render/Graphics.h"
|
#include "../render/Graphics.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
|
#include "../widgets/Slider.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../windows/CMessage.h"
|
#include "../windows/CMessage.h"
|
||||||
@ -141,8 +142,10 @@ void BattleConsole::scrollDown(ui32 by)
|
|||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleConsole::BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size)
|
BattleConsole::BattleConsole(const BattleInterface & owner, std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size)
|
||||||
: scrollPosition(-1)
|
: CIntObject(LCLICK)
|
||||||
|
, owner(owner)
|
||||||
|
, scrollPosition(-1)
|
||||||
, enteringText(false)
|
, enteringText(false)
|
||||||
{
|
{
|
||||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
@ -161,6 +164,14 @@ void BattleConsole::deactivate()
|
|||||||
CIntObject::deactivate();
|
CIntObject::deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BattleConsole::clickPressed(const Point & cursorPosition)
|
||||||
|
{
|
||||||
|
if(owner.makingTurn() && !owner.openingPlaying())
|
||||||
|
{
|
||||||
|
GH.windows().createAndPushWindow<BattleConsoleWindow>(boost::algorithm::join(logEntries, "\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BattleConsole::setEnteringMode(bool on)
|
void BattleConsole::setEnteringMode(bool on)
|
||||||
{
|
{
|
||||||
consoleText.clear();
|
consoleText.clear();
|
||||||
@ -203,6 +214,26 @@ void BattleConsole::clear()
|
|||||||
write({});
|
write({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BattleConsoleWindow::BattleConsoleWindow(const std::string & text)
|
||||||
|
: CWindowObject(BORDERED)
|
||||||
|
{
|
||||||
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
|
|
||||||
|
pos.w = 429;
|
||||||
|
pos.h = 434;
|
||||||
|
|
||||||
|
updateShadow();
|
||||||
|
center();
|
||||||
|
|
||||||
|
backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||||
|
buttonOk = std::make_shared<CButton>(Point(183, 388), AnimationPath::builtin("IOKAY"), CButton::tooltip(), [this](){ close(); }, EShortcut::GLOBAL_ACCEPT);
|
||||||
|
Rect textArea(18, 17, 393, 354);
|
||||||
|
textBoxBackgroundBorder = std::make_shared<TransparentFilledRectangle>(textArea, ColorRGBA(0, 0, 0, 75), ColorRGBA(128, 100, 75));
|
||||||
|
textBox = std::make_shared<CTextBox>(text, textArea.resize(-5), CSlider::BROWN);
|
||||||
|
if(textBox->slider)
|
||||||
|
textBox->slider->scrollToMax();
|
||||||
|
}
|
||||||
|
|
||||||
const CGHeroInstance * BattleHero::instance()
|
const CGHeroInstance * BattleHero::instance()
|
||||||
{
|
{
|
||||||
return hero;
|
return hero;
|
||||||
|
@ -47,6 +47,8 @@ class BattleRenderer;
|
|||||||
class BattleConsole : public CIntObject, public IStatusBar
|
class BattleConsole : public CIntObject, public IStatusBar
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
const BattleInterface & owner;
|
||||||
|
|
||||||
std::shared_ptr<CPicture> background;
|
std::shared_ptr<CPicture> background;
|
||||||
|
|
||||||
/// List of all texts added during battle, essentially - log of entire battle
|
/// List of all texts added during battle, essentially - log of entire battle
|
||||||
@ -70,11 +72,13 @@ private:
|
|||||||
/// select line(s) that will be visible in UI
|
/// select line(s) that will be visible in UI
|
||||||
std::vector<std::string> getVisibleText();
|
std::vector<std::string> getVisibleText();
|
||||||
public:
|
public:
|
||||||
BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size);
|
BattleConsole(const BattleInterface & owner, std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size);
|
||||||
|
|
||||||
void showAll(Canvas & to) override;
|
void showAll(Canvas & to) override;
|
||||||
void deactivate() override;
|
void deactivate() override;
|
||||||
|
|
||||||
|
void clickPressed(const Point & cursorPosition) override;
|
||||||
|
|
||||||
bool addText(const std::string &text); //adds text at the last position; returns false if failed (e.g. text longer than 70 characters)
|
bool addText(const std::string &text); //adds text at the last position; returns false if failed (e.g. text longer than 70 characters)
|
||||||
void scrollUp(ui32 by = 1); //scrolls console up by 'by' positions
|
void scrollUp(ui32 by = 1); //scrolls console up by 'by' positions
|
||||||
void scrollDown(ui32 by = 1); //scrolls console up by 'by' positions
|
void scrollDown(ui32 by = 1); //scrolls console up by 'by' positions
|
||||||
@ -87,6 +91,17 @@ public:
|
|||||||
void setEnteredText(const std::string & text) override;
|
void setEnteredText(const std::string & text) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BattleConsoleWindow : public CWindowObject
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::shared_ptr<CFilledTexture> backgroundTexture;
|
||||||
|
std::shared_ptr<CButton> buttonOk;
|
||||||
|
std::shared_ptr<TransparentFilledRectangle> textBoxBackgroundBorder;
|
||||||
|
std::shared_ptr<CTextBox> textBox;
|
||||||
|
public:
|
||||||
|
BattleConsoleWindow(const std::string & text);
|
||||||
|
};
|
||||||
|
|
||||||
/// Hero battle animation
|
/// Hero battle animation
|
||||||
class BattleHero : public CIntObject
|
class BattleHero : public CIntObject
|
||||||
{
|
{
|
||||||
|
@ -199,7 +199,7 @@ std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode &
|
|||||||
auto rect = readRect(config["rect"]);
|
auto rect = readRect(config["rect"]);
|
||||||
auto offset = readPosition(config["imagePosition"]);
|
auto offset = readPosition(config["imagePosition"]);
|
||||||
auto background = widget<CPicture>("menuBattle");
|
auto background = widget<CPicture>("menuBattle");
|
||||||
return std::make_shared<BattleConsole>(background, rect.topLeft(), offset, rect.dimensions() );
|
return std::make_shared<BattleConsole>(owner, background, rect.topLeft(), offset, rect.dimensions() );
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleWindow::toggleQueueVisibility()
|
void BattleWindow::toggleQueueVisibility()
|
||||||
|
@ -300,6 +300,11 @@ void InputHandler::fetchEvents()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InputHandler::isKeyboardCmdDown() const
|
||||||
|
{
|
||||||
|
return keyboardHandler->isKeyboardCmdDown();
|
||||||
|
}
|
||||||
|
|
||||||
bool InputHandler::isKeyboardCtrlDown() const
|
bool InputHandler::isKeyboardCtrlDown() const
|
||||||
{
|
{
|
||||||
return keyboardHandler->isKeyboardCtrlDown();
|
return keyboardHandler->isKeyboardCtrlDown();
|
||||||
|
@ -88,6 +88,7 @@ public:
|
|||||||
|
|
||||||
/// returns true if chosen keyboard key is currently pressed down
|
/// returns true if chosen keyboard key is currently pressed down
|
||||||
bool isKeyboardAltDown() const;
|
bool isKeyboardAltDown() const;
|
||||||
|
bool isKeyboardCmdDown() const;
|
||||||
bool isKeyboardCtrlDown() const;
|
bool isKeyboardCtrlDown() const;
|
||||||
bool isKeyboardShiftDown() const;
|
bool isKeyboardShiftDown() const;
|
||||||
};
|
};
|
||||||
|
@ -120,10 +120,20 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
|
|||||||
GH.events().dispatchShortcutReleased(shortcutsVector);
|
GH.events().dispatchShortcutReleased(shortcutsVector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InputSourceKeyboard::isKeyboardCmdDown() const
|
||||||
|
{
|
||||||
|
#ifdef VCMI_APPLE
|
||||||
|
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
|
||||||
|
#else
|
||||||
|
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool InputSourceKeyboard::isKeyboardCtrlDown() const
|
bool InputSourceKeyboard::isKeyboardCtrlDown() const
|
||||||
{
|
{
|
||||||
#ifdef VCMI_MAC
|
#ifdef VCMI_APPLE
|
||||||
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
|
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL] ||
|
||||||
|
SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
|
||||||
#else
|
#else
|
||||||
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
|
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
|
||||||
#endif
|
#endif
|
||||||
|
@ -23,6 +23,7 @@ public:
|
|||||||
void handleEventKeyUp(const SDL_KeyboardEvent & current);
|
void handleEventKeyUp(const SDL_KeyboardEvent & current);
|
||||||
|
|
||||||
bool isKeyboardAltDown() const;
|
bool isKeyboardAltDown() const;
|
||||||
|
bool isKeyboardCmdDown() const;
|
||||||
bool isKeyboardCtrlDown() const;
|
bool isKeyboardCtrlDown() const;
|
||||||
bool isKeyboardShiftDown() const;
|
bool isKeyboardShiftDown() const;
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "GlobalLobbyInviteWindow.h"
|
#include "GlobalLobbyInviteWindow.h"
|
||||||
#include "GlobalLobbyLoginWindow.h"
|
#include "GlobalLobbyLoginWindow.h"
|
||||||
|
#include "GlobalLobbyObserver.h"
|
||||||
#include "GlobalLobbyWindow.h"
|
#include "GlobalLobbyWindow.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
@ -144,6 +145,9 @@ void GlobalLobbyClient::receiveChatHistory(const JsonNode & json)
|
|||||||
if(lobbyWindowPtr && lobbyWindowPtr->isChannelOpen(channelType, channelName))
|
if(lobbyWindowPtr && lobbyWindowPtr->isChannelOpen(channelType, channelName))
|
||||||
lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
|
lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(lobbyWindowPtr && lobbyWindowPtr->isChannelOpen(channelType, channelName))
|
||||||
|
lobbyWindowPtr->refreshChatText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
|
void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
|
||||||
@ -163,9 +167,13 @@ void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
|
|||||||
|
|
||||||
auto lobbyWindowPtr = lobbyWindow.lock();
|
auto lobbyWindowPtr = lobbyWindow.lock();
|
||||||
if(lobbyWindowPtr)
|
if(lobbyWindowPtr)
|
||||||
|
{
|
||||||
lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
|
lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
|
||||||
|
lobbyWindowPtr->refreshChatText();
|
||||||
|
|
||||||
CCS->soundh->playSound(AudioPath::builtin("CHAT"));
|
if(channelType == "player" || lobbyWindowPtr->isChannelOpen(channelType, channelName))
|
||||||
|
CCS->soundh->playSound(AudioPath::builtin("CHAT"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
|
void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
|
||||||
@ -186,6 +194,9 @@ void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
|
|||||||
auto lobbyWindowPtr = lobbyWindow.lock();
|
auto lobbyWindowPtr = lobbyWindow.lock();
|
||||||
if(lobbyWindowPtr)
|
if(lobbyWindowPtr)
|
||||||
lobbyWindowPtr->onActiveAccounts(activeAccounts);
|
lobbyWindowPtr->onActiveAccounts(activeAccounts);
|
||||||
|
|
||||||
|
for (auto const & window : GH.windows().findWindows<GlobalLobbyObserver>())
|
||||||
|
window->onActiveAccounts(activeAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
|
void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
|
||||||
@ -213,6 +224,15 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
|
|||||||
account.displayName = jsonParticipant["displayName"].String();
|
account.displayName = jsonParticipant["displayName"].String();
|
||||||
room.participants.push_back(account);
|
room.participants.push_back(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(const auto & jsonParticipant : jsonEntry["invited"].Vector())
|
||||||
|
{
|
||||||
|
GlobalLobbyAccount account;
|
||||||
|
account.accountID = jsonParticipant["accountID"].String();
|
||||||
|
account.displayName = jsonParticipant["displayName"].String();
|
||||||
|
room.invited.push_back(account);
|
||||||
|
}
|
||||||
|
|
||||||
room.playerLimit = jsonEntry["playerLimit"].Integer();
|
room.playerLimit = jsonEntry["playerLimit"].Integer();
|
||||||
|
|
||||||
activeRooms.push_back(room);
|
activeRooms.push_back(room);
|
||||||
@ -220,7 +240,10 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
|
|||||||
|
|
||||||
auto lobbyWindowPtr = lobbyWindow.lock();
|
auto lobbyWindowPtr = lobbyWindow.lock();
|
||||||
if(lobbyWindowPtr)
|
if(lobbyWindowPtr)
|
||||||
lobbyWindowPtr->onActiveRooms(activeRooms);
|
lobbyWindowPtr->onActiveGameRooms(activeRooms);
|
||||||
|
|
||||||
|
for (auto const & window : GH.windows().findWindows<GlobalLobbyObserver>())
|
||||||
|
window->onActiveGameRooms(activeRooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalLobbyClient::receiveMatchesHistory(const JsonNode & json)
|
void GlobalLobbyClient::receiveMatchesHistory(const JsonNode & json)
|
||||||
@ -246,6 +269,7 @@ void GlobalLobbyClient::receiveMatchesHistory(const JsonNode & json)
|
|||||||
account.displayName = jsonParticipant["displayName"].String();
|
account.displayName = jsonParticipant["displayName"].String();
|
||||||
room.participants.push_back(account);
|
room.participants.push_back(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
room.playerLimit = jsonEntry["playerLimit"].Integer();
|
room.playerLimit = jsonEntry["playerLimit"].Integer();
|
||||||
|
|
||||||
matchesHistory.push_back(room);
|
matchesHistory.push_back(room);
|
||||||
@ -270,6 +294,7 @@ void GlobalLobbyClient::receiveInviteReceived(const JsonNode & json)
|
|||||||
|
|
||||||
lobbyWindowPtr->onGameChatMessage("System", message, time, "player", accountID);
|
lobbyWindowPtr->onGameChatMessage("System", message, time, "player", accountID);
|
||||||
lobbyWindowPtr->onInviteReceived(gameRoomID);
|
lobbyWindowPtr->onInviteReceived(gameRoomID);
|
||||||
|
lobbyWindowPtr->refreshChatText();
|
||||||
}
|
}
|
||||||
|
|
||||||
CCS->soundh->playSound(AudioPath::builtin("CHAT"));
|
CCS->soundh->playSound(AudioPath::builtin("CHAT"));
|
||||||
@ -552,6 +577,11 @@ void GlobalLobbyClient::resetMatchState()
|
|||||||
currentGameRoomUUID.clear();
|
currentGameRoomUUID.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string & GlobalLobbyClient::getCurrentGameRoomID() const
|
||||||
|
{
|
||||||
|
return currentGameRoomUUID;
|
||||||
|
}
|
||||||
|
|
||||||
void GlobalLobbyClient::sendMatchChatMessage(const std::string & messageText)
|
void GlobalLobbyClient::sendMatchChatMessage(const std::string & messageText)
|
||||||
{
|
{
|
||||||
if (!isLoggedIn())
|
if (!isLoggedIn())
|
||||||
@ -573,5 +603,15 @@ void GlobalLobbyClient::sendMatchChatMessage(const std::string & messageText)
|
|||||||
|
|
||||||
bool GlobalLobbyClient::isInvitedToRoom(const std::string & gameRoomID)
|
bool GlobalLobbyClient::isInvitedToRoom(const std::string & gameRoomID)
|
||||||
{
|
{
|
||||||
return activeInvites.count(gameRoomID) > 0;
|
if (activeInvites.count(gameRoomID) > 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const auto & gameRoom = CSH->getGlobalLobby().getActiveRoomByName(gameRoomID);
|
||||||
|
for (auto const & invited : gameRoom.invited)
|
||||||
|
{
|
||||||
|
if (invited.accountID == getAccountID())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,7 @@ public:
|
|||||||
/// Returns active room by ID. Throws out-of-range on failure
|
/// Returns active room by ID. Throws out-of-range on failure
|
||||||
const GlobalLobbyRoom & getActiveRoomByName(const std::string & roomUUID) const;
|
const GlobalLobbyRoom & getActiveRoomByName(const std::string & roomUUID) const;
|
||||||
|
|
||||||
|
const std::string & getCurrentGameRoomID() const;
|
||||||
const std::string & getAccountID() const;
|
const std::string & getAccountID() const;
|
||||||
const std::string & getAccountCookie() const;
|
const std::string & getAccountCookie() const;
|
||||||
const std::string & getAccountDisplayName() const;
|
const std::string & getAccountDisplayName() const;
|
||||||
|
@ -29,6 +29,7 @@ struct GlobalLobbyRoom
|
|||||||
std::string startDateFormatted;
|
std::string startDateFormatted;
|
||||||
ModCompatibilityInfo modList;
|
ModCompatibilityInfo modList;
|
||||||
std::vector<GlobalLobbyAccount> participants;
|
std::vector<GlobalLobbyAccount> participants;
|
||||||
|
std::vector<GlobalLobbyAccount> invited;
|
||||||
int playerLimit;
|
int playerLimit;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,10 +31,33 @@ GlobalLobbyInviteAccountCard::GlobalLobbyInviteAccountCard(const GlobalLobbyAcco
|
|||||||
pos.h = 40;
|
pos.h = 40;
|
||||||
addUsedEvents(LCLICK);
|
addUsedEvents(LCLICK);
|
||||||
|
|
||||||
|
bool thisAccountInvited = false;
|
||||||
|
const auto & myRoomID = CSH->getGlobalLobby().getCurrentGameRoomID();
|
||||||
|
if (!myRoomID.empty())
|
||||||
|
{
|
||||||
|
const auto & myRoom = CSH->getGlobalLobby().getActiveRoomByName(myRoomID);
|
||||||
|
for (auto const & invited : myRoom.invited)
|
||||||
|
{
|
||||||
|
if (invited.accountID == accountID)
|
||||||
|
{
|
||||||
|
thisAccountInvited = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
if (thisAccountInvited)
|
||||||
|
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), Colors::WHITE, 1);
|
||||||
|
else
|
||||||
|
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||||
|
|
||||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
|
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
|
||||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
|
|
||||||
|
if (thisAccountInvited)
|
||||||
|
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.room.state.invited").toString());
|
||||||
|
else
|
||||||
|
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalLobbyInviteAccountCard::clickPressed(const Point & cursorPosition)
|
void GlobalLobbyInviteAccountCard::clickPressed(const Point & cursorPosition)
|
||||||
@ -52,7 +75,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
|
|||||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
|
|
||||||
pos.w = 236;
|
pos.w = 236;
|
||||||
pos.h = 400;
|
pos.h = 420;
|
||||||
|
|
||||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||||
filledBackground->playerColored(PlayerColor(1));
|
filledBackground->playerColored(PlayerColor(1));
|
||||||
@ -69,10 +92,25 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
|
|||||||
return std::make_shared<CIntObject>();
|
return std::make_shared<CIntObject>();
|
||||||
};
|
};
|
||||||
|
|
||||||
listBackground = std::make_shared<TransparentFilledRectangle>(Rect(8, 48, 220, 304), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1);
|
listBackground = std::make_shared<TransparentFilledRectangle>(Rect(8, 48, 220, 324), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1);
|
||||||
accountList = std::make_shared<CListBox>(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, 0, 0, 1 | 4, Rect(200, 0, 300, 300));
|
accountList = std::make_shared<CListBox>(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, 0, 0, 1 | 4, Rect(200, 0, 320, 320));
|
||||||
|
|
||||||
buttonClose = std::make_shared<CButton>(Point(86, 364), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this]() { close(); } );
|
buttonClose = std::make_shared<CButton>(Point(86, 384), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this]() { close(); } );
|
||||||
|
|
||||||
center();
|
center();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GlobalLobbyInviteWindow::onActiveGameRooms(const std::vector<GlobalLobbyRoom> & rooms)
|
||||||
|
{
|
||||||
|
accountList->reset();
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalLobbyInviteWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts)
|
||||||
|
{
|
||||||
|
if (accountList->size() == accounts.size())
|
||||||
|
accountList->reset();
|
||||||
|
else
|
||||||
|
accountList->resize(accounts.size());
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "GlobalLobbyObserver.h"
|
||||||
|
|
||||||
#include "../windows/CWindowObject.h"
|
#include "../windows/CWindowObject.h"
|
||||||
|
|
||||||
class CLabel;
|
class CLabel;
|
||||||
@ -18,7 +20,7 @@ class CListBox;
|
|||||||
class CButton;
|
class CButton;
|
||||||
struct GlobalLobbyAccount;
|
struct GlobalLobbyAccount;
|
||||||
|
|
||||||
class GlobalLobbyInviteWindow : public CWindowObject
|
class GlobalLobbyInviteWindow final : public CWindowObject, public GlobalLobbyObserver
|
||||||
{
|
{
|
||||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||||
std::shared_ptr<CLabel> labelTitle;
|
std::shared_ptr<CLabel> labelTitle;
|
||||||
@ -26,6 +28,9 @@ class GlobalLobbyInviteWindow : public CWindowObject
|
|||||||
std::shared_ptr<TransparentFilledRectangle> listBackground;
|
std::shared_ptr<TransparentFilledRectangle> listBackground;
|
||||||
std::shared_ptr<CButton> buttonClose;
|
std::shared_ptr<CButton> buttonClose;
|
||||||
|
|
||||||
|
void onActiveGameRooms(const std::vector<GlobalLobbyRoom> & rooms) override;
|
||||||
|
void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GlobalLobbyInviteWindow();
|
GlobalLobbyInviteWindow();
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
@ -45,7 +46,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
|
|||||||
labelUsernameTitle = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username"));
|
labelUsernameTitle = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username"));
|
||||||
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString());
|
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString());
|
||||||
backgroundUsername = std::make_shared<TransparentFilledRectangle>(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
|
backgroundUsername = std::make_shared<TransparentFilledRectangle>(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
|
||||||
inputUsername = std::make_shared<CTextInput>(Rect(15, 93, 260, 16), FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true);
|
inputUsername = std::make_shared<CTextInput>(Rect(15, 93, 260, 16), FONT_SMALL, ETextAlignment::CENTERLEFT, true);
|
||||||
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); });
|
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); });
|
||||||
buttonClose = std::make_shared<CButton>(Point(210, 180), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
|
buttonClose = std::make_shared<CButton>(Point(210, 180), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
|
||||||
labelStatus = std::make_shared<CTextBox>( "", Rect(15, 115, 255, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
labelStatus = std::make_shared<CTextBox>( "", Rect(15, 115, 255, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
||||||
@ -71,10 +72,10 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
|
|||||||
toggleMode->setSelected(1);
|
toggleMode->setSelected(1);
|
||||||
|
|
||||||
filledBackground->playerColored(PlayerColor(1));
|
filledBackground->playerColored(PlayerColor(1));
|
||||||
inputUsername->cb += [this](const std::string & text)
|
inputUsername->setCallback([this](const std::string & text)
|
||||||
{
|
{
|
||||||
this->buttonLogin->block(text.empty());
|
this->buttonLogin->block(text.empty());
|
||||||
};
|
});
|
||||||
|
|
||||||
center();
|
center();
|
||||||
}
|
}
|
||||||
|
23
client/globalLobby/GlobalLobbyObserver.h
Normal file
23
client/globalLobby/GlobalLobbyObserver.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* GlobalLobbyObserver.h, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct GlobalLobbyAccount;
|
||||||
|
struct GlobalLobbyRoom;
|
||||||
|
|
||||||
|
/// Interface for windows that want to receive updates whenever state of global lobby changes
|
||||||
|
class GlobalLobbyObserver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts) {}
|
||||||
|
virtual void onActiveGameRooms(const std::vector<GlobalLobbyRoom> & rooms) {}
|
||||||
|
|
||||||
|
virtual ~GlobalLobbyObserver() = default;
|
||||||
|
};
|
@ -76,7 +76,11 @@ static const std::string getJoinRoomErrorMessage(const GlobalLobbyRoom & roomDes
|
|||||||
if (gameStarted)
|
if (gameStarted)
|
||||||
return "vcmi.lobby.preview.error.busy";
|
return "vcmi.lobby.preview.error.busy";
|
||||||
|
|
||||||
if (VCMI_VERSION_STRING != roomDescription.gameVersion)
|
CModVersion localVersion = CModVersion::fromString(VCMI_VERSION_STRING);
|
||||||
|
CModVersion hostVersion = CModVersion::fromString(roomDescription.gameVersion);
|
||||||
|
|
||||||
|
// 1.5.X can play with each other, but not with 1.X.Y
|
||||||
|
if (localVersion.major != hostVersion.major || localVersion.minor != hostVersion.minor)
|
||||||
return "vcmi.lobby.preview.error.version";
|
return "vcmi.lobby.preview.error.version";
|
||||||
|
|
||||||
if (roomDescription.playerLimit == roomDescription.participants.size())
|
if (roomDescription.playerLimit == roomDescription.participants.size())
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../render/Colors.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
@ -231,7 +233,7 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
|
|||||||
pos.w = 230;
|
pos.w = 230;
|
||||||
pos.h = 40;
|
pos.h = 40;
|
||||||
|
|
||||||
if (window->isInviteUnread(roomDescription.gameRoomID))
|
if (hasInvite)
|
||||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), Colors::WHITE, 1);
|
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), Colors::WHITE, 1);
|
||||||
else
|
else
|
||||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||||
|
@ -18,10 +18,11 @@
|
|||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/CTextInput.h"
|
||||||
|
#include "../widgets/Slider.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
|
#include "../widgets/TextControls.h"
|
||||||
|
|
||||||
#include "../../lib/CConfigHandler.h"
|
|
||||||
#include "../../lib/Languages.h"
|
#include "../../lib/Languages.h"
|
||||||
#include "../../lib/MetaString.h"
|
#include "../../lib/MetaString.h"
|
||||||
#include "../../lib/TextOperations.h"
|
#include "../../lib/TextOperations.h"
|
||||||
@ -51,13 +52,14 @@ void GlobalLobbyWindow::doOpenChannel(const std::string & channelType, const std
|
|||||||
currentChannelName = channelName;
|
currentChannelName = channelName;
|
||||||
chatHistory.clear();
|
chatHistory.clear();
|
||||||
unreadChannels.erase(channelType + "_" + channelName);
|
unreadChannels.erase(channelType + "_" + channelName);
|
||||||
widget->getGameChat()->setText("");
|
|
||||||
|
|
||||||
auto history = CSH->getGlobalLobby().getChannelHistory(channelType, channelName);
|
auto history = CSH->getGlobalLobby().getChannelHistory(channelType, channelName);
|
||||||
|
|
||||||
for(const auto & entry : history)
|
for(const auto & entry : history)
|
||||||
onGameChatMessage(entry.displayName, entry.messageText, entry.timeFormatted, channelType, channelName);
|
onGameChatMessage(entry.displayName, entry.messageText, entry.timeFormatted, channelType, channelName);
|
||||||
|
|
||||||
|
refreshChatText();
|
||||||
|
|
||||||
MetaString text;
|
MetaString text;
|
||||||
text.appendTextID("vcmi.lobby.header.chat." + channelType);
|
text.appendTextID("vcmi.lobby.header.chat." + channelType);
|
||||||
text.replaceRawString(roomDescription);
|
text.replaceRawString(roomDescription);
|
||||||
@ -105,8 +107,6 @@ void GlobalLobbyWindow::doInviteAccount(const std::string & accountID)
|
|||||||
|
|
||||||
void GlobalLobbyWindow::doJoinRoom(const std::string & roomID)
|
void GlobalLobbyWindow::doJoinRoom(const std::string & roomID)
|
||||||
{
|
{
|
||||||
unreadInvites.erase(roomID);
|
|
||||||
|
|
||||||
JsonNode toSend;
|
JsonNode toSend;
|
||||||
toSend["type"].String() = "joinGameRoom";
|
toSend["type"].String() = "joinGameRoom";
|
||||||
toSend["gameRoomID"].String() = roomID;
|
toSend["gameRoomID"].String() = roomID;
|
||||||
@ -133,8 +133,12 @@ void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std:
|
|||||||
chatMessageFormatted.replaceRawString(message);
|
chatMessageFormatted.replaceRawString(message);
|
||||||
|
|
||||||
chatHistory += chatMessageFormatted.toString();
|
chatHistory += chatMessageFormatted.toString();
|
||||||
|
}
|
||||||
|
void GlobalLobbyWindow::refreshChatText()
|
||||||
|
{
|
||||||
widget->getGameChat()->setText(chatHistory);
|
widget->getGameChat()->setText(chatHistory);
|
||||||
|
if (widget->getGameChat()->slider)
|
||||||
|
widget->getGameChat()->slider->scrollToMax();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalLobbyWindow::isChannelUnread(const std::string & channelType, const std::string & channelName) const
|
bool GlobalLobbyWindow::isChannelUnread(const std::string & channelType, const std::string & channelName) const
|
||||||
@ -154,7 +158,7 @@ void GlobalLobbyWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> &
|
|||||||
widget->getAccountListHeader()->setText(text.toString());
|
widget->getAccountListHeader()->setText(text.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalLobbyWindow::onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms)
|
void GlobalLobbyWindow::onActiveGameRooms(const std::vector<GlobalLobbyRoom> & rooms)
|
||||||
{
|
{
|
||||||
if (rooms.size() == widget->getRoomList()->size())
|
if (rooms.size() == widget->getRoomList()->size())
|
||||||
widget->getRoomList()->reset();
|
widget->getRoomList()->reset();
|
||||||
@ -180,15 +184,9 @@ void GlobalLobbyWindow::onMatchesHistory(const std::vector<GlobalLobbyRoom> & hi
|
|||||||
|
|
||||||
void GlobalLobbyWindow::onInviteReceived(const std::string & invitedRoomID)
|
void GlobalLobbyWindow::onInviteReceived(const std::string & invitedRoomID)
|
||||||
{
|
{
|
||||||
unreadInvites.insert(invitedRoomID);
|
|
||||||
widget->getRoomList()->reset();
|
widget->getRoomList()->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalLobbyWindow::isInviteUnread(const std::string & gameRoomID) const
|
|
||||||
{
|
|
||||||
return unreadInvites.count(gameRoomID) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GlobalLobbyWindow::onJoinedRoom()
|
void GlobalLobbyWindow::onJoinedRoom()
|
||||||
{
|
{
|
||||||
widget->getAccountList()->reset();
|
widget->getAccountList()->reset();
|
||||||
|
@ -9,13 +9,14 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "GlobalLobbyObserver.h"
|
||||||
#include "../windows/CWindowObject.h"
|
#include "../windows/CWindowObject.h"
|
||||||
|
|
||||||
class GlobalLobbyWidget;
|
class GlobalLobbyWidget;
|
||||||
struct GlobalLobbyAccount;
|
struct GlobalLobbyAccount;
|
||||||
struct GlobalLobbyRoom;
|
struct GlobalLobbyRoom;
|
||||||
|
|
||||||
class GlobalLobbyWindow : public CWindowObject
|
class GlobalLobbyWindow final : public CWindowObject, public GlobalLobbyObserver
|
||||||
{
|
{
|
||||||
std::string chatHistory;
|
std::string chatHistory;
|
||||||
std::string currentChannelType;
|
std::string currentChannelType;
|
||||||
@ -23,7 +24,6 @@ class GlobalLobbyWindow : public CWindowObject
|
|||||||
|
|
||||||
std::shared_ptr<GlobalLobbyWidget> widget;
|
std::shared_ptr<GlobalLobbyWidget> widget;
|
||||||
std::set<std::string> unreadChannels;
|
std::set<std::string> unreadChannels;
|
||||||
std::set<std::string> unreadInvites;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GlobalLobbyWindow();
|
GlobalLobbyWindow();
|
||||||
@ -38,14 +38,15 @@ public:
|
|||||||
|
|
||||||
/// Returns true if provided chat channel is the one that is currently open in UI
|
/// Returns true if provided chat channel is the one that is currently open in UI
|
||||||
bool isChannelOpen(const std::string & channelType, const std::string & channelName) const;
|
bool isChannelOpen(const std::string & channelType, const std::string & channelName) const;
|
||||||
|
/// Returns true if provided channel has unread messages (only messages that were received after login)
|
||||||
bool isChannelUnread(const std::string & channelType, const std::string & channelName) const;
|
bool isChannelUnread(const std::string & channelType, const std::string & channelName) const;
|
||||||
bool isInviteUnread(const std::string & gameRoomID) const;
|
|
||||||
|
|
||||||
// Callbacks for network packs
|
// Callbacks for network packs
|
||||||
|
|
||||||
void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when, const std::string & channelType, const std::string & channelName);
|
void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when, const std::string & channelType, const std::string & channelName);
|
||||||
void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts);
|
void refreshChatText();
|
||||||
void onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms);
|
void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts) override;
|
||||||
|
void onActiveGameRooms(const std::vector<GlobalLobbyRoom> & rooms) override;
|
||||||
void onMatchesHistory(const std::vector<GlobalLobbyRoom> & history);
|
void onMatchesHistory(const std::vector<GlobalLobbyRoom> & history);
|
||||||
void onInviteReceived(const std::string & invitedRoomID);
|
void onInviteReceived(const std::string & invitedRoomID);
|
||||||
void onJoinedRoom();
|
void onJoinedRoom();
|
||||||
|
@ -165,6 +165,11 @@ bool CGuiHandler::isKeyboardCtrlDown() const
|
|||||||
return inputHandlerInstance->isKeyboardCtrlDown();
|
return inputHandlerInstance->isKeyboardCtrlDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CGuiHandler::isKeyboardCmdDown() const
|
||||||
|
{
|
||||||
|
return inputHandlerInstance->isKeyboardCmdDown();
|
||||||
|
}
|
||||||
|
|
||||||
bool CGuiHandler::isKeyboardAltDown() const
|
bool CGuiHandler::isKeyboardAltDown() const
|
||||||
{
|
{
|
||||||
return inputHandlerInstance->isKeyboardAltDown();
|
return inputHandlerInstance->isKeyboardAltDown();
|
||||||
|
@ -62,9 +62,17 @@ public:
|
|||||||
/// May not match size of window if user has UI scaling different from 100%
|
/// May not match size of window if user has UI scaling different from 100%
|
||||||
Point screenDimensions() const;
|
Point screenDimensions() const;
|
||||||
|
|
||||||
/// returns true if chosen keyboard key is currently pressed down
|
/// returns true if Alt is currently pressed down
|
||||||
bool isKeyboardAltDown() const;
|
bool isKeyboardAltDown() const;
|
||||||
|
/// returns true if Ctrl is currently pressed down
|
||||||
|
/// on Apple system, this also tests for Cmd key
|
||||||
|
/// For use with keyboard-based events
|
||||||
bool isKeyboardCtrlDown() const;
|
bool isKeyboardCtrlDown() const;
|
||||||
|
/// on Apple systems, returns true if Cmd key is pressed
|
||||||
|
/// on other systems, returns true if Ctrl is pressed
|
||||||
|
/// /// For use with mouse-based events
|
||||||
|
bool isKeyboardCmdDown() const;
|
||||||
|
/// returns true if Shift is currently pressed down
|
||||||
bool isKeyboardShiftDown() const;
|
bool isKeyboardShiftDown() const;
|
||||||
|
|
||||||
void startTextInput(const Rect & where);
|
void startTextInput(const Rect & where);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/ComboBox.h"
|
#include "../widgets/ComboBox.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
#include "../widgets/Slider.h"
|
#include "../widgets/Slider.h"
|
||||||
@ -610,19 +611,17 @@ std::shared_ptr<CTextInput> InterfaceObjectConfigurable::buildTextInput(const Js
|
|||||||
auto rect = readRect(config["rect"]);
|
auto rect = readRect(config["rect"]);
|
||||||
auto offset = readPosition(config["backgroundOffset"]);
|
auto offset = readPosition(config["backgroundOffset"]);
|
||||||
auto bgName = ImagePath::fromJson(config["background"]);
|
auto bgName = ImagePath::fromJson(config["background"]);
|
||||||
auto result = std::make_shared<CTextInput>(rect, offset, bgName, 0);
|
auto result = std::make_shared<CTextInput>(rect, offset, bgName);
|
||||||
if(!config["alignment"].isNull())
|
if(!config["alignment"].isNull())
|
||||||
result->alignment = readTextAlignment(config["alignment"]);
|
result->setAlignment(readTextAlignment(config["alignment"]));
|
||||||
if(!config["font"].isNull())
|
if(!config["font"].isNull())
|
||||||
result->font = readFont(config["font"]);
|
result->setFont(readFont(config["font"]));
|
||||||
if(!config["color"].isNull())
|
if(!config["color"].isNull())
|
||||||
result->setColor(readColor(config["color"]));
|
result->setColor(readColor(config["color"]));
|
||||||
if(!config["text"].isNull() && config["text"].isString())
|
if(!config["text"].isNull() && config["text"].isString())
|
||||||
result->setText(config["text"].String()); //for input field raw string is taken
|
result->setText(config["text"].String()); //for input field raw string is taken
|
||||||
if(!config["callback"].isNull())
|
if(!config["callback"].isNull())
|
||||||
result->cb += callbacks_string.at(config["callback"].String());
|
result->setCallback(callbacks_string.at(config["callback"].String()));
|
||||||
if(!config["help"].isNull())
|
|
||||||
result->setHelpText(readText(config["help"]));
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +230,16 @@ void CLobbyScreen::toggleChat()
|
|||||||
|
|
||||||
void CLobbyScreen::updateAfterStateChange()
|
void CLobbyScreen::updateAfterStateChange()
|
||||||
{
|
{
|
||||||
|
if(CSH->isHost() && screenType == ESelectionScreen::newGame)
|
||||||
|
{
|
||||||
|
bool isMultiplayer = CSH->loadMode == ELoadMode::MULTI;
|
||||||
|
ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo;
|
||||||
|
info.cheatsAllowed = isMultiplayer ? persistentStorage["startExtraOptions"]["multiPlayer"]["cheatsAllowed"].Bool() : !persistentStorage["startExtraOptions"]["singlePlayer"]["cheatsNotAllowed"].Bool();
|
||||||
|
info.unlimitedReplay = persistentStorage["startExtraOptions"][isMultiplayer ? "multiPlayer" : "singlePlayer"]["unlimitedReplay"].Bool();
|
||||||
|
if(info.cheatsAllowed != CSH->si->extraOptionsInfo.cheatsAllowed || info.unlimitedReplay != CSH->si->extraOptionsInfo.unlimitedReplay)
|
||||||
|
CSH->setExtraOptionsInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
if(CSH->mi)
|
if(CSH->mi)
|
||||||
{
|
{
|
||||||
if (tabOpt)
|
if (tabOpt)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/CTextInput.h"
|
||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
#include "../../lib/CConfigHandler.h"
|
#include "../../lib/CConfigHandler.h"
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "../mainmenu/CMainMenu.h"
|
#include "../mainmenu/CMainMenu.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
@ -362,7 +363,7 @@ CChatBox::CChatBox(const Rect & rect)
|
|||||||
Rect textInputArea(1, rect.h - height, rect.w - 1, height);
|
Rect textInputArea(1, rect.h - height, rect.w - 1, height);
|
||||||
Rect chatHistoryArea(3, 1, rect.w - 3, rect.h - height - 1);
|
Rect chatHistoryArea(3, 1, rect.w - 3, rect.h - height - 1);
|
||||||
inputBackground = std::make_shared<TransparentFilledRectangle>(textInputArea, ColorRGBA(0,0,0,192));
|
inputBackground = std::make_shared<TransparentFilledRectangle>(textInputArea, ColorRGBA(0,0,0,192));
|
||||||
inputBox = std::make_shared<CTextInput>(textInputArea, EFonts::FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true);
|
inputBox = std::make_shared<CTextInput>(textInputArea, EFonts::FONT_SMALL, ETextAlignment::CENTERLEFT, true);
|
||||||
inputBox->removeUsedEvents(KEYBOARD);
|
inputBox->removeUsedEvents(KEYBOARD);
|
||||||
chatHistory = std::make_shared<CTextBox>("", chatHistoryArea, 1);
|
chatHistory = std::make_shared<CTextBox>("", chatHistoryArea, 1);
|
||||||
|
|
||||||
|
@ -10,9 +10,11 @@
|
|||||||
|
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "ExtraOptionsTab.h"
|
#include "ExtraOptionsTab.h"
|
||||||
|
#include "../widgets/Images.h"
|
||||||
|
|
||||||
ExtraOptionsTab::ExtraOptionsTab()
|
ExtraOptionsTab::ExtraOptionsTab()
|
||||||
: OptionsTabBase(JsonPath::builtin("config/widgets/extraOptionsTab.json"))
|
: OptionsTabBase(JsonPath::builtin("config/widgets/extraOptionsTab.json"))
|
||||||
{
|
{
|
||||||
|
if(auto textureCampaignOverdraw = widget<CFilledTexture>("textureCampaignOverdraw"))
|
||||||
|
textureCampaignOverdraw->disable();
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "../render/IFont.h"
|
#include "../render/IFont.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/ComboBox.h"
|
#include "../widgets/ComboBox.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
@ -892,7 +893,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
|
|||||||
labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, name, 95);
|
labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, name, 95);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
labelPlayerNameEdit = std::make_shared<CTextInput>(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, nullptr, ETextAlignment::CENTER, false);
|
labelPlayerNameEdit = std::make_shared<CTextInput>(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, false);
|
||||||
labelPlayerNameEdit->setText(name);
|
labelPlayerNameEdit->setText(name);
|
||||||
}
|
}
|
||||||
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
|
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "CSelectionBase.h"
|
#include "CSelectionBase.h"
|
||||||
|
|
||||||
#include "../widgets/ComboBox.h"
|
#include "../widgets/ComboBox.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/Slider.h"
|
#include "../widgets/Slider.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
@ -22,6 +23,7 @@
|
|||||||
#include "../../lib/Languages.h"
|
#include "../../lib/Languages.h"
|
||||||
#include "../../lib/MetaString.h"
|
#include "../../lib/MetaString.h"
|
||||||
#include "../../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
|
|
||||||
static std::string timeToString(int time)
|
static std::string timeToString(int time)
|
||||||
{
|
{
|
||||||
@ -100,12 +102,18 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
|
|||||||
});
|
});
|
||||||
|
|
||||||
addCallback("setCheatAllowed", [&](int index){
|
addCallback("setCheatAllowed", [&](int index){
|
||||||
|
bool isMultiplayer = CSH->loadMode == ELoadMode::MULTI;
|
||||||
|
Settings entry = persistentStorage.write["startExtraOptions"][isMultiplayer ? "multiPlayer" : "singlePlayer"][isMultiplayer ? "cheatsAllowed" : "cheatsNotAllowed"];
|
||||||
|
entry->Bool() = isMultiplayer ? index : !index;
|
||||||
ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo;
|
ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo;
|
||||||
info.cheatsAllowed = index;
|
info.cheatsAllowed = index;
|
||||||
CSH->setExtraOptionsInfo(info);
|
CSH->setExtraOptionsInfo(info);
|
||||||
});
|
});
|
||||||
|
|
||||||
addCallback("setUnlimitedReplay", [&](int index){
|
addCallback("setUnlimitedReplay", [&](int index){
|
||||||
|
bool isMultiplayer = CSH->loadMode == ELoadMode::MULTI;
|
||||||
|
Settings entry = persistentStorage.write["startExtraOptions"][isMultiplayer ? "multiPlayer" : "singlePlayer"]["unlimitedReplay"];
|
||||||
|
entry->Bool() = index;
|
||||||
ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo;
|
ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo;
|
||||||
info.unlimitedReplay = index;
|
info.unlimitedReplay = index;
|
||||||
CSH->setExtraOptionsInfo(info);
|
CSH->setExtraOptionsInfo(info);
|
||||||
@ -173,7 +181,7 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
|
|||||||
tinfo.baseTimer = time;
|
tinfo.baseTimer = time;
|
||||||
CSH->setTurnTimerInfo(tinfo);
|
CSH->setTurnTimerInfo(tinfo);
|
||||||
if(auto ww = widget<CTextInput>("chessFieldBase"))
|
if(auto ww = widget<CTextInput>("chessFieldBase"))
|
||||||
ww->setText(timeToString(time), false);
|
ww->setText(timeToString(time));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addCallback("parseAndSetTimer_turn", [this, parseTimerString](const std::string & str){
|
addCallback("parseAndSetTimer_turn", [this, parseTimerString](const std::string & str){
|
||||||
@ -184,7 +192,7 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
|
|||||||
tinfo.turnTimer = time;
|
tinfo.turnTimer = time;
|
||||||
CSH->setTurnTimerInfo(tinfo);
|
CSH->setTurnTimerInfo(tinfo);
|
||||||
if(auto ww = widget<CTextInput>("chessFieldTurn"))
|
if(auto ww = widget<CTextInput>("chessFieldTurn"))
|
||||||
ww->setText(timeToString(time), false);
|
ww->setText(timeToString(time));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addCallback("parseAndSetTimer_battle", [this, parseTimerString](const std::string & str){
|
addCallback("parseAndSetTimer_battle", [this, parseTimerString](const std::string & str){
|
||||||
@ -195,7 +203,7 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
|
|||||||
tinfo.battleTimer = time;
|
tinfo.battleTimer = time;
|
||||||
CSH->setTurnTimerInfo(tinfo);
|
CSH->setTurnTimerInfo(tinfo);
|
||||||
if(auto ww = widget<CTextInput>("chessFieldBattle"))
|
if(auto ww = widget<CTextInput>("chessFieldBattle"))
|
||||||
ww->setText(timeToString(time), false);
|
ww->setText(timeToString(time));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addCallback("parseAndSetTimer_unit", [this, parseTimerString](const std::string & str){
|
addCallback("parseAndSetTimer_unit", [this, parseTimerString](const std::string & str){
|
||||||
@ -206,7 +214,7 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
|
|||||||
tinfo.unitTimer = time;
|
tinfo.unitTimer = time;
|
||||||
CSH->setTurnTimerInfo(tinfo);
|
CSH->setTurnTimerInfo(tinfo);
|
||||||
if(auto ww = widget<CTextInput>("chessFieldUnit"))
|
if(auto ww = widget<CTextInput>("chessFieldUnit"))
|
||||||
ww->setText(timeToString(time), false);
|
ww->setText(timeToString(time));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -389,13 +397,13 @@ void OptionsTabBase::recreate(bool campaign)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(auto ww = widget<CTextInput>("chessFieldBase"))
|
if(auto ww = widget<CTextInput>("chessFieldBase"))
|
||||||
ww->setText(timeToString(turnTimerRemote.baseTimer), false);
|
ww->setText(timeToString(turnTimerRemote.baseTimer));
|
||||||
if(auto ww = widget<CTextInput>("chessFieldTurn"))
|
if(auto ww = widget<CTextInput>("chessFieldTurn"))
|
||||||
ww->setText(timeToString(turnTimerRemote.turnTimer), false);
|
ww->setText(timeToString(turnTimerRemote.turnTimer));
|
||||||
if(auto ww = widget<CTextInput>("chessFieldBattle"))
|
if(auto ww = widget<CTextInput>("chessFieldBattle"))
|
||||||
ww->setText(timeToString(turnTimerRemote.battleTimer), false);
|
ww->setText(timeToString(turnTimerRemote.battleTimer));
|
||||||
if(auto ww = widget<CTextInput>("chessFieldUnit"))
|
if(auto ww = widget<CTextInput>("chessFieldUnit"))
|
||||||
ww->setText(timeToString(turnTimerRemote.unitTimer), false);
|
ww->setText(timeToString(turnTimerRemote.unitTimer));
|
||||||
|
|
||||||
if(auto w = widget<ComboBox>("timerModeSwitch"))
|
if(auto w = widget<ComboBox>("timerModeSwitch"))
|
||||||
{
|
{
|
||||||
@ -410,18 +418,15 @@ void OptionsTabBase::recreate(bool campaign)
|
|||||||
if(auto buttonCheatAllowed = widget<CToggleButton>("buttonCheatAllowed"))
|
if(auto buttonCheatAllowed = widget<CToggleButton>("buttonCheatAllowed"))
|
||||||
{
|
{
|
||||||
buttonCheatAllowed->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.cheatsAllowed);
|
buttonCheatAllowed->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.cheatsAllowed);
|
||||||
buttonCheatAllowed->block(SEL->screenType == ESelectionScreen::loadGame);
|
buttonCheatAllowed->block(CSH->isGuest());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(auto buttonUnlimitedReplay = widget<CToggleButton>("buttonUnlimitedReplay"))
|
if(auto buttonUnlimitedReplay = widget<CToggleButton>("buttonUnlimitedReplay"))
|
||||||
{
|
{
|
||||||
buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay);
|
buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay);
|
||||||
buttonUnlimitedReplay->block(SEL->screenType == ESelectionScreen::loadGame);
|
buttonUnlimitedReplay->block(CSH->isGuest());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(auto textureCampaignOverdraw = widget<CFilledTexture>("textureCampaignOverdraw"))
|
if(auto textureCampaignOverdraw = widget<CFilledTexture>("textureCampaignOverdraw"))
|
||||||
{
|
textureCampaignOverdraw->setEnabled(campaign);
|
||||||
if(!campaign)
|
|
||||||
textureCampaignOverdraw->disable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
#include "../widgets/Slider.h"
|
#include "../widgets/Slider.h"
|
||||||
@ -163,8 +164,8 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
|
|||||||
{
|
{
|
||||||
background = std::make_shared<CPicture>(ImagePath::builtin("SCSELBCK.bmp"), 0, 6);
|
background = std::make_shared<CPicture>(ImagePath::builtin("SCSELBCK.bmp"), 0, 6);
|
||||||
pos = background->pos;
|
pos = background->pos;
|
||||||
inputName = std::make_shared<CTextInput>(inputNameRect, Point(-32, -25), ImagePath::builtin("GSSTRIP.bmp"), 0);
|
inputName = std::make_shared<CTextInput>(inputNameRect, Point(-32, -25), ImagePath::builtin("GSSTRIP.bmp"));
|
||||||
inputName->filters += CTextInput::filenameFilter;
|
inputName->setFilterFilename();
|
||||||
labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
|
labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
|
||||||
|
|
||||||
// TODO: Global constants?
|
// TODO: Global constants?
|
||||||
@ -313,7 +314,7 @@ void SelectionTab::clickReleased(const Point & cursorPosition)
|
|||||||
{
|
{
|
||||||
select(line);
|
select(line);
|
||||||
}
|
}
|
||||||
#ifdef VCMI_IOS
|
#ifdef VCMI_MOBILE
|
||||||
// focus input field if clicked inside it
|
// focus input field if clicked inside it
|
||||||
else if(inputName && inputName->isActive() && inputNameRect.isInside(cursorPosition))
|
else if(inputName && inputName->isActive() && inputNameRect.isInside(cursorPosition))
|
||||||
inputName->giveFocus();
|
inputName->giveFocus();
|
||||||
@ -358,6 +359,17 @@ void SelectionTab::clickDouble(const Point & cursorPosition)
|
|||||||
if(itemIndex >= curItems.size())
|
if(itemIndex >= curItems.size())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto clickedItem = curItems[itemIndex];
|
||||||
|
auto selectedItem = getSelectedMapInfo();
|
||||||
|
|
||||||
|
if (clickedItem != selectedItem)
|
||||||
|
{
|
||||||
|
// double-click BUT player hit different item than he had selected
|
||||||
|
// ignore - clickReleased would still trigger and update selection.
|
||||||
|
// After which another (3rd) click if it happens would still register as double-click
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(itemIndex >= 0 && curItems[itemIndex]->isFolder)
|
if(itemIndex >= 0 && curItems[itemIndex]->isFolder)
|
||||||
{
|
{
|
||||||
select(position);
|
select(position);
|
||||||
|
@ -14,11 +14,12 @@
|
|||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../widgets/TextControls.h"
|
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
|
#include "../widgets/TextControls.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
@ -372,7 +373,7 @@ CHighScoreInput::CHighScoreInput(std::string playerName, std::function<void(std:
|
|||||||
buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT);
|
buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT);
|
||||||
buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL);
|
buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL);
|
||||||
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
|
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
|
||||||
textInput = std::make_shared<CTextInput>(Rect(18, 104, 200, 25), FONT_SMALL, nullptr, ETextAlignment::CENTER, true);
|
textInput = std::make_shared<CTextInput>(Rect(18, 104, 200, 25), FONT_SMALL, ETextAlignment::CENTER, true);
|
||||||
textInput->setText(playerName);
|
textInput->setText(playerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "../globalLobby/GlobalLobbyWindow.h"
|
#include "../globalLobby/GlobalLobbyWindow.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
@ -454,7 +455,7 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType)
|
|||||||
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 465, 440, 18), 7, 465));
|
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 465, 440, 18), 7, 465));
|
||||||
playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), background->getSurface());
|
playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), background->getSurface());
|
||||||
playerName->setText(getPlayerName());
|
playerName->setText(getPlayerName());
|
||||||
playerName->cb += std::bind(&CMultiMode::onNameChange, this, _1);
|
playerName->setCallback(std::bind(&CMultiMode::onNameChange, this, _1));
|
||||||
|
|
||||||
buttonHotseat = std::make_shared<CButton>(Point(373, 78 + 57 * 0), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
|
buttonHotseat = std::make_shared<CButton>(Point(373, 78 + 57 * 0), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
|
||||||
buttonLobby = std::make_shared<CButton>(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this));
|
buttonLobby = std::make_shared<CButton>(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this));
|
||||||
@ -513,15 +514,15 @@ CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen S
|
|||||||
for(int i = 0; i < inputNames.size(); i++)
|
for(int i = 0; i < inputNames.size(); i++)
|
||||||
{
|
{
|
||||||
inputNames[i] = std::make_shared<CTextInput>(Rect(60, 85 + i * 30, 280, 16), background->getSurface());
|
inputNames[i] = std::make_shared<CTextInput>(Rect(60, 85 + i * 30, 280, 16), background->getSurface());
|
||||||
inputNames[i]->cb += std::bind(&CMultiPlayers::onChange, this, _1);
|
inputNames[i]->setCallback(std::bind(&CMultiPlayers::onChange, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonOk = std::make_shared<CButton>(Point(95, 338), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), EShortcut::GLOBAL_ACCEPT);
|
buttonOk = std::make_shared<CButton>(Point(95, 338), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), EShortcut::GLOBAL_ACCEPT);
|
||||||
buttonCancel = std::make_shared<CButton>(Point(205, 338), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
|
buttonCancel = std::make_shared<CButton>(Point(205, 338), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
|
||||||
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 381, 348, 18), 7, 381));
|
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 381, 348, 18), 7, 381));
|
||||||
|
|
||||||
inputNames[0]->setText(firstPlayer, true);
|
inputNames[0]->setText(firstPlayer);
|
||||||
#ifndef VCMI_IOS
|
#ifndef VCMI_MOBILE
|
||||||
inputNames[0]->giveFocus();
|
inputNames[0]->giveFocus();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -564,13 +565,14 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
|
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
|
||||||
inputAddress->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
|
inputAddress->setCallback(std::bind(&CSimpleJoinScreen::onChange, this, _1));
|
||||||
inputPort->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
|
inputPort->setCallback(std::bind(&CSimpleJoinScreen::onChange, this, _1));
|
||||||
inputPort->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
|
inputPort->setFilterNumber(0, 65535);
|
||||||
inputAddress->giveFocus();
|
inputAddress->giveFocus();
|
||||||
}
|
}
|
||||||
inputAddress->setText(host ? CSH->getLocalHostname() : CSH->getRemoteHostname(), true);
|
inputAddress->setText(host ? CSH->getLocalHostname() : CSH->getRemoteHostname());
|
||||||
inputPort->setText(std::to_string(host ? CSH->getLocalPort() : CSH->getRemotePort()), true);
|
inputPort->setText(std::to_string(host ? CSH->getLocalPort() : CSH->getRemotePort()));
|
||||||
|
buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty());
|
||||||
|
|
||||||
buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), EShortcut::GLOBAL_CANCEL);
|
buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), EShortcut::GLOBAL_CANCEL);
|
||||||
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
|
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
|
||||||
|
@ -335,8 +335,15 @@ void CSelectableComponent::clickPressed(const Point & cursorPosition)
|
|||||||
|
|
||||||
void CSelectableComponent::clickDouble(const Point & cursorPosition)
|
void CSelectableComponent::clickDouble(const Point & cursorPosition)
|
||||||
{
|
{
|
||||||
if(onChoose)
|
if (!selected)
|
||||||
onChoose();
|
{
|
||||||
|
clickPressed(cursorPosition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (onChoose)
|
||||||
|
onChoose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSelectableComponent::init()
|
void CSelectableComponent::init()
|
||||||
|
@ -485,7 +485,7 @@ bool CGarrisonSlot::handleSplittingShortcuts()
|
|||||||
{
|
{
|
||||||
const bool isAlt = GH.isKeyboardAltDown();
|
const bool isAlt = GH.isKeyboardAltDown();
|
||||||
const bool isLShift = GH.isKeyboardShiftDown();
|
const bool isLShift = GH.isKeyboardShiftDown();
|
||||||
const bool isLCtrl = GH.isKeyboardCtrlDown();
|
const bool isLCtrl = GH.isKeyboardCmdDown();
|
||||||
|
|
||||||
if(!isAlt && !isLShift && !isLCtrl)
|
if(!isAlt && !isLShift && !isLCtrl)
|
||||||
return false; // This is only case when return false
|
return false; // This is only case when return false
|
||||||
|
373
client/widgets/CTextInput.cpp
Normal file
373
client/widgets/CTextInput.cpp
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
/*
|
||||||
|
* CTextInput.cpp, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "CTextInput.h"
|
||||||
|
|
||||||
|
#include "Images.h"
|
||||||
|
#include "TextControls.h"
|
||||||
|
|
||||||
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../gui/Shortcut.h"
|
||||||
|
#include "../render/Graphics.h"
|
||||||
|
#include "../render/IFont.h"
|
||||||
|
|
||||||
|
#include "../../lib/TextOperations.h"
|
||||||
|
|
||||||
|
std::list<CFocusable *> CFocusable::focusables;
|
||||||
|
CFocusable * CFocusable::inputWithFocus;
|
||||||
|
|
||||||
|
CTextInput::CTextInput(const Rect & Pos)
|
||||||
|
:originalAlignment(ETextAlignment::CENTERLEFT)
|
||||||
|
{
|
||||||
|
pos += Pos.topLeft();
|
||||||
|
pos.h = Pos.h;
|
||||||
|
pos.w = Pos.w;
|
||||||
|
|
||||||
|
addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::createLabel(bool giveFocusToInput)
|
||||||
|
{
|
||||||
|
OBJ_CONSTRUCTION;
|
||||||
|
label = std::make_shared<CLabel>();
|
||||||
|
label->pos = pos;
|
||||||
|
label->alignment = originalAlignment;
|
||||||
|
|
||||||
|
#if !defined(VCMI_MOBILE)
|
||||||
|
if(giveFocusToInput)
|
||||||
|
giveFocus();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
CTextInput::CTextInput(const Rect & Pos, EFonts font, ETextAlignment alignment, bool giveFocusToInput)
|
||||||
|
: CTextInput(Pos)
|
||||||
|
{
|
||||||
|
originalAlignment = alignment;
|
||||||
|
setRedrawParent(true);
|
||||||
|
createLabel(giveFocusToInput);
|
||||||
|
setFont(font);
|
||||||
|
setAlignment(alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName)
|
||||||
|
: CTextInput(Pos)
|
||||||
|
{
|
||||||
|
OBJ_CONSTRUCTION;
|
||||||
|
if (!bgName.empty())
|
||||||
|
background = std::make_shared<CPicture>(bgName, bgOffset.x, bgOffset.y);
|
||||||
|
else
|
||||||
|
setRedrawParent(true);
|
||||||
|
|
||||||
|
createLabel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
|
||||||
|
: CTextInput(Pos)
|
||||||
|
{
|
||||||
|
OBJ_CONSTRUCTION;
|
||||||
|
background = std::make_shared<CPicture>(srf, Pos);
|
||||||
|
pos.w = background->pos.w;
|
||||||
|
pos.h = background->pos.h;
|
||||||
|
background->pos = pos;
|
||||||
|
createLabel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setFont(EFonts font)
|
||||||
|
{
|
||||||
|
label->font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setColor(const ColorRGBA & color)
|
||||||
|
{
|
||||||
|
label->color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setAlignment(ETextAlignment alignment)
|
||||||
|
{
|
||||||
|
originalAlignment = alignment;
|
||||||
|
label->alignment = alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string & CTextInput::getText() const
|
||||||
|
{
|
||||||
|
return currentText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setCallback(const TextEditedCallback & cb)
|
||||||
|
{
|
||||||
|
assert(!onTextEdited);
|
||||||
|
onTextEdited = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setFilterFilename()
|
||||||
|
{
|
||||||
|
assert(!onTextFiltering);
|
||||||
|
onTextFiltering = std::bind(&CTextInput::filenameFilter, _1, _2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setFilterNumber(int minValue, int maxValue)
|
||||||
|
{
|
||||||
|
onTextFiltering = std::bind(&CTextInput::numberFilter, _1, _2, minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CTextInput::getVisibleText()
|
||||||
|
{
|
||||||
|
return hasFocus() ? currentText + composedText + "_" : currentText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::clickPressed(const Point & cursorPosition)
|
||||||
|
{
|
||||||
|
// attempt to give focus unconditionally, even if we already have it
|
||||||
|
// this forces on-screen keyboard to show up again, even if player have closed it before
|
||||||
|
giveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::keyPressed(EShortcut key)
|
||||||
|
{
|
||||||
|
if(!hasFocus())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(key == EShortcut::GLOBAL_MOVE_FOCUS)
|
||||||
|
{
|
||||||
|
moveFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool redrawNeeded = false;
|
||||||
|
|
||||||
|
switch(key)
|
||||||
|
{
|
||||||
|
case EShortcut::GLOBAL_BACKSPACE:
|
||||||
|
if(!composedText.empty())
|
||||||
|
{
|
||||||
|
TextOperations::trimRightUnicode(composedText);
|
||||||
|
redrawNeeded = true;
|
||||||
|
}
|
||||||
|
else if(!currentText.empty())
|
||||||
|
{
|
||||||
|
TextOperations::trimRightUnicode(currentText);
|
||||||
|
redrawNeeded = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(redrawNeeded)
|
||||||
|
{
|
||||||
|
updateLabel();
|
||||||
|
if(onTextEdited)
|
||||||
|
onTextEdited(currentText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::setText(const std::string & nText)
|
||||||
|
{
|
||||||
|
currentText = nText;
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::updateLabel()
|
||||||
|
{
|
||||||
|
std::string visibleText = getVisibleText();
|
||||||
|
|
||||||
|
label->alignment = originalAlignment;
|
||||||
|
|
||||||
|
while (graphics->fonts[label->font]->getStringWidth(visibleText) > pos.w)
|
||||||
|
{
|
||||||
|
label->alignment = ETextAlignment::CENTERRIGHT;
|
||||||
|
visibleText = visibleText.substr(TextOperations::getUnicodeCharacterSize(visibleText[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
label->setText(visibleText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::textInputed(const std::string & enteredText)
|
||||||
|
{
|
||||||
|
if(!hasFocus())
|
||||||
|
return;
|
||||||
|
std::string oldText = currentText;
|
||||||
|
|
||||||
|
setText(getText() + enteredText);
|
||||||
|
|
||||||
|
if(onTextFiltering)
|
||||||
|
onTextFiltering(currentText, oldText);
|
||||||
|
|
||||||
|
if(currentText != oldText)
|
||||||
|
{
|
||||||
|
updateLabel();
|
||||||
|
if(onTextEdited)
|
||||||
|
onTextEdited(currentText);
|
||||||
|
}
|
||||||
|
composedText.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::textEdited(const std::string & enteredText)
|
||||||
|
{
|
||||||
|
if(!hasFocus())
|
||||||
|
return;
|
||||||
|
|
||||||
|
composedText = enteredText;
|
||||||
|
updateLabel();
|
||||||
|
//onTextEdited(currentText + composedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::filenameFilter(std::string & text, const std::string &oldText)
|
||||||
|
{
|
||||||
|
static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed
|
||||||
|
size_t pos;
|
||||||
|
while((pos = text.find_first_of(forbiddenChars)) != std::string::npos)
|
||||||
|
text.erase(pos, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue)
|
||||||
|
{
|
||||||
|
assert(minValue < maxValue);
|
||||||
|
|
||||||
|
if(text.empty())
|
||||||
|
text = "0";
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
if(text[0] == '-') //allow '-' sign as first symbol only
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
while(pos < text.size())
|
||||||
|
{
|
||||||
|
if(text[pos] < '0' || text[pos] > '9')
|
||||||
|
{
|
||||||
|
text = oldText;
|
||||||
|
return; //new text is not number.
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int value = boost::lexical_cast<int>(text);
|
||||||
|
if(value < minValue)
|
||||||
|
text = std::to_string(minValue);
|
||||||
|
else if(value > maxValue)
|
||||||
|
text = std::to_string(maxValue);
|
||||||
|
}
|
||||||
|
catch(boost::bad_lexical_cast &)
|
||||||
|
{
|
||||||
|
//Should never happen. Unless I missed some cases
|
||||||
|
logGlobal->warn("Warning: failed to convert %s to number!", text);
|
||||||
|
text = oldText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::activate()
|
||||||
|
{
|
||||||
|
CFocusable::activate();
|
||||||
|
if (hasFocus())
|
||||||
|
{
|
||||||
|
#if defined(VCMI_MOBILE)
|
||||||
|
//giveFocus();
|
||||||
|
#else
|
||||||
|
GH.startTextInput(pos);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::deactivate()
|
||||||
|
{
|
||||||
|
CFocusable::deactivate();
|
||||||
|
if (hasFocus())
|
||||||
|
{
|
||||||
|
#if defined(VCMI_MOBILE)
|
||||||
|
removeFocus();
|
||||||
|
#else
|
||||||
|
GH.stopTextInput();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::onFocusGot()
|
||||||
|
{
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTextInput::onFocusLost()
|
||||||
|
{
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFocusable::focusGot()
|
||||||
|
{
|
||||||
|
if (isActive())
|
||||||
|
GH.startTextInput(pos);
|
||||||
|
onFocusGot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFocusable::focusLost()
|
||||||
|
{
|
||||||
|
if (isActive())
|
||||||
|
GH.stopTextInput();
|
||||||
|
onFocusLost();
|
||||||
|
}
|
||||||
|
|
||||||
|
CFocusable::CFocusable()
|
||||||
|
{
|
||||||
|
focusables.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
CFocusable::~CFocusable()
|
||||||
|
{
|
||||||
|
if(hasFocus())
|
||||||
|
inputWithFocus = nullptr;
|
||||||
|
|
||||||
|
focusables -= this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CFocusable::hasFocus() const
|
||||||
|
{
|
||||||
|
return inputWithFocus == this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFocusable::giveFocus()
|
||||||
|
{
|
||||||
|
auto previousInput = inputWithFocus;
|
||||||
|
inputWithFocus = this;
|
||||||
|
|
||||||
|
if(previousInput)
|
||||||
|
previousInput->focusLost();
|
||||||
|
|
||||||
|
focusGot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFocusable::moveFocus()
|
||||||
|
{
|
||||||
|
auto i = vstd::find(focusables, this);
|
||||||
|
auto ourIt = i;
|
||||||
|
|
||||||
|
for(i++; i != ourIt; i++)
|
||||||
|
{
|
||||||
|
if(i == focusables.end())
|
||||||
|
i = focusables.begin();
|
||||||
|
|
||||||
|
if(*i == this)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if((*i)->isActive())
|
||||||
|
{
|
||||||
|
(*i)->giveFocus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFocusable::removeFocus()
|
||||||
|
{
|
||||||
|
if(this == inputWithFocus)
|
||||||
|
{
|
||||||
|
inputWithFocus = nullptr;
|
||||||
|
focusLost();
|
||||||
|
}
|
||||||
|
}
|
106
client/widgets/CTextInput.h
Normal file
106
client/widgets/CTextInput.h
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* CTextInput.h, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../gui/CIntObject.h"
|
||||||
|
#include "../gui/TextAlignment.h"
|
||||||
|
#include "../render/EFont.h"
|
||||||
|
|
||||||
|
#include "../../lib/filesystem/ResourcePath.h"
|
||||||
|
|
||||||
|
class CLabel;
|
||||||
|
class IImage;
|
||||||
|
|
||||||
|
/// UIElement which can get input focus
|
||||||
|
class CFocusable : public CIntObject
|
||||||
|
{
|
||||||
|
friend void removeFocusFromActiveInput();
|
||||||
|
|
||||||
|
static std::atomic<int> usageIndex;
|
||||||
|
static std::list<CFocusable *> focusables; //all existing objs
|
||||||
|
static CFocusable * inputWithFocus; //who has focus now
|
||||||
|
|
||||||
|
void focusGot();
|
||||||
|
void focusLost();
|
||||||
|
|
||||||
|
virtual void onFocusGot() = 0;
|
||||||
|
virtual void onFocusLost() = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void giveFocus(); //captures focus
|
||||||
|
void moveFocus(); //moves focus to next active control (may be used for tab switching)
|
||||||
|
void removeFocus(); //remove focus
|
||||||
|
bool hasFocus() const;
|
||||||
|
|
||||||
|
CFocusable();
|
||||||
|
~CFocusable();
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Text input box where players can enter text
|
||||||
|
class CTextInput final : public CFocusable
|
||||||
|
{
|
||||||
|
using TextEditedCallback = std::function<void(const std::string &)>;
|
||||||
|
using TextFilterCallback = std::function<void(std::string &, const std::string &)>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string currentText;
|
||||||
|
std::string composedText;
|
||||||
|
ETextAlignment originalAlignment;
|
||||||
|
|
||||||
|
std::shared_ptr<CPicture> background;
|
||||||
|
std::shared_ptr<CLabel> label;
|
||||||
|
|
||||||
|
TextEditedCallback onTextEdited;
|
||||||
|
TextFilterCallback onTextFiltering;
|
||||||
|
|
||||||
|
//Filter that will block all characters not allowed in filenames
|
||||||
|
static void filenameFilter(std::string & text, const std::string & oldText);
|
||||||
|
//Filter that will allow only input of numbers in range min-max (min-max are allowed)
|
||||||
|
//min-max should be set via something like std::bind
|
||||||
|
static void numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue);
|
||||||
|
|
||||||
|
std::string getVisibleText();
|
||||||
|
void createLabel(bool giveFocusToInput);
|
||||||
|
void updateLabel();
|
||||||
|
|
||||||
|
void clickPressed(const Point & cursorPosition) final;
|
||||||
|
void textInputed(const std::string & enteredText) final;
|
||||||
|
void textEdited(const std::string & enteredText) final;
|
||||||
|
void onFocusGot() final;
|
||||||
|
void onFocusLost() final;
|
||||||
|
|
||||||
|
CTextInput(const Rect & Pos);
|
||||||
|
public:
|
||||||
|
CTextInput(const Rect & Pos, EFonts font, ETextAlignment alignment, bool giveFocusToInput);
|
||||||
|
CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName);
|
||||||
|
CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf);
|
||||||
|
|
||||||
|
/// Returns currently entered text. May not match visible text
|
||||||
|
const std::string & getText() const;
|
||||||
|
|
||||||
|
void setText(const std::string & nText);
|
||||||
|
|
||||||
|
/// Set callback that will be called whenever player enters new text
|
||||||
|
void setCallback(const TextEditedCallback & cb);
|
||||||
|
|
||||||
|
/// Enables filtering entered text that ensures that text is valid filename (existing or not)
|
||||||
|
void setFilterFilename();
|
||||||
|
/// Enable filtering entered text that ensures that text is valid number in provided range [min, max]
|
||||||
|
void setFilterNumber(int minValue, int maxValue);
|
||||||
|
|
||||||
|
void setFont(EFonts Font);
|
||||||
|
void setColor(const ColorRGBA & Color);
|
||||||
|
void setAlignment(ETextAlignment alignment);
|
||||||
|
|
||||||
|
// CIntObject interface impl
|
||||||
|
void keyPressed(EShortcut key) final;
|
||||||
|
void activate() final;
|
||||||
|
void deactivate() final;
|
||||||
|
};
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
|
||||||
#include "../windows/CMessage.h"
|
#include "../windows/CMessage.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../adventureMap/CInGameConsole.h"
|
#include "../adventureMap/CInGameConsole.h"
|
||||||
@ -30,9 +29,6 @@
|
|||||||
#include "lib/CAndroidVMHelper.h"
|
#include "lib/CAndroidVMHelper.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::list<CFocusable*> CFocusable::focusables;
|
|
||||||
CFocusable * CFocusable::inputWithFocus;
|
|
||||||
|
|
||||||
std::string CLabel::visibleText()
|
std::string CLabel::visibleText()
|
||||||
{
|
{
|
||||||
return text;
|
return text;
|
||||||
@ -409,6 +405,7 @@ void CTextBox::setText(const std::string & text)
|
|||||||
{
|
{
|
||||||
// slider is no longer needed
|
// slider is no longer needed
|
||||||
slider.reset();
|
slider.reset();
|
||||||
|
label->scrollTextTo(0);
|
||||||
}
|
}
|
||||||
else if(slider)
|
else if(slider)
|
||||||
{
|
{
|
||||||
@ -571,283 +568,3 @@ Point CGStatusBar::getBorderSize()
|
|||||||
assert(0);
|
assert(0);
|
||||||
return Point();
|
return Point();
|
||||||
}
|
}
|
||||||
|
|
||||||
CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(const std::string &)> & CB, ETextAlignment alignment, bool giveFocusToInput)
|
|
||||||
: CLabel(Pos.x, Pos.y, font, alignment),
|
|
||||||
cb(CB)
|
|
||||||
{
|
|
||||||
setRedrawParent(true);
|
|
||||||
pos.h = Pos.h;
|
|
||||||
pos.w = Pos.w;
|
|
||||||
maxWidth = Pos.w;
|
|
||||||
background.reset();
|
|
||||||
addUsedEvents(LCLICK | SHOW_POPUP | KEYBOARD | TEXTINPUT);
|
|
||||||
|
|
||||||
#if !defined(VCMI_MOBILE)
|
|
||||||
if(giveFocusToInput)
|
|
||||||
giveFocus();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName, const CFunctionList<void(const std::string &)> & CB)
|
|
||||||
:cb(CB)
|
|
||||||
{
|
|
||||||
pos += Pos.topLeft();
|
|
||||||
pos.h = Pos.h;
|
|
||||||
pos.w = Pos.w;
|
|
||||||
maxWidth = Pos.w;
|
|
||||||
|
|
||||||
OBJ_CONSTRUCTION;
|
|
||||||
background = std::make_shared<CPicture>(bgName, bgOffset.x, bgOffset.y);
|
|
||||||
addUsedEvents(LCLICK | SHOW_POPUP | KEYBOARD | TEXTINPUT);
|
|
||||||
|
|
||||||
#if !defined(VCMI_MOBILE)
|
|
||||||
giveFocus();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
|
|
||||||
{
|
|
||||||
pos += Pos.topLeft();
|
|
||||||
OBJ_CONSTRUCTION;
|
|
||||||
background = std::make_shared<CPicture>(srf, Pos);
|
|
||||||
pos.w = background->pos.w;
|
|
||||||
pos.h = background->pos.h;
|
|
||||||
maxWidth = Pos.w;
|
|
||||||
background->pos = pos;
|
|
||||||
addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
|
|
||||||
|
|
||||||
#if !defined(VCMI_MOBILE)
|
|
||||||
giveFocus();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::atomic<int> CFocusable::usageIndex(0);
|
|
||||||
|
|
||||||
void CFocusable::focusGot()
|
|
||||||
{
|
|
||||||
GH.startTextInput(pos);
|
|
||||||
usageIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CFocusable::focusLost()
|
|
||||||
{
|
|
||||||
if(0 == --usageIndex)
|
|
||||||
{
|
|
||||||
GH.stopTextInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CTextInput::visibleText()
|
|
||||||
{
|
|
||||||
return focus ? text + newText + "_" : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::clickPressed(const Point & cursorPosition)
|
|
||||||
{
|
|
||||||
if(!focus)
|
|
||||||
giveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::keyPressed(EShortcut key)
|
|
||||||
{
|
|
||||||
if(!focus)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(key == EShortcut::GLOBAL_MOVE_FOCUS)
|
|
||||||
{
|
|
||||||
moveFocus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool redrawNeeded = false;
|
|
||||||
|
|
||||||
switch(key)
|
|
||||||
{
|
|
||||||
case EShortcut::GLOBAL_BACKSPACE:
|
|
||||||
if(!newText.empty())
|
|
||||||
{
|
|
||||||
TextOperations::trimRightUnicode(newText);
|
|
||||||
redrawNeeded = true;
|
|
||||||
}
|
|
||||||
else if(!text.empty())
|
|
||||||
{
|
|
||||||
TextOperations::trimRightUnicode(text);
|
|
||||||
redrawNeeded = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(redrawNeeded)
|
|
||||||
{
|
|
||||||
redraw();
|
|
||||||
cb(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::showPopupWindow(const Point & cursorPosition)
|
|
||||||
{
|
|
||||||
if(!helpBox.empty()) //there is no point to show window with nothing inside...
|
|
||||||
CRClickPopup::createAndPush(helpBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void CTextInput::setText(const std::string & nText)
|
|
||||||
{
|
|
||||||
setText(nText, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::setText(const std::string & nText, bool callCb)
|
|
||||||
{
|
|
||||||
CLabel::setText(nText);
|
|
||||||
if(callCb)
|
|
||||||
cb(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::setHelpText(const std::string & text)
|
|
||||||
{
|
|
||||||
helpBox = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::textInputed(const std::string & enteredText)
|
|
||||||
{
|
|
||||||
if(!focus)
|
|
||||||
return;
|
|
||||||
std::string oldText = text;
|
|
||||||
|
|
||||||
setText(getText() + enteredText);
|
|
||||||
|
|
||||||
filters(text, oldText);
|
|
||||||
if(text != oldText)
|
|
||||||
{
|
|
||||||
redraw();
|
|
||||||
cb(text);
|
|
||||||
}
|
|
||||||
newText.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::textEdited(const std::string & enteredText)
|
|
||||||
{
|
|
||||||
if(!focus)
|
|
||||||
return;
|
|
||||||
|
|
||||||
newText = enteredText;
|
|
||||||
redraw();
|
|
||||||
cb(text + newText);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::filenameFilter(std::string & text, const std::string &)
|
|
||||||
{
|
|
||||||
static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed
|
|
||||||
size_t pos;
|
|
||||||
while((pos = text.find_first_of(forbiddenChars)) != std::string::npos)
|
|
||||||
text.erase(pos, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue)
|
|
||||||
{
|
|
||||||
assert(minValue < maxValue);
|
|
||||||
|
|
||||||
if(text.empty())
|
|
||||||
text = "0";
|
|
||||||
|
|
||||||
size_t pos = 0;
|
|
||||||
if(text[0] == '-') //allow '-' sign as first symbol only
|
|
||||||
pos++;
|
|
||||||
|
|
||||||
while(pos < text.size())
|
|
||||||
{
|
|
||||||
if(text[pos] < '0' || text[pos] > '9')
|
|
||||||
{
|
|
||||||
text = oldText;
|
|
||||||
return; //new text is not number.
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int value = boost::lexical_cast<int>(text);
|
|
||||||
if(value < minValue)
|
|
||||||
text = std::to_string(minValue);
|
|
||||||
else if(value > maxValue)
|
|
||||||
text = std::to_string(maxValue);
|
|
||||||
}
|
|
||||||
catch(boost::bad_lexical_cast &)
|
|
||||||
{
|
|
||||||
//Should never happen. Unless I missed some cases
|
|
||||||
logGlobal->warn("Warning: failed to convert %s to number!", text);
|
|
||||||
text = oldText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CFocusable::CFocusable()
|
|
||||||
{
|
|
||||||
focus = false;
|
|
||||||
focusables.push_back(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
CFocusable::~CFocusable()
|
|
||||||
{
|
|
||||||
if(hasFocus())
|
|
||||||
{
|
|
||||||
inputWithFocus = nullptr;
|
|
||||||
focusLost();
|
|
||||||
}
|
|
||||||
|
|
||||||
focusables -= this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CFocusable::hasFocus() const
|
|
||||||
{
|
|
||||||
return inputWithFocus == this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CFocusable::giveFocus()
|
|
||||||
{
|
|
||||||
focus = true;
|
|
||||||
focusGot();
|
|
||||||
redraw();
|
|
||||||
|
|
||||||
if(inputWithFocus)
|
|
||||||
{
|
|
||||||
inputWithFocus->focus = false;
|
|
||||||
inputWithFocus->focusLost();
|
|
||||||
inputWithFocus->redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputWithFocus = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CFocusable::moveFocus()
|
|
||||||
{
|
|
||||||
auto i = vstd::find(focusables, this),
|
|
||||||
ourIt = i;
|
|
||||||
for(i++; i != ourIt; i++)
|
|
||||||
{
|
|
||||||
if(i == focusables.end())
|
|
||||||
i = focusables.begin();
|
|
||||||
|
|
||||||
if (*i == this)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if((*i)->isActive())
|
|
||||||
{
|
|
||||||
(*i)->giveFocus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CFocusable::removeFocus()
|
|
||||||
{
|
|
||||||
if(this == inputWithFocus)
|
|
||||||
{
|
|
||||||
focus = false;
|
|
||||||
focusLost();
|
|
||||||
redraw();
|
|
||||||
|
|
||||||
inputWithFocus = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
#include "../gui/TextAlignment.h"
|
#include "../gui/TextAlignment.h"
|
||||||
#include "../render/Colors.h"
|
#include "../render/Colors.h"
|
||||||
#include "../render/EFont.h"
|
#include "../render/EFont.h"
|
||||||
#include "../../lib/FunctionList.h"
|
|
||||||
#include "../../lib/filesystem/ResourcePath.h"
|
#include "../../lib/filesystem/ResourcePath.h"
|
||||||
|
|
||||||
class IImage;
|
class IImage;
|
||||||
@ -168,64 +167,3 @@ public:
|
|||||||
void setEnteringMode(bool on) override;
|
void setEnteringMode(bool on) override;
|
||||||
void setEnteredText(const std::string & text) override;
|
void setEnteredText(const std::string & text) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// UIElement which can get input focus
|
|
||||||
class CFocusable : public virtual CIntObject
|
|
||||||
{
|
|
||||||
static std::atomic<int> usageIndex;
|
|
||||||
public:
|
|
||||||
bool focus; //only one focusable control can have focus at one moment
|
|
||||||
|
|
||||||
void giveFocus(); //captures focus
|
|
||||||
void moveFocus(); //moves focus to next active control (may be used for tab switching)
|
|
||||||
void removeFocus(); //remove focus
|
|
||||||
bool hasFocus() const;
|
|
||||||
|
|
||||||
void focusGot();
|
|
||||||
void focusLost();
|
|
||||||
|
|
||||||
static std::list<CFocusable *> focusables; //all existing objs
|
|
||||||
static CFocusable * inputWithFocus; //who has focus now
|
|
||||||
|
|
||||||
CFocusable();
|
|
||||||
~CFocusable();
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Text input box where players can enter text
|
|
||||||
class CTextInput : public CLabel, public CFocusable
|
|
||||||
{
|
|
||||||
std::string newText;
|
|
||||||
std::string helpBox; //for right-click help
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string visibleText() override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
CFunctionList<void(const std::string &)> cb;
|
|
||||||
CFunctionList<void(std::string &, const std::string &)> filters;
|
|
||||||
void setText(const std::string & nText) override;
|
|
||||||
void setText(const std::string & nText, bool callCb);
|
|
||||||
void setHelpText(const std::string &);
|
|
||||||
|
|
||||||
CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(const std::string &)> & CB, ETextAlignment alignment, bool giveFocusToInput);
|
|
||||||
CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName, const CFunctionList<void(const std::string &)> & CB);
|
|
||||||
CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf);
|
|
||||||
|
|
||||||
void clickPressed(const Point & cursorPosition) override;
|
|
||||||
void keyPressed(EShortcut key) override;
|
|
||||||
void showPopupWindow(const Point & cursorPosition) override;
|
|
||||||
|
|
||||||
//bool captureThisKey(EShortcut key) override;
|
|
||||||
|
|
||||||
void textInputed(const std::string & enteredText) override;
|
|
||||||
void textEdited(const std::string & enteredText) override;
|
|
||||||
|
|
||||||
//Filter that will block all characters not allowed in filenames
|
|
||||||
static void filenameFilter(std::string & text, const std::string & oldText);
|
|
||||||
//Filter that will allow only input of numbers in range min-max (min-max are allowed)
|
|
||||||
//min-max should be set via something like std::bind
|
|
||||||
static void numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue);
|
|
||||||
|
|
||||||
friend class CKeyboardFocusListener;
|
|
||||||
};
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
#include "../adventureMap/AdventureMapInterface.h"
|
#include "../adventureMap/AdventureMapInterface.h"
|
||||||
#include "../render/CAnimation.h"
|
#include "../render/CAnimation.h"
|
||||||
@ -137,7 +138,8 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
|
|||||||
searchBoxRectangle = std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor);
|
searchBoxRectangle = std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor);
|
||||||
searchBoxDescription = std::make_shared<CLabel>(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search"));
|
searchBoxDescription = std::make_shared<CLabel>(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search"));
|
||||||
|
|
||||||
searchBox = std::make_shared<CTextInput>(r, FONT_SMALL, std::bind(&CSpellWindow::searchInput, this), ETextAlignment::CENTER, true);
|
searchBox = std::make_shared<CTextInput>(r, FONT_SMALL, ETextAlignment::CENTER, true);
|
||||||
|
searchBox->setCallback(std::bind(&CSpellWindow::searchInput, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
processSpells();
|
processSpells();
|
||||||
|
@ -151,7 +151,7 @@ void CWindowWithArtifacts::clickPressedArtPlaceHero(const CArtifactsOfHeroBase &
|
|||||||
{
|
{
|
||||||
assert(artSetPtr->getHero()->getSlotByInstance(art) != ArtifactPosition::PRE_FIRST);
|
assert(artSetPtr->getHero()->getSlotByInstance(art) != ArtifactPosition::PRE_FIRST);
|
||||||
|
|
||||||
if(GH.isKeyboardCtrlDown())
|
if(GH.isKeyboardCmdDown())
|
||||||
{
|
{
|
||||||
std::shared_ptr<CArtifactsOfHeroMain> anotherHeroEquipmentPointer = nullptr;
|
std::shared_ptr<CArtifactsOfHeroMain> anotherHeroEquipmentPointer = nullptr;
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/CGarrisonInt.h"
|
#include "../widgets/CGarrisonInt.h"
|
||||||
#include "../widgets/CreatureCostBox.h"
|
#include "../widgets/CreatureCostBox.h"
|
||||||
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/Slider.h"
|
#include "../widgets/Slider.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
@ -328,15 +329,18 @@ CSplitWindow::CSplitWindow(const CCreature * creature, std::function<void(int, i
|
|||||||
|
|
||||||
int sliderPosition = total - leftMin - rightMin;
|
int sliderPosition = total - leftMin - rightMin;
|
||||||
|
|
||||||
leftInput = std::make_shared<CTextInput>(Rect(20, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, true), ETextAlignment::CENTER, true);
|
leftInput = std::make_shared<CTextInput>(Rect(20, 218, 100, 36), FONT_BIG, ETextAlignment::CENTER, true);
|
||||||
rightInput = std::make_shared<CTextInput>(Rect(176, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, false), ETextAlignment::CENTER, true);
|
rightInput = std::make_shared<CTextInput>(Rect(176, 218, 100, 36), FONT_BIG, ETextAlignment::CENTER, true);
|
||||||
|
|
||||||
|
leftInput->setCallback(std::bind(&CSplitWindow::setAmountText, this, _1, true));
|
||||||
|
rightInput->setCallback(std::bind(&CSplitWindow::setAmountText, this, _1, false));
|
||||||
|
|
||||||
//add filters to allow only number input
|
//add filters to allow only number input
|
||||||
leftInput->filters += std::bind(&CTextInput::numberFilter, _1, _2, leftMin, leftMax);
|
leftInput->setFilterNumber(leftMin, leftMax);
|
||||||
rightInput->filters += std::bind(&CTextInput::numberFilter, _1, _2, rightMin, rightMax);
|
rightInput->setFilterNumber(rightMin, rightMax);
|
||||||
|
|
||||||
leftInput->setText(std::to_string(leftAmount), false);
|
leftInput->setText(std::to_string(leftAmount));
|
||||||
rightInput->setText(std::to_string(rightAmount), false);
|
rightInput->setText(std::to_string(rightAmount));
|
||||||
|
|
||||||
animLeft = std::make_shared<CCreaturePic>(20, 54, creature, true, false);
|
animLeft = std::make_shared<CCreaturePic>(20, 54, creature, true, false);
|
||||||
animRight = std::make_shared<CCreaturePic>(177, 54,creature, true, false);
|
animRight = std::make_shared<CCreaturePic>(177, 54,creature, true, false);
|
||||||
@ -616,6 +620,9 @@ void CTavernWindow::HeroPortrait::clickDouble(const Point & cursorPosition)
|
|||||||
|
|
||||||
void CTavernWindow::HeroPortrait::showPopupWindow(const Point & cursorPosition)
|
void CTavernWindow::HeroPortrait::showPopupWindow(const Point & cursorPosition)
|
||||||
{
|
{
|
||||||
|
// h3 behavior - right-click also selects hero
|
||||||
|
clickPressed(cursorPosition);
|
||||||
|
|
||||||
if(h)
|
if(h)
|
||||||
GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(h));
|
GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(h));
|
||||||
}
|
}
|
||||||
@ -848,7 +855,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
|
|||||||
bool moveEquipped = true;
|
bool moveEquipped = true;
|
||||||
bool moveBackpack = true;
|
bool moveBackpack = true;
|
||||||
|
|
||||||
if(GH.isKeyboardCtrlDown())
|
if(GH.isKeyboardCmdDown())
|
||||||
moveBackpack = false;
|
moveBackpack = false;
|
||||||
else if(GH.isKeyboardShiftDown())
|
else if(GH.isKeyboardShiftDown())
|
||||||
moveEquipped = false;
|
moveEquipped = false;
|
||||||
@ -1712,6 +1719,12 @@ void CObjectListWindow::CItem::clickPressed(const Point & cursorPosition)
|
|||||||
|
|
||||||
void CObjectListWindow::CItem::clickDouble(const Point & cursorPosition)
|
void CObjectListWindow::CItem::clickDouble(const Point & cursorPosition)
|
||||||
{
|
{
|
||||||
|
if (parent->selected != index)
|
||||||
|
{
|
||||||
|
clickPressed(cursorPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
parent->elementSelected();
|
parent->elementSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"invited" :
|
||||||
|
{
|
||||||
|
"type" : "array",
|
||||||
|
"description" : "List of accounts that were invited to this room",
|
||||||
|
"items" :
|
||||||
|
{
|
||||||
|
"type" : "object",
|
||||||
|
"additionalProperties" : false,
|
||||||
|
"required" : [ "accountID", "displayName" ],
|
||||||
|
"properties" : {
|
||||||
|
"accountID" :
|
||||||
|
{
|
||||||
|
"type" : "string",
|
||||||
|
"description" : "Unique ID of an account"
|
||||||
|
},
|
||||||
|
"displayName" :
|
||||||
|
{
|
||||||
|
"type" : "string",
|
||||||
|
"description" : "Display name of an account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"mods" :
|
"mods" :
|
||||||
{
|
{
|
||||||
"type" : "array",
|
"type" : "array",
|
||||||
|
@ -78,6 +78,11 @@
|
|||||||
"maximum" : 8,
|
"maximum" : 8,
|
||||||
"description" : "Maximum number of players that can join this room, including host"
|
"description" : "Maximum number of players that can join this room, including host"
|
||||||
},
|
},
|
||||||
|
"version" :
|
||||||
|
{
|
||||||
|
"type" : "string",
|
||||||
|
"description" : "Version of match server, e.g. 1.5.0"
|
||||||
|
},
|
||||||
"ageSeconds" :
|
"ageSeconds" :
|
||||||
{
|
{
|
||||||
"type" : "number",
|
"type" : "number",
|
||||||
|
@ -374,7 +374,7 @@
|
|||||||
},
|
},
|
||||||
"backgroundDimSmallWindows" : {
|
"backgroundDimSmallWindows" : {
|
||||||
"type" : "boolean",
|
"type" : "boolean",
|
||||||
"default" : true
|
"default" : false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -130,7 +130,6 @@
|
|||||||
{
|
{
|
||||||
"name" : "messageInput",
|
"name" : "messageInput",
|
||||||
"type": "textInput",
|
"type": "textInput",
|
||||||
"alignment" : "left",
|
|
||||||
"rect": {"x": 440, "y": 568, "w": 377, "h": 20}
|
"rect": {"x": 440, "y": 568, "w": 377, "h": 20}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
||||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
|
[](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
|
||||||
|
[](https://github.com/vcmi/vcmi/releases/tag/1.5.1)
|
||||||
[](https://github.com/vcmi/vcmi/releases)
|
[](https://github.com/vcmi/vcmi/releases)
|
||||||
|
|
||||||
# VCMI Project
|
# VCMI Project
|
||||||
|
@ -123,6 +123,7 @@
|
|||||||
<control>pointing</control>
|
<control>pointing</control>
|
||||||
<control>keyboard</control>
|
<control>keyboard</control>
|
||||||
<control>touch</control>
|
<control>touch</control>
|
||||||
|
<control>gamepad</control>
|
||||||
</recommends>
|
</recommends>
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1">
|
||||||
<content_attribute id="violence-cartoon">moderate</content_attribute>
|
<content_attribute id="violence-cartoon">moderate</content_attribute>
|
||||||
|
@ -254,7 +254,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.ui" line="373"/>
|
<location filename="../modManager/cmodlistview_moc.ui" line="373"/>
|
||||||
<source>Install from file</source>
|
<source>Install from file</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Instalar a partir de arquivo</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.ui" line="424"/>
|
<location filename="../modManager/cmodlistview_moc.ui" line="424"/>
|
||||||
@ -350,18 +350,18 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="310"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="310"/>
|
||||||
<source>please upgrade mod</source>
|
<source>please upgrade mod</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>por favor, atualize o mod</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="182"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="182"/>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="796"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="796"/>
|
||||||
<source>mods repository index</source>
|
<source>mods repository index</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>índice do repositório de mods</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
|
||||||
<source>or newer</source>
|
<source>or newer</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>ou mais recente</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
|
||||||
@ -416,42 +416,42 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
||||||
<source>All supported files</source>
|
<source>All supported files</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Todos os arquivos suportados</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
||||||
<source>Maps</source>
|
<source>Maps</source>
|
||||||
<translation type="unfinished">Mapas</translation>
|
<translation>Mapas</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
||||||
<source>Campaigns</source>
|
<source>Campaigns</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Campanhas</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
||||||
<source>Configs</source>
|
<source>Configs</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Configurações</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
|
||||||
<source>Mods</source>
|
<source>Mods</source>
|
||||||
<translation type="unfinished">Mods</translation>
|
<translation>Mods</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="628"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="628"/>
|
||||||
<source>Select files (configs, mods, maps, campaigns) to install...</source>
|
<source>Select files (configs, mods, maps, campaigns) to install...</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Selecione arquivos (configurações, mods, mapas, campanhas) para instalar...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
|
||||||
<source>Replace config file?</source>
|
<source>Replace config file?</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Substituir arquivo de configuração?</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
|
||||||
<source>Do you want to replace %1?</source>
|
<source>Do you want to replace %1?</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Você deseja substituir %1?</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="693"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="693"/>
|
||||||
@ -505,7 +505,7 @@ Instalar o download realizado com sucesso?</translation>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="960"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="960"/>
|
||||||
<source>screenshots</source>
|
<source>screenshots</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>capturas de tela</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistview_moc.cpp" line="966"/>
|
<location filename="../modManager/cmodlistview_moc.cpp" line="966"/>
|
||||||
@ -841,12 +841,12 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../settingsView/csettingsview_moc.cpp" line="437"/>
|
<location filename="../settingsView/csettingsview_moc.cpp" line="437"/>
|
||||||
<source>Enable</source>
|
<source>Enable</source>
|
||||||
<translation>Habilitar</translation>
|
<translation>Ativar</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../settingsView/csettingsview_moc.cpp" line="442"/>
|
<location filename="../settingsView/csettingsview_moc.cpp" line="442"/>
|
||||||
<source>Not Installed</source>
|
<source>Not Installed</source>
|
||||||
<translation>Não instalado</translation>
|
<translation>Não Instalado</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../settingsView/csettingsview_moc.cpp" line="443"/>
|
<location filename="../settingsView/csettingsview_moc.cpp" line="443"/>
|
||||||
@ -859,27 +859,27 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlist.cpp" line="21"/>
|
<location filename="../modManager/cmodlist.cpp" line="21"/>
|
||||||
<source>%1 B</source>
|
<source>%1 B</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1 B</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlist.cpp" line="22"/>
|
<location filename="../modManager/cmodlist.cpp" line="22"/>
|
||||||
<source>%1 KiB</source>
|
<source>%1 KiB</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1 KiB</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlist.cpp" line="23"/>
|
<location filename="../modManager/cmodlist.cpp" line="23"/>
|
||||||
<source>%1 MiB</source>
|
<source>%1 MiB</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1 MiB</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlist.cpp" line="24"/>
|
<location filename="../modManager/cmodlist.cpp" line="24"/>
|
||||||
<source>%1 GiB</source>
|
<source>%1 GiB</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1 GiB</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlist.cpp" line="25"/>
|
<location filename="../modManager/cmodlist.cpp" line="25"/>
|
||||||
<source>%1 TiB</source>
|
<source>%1 TiB</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1 TiB</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -935,7 +935,7 @@ Heroes® of Might and Magic® III HD atualmente não é suportado!</translation>
|
|||||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="288"/>
|
<location filename="../firstLaunch/firstlaunch_moc.ui" line="288"/>
|
||||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="350"/>
|
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="350"/>
|
||||||
<source>Install gog.com files</source>
|
<source>Install gog.com files</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Instalar arquivos do gog.com</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
|
<location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
|
||||||
@ -1021,7 +1021,7 @@ Heroes® of Might and Magic® III HD atualmente não é suportado!</translation>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
|
<location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
|
||||||
<source>If you don't have a copy of Heroes III installed, VCMI can import your Heroes III data using the offline installer from gog.com.</source>
|
<source>If you don't have a copy of Heroes III installed, VCMI can import your Heroes III data using the offline installer from gog.com.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Se você não tiver uma cópia do Heroes III instalada, o VCMI pode importar seus dados do Heroes III usando o instalador offline do gog.com.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
|
<location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
|
||||||
@ -1316,17 +1316,17 @@ Por favor, selecione o diretório com Heroes III: Complete Edition ou Heroes III
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistmodel_moc.cpp" line="172"/>
|
<location filename="../modManager/cmodlistmodel_moc.cpp" line="172"/>
|
||||||
<source>Name</source>
|
<source>Name</source>
|
||||||
<translation type="unfinished">Nome</translation>
|
<translation>Nome</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistmodel_moc.cpp" line="175"/>
|
<location filename="../modManager/cmodlistmodel_moc.cpp" line="175"/>
|
||||||
<source>Type</source>
|
<source>Type</source>
|
||||||
<translation type="unfinished">Tipo</translation>
|
<translation>Tipo</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../modManager/cmodlistmodel_moc.cpp" line="176"/>
|
<location filename="../modManager/cmodlistmodel_moc.cpp" line="176"/>
|
||||||
<source>Version</source>
|
<source>Version</source>
|
||||||
<translation type="unfinished">Versão</translation>
|
<translation>Versão</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -1349,12 +1349,12 @@ Por favor, selecione o diretório com Heroes III: Complete Edition ou Heroes III
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../updatedialog_moc.cpp" line="64"/>
|
<location filename="../updatedialog_moc.cpp" line="64"/>
|
||||||
<source>Network error</source>
|
<source>Network error</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Erro de rede</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../updatedialog_moc.cpp" line="101"/>
|
<location filename="../updatedialog_moc.cpp" line="101"/>
|
||||||
<source>Cannot read JSON from url or incorrect JSON data</source>
|
<source>Cannot read JSON from url or incorrect JSON data</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Não é possível ler JSON a partir do URL ou os dados JSON estão incorretos</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
@ -107,10 +107,14 @@ void setThreadName(const std::string &name)
|
|||||||
|
|
||||||
#elif defined(VCMI_APPLE)
|
#elif defined(VCMI_APPLE)
|
||||||
pthread_setname_np(name.c_str());
|
pthread_setname_np(name.c_str());
|
||||||
|
#elif defined(VCMI_FREEBSD)
|
||||||
|
pthread_setname_np(pthread_self(), name.c_str());
|
||||||
#elif defined(VCMI_HAIKU)
|
#elif defined(VCMI_HAIKU)
|
||||||
rename_thread(find_thread(NULL), name.c_str());
|
rename_thread(find_thread(NULL), name.c_str());
|
||||||
#elif defined(VCMI_UNIX)
|
#elif defined(VCMI_UNIX)
|
||||||
prctl(PR_SET_NAME, name.c_str(), 0, 0, 0);
|
prctl(PR_SET_NAME, name.c_str(), 0, 0, 0);
|
||||||
|
#else
|
||||||
|
#error "Failed to find method to set thread name on this system. Please provide one (or disable this line if you just want code to compile)"
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -760,7 +760,7 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInf
|
|||||||
|
|
||||||
DamageEstimation ret = calculateDmgRange(bai);
|
DamageEstimation ret = calculateDmgRange(bai);
|
||||||
|
|
||||||
if(retaliationDmg)
|
if(retaliationDmg && bai.defender->ableToRetaliate())
|
||||||
{
|
{
|
||||||
if(bai.shooting)
|
if(bai.shooting)
|
||||||
{
|
{
|
||||||
@ -782,7 +782,7 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInf
|
|||||||
};
|
};
|
||||||
|
|
||||||
DamageEstimation retaliationMin = estimateRetaliation(ret.damage.min);
|
DamageEstimation retaliationMin = estimateRetaliation(ret.damage.min);
|
||||||
DamageEstimation retaliationMax = estimateRetaliation(ret.damage.min);
|
DamageEstimation retaliationMax = estimateRetaliation(ret.damage.max);
|
||||||
|
|
||||||
retaliationDmg->damage.min = std::min(retaliationMin.damage.min, retaliationMax.damage.min);
|
retaliationDmg->damage.min = std::min(retaliationMin.damage.min, retaliationMax.damage.min);
|
||||||
retaliationDmg->damage.max = std::max(retaliationMin.damage.max, retaliationMax.damage.max);
|
retaliationDmg->damage.max = std::max(retaliationMin.damage.max, retaliationMax.damage.max);
|
||||||
|
@ -66,7 +66,9 @@ void CBufferedStream::ensureSize(si64 size)
|
|||||||
{
|
{
|
||||||
si64 initialSize = buffer.size();
|
si64 initialSize = buffer.size();
|
||||||
si64 currentStep = std::min<si64>(size, buffer.size());
|
si64 currentStep = std::min<si64>(size, buffer.size());
|
||||||
vstd::amax(currentStep, 1024); // to avoid large number of calls at start
|
// to avoid large number of calls at start
|
||||||
|
// this is often used to load h3m map headers, most of which are ~300 bytes in size
|
||||||
|
vstd::amax(currentStep, 512);
|
||||||
|
|
||||||
buffer.resize(initialSize + currentStep);
|
buffer.resize(initialSize + currentStep);
|
||||||
|
|
||||||
|
@ -294,6 +294,7 @@ void CGameState::updateOnLoad(StartInfo * si)
|
|||||||
scenarioOps->playerInfos = si->playerInfos;
|
scenarioOps->playerInfos = si->playerInfos;
|
||||||
for(auto & i : si->playerInfos)
|
for(auto & i : si->playerInfos)
|
||||||
gs->players[i.first].human = i.second.isControlledByHuman();
|
gs->players[i.first].human = i.second.isControlledByHuman();
|
||||||
|
scenarioOps->extraOptionsInfo = si->extraOptionsInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
|
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
|
||||||
|
@ -392,9 +392,15 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO
|
|||||||
|
|
||||||
auto * donorHero = campaignHeroReplacement.hero;
|
auto * donorHero = campaignHeroReplacement.hero;
|
||||||
|
|
||||||
|
if (!donorHero)
|
||||||
|
throw std::runtime_error("Failed to find hero to take artifacts from! Scenario: " + gameState->map->name.toString());
|
||||||
|
|
||||||
for (auto const & artLocation : campaignHeroReplacement.transferrableArtifacts)
|
for (auto const & artLocation : campaignHeroReplacement.transferrableArtifacts)
|
||||||
{
|
{
|
||||||
auto * artifact = donorHero->getArt(artLocation);
|
auto * artifact = donorHero->getArt(artLocation);
|
||||||
|
if (!donorHero)
|
||||||
|
throw std::runtime_error("Failed to find artifacts to transfer to travelling hero! Scenario: " + gameState->map->name.toString());
|
||||||
|
|
||||||
artifact->removeFrom(*donorHero, artLocation);
|
artifact->removeFrom(*donorHero, artLocation);
|
||||||
|
|
||||||
if (receiver)
|
if (receiver)
|
||||||
|
@ -544,12 +544,12 @@ void CGTownInstance::newTurn(CRandomGenerator & rand) const
|
|||||||
|
|
||||||
//get Mana Vortex or Stables bonuses
|
//get Mana Vortex or Stables bonuses
|
||||||
//same code is in the CGameHandler::buildStructure method
|
//same code is in the CGameHandler::buildStructure method
|
||||||
|
if (garrisonHero != nullptr) //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
|
||||||
|
cb->visitCastleObjects(this, garrisonHero);
|
||||||
|
|
||||||
if (visitingHero != nullptr)
|
if (visitingHero != nullptr)
|
||||||
cb->visitCastleObjects(this, visitingHero);
|
cb->visitCastleObjects(this, visitingHero);
|
||||||
|
|
||||||
if (garrisonHero != nullptr)
|
|
||||||
cb->visitCastleObjects(this, garrisonHero);
|
|
||||||
|
|
||||||
if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
|
if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
|
||||||
{
|
{
|
||||||
std::vector<SlotID> nativeCrits; //slots
|
std::vector<SlotID> nativeCrits; //slots
|
||||||
|
@ -124,6 +124,49 @@ void CMapLoaderH3M::init()
|
|||||||
//map->banWaterContent(); //Not sure if force this for custom scenarios
|
//map->banWaterContent(); //Not sure if force this for custom scenarios
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MapIdentifiersH3M generateMapping(EMapFormat format)
|
||||||
|
{
|
||||||
|
auto features = MapFormatFeaturesH3M::find(format, 0);
|
||||||
|
MapIdentifiersH3M identifierMapper;
|
||||||
|
|
||||||
|
if(features.levelROE)
|
||||||
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
|
||||||
|
if(features.levelAB)
|
||||||
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
|
||||||
|
if(features.levelSOD)
|
||||||
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
|
||||||
|
if(features.levelWOG)
|
||||||
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
|
||||||
|
if(features.levelHOTA0)
|
||||||
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
|
||||||
|
|
||||||
|
return identifierMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::map<EMapFormat, MapIdentifiersH3M> generateMappings()
|
||||||
|
{
|
||||||
|
std::map<EMapFormat, MapIdentifiersH3M> result;
|
||||||
|
auto addMapping = [&result](EMapFormat format)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result[format] = generateMapping(format);
|
||||||
|
}
|
||||||
|
catch(const std::runtime_error &)
|
||||||
|
{
|
||||||
|
// unsupported map format - skip
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addMapping(EMapFormat::ROE);
|
||||||
|
addMapping(EMapFormat::AB);
|
||||||
|
addMapping(EMapFormat::SOD);
|
||||||
|
addMapping(EMapFormat::HOTA);
|
||||||
|
addMapping(EMapFormat::WOG);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void CMapLoaderH3M::readHeader()
|
void CMapLoaderH3M::readHeader()
|
||||||
{
|
{
|
||||||
// Map version
|
// Map version
|
||||||
@ -159,18 +202,10 @@ void CMapLoaderH3M::readHeader()
|
|||||||
features = MapFormatFeaturesH3M::find(mapHeader->version, 0);
|
features = MapFormatFeaturesH3M::find(mapHeader->version, 0);
|
||||||
reader->setFormatLevel(features);
|
reader->setFormatLevel(features);
|
||||||
}
|
}
|
||||||
MapIdentifiersH3M identifierMapper;
|
|
||||||
|
|
||||||
if (features.levelROE)
|
// optimization - load mappings only once to avoid slow parsing of map headers for map list
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
|
static const std::map<EMapFormat, MapIdentifiersH3M> identifierMappers = generateMappings();
|
||||||
if (features.levelAB)
|
const MapIdentifiersH3M & identifierMapper = identifierMappers.at(mapHeader->version);
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
|
|
||||||
if (features.levelSOD)
|
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
|
|
||||||
if (features.levelWOG)
|
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
|
|
||||||
if (features.levelHOTA0)
|
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
|
|
||||||
|
|
||||||
reader->setIdentifierRemapper(identifierMapper);
|
reader->setIdentifierRemapper(identifierMapper);
|
||||||
|
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket)
|
NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context)
|
||||||
: socket(socket)
|
: socket(socket)
|
||||||
|
, timer(std::make_shared<NetworkTimer>(*context))
|
||||||
, listener(listener)
|
, listener(listener)
|
||||||
{
|
{
|
||||||
socket->set_option(boost::asio::ip::tcp::no_delay(true));
|
socket->set_option(boost::asio::ip::tcp::no_delay(true));
|
||||||
socket->set_option(boost::asio::socket_base::keep_alive(true));
|
|
||||||
|
|
||||||
// iOS throws exception on attempt to set buffer size
|
// iOS throws exception on attempt to set buffer size
|
||||||
constexpr auto bufferSize = 4 * 1024 * 1024;
|
constexpr auto bufferSize = 4 * 1024 * 1024;
|
||||||
@ -42,6 +42,12 @@ NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NetworkConnection::start()
|
void NetworkConnection::start()
|
||||||
|
{
|
||||||
|
heartbeat();
|
||||||
|
startReceiving();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::startReceiving()
|
||||||
{
|
{
|
||||||
boost::asio::async_read(*socket,
|
boost::asio::async_read(*socket,
|
||||||
readBuffer,
|
readBuffer,
|
||||||
@ -49,11 +55,30 @@ void NetworkConnection::start()
|
|||||||
[self = shared_from_this()](const auto & ec, const auto & endpoint) { self->onHeaderReceived(ec); });
|
[self = shared_from_this()](const auto & ec, const auto & endpoint) { self->onHeaderReceived(ec); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::heartbeat()
|
||||||
|
{
|
||||||
|
constexpr auto heartbeatInterval = std::chrono::seconds(10);
|
||||||
|
|
||||||
|
timer->expires_after(heartbeatInterval);
|
||||||
|
timer->async_wait( [self = weak_from_this()](const auto & ec)
|
||||||
|
{
|
||||||
|
if (ec)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto locked = self.lock();
|
||||||
|
if (!locked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
locked->sendPacket({});
|
||||||
|
locked->heartbeat();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHeader)
|
void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHeader)
|
||||||
{
|
{
|
||||||
if (ecHeader)
|
if (ecHeader)
|
||||||
{
|
{
|
||||||
listener.onDisconnected(shared_from_this(), ecHeader.message());
|
onError(ecHeader.message());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,14 +90,14 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHea
|
|||||||
|
|
||||||
if (messageSize > messageMaxSize)
|
if (messageSize > messageMaxSize)
|
||||||
{
|
{
|
||||||
listener.onDisconnected(shared_from_this(), "Invalid packet size!");
|
onError("Invalid packet size!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageSize == 0)
|
if (messageSize == 0)
|
||||||
{
|
{
|
||||||
// Zero-sized packet. Strange, but safe to ignore. Start reading next packet
|
//heartbeat package with no payload - wait for next packet
|
||||||
start();
|
startReceiving();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +111,7 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u
|
|||||||
{
|
{
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
listener.onDisconnected(shared_from_this(), ec.message());
|
onError(ec.message());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,29 +124,80 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u
|
|||||||
readBuffer.sgetn(reinterpret_cast<char *>(message.data()), expectedPacketSize);
|
readBuffer.sgetn(reinterpret_cast<char *>(message.data()), expectedPacketSize);
|
||||||
listener.onPacketReceived(shared_from_this(), message);
|
listener.onPacketReceived(shared_from_this(), message);
|
||||||
|
|
||||||
start();
|
startReceiving();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::setAsyncWritesEnabled(bool on)
|
||||||
|
{
|
||||||
|
asyncWritesEnabled = on;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkConnection::sendPacket(const std::vector<std::byte> & message)
|
void NetworkConnection::sendPacket(const std::vector<std::byte> & message)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(writeMutex);
|
std::lock_guard<std::mutex> lock(writeMutex);
|
||||||
|
std::vector<std::byte> headerVector(sizeof(uint32_t));
|
||||||
|
uint32_t messageSize = message.size();
|
||||||
|
std::memcpy(headerVector.data(), &messageSize, sizeof(uint32_t));
|
||||||
|
|
||||||
boost::system::error_code ec;
|
// At the moment, vcmilobby *requires* async writes in order to handle multiple connections with different speeds and at optimal performance
|
||||||
|
// However server (and potentially - client) can not handle this mode and may shutdown either socket or entire asio service too early, before all writes are performed
|
||||||
|
if (asyncWritesEnabled)
|
||||||
|
{
|
||||||
|
|
||||||
// create array with single element - boost::asio::buffer can be constructed from containers, but not from plain integer
|
bool messageQueueEmpty = dataToSend.empty();
|
||||||
std::array<uint32_t, 1> messageSize{static_cast<uint32_t>(message.size())};
|
dataToSend.push_back(headerVector);
|
||||||
|
if (message.size() > 0)
|
||||||
|
dataToSend.push_back(message);
|
||||||
|
|
||||||
boost::asio::write(*socket, boost::asio::buffer(messageSize), ec );
|
if (messageQueueEmpty)
|
||||||
if (message.size() > 0)
|
doSendData();
|
||||||
boost::asio::write(*socket, boost::asio::buffer(message), ec );
|
//else - data sending loop is still active and still sending previous messages
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::asio::write(*socket, boost::asio::buffer(headerVector), ec );
|
||||||
|
if (message.size() > 0)
|
||||||
|
boost::asio::write(*socket, boost::asio::buffer(message), ec );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Note: ignoring error code, intended
|
void NetworkConnection::doSendData()
|
||||||
|
{
|
||||||
|
if (dataToSend.empty())
|
||||||
|
throw std::runtime_error("Attempting to sent data but there is no data to send!");
|
||||||
|
|
||||||
|
boost::asio::async_write(*socket, boost::asio::buffer(dataToSend.front()), [self = shared_from_this()](const auto & error, const auto & )
|
||||||
|
{
|
||||||
|
self->onDataSent(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::onDataSent(const boost::system::error_code & ec)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(writeMutex);
|
||||||
|
dataToSend.pop_front();
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
onError(ec.message());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataToSend.empty())
|
||||||
|
doSendData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::onError(const std::string & message)
|
||||||
|
{
|
||||||
|
listener.onDisconnected(shared_from_this(), message);
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkConnection::close()
|
void NetworkConnection::close()
|
||||||
{
|
{
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
socket->close(ec);
|
socket->close(ec);
|
||||||
|
timer->cancel(ec);
|
||||||
|
|
||||||
//NOTE: ignoring error code, intended
|
//NOTE: ignoring error code, intended
|
||||||
}
|
}
|
||||||
|
@ -13,26 +13,37 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
class NetworkConnection : public INetworkConnection, public std::enable_shared_from_this<NetworkConnection>
|
class NetworkConnection final : public INetworkConnection, public std::enable_shared_from_this<NetworkConnection>
|
||||||
{
|
{
|
||||||
static const int messageHeaderSize = sizeof(uint32_t);
|
static const int messageHeaderSize = sizeof(uint32_t);
|
||||||
static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input
|
static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input
|
||||||
|
|
||||||
|
std::list<std::vector<std::byte>> dataToSend;
|
||||||
std::shared_ptr<NetworkSocket> socket;
|
std::shared_ptr<NetworkSocket> socket;
|
||||||
|
std::shared_ptr<NetworkTimer> timer;
|
||||||
std::mutex writeMutex;
|
std::mutex writeMutex;
|
||||||
|
|
||||||
NetworkBuffer readBuffer;
|
NetworkBuffer readBuffer;
|
||||||
INetworkConnectionListener & listener;
|
INetworkConnectionListener & listener;
|
||||||
|
bool asyncWritesEnabled = false;
|
||||||
|
|
||||||
|
void heartbeat();
|
||||||
|
void onError(const std::string & message);
|
||||||
|
|
||||||
|
void startReceiving();
|
||||||
void onHeaderReceived(const boost::system::error_code & ec);
|
void onHeaderReceived(const boost::system::error_code & ec);
|
||||||
void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize);
|
void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize);
|
||||||
|
|
||||||
|
void doSendData();
|
||||||
|
void onDataSent(const boost::system::error_code & ec);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket);
|
NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context);
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void close() override;
|
void close() override;
|
||||||
void sendPacket(const std::vector<std::byte> & message) override;
|
void sendPacket(const std::vector<std::byte> & message) override;
|
||||||
|
void setAsyncWritesEnabled(bool on) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@ -35,7 +35,7 @@ void NetworkHandler::connectToRemote(INetworkClientListener & listener, const st
|
|||||||
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(*io);
|
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(*io);
|
||||||
|
|
||||||
resolver->async_resolve(host, std::to_string(port),
|
resolver->async_resolve(host, std::to_string(port),
|
||||||
[&listener, resolver, socket](const boost::system::error_code& error, const boost::asio::ip::tcp::resolver::results_type & endpoints)
|
[this, &listener, resolver, socket](const boost::system::error_code& error, const boost::asio::ip::tcp::resolver::results_type & endpoints)
|
||||||
{
|
{
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
@ -43,14 +43,14 @@ void NetworkHandler::connectToRemote(INetworkClientListener & listener, const st
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::async_connect(*socket, endpoints, [socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
|
boost::asio::async_connect(*socket, endpoints, [this, socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
|
||||||
{
|
{
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
listener.onConnectionFailed(error.message());
|
listener.onConnectionFailed(error.message());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto connection = std::make_shared<NetworkConnection>(listener, socket);
|
auto connection = std::make_shared<NetworkConnection>(listener, socket, io);
|
||||||
connection->start();
|
connection->start();
|
||||||
|
|
||||||
listener.onConnectionEstablished(connection);
|
listener.onConnectionEstablished(connection);
|
||||||
|
@ -17,6 +17,7 @@ class DLL_LINKAGE INetworkConnection : boost::noncopyable
|
|||||||
public:
|
public:
|
||||||
virtual ~INetworkConnection() = default;
|
virtual ~INetworkConnection() = default;
|
||||||
virtual void sendPacket(const std::vector<std::byte> & message) = 0;
|
virtual void sendPacket(const std::vector<std::byte> & message) = 0;
|
||||||
|
virtual void setAsyncWritesEnabled(bool on) = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
logNetwork->info("We got a new connection! :)");
|
logNetwork->info("We got a new connection! :)");
|
||||||
auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection);
|
auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection, io);
|
||||||
connections.insert(connection);
|
connections.insert(connection);
|
||||||
connection->start();
|
connection->start();
|
||||||
listener.onNewConnection(connection);
|
listener.onNewConnection(connection);
|
||||||
@ -49,9 +49,11 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
|
|||||||
void NetworkServer::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
|
void NetworkServer::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
|
||||||
{
|
{
|
||||||
logNetwork->info("Connection lost! Reason: %s", errorMessage);
|
logNetwork->info("Connection lost! Reason: %s", errorMessage);
|
||||||
assert(connections.count(connection));
|
if (connections.count(connection))
|
||||||
connections.erase(connection);
|
{
|
||||||
listener.onDisconnected(connection, errorMessage);
|
connections.erase(connection);
|
||||||
|
listener.onDisconnected(connection, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkServer::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
|
void NetworkServer::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
|
||||||
|
@ -254,6 +254,11 @@ bool Area::overlap(const Area & area) const
|
|||||||
return overlap(area.getTilesVector());
|
return overlap(area.getTilesVector());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Area::distance(const int3 & tile) const
|
||||||
|
{
|
||||||
|
return nearest(tile).dist2d(tile);
|
||||||
|
}
|
||||||
|
|
||||||
int Area::distanceSqr(const int3 & tile) const
|
int Area::distanceSqr(const int3 & tile) const
|
||||||
{
|
{
|
||||||
return nearest(tile).dist2dSQ(tile);
|
return nearest(tile).dist2dSQ(tile);
|
||||||
|
@ -51,6 +51,7 @@ namespace rmg
|
|||||||
bool contains(const Area & area) const;
|
bool contains(const Area & area) const;
|
||||||
bool overlap(const Area & area) const;
|
bool overlap(const Area & area) const;
|
||||||
bool overlap(const std::vector<int3> & tiles) const;
|
bool overlap(const std::vector<int3> & tiles) const;
|
||||||
|
int distance(const int3 & tile) const;
|
||||||
int distanceSqr(const int3 & tile) const;
|
int distanceSqr(const int3 & tile) const;
|
||||||
int distanceSqr(const Area & area) const;
|
int distanceSqr(const Area & area) const;
|
||||||
int3 nearest(const int3 & tile) const;
|
int3 nearest(const int3 & tile) const;
|
||||||
|
@ -64,16 +64,16 @@ const rmg::Area & RoadPlacer::getRoads() const
|
|||||||
return roads;
|
return roads;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RoadPlacer::createRoad(const int3 & dst)
|
bool RoadPlacer::createRoad(const int3 & destination)
|
||||||
{
|
{
|
||||||
auto searchArea = zone.areaPossible() + zone.freePaths() + areaRoads + roads;
|
auto searchArea = zone.areaPossible() + zone.freePaths() + areaRoads + roads;
|
||||||
|
|
||||||
|
rmg::Area border(zone.area()->getBorder());
|
||||||
|
|
||||||
rmg::Path path(searchArea);
|
rmg::Path path(searchArea);
|
||||||
path.connect(roads);
|
path.connect(roads);
|
||||||
|
|
||||||
const float VISITABLE_PENALTY = 1.33f;
|
auto simpleRoutig = [this, &border](const int3& src, const int3& dst)
|
||||||
|
|
||||||
auto simpleRoutig = [this, VISITABLE_PENALTY](const int3& src, const int3& dst)
|
|
||||||
{
|
{
|
||||||
if(areaIsolated().contains(dst))
|
if(areaIsolated().contains(dst))
|
||||||
{
|
{
|
||||||
@ -81,52 +81,28 @@ bool RoadPlacer::createRoad(const int3 & dst)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float weight = dst.dist2dSQ(src);
|
float ret = dst.dist2d(src);
|
||||||
auto ret = weight * weight;
|
|
||||||
|
|
||||||
if (visitableTiles.contains(src) || visitableTiles.contains(dst))
|
if (visitableTiles.contains(src) || visitableTiles.contains(dst))
|
||||||
{
|
{
|
||||||
ret *= VISITABLE_PENALTY;
|
ret *= VISITABLE_PENALTY;
|
||||||
}
|
}
|
||||||
|
float dist = border.distance(dst);
|
||||||
|
if(dist > 1)
|
||||||
|
{
|
||||||
|
ret /= dist;
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto res = path.search(dst, true, simpleRoutig);
|
auto res = path.search(destination, true, simpleRoutig);
|
||||||
if(!res.valid())
|
if(!res.valid())
|
||||||
{
|
{
|
||||||
auto desperateRoutig = [this, VISITABLE_PENALTY](const int3& src, const int3& dst) -> float
|
res = createRoadDesperate(path, destination);
|
||||||
|
if (!res.valid())
|
||||||
{
|
{
|
||||||
//Do not allow connections straight up through object not visitable from top
|
logGlobal->warn("Failed to create road to node %s", destination.toString());
|
||||||
if(std::abs((src - dst).y) == 1)
|
|
||||||
{
|
|
||||||
if(areaIsolated().contains(dst) || areaIsolated().contains(src))
|
|
||||||
{
|
|
||||||
return 1e12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(areaIsolated().contains(dst))
|
|
||||||
{
|
|
||||||
return 1e6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float weight = dst.dist2dSQ(src);
|
|
||||||
|
|
||||||
auto ret = weight * weight;
|
|
||||||
if (visitableTiles.contains(src) || visitableTiles.contains(dst))
|
|
||||||
{
|
|
||||||
ret *= VISITABLE_PENALTY;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
res = path.search(dst, false, desperateRoutig);
|
|
||||||
|
|
||||||
if(!res.valid())
|
|
||||||
{
|
|
||||||
logGlobal->warn("Failed to create road to node %s", dst.toString());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,6 +111,38 @@ bool RoadPlacer::createRoad(const int3 & dst)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rmg::Path RoadPlacer::createRoadDesperate(rmg::Path & path, const int3 & destination)
|
||||||
|
{
|
||||||
|
auto desperateRoutig = [this](const int3& src, const int3& dst) -> float
|
||||||
|
{
|
||||||
|
//Do not allow connections straight up through object not visitable from top
|
||||||
|
if(std::abs((src - dst).y) == 1)
|
||||||
|
{
|
||||||
|
if(areaIsolated().contains(dst) || areaIsolated().contains(src))
|
||||||
|
{
|
||||||
|
return 1e12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(areaIsolated().contains(dst))
|
||||||
|
{
|
||||||
|
return 1e6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float weight = dst.dist2dSQ(src);
|
||||||
|
auto ret = weight * weight; // Still prefer straight paths
|
||||||
|
|
||||||
|
if (visitableTiles.contains(src) || visitableTiles.contains(dst))
|
||||||
|
{
|
||||||
|
ret *= VISITABLE_PENALTY;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
return path.search(destination, false, desperateRoutig);
|
||||||
|
}
|
||||||
|
|
||||||
void RoadPlacer::drawRoads(bool secondary)
|
void RoadPlacer::drawRoads(bool secondary)
|
||||||
{
|
{
|
||||||
//Do not draw roads on underground rock or water
|
//Do not draw roads on underground rock or water
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
const float VISITABLE_PENALTY = 1.33f;
|
||||||
|
|
||||||
class RoadPlacer: public Modificator
|
class RoadPlacer: public Modificator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -34,6 +36,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool createRoad(const int3 & dst);
|
bool createRoad(const int3 & dst);
|
||||||
|
rmg::Path createRoadDesperate(rmg::Path & path, const int3 & destination);
|
||||||
void drawRoads(bool secondary = false); //actually updates tiles
|
void drawRoads(bool secondary = false); //actually updates tiles
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -272,6 +272,13 @@ void LobbyDatabase::prepareStatements()
|
|||||||
ORDER BY secondsElapsed ASC
|
ORDER BY secondsElapsed ASC
|
||||||
)");
|
)");
|
||||||
|
|
||||||
|
getGameRoomInvitesStatement = database->prepare(R"(
|
||||||
|
SELECT a.accountID, a.displayName
|
||||||
|
FROM gameRoomInvites gri
|
||||||
|
LEFT JOIN accounts a ON a.accountID = gri.accountID
|
||||||
|
WHERE roomID = ?
|
||||||
|
)");
|
||||||
|
|
||||||
getGameRoomPlayersStatement = database->prepare(R"(
|
getGameRoomPlayersStatement = database->prepare(R"(
|
||||||
SELECT a.accountID, a.displayName
|
SELECT a.accountID, a.displayName
|
||||||
FROM gameRoomPlayers grp
|
FROM gameRoomPlayers grp
|
||||||
@ -581,6 +588,19 @@ std::vector<LobbyGameRoom> LobbyDatabase::getActiveGameRooms()
|
|||||||
}
|
}
|
||||||
getGameRoomPlayersStatement->reset();
|
getGameRoomPlayersStatement->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto & room : result)
|
||||||
|
{
|
||||||
|
getGameRoomInvitesStatement->setBinds(room.roomID);
|
||||||
|
while(getGameRoomInvitesStatement->execute())
|
||||||
|
{
|
||||||
|
LobbyAccount account;
|
||||||
|
getGameRoomInvitesStatement->getColumns(account.accountID, account.displayName);
|
||||||
|
room.invited.push_back(account);
|
||||||
|
}
|
||||||
|
getGameRoomInvitesStatement->reset();
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ class LobbyDatabase
|
|||||||
SQLiteStatementPtr getAccountGameRoomStatement;
|
SQLiteStatementPtr getAccountGameRoomStatement;
|
||||||
SQLiteStatementPtr getAccountDisplayNameStatement;
|
SQLiteStatementPtr getAccountDisplayNameStatement;
|
||||||
SQLiteStatementPtr getGameRoomPlayersStatement;
|
SQLiteStatementPtr getGameRoomPlayersStatement;
|
||||||
|
SQLiteStatementPtr getGameRoomInvitesStatement;
|
||||||
SQLiteStatementPtr countRoomUsedSlotsStatement;
|
SQLiteStatementPtr countRoomUsedSlotsStatement;
|
||||||
SQLiteStatementPtr countRoomTotalSlotsStatement;
|
SQLiteStatementPtr countRoomTotalSlotsStatement;
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ struct LobbyGameRoom
|
|||||||
std::string version;
|
std::string version;
|
||||||
std::string modsJson;
|
std::string modsJson;
|
||||||
std::vector<LobbyAccount> participants;
|
std::vector<LobbyAccount> participants;
|
||||||
|
std::vector<LobbyAccount> invited;
|
||||||
LobbyRoomState roomState;
|
LobbyRoomState roomState;
|
||||||
uint32_t playerLimit;
|
uint32_t playerLimit;
|
||||||
std::chrono::seconds age;
|
std::chrono::seconds age;
|
||||||
|
@ -212,11 +212,15 @@ static JsonNode loadLobbyGameRoomToJson(const LobbyGameRoom & gameRoom)
|
|||||||
jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
|
jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
|
||||||
jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
|
jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
|
||||||
jsonEntry["ageSeconds"].Integer() = gameRoom.age.count();
|
jsonEntry["ageSeconds"].Integer() = gameRoom.age.count();
|
||||||
jsonEntry["mods"] = JsonNode(reinterpret_cast<const std::byte *>(gameRoom.modsJson.data()), gameRoom.modsJson.size());
|
if (!gameRoom.modsJson.empty()) // not present in match history
|
||||||
|
jsonEntry["mods"] = JsonNode(reinterpret_cast<const std::byte *>(gameRoom.modsJson.data()), gameRoom.modsJson.size());
|
||||||
|
|
||||||
for(const auto & account : gameRoom.participants)
|
for(const auto & account : gameRoom.participants)
|
||||||
jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account));
|
jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account));
|
||||||
|
|
||||||
|
for(const auto & account : gameRoom.invited)
|
||||||
|
jsonEntry["invited"].Vector().push_back(loadLobbyAccountToJson(account));
|
||||||
|
|
||||||
return jsonEntry;
|
return jsonEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +292,7 @@ void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std
|
|||||||
|
|
||||||
void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
|
void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
|
||||||
{
|
{
|
||||||
|
connection->setAsyncWritesEnabled(true);
|
||||||
// no-op - waiting for incoming data
|
// no-op - waiting for incoming data
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -815,6 +820,7 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con
|
|||||||
|
|
||||||
database->insertGameRoomInvite(accountID, gameRoomID);
|
database->insertGameRoomInvite(accountID, gameRoomID);
|
||||||
sendInviteReceived(targetAccountConnection, senderName, gameRoomID);
|
sendInviteReceived(targetAccountConnection, senderName, gameRoomID);
|
||||||
|
broadcastActiveGameRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
LobbyServer::~LobbyServer() = default;
|
LobbyServer::~LobbyServer() = default;
|
||||||
|
@ -40,6 +40,9 @@ ArmyWidget::ArmyWidget(CArmedInstance & a, QWidget *parent) :
|
|||||||
uiSlots[i]->insertItem(c + 1, creature->getNamePluralTranslated().c_str());
|
uiSlots[i]->insertItem(c + 1, creature->getNamePluralTranslated().c_str());
|
||||||
uiSlots[i]->setItemData(c + 1, creature->getIndex());
|
uiSlots[i]->setItemData(c + 1, creature->getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uiSlots[i]->completer()->setCompletionMode(QCompleter::PopupCompletion);
|
||||||
|
uiSlots[i]->completer()->setFilterMode(Qt::MatchContains);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->formationTight->setChecked(true);
|
ui->formationTight->setChecked(true);
|
||||||
|
@ -34,6 +34,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
@ -44,6 +47,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
@ -54,6 +60,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
@ -64,6 +73,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
@ -74,6 +86,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
@ -84,6 +99,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
@ -94,6 +112,9 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0">
|
<item row="8" column="0">
|
||||||
|
@ -317,7 +317,7 @@ void Inspector::updateProperties(CGHeroInstance * o)
|
|||||||
addProperty("Skills", PropertyEditorPlaceholder(), delegate, false);
|
addProperty("Skills", PropertyEditorPlaceholder(), delegate, false);
|
||||||
addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false);
|
addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false);
|
||||||
|
|
||||||
if(o->type)
|
if(o->type || o->ID == Obj::PRISON)
|
||||||
{ //Hero type
|
{ //Hero type
|
||||||
auto * delegate = new InspectorDelegate;
|
auto * delegate = new InspectorDelegate;
|
||||||
for(int i = 0; i < VLC->heroh->objects.size(); ++i)
|
for(int i = 0; i < VLC->heroh->objects.size(); ++i)
|
||||||
@ -330,7 +330,7 @@ void Inspector::updateProperties(CGHeroInstance * o)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addProperty("Hero type", o->type->getNameTranslated(), delegate, false);
|
addProperty("Hero type", o->type ? o->type->getNameTranslated() : "", delegate, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) :
|
|||||||
|
|
||||||
//fill core elements
|
//fill core elements
|
||||||
for(const auto & s : Rewardable::VisitModeString)
|
for(const auto & s : Rewardable::VisitModeString)
|
||||||
ui->selectMode->addItem(QString::fromUtf8(s.data(), s.size()));
|
ui->visitMode->addItem(QString::fromUtf8(s.data(), s.size()));
|
||||||
|
|
||||||
for(const auto & s : Rewardable::SelectModeString)
|
for(const auto & s : Rewardable::SelectModeString)
|
||||||
ui->selectMode->addItem(QString::fromUtf8(s.data(), s.size()));
|
ui->selectMode->addItem(QString::fromUtf8(s.data(), s.size()));
|
||||||
@ -636,10 +636,12 @@ void RewardsWidget::on_visitInfoList_itemSelectionChanged()
|
|||||||
if(ui->visitInfoList->currentItem() == nullptr)
|
if(ui->visitInfoList->currentItem() == nullptr)
|
||||||
{
|
{
|
||||||
ui->eventInfoGroup->hide();
|
ui->eventInfoGroup->hide();
|
||||||
|
ui->removeVisitInfo->setEnabled(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->eventInfoGroup->show();
|
ui->eventInfoGroup->show();
|
||||||
|
ui->removeVisitInfo->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RewardsWidget::on_visitInfoList_currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous)
|
void RewardsWidget::on_visitInfoList_currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous)
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="removeVisitInfo">
|
<widget class="QPushButton" name="removeVisitInfo">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove</string>
|
<string>Remove</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
|
|
||||||
int main(int argc, char * argv[])
|
int main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
|
#endif
|
||||||
|
|
||||||
QApplication vcmieditor(argc, argv);
|
QApplication vcmieditor(argc, argv);
|
||||||
MainWindow mainWindow;
|
MainWindow mainWindow;
|
||||||
return vcmieditor.exec();
|
return vcmieditor.exec();
|
||||||
|
@ -90,6 +90,9 @@ void MainWindow::loadUserSettings()
|
|||||||
{
|
{
|
||||||
move(position);
|
move(position);
|
||||||
}
|
}
|
||||||
|
lastSavingDir = s.value(lastDirectorySetting).toString();
|
||||||
|
if(lastSavingDir.isEmpty())
|
||||||
|
lastSavingDir = QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::saveUserSettings()
|
void MainWindow::saveUserSettings()
|
||||||
@ -97,6 +100,7 @@ void MainWindow::saveUserSettings()
|
|||||||
QSettings s(Ui::teamName, Ui::appName);
|
QSettings s(Ui::teamName, Ui::appName);
|
||||||
s.setValue(mainWindowSizeSetting, size());
|
s.setValue(mainWindowSizeSetting, size());
|
||||||
s.setValue(mainWindowPositionSetting, pos());
|
s.setValue(mainWindowPositionSetting, pos());
|
||||||
|
s.setValue(lastDirectorySetting, lastSavingDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::parseCommandLine(ExtractionOptions & extractionOptions)
|
void MainWindow::parseCommandLine(ExtractionOptions & extractionOptions)
|
||||||
@ -382,7 +386,7 @@ void MainWindow::on_actionOpen_triggered()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
auto filenameSelect = QFileDialog::getOpenFileName(this, tr("Open map"),
|
auto filenameSelect = QFileDialog::getOpenFileName(this, tr("Open map"),
|
||||||
QString::fromStdString(VCMIDirs::get().userCachePath().make_preferred().string()),
|
QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()),
|
||||||
tr("All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m)"));
|
tr("All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m)"));
|
||||||
if(filenameSelect.isEmpty())
|
if(filenameSelect.isEmpty())
|
||||||
return;
|
return;
|
||||||
@ -439,11 +443,13 @@ void MainWindow::on_actionSave_as_triggered()
|
|||||||
if(filenameSelect.isNull())
|
if(filenameSelect.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(filenameSelect == filename)
|
QFileInfo fileInfo(filenameSelect);
|
||||||
return;
|
lastSavingDir = fileInfo.dir().path();
|
||||||
|
|
||||||
|
if(fileInfo.suffix().toLower() != "vmap")
|
||||||
|
filenameSelect += ".vmap";
|
||||||
|
|
||||||
filename = filenameSelect;
|
filename = filenameSelect;
|
||||||
lastSavingDir = filenameSelect.remove(QUrl(filenameSelect).fileName());
|
|
||||||
|
|
||||||
saveMap();
|
saveMap();
|
||||||
}
|
}
|
||||||
@ -1171,7 +1177,7 @@ void MainWindow::on_actionTranslations_triggered()
|
|||||||
void MainWindow::on_actionh3m_converter_triggered()
|
void MainWindow::on_actionh3m_converter_triggered()
|
||||||
{
|
{
|
||||||
auto mapFiles = QFileDialog::getOpenFileNames(this, tr("Select maps to convert"),
|
auto mapFiles = QFileDialog::getOpenFileNames(this, tr("Select maps to convert"),
|
||||||
QString::fromStdString(VCMIDirs::get().userCachePath().make_preferred().string()),
|
QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()),
|
||||||
tr("HoMM3 maps(*.h3m)"));
|
tr("HoMM3 maps(*.h3m)"));
|
||||||
if(mapFiles.empty())
|
if(mapFiles.empty())
|
||||||
return;
|
return;
|
||||||
|
@ -27,6 +27,7 @@ class MainWindow : public QMainWindow
|
|||||||
|
|
||||||
const QString mainWindowSizeSetting = "MainWindow/Size";
|
const QString mainWindowSizeSetting = "MainWindow/Size";
|
||||||
const QString mainWindowPositionSetting = "MainWindow/Position";
|
const QString mainWindowPositionSetting = "MainWindow/Position";
|
||||||
|
const QString lastDirectorySetting = "MainWindow/Directory";
|
||||||
|
|
||||||
#ifdef ENABLE_QT_TRANSLATIONS
|
#ifdef ENABLE_QT_TRANSLATIONS
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
|
@ -1797,7 +1797,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../windownewmap.cpp" line="296"/>
|
<location filename="../windownewmap.cpp" line="296"/>
|
||||||
<source>RMG failure</source>
|
<source>RMG failure</source>
|
||||||
<translation>Falha do RMG</translation>
|
<translation>Falha do GMA</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
@ -900,7 +900,7 @@ void CGameHandler::onNewTurn()
|
|||||||
{
|
{
|
||||||
if (getPlayerStatus(player.first) == EPlayerStatus::INGAME &&
|
if (getPlayerStatus(player.first) == EPlayerStatus::INGAME &&
|
||||||
getPlayerRelations(player.first, t->tempOwner) == PlayerRelations::ENEMIES)
|
getPlayerRelations(player.first, t->tempOwner) == PlayerRelations::ENEMIES)
|
||||||
changeFogOfWar(t->visitablePos(), t->getFirstBonus(Selector::type()(BonusType::DARKNESS))->val, player.first, ETileVisibility::HIDDEN);
|
changeFogOfWar(t->getSightCenter(), t->getFirstBonus(Selector::type()(BonusType::DARKNESS))->val, player.first, ETileVisibility::HIDDEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1550,7 +1550,7 @@ void CGameHandler::giveHero(ObjectInstanceID id, PlayerColor player, ObjectInsta
|
|||||||
|
|
||||||
//Reveal fow around new hero, especially released from Prison
|
//Reveal fow around new hero, especially released from Prison
|
||||||
auto h = getHero(id);
|
auto h = getHero(id);
|
||||||
changeFogOfWar(h->pos, h->getSightRadius(), player, ETileVisibility::REVEALED);
|
changeFogOfWar(h->getSightCenter(), h->getSightRadius(), player, ETileVisibility::REVEALED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameHandler::changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)
|
void CGameHandler::changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)
|
||||||
@ -2434,10 +2434,10 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
|
|||||||
// now when everything is built - reveal tiles for lookout tower
|
// now when everything is built - reveal tiles for lookout tower
|
||||||
changeFogOfWar(t->getSightCenter(), t->getSightRadius(), t->getOwner(), ETileVisibility::REVEALED);
|
changeFogOfWar(t->getSightCenter(), t->getSightRadius(), t->getOwner(), ETileVisibility::REVEALED);
|
||||||
|
|
||||||
|
if(t->garrisonHero) //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
|
||||||
|
visitCastleObjects(t, t->garrisonHero);
|
||||||
if(t->visitingHero)
|
if(t->visitingHero)
|
||||||
visitCastleObjects(t, t->visitingHero);
|
visitCastleObjects(t, t->visitingHero);
|
||||||
if(t->garrisonHero)
|
|
||||||
visitCastleObjects(t, t->garrisonHero);
|
|
||||||
|
|
||||||
checkVictoryLossConditionsForPlayer(t->tempOwner);
|
checkVictoryLossConditionsForPlayer(t->tempOwner);
|
||||||
return true;
|
return true;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "processors/PlayerMessageProcessor.h"
|
#include "processors/PlayerMessageProcessor.h"
|
||||||
|
|
||||||
#include "../lib/CHeroHandler.h"
|
#include "../lib/CHeroHandler.h"
|
||||||
|
#include "../lib/CPlayerState.h"
|
||||||
#include "../lib/MetaString.h"
|
#include "../lib/MetaString.h"
|
||||||
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
|
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
|
||||||
#include "../lib/serializer/CMemorySerializer.h"
|
#include "../lib/serializer/CMemorySerializer.h"
|
||||||
@ -332,6 +333,8 @@ void CVCMIServer::startGameImmediately()
|
|||||||
setState(EServerState::GAMEPLAY);
|
setState(EServerState::GAMEPLAY);
|
||||||
lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now();
|
lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now();
|
||||||
onTimer();
|
onTimer();
|
||||||
|
|
||||||
|
multiplayerWelcomeMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
|
void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
|
||||||
@ -979,6 +982,38 @@ ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CVCMIServer::multiplayerWelcomeMessage()
|
||||||
|
{
|
||||||
|
int humanPlayer = 0;
|
||||||
|
for (auto & pi : si->playerInfos)
|
||||||
|
if(gh->getPlayerState(pi.first)->isHuman())
|
||||||
|
humanPlayer++;
|
||||||
|
|
||||||
|
if(humanPlayer < 2) // Singleplayer
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<std::string> optionIds;
|
||||||
|
if(si->extraOptionsInfo.cheatsAllowed)
|
||||||
|
optionIds.push_back("vcmi.optionsTab.cheatAllowed.hover");
|
||||||
|
if(si->extraOptionsInfo.unlimitedReplay)
|
||||||
|
optionIds.push_back("vcmi.optionsTab.unlimitedReplay.hover");
|
||||||
|
|
||||||
|
if(!optionIds.size()) // No settings to publish
|
||||||
|
return;
|
||||||
|
|
||||||
|
MetaString str;
|
||||||
|
str.appendTextID("vcmi.optionsTab.extraOptions.hover");
|
||||||
|
str.appendRawString(": ");
|
||||||
|
for(int i = 0; i < optionIds.size(); i++)
|
||||||
|
{
|
||||||
|
str.appendTextID(optionIds[i]);
|
||||||
|
if(i < optionIds.size() - 1)
|
||||||
|
str.appendRawString(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
gh->playerMessages->broadcastSystemMessage(str);
|
||||||
|
}
|
||||||
|
|
||||||
INetworkHandler & CVCMIServer::getNetworkHandler()
|
INetworkHandler & CVCMIServer::getNetworkHandler()
|
||||||
{
|
{
|
||||||
return *networkHandler;
|
return *networkHandler;
|
||||||
|
@ -130,4 +130,6 @@ public:
|
|||||||
void setCampaignBonus(int bonusId);
|
void setCampaignBonus(int bonusId);
|
||||||
|
|
||||||
ui8 getIdOfFirstUnallocatedPlayer() const;
|
ui8 getIdOfFirstUnallocatedPlayer() const;
|
||||||
|
|
||||||
|
void multiplayerWelcomeMessage();
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user