1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Merge remote-tracking branch 'origin/develop' into parellel_rmg

# Conflicts:
#	lib/rmg/CZonePlacer.h
#	lib/rmg/TreasurePlacer.h
This commit is contained in:
Tomasz Zieliński 2023-05-05 09:00:44 +02:00
commit d137f7157c
463 changed files with 16504 additions and 12677 deletions

View File

@ -71,11 +71,11 @@ jobs:
- platform: linux-qt6
os: ubuntu-22.04
test: 0
preset: linux-clang-release
preset: linux-clang-test
- platform: linux
os: ubuntu-20.04
test: 0
preset: linux-gcc-release
preset: linux-gcc-test
- platform: mac-intel
os: macos-12
test: 0
@ -130,7 +130,7 @@ jobs:
preset: android-conan-ninja-release
conan_profile: android-64
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
artifact_platform: aarch64-v8a
artifact_platform: arm64-v8a
runs-on: ${{ matrix.os }}
defaults:
run:
@ -225,6 +225,7 @@ jobs:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
path: |
${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
- name: Android artifacts
if: ${{ startsWith(matrix.platform, 'android') }}
uses: actions/upload-artifact@v3
@ -233,6 +234,14 @@ jobs:
path: |
${{ env.ANDROID_APK_PATH }}
- name: Android JNI ${{matrix.platform}}
if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
uses: actions/upload-artifact@v3
with:
name: Android JNI ${{matrix.platform}}
path: |
${{ github.workspace }}/android/vcmi-app/src/main/jniLibs
- name: Upload build
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
continue-on-error: true
@ -254,3 +263,106 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
# copy-pasted mostly
bundle_release:
needs: build
if: always() && github.ref == 'refs/heads/master'
strategy:
matrix:
include:
- platform: android-32
os: ubuntu-22.04
extension: aab
preset: android-conan-ninja-release
conan_profile: android-32
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
artifact_platform: aab
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Dependencies
run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
env:
VCMI_BUILD_PLATFORM: x64
- uses: actions/setup-python@v4
if: "${{ matrix.conan_profile != '' }}"
with:
python-version: '3.10'
- name: Conan setup
if: "${{ matrix.conan_profile != '' }}"
run: |
pip3 install 'conan<2.0'
conan profile new default --detect
conan install . \
--install-folder=conan-generated \
--no-imports \
--build=never \
--profile:build=default \
--profile:host=CI/conan/${{ matrix.conan_profile }} \
${{ matrix.conan_options }}
env:
GENERATE_ONLY_BUILT_CONFIG: 1
- name: Git branch name
id: git-branch-name
uses: EthanSK/git-branch-name-action@v1
- name: Build Number
run: |
source '${{github.workspace}}/CI/get_package_name.sh'
if [ '${{ matrix.artifact_platform }}' ]; then
VCMI_PACKAGE_FILE_NAME+="-${{ matrix.artifact_platform }}"
fi
echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV
echo VCMI_PACKAGE_NAME_SUFFIX="$VCMI_PACKAGE_NAME_SUFFIX" >> $GITHUB_ENV
echo VCMI_PACKAGE_GITVERSION="$VCMI_PACKAGE_GITVERSION" >> $GITHUB_ENV
env:
PULL_REQUEST: ${{ github.event.pull_request.number }}
- name: CMake Preset
run: |
cmake --preset ${{ matrix.preset }}
- name: Build Preset
run: |
cmake --build --preset ${{matrix.preset}}
- name: Download libs x64
uses: actions/download-artifact@v3
with:
name: Android JNI android-64
path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
- name: Create android package
run: |
cd android
./gradlew bundleRelease --info
echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/android/vcmi-app/build/outputs/bundle/release/*.aab)" >> $GITHUB_ENV
env:
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Android artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
path: |
${{ env.ANDROID_APK_PATH }}
- uses: act10ns/slack@v1
with:
status: ${{ job.status }}
channel: '#notifications'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()

1
.gitmodules vendored
View File

@ -1,6 +1,7 @@
[submodule "test/googletest"]
path = test/googletest
url = https://github.com/google/googletest
branch = v1.13.x
[submodule "AI/FuzzyLite"]
path = AI/FuzzyLite
url = https://github.com/fuzzylite/fuzzylite.git

View File

@ -51,11 +51,11 @@ int64_t AttackPossibility::calculateDamageReduce(
// FIXME: provide distance info for Jousting bonus
auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
auto enemiesKilled = damageDealt / defender->getMaxHealth() + (damageDealt % defender->getMaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
auto damagePerEnemy = enemyDamage / (double)defender->getCount();
return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->getMaxHealth()));
}
int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
@ -97,7 +97,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
auto attacker = attackInfo.attacker;
auto defender = attackInfo.defender;
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);

View File

@ -102,14 +102,14 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
try
{
if(stack->type->getId() == CreatureID::CATAPULT)
if(stack->creatureId() == CreatureID::CATAPULT)
return useCatapult(stack);
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
{
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
std::map<int, const CStack*> woundHpToStack;
for(auto stack : healingTargets)
if(auto woundHp = stack->MaxHealth() - stack->getFirstHPleft())
if(auto woundHp = stack->getMaxHealth() - stack->getFirstHPleft())
woundHpToStack[woundHp] = stack;
if(woundHpToStack.empty())
return BattleAction::makeDefend(stack);
@ -137,7 +137,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
std::optional<PossibleSpellcast> bestSpellcast(std::nullopt);
//TODO: faerie dragon type spell should be selected by server
SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
if(stack->hasBonusOfType(Bonus::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
{
const CSpell * spell = creatureSpellToCast.toSpell();
@ -214,7 +214,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
bestAttack.attackerState->unitType()->getJsonKey(),
bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
(int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex,
bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true),
bestAttack.attack.chargeDistance, bestAttack.attack.attacker->speed(0, true),
bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue()
);
}
@ -241,7 +241,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
}
if(score <= EvaluationResult::INEFFECTIVE_SCORE
&& !stack->hasBonusOfType(Bonus::FLYING)
&& !stack->hasBonusOfType(BonusType::FLYING)
&& stack->unitSide() == BattleSide::ATTACKER
&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
{
@ -282,7 +282,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const
{
auto reachability = cb->getReachability(stack);
auto avHexes = cb->battleGetAvailableHexes(reachability, stack, true);
auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
{
@ -321,7 +321,7 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<Battl
scoreEvaluator.updateReachabilityMap(hb);
if(stack->hasBonusOfType(Bonus::FLYING))
if(stack->hasBonusOfType(BonusType::FLYING))
{
std::set<BattleHex> obstacleHexes;
@ -420,7 +420,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
attack.aimToHex(targetHex);
attack.actionType = EActionType::CATAPULT;
attack.side = side;
attack.stackNumber = stack->ID;
attack.stackNumber = stack->unitId();
movesSkippedByDefense = 0;
@ -815,7 +815,7 @@ std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
{
if(stack->alive())
{
if(stack->side == bs.ourSide)
if(stack->unitSide() == bs.ourSide)
bs.ourStacks.push_back(stack);
else
{

View File

@ -31,7 +31,7 @@ struct CurrentOffensivePotential
{
for(auto stack : cbc->battleGetStacks())
{
if(stack->side == side)
if(stack->unitSide() == side)
ourAttacks[stack] = PotentialTargets(stack);
else
enemyAttacks[stack] = PotentialTargets(stack);

View File

@ -65,7 +65,7 @@ int64_t BattleExchangeVariant::trackAttack(
bool evaluateOnly)
{
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
DamageEstimation retaliation;
@ -205,7 +205,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
if(targets.unreachableEnemies.empty())
return result;
auto speed = activeStack->Speed();
auto speed = activeStack->speed();
if(speed == 0)
return result;
@ -607,7 +607,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
{
bool reachable = unitReachability.distances[hex] <= unit->Speed(turn);
bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
{
@ -617,7 +617,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
{
for(BattleHex neighbor : hex.neighbouringTiles())
{
reachable = unitReachability.distances[neighbor] <= unit->Speed(turn);
reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
if(reachable) break;
}
@ -665,7 +665,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
{
bool enemyUnit = false;
bool reachable = unitReachability.distances[hex] <= unit->Speed(turn);
bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
{
@ -677,7 +677,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
for(BattleHex neighbor : hex.neighbouringTiles())
{
reachable = unitReachability.distances[neighbor] <= unit->Speed(turn);
reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
if(reachable) break;
}

View File

@ -56,10 +56,7 @@ struct EvaluationResult
class BattleExchangeVariant
{
public:
BattleExchangeVariant()
:dpsScore(0), attackerValue()
{
}
BattleExchangeVariant(): dpsScore(0) {}
int64_t trackAttack(const AttackPossibility & ap, HypotheticBattle & state);
@ -92,10 +89,7 @@ private:
std::vector<battle::Units> turnOrder;
public:
BattleExchangeEvaluator(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env)
:cb(cb), reachabilityMap(), env(env), turnOrder()
{
}
BattleExchangeEvaluator(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env): cb(cb), env(env) {}
EvaluationResult findBestTarget(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb);
int64_t calculateExchange(const AttackPossibility & ap, PotentialTargets & targets, HypotheticBattle & hb);

View File

@ -15,14 +15,14 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
{
auto attackerInfo = state.battleGetUnitByID(attacker->unitId());
auto reachability = state.getReachability(attackerInfo);
auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo, true);
auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo, false);
//FIXME: this should part of battleGetAvailableHexes
bool forceTarget = false;
const battle::Unit * forcedTarget = nullptr;
BattleHex forcedHex;
if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE))
if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
{
forceTarget = true;
auto nearest = state.getNearestStack(attackerInfo);

View File

@ -24,7 +24,7 @@ void actualizeEffect(TBonusListPtr target, const Bonus & ef)
{
for(auto & bonus : *target) //TODO: optimize
{
if(bonus->source == Bonus::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
if(bonus->source == BonusSource::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
{
if(bonus->turnsRemain < ef.turnsRemain)
{
@ -36,9 +36,9 @@ void actualizeEffect(TBonusListPtr target, const Bonus & ef)
}
}
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack)
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack)
: battle::CUnitState(),
origBearer(Stack),
origBearer(Stack->getBonusBearer()),
owner(Owner),
type(Stack->unitType()),
baseAmount(Stack->unitBaseAmount()),
@ -126,7 +126,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
{
if(selector(&bonus) && (!limit || !limit(&bonus)))
{
if(ret->getFirst(Selector::source(Bonus::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
{
actualizeEffect(ret, bonus);
}

View File

@ -14,16 +14,10 @@
#include <vcmi/Environment.h>
#include <vcmi/ServerCallback.h>
#include "../../lib/HeroBonus.h"
#include "../../lib/bonuses/Bonus.h"
#include "../../lib/battle/BattleProxy.h"
#include "../../lib/battle/CUnitState.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
VCMI_LIB_NAMESPACE_END
class HypotheticBattle;
///Fake random generator, used by AI to evaluate random server behavior
@ -54,7 +48,7 @@ public:
std::vector<Bonus> bonusesToUpdate;
std::set<std::shared_ptr<Bonus>> bonusesToRemove;
StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack);
StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack);
StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);

View File

@ -30,7 +30,7 @@ ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
for(const CStack *enemy : getCbc()->battleGetStacks())
{
//Consider only stacks of different owner
if(enemy->side == endangered->side)
if(enemy->unitSide() == endangered->unitSide())
continue;
//Look-up which tiles can be melee-attacked

@ -1 +1 @@
Subproject commit 9751a751a17c0682ed5d02e583c6a0cda8bc88e5
Subproject commit 7aee562d6ca17f3cf42588ffb5116e03017c3c50

View File

@ -256,7 +256,7 @@ void AIGateway::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance
NET_EVENT_HANDLER;
}
void AIGateway::tileHidden(const std::unordered_set<int3, ShashInt3> & pos)
void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
{
LOG_TRACE(logAi);
NET_EVENT_HANDLER;
@ -264,7 +264,7 @@ void AIGateway::tileHidden(const std::unordered_set<int3, ShashInt3> & pos)
nullkiller->memory->removeInvisibleObjects(myCb.get());
}
void AIGateway::tileRevealed(const std::unordered_set<int3, ShashInt3> & pos)
void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
{
LOG_TRACE(logAi);
NET_EVENT_HANDLER;
@ -1058,27 +1058,6 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
}
}
bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const
{
//TODO: make gathering gold, building tavern or conquering town (?) possible subgoals
if(!t)
t = findTownWithTavern();
if(!t || !townHasFreeTavern(t))
return false;
if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
return false;
if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
return false;
if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
return false;
if(!cb->getAvailableHeroes(t).size())
return false;
return true;
}
void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side)
{
NET_EVENT_HANDLER;
@ -1160,16 +1139,6 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj)
}
}
HeroPtr AIGateway::getHeroWithGrail() const
{
for(const CGHeroInstance * h : cb->getHeroesInfo())
{
if(h->hasArt(ArtifactID::GRAIL))
return h;
}
return nullptr;
}
bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
{
if(h->inTownGarrison && h->visitedTown)
@ -1416,7 +1385,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
//TODO trade only as much as needed
if (toGive) //don't try to sell 0 resources
{
cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
}
@ -1437,15 +1406,6 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
}
}
const CGTownInstance * AIGateway::findTownWithTavern() const
{
for(const CGTownInstance * t : cb->getTownsInfo())
if(townHasFreeTavern(t))
return t;
return nullptr;
}
void AIGateway::endTurn()
{
logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr());

View File

@ -127,7 +127,7 @@ public:
void heroMoved(const TryMoveHero & details, bool verbose = true) override;
void heroInGarrisonChange(const CGTownInstance * town) override;
void centerView(int3 pos, int focusTime) override;
void tileHidden(const std::unordered_set<int3, ShashInt3> & pos) override;
void tileHidden(const std::unordered_set<int3> & pos) override;
void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
void artifactAssembled(const ArtifactLocation & al) override;
void showTavernWindow(const CGObjectInstance * townOrTavern) override;
@ -142,7 +142,7 @@ public:
void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
void tileRevealed(const std::unordered_set<int3, ShashInt3> & pos) override;
void tileRevealed(const std::unordered_set<int3> & pos) override;
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
@ -198,11 +198,6 @@ public:
void retrieveVisitableObjs();
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
HeroPtr getHeroWithGrail() const;
const CGTownInstance * findTownWithTavern() const;
bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
void requestSent(const CPackForServer * pack, int requestID) override;
void answerQuery(QueryID queryID, int selection);
//special function that can be called ONLY from game events handling thread and will send request ASAP

View File

@ -307,7 +307,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
auto art2 = a2->artType;
if(art1->price == art2->price)
return art1->valOfBonuses(Bonus::PRIMARY_SKILL) > art2->valOfBonuses(Bonus::PRIMARY_SKILL);
return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
else
return art1->price > art2->price;
}
@ -319,7 +319,7 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
//TODO: allow polling of remaining creatures in dwelling
if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
return rewardable->getResetDuration() == 7;
return rewardable->configuration.getResetDuration() == 7;
if(dynamic_cast<const CGDwelling *>(obj))
return true;

View File

@ -54,7 +54,7 @@
using namespace tbb;
typedef std::pair<ui32, std::vector<CreatureID>> dwellingContent;
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
namespace NKAI
{
@ -305,10 +305,10 @@ public:
public:
using ptr_type = std::unique_ptr<T, External_Deleter>;
SharedPool(std::function<std::unique_ptr<T>()> elementFactory)
: elementFactory(elementFactory), pool(), sync(), instance_tracker(new SharedPool<T>*(this))
SharedPool(std::function<std::unique_ptr<T>()> elementFactory):
elementFactory(elementFactory), pool(), instance_tracker(new SharedPool<T> *(this))
{}
void add(std::unique_ptr<T> t)
{
boost::lock_guard<boost::mutex> lock(sync);

View File

@ -77,7 +77,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
if(left.creature->getLevel() != right.creature->getLevel())
return left.creature->getLevel() < right.creature->getLevel();
return left.creature->Speed() > right.creature->Speed();
return left.creature->speed() > right.creature->speed();
});
return weakest;
@ -108,12 +108,12 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
uint64_t armyValue = 0;
TemporaryArmy newArmyInstance;
auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(Bonus::MORALE));
auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(BonusType::MORALE));
for(auto bonus : *bonusModifiers)
{
// army bonuses will change and object bonuses are temporary
if(bonus->source != Bonus::ARMY && bonus->source != Bonus::OBJECT)
if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT)
{
newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
}
@ -150,7 +150,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
for(auto & slot : newArmyInstance.Slots())
{
auto morale = slot.second->MoraleVal();
auto morale = slot.second->moraleVal();
auto multiplier = 1.0f;
const float BadMoraleChance = 0.083f;

View File

@ -32,13 +32,8 @@ struct SlotInfo
struct ArmyUpgradeInfo
{
std::vector<SlotInfo> resultingArmy;
uint64_t upgradeValue;
uint64_t upgradeValue = 0;
TResources upgradeCost;
ArmyUpgradeInfo()
: resultingArmy(), upgradeValue(0), upgradeCost()
{
}
};
class DLL_EXPORT IArmyManager //: public: IAbstractManager

View File

@ -62,8 +62,11 @@ public:
HeroRole townRole;
bool hasSomethingToBuild;
TownDevelopmentInfo(const CGTownInstance* town)
:town(town), armyStrength(0), toBuild(), townDevelopmentCost(), requiredResources(), townRole(HeroRole::SCOUT), hasSomethingToBuild(false)
TownDevelopmentInfo(const CGTownInstance * town):
town(town),
armyStrength(0),
townRole(HeroRole::SCOUT),
hasSomethingToBuild(false)
{
}

View File

@ -12,6 +12,8 @@
#include "../Engine/Nullkiller.h"
#include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/CHeroHandler.h"
#include "../../../lib/GameSettings.h"
#include "../../../lib/CGameState.h"
namespace NKAI
{
@ -70,10 +72,10 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
{
auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->getIndex());
auto secondarySkillBonus = Selector::targetSourceType()(Bonus::SECONDARY_SKILL);
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, hero->type->getIndex());
auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(Bonus::SECONDARY_SKILL));
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
float specialityScore = 0.0f;
for(auto bonus : *secondarySkillBonuses)
@ -179,6 +181,51 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const
return evaluateFightingStrength(hero);
}
bool HeroManager::canRecruitHero(const CGTownInstance * town) const
{
if(!town)
town = findTownWithTavern();
if(!town || !townHasFreeTavern(town))
return false;
if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST)
return false;
const bool includeGarnisoned = true;
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
if(heroCount >= ALLOWED_ROAMING_HEROES)
return false;
if(heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
return false;
if(!cb->getAvailableHeroes(town).size())
return false;
return true;
}
const CGTownInstance * HeroManager::findTownWithTavern() const
{
for(const CGTownInstance * t : cb->getTownsInfo())
if(townHasFreeTavern(t))
return t;
return nullptr;
}
const CGHeroInstance * HeroManager::findHeroWithGrail() const
{
for(const CGHeroInstance * h : cb->getHeroesInfo())
{
if(h->hasArt(ArtifactID::GRAIL))
return h;
}
return nullptr;
}
SecondarySkillScoreMap::SecondarySkillScoreMap(std::map<SecondarySkill, float> scoreMap)
:scoreMap(scoreMap)
{

View File

@ -30,6 +30,8 @@ public:
virtual void update() = 0;
virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0;
virtual float evaluateHero(const CGHeroInstance * hero) const = 0;
virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0;
virtual const CGHeroInstance * findHeroWithGrail() const = 0;
};
class DLL_EXPORT ISecondarySkillRule
@ -57,20 +59,24 @@ private:
static SecondarySkillEvaluator scountSkillsScores;
CCallback * cb; //this is enough, but we downcast from CCallback
const Nullkiller * ai;
std::map<HeroPtr, HeroRole> heroRoles;
public:
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {}
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {}
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
HeroRole getHeroRole(const HeroPtr & hero) const override;
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
void update() override;
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
float evaluateHero(const CGHeroInstance * hero) const override;
bool canRecruitHero(const CGTownInstance * t = nullptr) const override;
const CGHeroInstance * findHeroWithGrail() const override;
private:
float evaluateFightingStrength(const CGHeroInstance * hero) const;
float evaluateSpeciality(const CGHeroInstance * hero) const;
const CGTownInstance * findTownWithTavern() const;
};
// basic skill scores. missing skills will have score of 0

View File

@ -22,7 +22,7 @@ struct ClusterObjectInfo
uint8_t turn;
};
typedef tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo> ClusterObjects;
using ClusterObjects = tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo>;
struct ObjectCluster
{
@ -36,11 +36,8 @@ public:
}
void addObject(const CGObjectInstance * object, const AIPath & path, float priority);
ObjectCluster(const CGObjectInstance * blocker)
:objects(), blocker(blocker)
{
}
ObjectCluster(const CGObjectInstance * blocker): blocker(blocker) {}
ObjectCluster() : ObjectCluster(nullptr)
{
@ -50,7 +47,7 @@ public:
const CGObjectInstance * calculateCenter() const;
};
typedef tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> ClusterMap;
using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>;
class ObjectClusterizer
{
@ -67,10 +64,7 @@ public:
std::vector<std::shared_ptr<ObjectCluster>> getLockedClusters() const;
const CGObjectInstance * getBlocker(const AIPath & path) const;
ObjectClusterizer(const Nullkiller * ai)
:nearObjects(), farObjects(), blockedObjects(), ai(ai)
{
}
ObjectClusterizer(const Nullkiller * ai): ai(ai) {}
private:
bool shouldVisitObject(const CGObjectInstance * obj) const;

View File

@ -209,7 +209,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
{
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
if(tasks.empty())
if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL)
captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
}

View File

@ -58,13 +58,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
auto treats = { treatNode.maximumDanger, treatNode.fastestDanger };
if(!treatNode.fastestDanger.hero)
{
logAi->trace("No treat found for town %s", town->getNameTranslated());
return;
}
int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
if(town->garrisonHero)
@ -91,6 +84,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
return;
}
if(!treatNode.fastestDanger.hero)
{
logAi->trace("No treat found for town %s", town->getNameTranslated());
return;
}
uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);

View File

@ -53,7 +53,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
for(auto town : towns)
{
if(ai->canRecruitAnyHero(town))
if(ai->nullkiller->heroManager->canRecruitHero(town))
{
auto availableHeroes = cb->getAvailableHeroes(town);

View File

@ -66,7 +66,7 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town)
bool needToRecruitHero(const CGTownInstance * startupTown)
{
if(!ai->canRecruitAnyHero(startupTown))
if(!ai->nullkiller->heroManager->canRecruitHero(startupTown))
return false;
if(!startupTown->garrisonHero && !startupTown->visitingHero)

View File

@ -72,10 +72,10 @@ void AIMemory::markObjectVisited(const CGObjectInstance * obj)
// TODO: maybe this logic belongs to CaptureObjects::shouldVisit
if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
{
if (rewardable->getVisitMode() == CRewardableObject::VISIT_HERO) //we may want to visit it with another hero
if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_HERO) //we may want to visit it with another hero
return;
if (rewardable->getVisitMode() == CRewardableObject::VISIT_BONUS) //or another time
if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_BONUS) //or another time
return;
}

View File

@ -22,7 +22,7 @@ struct GoalHash
}
};
typedef std::unordered_map<Goals::TSubgoal, Goals::TGoalVec, GoalHash> TGoalHashSet;
using TGoalHashSet = std::unordered_map<Goals::TSubgoal, Goals::TGoalVec, GoalHash>;
class DeepDecomposer
{

View File

@ -53,14 +53,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
double shootersStrength = 0;
ui32 maxSpeed = 0;
static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
static const CSelector selectorSHOOTER = Selector::type()(BonusType::SHOOTER);
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)BonusType::SHOOTER);
static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
static const CSelector selectorFLYING = Selector::type()(BonusType::FLYING);
static const std::string keyFLYING = "type_"+std::to_string((int32_t)BonusType::FLYING);
static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
for(auto s : army->Slots())
{

View File

@ -28,7 +28,7 @@ private:
TacticalAdvantageEngine tacticalAdvantageEngine;
public:
FuzzyHelper(const Nullkiller * ai) : ai(ai), tacticalAdvantageEngine() {}
FuzzyHelper(const Nullkiller * ai): ai(ai) {}
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?

View File

@ -117,7 +117,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
void Nullkiller::resetAiState()
{
lockedResources = TResources();
scanDepth = ScanDepth::SMALL;
scanDepth = ScanDepth::FULL;
playerID = ai->playerID;
lockedHeroes.clear();
dangerHitMap->reset();

View File

@ -88,6 +88,7 @@ public:
int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; }
void lockResources(const TResources & res);
const TResources & getLockedResources() const { return lockedResources; }
ScanDepth getScanDepth() const { return scanDepth; }
private:
void resetAiState();

View File

@ -210,14 +210,14 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
return 1500;
auto statsValue =
10 * art->valOfBonuses(Bonus::MOVEMENT, 1)
+ 1200 * art->valOfBonuses(Bonus::STACKS_SPEED)
+ 700 * art->valOfBonuses(Bonus::MORALE)
+ 700 * art->getAttack(false)
+ 700 * art->getDefense(false)
+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
+ 500 * art->valOfBonuses(Bonus::LUCK);
10 * art->valOfBonuses(BonusType::MOVEMENT, 1)
+ 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
+ 700 * art->valOfBonuses(BonusType::MORALE)
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK)
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
+ 500 * art->valOfBonuses(BonusType::LUCK);
auto classValue = 0;
@ -297,6 +297,12 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
{
if(!target)
return 0;
if(auto * m = dynamic_cast<const IMarket *>(target))
{
if(m->allowsTrade(EMarketMode::RESOURCE_SKILL))
return 2000;
}
switch(target->ID)
{
@ -305,8 +311,6 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
case Obj::SCHOOL_OF_MAGIC:
case Obj::SCHOOL_OF_WAR:
return 1000;
case Obj::UNIVERSITY:
return 2000;
case Obj::CREATURE_GENERATOR1:
case Obj::CREATURE_GENERATOR2:
case Obj::CREATURE_GENERATOR3:

View File

@ -81,9 +81,9 @@ namespace Goals
bool operator<(const TSubgoal & rhs) const;
};
typedef std::shared_ptr<ITask> TTask;
typedef std::vector<TTask> TTaskVec;
typedef std::vector<TSubgoal> TGoalVec;
using TTask = std::shared_ptr<ITask>;
using TTaskVec = std::vector<TTask>;
using TGoalVec = std::vector<TSubgoal>;
//method chaining + clone pattern
#define SETTER(type, field) AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;};
@ -107,8 +107,7 @@ namespace Goals
const CGTownInstance *town; SETTER(CGTownInstance *, town)
int bid; SETTER(int, bid)
AbstractGoal(EGoals goal = EGoals::INVALID)
: goalType(goal), hero()
AbstractGoal(EGoals goal = EGoals::INVALID): goalType(goal)
{
isAbstract = false;
value = 0;

View File

@ -33,8 +33,6 @@ void RecruitHero::accept(AIGateway * ai)
{
auto t = town;
if(!t) t = ai->findTownWithTavern();
if(!t)
{
throw cannotFulfillGoalException("No town to recruit hero!");

View File

@ -1093,7 +1093,7 @@ void AINodeStorage::calculateTownPortal(
if(nodeOptional)
{
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
logAi->trace("Adding town portal node at %s", targetTown->name);
logAi->trace("Adding town portal node at %s", targetTown->getObjectName());
#endif
output.push_back(nodeOptional.value());
}

View File

@ -205,14 +205,14 @@ public:
inline void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater)
{
auto aiNode = static_cast<AIPathNode *>(node);
auto * aiNode = static_cast<AIPathNode *>(node);
updater(aiNode);
}
inline const CGHeroInstance * getHero(const CGPathNode * node) const
{
auto aiNode = getAINode(node);
const auto * aiNode = getAINode(node);
return aiNode->actor->hero;
}

View File

@ -32,9 +32,7 @@ public:
virtual bool needsLastStack() const override;
std::shared_ptr<SpecialAction> getActorAction() const;
HeroExchangeArmy() : CArmedInstance(true), armyCost(), requireBuyArmy(false)
{
}
HeroExchangeArmy(): CArmedInstance(true), requireBuyArmy(false) {}
};
struct ExchangeResult

View File

@ -53,15 +53,13 @@ namespace AIPathfinding
for(const CGTownInstance * t : cb->getTownsInfo())
{
// do not allow ally shipyards because of bug
if(t->hasBuilt(BuildingID::SHIPYARD) && t->getOwner() == ai->playerID)
if(t->hasBuilt(BuildingID::SHIPYARD))
shipyards.push_back(t);
}
for(const CGObjectInstance * obj : ai->memory->visitableObjs)
{
// do not allow ally shipyards because of bug
if(obj->ID != Obj::TOWN && obj->getOwner() == ai->playerID) //towns were handled in the previous loop
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
{
if(const IShipyard * shipyard = IShipyard::castFrom(obj))
shipyards.push_back(shipyard);

View File

@ -95,7 +95,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
ReachabilityInfo dists = cb->getReachability(stack);
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
if(stack->type->getId() == CreatureID::CATAPULT)
if(stack->creatureId() == CreatureID::CATAPULT)
{
BattleAction attack;
static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
@ -103,11 +103,11 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
attack.aimToHex(seletectedHex);
attack.actionType = EActionType::CATAPULT;
attack.side = side;
attack.stackNumber = stack->ID;
attack.stackNumber = stack->unitId();
return attack;
}
else if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON))
else if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON))
{
return BattleAction::makeDefend(stack);
}
@ -120,7 +120,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
}
else
{
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, true);
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
for (BattleHex hex : avHexes)
{
@ -270,7 +270,7 @@ BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> h
return BattleAction::makeDefend(stack);
}
if(stack->hasBonusOfType(Bonus::FLYING))
if(stack->hasBonusOfType(BonusType::FLYING))
{
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
// We just check all available hexes and pick the one closest to the target.

View File

@ -257,7 +257,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
auto art2 = a2->artType;
if(art1->price == art2->price)
return art1->valOfBonuses(Bonus::PRIMARY_SKILL) > art2->valOfBonuses(Bonus::PRIMARY_SKILL);
return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
else
return art1->price > art2->price;
}

View File

@ -23,9 +23,9 @@
class CCallback;
struct creInfo;
typedef const int3 & crint3;
typedef const std::string & crstring;
typedef std::pair<ui32, std::vector<CreatureID>> dwellingContent;
using crint3 = const int3 &;
using crstring = const std::string &;
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
const int ACTUAL_RESOURCE_COUNT = 7;

View File

@ -63,7 +63,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
if(left.creature->getLevel() != right.creature->getLevel())
return left.creature->getLevel() < right.creature->getLevel();
return left.creature->Speed() > right.creature->Speed();
return left.creature->speed() > right.creature->speed();
});
return weakest;

View File

@ -52,14 +52,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
double shootersStrength = 0;
ui32 maxSpeed = 0;
static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
static const CSelector selectorSHOOTER = Selector::type()(BonusType::SHOOTER);
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)BonusType::SHOOTER);
static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
static const CSelector selectorFLYING = Selector::type()(BonusType::FLYING);
static const std::string keyFLYING = "type_"+std::to_string((int32_t)BonusType::FLYING);
static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
for(auto s : army->Slots())
{

View File

@ -76,7 +76,7 @@ namespace Goals
//TODO: serialize?
};
typedef std::vector<TSubgoal> TGoalVec;
using TGoalVec = std::vector<TSubgoal>;
//method chaining + clone pattern
#define VSETTER(type, field) virtual AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;};
@ -121,8 +121,7 @@ namespace Goals
TSubgoal parent; VSETTER(TSubgoal, parent)
EvaluationContext evaluationContext; VSETTER(EvaluationContext, evaluationContext)
AbstractGoal(EGoals goal = EGoals::INVALID)
: goalType(goal), evaluationContext()
AbstractGoal(EGoals goal = EGoals::INVALID): goalType(goal)
{
priority = 0;
isElementar = false;

View File

@ -131,13 +131,16 @@ TSubgoal CollectRes::whatToDoToTrade()
std::vector<const CGObjectInstance *> visObjs;
ai->retrieveVisitableObjs(visObjs, true);
for (const CGObjectInstance * obj : visObjs)
for(const CGObjectInstance * obj : visObjs)
{
if (const IMarket * m = IMarket::castFrom(obj, false))
if(const IMarket * m = IMarket::castFrom(obj, false); m->allowsTrade(EMarketMode::RESOURCE_RESOURCE))
{
if (obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE))
markets.push_back(m);
else if (obj->ID == Obj::TRADING_POST)
if(obj->ID == Obj::TOWN)
{
if(obj->tempOwner == ai->playerID)
markets.push_back(m);
}
else
markets.push_back(m);
}
}
@ -149,9 +152,10 @@ TSubgoal CollectRes::whatToDoToTrade()
markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool
{
if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID))
auto * o = dynamic_cast<const CGObjectInstance *>(market);
if(o && !(o->ID == Obj::TOWN && o->tempOwner == ai->playerID))
{
if (!ai->isAccessible(market->o->visitablePos()))
if(!ai->isAccessible(o->visitablePos()))
return true;
}
return false;
@ -170,7 +174,7 @@ TSubgoal CollectRes::whatToDoToTrade()
const IMarket * m = markets.back();
//attempt trade at back (best prices)
int howManyCanWeBuy = 0;
for (auto i = EGameResID::WOOD; i <= EGameResID::GOLD; vstd::advance(i, 1))
for (GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i)
{
if (GameResID(i) == resID)
continue;
@ -182,9 +186,10 @@ TSubgoal CollectRes::whatToDoToTrade()
if (howManyCanWeBuy >= value)
{
auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
auto * o = dynamic_cast<const CGObjectInstance *>(m);
auto backObj = cb->getTopObj(o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
assert(backObj);
auto objid = m->o->id.getNum();
auto objid = o->id.getNum();
if (backObj->tempOwner != ai->playerID) //top object not owned
{
return sptr(VisitObj(objid)); //just go there

View File

@ -19,8 +19,6 @@
class CCallback;
extern boost::thread_specific_ptr<CCallback> cb; //for templates
struct AIPathNode : public CGPathNode
{
uint32_t chainMask;

View File

@ -22,6 +22,10 @@
#include "../../lib/CGameState.h"
#include "../../lib/NetPacksBase.h"
#include "../../lib/NetPacks.h"
#include "../../lib/bonuses/CBonusSystemNode.h"
#include "../../lib/bonuses/Limiters.h"
#include "../../lib/bonuses/Updaters.h"
#include "../../lib/bonuses/Propagators.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/serializer/BinarySerializer.h"
#include "../../lib/serializer/BinaryDeserializer.h"
@ -268,7 +272,7 @@ void VCAI::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * to
//moveCreaturesToHero(town);
}
void VCAI::tileHidden(const std::unordered_set<int3, ShashInt3> & pos)
void VCAI::tileHidden(const std::unordered_set<int3> & pos)
{
LOG_TRACE(logAi);
NET_EVENT_HANDLER;
@ -277,7 +281,7 @@ void VCAI::tileHidden(const std::unordered_set<int3, ShashInt3> & pos)
clearPathsInfo();
}
void VCAI::tileRevealed(const std::unordered_set<int3, ShashInt3> & pos)
void VCAI::tileRevealed(const std::unordered_set<int3> & pos)
{
LOG_TRACE(logAi);
NET_EVENT_HANDLER;
@ -1605,10 +1609,10 @@ void VCAI::markObjectVisited(const CGObjectInstance * obj)
if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj)) //we may want to visit it with another hero
{
if (rewardable->getVisitMode() == CRewardableObject::VISIT_HERO) //we may want to visit it with another hero
if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_HERO) //we may want to visit it with another hero
return;
if (rewardable->getVisitMode() == CRewardableObject::VISIT_BONUS) //or another time
if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_BONUS) //or another time
return;
}
@ -2130,7 +2134,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
//TODO trade only as much as needed
if (toGive) //don't try to sell 0 resources
{
cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
}
@ -2746,7 +2750,7 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
{
//TODO: allow polling of remaining creatures in dwelling
if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
return rewardable->getResetDuration() == 7;
return rewardable->configuration.getResetDuration() == 7;
if(dynamic_cast<const CGDwelling *>(obj))
return true;

View File

@ -160,7 +160,7 @@ public:
void heroMoved(const TryMoveHero & details, bool verbose = true) override;
void heroInGarrisonChange(const CGTownInstance * town) override;
void centerView(int3 pos, int focusTime) override;
void tileHidden(const std::unordered_set<int3, ShashInt3> & pos) override;
void tileHidden(const std::unordered_set<int3> & pos) override;
void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
void artifactAssembled(const ArtifactLocation & al) override;
void showTavernWindow(const CGObjectInstance * townOrTavern) override;
@ -175,7 +175,7 @@ public:
void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
void tileRevealed(const std::unordered_set<int3, ShashInt3> & pos) override;
void tileRevealed(const std::unordered_set<int3> & pos) override;
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;

View File

@ -244,15 +244,15 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
sendRequest(&pack);
}
void CCallback::trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero)
void CCallback::trade(const IMarket * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero)
{
trade(market, mode, std::vector<ui32>(1, id1), std::vector<ui32>(1, id2), std::vector<ui32>(1, val1), hero);
}
void CCallback::trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
void CCallback::trade(const IMarket * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
{
TradeOnMarketplace pack;
pack.marketId = market->id;
pack.marketId = dynamic_cast<const CGObjectInstance *>(market)->id;
pack.heroId = hero ? hero->id : ObjectInstanceID();
pack.mode = mode;
pack.r1 = id1;

View File

@ -33,6 +33,7 @@ class IBattleEventsReceiver;
class IGameEventsReceiver;
struct ArtifactLocation;
class BattleStateInfoForRetreat;
class IMarket;
VCMI_LIB_NAMESPACE_END
@ -73,8 +74,8 @@ public:
virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
virtual void swapGarrisonHero(const CGTownInstance *town)=0;
virtual void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
virtual void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
virtual void trade(const IMarket * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
virtual void trade(const IMarket * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
virtual int selectionMade(int selection, QueryID queryID) =0;
virtual int sendQueryReply(const JsonNode & reply, QueryID queryID) =0;
@ -168,8 +169,8 @@ public:
void endTurn() override;
void swapGarrisonHero(const CGTownInstance *town) override;
void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
void trade(const IMarket * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
void trade(const IMarket * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
void setFormation(const CGHeroInstance * hero, bool tight) override;
void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) override;
void save(const std::string &fname) override;

Binary file not shown.

View File

@ -0,0 +1,2 @@
STORE_FILE=android-release.jks
KEY_ALIAS=vcmi

View File

@ -251,6 +251,7 @@ if(MINGW OR MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250") # 4250: 'class1' : inherits 'class2::member' via dominance
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # 4251: class 'xxx' needs to have dll-interface to be used by clients of class 'yyy'

View File

@ -45,6 +45,15 @@
"CMAKE_INSTALL_PREFIX" : "/usr/local"
}
},
{
"name": "linux-test",
"inherits": "linux-release",
"hidden": true,
"cacheVariables": {
"ENABLE_TEST": "ON",
"ENABLE_LUA": "ON"
}
},
{
"name": "linux-clang-release",
"displayName": "Clang x86_64-pc-linux-gnu",
@ -67,6 +76,28 @@
"CMAKE_CXX_COMPILER": "/usr/bin/g++"
}
},
{
"name": "linux-clang-test",
"displayName": "Clang x86_64-pc-linux-gnu with unit testing",
"description": "VCMI Linux Clang",
"inherits": "linux-test",
"cacheVariables": {
"CMAKE_C_COMPILER": "/usr/bin/clang",
"CMAKE_CXX_COMPILER": "/usr/bin/clang++"
}
},
{
"name": "linux-gcc-test",
"displayName": "GCC x86_64-pc-linux-gnu with unit testing",
"description": "VCMI Linux GCC",
"inherits": "linux-test",
"cacheVariables": {
"ENABLE_LUA" : "OFF",
"ENABLE_PCH" : "OFF",
"CMAKE_C_COMPILER": "/usr/bin/gcc",
"CMAKE_CXX_COMPILER": "/usr/bin/g++"
}
},
{
"name": "windows-msvc-release",
"displayName": "Windows x64 RelWithDebInfo",
@ -213,6 +244,16 @@
"configurePreset": "linux-clang-release",
"inherits": "default-release"
},
{
"name": "linux-clang-test",
"configurePreset": "linux-clang-test",
"inherits": "default-release"
},
{
"name": "linux-gcc-test",
"configurePreset": "linux-gcc-test",
"inherits": "default-release"
},
{
"name": "linux-gcc-release",
"configurePreset": "linux-gcc-release",
@ -295,6 +336,16 @@
"configurePreset": "linux-gcc-release",
"inherits": "default-release"
},
{
"name": "linux-clang-test",
"configurePreset": "linux-clang-test",
"inherits": "default-release"
},
{
"name": "linux-gcc-test",
"configurePreset": "linux-gcc-test",
"inherits": "default-release"
},
{
"name": "macos-xcode-release",
"configurePreset": "macos-xcode-release",

View File

@ -1,6 +1,29 @@
# 1.2.0 -> 1.3.0
# 1.2.1 -> 1.3.0
(unreleased)
# 1.2.0 -> 1.2.1
### GENERAL:
* Implemented spell range overlay for Dimension Door and Scuttle Boat
* Fixed movement cost penalty from terrain
* Fixed empty Black Market on game start
* Fixed bad morale happening after waiting
* Fixed good morale happening after defeating last enemy unit
* Fixed death animation of Efreeti killed by petrification attack
* Fixed crash on leaving to main menu from battle in hotseat mode
* Fixed music playback on switching between towns
* Special months (double growth and plague) will now appear correctly
* Adventure map spells are no longer visible on units in battle
* Attempt to cast spell with no valid targets in hotseat will show appropriate error message
* RMG settings will now show all existing in game templates and not just those suitable for current settings
* RMG settings (map size and two-level maps) that are not compatible with current template will be blocked
* Fixed centering of scenario information window
* Fixed crash on empty save game list after filtering
* Fixed blocked progress in Launcher on language detection failure
* Launcher will now correctly handle selection of Ddata directory in H3 install
* Map editor will now correctly save message property for events and pandoras
* Fixed incorrect saving of heroes portraits in editor
# 1.1.1 -> 1.2.0
### GENERAL:

View File

@ -543,10 +543,12 @@ namespace vstd
});
}
/// Increments value by specific delta
/// similar to std::next but works with other types, e.g. enum class
template<typename T>
void advance(T &obj, int change)
T next(const T &obj, int change)
{
obj = (T)(((int)obj) + change);
return static_cast<T>(static_cast<ptrdiff_t>(obj) + change);
}
template <typename Container>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

View File

@ -1,17 +1,17 @@
{
"vcmi.adventureMap.monsterThreat.title" : "\n\n 威胁等级: ",
"vcmi.adventureMap.monsterThreat.levels.0" : "极低",
"vcmi.adventureMap.monsterThreat.title" : "\n\n威胁度: ",
"vcmi.adventureMap.monsterThreat.levels.0" : "不值一提",
"vcmi.adventureMap.monsterThreat.levels.1" : "很低",
"vcmi.adventureMap.monsterThreat.levels.2" : "低",
"vcmi.adventureMap.monsterThreat.levels.3" : "较低",
"vcmi.adventureMap.monsterThreat.levels.4" : "中等",
"vcmi.adventureMap.monsterThreat.levels.4" : "势均力敌",
"vcmi.adventureMap.monsterThreat.levels.5" : "较高",
"vcmi.adventureMap.monsterThreat.levels.6" : "高",
"vcmi.adventureMap.monsterThreat.levels.7" : "很高",
"vcmi.adventureMap.monsterThreat.levels.8" : "挑战性的",
"vcmi.adventureMap.monsterThreat.levels.8" : "略有挑战",
"vcmi.adventureMap.monsterThreat.levels.9" : "压倒性的",
"vcmi.adventureMap.monsterThreat.levels.10" : "致命的",
"vcmi.adventureMap.monsterThreat.levels.11" : "无法取胜的",
"vcmi.adventureMap.monsterThreat.levels.10" : "自寻死路",
"vcmi.adventureMap.monsterThreat.levels.11" : "天方夜谭",
"vcmi.adventureMap.confirmRestartGame" : "你想要重新开始游戏吗?",
"vcmi.adventureMap.noTownWithMarket" : "没有足够的市场。",
@ -21,100 +21,139 @@
"vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING",
"vcmi.server.errors.existingProcess" : "另一个VCMI进程在运行,请结束当前进程。",
"vcmi.server.errors.modsIncompatibility" : "需要加载mod:",
"vcmi.server.confirmReconnect" : "连接到上次吗?",
"vcmi.capitalColors.0" : "红色",
"vcmi.capitalColors.1" : "蓝色",
"vcmi.capitalColors.2" : "青色",
"vcmi.capitalColors.3" : "绿色",
"vcmi.capitalColors.4" : "橙色",
"vcmi.capitalColors.5" : "紫色",
"vcmi.capitalColors.6" : "褐色",
"vcmi.capitalColors.7" : "粉色",
"vcmi.settingsMainWindow.generalTab.hover" : "常规",
"vcmi.settingsMainWindow.generalTab.help" : "切换到“系统选项”选项卡 - 这些设置与常规游戏客户端行为相关",
"vcmi.settingsMainWindow.battleTab.hover" : "战斗",
"vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗选项”选项卡 - 这些设置允许配置战斗界面和相关内容",
"vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。",
"vcmi.server.errors.modsIncompatibility" : "需要加载的MOD列表:",
"vcmi.server.confirmReconnect" : "您想要重连上一个会话么?",
"vcmi.settingsMainWindow.generalTab.hover" : "常规",
"vcmi.settingsMainWindow.generalTab.help" : "切换到“常规”选项卡 - 设置游戏客户端呈现",
"vcmi.settingsMainWindow.battleTab.hover" : "战斗",
"vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗”选项卡 - 这些设置允许配置战斗界面和相关内容",
"vcmi.settingsMainWindow.adventureTab.hover" : "冒险地图",
"vcmi.settingsMainWindow.adventureTab.help" : "切换到“冒险地图”选项卡 - 冒险地图允许你移动英雄",
"vcmi.settingsMainWindow.otherTab.hover" : "其他设置",
"vcmi.settingsMainWindow.otherTab.help" : "切换到“其他设置”选项卡 - 由于各种原因,这些选项不适合其他类别",
"vcmi.settingsMainWindow.adventureTab.help" : "切换到“冒险地图”选项卡 - 冒险地图即玩家能操作英雄移动的界面",
"vcmi.systemOptions.videoGroup" : "视频设置",
"vcmi.systemOptions.audioGroup" : "音频设置",
"vcmi.systemOptions.otherGroup" : "其他设置", // unused right now
"vcmi.systemOptions.townsGroup" : "城镇画面",
"vcmi.systemOptions.fullscreenButton.hover" : "全屏",
"vcmi.systemOptions.fullscreenButton.help" : "{全屏n}\n\n 当你选择全屏时,VCMI将会全屏运行,否则只会运行在指定框内",
"vcmi.systemOptions.fullscreenButton.help" : "{全屏}\n\n选中时,VCMI将以全屏运行,否则运行在窗口模式。",
"vcmi.systemOptions.resolutionButton.hover" : "分辨率",
"vcmi.systemOptions.resolutionButton.help" : "{选择分辨率}\n\n 改变游戏的分辨率,达到更加清晰的效果。需要重新启动才能完成更改。",
"vcmi.systemOptions.resolutionMenu.hover" : "选择分辨率",
"vcmi.systemOptions.resolutionMenu.help" : "选择游戏的分辨率。",
"vcmi.systemOptions.fullscreenFailed" : "{全屏}\n\n 选择切换到全屏失败!当前分辨率不支持全屏!",
"vcmi.systemOptions.framerateButton.hover" : "显示传输帧数",
"vcmi.systemOptions.framerateButton.help" : "{显示传输帧数}\n\n 打开/关闭在游戏窗口角落的传输帧数计数器。",
"vcmi.systemOptions.resolutionButton.help" : "{分辨率选择}\n\n改变游戏内的分辨率,更改后需要重启游戏使其生效。",
"vcmi.systemOptions.resolutionMenu.hover" : "分辨率选择",
"vcmi.systemOptions.resolutionMenu.help" : "修改游戏运行时的分辨率。",
"vcmi.systemOptions.fullscreenFailed" : "{全屏}\n\n选择切换到全屏模式失败!当前显示器不支持该分辨率!",
"vcmi.systemOptions.framerateButton.hover" : "显示FPS",
"vcmi.systemOptions.framerateButton.help" : "{显示FPS}\n\n打开/关闭在游戏窗口角落的FPS指示器。",
"vcmi.adventureOptions.infoBarPick.hover" : "在信息面板显示消息",
"vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。",
"vcmi.adventureOptions.numericQuantities.hover" : "生物数量显示",
"vcmi.adventureOptions.numericQuantities.help" : "{生物数量显示}\n\n 以数字 A-B 格式显示不准确的敌方生物数量。",
"vcmi.adventureOptions.numericQuantities.help" : "{生物数量显示}\n\n以数字 A-B 格式显示不准确的敌方生物数量。",
"vcmi.adventureOptions.forceMovementInfo.hover" : "在状态栏中显示移动力",
"vcmi.adventureOptions.forceMovementInfo.help" : "{在状态栏中显示移动力}\n\n 不需要按ALT就可以显示移动力。",
"vcmi.adventureOptions.showGrid.hover" : "显示六角网格",
"vcmi.adventureOptions.showGrid.help" : "{显示六角网格}\n\n 在战场上显示六角网格。",
"vcmi.adventureOptions.mapScrollSpeed4.hover": "4",
"vcmi.adventureOptions.mapScrollSpeed4.help": "设置动画速度为超快",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "5",
"vcmi.adventureOptions.mapScrollSpeed5.help": "设置动画速度为极速",
"vcmi.adventureOptions.forceMovementInfo.help" : "{在状态栏中显示移动力}\n\n不需要按ALT就可以显示移动力。",
"vcmi.adventureOptions.showGrid.hover" : "显示网格",
"vcmi.adventureOptions.showGrid.help" : "{显示网格}\n\n显示网格覆盖层,高亮冒险地图物件的边沿。",
"vcmi.adventureOptions.mapSwipe.hover" : "地图拖动/镜头",
"vcmi.adventureOptions.mapSwipe.help" : "{地图拖动/镜头}\n\n在触摸屏设备上,你可以用手指轻扫来移动地图。使用鼠标时,按住鼠标左键或中键移动地图。",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
"vcmi.adventureOptions.mapScrollSpeed1.help": "将地图卷动速度设置为非常慢",
"vcmi.adventureOptions.mapScrollSpeed5.help": "将地图卷动速度设置为非常快",
"vcmi.adventureOptions.mapScrollSpeed6.help": "将地图卷动速度设置为即刻。",
"vcmi.battleOptions.showQueue.hover": "显示移动次序",
"vcmi.battleOptions.showQueue.help": "{显示移动次序}\n\n 显示当前生物的移动次序。",
"vcmi.battleOptions.queueSizeLabel.hover": "次序条尺寸 (设置后下一场战斗生效)",
"vcmi.battleOptions.queueSizeAutoButton.hover": "自动设置尺寸",
"vcmi.battleOptions.queueSizeAutoButton.help": "根据游戏分辨率设置尺寸 (像素小于700为小尺寸,根据实际调整)",
"vcmi.battleOptions.queueSizeSmallButton.hover": "小尺寸",
"vcmi.battleOptions.queueSizeSmallButton.help": "设置次序条为小尺寸",
"vcmi.battleOptions.queueSizeBigButton.hover": "大尺寸",
"vcmi.battleOptions.queueSizeBigButton.help": "设置次寻条为大尺寸(不能在像素小于700时生效)",
"vcmi.battleOptions.animationsSpeed4.hover": "4",
"vcmi.battleOptions.animationsSpeed4.help": "设置动画速度为超快",
"vcmi.battleOptions.animationsSpeed5.hover": "5",
"vcmi.battleOptions.animationsSpeed5.help": "设置动画速度为极速",
"vcmi.battleOptions.animationsSpeed6.hover": "6",
"vcmi.battleOptions.animationsSpeed6.help": "设置动画速度为最快",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过开场音乐",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过开场音乐}\n\n 战斗开始时跳过开场音乐,直接按Esc也可以跳过。",
"vcmi.battleOptions.queueSizeLabel.hover": "回合顺序指示器",
"vcmi.battleOptions.queueSizeNoneButton.hover": "关闭",
"vcmi.battleOptions.queueSizeAutoButton.hover": "自动",
"vcmi.battleOptions.queueSizeSmallButton.hover": "小",
"vcmi.battleOptions.queueSizeBigButton.hover": "大",
"vcmi.battleOptions.queueSizeNoneButton.help": "不显示回合顺序指示器",
"vcmi.battleOptions.queueSizeAutoButton.help": "根据游戏的分辨率自动调整回合顺序指示器的大小(游戏处于高度低于700像素的分辨率时,使用小,否则使用大)",
"vcmi.battleOptions.queueSizeSmallButton.help": "设置回合顺序指示器为小",
"vcmi.battleOptions.queueSizeBigButton.help": "设置次寻条为大尺寸(无法在游戏高度像素低于700时生效)",
"vcmi.battleOptions.animationsSpeed1.hover": "",
"vcmi.battleOptions.animationsSpeed5.hover": "",
"vcmi.battleOptions.animationsSpeed6.hover": "",
"vcmi.battleOptions.animationsSpeed1.help": "设置动画速度为非常慢",
"vcmi.battleOptions.animationsSpeed5.help": "设置动画速度为非常快",
"vcmi.battleOptions.animationsSpeed6.help": "设置动画速度为即刻",
"vcmi.battleOptions.touchscreenMode.hover": "触屏模式",
"vcmi.battleOptions.touchscreenMode.help": "{触屏模式}\n\n当启用时,需要进行双击进行确认和执行动作。减少触屏设备误触。",
"vcmi.battleOptions.movementHighlightOnHover.hover": "鼠标悬停高亮单位移动范围",
"vcmi.battleOptions.movementHighlightOnHover.help": "{鼠标悬停高亮单位移动范围}\n\n当你的鼠标悬停在单位上时高亮他的行动范围。",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
"vcmi.battleWindow.pressKeyToSkipIntro" : "按下任意键立即开始战斗",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示所有可以招募的城镇生物",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示所有可以招募的城镇生物}\n\n 显示当前所有可供招募的城镇生物 (左下角)。",
"vcmi.otherOptions.compactTownCreatureInfo.hover": "缩小城镇生物信息",
"vcmi.otherOptions.compactTownCreatureInfo.help": "{缩小城镇生物信息}\n\n 将城镇生物信息最小化.",
"vcmi.battleWindow.damageEstimation.melee" : "近战攻击 %CREATURE (%DAMAGE).",
"vcmi.battleWindow.damageEstimation.meleeKills" : "近战攻击 %CREATURE (%DAMAGE, %KILLS).",
"vcmi.battleWindow.damageEstimation.ranged" : "射击 %CREATURE (%SHOTS, %DAMAGE).",
"vcmi.battleWindow.damageEstimation.rangedKills" : "射击 %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
"vcmi.battleWindow.damageEstimation.shots" : "%d 弹药剩余",
"vcmi.battleWindow.damageEstimation.shots.1" : "%d 弹药剩余",
"vcmi.battleWindow.damageEstimation.damage" : "%d 伤害",
"vcmi.battleWindow.damageEstimation.damage.1" : "%d 伤害",
"vcmi.battleWindow.damageEstimation.kills" : "%d 将被消灭",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭",
"vcmi.townHall.missingBase" : "你必须先建造%s ",
"vcmi.townHall.noCreaturesToRecruit" : "没有可供雇佣的生物。",
"vcmi.townHall.greetingManaVortex" : "当你接近%s时,你的身体充满了新的能量。这使你的魔法值加倍。",
"vcmi.townHall.greetingKnowledge" : "你学习了%s上的图形,并深入了解各种魔法的运作,这使你的知识点数+1。",
"vcmi.townHall.greetingSpellPower" : "%s教你新的方法来集中你的魔法力量,这使你的力量点数+1。",
"vcmi.townHall.greetingExperience" : "访问%s给你提供了更好的学习方法。这使你的经验值+1000。",
"vcmi.townHall.greetingAttack" : "在%s参观后给你提供了更好的战斗技巧,这使你的攻击点数+1。",
"vcmi.townHall.greetingDefence" : "在%s中度过一段时间后,经验丰富的勇士会教你额外的防御技能,这使你的防御点数+1。",
"vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示可招募生物",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示可招募生物}\n\n在城镇摘要(城镇屏幕的左下角)中显示可招募的生物数量,而不是增长。",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "显示生物增长",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{显示生物增长}\n\n在城镇摘要(城镇屏幕的左下角)中显示生物的每周增长而不是可用数量。",
"vcmi.otherOptions.compactTownCreatureInfo.hover": "紧凑生物信息",
"vcmi.otherOptions.compactTownCreatureInfo.help": "{紧凑生物信息}\n\n城镇概要(城镇屏幕的左下角)中城镇生物信息紧凑显示。",
"vcmi.townHall.missingBase" : "必须先建造基础建筑 %s",
"vcmi.townHall.noCreaturesToRecruit" : "没有可供招募的生物。",
"vcmi.townHall.greetingManaVortex" : "接近%s时,你会全身充满活力,并且你的魔法值会加倍。",
"vcmi.townHall.greetingKnowledge" : "你研究了%s的浮雕,洞察了魔法的秘密(知识+1)。",
"vcmi.townHall.greetingSpellPower" : "%s教你如何运用魔法力量(力量+1)。",
"vcmi.townHall.greetingExperience" : "参观%s可以让你学会许多新的技能(经验值+1000)。",
"vcmi.townHall.greetingAttack" : "在%s中稍待片刻可以让你学会更有效的战斗技巧(攻击力+1)。",
"vcmi.townHall.greetingDefence" : "在%s中稍待片刻,富有战斗经验的战士会教你防御技巧(防御力+1)。",
"vcmi.townHall.hasNotProduced" : "本周%s并没有产生什么资源。",
"vcmi.townHall.hasProduced" : "本周%s产生了%d个%s。",
"vcmi.townHall.greetingCustomBonus" : "参观%s后,你的技巧有了提升。这使你受益匪浅。并且使你+%d %s%s",
"vcmi.townHall.greetingCustomBonus" : "%s 赋予你 +%d %s%s",
"vcmi.townHall.greetingCustomUntil" : "直到下一场战斗。",
"vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。",
"vcmi.logicalExpressions.anyOf" : "以下任何前提:",
"vcmi.logicalExpressions.anyOf" : "以下任前提:",
"vcmi.logicalExpressions.allOf" : "以下所有前提:",
"vcmi.logicalExpressions.noneOf" : "无前提:",
"vcmi.heroWindow.openCommander.hover" : "开启指挥官界面",
"vcmi.heroWindow.openCommander.help" : "开启英雄的指挥官界面",
"vcmi.heroWindow.openCommander.help" : "显示该英雄指挥官详细信息",
"vcmi.commanderWindow.artifactMessage" : "你要把这个宝物还给英雄吗?",
"vcmi.creatureWindow.showBonuses.hover" : "属性界面",
"vcmi.creatureWindow.showBonuses.help" : "显示指挥官的所有增强属性",
"vcmi.creatureWindow.showSkills.hover" : "技能页面",
"vcmi.creatureWindow.showSkills.help" : "显示指挥官的所有技能",
"vcmi.creatureWindow.showBonuses.hover" : "属性视图",
"vcmi.creatureWindow.showBonuses.help" : "显示指挥官的所有属性增益",
"vcmi.creatureWindow.showSkills.hover" : "技能视图",
"vcmi.creatureWindow.showSkills.help" : "显示指挥官的所有学习的技能",
"vcmi.creatureWindow.returnArtifact.hover" : "交换宝物",
"vcmi.creatureWindow.returnArtifact.help" : "将宝物还到英雄的背包里",
"vcmi.creatureWindow.returnArtifact.help" : "点击这个按钮将宝物还到英雄的背包里",
"vcmi.questLog.hideComplete.hover" : "隐藏完成任务",
"vcmi.questLog.hideComplete.help" : "隐藏所有完成的任务",
"vcmi.randomMapTab.widgets.defaultTemplate" : "默认",
"vcmi.randomMapTab.widgets.templateLabel" : "格式",
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "设...",
"vcmi.randomMapTab.widgets.templateLabel" : "模板",
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "设...",
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "同盟关系",
"vcmi.randomMapTab.widgets.roadTypesLabel" : "道路类型",
// few strings from WoG used by vcmi
"vcmi.stackExperience.description" : "» 经 验 获 得 明 细 «\n\n生物类型 ................... : %s\n经验等级 ................. : %s (%i)\n经验点数 ............... : %i\n下一个等级所需经验 .. : %i\n每次战斗最大获得经验 ... : %i%% (%i)\n获得经验的生物数量 .... : %i\n最大招募数量\n不会丢失经验升级 .... : %i\n经验倍数 ........... : %.2f\n升级倍数 .............. : %.2f\n10级后经验值 ........ : %i\n最大招募数量下\n 升级到10级所需经验数量: %i",
@ -130,154 +169,148 @@
"vcmi.stackExperience.rank.9" : "中校 10级",
"vcmi.stackExperience.rank.10" : "上校 11级",
"core.bonus.ADDITIONAL_ATTACK.name": "双击",
"core.bonus.ADDITIONAL_ATTACK.description": "可以攻击两次",
"core.bonus.ADDITIONAL_ATTACK.name": "双重攻击",
"core.bonus.ADDITIONAL_ATTACK.description": "攻击两次",
"core.bonus.ADDITIONAL_RETALIATION.name": "额外反击",
"core.bonus.ADDITIONAL_RETALIATION.description": "可以额外反击 ${val} 次",
"core.bonus.ADDITIONAL_RETALIATION.description": "每回合额外获得${val}次反击机会",
"core.bonus.AIR_IMMUNITY.name": "气系免疫",
"core.bonus.AIR_IMMUNITY.description": "免疫所有气系魔法",
"core.bonus.ATTACKS_ALL_ADJACENT.name": "环击",
"core.bonus.ATTACKS_ALL_ADJACENT.description": "攻击所有相邻部队",
"core.bonus.ATTACKS_ALL_ADJACENT.description": "攻击所有相邻敌人",
"core.bonus.BLOCKS_RETALIATION.name": "无反击",
"core.bonus.BLOCKS_RETALIATION.description": "敌人无法反击",
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "远程无反击",
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "敌人无法对射击进行反击",
"core.bonus.CATAPULT.name": "攻城",
"core.bonus.CATAPULT.description": "可以攻击城墙",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "施法消耗 - (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "减少英雄施法消耗",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "对方施法消耗 + (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "增加对方施法消耗",
"core.bonus.CHARGE_IMMUNITY.name": "I免疫冲锋",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "施法消耗减少 (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "英雄施法消耗魔法值减少${val}点",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "施法阻碍 (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "敌方施法消耗的魔法值增加${val}点",
"core.bonus.CHARGE_IMMUNITY.name": "免疫冲锋",
"core.bonus.CHARGE_IMMUNITY.description": "对冲锋特技的额外伤害免疫",
"core.bonus.DARKNESS.name": "黑暗天幕",
"core.bonus.DARKNESS.description": "增加 ${val} 半径黑幕",
"core.bonus.DARKNESS.name": "阴影庇体",
"core.bonus.DARKNESS.description": "创建${val}半径黑幕",
"core.bonus.DEATH_STARE.name": "死亡凝视 (${val}%)",
"core.bonus.DEATH_STARE.description": "${val}% 几率直接杀死生物",
"core.bonus.DEFENSIVE_STANCE.name": "防御奖励",
"core.bonus.DEFENSIVE_STANCE.description": "当选择防御时+${val} 防御力",
"core.bonus.DEATH_STARE.description": "${val}%几率直接杀死一单位生物",
"core.bonus.DEFENSIVE_STANCE.name": "防御增效",
"core.bonus.DEFENSIVE_STANCE.description": "当选择防御时+${val}防御力",
"core.bonus.DESTRUCTION.name": "毁灭",
"core.bonus.DESTRUCTION.description": "有${val}% 杀死额外数量的部队",
"core.bonus.DESTRUCTION.description": "${val}%几率在攻击后追加消灭数量",
"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "致命一击",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% 几率造成双倍伤害",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}%几率造成双倍基础伤害",
"core.bonus.DRAGON_NATURE.name": "龙",
"core.bonus.DRAGON_NATURE.description": "生物属于龙类",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "魔法免疫",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "对魔法伤害免疫",
"core.bonus.DRAGON_NATURE.description": "生物拥有龙的特性",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "魔法伤免疫",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "免疫直接造成伤害的魔法",
"core.bonus.EARTH_IMMUNITY.name": "土系免疫",
"core.bonus.EARTH_IMMUNITY.description": "免疫所有土系魔法",
"core.bonus.ENCHANTER.name": "施法者",
"core.bonus.ENCHANTER.description": "每回合群体施放 ${subtype.spell} ",
"core.bonus.ENCHANTED.name": "魔法身",
"core.bonus.ENCHANTED.description": "自身被 ${subtype.spell} 魔法影响",
"core.bonus.ENCHANTER.name": "强化师",
"core.bonus.ENCHANTER.description": "每回合群体施放${subtype.spell}",
"core.bonus.ENCHANTED.name": "魔法身",
"core.bonus.ENCHANTED.description": "永久处于${subtype.spell}影响",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "攻击时忽略对方部分防御力",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "当攻击时,目标生物${val}%的防御力将被无视。",
"core.bonus.FIRE_IMMUNITY.name": "火系免疫",
"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法",
"core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)",
"core.bonus.FIRE_SHIELD.description": "拥有烈火神盾护身",
"core.bonus.FIRE_SHIELD.description": "反弹部分受到的近战伤害",
"core.bonus.FIRST_STRIKE.name": "抢先攻击",
"core.bonus.FIRST_STRIKE.description": "在被反击前做出攻击",
"core.bonus.FIRST_STRIKE.description": "该生物的反击将会在被攻击前进行",
"core.bonus.FEAR.name": "恐惧",
"core.bonus.FEAR.description": "引起恐惧",
"core.bonus.FEAR.description": "使得敌方一只部队恐惧",
"core.bonus.FEARLESS.name": "无惧",
"core.bonus.FEARLESS.description": "免疫恐惧",
"core.bonus.FLYING.name": "飞行兵种",
"core.bonus.FLYING.description": "生物可以飞行",
"core.bonus.FEARLESS.description": "免疫恐惧特质",
"core.bonus.FLYING.name": "飞行",
"core.bonus.FLYING.description": "以飞行的方式移动(无视障碍)",
"core.bonus.FREE_SHOOTING.name": "近身射击",
"core.bonus.FREE_SHOOTING.description": "靠近敌方也能射击",
"core.bonus.FULL_HP_REGENERATION.name": "重生",
"core.bonus.FULL_HP_REGENERATION.description": "可以自动恢复所有生命值",
"core.bonus.GARGOYLE.name": "石像鬼属性",
"core.bonus.FREE_SHOOTING.description": "能在近战范围内进行射击",
"core.bonus.GARGOYLE.name": "石像鬼",
"core.bonus.GARGOYLE.description": "不能被复活或治疗",
"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "减少伤害 (${val}%)",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "受攻击时减少受的伤害",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "减少从远程和近战中遭受的物理伤害",
"core.bonus.HATE.name": "${subtype.creature}的死敌",
"core.bonus.HATE.description": "对该部队造成 ${val}% 的额外伤害",
"core.bonus.HEALER.name": "治疗",
"core.bonus.HATE.description": "对${subtype.creature}造成额外${val}%伤害",
"core.bonus.HEALER.name": "治疗",
"core.bonus.HEALER.description": "可以治疗友军单位",
"core.bonus.HP_REGENERATION.name": "重生",
"core.bonus.HP_REGENERATION.description": "每回合恢复 ${val} 点生命值",
"core.bonus.JOUSTING.name": "冲锋",
"core.bonus.JOUSTING.description": "每格行动增加+5%伤害",
"core.bonus.KING1.name": "一般顶级怪物",
"core.bonus.KING1.description": "被初级屠戮成性影响",
"core.bonus.KING2.name": "智慧顶级怪物",
"core.bonus.KING2.description": "被中级屠戮成性影响",
"core.bonus.KING3.name": "精神顶级怪物",
"core.bonus.KING3.description":"被高级屠戮成性影响",
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "免疫 1-${val} 级魔法",
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "免疫等级为 1-${val} 级的所有魔法",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "半程射击",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "超过 ${val} 格不能射击",
"core.bonus.HP_REGENERATION.name": "再生",
"core.bonus.HP_REGENERATION.description": "每回合恢复${SHval}点生命值",
"core.bonus.JOUSTING.name": "英勇冲锋",
"core.bonus.JOUSTING.description": "每移动一格 +${val}%伤害",
"core.bonus.KING.name": "王牌",
"core.bonus.KING.description": "受${val}级或更高级屠戮成性影响",
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "免疫1-${val}级魔法",
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "免疫1-${val}级的魔法",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "受限射击距离",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "无法以${val}格外的单位为射击目标",
"core.bonus.LIFE_DRAIN.name": "吸取生命 (${val}%)",
"core.bonus.LIFE_DRAIN.description": "吸取 ${val}% 伤害回复自身",
"core.bonus.MANA_CHANNELING.name": "偷取魔法 ${val}%",
"core.bonus.MANA_CHANNELING.description": "偷取部分敌人施法消耗",
"core.bonus.LIFE_DRAIN.description": "吸取${val}%伤害回复自身",
"core.bonus.MANA_CHANNELING.name": "魔法通道${val}%",
"core.bonus.MANA_CHANNELING.description": "使你的英雄有${val}%几率获得敌人施法的魔法值",
"core.bonus.MANA_DRAIN.name": "吸取魔力",
"core.bonus.MANA_DRAIN.description": "每回合吸取 ${val} 魔法值",
"core.bonus.MAGIC_MIRROR.name": "带有魔法神镜 (${val}%)",
"core.bonus.MAGIC_MIRROR.description": "${val}% 几率反射魔法",
"core.bonus.MAGIC_RESISTANCE.name": "(${MR}%) 魔法抵抗",
"core.bonus.MAGIC_RESISTANCE.description": "${MR}% 几率抵抗敌人的魔法",
"core.bonus.MANA_DRAIN.description": "每回合吸取${val}魔法值",
"core.bonus.MAGIC_MIRROR.name": "魔法神镜 (${val}%)",
"core.bonus.MAGIC_MIRROR.description": "${val}%几率将进攻性魔法导向一个敌人单位",
"core.bonus.MAGIC_RESISTANCE.name": "魔法抵抗 (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description": "${val}%几率抵抗敌人的魔法",
"core.bonus.MIND_IMMUNITY.name": "免疫心智",
"core.bonus.MIND_IMMUNITY.description": "不受心智魔法的影响",
"core.bonus.NO_DISTANCE_PENALTY.name": "无障碍射击",
"core.bonus.NO_DISTANCE_PENALTY.description": "射击不受距离影响",
"core.bonus.NO_DISTANCE_PENALTY.name": "无视距离惩罚",
"core.bonus.NO_DISTANCE_PENALTY.description": "任意距离均造成全额伤害",
"core.bonus.NO_MELEE_PENALTY.name": "无近战惩罚",
"core.bonus.NO_MELEE_PENALTY.description": "近战伤害不减",
"core.bonus.NO_MELEE_PENALTY.description": "该生物没有近战伤害惩罚",
"core.bonus.NO_MORALE.name": "无士气",
"core.bonus.NO_MORALE.description": "生物不受士气影响",
"core.bonus.NO_WALL_PENALTY.name": "无城墙影响",
"core.bonus.NO_WALL_PENALTY.description": "射击不受城墙的影响",
"core.bonus.NO_WALL_PENALTY.description": "攻城战中被城墙阻挡造成全额伤害",
"core.bonus.NON_LIVING.name": "无生命",
"core.bonus.NON_LIVING.description": "不受只对生命实体生物有效的魔法",
"core.bonus.NON_LIVING.description": "免疫大多数的效果",
"core.bonus.RANDOM_SPELLCASTER.name": "随机施法",
"core.bonus.RANDOM_SPELLCASTER.description": "随机施放增益魔法",
"core.bonus.RANDOM_SPELLCASTER.description": "可以施放随机魔法",
"core.bonus.RANGED_RETALIATION.name": "远程反击",
"core.bonus.RANGED_RETALIATION.description": "可以对远程攻击进行反击",
"core.bonus.RECEPTIVE.name": "接受有益魔法",
"core.bonus.RECEPTIVE.description": "不会免疫有益魔法",
"core.bonus.RECEPTIVE.name": "适应",
"core.bonus.RECEPTIVE.description": "不会免疫有益魔法",
"core.bonus.REBIRTH.name": "复生 (${val}%)",
"core.bonus.REBIRTH.description": "{val}% 数量死亡后会复活",
"core.bonus.RETURN_AFTER_STRIKE.name": "攻击返回",
"core.bonus.RETURN_AFTER_STRIKE.description": "攻击后回到初始位置",
"core.bonus.SHOOTER.name": "射手",
"core.bonus.SHOOTER.description": "生物可以设计",
"core.bonus.REBIRTH.description": "当整支部队死亡后${val}%会复活",
"core.bonus.RETURN_AFTER_STRIKE.name": "攻击返回",
"core.bonus.RETURN_AFTER_STRIKE.description": "近战攻击后回到初始位置",
"core.bonus.SHOOTER.name": "远程攻击",
"core.bonus.SHOOTER.description": "生物可以射击",
"core.bonus.SHOOTS_ALL_ADJACENT.name": "范围远程攻击",
"core.bonus.SHOOTS_ALL_ADJACENT.description": "远程攻击可伤害范围内的多个目标",
"core.bonus.SOUL_STEAL.name": "杀死敌人复生",
"core.bonus.SOUL_STEAL.description": "当杀死敌人时获得 ${val} 数量",
"core.bonus.SHOOTS_ALL_ADJACENT.description": "该生物的远程攻击将同时命中小范围内所有目标",
"core.bonus.SOUL_STEAL.name": "灵魂窃取",
"core.bonus.SOUL_STEAL.description": "每杀死一个敌人将获得${val}数量的生物",
"core.bonus.SPELLCASTER.name": "施法者",
"core.bonus.SPELLCASTER.description": "生物可以施放 ${subtype.spell}",
"core.bonus.SPELLCASTER.description": "生物可以施放${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.name": "攻击后施法",
"core.bonus.SPELL_AFTER_ATTACK.description": "${val}% 攻击后施放 ${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.description": "攻击后${val}%几率施放${subtype.spell}魔法",
"core.bonus.SPELL_BEFORE_ATTACK.name": "攻击前施法",
"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% 攻击前施放 ${subtype.spell}",
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "魔法伤害抵抗",
"core.bonus.SPELL_DAMAGE_REDUCTION.description": "受魔法攻击时伤害减少 ${val}%",
"core.bonus.SPELL_BEFORE_ATTACK.description": "攻击前${val}%几率施放${subtype.spell}魔法",
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "魔法伤害减免",
"core.bonus.SPELL_DAMAGE_REDUCTION.description": "受魔法攻击时伤害减少${val}%",
"core.bonus.SPELL_IMMUNITY.name": "特定魔法免疫",
"core.bonus.SPELL_IMMUNITY.description": "免疫 ${subtype.spell}",
"core.bonus.SPELL_LIKE_ATTACK.name": "法攻击",
"core.bonus.SPELL_LIKE_ATTACK.description": "攻击时使用 ${subtype.spell}",
"core.bonus.SPELL_IMMUNITY.description": "免疫${subtype.spell}魔法",
"core.bonus.SPELL_LIKE_ATTACK.name": "类施法攻击",
"core.bonus.SPELL_LIKE_ATTACK.description": "攻击时使用${subtype.spell}",
"core.bonus.SPELL_RESISTANCE_AURA.name": "抗魔光环",
"core.bonus.SPELL_RESISTANCE_AURA.description": "邻近部队获得 ${val}% 魔法抵抗",
"core.bonus.SPELL_RESISTANCE_AURA.description": "邻近部队获得${val}%几率抵抗魔法",
"core.bonus.SUMMON_GUARDIANS.name": "召唤守卫",
"core.bonus.SUMMON_GUARDIANS.description": "战斗前召唤 ${subtype.creature} (${val}%)",
"core.bonus.SYNERGY_TARGET.name": "可协助攻击",
"core.bonus.SUMMON_GUARDIANS.description": "战斗开始时召唤${subtype.creature}(${val}%)",
"core.bonus.SYNERGY_TARGET.name": "协同攻击",
"core.bonus.SYNERGY_TARGET.description": "生物受到协助攻击的影响",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "息",
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "吐息攻击2个部队",
"core.bonus.THREE_HEADED_ATTACK.name": "半环击",
"core.bonus.THREE_HEADED_ATTACK.description": "攻击正前方多个敌人",
"core.bonus.TRANSMUTATION.name": "变",
"core.bonus.TRANSMUTATION.description": "${val}% 机会将敌人变成其他生物",
"core.bonus.UNDEAD.name": "不死生物",
"core.bonus.UNDEAD.description": "生物有丧尸属性",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "息",
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "吐息攻击(2格范围)",
"core.bonus.THREE_HEADED_ATTACK.name": "三头攻击",
"core.bonus.THREE_HEADED_ATTACK.description": "攻击三格邻接单位",
"core.bonus.TRANSMUTATION.name": "变形术",
"core.bonus.TRANSMUTATION.description": "${val}%机会将被攻击单位变成其他生物",
"core.bonus.UNDEAD.name": "亡灵",
"core.bonus.UNDEAD.description": "该生物属于亡灵",
"core.bonus.UNLIMITED_RETALIATIONS.name": "无限反击",
"core.bonus.UNLIMITED_RETALIATIONS.description": "每回合可以无限反击敌人",
"core.bonus.WATER_IMMUNITY.name": "水系免疫",
"core.bonus.WATER_IMMUNITY.description": "免疫水系魔法",
"core.bonus.WIDE_BREATH.name": "弧形焰息",
"core.bonus.WIDE_BREATH.description": "吐息攻击前方扇形6个部队"
"core.bonus.WIDE_BREATH.description": "大范围喷吐攻击(目标左右以及后方共6格)"
}

View File

@ -21,6 +21,15 @@
"vcmi.adventureMap.moveCostDetails" : "Movement points - Cost: %TURNS turns + %POINTS points, Remaining points: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Movement points - Cost: %POINTS points, Remaining points: %REMAINING",
"vcmi.capitalColors.0" : "Red",
"vcmi.capitalColors.1" : "Blue",
"vcmi.capitalColors.2" : "Tan",
"vcmi.capitalColors.3" : "Green",
"vcmi.capitalColors.4" : "Orange",
"vcmi.capitalColors.5" : "Purple",
"vcmi.capitalColors.6" : "Teal",
"vcmi.capitalColors.7" : "Pink",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
"vcmi.server.errors.modsIncompatibility" : "The following mods are required to load the game:",
"vcmi.server.confirmReconnect" : "Do you want to reconnect to the last session?",
@ -225,7 +234,7 @@
"core.bonus.HEALER.name": "Healer",
"core.bonus.HEALER.description": "Heals allied units",
"core.bonus.HP_REGENERATION.name": "Regeneration",
"core.bonus.HP_REGENERATION.description": "Heals ${SHval} hit points every round",
"core.bonus.HP_REGENERATION.description": "Heals ${val} hit points every round",
"core.bonus.JOUSTING.name": "Champion charge",
"core.bonus.JOUSTING.description": "+${val}% damage for each hex travelled",
"core.bonus.KING.name": "King",

View File

@ -21,6 +21,15 @@
"vcmi.adventureMap.moveCostDetails" : "Bewegungspunkte - Kosten: %TURNS Runden + %POINTS Punkte, Verbleibende Punkte: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Bewegungspunkte - Kosten: %POINTS Punkte, Verbleibende Punkte: %REMAINING",
"vcmi.capitalColors.0" : "Rot",
"vcmi.capitalColors.1" : "Blau",
"vcmi.capitalColors.2" : "Braun",
"vcmi.capitalColors.3" : "Grün",
"vcmi.capitalColors.4" : "Orange",
"vcmi.capitalColors.5" : "Violett",
"vcmi.capitalColors.6" : "Türkis",
"vcmi.capitalColors.7" : "Rosa",
"vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
"vcmi.server.errors.modsIncompatibility" : "Erforderliche Mods um das Spiel zu laden:",
"vcmi.server.confirmReconnect" : "Mit der letzten Sitzung verbinden?",
@ -220,7 +229,7 @@
"core.bonus.HEALER.name": "Heiler",
"core.bonus.HEALER.description": "Heilt verbündete Einheiten",
"core.bonus.HP_REGENERATION.name": "Regeneration",
"core.bonus.HP_REGENERATION.description": "Heilt ${SHval} Trefferpunkte jede Runde",
"core.bonus.HP_REGENERATION.description": "Heilt ${val} Trefferpunkte jede Runde",
"core.bonus.JOUSTING.name": "Champion Charge",
"core.bonus.JOUSTING.description": "+${val}% Schaden pro zurückgelegtem Feld",
"core.bonus.KING.name": "König",

View File

@ -208,7 +208,7 @@
"core.bonus.HEALER.name": "Uzdrowiciel",
"core.bonus.HEALER.description": "Leczy sprzymierzone jednostki",
"core.bonus.HP_REGENERATION.name": "Regeneracja",
"core.bonus.HP_REGENERATION.description": "Leczy ${SHval} punktów zdrowia każdej rundy",
"core.bonus.HP_REGENERATION.description": "Leczy ${val} punktów zdrowia każdej rundy",
"core.bonus.JOUSTING.name": "Szarża Czempiona",
"core.bonus.JOUSTING.description": "+${val}% obrażeń na przebytego heksa",
"core.bonus.KING.name": "Król",

View File

@ -112,9 +112,8 @@
"matchTerrainToTown" : false,
"treasure" :
[
{ "min" : 0, "max" : 0, "density" : 1 },
{ "min" : 0, "max" : 0, "density" : 1 },
{ "min" : 0, "max" : 0, "density" : 1 }
{ "min" : 100, "max" : 1000, "density" : 10 },
{ "min" : 1000, "max" : 3000, "density" : 1 }
]
},
"12" :

View File

@ -232,7 +232,7 @@
"core.bonus.HEALER.name": "Целитель",
"core.bonus.HEALER.description": "Исцеляет дружественные юниты",
"core.bonus.HP_REGENERATION.name": "Регенерация",
"core.bonus.HP_REGENERATION.description": "Исцеляет ${SHval} очков здоровья каждый ход",
"core.bonus.HP_REGENERATION.description": "Исцеляет ${val} очков здоровья каждый ход",
"core.bonus.JOUSTING.name": "Разгон",
"core.bonus.JOUSTING.description": "+${val}% урона за каждую пройденную клетку",
"core.bonus.KING.name": "Король",

View File

@ -208,7 +208,7 @@
"core.bonus.HEALER.name" : "Цілитель",
"core.bonus.HEALER.description" : "Лікує союзників",
"core.bonus.HP_REGENERATION.name" : "Регенерація",
"core.bonus.HP_REGENERATION.description" : "Відновлює ${SHval} очок здоров'я кожного раунду",
"core.bonus.HP_REGENERATION.description" : "Відновлює ${val} очок здоров'я кожного раунду",
"core.bonus.JOUSTING.name" : "Турнірна перевага",
"core.bonus.JOUSTING.description" : "+${val}% шкоди за кожен пройдений гекс",
"core.bonus.KING.name" : "Король",

View File

@ -1,5 +1,6 @@
[![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.2.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.2.1)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.2.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.2.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project

View File

@ -10,14 +10,16 @@ android {
applicationId "is.xyz.vcmi"
minSdk 19
targetSdk 31
versionCode 1103
versionName "1.1"
versionCode 1201
versionName "1.2.1"
setProperty("archivesBaseName", "vcmi")
}
signingConfigs {
releaseSigning
LoadSigningConfig()
dailySigning
LoadSigningConfig("releaseSigning")
LoadSigningConfig("dailySigning")
}
buildTypes {
@ -46,6 +48,7 @@ android {
daily {
initWith release
applicationIdSuffix '.daily'
signingConfig signingConfigs.dailySigning
manifestPlaceholders = [
applicationLabel: 'VCMI daily',
]
@ -118,38 +121,48 @@ def ResolveGitInfo() {
CommandOutput("git", ["describe", "--match=", "--always", "--abbrev=7"], ".")
}
def SigningPropertiesPath(final basePath) {
return file("${basePath}/signing.properties")
def SigningPropertiesPath(final basePath, final signingConfigKey) {
return file("${basePath}/${signingConfigKey}.properties")
}
def SigningKeystorePath(final basePath, final keystoreFileName) {
return file("${basePath}/${keystoreFileName}")
}
def LoadSigningConfig() {
def LoadSigningConfig(final signingConfigKey) {
final def projectRoot = "${project.projectDir}/../../CI/android"
final def props = new Properties()
final def propFile = SigningPropertiesPath(projectRoot)
final def propFile = SigningPropertiesPath(projectRoot, signingConfigKey)
def signingConfig = android.signingConfigs.getAt(signingConfigKey)
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null
&& props.containsKey('STORE_FILE')
&& props.containsKey('STORE_PASSWORD')
&& props.containsKey('KEY_ALIAS')
&& props.containsKey('KEY_PASSWORD')) {
&& props.containsKey('KEY_ALIAS')) {
android.signingConfigs.releaseSigning.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE'])
android.signingConfigs.releaseSigning.storePassword = props['STORE_PASSWORD']
android.signingConfigs.releaseSigning.keyAlias = props['KEY_ALIAS']
android.signingConfigs.releaseSigning.keyPassword = props['KEY_PASSWORD']
signingConfig.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE'])
signingConfig.storePassword = props['STORE_PASSWORD']
signingConfig.keyAlias = props['KEY_ALIAS']
if(props.containsKey('STORE_PASSWORD'))
signingConfig.storePassword = props['STORE_PASSWORD']
else
signingConfig.storePassword = System.getenv("ANDROID_STORE_PASSWORD")
if(props.containsKey('KEY_PASSWORD'))
signingConfig.keyPassword = props['KEY_PASSWORD']
else
signingConfig.keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
} else {
println("Some props from signing file are missing")
android.buildTypes.release.signingConfig = null
android.signingConfigs.putAt(signingConfigKey, null)
}
} else {
println("file with signing properties is missing")
android.buildTypes.release.signingConfig = null
android.signingConfigs.putAt(signingConfigKey, null)
}
}

View File

@ -3,7 +3,7 @@ set(client_SRCS
../CCallback.cpp
adventureMap/CAdvMapPanel.cpp
adventureMap/CAdvMapInt.cpp
adventureMap/CAdventureMapInterface.cpp
adventureMap/CAdventureOptions.cpp
adventureMap/CInGameConsole.cpp
adventureMap/CInfoBar.cpp
@ -31,6 +31,7 @@ set(client_SRCS
gui/CursorHandler.cpp
gui/InterfaceObjectConfigurable.cpp
gui/NotificationHandler.cpp
gui/ShortcutHandler.cpp
lobby/CBonusSelection.cpp
lobby/CCampaignInfoScreen.cpp
@ -85,6 +86,12 @@ set(client_SRCS
widgets/MiscWidgets.cpp
widgets/ObjectLists.cpp
widgets/TextControls.cpp
widgets/CArtifactsOfHeroBase.cpp
widgets/CArtifactsOfHeroMain.cpp
widgets/CArtifactsOfHeroKingdom.cpp
widgets/CArtifactsOfHeroAltar.cpp
widgets/CArtifactsOfHeroMarket.cpp
widgets/CWindowWithArtifacts.cpp
windows/CCastleInterface.cpp
windows/CCreatureWindow.cpp
@ -110,6 +117,7 @@ set(client_SRCS
CMT.cpp
CMusicHandler.cpp
CPlayerInterface.cpp
PlayerLocalState.cpp
CServerHandler.cpp
CVideoHandler.cpp
Client.cpp
@ -122,7 +130,7 @@ set(client_HEADERS
StdInc.h
adventureMap/CAdvMapPanel.h
adventureMap/CAdvMapInt.h
adventureMap/CAdventureMapInterface.h
adventureMap/CAdventureOptions.h
adventureMap/CInGameConsole.h
adventureMap/CInfoBar.h
@ -152,6 +160,8 @@ set(client_HEADERS
gui/InterfaceObjectConfigurable.h
gui/MouseButton.h
gui/NotificationHandler.h
gui/Shortcut.h
gui/ShortcutHandler.h
gui/TextAlignment.h
lobby/CBonusSelection.h
@ -213,6 +223,12 @@ set(client_HEADERS
widgets/MiscWidgets.h
widgets/ObjectLists.h
widgets/TextControls.h
widgets/CArtifactsOfHeroBase.h
widgets/CArtifactsOfHeroMain.h
widgets/CArtifactsOfHeroKingdom.h
widgets/CArtifactsOfHeroAltar.h
widgets/CArtifactsOfHeroMarket.h
widgets/CWindowWithArtifacts.h
windows/CCastleInterface.h
windows/CCreatureWindow.h
@ -238,6 +254,7 @@ set(client_HEADERS
CMT.h
CMusicHandler.h
CPlayerInterface.h
PlayerLocalState.h
CServerHandler.h
CVideoHandler.h
Client.h

View File

@ -14,7 +14,7 @@
struct _Mix_Music;
struct SDL_RWops;
typedef struct _Mix_Music Mix_Music;
using Mix_Music = struct _Mix_Music;
struct Mix_Chunk;
class CAudioBase {

File diff suppressed because it is too large Load Diff

View File

@ -9,16 +9,10 @@
*/
#pragma once
#include "../lib/FunctionList.h"
#include "../lib/CGameInterface.h"
#include "../lib/NetPacksBase.h"
#include "gui/CIntObject.h"
#ifdef __GNUC__
#define sprintf_s snprintf
#endif
VCMI_LIB_NAMESPACE_BEGIN
class Artifact;
@ -37,7 +31,7 @@ struct CPathsInfo;
VCMI_LIB_NAMESPACE_END
class CButton;
class CAdvMapInt;
class CAdventureMapInterface;
class CCastleInterface;
class BattleInterface;
class CComponent;
@ -51,103 +45,63 @@ class ClickableR;
class Hoverable;
class KeyInterested;
class MotionInterested;
class PlayerLocalState;
class TimeInterested;
class IShowable;
struct SDL_Surface;
union SDL_Event;
namespace boost
{
class mutex;
class recursive_mutex;
}
class CPlayerInterface;
class HeroPathStorage
{
CPlayerInterface & owner;
std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
public:
explicit HeroPathStorage(CPlayerInterface &owner);
void setPath(const CGHeroInstance *h, const CGPath & path);
bool setPath(const CGHeroInstance *h, const int3 & destination);
const CGPath & getPath(const CGHeroInstance *h) const;
bool hasPath(const CGHeroInstance *h) const;
void removeLastNode(const CGHeroInstance *h);
void erasePath(const CGHeroInstance *h);
void verifyPath(const CGHeroInstance *h);
template <typename Handler>
void serialize( Handler &h, int version );
};
/// Central class for managing user interface logic
class CPlayerInterface : public CGameInterface, public IUpdateable
{
public:
HeroPathStorage paths;
bool duringMovement;
bool ignoreEvents;
size_t numOfMovedArts;
// -1 - just loaded game; 1 - just started game; 0 otherwise
int firstCall;
int autosaveCount;
static const int SAVES_COUNT = 5;
std::pair<const CCreatureSet *, const CCreatureSet *> lastBattleArmies;
bool allowBattleReplay = false;
std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
const BattleAction *curAction; //during the battle - action currently performed by active stack (or nullptr)
std::shared_ptr<Environment> env;
ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
int3 destinationTeleportPos;
public: // TODO: make private
std::shared_ptr<Environment> env;
std::unique_ptr<PlayerLocalState> localState;
//minor interfaces
CondSh<bool> *showingDialog; //indicates if dialog box is displayed
static boost::recursive_mutex *pim;
bool makingTurn; //if player is already making his turn
int firstCall; // -1 - just loaded game; 1 - just started game; 0 otherwise
int autosaveCount;
static const int SAVES_COUNT = 5;
CCastleInterface * castleInt; //nullptr if castle window isn't opened
static std::shared_ptr<BattleInterface> battleInt; //nullptr if no battle
CInGameConsole * cingconsole;
std::shared_ptr<CCallback> cb; //to communicate with engine
const BattleAction *curAction; //during the battle - action currently performed by active stack (or nullptr)
std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones)
std::vector<const CGTownInstance *> towns; //our towns on the adventure map
std::vector<const CGHeroInstance *> sleepingHeroes; //if hero is in here, he's sleeping
//During battle is quick combat mode is used
std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
bool allowBattleReplay = false;
std::pair<const CCreatureSet *, const CCreatureSet *> lastBattleArmies;
struct SpellbookLastSetting
{
int spellbookLastPageBattle, spellbokLastPageAdvmap; //on which page we left spellbook
int spellbookLastTabBattle, spellbookLastTabAdvmap; //on which page we left spellbook
SpellbookLastSetting();
template <typename Handler> void serialize( Handler &h, const int version )
{
h & spellbookLastPageBattle;
h & spellbokLastPageAdvmap;
h & spellbookLastTabBattle;
h & spellbookLastTabAdvmap;
}
} spellbookSettings;
protected: // Call-ins from server, should not be called directly, but only via GameInterface
void update() override;
void initializeHeroTownList();
int getLastIndex(std::string namePrefix);
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
//overridden funcs from CGameInterface
void garrisonsChanged(ObjectInstanceID id1, ObjectInstanceID id2) override;
void buildChanged(const CGTownInstance *town, BuildingID buildingID, int what) override; //what: 1 - built, 2 - demolished
void artifactPut(const ArtifactLocation &al) override;
@ -172,22 +126,16 @@ public:
void receivedResource() override;
void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
void showRecruitmentDialog(const CGDwelling *dwelling, const CArmedInstance *dst, int level) override;
void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard;
void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
void showPuzzleMap() override;
void viewWorldMap() override;
void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor) override;
void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor) override;
void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
void showTavernWindow(const CGObjectInstance *townOrTavern) override;
void showThievesGuildWindow (const CGObjectInstance * obj) override;
void showQuestLog() override;
void advmapSpellCast(const CGHeroInstance * caster, int spellID) override; //called when a hero casts a spell
void tileHidden(const std::unordered_set<int3, ShashInt3> &pos) override; //called when given tiles become hidden under fog of war
void tileRevealed(const std::unordered_set<int3, ShashInt3> &pos) override; //called when fog of war disappears from given tiles
void tileHidden(const std::unordered_set<int3> &pos) override; //called when given tiles become hidden under fog of war
void tileRevealed(const std::unordered_set<int3> &pos) override; //called when fog of war disappears from given tiles
void newObject(const CGObjectInstance * obj) override;
void availableArtifactsChanged(const CGBlackMarket *bm = nullptr) override; //bm may be nullptr, then artifacts are changed in the global pool (used by merchants in towns)
void yourTurn() override;
@ -230,21 +178,23 @@ public:
void yourTacticPhase(int distance) override;
void forceEndTacticPhase() override;
//-------------//
public: // public interface for use by client via LOCPLINT access
// part of GameInterface that is also used by client code
void showPuzzleMap() override;
void viewWorldMap() override;
void showQuestLog() override;
void showThievesGuildWindow (const CGObjectInstance * obj) override;
void showTavernWindow(const CGObjectInstance *townOrTavern) override;
void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard;
void showHeroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2);
void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes);
void garrisonsChanged(std::vector<const CGObjectInstance *> objs);
void garrisonChanged(const CGObjectInstance * obj);
void heroKilled(const CGHeroInstance* hero);
void waitWhileDialog(bool unlockPim = true);
void waitForAllDialogs(bool unlockPim = true);
void redrawHeroWin(const CGHeroInstance * hero);
void openTownWindow(const CGTownInstance * town); //shows townscreen
void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero
void updateInfo(const CGObjectInstance * specific);
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
void activateForSpectator(); // TODO: spectator probably need own player interface class
// show dialogs
void showInfoDialog(const std::string &text, std::shared_ptr<CComponent> component);
void showInfoDialog(const std::string &text, const std::vector<std::shared_ptr<CComponent>> & components = std::vector<std::shared_ptr<CComponent>>(), int soundID = 0);
void showInfoDialogAndWait(std::vector<Component> & components, const MetaString & text);
@ -253,10 +203,8 @@ public:
void stopMovement();
void moveHero(const CGHeroInstance *h, const CGPath& path);
void acceptTurn(); //used during hot seat after your turn message is close
void tryDiggging(const CGHeroInstance *h);
void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
void requestReturningToMainMenu(bool won);
void proposeLoadingGame();
///returns true if all events are processed internally
@ -266,11 +214,6 @@ public:
~CPlayerInterface();
private:
template <typename Handler> void serializeTempl(Handler &h, const int version);
private:
struct IgnoreEvents
{
CPlayerInterface & owner;
@ -285,14 +228,18 @@ private:
};
bool duringMovement;
bool ignoreEvents;
size_t numOfMovedArts;
void heroKilled(const CGHeroInstance* hero);
void garrisonsChanged(std::vector<const CGObjectInstance *> objs);
void requestReturningToMainMenu(bool won);
void acceptTurn(); //used during hot seat after your turn message is close
void initializeHeroTownList();
int getLastIndex(std::string namePrefix);
void doMoveHero(const CGHeroInstance *h, CGPath path);
void setMovementStatus(bool value);
/// Performs autosave, if needed according to settings
void performAutosave();
};
/// Provides global access to instance of interface of currently active player
extern CPlayerInterface * LOCPLINT;

View File

@ -15,7 +15,7 @@
#include "CPlayerInterface.h"
#include "CServerHandler.h"
#include "ClientNetPackVisitors.h"
#include "adventureMap/CAdvMapInt.h"
#include "adventureMap/CAdventureMapInterface.h"
#include "battle/BattleInterface.h"
#include "gui/CGuiHandler.h"
#include "mapView/mapHandler.h"
@ -374,6 +374,7 @@ void CClient::endGame()
//threads cleanup has to be after gs cleanup and before battleints cleanup to stop tacticThread
cleanThreads();
CPlayerInterface::battleInt.reset();
playerint.clear();
battleints.clear();
battleCallbacks.clear();

View File

@ -61,8 +61,8 @@ namespace boost { class thread; }
template<typename T>
class ThreadSafeVector
{
typedef std::vector<T> TVector;
typedef boost::unique_lock<boost::mutex> TLock;
using TVector = std::vector<T>;
using TLock = boost::unique_lock<boost::mutex>;
TVector items;
boost::mutex mx;
boost::condition_variable cond;
@ -235,7 +235,7 @@ public:
void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {};
void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
void changeFogOfWar(std::unordered_set<int3, ShashInt3> & tiles, PlayerColor player, bool hide) override {}
void changeFogOfWar(std::unordered_set<int3> & tiles, PlayerColor player, bool hide) override {}
void setObjProperty(ObjectInstanceID objid, int prop, si64 val) override {}

View File

@ -8,13 +8,12 @@
*
*/
#include "StdInc.h"
#include "ClientCommandManager.h"
#include "Client.h"
#include "adventureMap/CInGameConsole.h"
#include "adventureMap/CAdvMapInt.h"
#include "CPlayerInterface.h"
#include "PlayerLocalState.h"
#include "CServerHandler.h"
#include "gui/CGuiHandler.h"
#include "../lib/NetPacks.h"
@ -387,12 +386,12 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
ss << b;
return ss.str();
};
printCommandMessage("Bonuses of " + adventureInt->curArmy()->getObjectName() + "\n");
printCommandMessage(format(adventureInt->curArmy()->getBonusList()) + "\n");
printCommandMessage("Bonuses of " + LOCPLINT->localState->getCurrentArmy()->getObjectName() + "\n");
printCommandMessage(format(LOCPLINT->localState->getCurrentArmy()->getBonusList()) + "\n");
printCommandMessage("\nInherited bonuses:\n");
TCNodes parents;
adventureInt->curArmy()->getParents(parents);
LOCPLINT->localState->getCurrentArmy()->getParents(parents);
for(const CBonusSystemNode *parent : parents)
{
printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n");
@ -416,7 +415,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe
void ClientCommandManager::handleMpCommand()
{
if(const CGHeroInstance* h = adventureInt->curHero())
if(const CGHeroInstance* h = LOCPLINT->localState->getCurrentHero())
printCommandMessage(std::to_string(h->movement) + "; max: " + std::to_string(h->maxMovePoints(true)) + "/" + std::to_string(h->maxMovePoints(false)) + "\n");
}
@ -602,7 +601,7 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
else if(commandName == "tell")
handleTellCommand(singleWordBuffer);
else if(commandName == "mp" && adventureInt)
else if(commandName == "mp" && LOCPLINT)
handleMpCommand();
else if (commandName == "set")

View File

@ -706,15 +706,15 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack &
const CStack *activated = gs.curB->battleGetStackByID(pack.stack);
PlayerColor playerToCall; //pack.player that will move activated stack
if (activated->hasBonusOfType(Bonus::HYPNOTIZED))
if (activated->hasBonusOfType(BonusType::HYPNOTIZED))
{
playerToCall = (gs.curB->sides[0].color == activated->owner
playerToCall = (gs.curB->sides[0].color == activated->unitOwner()
? gs.curB->sides[1].color
: gs.curB->sides[0].color);
}
else
{
playerToCall = activated->owner;
playerToCall = activated->unitOwner();
}
cl.startPlayerBattleAction(playerToCall);

268
client/PlayerLocalState.cpp Normal file
View File

@ -0,0 +1,268 @@
/*
* PlayerLocalState.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 "PlayerLocalState.h"
#include "../CCallback.h"
#include "../lib/CPathfinder.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "CPlayerInterface.h"
#include "adventureMap/CAdventureMapInterface.h"
PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
: owner(owner)
, currentSelection(nullptr)
{
}
void PlayerLocalState::saveHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap)
{
for(auto & p : paths)
{
if(p.second.nodes.size())
pathsMap[p.first] = p.second.endPos();
else
logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
}
}
void PlayerLocalState::loadHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap)
{
if(owner.cb)
{
for(auto & p : pathsMap)
{
CGPath path;
owner.cb->getPathsInfo(p.first)->getPath(path, p.second);
paths[p.first] = path;
logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
}
}
}
void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path)
{
paths[h] = path;
}
const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const
{
assert(hasPath(h));
return paths.at(h);
}
bool PlayerLocalState::hasPath(const CGHeroInstance * h) const
{
return paths.count(h) > 0;
}
bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destination)
{
CGPath path;
if(!owner.cb->getPathsInfo(h)->getPath(path, destination))
return false;
setPath(h, path);
return true;
}
void PlayerLocalState::removeLastNode(const CGHeroInstance * h)
{
assert(hasPath(h));
if(!hasPath(h))
return;
auto & path = paths[h];
path.nodes.pop_back();
if(path.nodes.size() < 2) //if it was the last one, remove entire path and path with only one tile is not a real path
erasePath(h);
}
void PlayerLocalState::erasePath(const CGHeroInstance * h)
{
paths.erase(h);
adventureInt->onHeroChanged(h);
}
void PlayerLocalState::verifyPath(const CGHeroInstance * h)
{
if(!hasPath(h))
return;
setPath(h, getPath(h).endPos());
}
const CGHeroInstance * PlayerLocalState::getCurrentHero() const
{
if(currentSelection && currentSelection->ID == Obj::HERO)
return dynamic_cast<const CGHeroInstance *>(currentSelection);
else
return nullptr;
}
const CGHeroInstance * PlayerLocalState::getNextWanderingHero(const CGHeroInstance * currentHero)
{
bool currentHeroFound = false;
const CGHeroInstance * firstSuitable = nullptr;
const CGHeroInstance * nextSuitable = nullptr;
for(const auto * hero : getWanderingHeroes())
{
if (hero == currentHero)
{
currentHeroFound = true;
continue;
}
if (isHeroSleeping(hero))
continue;
if (hero->movement == 0)
continue;
if (!firstSuitable)
firstSuitable = hero;
if (!nextSuitable && currentHeroFound)
nextSuitable = hero;
}
// if we found suitable hero after currently selected hero -> return this hero
if (nextSuitable)
return nextSuitable;
// othervice -> loop over and return first suitable hero in the list (or null if none)
return firstSuitable;
}
const CGTownInstance * PlayerLocalState::getCurrentTown() const
{
if(currentSelection && currentSelection->ID == Obj::TOWN)
return dynamic_cast<const CGTownInstance *>(currentSelection);
else
return nullptr;
}
const CArmedInstance * PlayerLocalState::getCurrentArmy() const
{
if(currentSelection)
return dynamic_cast<const CArmedInstance *>(currentSelection);
else
return nullptr;
}
void PlayerLocalState::setSelection(const CArmedInstance * selection)
{
if (currentSelection == selection)
return;
currentSelection = selection;
if (selection)
adventureInt->onSelectionChanged(selection);
}
bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const
{
return vstd::contains(sleepingHeroes, hero);
}
void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero)
{
assert(hero);
assert(vstd::contains(wanderingHeroes, hero));
assert(!vstd::contains(sleepingHeroes, hero));
sleepingHeroes.push_back(hero);
}
void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
{
assert(hero);
assert(vstd::contains(wanderingHeroes, hero));
assert(vstd::contains(sleepingHeroes, hero));
vstd::erase(sleepingHeroes, hero);
}
const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes()
{
return wanderingHeroes;
}
const CGHeroInstance * PlayerLocalState::getWanderingHero(size_t index)
{
if(index < wanderingHeroes.size())
return wanderingHeroes[index];
return nullptr;
}
void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
{
assert(hero);
assert(!vstd::contains(wanderingHeroes, hero));
wanderingHeroes.push_back(hero);
}
void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
{
assert(hero);
assert(vstd::contains(wanderingHeroes, hero));
if (hero == currentSelection)
{
auto const * nextHero = getNextWanderingHero(hero);
setSelection(nextHero);
}
vstd::erase(wanderingHeroes, hero);
vstd::erase(sleepingHeroes, hero);
if (currentSelection == nullptr && !wanderingHeroes.empty())
setSelection(wanderingHeroes.front());
if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front());
}
const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()
{
return ownedTowns;
}
const CGTownInstance * PlayerLocalState::getOwnedTown(size_t index)
{
if(index < ownedTowns.size())
return ownedTowns[index];
return nullptr;
}
void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
{
assert(town);
assert(!vstd::contains(ownedTowns, town));
ownedTowns.push_back(town);
}
void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
{
assert(town);
assert(vstd::contains(ownedTowns, town));
vstd::erase(ownedTowns, town);
if (town == currentSelection)
setSelection(nullptr);
if (currentSelection == nullptr && !wanderingHeroes.empty())
setSelection(wanderingHeroes.front());
if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front());
}

111
client/PlayerLocalState.h Normal file
View File

@ -0,0 +1,111 @@
/*
* PlayerLocalState.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
VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CGTownInstance;
class CArmedInstance;
struct CGPath;
class int3;
VCMI_LIB_NAMESPACE_END
class CPlayerInterface;
/// Class that contains potentially serializeable state of a local player
class PlayerLocalState
{
CPlayerInterface & owner;
/// Currently selected object, can be town, hero or null
const CArmedInstance * currentSelection;
std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
std::vector<const CGHeroInstance *> sleepingHeroes; //if hero is in here, he's sleeping
std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones)
std::vector<const CGTownInstance *> ownedTowns; //our towns on the adventure map
void saveHeroPaths(std::map<const CGHeroInstance *, int3> & paths);
void loadHeroPaths(std::map<const CGHeroInstance *, int3> & paths);
public:
struct SpellbookLastSetting
{
//on which page we left spellbook
int spellbookLastPageBattle = 0;
int spellbokLastPageAdvmap = 0;
int spellbookLastTabBattle = 4;
int spellbookLastTabAdvmap = 4;
template<typename Handler>
void serialize(Handler & h, const int version)
{
h & spellbookLastPageBattle;
h & spellbokLastPageAdvmap;
h & spellbookLastTabBattle;
h & spellbookLastTabAdvmap;
}
} spellbookSettings;
explicit PlayerLocalState(CPlayerInterface & owner);
bool isHeroSleeping(const CGHeroInstance * hero) const;
void setHeroAsleep(const CGHeroInstance * hero);
void setHeroAwaken(const CGHeroInstance * hero);
const std::vector<const CGTownInstance *> & getOwnedTowns();
const CGTownInstance * getOwnedTown(size_t index);
void addOwnedTown(const CGTownInstance * hero);
void removeOwnedTown(const CGTownInstance * hero);
const std::vector<const CGHeroInstance *> & getWanderingHeroes();
const CGHeroInstance * getWanderingHero(size_t index);
const CGHeroInstance * getNextWanderingHero(const CGHeroInstance * hero);
void addWanderingHero(const CGHeroInstance * hero);
void removeWanderingHero(const CGHeroInstance * hero);
void setPath(const CGHeroInstance * h, const CGPath & path);
bool setPath(const CGHeroInstance * h, const int3 & destination);
const CGPath & getPath(const CGHeroInstance * h) const;
bool hasPath(const CGHeroInstance * h) const;
void removeLastNode(const CGHeroInstance * h);
void erasePath(const CGHeroInstance * h);
void verifyPath(const CGHeroInstance * h);
/// Returns currently selected object
const CGHeroInstance * getCurrentHero() const;
const CGTownInstance * getCurrentTown() const;
const CArmedInstance * getCurrentArmy() const;
/// Changes currently selected object
void setSelection(const CArmedInstance *sel);
template<typename Handler>
void serialize(Handler & h, int version)
{
//WARNING: this code is broken and not used. See CClient::loadGame
std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest
if(h.saving)
saveHeroPaths(pathsMap);
h & pathsMap;
if(!h.saving)
loadHeroPaths(pathsMap);
h & ownedTowns;
h & wanderingHeroes;
h & sleepingHeroes;
}
};

View File

@ -11,9 +11,6 @@
#include "../gui/CIntObject.h"
#include "../../lib/int3.h"
#include "../../lib/GameConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGObjectInstance;
@ -23,6 +20,8 @@ class CArmedInstance;
class IShipyard;
struct CGPathNode;
struct ObjectPosInfo;
struct Component;
class int3;
VCMI_LIB_NAMESPACE_END
@ -43,38 +42,28 @@ class MapAudioPlayer;
struct MapDrawingInfo;
enum class EAdvMapMode
{
NORMAL,
WORLD_VIEW
};
/// That's a huge class which handles general adventure map actions and
/// shows the right menu(questlog, spellbook, end turn,..) from where you
/// can get to the towns and heroes.
class CAdvMapInt : public CIntObject
class CAdventureMapInterface : public CIntObject
{
//TODO: remove
friend class CPlayerInterface;
private:
enum EDirections {LEFT=1, RIGHT=2, UP=4, DOWN=8};
enum EGameStates {NA, INGAME, WAITING};
enum class EGameState
{
NOT_INITIALIZED,
HOTSEAT_WAIT,
MAKING_TURN,
ENEMY_TURN,
WORLD_VIEW
};
EGameStates state;
EAdvMapMode mode;
/// Currently selected object, can be town, hero or null
const CArmedInstance *selection;
EGameState state;
/// currently acting player
PlayerColor player;
bool duringAITurn;
PlayerColor currentPlayerID;
/// uses EDirections enum
ui8 scrollingDir;
bool scrollingState;
bool scrollingCursorSet;
const CSpell *spellBeingCasted; //nullptr if none
@ -127,15 +116,15 @@ private:
void fnextHero();
void fendTurn();
void setScrollingCursor(ui8 direction) const;
void selectionChanged();
void hotkeyMoveHeroDirectional(Point direction);
bool isActive();
void adjustActiveness(bool aiTurnStart); //should be called every time at AI/human turn transition; blocks GUI during AI turn
const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const; //checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
//button updates
void updateSleepWake(const CGHeroInstance *h);
void updateSpellbook(const CGHeroInstance *h);
// update locked state of buttons
void updateButtons();
void handleMapScrollingUpdate();
@ -143,11 +132,17 @@ private:
const CGObjectInstance *getActiveObject(const int3 &tile);
std::optional<Point> keyToMoveDirection(const SDL_Keycode & key);
std::optional<Point> keyToMoveDirection(EShortcut key);
public:
CAdvMapInt();
void endingTurn();
/// exits currently opened world view mode and returns to normal map
void exitWorldView();
void exitCastingMode();
void leaveCastingMode(const int3 & castTarget);
void abortCastingMode();
protected:
// CIntObject interface implementation
void activate() override;
@ -156,59 +151,70 @@ public:
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
void keyPressed(const SDL_Keycode & key) override;
void keyReleased(const SDL_Keycode & key) override;
void mouseMoved (const Point & cursorPosition) override;
void keyPressed(EShortcut key) override;
// public interface
public:
CAdventureMapInterface();
/// called by MapView whenever currently visible area changes
/// visibleArea describen now visible map section measured in tiles
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
/// Called by PlayerInterface when specified player is ready to start his turn
void onHotseatWaitStarted(PlayerColor playerID);
/// Called when map audio should be paused, e.g. on combat or town scren access
/// Called by PlayerInterface when AI or remote human player starts his turn
void onEnemyTurnStarted(PlayerColor playerID);
/// Called by PlayerInterface when local human player starts his turn
void onPlayerTurnStarted(PlayerColor playerID);
/// Called by PlayerInterface when interface should be switched to specified player without starting turn
void onCurrentPlayerChanged(PlayerColor playerID);
/// Called by PlayerInterface when specific map tile changed and must be updated on minimap
void onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions);
/// Called by PlayerInterface when hero starts movement
void onHeroMovementStarted(const CGHeroInstance * hero);
/// Called by PlayerInterface when hero state changed and hero list must be updated
void onHeroChanged(const CGHeroInstance * hero);
/// Called by PlayerInterface when town state changed and town list must be updated
void onTownChanged(const CGTownInstance * town);
/// Called when currently selected object changes
void onSelectionChanged(const CArmedInstance *sel);
/// Called when map audio should be paused, e.g. on combat or town screen access
void onAudioPaused();
/// Called when map audio should be resume, opposite to onPaused
void onAudioResumed();
void select(const CArmedInstance *sel, bool centerView = true);
/// Requests to display provided information inside infobox
void showInfoBoxMessage(const std::vector<Component> & components, std::string message, int timer);
/// Changes position on map to center selected location
void centerOnTile(int3 on);
void centerOnObject(const CGObjectInstance *obj);
bool isHeroSleeping(const CGHeroInstance *hero);
void setHeroSleeping(const CGHeroInstance *hero, bool sleep);
int getNextHeroIndex(int startIndex); //for Next Hero button - cycles awake heroes with movement only
void setPlayer(PlayerColor Player);
void startHotSeatWait(PlayerColor Player);
void startTurn();
void initializeNewTurn();
void endingTurn();
void aiTurnStarted();
void quickCombatLock(); //should be called when quick battle started
void quickCombatUnlock();
/// called by MapView whenever currently visible area changes
/// visibleArea describes now visible map section measured in tiles
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
/// called by MapView whenever tile is clicked
void onTileLeftClicked(const int3 & mapPos);
/// called by MapView whenever tile is hovered
void onTileHovered(const int3 & mapPos);
/// called by MapView whenever tile is clicked
void onTileRightClicked(const int3 & mapPos);
/// called by spell window when spell to cast has been selected
void enterCastingMode(const CSpell * sp);
void leaveCastingMode(bool cast = false, int3 dest = int3(-1, -1, -1));
const CGHeroInstance * curHero() const;
const CGTownInstance * curTown() const;
const CArmedInstance * curArmy() const;
void updateMoveHero(const CGHeroInstance *h, tribool hasPath = boost::logic::indeterminate);
void updateNextHero(const CGHeroInstance *h);
/// returns area of screen covered by terrain (main game area)
Rect terrainAreaPixels() const;
/// exits currently opened world view mode and returns to normal map
void exitWorldView();
/// opens world view at default scale
void openWorldView();
@ -219,4 +225,4 @@ public:
void openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain);
};
extern std::shared_ptr<CAdvMapInt> adventureInt;
extern std::shared_ptr<CAdventureMapInterface> adventureInt;

View File

@ -11,13 +11,13 @@
#include "StdInc.h"
#include "CAdventureOptions.h"
#include "CAdvMapInt.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../lobby/CCampaignInfoScreen.h"
#include "../lobby/CScenarioInfoScreen.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../widgets/Buttons.h"
#include "../../CCallback.h"
@ -28,20 +28,19 @@ CAdventureOptions::CAdventureOptions()
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_v);
viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), SDLK_RETURN);
exit->assignedKeys.insert(SDLK_ESCAPE);
exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_i);
scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_p);
puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
dig = std::make_shared<CButton>(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_d);
if(const CGHeroInstance *h = adventureInt->curHero())
dig = std::make_shared<CButton>(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_DIG_GRAIL);
if(const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero())
dig->addCallback(std::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h));
else
dig->block(true);

View File

@ -14,9 +14,10 @@
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../ClientCommandManager.h"
#include "../adventureMap/CAdvMapInt.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../render/Colors.h"
#include "../../CCallback.h"
@ -108,30 +109,29 @@ void CInGameConsole::print(const std::string & txt)
GH.totalRedraw(); // FIXME: ingame console has no parent widget set
}
void CInGameConsole::keyPressed (const SDL_Keycode & key)
void CInGameConsole::keyPressed (EShortcut key)
{
if (LOCPLINT->cingconsole != this)
return;
if(!captureAllKeys && key != SDLK_TAB)
if(!captureAllKeys && key != EShortcut::GAME_ACTIVATE_CONSOLE)
return; //because user is not entering any text
switch(key)
{
case SDLK_TAB:
case SDLK_ESCAPE:
{
if(captureAllKeys)
{
endEnteringText(false);
}
else if(SDLK_TAB == key)
{
startEnteringText();
}
break;
}
case SDLK_RETURN: //enter key
case EShortcut::GLOBAL_CANCEL:
if(captureAllKeys)
endEnteringText(false);
break;
case EShortcut::GAME_ACTIVATE_CONSOLE:
if(captureAllKeys)
endEnteringText(false);
else
startEnteringText();
break;
case EShortcut::GLOBAL_ACCEPT:
{
if(!enteredText.empty() && captureAllKeys)
{
@ -145,7 +145,7 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
}
break;
}
case SDLK_BACKSPACE:
case EShortcut::GLOBAL_BACKSPACE:
{
if(enteredText.size() > 1)
{
@ -155,7 +155,7 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
}
break;
}
case SDLK_UP: //up arrow
case EShortcut::MOVE_UP:
{
if(previouslyEntered.empty())
break;
@ -174,7 +174,7 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
}
break;
}
case SDLK_DOWN: //down arrow
case EShortcut::MOVE_DOWN:
{
if(prevEntDisp != -1 && prevEntDisp+1 < previouslyEntered.size())
{
@ -190,10 +190,6 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
}
break;
}
default:
{
break;
}
}
}
@ -259,7 +255,7 @@ void CInGameConsole::endEnteringText(bool processEnteredText)
clientCommandThread.detach();
}
else
LOCPLINT->cb->sendMessage(txt, adventureInt->curArmy());
LOCPLINT->cb->sendMessage(txt, LOCPLINT->localState->getCurrentArmy());
}
enteredText.clear();

View File

@ -47,7 +47,7 @@ public:
void tick(uint32_t msPassed) override;
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
void keyPressed(const SDL_Keycode & key) override;
void keyPressed(EShortcut key) override;
void textInputed(const std::string & enteredText) override;
void textEdited(const std::string & enteredText) override;

View File

@ -11,7 +11,7 @@
#include "StdInc.h"
#include "CInfoBar.h"
#include "CAdvMapInt.h"
#include "CAdventureMapInterface.h"
#include "../widgets/CComponent.h"
#include "../widgets/Images.h"
@ -22,6 +22,7 @@
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../../CCallback.h"
@ -114,7 +115,7 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
//get amount of halls of each level
std::vector<int> halls(4, 0);
for(auto town : LOCPLINT->towns)
for(auto town : LOCPLINT->localState->getOwnedTowns())
{
int hallLevel = town->hallLevel();
//negative value means no village hall, unlikely but possible
@ -237,15 +238,15 @@ void CInfoBar::reset()
void CInfoBar::showSelection()
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
if(adventureInt->curHero())
if(LOCPLINT->localState->getCurrentHero())
{
showHeroSelection(adventureInt->curHero());
showHeroSelection(LOCPLINT->localState->getCurrentHero());
return;
}
if(adventureInt->curTown())
if(LOCPLINT->localState->getCurrentTown())
{
showTownSelection(adventureInt->curTown());
showTownSelection(LOCPLINT->localState->getCurrentTown());
return;
}

View File

@ -11,13 +11,14 @@
#include "StdInc.h"
#include "CList.h"
#include "CAdvMapInt.h"
#include "CAdventureMapInterface.h"
#include "../widgets/Images.h"
#include "../widgets/Buttons.h"
#include "../windows/InfoWindows.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../../lib/CGeneralTextHandler.h"
@ -203,8 +204,8 @@ std::shared_ptr<CIntObject> CHeroList::CHeroItem::genSelection()
void CHeroList::CHeroItem::select(bool on)
{
if(on && adventureInt->curHero() != hero)
adventureInt->select(hero);
if(on)
LOCPLINT->localState->setSelection(hero);
}
void CHeroList::CHeroItem::open()
@ -224,19 +225,19 @@ std::string CHeroList::CHeroItem::getHoverText()
std::shared_ptr<CIntObject> CHeroList::createHeroItem(size_t index)
{
if (LOCPLINT->wanderingHeroes.size() > index)
return std::make_shared<CHeroItem>(this, LOCPLINT->wanderingHeroes[index]);
if (LOCPLINT->localState->getWanderingHeroes().size() > index)
return std::make_shared<CHeroItem>(this, LOCPLINT->localState->getWanderingHero(index));
return std::make_shared<CEmptyHeroItem>();
}
CHeroList::CHeroList(int size, Point position, std::string btnUp, std::string btnDown):
CList(size, position, btnUp, btnDown, LOCPLINT->wanderingHeroes.size(), 303, 304, std::bind(&CHeroList::createHeroItem, this, _1))
CList(size, position, btnUp, btnDown, LOCPLINT->localState->getWanderingHeroes().size(), 303, 304, std::bind(&CHeroList::createHeroItem, this, _1))
{
}
void CHeroList::select(const CGHeroInstance * hero)
{
selectIndex(vstd::find_pos(LOCPLINT->wanderingHeroes, hero));
selectIndex(vstd::find_pos(LOCPLINT->localState->getWanderingHeroes(), hero));
}
void CHeroList::update(const CGHeroInstance * hero)
@ -245,7 +246,7 @@ void CHeroList::update(const CGHeroInstance * hero)
for(auto & elem : listBox->getItems())
{
auto item = std::dynamic_pointer_cast<CHeroItem>(elem);
if(item && item->hero == hero && vstd::contains(LOCPLINT->wanderingHeroes, hero))
if(item && item->hero == hero && vstd::contains(LOCPLINT->localState->getWanderingHeroes(), hero))
{
item->update();
return;
@ -253,17 +254,17 @@ void CHeroList::update(const CGHeroInstance * hero)
}
//simplest solution for now: reset list and restore selection
listBox->resize(LOCPLINT->wanderingHeroes.size());
if (adventureInt->curHero())
select(adventureInt->curHero());
listBox->resize(LOCPLINT->localState->getWanderingHeroes().size());
if (LOCPLINT->localState->getCurrentHero())
select(LOCPLINT->localState->getCurrentHero());
CList::update();
}
std::shared_ptr<CIntObject> CTownList::createTownItem(size_t index)
{
if (LOCPLINT->towns.size() > index)
return std::make_shared<CTownItem>(this, LOCPLINT->towns[index]);
if (LOCPLINT->localState->getOwnedTowns().size() > index)
return std::make_shared<CTownItem>(this, LOCPLINT->localState->getOwnedTown(index));
return std::make_shared<CAnimImage>("ITPA", 0);
}
@ -292,8 +293,8 @@ void CTownList::CTownItem::update()
void CTownList::CTownItem::select(bool on)
{
if (on && adventureInt->curTown() != town)
adventureInt->select(town);
if(on)
LOCPLINT->localState->setSelection(town);
}
void CTownList::CTownItem::open()
@ -312,22 +313,22 @@ std::string CTownList::CTownItem::getHoverText()
}
CTownList::CTownList(int size, Point position, std::string btnUp, std::string btnDown):
CList(size, position, btnUp, btnDown, LOCPLINT->towns.size(), 306, 307, std::bind(&CTownList::createTownItem, this, _1))
CList(size, position, btnUp, btnDown, LOCPLINT->localState->getOwnedTowns().size(), 306, 307, std::bind(&CTownList::createTownItem, this, _1))
{
}
void CTownList::select(const CGTownInstance * town)
{
selectIndex(vstd::find_pos(LOCPLINT->towns, town));
selectIndex(vstd::find_pos(LOCPLINT->localState->getOwnedTowns(), town));
}
void CTownList::update(const CGTownInstance *)
{
//simplest solution for now: reset list and restore selection
listBox->resize(LOCPLINT->towns.size());
if (adventureInt->curTown())
select(adventureInt->curTown());
listBox->resize(LOCPLINT->localState->getOwnedTowns().size());
if (LOCPLINT->localState->getCurrentTown())
select(LOCPLINT->localState->getCurrentTown());
CList::update();
}

View File

@ -11,14 +11,15 @@
#include "StdInc.h"
#include "CMinimap.h"
#include "CAdvMapInt.h"
#include "CAdventureMapInterface.h"
#include "../widgets/Images.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../render/Colors.h"
#include "../renderSDL/SDL_PixelAccess.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/Canvas.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h"
@ -27,6 +28,8 @@
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapping/CMapDefines.h"
#include <SDL_pixels.h>
ColorRGBA CMinimapInstance::getTileColor(const int3 & pos) const
{
const TerrainTile * tile = LOCPLINT->cb->getTile(pos, false);
@ -225,10 +228,13 @@ void CMinimap::setAIRadar(bool on)
redraw();
}
void CMinimap::updateTile(const int3 &pos)
void CMinimap::updateTiles(std::unordered_set<int3> positions)
{
if(minimap)
minimap->refreshTile(pos);
{
for (auto const & tile : positions)
minimap->refreshTile(tile);
}
redraw();
}

View File

@ -10,13 +10,12 @@
#pragma once
#include "../gui/CIntObject.h"
#include "../../lib/GameConstants.h"
#include "../render/Canvas.h"
VCMI_LIB_NAMESPACE_BEGIN
class ColorRGBA;
VCMI_LIB_NAMESPACE_END
class Canvas;
class CMinimap;
class CMinimapInstance : public CIntObject
@ -68,6 +67,6 @@ public:
void showAll(SDL_Surface * to) override;
void updateTile(const int3 &pos);
void updateTiles(std::unordered_set<int3> positions);
};

View File

@ -80,7 +80,7 @@ std::string CResDataBar::buildDateString()
void CResDataBar::draw(SDL_Surface * to)
{
//TODO: all this should be labels, but they require proper text update on change
for (auto i=GameResID(EGameResID::WOOD); i <= GameResID(EGameResID::GOLD); vstd::advance(i, 1))
for (GameResID i=EGameResID::WOOD; i <= GameResID(EGameResID::GOLD); ++i)
{
std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(i));

View File

@ -157,7 +157,7 @@ void MapAudioPlayer::updateAmbientSounds()
};
int3 pos = currentSelection->getSightCenter();
std::unordered_set<int3, ShashInt3> tiles;
std::unordered_set<int3> tiles;
LOCPLINT->cb->getVisibleTilesInRange(tiles, pos, CCS->soundh->ambientGetRange(), int3::DIST_CHEBYSHEV);
for(int3 tile : tiles)
{

View File

@ -140,7 +140,7 @@ bool BattleActionsController::isActiveStackSpellcaster() const
if (!casterStack)
return false;
bool spellcaster = casterStack->hasBonusOfType(Bonus::SPELLCASTER);
bool spellcaster = casterStack->hasBonusOfType(BonusType::SPELLCASTER);
return (spellcaster && casterStack->canCast());
}
@ -228,7 +228,7 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
case PossiblePlayerBattleAction::NO_LOCATION:
case PossiblePlayerBattleAction::FREE_LOCATION:
case PossiblePlayerBattleAction::OBSTACLE:
if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
if(!stack->hasBonusOfType(BonusType::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
return 1;
else
return 100;//bottom priority
@ -349,7 +349,7 @@ void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action,
case PossiblePlayerBattleAction::MOVE_TACTICS:
case PossiblePlayerBattleAction::MOVE_STACK:
if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
if (owner.stacksController->getActiveStack()->hasBonusOfType(BonusType::FLYING))
CCS->curh->set(Cursor::Combat::FLY);
else
CCS->curh->set(Cursor::Combat::MOVE);
@ -434,7 +434,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
case PossiblePlayerBattleAction::MOVE_TACTICS:
case PossiblePlayerBattleAction::MOVE_STACK:
if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
if (owner.stacksController->getActiveStack()->hasBonusOfType(BonusType::FLYING))
return (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
else
return (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
@ -524,12 +524,12 @@ std::string BattleActionsController::actionGetStatusMessageBlocked(PossiblePlaye
bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, BattleHex targetHex)
{
const CStack * targetStack = getStackForHex(targetHex);
bool targetStackOwned = targetStack && targetStack->owner == owner.curInt->playerID;
bool targetStackOwned = targetStack && targetStack->unitOwner() == owner.curInt->playerID;
switch (action.get())
{
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
return (targetStack && targetStackOwned && targetStack->Speed() > 0);
return (targetStack && targetStackOwned && targetStack->speed() > 0);
case PossiblePlayerBattleAction::CREATURE_INFO:
return (targetStack && targetStackOwned && targetStack->alive());
@ -620,7 +620,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
{
if(owner.stacksController->getActiveStack()->doubleWide())
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack(), true);
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false);
BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
if(vstd::contains(acc, targetHex))
owner.giveCommand(EActionType::WALK, targetHex);
@ -863,7 +863,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
{
creatureSpells.clear();
bool spellcaster = casterStack->hasBonusOfType(Bonus::SPELLCASTER);
bool spellcaster = casterStack->hasBonusOfType(BonusType::SPELLCASTER);
if(casterStack->canCast() && spellcaster)
{
// faerie dragon can cast only one, randomly selected spell until their next move
@ -874,7 +874,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
creatureSpells.push_back(spellToCast);
}
TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(Bonus::SPELLCASTER));
TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER));
for (auto const & bonus : *bl)
{

View File

@ -71,17 +71,17 @@ std::vector<BattleAnimation *> & BattleAnimation::pendingAnimations()
std::shared_ptr<CreatureAnimation> BattleAnimation::stackAnimation(const CStack * stack) const
{
return owner.stacksController->stackAnimation[stack->ID];
return owner.stacksController->stackAnimation[stack->unitId()];
}
bool BattleAnimation::stackFacingRight(const CStack * stack)
{
return owner.stacksController->stackFacingRight[stack->ID];
return owner.stacksController->stackFacingRight[stack->unitId()];
}
void BattleAnimation::setStackFacingRight(const CStack * stack, bool facingRight)
{
owner.stacksController->stackFacingRight[stack->ID] = facingRight;
owner.stacksController->stackFacingRight[stack->unitId()] = facingRight;
}
BattleStackAnimation::BattleStackAnimation(BattleInterface & owner, const CStack * stack)
@ -137,7 +137,7 @@ bool StackActionAnimation::init()
StackActionAnimation::~StackActionAnimation()
{
if (stack->isFrozen())
if (stack->isFrozen() && currGroup != ECreatureAnimType::DEATH && currGroup != ECreatureAnimType::DEATH_RANGED)
myAnim->setType(ECreatureAnimType::HOLDING);
else
myAnim->setType(nextGroup);
@ -279,7 +279,7 @@ ECreatureAnimType MeleeAttackAnimation::selectGroup(bool multiAttack)
getForwardGroup (multiAttack)
};
int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1);
int revShiftattacker = (attackingStack->unitSide() == BattleSide::ATTACKER ? -1 : 1);
int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest);
if(mutPos == -1 && attackingStack->doubleWide())
@ -370,7 +370,7 @@ bool MovementAnimation::init()
distanceX = endPosition.x - begPosition.x;
distanceY = endPosition.y - begPosition.y;
if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
if (stack->hasBonus(Selector::type()(BonusType::FLYING)))
{
float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
progressPerSecond = AnimationControls::getFlightDistance(stack->unitType()) / distance;

View File

@ -65,21 +65,21 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
return;
}
//don't show animation when no HP is regenerated
switch(bte.effect)
switch(static_cast<BonusType>(bte.effect))
{
case Bonus::HP_REGENERATION:
case BonusType::HP_REGENERATION:
displayEffect(EBattleEffect::REGENERATION, "REGENER", stack->getPosition());
break;
case Bonus::MANA_DRAIN:
case BonusType::MANA_DRAIN:
displayEffect(EBattleEffect::MANA_DRAIN, "MANADRAI", stack->getPosition());
break;
case Bonus::POISON:
case BonusType::POISON:
displayEffect(EBattleEffect::POISON, "POISON", stack->getPosition());
break;
case Bonus::FEAR:
case BonusType::FEAR:
displayEffect(EBattleEffect::FEAR, "FEAR", stack->getPosition());
break;
case Bonus::MORALE:
case BonusType::MORALE:
{
std::string hlp = CGI->generaltexth->allTexts[33];
boost::algorithm::replace_first(hlp,"%s",(stack->getName()));

View File

@ -134,7 +134,7 @@ void BattleFieldController::renderBattlefield(Canvas & canvas)
void BattleFieldController::showBackground(Canvas & canvas)
{
if (owner.stacksController->getActiveStack() != nullptr ) //&& creAnims[stacksController->getActiveStack()->ID]->isIdle() //show everything with range
if (owner.stacksController->getActiveStack() != nullptr ) //&& creAnims[stacksController->getActiveStack()->unitId()]->isIdle() //show everything with range
showBackgroundImageWithHexes(canvas);
else
showBackgroundImage(canvas);
@ -174,7 +174,7 @@ void BattleFieldController::redrawBackgroundWithHexes()
const CStack *activeStack = owner.stacksController->getActiveStack();
std::vector<BattleHex> attackableHexes;
if(activeStack)
occupiableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, true, &attackableHexes);
occupiableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, false, true, &attackableHexes);
// prepare background graphic with hexes and shaded hexes
backgroundWithHexes->draw(background, Point(0,0));
@ -243,7 +243,7 @@ std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
if (!owner.stacksController->getActiveStack())
return result;
if (!settings["battle"]["movementHighlightOnHover"].Bool())
if (!settings["battle"]["movementHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
return result;
auto hoveredHex = getHoveredHex();
@ -252,7 +252,7 @@ std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
if(hoveredStack)
{
std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, false, true, nullptr);
std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, true, true, nullptr);
for(BattleHex hex : v)
result.insert(hex);
}
@ -289,7 +289,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
return result;
}
std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget()
{
const CStack * stack = owner.stacksController->getActiveStack();
auto hoveredHex = getHoveredHex();
@ -297,7 +297,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
if(!stack)
return {};
std::vector<BattleHex> availableHexes = owner.curInt->cb->battleGetAvailableHexes(stack, true, false, nullptr);
std::vector<BattleHex> availableHexes = owner.curInt->cb->battleGetAvailableHexes(stack, false, false, nullptr);
auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
@ -337,7 +337,7 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
{
std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
std::set<BattleHex> hoveredMoveHexes = getHighlightedHexesMovementTarget();
std::set<BattleHex> hoveredMoveHexes = getHighlightedHexesForMovementTarget();
if(getHoveredHex() == BattleHex::INVALID)
return;
@ -540,7 +540,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
case BattleHex::LEFT:
case BattleHex::BOTTOM_LEFT:
{
if ( attacker->side == BattleSide::ATTACKER )
if ( attacker->unitSide() == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(direction);
else
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::LEFT);
@ -550,7 +550,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
case BattleHex::RIGHT:
case BattleHex::BOTTOM_RIGHT:
{
if ( attacker->side == BattleSide::ATTACKER )
if ( attacker->unitSide() == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::RIGHT);
else
return attackTarget.cloneInDirection(direction);
@ -558,7 +558,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
case BattleHex::TOP:
{
if ( attacker->side == BattleSide::ATTACKER )
if ( attacker->unitSide() == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(BattleHex::TOP_RIGHT);
else
return attackTarget.cloneInDirection(BattleHex::TOP_LEFT);
@ -566,7 +566,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
case BattleHex::BOTTOM:
{
if ( attacker->side == BattleSide::ATTACKER )
if ( attacker->unitSide() == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(BattleHex::BOTTOM_RIGHT);
else
return attackTarget.cloneInDirection(BattleHex::BOTTOM_LEFT);

View File

@ -53,7 +53,7 @@ class BattleFieldController : public CIntObject
std::set<BattleHex> getHighlightedHexesForActiveStack();
std::set<BattleHex> getMovementRangeForHoveredStack();
std::set<BattleHex> getHighlightedHexesForSpellRange();
std::set<BattleHex> getHighlightedHexesMovementTarget();
std::set<BattleHex> getHighlightedHexesForMovementTarget();
void showBackground(Canvas & canvas);
void showBackgroundImage(Canvas & canvas);

View File

@ -29,7 +29,7 @@
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../render/Canvas.h"
#include "../adventureMap/CAdvMapInt.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
@ -208,7 +208,7 @@ void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedI
for(const StackAttackedInfo & attackedInfo : attackedInfos)
{
ui8 side = attackedInfo.defender->side;
ui8 side = attackedInfo.defender->unitSide();
killedBySide.at(side) += attackedInfo.amountKilled;
}
@ -288,7 +288,7 @@ const CGHeroInstance * BattleInterface::getActiveHero()
return nullptr;
}
if(attacker->side == BattleSide::ATTACKER)
if(attacker->unitSide() == BattleSide::ATTACKER)
{
return attackingHeroInstance;
}
@ -636,7 +636,7 @@ void BattleInterface::tacticPhaseEnd()
static bool immobile(const CStack *s)
{
return !s->Speed(0, true); //should bound stacks be immobile?
return !s->speed(0, true); //should bound stacks be immobile?
}
void BattleInterface::tacticNextStack(const CStack * current)

View File

@ -24,6 +24,7 @@
#include "../CVideoHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../widgets/Buttons.h"
@ -406,12 +407,12 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
background->colorize(owner.playerID);
pos = center(background->pos);
exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, SDLK_RETURN);
exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
exit->setBorderColor(Colors::METALLIC_GOLD);
if(allowReplay)
{
repeat = std::make_shared<CButton>(Point(24, 505), "icn6432.def", std::make_pair("", ""), [&](){ bRepeatf();}, SDLK_ESCAPE);
repeat = std::make_shared<CButton>(Point(24, 505), "icn6432.def", std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
repeat->setBorderColor(Colors::METALLIC_GOLD);
labels.push_back(std::make_shared<CLabel>(232, 520, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.battleResultsWindow.applyResultsLabel")));
}
@ -455,18 +456,18 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
auto stacks = owner.cb->battleGetAllStacks();
vstd::erase_if(stacks, [i](const CStack * stack) //erase stack of other side and not coming from garrison
{
return stack->side != i || !stack->base;
return stack->unitSide() != i || !stack->base;
});
auto best = vstd::maxElementByFun(stacks, [](const CStack * stack)
{
return stack->type->getAIValue();
return stack->unitType()->getAIValue();
});
if(best != stacks.end()) //should be always but to be safe...
{
icons.push_back(std::make_shared<CAnimImage>("TWCRPORT", (*best)->type->getIconIndex(), 0, xs[i], 38));
sideNames[i] = (*best)->type->getNamePluralTranslated();
icons.push_back(std::make_shared<CAnimImage>("TWCRPORT", (*best)->unitType()->getIconIndex(), 0, xs[i], 38));
sideNames[i] = (*best)->unitType()->getNamePluralTranslated();
}
}
}

View File

@ -205,7 +205,7 @@ std::shared_ptr<CAnimation> BattleProjectileController::getProjectileImage(const
void BattleProjectileController::emitStackProjectile(const CStack * stack)
{
int stackID = stack ? stack->ID : -1;
int stackID = stack ? stack->unitId() : -1;
for (auto projectile : projectiles)
{
@ -232,7 +232,7 @@ void BattleProjectileController::showProjectiles(Canvas & canvas)
bool BattleProjectileController::hasActiveProjectile(const CStack * stack, bool emittedOnly) const
{
int stackID = stack ? stack->ID : -1;
int stackID = stack ? stack->unitId() : -1;
for(auto const & instance : projectiles)
{
@ -294,7 +294,7 @@ void BattleProjectileController::createCatapultProjectile(const CStack * shooter
catapultProjectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
catapultProjectile->from = from;
catapultProjectile->dest = dest;
catapultProjectile->shooterID = shooter->ID;
catapultProjectile->shooterID = shooter->unitId();
catapultProjectile->playing = false;
catapultProjectile->frameProgress = 0.f;
@ -333,7 +333,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
projectile->from = from;
projectile->dest = dest;
projectile->shooterID = shooter->ID;
projectile->shooterID = shooter->unitId();
projectile->progress = 0;
projectile->playing = false;
@ -357,7 +357,7 @@ void BattleProjectileController::createSpellProjectile(const CStack * shooter, P
projectile->reverse = from.x > dest.x;
projectile->from = from;
projectile->dest = dest;
projectile->shooterID = shooter ? shooter->ID : -1;
projectile->shooterID = shooter ? shooter->unitId() : -1;
projectile->progress = 0;
projectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
projectile->playing = false;

View File

@ -104,10 +104,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack) const
{
if ( !stackAnimation.at(stack->ID)->isMoving())
if ( !stackAnimation.at(stack->unitId())->isMoving())
return stack->getPosition();
if (stack->hasBonusOfType(Bonus::FLYING) && stackAnimation.at(stack->ID)->getType() == ECreatureAnimType::MOVING )
if (stack->hasBonusOfType(BonusType::FLYING) && stackAnimation.at(stack->unitId())->getType() == ECreatureAnimType::MOVING )
return BattleHex::HEX_AFTER_ALL;
for (auto & anim : currentAnimations)
@ -131,14 +131,14 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
for (auto stack : stacks)
{
if (stackAnimation.find(stack->ID) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
if (stackAnimation.find(stack->unitId()) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
continue;
//FIXME: hack to ignore ghost stacks
if ((stackAnimation[stack->ID]->getType() == ECreatureAnimType::DEAD || stackAnimation[stack->ID]->getType() == ECreatureAnimType::HOLDING) && stack->isGhost())
if ((stackAnimation[stack->unitId()]->getType() == ECreatureAnimType::DEAD || stackAnimation[stack->unitId()]->getType() == ECreatureAnimType::HOLDING) && stack->isGhost())
continue;
auto layer = stackAnimation[stack->ID]->isDead() ? EBattleFieldLayer::CORPSES : EBattleFieldLayer::STACKS;
auto layer = stackAnimation[stack->unitId()]->isDead() ? EBattleFieldLayer::CORPSES : EBattleFieldLayer::STACKS;
auto location = getStackCurrentPosition(stack);
renderer.insert(layer, location, [this, stack]( BattleRenderer::RendererRef renderer ){
@ -159,13 +159,13 @@ void BattleStacksController::stackReset(const CStack * stack)
owner.checkForAnimations();
//reset orientation?
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
//stackFacingRight[stack->unitId()] = stack->unitSide() == BattleSide::ATTACKER;
auto iter = stackAnimation.find(stack->ID);
auto iter = stackAnimation.find(stack->unitId());
if(iter == stackAnimation.end())
{
logGlobal->error("Unit %d have no animation", stack->ID);
logGlobal->error("Unit %d have no animation", stack->unitId());
return;
}
@ -185,7 +185,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
// Tower shooters have only their upper half visible
static const int turretCreatureAnimationHeight = 232;
stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
stackFacingRight[stack->unitId()] = stack->unitSide() == BattleSide::ATTACKER; // must be set before getting stack position
Point coords = getStackPositionAtHex(stack->getPosition(), stack);
@ -195,26 +195,26 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
const CCreature *turretCreature = owner.siegeController->getTurretCreature();
stackAnimation[stack->ID] = AnimationControls::getAnimation(turretCreature);
stackAnimation[stack->ID]->pos.h = turretCreatureAnimationHeight;
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
stackAnimation[stack->unitId()] = AnimationControls::getAnimation(turretCreature);
stackAnimation[stack->unitId()]->pos.h = turretCreatureAnimationHeight;
stackAnimation[stack->unitId()]->pos.w = stackAnimation[stack->unitId()]->getWidth();
// FIXME: workaround for visible animation of Medusa tails (animation disabled in H3)
if (turretCreature->getId() == CreatureID::MEDUSA )
stackAnimation[stack->ID]->pos.w = 250;
stackAnimation[stack->unitId()]->pos.w = 250;
coords = owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
}
else
{
stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->unitType());
stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]);
stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight();
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
stackAnimation[stack->unitId()] = AnimationControls::getAnimation(stack->unitType());
stackAnimation[stack->unitId()]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->unitId()]);
stackAnimation[stack->unitId()]->pos.h = stackAnimation[stack->unitId()]->getHeight();
stackAnimation[stack->unitId()]->pos.w = stackAnimation[stack->unitId()]->getWidth();
}
stackAnimation[stack->ID]->pos.x = coords.x;
stackAnimation[stack->ID]->pos.y = coords.y;
stackAnimation[stack->ID]->setType(ECreatureAnimType::HOLDING);
stackAnimation[stack->unitId()]->pos.x = coords.x;
stackAnimation[stack->unitId()]->pos.y = coords.y;
stackAnimation[stack->unitId()]->setType(ECreatureAnimType::HOLDING);
if (!instant)
{
@ -234,28 +234,20 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
void BattleStacksController::setActiveStack(const CStack *stack)
{
if (activeStack) // update UI
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder());
stackAnimation[activeStack->unitId()]->setBorderColor(AnimationControls::getNoBorder());
activeStack = stack;
if (activeStack) // update UI
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
stackAnimation[activeStack->unitId()]->setBorderColor(AnimationControls::getGoldBorder());
owner.windowObject->blockUI(activeStack == nullptr);
}
bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
{
BattleHex currentActionTarget;
if(owner.curInt->curAction)
{
auto target = owner.curInt->curAction->getTarget(owner.curInt->cb.get());
if(!target.empty())
currentActionTarget = target.at(0).hexValue;
}
//do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1)
if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->getCount() == 1)
return false;
if(!stack->alive())
@ -266,7 +258,7 @@ bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
return false;
// if stack has any ongoing animation - hide the box
if (stackAmountBoxHidden.count(stack->ID))
if (stackAmountBoxHidden.count(stack->unitId()))
return false;
return true;
@ -299,7 +291,7 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
bool doubleWide = stack->doubleWide();
bool turnedRight = facingRight(stack);
bool attacker = stack->side == BattleSide::ATTACKER;
bool attacker = stack->unitSide() == BattleSide::ATTACKER;
BattleHex stackPos = stack->getPosition();
@ -342,8 +334,8 @@ void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
fullFilter = ColorFilter::genCombined(fullFilter, filter.effect);
}
stackAnimation[stack->ID]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
stackAnimation[stack->unitId()]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
stackAnimation[stack->unitId()]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
}
void BattleStacksController::update()
@ -398,17 +390,17 @@ void BattleStacksController::addNewAnim(BattleAnimation *anim)
auto stackAnimation = dynamic_cast<BattleStackAnimation*>(anim);
if(stackAnimation)
stackAmountBoxHidden.insert(stackAnimation->stack->ID);
stackAmountBoxHidden.insert(stackAnimation->stack->unitId());
}
void BattleStacksController::stackRemoved(uint32_t stackID)
{
if (getActiveStack() && getActiveStack()->ID == stackID)
if (getActiveStack() && getActiveStack()->unitId() == stackID)
{
BattleAction *action = new BattleAction();
action->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
action->actionType = EActionType::CANCEL;
action->stackNumber = getActiveStack()->ID;
action->stackNumber = getActiveStack()->unitId();
owner.givenCommand.setn(action);
setActiveStack(nullptr);
}
@ -439,7 +431,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
// FIXME: this check is better, however not usable since stacksAreAttacked is called after net pack is applyed - petrification is already removed
// if (needsReverse && !attackedInfo.defender->isFrozen())
if (needsReverse && stackAnimation[attackedInfo.defender->ID]->getType() != ECreatureAnimType::FROZEN)
if (needsReverse && stackAnimation[attackedInfo.defender->unitId()]->getType() != ECreatureAnimType::FROZEN)
{
owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [=]()
{
@ -493,7 +485,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
{
owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
addNewAnim(new ColorTransformAnimation(owner, attackedInfo.defender, "summonFadeOut", nullptr));
stackRemoved(attackedInfo.defender->ID);
stackRemoved(attackedInfo.defender->unitId());
});
}
}
@ -511,7 +503,7 @@ void BattleStacksController::stackTeleported(const CStack *stack, std::vector<Ba
});
owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack));
stackAnimation[stack->unitId()]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack));
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) );
});
@ -536,7 +528,7 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
addNewAnim(new MovementStartAnimation(owner, stack));
});
if (!stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
if (!stack->hasBonus(Selector::typeSubtype(BonusType::FLYING, 1)))
{
owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [&]()
{
@ -559,7 +551,7 @@ bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, co
attacker,
defender);
if (attacker->side == BattleSide::ATTACKER)
if (attacker->unitSide() == BattleSide::ATTACKER)
return !mustReverse;
else
return mustReverse;
@ -677,9 +669,9 @@ void BattleStacksController::endAction(const BattleAction* action)
for (const CStack *s : stacks)
{
bool shouldFaceRight = s && s->side == BattleSide::ATTACKER;
bool shouldFaceRight = s && s->unitSide() == BattleSide::ATTACKER;
if (s && facingRight(s) != shouldFaceRight && s->alive() && stackAnimation[s->ID]->isIdle())
if (s && facingRight(s) != shouldFaceRight && s->alive() && stackAnimation[s->unitId()]->isIdle())
{
addNewAnim(new ReverseAnimation(owner, s, s->getPosition()));
}
@ -723,7 +715,7 @@ void BattleStacksController::activateStack()
if ( !stackToActivate)
return;
owner.trySetActivePlayer(stackToActivate->owner);
owner.trySetActivePlayer(stackToActivate->unitOwner());
setActiveStack(stackToActivate);
stackToActivate = nullptr;
@ -740,7 +732,7 @@ const CStack* BattleStacksController::getActiveStack() const
bool BattleStacksController::facingRight(const CStack * stack) const
{
return stackFacingRight.at(stack->ID);
return stackFacingRight.at(stack->unitId());
}
Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CStack * stack) const
@ -765,7 +757,7 @@ Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CSta
//shifting position for double - hex creatures
if(stack->doubleWide())
{
if(stack->side == BattleSide::ATTACKER)
if(stack->unitSide() == BattleSide::ATTACKER)
{
if(facingRight(stack))
ret.x -= 44;
@ -801,7 +793,7 @@ void BattleStacksController::removeExpiredColorFilters()
{
if (!filter.persistent)
{
if (filter.source && !filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id), Selector::all))
if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, filter.source->id), Selector::all))
return true;
if (filter.effect == ColorFilter::genEmptyShifter())
return true;
@ -820,9 +812,9 @@ void BattleStacksController::updateHoveredStacks()
continue;
if (stack == activeStack)
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getGoldBorder());
stackAnimation[stack->unitId()]->setBorderColor(AnimationControls::getGoldBorder());
else
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getNoBorder());
stackAnimation[stack->unitId()]->setBorderColor(AnimationControls::getNoBorder());
}
for(const auto * stack : newStacks)
@ -830,9 +822,9 @@ void BattleStacksController::updateHoveredStacks()
if (vstd::contains(mouseHoveredStacks, stack))
continue;
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON);
stackAnimation[stack->unitId()]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[stack->unitId()]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
stackAnimation[stack->unitId()]->playOnce(ECreatureAnimType::MOUSEON);
}
mouseHoveredStacks = newStacks;

View File

@ -21,6 +21,7 @@
#include "../CMusicHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../windows/CSpellWindow.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
@ -50,19 +51,23 @@ BattleWindow::BattleWindow(BattleInterface & owner):
const JsonNode config(ResourceID("config/widgets/BattleWindow.json"));
addCallback("options", std::bind(&BattleWindow::bOptionsf, this));
addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this));
addCallback("flee", std::bind(&BattleWindow::bFleef, this));
addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this));
addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this));
addCallback("wait", std::bind(&BattleWindow::bWaitf, this));
addCallback("defence", std::bind(&BattleWindow::bDefencef, this));
addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this));
addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this));
addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this));
addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this));
addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this));
addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
addShortcut(EShortcut::BATTLE_AUTOCOMBAT, std::bind(&BattleWindow::bAutofightf, this));
addShortcut(EShortcut::BATTLE_CAST_SPELL, std::bind(&BattleWindow::bSpellf, this));
addShortcut(EShortcut::BATTLE_WAIT, std::bind(&BattleWindow::bWaitf, this));
addShortcut(EShortcut::BATTLE_DEFEND, std::bind(&BattleWindow::bDefencef, this));
addShortcut(EShortcut::BATTLE_CONSOLE_UP, std::bind(&BattleWindow::bConsoleUpf, this));
addShortcut(EShortcut::BATTLE_CONSOLE_DOWN, std::bind(&BattleWindow::bConsoleDownf, this));
addShortcut(EShortcut::BATTLE_TACTICS_NEXT, std::bind(&BattleWindow::bTacticNextStack, this));
addShortcut(EShortcut::BATTLE_TACTICS_END, std::bind(&BattleWindow::bTacticPhaseEnd, this));
addShortcut(EShortcut::BATTLE_SELECT_ACTION, std::bind(&BattleWindow::bSwitchActionf, this));
addShortcut(EShortcut::BATTLE_TOGGLE_QUEUE, [this](){ this->toggleQueueVisibility();});
addShortcut(EShortcut::BATTLE_USE_CREATURE_SPELL, [this](){ this->owner.actionsController->enterCreatureCastingMode(); });
addShortcut(EShortcut::GLOBAL_CANCEL, [this](){ this->owner.actionsController->endCastingSpell(); });
build(config);
console = widget<BattleConsole>("console");
@ -182,43 +187,14 @@ void BattleWindow::deactivate()
LOCPLINT->cingconsole->deactivate();
}
void BattleWindow::keyPressed(const SDL_Keycode & key)
void BattleWindow::keyPressed(EShortcut key)
{
if (owner.openingPlaying())
{
owner.openingEnd();
return;
}
if(key == SDLK_q)
{
toggleQueueVisibility();
}
else if(key == SDLK_f)
{
owner.actionsController->enterCreatureCastingMode();
}
else if(key == SDLK_ESCAPE)
{
owner.actionsController->endCastingSpell();
}
else if(GH.isKeyboardShiftDown())
{
// save and activate setting
Settings movementHighlightOnHover = settings.write["battle"]["movementHighlightOnHover"];
movementHighlightOnHoverCache = movementHighlightOnHover->Bool();
movementHighlightOnHover->Bool() = true;
}
}
void BattleWindow::keyReleased(const SDL_Keycode & key)
{
if(!GH.isKeyboardShiftDown())
{
// set back to initial state
Settings movementHighlightOnHover = settings.write["battle"]["movementHighlightOnHover"];
movementHighlightOnHover->Bool() = movementHighlightOnHoverCache;
}
InterfaceObjectConfigurable::keyPressed(key);
}
void BattleWindow::clickRight(tribool down, bool previousState)
@ -450,11 +426,11 @@ void BattleWindow::bSpellf()
{
//TODO: move to spell mechanics, add more information to spell cast problem
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(BonusType::BLOCK_ALL_MAGIC));
if (!blockingBonus)
return;
if (blockingBonus->source == Bonus::ARTIFACT)
if (blockingBonus->source == BonusSource::ARTIFACT)
{
const auto artID = ArtifactID(blockingBonus->sid);
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
@ -554,40 +530,18 @@ void BattleWindow::blockUI(bool on)
bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
if(auto w = widget<CButton>("options"))
w->block(on);
if(auto w = widget<CButton>("flee"))
w->block(on || !owner.curInt->cb->battleCanFlee());
if(auto w = widget<CButton>("surrender"))
w->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
if(auto w = widget<CButton>("cast"))
w->block(on || owner.tacticsMode || !canCastSpells);
if(auto w = widget<CButton>("wait"))
w->block(on || owner.tacticsMode || !canWait);
if(auto w = widget<CButton>("defence"))
w->block(on || owner.tacticsMode);
if(auto w = widget<CButton>("alternativeAction"))
w->block(on || owner.tacticsMode);
if(auto w = widget<CButton>("autofight"))
w->block(owner.actionsController->spellcastingModeActive());
auto btactEnd = widget<CButton>("tacticEnd");
auto btactNext = widget<CButton>("tacticNext");
if(owner.tacticsMode && btactEnd && btactNext)
{
btactNext->block(on);
btactEnd->block(on);
}
else
{
auto bConsoleUp = widget<CButton>("consoleUp");
auto bConsoleDown = widget<CButton>("consoleDown");
if(bConsoleUp && bConsoleDown)
{
bConsoleUp->block(on);
bConsoleDown->block(on);
}
}
setShortcutBlocked(EShortcut::GLOBAL_OPTIONS, on);
setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.curInt->cb->battleCanFlee());
setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.curInt->cb->battleGetSurrenderCost() < 0);
setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells);
setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, owner.actionsController->spellcastingModeActive());
setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on && owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on && owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
}
std::optional<uint32_t> BattleWindow::getQueueHoveredUnitId()

Some files were not shown because too many files have changed in this diff Show More