mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
Merge branch 'develop' into interface-builder
This commit is contained in:
commit
785787e454
10
.github/workflows/github.yml
vendored
10
.github/workflows/github.yml
vendored
@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- features/*
|
||||
- beta
|
||||
- master
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
@ -158,6 +160,7 @@ jobs:
|
||||
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 }}
|
||||
|
||||
@ -171,7 +174,8 @@ jobs:
|
||||
${{matrix.cmake_args}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
|
||||
-DENABLE_TEST=${{matrix.test}} \
|
||||
-DPACKAGE_NAME_SUFFIX:STRING="$VCMI_PACKAGE_NAME_SUFFIX" \
|
||||
-DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME"
|
||||
-DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME" \
|
||||
-DENABLE_GITVERSION="$VCMI_PACKAGE_GITVERSION"
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
CXX: ${{ matrix.cxx }}
|
||||
@ -222,7 +226,7 @@ jobs:
|
||||
${{github.workspace}}/**/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
|
||||
|
||||
- name: Upload build
|
||||
if: ${{ matrix.pack == 1 && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
|
||||
if: ${{ matrix.pack == 1 && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
|
||||
run: |
|
||||
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
|
||||
source '${{github.workspace}}/CI/upload_package.sh'
|
||||
@ -240,7 +244,7 @@ jobs:
|
||||
|
||||
- name: Trigger Android
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
if: ${{ github.ref == 'refs/heads/develop' && matrix.platform == 'mxe' }}
|
||||
if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }}
|
||||
with:
|
||||
token: ${{ secrets.VCMI_ANDROID_ACCESS_TOKEN }}
|
||||
repository: vcmi/vcmi-android
|
||||
|
@ -204,10 +204,14 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
auto speed = activeStack->Speed();
|
||||
|
||||
if(speed == 0)
|
||||
return result;
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
auto dists = cb->getReachability(activeStack);
|
||||
auto speed = activeStack->Speed();
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
|
@ -40,4 +40,6 @@ add_subdirectory(BattleAI)
|
||||
add_subdirectory(StupidAI)
|
||||
add_subdirectory(EmptyAI)
|
||||
add_subdirectory(VCAI)
|
||||
add_subdirectory(Nullkiller)
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
add_subdirectory(Nullkiller)
|
||||
endif()
|
||||
|
@ -30,6 +30,7 @@ namespace NKAI
|
||||
// our to enemy strength ratio constants
|
||||
const float SAFE_ATTACK_CONSTANT = 1.2;
|
||||
const float RETREAT_THRESHOLD = 0.3;
|
||||
const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
|
||||
|
||||
//one thread may be turn of AI and another will be handling a side effect for AI2
|
||||
boost::thread_specific_ptr<CCallback> cb;
|
||||
@ -500,10 +501,11 @@ boost::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
|
||||
double fightRatio = battleState.getOurStrength() / (double)battleState.getEnemyStrength();
|
||||
double ourStrength = battleState.getOurStrength();
|
||||
double fightRatio = ourStrength / (double)battleState.getEnemyStrength();
|
||||
|
||||
// if we have no towns - things are already bad, so retreat is not an option.
|
||||
if(cb->getTownsInfo().size() && fightRatio < RETREAT_THRESHOLD && battleState.canFlee)
|
||||
if(cb->getTownsInfo().size() && ourStrength < RETREAT_ABSOLUTE_THRESHOLD && fightRatio < RETREAT_THRESHOLD && battleState.canFlee)
|
||||
{
|
||||
return BattleAction::makeRetreat(battleState.ourSide);
|
||||
}
|
||||
@ -533,6 +535,7 @@ void AIGateway::yourTurn()
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
status.startedTurn();
|
||||
|
||||
makingTurn = make_unique<boost::thread>(&AIGateway::makeTurn, this);
|
||||
}
|
||||
|
||||
@ -1426,7 +1429,15 @@ void AIGateway::endTurn()
|
||||
{
|
||||
logAi->error("Not having turn at the end of turn???");
|
||||
}
|
||||
|
||||
logAi->debug("Resources at the end of turn: %s", cb->getResourceAmount().toString());
|
||||
|
||||
if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
|
||||
{
|
||||
logAi->info("Ending turn is not needed because we already lost");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
cb->endTurn();
|
||||
@ -1599,7 +1610,7 @@ void AIStatus::waitTillFree()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mx);
|
||||
while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
|
||||
cv.timed_wait(lock, boost::posix_time::milliseconds(100));
|
||||
cv.timed_wait(lock, boost::posix_time::milliseconds(10));
|
||||
}
|
||||
|
||||
bool AIStatus::haveTurn()
|
||||
|
@ -74,6 +74,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
|
||||
|
||||
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
logAi->debug("Checking behavior %s", behavior->toString());
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
@ -160,8 +162,12 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
|
||||
}
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
pathfinder->updatePaths(activeHeroes, cfg);
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
objectClusterizer->clusterize();
|
||||
}
|
||||
|
||||
@ -212,6 +218,8 @@ HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) co
|
||||
|
||||
void Nullkiller::makeTurn()
|
||||
{
|
||||
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
|
||||
|
||||
const int MAX_DEPTH = 10;
|
||||
|
||||
resetAiState();
|
||||
|
@ -23,6 +23,7 @@ namespace NKAI
|
||||
{
|
||||
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
||||
boost::mutex AISharedStorage::locker;
|
||||
std::set<int3> commitedTiles;
|
||||
std::set<int3> commitedTilesInitial;
|
||||
|
||||
@ -119,18 +120,6 @@ void AINodeStorage::clear()
|
||||
turnDistanceLimit[HeroRole::SCOUT] = 255;
|
||||
}
|
||||
|
||||
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
|
||||
{
|
||||
return static_cast<const AIPathNode *>(node);
|
||||
}
|
||||
|
||||
void AINodeStorage::updateAINode(CGPathNode * node, std::function<void(AIPathNode *)> updater)
|
||||
{
|
||||
auto aiNode = static_cast<AIPathNode *>(node);
|
||||
|
||||
updater(aiNode);
|
||||
}
|
||||
|
||||
boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
const int3 & pos,
|
||||
const EPathfindingLayer layer,
|
||||
@ -823,13 +812,6 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
|
||||
return candidate;
|
||||
}
|
||||
|
||||
const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
|
||||
{
|
||||
auto aiNode = getAINode(node);
|
||||
|
||||
return aiNode->actor->hero;
|
||||
}
|
||||
|
||||
const std::set<const CGHeroInstance *> AINodeStorage::getAllHeroes() const
|
||||
{
|
||||
std::set<const CGHeroInstance *> heroes;
|
||||
|
@ -135,6 +135,8 @@ class AISharedStorage
|
||||
static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared;
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
|
||||
public:
|
||||
static boost::mutex locker;
|
||||
|
||||
AISharedStorage(int3 mapSize);
|
||||
~AISharedStorage();
|
||||
|
||||
@ -196,8 +198,24 @@ public:
|
||||
int movementLeft,
|
||||
float cost) const;
|
||||
|
||||
const AIPathNode * getAINode(const CGPathNode * node) const;
|
||||
void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
|
||||
inline const AIPathNode * getAINode(const CGPathNode * node) const
|
||||
{
|
||||
return static_cast<const AIPathNode *>(node);
|
||||
}
|
||||
|
||||
inline void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater)
|
||||
{
|
||||
auto aiNode = static_cast<AIPathNode *>(node);
|
||||
|
||||
updater(aiNode);
|
||||
}
|
||||
|
||||
inline const CGHeroInstance * getHero(const CGPathNode * node) const
|
||||
{
|
||||
auto aiNode = getAINode(node);
|
||||
|
||||
return aiNode->actor->hero;
|
||||
}
|
||||
|
||||
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
|
||||
@ -223,18 +241,17 @@ public:
|
||||
void setTownsAndDwellings(
|
||||
const std::vector<const CGTownInstance *> & towns,
|
||||
const std::set<const CGObjectInstance *> & visitableObjs);
|
||||
const CGHeroInstance * getHero(const CGPathNode * node) const;
|
||||
const std::set<const CGHeroInstance *> getAllHeroes() const;
|
||||
void clear();
|
||||
bool calculateHeroChain();
|
||||
bool calculateHeroChainFinal();
|
||||
|
||||
uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero, bool checkGuards) const
|
||||
inline uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero, bool checkGuards) const
|
||||
{
|
||||
return dangerEvaluator->evaluateDanger(tile, hero, checkGuards);
|
||||
}
|
||||
|
||||
uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const
|
||||
inline uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const
|
||||
{
|
||||
double ratio = (double)danger / (armyValue * hero->getFightingStrength());
|
||||
|
||||
@ -243,6 +260,7 @@ public:
|
||||
|
||||
STRONG_INLINE
|
||||
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
|
||||
|
||||
STRONG_INLINE int getBucket(const ChainActor * actor) const
|
||||
{
|
||||
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
|
||||
|
@ -80,6 +80,8 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
|
||||
|
||||
do
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
while(storage->calculateHeroChain())
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
@ -91,6 +93,8 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
|
||||
logAi->trace("Select next actor");
|
||||
} while(storage->selectNextActor());
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
if(storage->calculateHeroChainFinal())
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
|
2
AUTHORS
2
AUTHORS
@ -80,7 +80,7 @@ Dmitry Orlov, <shubus.corporation@gmail.com>
|
||||
* special buildings support in fan towns, new features and bug fixes
|
||||
|
||||
Andrey Cherkas aka nordsoft, <nordsoft@yahoo.com>
|
||||
* new terrain support, random map generator features and various bug fixes
|
||||
* new terrain support, rmg features, map editor, multiplayer improvements, bug fixes
|
||||
|
||||
Andrey Filipenkov aka kambala-decapitator, <decapitator@ukr.net>
|
||||
* iOS support, macOS improvements, various bug fixes
|
||||
|
@ -27,16 +27,27 @@ fi
|
||||
|
||||
VCMI_PACKAGE_FILE_NAME="${TMP_JOBID}-vcmi"
|
||||
VCMI_PACKAGE_NAME_SUFFIX=""
|
||||
VCMI_PACKAGE_GITVERSION="ON"
|
||||
if [ -z "$TMP_PRID" ] || [ "$TMP_PRID" == "false" ];
|
||||
then
|
||||
branch_name=$(echo "$TMP_BRANCH" | sed 's/[^[:alnum:]]\+/_/g')
|
||||
VCMI_PACKAGE_FILE_NAME="${VCMI_PACKAGE_FILE_NAME}-branch-${branch_name}-${TMP_COMMIT}"
|
||||
VCMI_PACKAGE_NAME_SUFFIX="branch ${branch_name}"
|
||||
if [ "${branch_name}" != "master" ];
|
||||
then
|
||||
VCMI_PACKAGE_NAME_SUFFIX="branch ${branch_name}"
|
||||
else
|
||||
VCMI_PACKAGE_GITVERSION="OFF"
|
||||
fi
|
||||
else
|
||||
VCMI_PACKAGE_FILE_NAME="${VCMI_PACKAGE_FILE_NAME}-PR-${TMP_PRID}-${TMP_COMMIT}"
|
||||
VCMI_PACKAGE_NAME_SUFFIX="PR ${TMP_PRID}"
|
||||
fi
|
||||
VCMI_PACKAGE_NAME_SUFFIX="(${VCMI_PACKAGE_NAME_SUFFIX})"
|
||||
|
||||
if [ "${VCMI_PACKAGE_NAME_SUFFIX}" != "" ];
|
||||
then
|
||||
VCMI_PACKAGE_NAME_SUFFIX="(${VCMI_PACKAGE_NAME_SUFFIX})"
|
||||
fi
|
||||
|
||||
export VCMI_PACKAGE_FILE_NAME
|
||||
export VCMI_PACKAGE_NAME_SUFFIX
|
||||
export VCMI_PACKAGE_GITVERSION
|
@ -48,6 +48,7 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
|
||||
option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF)
|
||||
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
|
||||
option(ENABLE_EDITOR "Enable compilation of map editor" ON)
|
||||
option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON)
|
||||
if(APPLE_IOS)
|
||||
set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
|
||||
set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
|
||||
@ -323,7 +324,6 @@ find_package(SDL2_ttf REQUIRED)
|
||||
if(TARGET SDL2_ttf::SDL2_ttf)
|
||||
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
|
||||
endif()
|
||||
find_package(TBB REQUIRED)
|
||||
|
||||
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
# Widgets finds its own dependencies (QtGui and QtCore).
|
||||
@ -331,6 +331,10 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
|
||||
endif()
|
||||
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
find_package(TBB REQUIRED)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LUA)
|
||||
find_package(luajit)
|
||||
# MXE paths hardcoded for current dependencies pack - tried and could not make it work another way
|
||||
@ -393,26 +397,34 @@ else()
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if(ENABLE_MONOLITHIC_INSTALL)
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/")
|
||||
set(BIN_DIR "." CACHE STRING "Where to install binaries")
|
||||
set(LIB_DIR "." CACHE STRING "Where to install main library")
|
||||
set(DATA_DIR "." CACHE STRING "Where to install data files")
|
||||
|
||||
# following constants only used for platforms using XDG (Linux, BSD, etc)
|
||||
add_definitions(-DM_DATA_DIR="${DATA_DIR}")
|
||||
add_definitions(-DM_BIN_DIR="${BIN_DIR}")
|
||||
add_definitions(-DM_LIB_DIR="${LIB_DIR}")
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/")
|
||||
else()
|
||||
if(NOT BIN_DIR)
|
||||
set(BIN_DIR "bin" CACHE STRING "Where to install binaries")
|
||||
set(BIN_DIR "${CMAKE_INSTALL_BINDIR}" CACHE STRING "Where to install binaries")
|
||||
endif()
|
||||
if(NOT LIB_DIR)
|
||||
set(LIB_DIR "${CMAKE_INSTALL_LIBDIR}/vcmi" CACHE STRING "Where to install main library")
|
||||
endif()
|
||||
if(NOT DATA_DIR)
|
||||
set(DATA_DIR "share/vcmi" CACHE STRING "Where to install data files")
|
||||
set(DATA_DIR "${CMAKE_INSTALL_DATAROOTDIR}/vcmi" CACHE STRING "Where to install data files")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# following constants only used for platforms using XDG (Linux, BSD, etc)
|
||||
add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
|
||||
add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
|
||||
add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
# following constants only used for platforms using XDG (Linux, BSD, etc)
|
||||
add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
|
||||
add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
|
||||
add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# iOS has flat libs directory structure
|
||||
|
@ -23,7 +23,8 @@
|
||||
"PACKAGE_FILE_NAME" : "$env{VCMI_PACKAGE_FILE_NAME}",
|
||||
"PACKAGE_NAME_SUFFIX" : "$env{VCMI_PACKAGE_NAME_SUFFIX}",
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"ENABLE_TEST": "OFF"
|
||||
"ENABLE_TEST": "OFF",
|
||||
"ENABLE_GITVERSION": "$env{VCMI_PACKAGE_GITVERSION}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
92
ChangeLog
92
ChangeLog
@ -1,21 +1,69 @@
|
||||
1.0.0 -> 1.1.0
|
||||
|
||||
GENERAL:
|
||||
* Mods and their versions and serialized into save files. Game checks mod compatibility before loading
|
||||
* Logs are stored in system default logs directory
|
||||
* LUA/ERM libs are not compiled by default
|
||||
* FFMpeg dependency is optional now
|
||||
|
||||
MODS:
|
||||
* Supported rewardable objects customization
|
||||
* Battleground obstacles are extendable now with VLC mechanism
|
||||
* Introduced "compatibility" section into mods settings
|
||||
|
||||
LAUNCHER:
|
||||
* Fixed problem with duplicated mods in the list
|
||||
* Launcher shows compatible mods only
|
||||
* Uninstall button was moved to the left of layout
|
||||
|
||||
1.0.0 -> 1.1.0
|
||||
|
||||
GENERAL:
|
||||
* iOS is supported
|
||||
* Mods and their versions and serialized into save files. Game checks mod compatibility before loading
|
||||
* Logs are stored in system default logs directory
|
||||
* LUA/ERM libs are not compiled by default
|
||||
* FFMpeg dependency is optional now
|
||||
* Conan package manager is supported for MacOS and iOS
|
||||
|
||||
MULTIPLAYER:
|
||||
* Map is passed over network, so different platforms are compatible with each other
|
||||
* Server self-killing is more robust
|
||||
* Unlock in-game console while opponent's turn
|
||||
* Host can control game session by using console commands
|
||||
* Control over player is transferred to AI if client escaped the game
|
||||
* Reconnection mode for crashed client processes
|
||||
* Playing online is available using proxy server
|
||||
|
||||
ADVENTURE MAP:
|
||||
* Fix for digging while opponent's turn
|
||||
* Supported right click for quick recruit window
|
||||
* Fixed problem with quests are requiring identical artefacts
|
||||
* Bulk move and swap artifacts
|
||||
* Pause & resume for towns and terrains music themes
|
||||
* Feature to assemble/disassemble artefacts in backpack
|
||||
* Clickable status bar to send messages
|
||||
* Heroes no longer have chance to receive forbidden skill on leveling up
|
||||
* Fixed visibility of newly recruited heroes near town
|
||||
* Fixed missing artifact slot in Artifact Merchant window
|
||||
|
||||
BATTLES:
|
||||
* Fix healing/regeneration behaviour and effect
|
||||
* Fix crashes related to auto battle
|
||||
* Implemented ray projectiles for shooters
|
||||
* Introduced default tower shooter icons
|
||||
* Towers destroyed during battle will no longer be listed as casualties
|
||||
|
||||
AI:
|
||||
* BattleAI: Target prioritizing is now based on damage difference instead of health difference
|
||||
* Nullkiller AI can retreat and surrender
|
||||
* Nullkiller AI doesn't visit allied dwellings anymore
|
||||
* Fixed a few freezes in Nullkiller AI
|
||||
|
||||
RANDOM MAP GENERATOR:
|
||||
* Speedup generation of random maps
|
||||
* Necromancy cannot be learned in Witch Hut on random maps
|
||||
|
||||
MODS:
|
||||
* Supported rewardable objects customization
|
||||
* Battleground obstacles are extendable now with VLC mechanism
|
||||
* Introduced "compatibility" section into mods settings
|
||||
* Fixed bonus system for custom advmap spells
|
||||
* Supported customisable town entrance placement
|
||||
* Fixed validation of mods with new adventure map objects
|
||||
|
||||
LAUNCHER:
|
||||
* Fixed problem with duplicated mods in the list
|
||||
* Launcher shows compatible mods only
|
||||
* Uninstall button was moved to the left of layout
|
||||
* Unsupported resolutions are not shown
|
||||
* Lobby for online gameplay is implemented
|
||||
|
||||
MAP EDITOR:
|
||||
* Basic version of Qt-based map editor
|
||||
|
||||
0.99 -> 1.0.0
|
||||
|
||||
GENERAL:
|
||||
@ -98,10 +146,10 @@ BATTLES:
|
||||
|
||||
ADVENTURE MAP:
|
||||
* Added buttons and keyboard shortcuts to quickly exchange army and artifacts between heroes
|
||||
* Fix: Captured town should not be duplicated on the UI
|
||||
|
||||
LAUNCHER:
|
||||
* Implemented notifications about updates
|
||||
* Fix: Captured town should not be duplicated on the UI
|
||||
|
||||
LAUNCHER:
|
||||
* Implemented notifications about updates
|
||||
* Supported redirection links for downloading mods
|
||||
|
||||
0.98 -> 0.99
|
||||
|
10
Mods/vcmi/config/vcmi/towerCreature.json
Normal file
10
Mods/vcmi/config/vcmi/towerCreature.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"core:arrowTower" :
|
||||
{
|
||||
"graphics" :
|
||||
{
|
||||
"iconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"iconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
}
|
101
Mods/vcmi/config/vcmi/towerFactions.json
Normal file
101
Mods/vcmi/config/vcmi/towerFactions.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"core:castle" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:rampart" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:tower" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:inferno" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:necropolis" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:dungeon" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:stronghold" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:fortress" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:conflux" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,20 @@
|
||||
"name" : "VCMI essential files",
|
||||
"description" : "Essential files required for VCMI to run correctly",
|
||||
|
||||
"version" : "1.0",
|
||||
"version" : "1.1",
|
||||
"author" : "VCMI Team",
|
||||
"contact" : "http://forum.vcmi.eu/index.php",
|
||||
"modType" : "Graphical",
|
||||
|
||||
"factions" : [ "config/vcmi/towerFactions" ],
|
||||
"creatures" : [ "config/vcmi/towerCreature" ],
|
||||
|
||||
"filesystem":
|
||||
{
|
||||
"CONFIG/" :
|
||||
[
|
||||
{"type" : "dir", "path" : "/Config"}
|
||||
],
|
||||
"DATA/" :
|
||||
[
|
||||
{"type" : "dir", "path" : "/Data"}
|
||||
|
@ -721,7 +721,7 @@ void processCommand(const std::string &message)
|
||||
{
|
||||
const JsonNode & object = nameAndObject.second;
|
||||
|
||||
std::string name = CModHandler::normalizeIdentifier(object.meta, "core", nameAndObject.first);
|
||||
std::string name = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first);
|
||||
|
||||
boost::algorithm::replace_all(name,":","_");
|
||||
|
||||
@ -1085,7 +1085,6 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
|
||||
if(!checkVideoMode(displayIndex, w, h))
|
||||
{
|
||||
logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -199,7 +199,10 @@ else()
|
||||
add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS} ${client_ICON})
|
||||
endif(ENABLE_DEBUG_CONSOLE)
|
||||
|
||||
add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI Nullkiller)
|
||||
add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI)
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
add_dependencies(vcmiclient Nullkiller)
|
||||
endif()
|
||||
if(APPLE_IOS)
|
||||
if(ENABLE_ERM)
|
||||
add_dependencies(vcmiclient vcmiERM)
|
||||
|
@ -100,7 +100,7 @@ void Graphics::loadPaletteAndColors()
|
||||
void Graphics::initializeBattleGraphics()
|
||||
{
|
||||
auto allConfigs = VLC->modh->getActiveMods();
|
||||
allConfigs.insert(allConfigs.begin(), "core");
|
||||
allConfigs.insert(allConfigs.begin(), CModHandler::scopeBuiltin());
|
||||
for(auto & mod : allConfigs)
|
||||
{
|
||||
if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/battles_graphics.json")))
|
||||
|
@ -490,7 +490,11 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
||||
int yPos = 344 + step * 97;
|
||||
for(auto & elem : br.casualties[step])
|
||||
{
|
||||
icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", CGI->creatures()->getByIndex(elem.first)->getIconIndex(), 0, xPos, yPos));
|
||||
auto creature = CGI->creatures()->getByIndex(elem.first);
|
||||
if (creature->getId() == CreatureID::ARROW_TOWERS )
|
||||
continue; // do not show destroyed towers in battle results
|
||||
|
||||
icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", creature->getIconIndex(), 0, xPos, yPos));
|
||||
std::ostringstream amount;
|
||||
amount<<elem.second;
|
||||
labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, amount.str()));
|
||||
|
@ -67,6 +67,8 @@ void CCursorHandler::initCursor()
|
||||
|
||||
void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
|
||||
{
|
||||
assert(dndObject == nullptr);
|
||||
|
||||
if(type != this->type)
|
||||
{
|
||||
this->type = type;
|
||||
|
@ -254,6 +254,22 @@ void CHeroArtPlace::clickRight(tribool down, bool previousState)
|
||||
}
|
||||
}
|
||||
|
||||
void CArtifactsOfHero::activate()
|
||||
{
|
||||
if (commonInfo->src.AOH == this && commonInfo->src.art)
|
||||
CCS->curh->dragAndDropCursor(make_unique<CAnimImage>("artifact", commonInfo->src.art->artType->getIconIndex()));
|
||||
|
||||
CIntObject::activate();
|
||||
}
|
||||
|
||||
void CArtifactsOfHero::deactivate()
|
||||
{
|
||||
if (commonInfo->src.AOH == this && commonInfo->src.art)
|
||||
CCS->curh->dragAndDropCursor(nullptr);
|
||||
|
||||
CIntObject::deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects artifact slot so that the containing artifact looks like it's picked up.
|
||||
*/
|
||||
@ -675,10 +691,8 @@ void CArtifactsOfHero::updateParentWindow()
|
||||
|
||||
if(!updateState)
|
||||
{
|
||||
cew->deactivate();
|
||||
cew->updateWidgets();
|
||||
cew->redraw();
|
||||
cew->activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,9 @@ public:
|
||||
void dispose(); //free resources not needed after closing windows and reset state
|
||||
void scrollBackpack(int dir); //dir==-1 => to left; dir==1 => to right
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
|
||||
void safeRedraw();
|
||||
void markPossibleSlots(const CArtifactInstance* art);
|
||||
void unmarkSlots(bool withRedraw = true); //unmarks slots in all visible AOHs
|
||||
|
@ -676,6 +676,10 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
|
||||
break;
|
||||
case EMarketMode::ARTIFACT_RESOURCE:
|
||||
title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name();
|
||||
|
||||
// create image that copies part of background containing slot MISC_1 into position of slot MISC_5
|
||||
// this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing
|
||||
images.push_back(std::make_shared<CPicture>(background->bg, Rect(20, 187, 47, 47), 18, 339 ));
|
||||
sliderNeeded = false;
|
||||
break;
|
||||
default:
|
||||
|
@ -21,6 +21,7 @@ VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CSlider;
|
||||
class CTextBox;
|
||||
class CPicture;
|
||||
class CGStatusBar;
|
||||
|
||||
class CTradeWindow : public CWindowObject, public CWindowWithArtifacts //base for markets and altar of sacrifice
|
||||
@ -108,6 +109,7 @@ public:
|
||||
protected:
|
||||
std::shared_ptr<CGStatusBar> statusBar;
|
||||
std::vector<std::shared_ptr<CLabel>> labels;
|
||||
std::vector<std::shared_ptr<CPicture>> images;
|
||||
std::vector<std::shared_ptr<CButton>> buttons;
|
||||
std::vector<std::shared_ptr<CTextBox>> texts;
|
||||
};
|
||||
|
@ -123,8 +123,6 @@
|
||||
},
|
||||
"graphics" :
|
||||
{
|
||||
"iconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"iconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"animation": "CLCBOW.DEF" // needed to pass validation, never used
|
||||
},
|
||||
"sound": {}
|
||||
|
@ -203,8 +203,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "archer",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGCS",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -210,8 +210,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "stormElemental",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGEL",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -204,8 +204,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "medusa",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGDN",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -209,8 +209,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "lizardman",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGFR",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -204,8 +204,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "gog",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGIN",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -214,8 +214,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "lich",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGNC",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -211,8 +211,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "woodElf",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGRM",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -203,8 +203,6 @@
|
||||
{
|
||||
"shooter" : "orc",
|
||||
"imagePrefix" : "SGST",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"gate" :
|
||||
{
|
||||
"arch" : { "x" : 478, "y" : 235 },
|
||||
|
@ -202,8 +202,6 @@
|
||||
"siege" :
|
||||
{
|
||||
"shooter" : "mage",
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
"imagePrefix" : "SGTW",
|
||||
"gate" :
|
||||
{
|
||||
|
@ -56,6 +56,23 @@
|
||||
"description": "List of mods that can't be enabled in the same time as this one",
|
||||
"items": { "type":"string" }
|
||||
},
|
||||
"compatibility" : {
|
||||
"type":"object",
|
||||
"description": "Supported versions of vcmi engine",
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"min" : {
|
||||
"type" : "string",
|
||||
"description" : "minimal compatible vcmi engine version in a format major.minor.patch. When specified, earlier versions won't be supported"
|
||||
//"pattern" : "^\\d+\\.\\d+\\.\\d+$" // Not implemented in schema support
|
||||
},
|
||||
"max" : {
|
||||
"type" : "string",
|
||||
"description" : "maximum compatible vcmi engine version in a format major.minor.patch. When specified, later versions won't be supported"
|
||||
//"pattern" : "^\\d+\\.\\d+\\.\\d+$" // Not implemented in schema support
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"keepDisabled" : {
|
||||
"type":"boolean",
|
||||
@ -118,6 +135,12 @@
|
||||
"description": "List of configuration files for battlefields",
|
||||
"items": { "type":"string", "format" : "textFile" }
|
||||
|
||||
},
|
||||
"obstacles":{
|
||||
"type":"array",
|
||||
"description": "List of configuration files for obstacles",
|
||||
"items": { "type":"string", "format" : "textFile" }
|
||||
|
||||
},
|
||||
|
||||
"changelog" : {
|
||||
|
12
debian/changelog
vendored
12
debian/changelog
vendored
@ -1,3 +1,15 @@
|
||||
vcmi (1.2.0) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 23 Dec 2022 16:00:00 +0200
|
||||
|
||||
vcmi (1.1.0) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 23 Dec 2022 12:00:00 +0200
|
||||
|
||||
vcmi (1.0.0) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
@ -38,6 +38,8 @@
|
||||
<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
|
||||
<url type="faq">https://vcmi.eu/faq/</url>
|
||||
<releases>
|
||||
<release version="1.2.0" date="2022-12-24" type="development" />
|
||||
<release version="1.1.0" date="2022-12-23" />
|
||||
<release version="1.0.0" date="2022-09-11" />
|
||||
<release version="0.99" date="2016-11-01" />
|
||||
</releases>
|
||||
|
@ -31,7 +31,7 @@ bool isCompatible(const QString & verMin, const QString & verMax)
|
||||
if(ver.segmentCount() < maxSections)
|
||||
{
|
||||
auto segments = ver.segments();
|
||||
for(int i = segments.size() - 1; i < maxSections; ++i)
|
||||
for(int i = segments.size(); i < maxSections; ++i)
|
||||
segments.append(0);
|
||||
ver = QVersionNumber(segments);
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ std::vector<JsonNode> CArtHandler::loadLegacyData(size_t dataSize)
|
||||
|
||||
void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), objects.size());
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), objects.size());
|
||||
|
||||
object->iconIndex = object->getIndex() + 5;
|
||||
|
||||
@ -291,7 +291,7 @@ void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode
|
||||
|
||||
void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
|
||||
|
||||
object->iconIndex = object->getIndex();
|
||||
|
||||
@ -861,8 +861,6 @@ void CArtifactInstance::removeFrom(ArtifactLocation al)
|
||||
al.getHolderArtSet()->eraseArtSlot(al.slot);
|
||||
if(!ArtifactUtils::isSlotBackpack(al.slot))
|
||||
al.getHolderNode()->detachFrom(*this);
|
||||
|
||||
//TODO delete me?
|
||||
}
|
||||
|
||||
bool CArtifactInstance::canBeDisassembled() const
|
||||
@ -1062,7 +1060,9 @@ void CCombinedArtifactInstance::createConstituents()
|
||||
|
||||
void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, ArtifactPosition slot)
|
||||
{
|
||||
assert(vstd::contains(*artType->constituents, art->artType.get()));
|
||||
assert(vstd::contains_if(*artType->constituents, [=](const CArtifact * constituent){
|
||||
return constituent->id == art->artType->id;
|
||||
}));
|
||||
assert(art->getParentNodes().size() == 1 && art->getParentNodes().front() == art->artType);
|
||||
constituentsInfo.push_back(ConstituentInfo(art, slot));
|
||||
attachTo(*art);
|
||||
@ -1354,11 +1354,16 @@ bool CArtifactSet::isPositionFree(ArtifactPosition pos, bool onlyLockCheck) cons
|
||||
ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot)
|
||||
{
|
||||
assert(!vstd::contains(artifactsWorn, slot));
|
||||
ArtSlotInfo &ret = !ArtifactUtils::isSlotBackpack(slot)
|
||||
? artifactsWorn[slot]
|
||||
: *artifactsInBackpack.insert(artifactsInBackpack.begin() + (slot - GameConstants::BACKPACK_START), ArtSlotInfo());
|
||||
|
||||
return ret;
|
||||
if (!ArtifactUtils::isSlotBackpack(slot))
|
||||
return artifactsWorn[slot];
|
||||
|
||||
ArtSlotInfo newSlot;
|
||||
size_t index = slot - GameConstants::BACKPACK_START;
|
||||
auto position = artifactsInBackpack.begin() + index;
|
||||
auto inserted = artifactsInBackpack.insert(position, newSlot);
|
||||
|
||||
return *inserted;
|
||||
}
|
||||
|
||||
void CArtifactSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked)
|
||||
@ -1372,9 +1377,10 @@ void CArtifactSet::eraseArtSlot(ArtifactPosition slot)
|
||||
{
|
||||
if(ArtifactUtils::isSlotBackpack(slot))
|
||||
{
|
||||
assert(artifactsInBackpack.begin() + slot < artifactsInBackpack.end());
|
||||
slot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
|
||||
artifactsInBackpack.erase(artifactsInBackpack.begin() + slot);
|
||||
auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
|
||||
|
||||
assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end());
|
||||
artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1547,16 +1553,18 @@ DLL_LINKAGE ArtifactPosition ArtifactUtils::getArtifactDstPosition( const CArtif
|
||||
|
||||
DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUtils::unmovableSlots()
|
||||
{
|
||||
return
|
||||
static const std::vector<ArtifactPosition::EArtifactPosition> positions =
|
||||
{
|
||||
ArtifactPosition::SPELLBOOK,
|
||||
ArtifactPosition::MACH4
|
||||
};
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUtils::constituentWornSlots()
|
||||
{
|
||||
return
|
||||
static const std::vector<ArtifactPosition::EArtifactPosition> positions =
|
||||
{
|
||||
ArtifactPosition::HEAD,
|
||||
ArtifactPosition::SHOULDERS,
|
||||
@ -1573,6 +1581,8 @@ DLL_LINKAGE const std::vector<ArtifactPosition::EArtifactPosition> & ArtifactUti
|
||||
ArtifactPosition::MISC4,
|
||||
ArtifactPosition::MISC5,
|
||||
};
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot)
|
||||
|
@ -425,7 +425,7 @@ const CCreature * CCreatureHandler::getCreature(const std::string & scope, const
|
||||
void CCreatureHandler::loadCommanders()
|
||||
{
|
||||
JsonNode data(ResourceID("config/commanders.json"));
|
||||
data.setMeta("core"); // assume that commanders are in core mod (for proper bonuses resolution)
|
||||
data.setMeta(CModHandler::scopeBuiltin()); // assume that commanders are in core mod (for proper bonuses resolution)
|
||||
|
||||
const JsonNode & config = data; // switch to const data accessors
|
||||
|
||||
|
@ -1053,7 +1053,7 @@ void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
|
||||
std::string typeName("");
|
||||
handler.serializeString("type", typeName);
|
||||
if(!typeName.empty())
|
||||
setType(VLC->creh->getCreature("core", typeName));
|
||||
setType(VLC->creh->getCreature(CModHandler::scopeMap(), typeName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -760,6 +760,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
|
||||
initHeroes();
|
||||
initStartingBonus();
|
||||
initTowns();
|
||||
placeHeroesInTowns();
|
||||
initMapObjects();
|
||||
buildBonusSystemTree();
|
||||
initVisitingAndGarrisonedHeroes();
|
||||
@ -1363,7 +1364,8 @@ void CGameState::placeStartingHeroes()
|
||||
{
|
||||
if(auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap())
|
||||
{
|
||||
if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1)) continue;
|
||||
if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1861,6 +1863,37 @@ void CGameState::initMapObjects()
|
||||
map->calculateGuardingGreaturePositions(); //calculate once again when all the guards are placed and initialized
|
||||
}
|
||||
|
||||
void CGameState::placeHeroesInTowns()
|
||||
{
|
||||
for(auto k=players.begin(); k!=players.end(); ++k)
|
||||
{
|
||||
if(k->first==PlayerColor::NEUTRAL)
|
||||
continue;
|
||||
|
||||
for(CGHeroInstance *h : k->second.heroes)
|
||||
{
|
||||
for(CGTownInstance *t : k->second.towns)
|
||||
{
|
||||
bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos().x, h->visitablePos().y);
|
||||
|
||||
// current hero position is at one of blocking tiles of current town
|
||||
// assume that this hero should be visiting the town (H3M format quirk) and move hero to correct position
|
||||
if (heroOnTownBlockableTile)
|
||||
{
|
||||
int3 townVisitablePos = t->visitablePos();
|
||||
int3 correctedPos = townVisitablePos + h->getVisitableOffset();
|
||||
|
||||
map->removeBlockVisTiles(h);
|
||||
h->pos = correctedPos;
|
||||
map->addBlockVisTiles(h);
|
||||
|
||||
assert(t->visitableAt(h->visitablePos().x, h->visitablePos().y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CGameState::initVisitingAndGarrisonedHeroes()
|
||||
{
|
||||
for(auto k=players.begin(); k!=players.end(); ++k)
|
||||
@ -1873,17 +1906,10 @@ void CGameState::initVisitingAndGarrisonedHeroes()
|
||||
{
|
||||
for(CGTownInstance *t : k->second.towns)
|
||||
{
|
||||
int3 vistile = t->visitablePos(); vistile.x++; //tile next to the entrance
|
||||
if(vistile == h->pos || h->pos==t->visitablePos())
|
||||
if (t->visitableAt(h->visitablePos().x, h->visitablePos().y))
|
||||
{
|
||||
assert(t->visitingHero == nullptr);
|
||||
t->setVisitingHero(h);
|
||||
if(h->pos == t->pos) //visiting hero placed in the editor has same pos as the town - we need to correct it
|
||||
{
|
||||
map->removeBlockVisTiles(h);
|
||||
h->pos.x -= 1;
|
||||
map->addBlockVisTiles(h);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,6 +269,7 @@ private:
|
||||
void placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeId, int3 townPos);
|
||||
void initStartingResources();
|
||||
void initHeroes();
|
||||
void placeHeroesInTowns();
|
||||
void giveCampaignBonusToHero(CGHeroInstance * hero);
|
||||
void initFogOfWar();
|
||||
void initStartingBonus();
|
||||
|
@ -348,7 +348,7 @@ CHeroHandler::CHeroHandler()
|
||||
loadTerrains();
|
||||
for(const auto & terrain : VLC->terrainTypeHandler->terrains())
|
||||
{
|
||||
VLC->modh->identifiers.registerObject("core", "terrain", terrain.name, terrain.id);
|
||||
VLC->modh->identifiers.registerObject(CModHandler::scopeBuiltin(), "terrain", terrain.name, terrain.id);
|
||||
}
|
||||
loadBallistics();
|
||||
loadExperience();
|
||||
@ -868,7 +868,7 @@ std::vector<JsonNode> CHeroHandler::loadLegacyData(size_t dataSize)
|
||||
void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
|
||||
{
|
||||
size_t index = objects.size();
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
|
||||
object->imageIndex = (si32)index + GameConstants::HERO_PORTRAIT_SHIFT; // 2 special frames + some extra portraits
|
||||
|
||||
objects.push_back(object);
|
||||
@ -878,7 +878,7 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
|
||||
|
||||
void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
|
||||
object->imageIndex = static_cast<si32>(index);
|
||||
|
||||
assert(objects[index] == nullptr); // ensure that this id was not loaded before
|
||||
|
@ -209,16 +209,15 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
|
||||
// called have not specified destination mod explicitly
|
||||
if (request.remoteScope.empty())
|
||||
{
|
||||
// FIXME: temporary, for queries from map loader allow access to any identifer
|
||||
// should be changed to list of mods that are marked as required by current map
|
||||
if (request.localScope == "map")
|
||||
// special scope that should have access to all in-game objects
|
||||
if (request.localScope == CModHandler::scopeGame())
|
||||
{
|
||||
for (auto const & modName : VLC->modh->getActiveMods())
|
||||
allowedScopes.insert(modName);
|
||||
}
|
||||
|
||||
// normally ID's from all required mods, own mod and virtual "core" mod are allowed
|
||||
else if(request.localScope != "core" && !request.localScope.empty())
|
||||
// normally ID's from all required mods, own mod and virtual built-in mod are allowed
|
||||
else if(request.localScope != CModHandler::scopeBuiltin() && !request.localScope.empty())
|
||||
{
|
||||
allowedScopes = VLC->modh->getModDependencies(request.localScope, isValidScope);
|
||||
|
||||
@ -229,15 +228,19 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
|
||||
}
|
||||
|
||||
// all mods can access built-in mod
|
||||
allowedScopes.insert("core");
|
||||
allowedScopes.insert(CModHandler::scopeBuiltin());
|
||||
}
|
||||
else
|
||||
{
|
||||
//if destination mod was specified explicitly, restrict lookup to this mod
|
||||
|
||||
if(request.remoteScope == "core" )
|
||||
if(request.remoteScope == CModHandler::scopeBuiltin() )
|
||||
{
|
||||
//"core" mod is an implicit dependency for all mods, allow access into it
|
||||
//built-in mod is an implicit dependency for all mods, allow access into it
|
||||
allowedScopes.insert(request.remoteScope);
|
||||
}
|
||||
else if ( request.localScope == CModHandler::scopeGame() )
|
||||
{
|
||||
// allow access, this is special scope that should have access to all in-game objects
|
||||
allowedScopes.insert(request.remoteScope);
|
||||
}
|
||||
else if(request.remoteScope == request.localScope )
|
||||
@ -340,7 +343,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objec
|
||||
{
|
||||
for(auto & node : originalData)
|
||||
{
|
||||
node.setMeta("core");
|
||||
node.setMeta(CModHandler::scopeBuiltin());
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,6 +404,9 @@ bool ContentTypeHandler::loadMod(std::string modName, bool validate)
|
||||
|
||||
if (vstd::contains(data.Struct(), "index") && !data["index"].isNull())
|
||||
{
|
||||
if (modName != "core")
|
||||
logMod->warn("Mod %s is attempting to load original data! This should be reserved for built-in mod.", modName);
|
||||
|
||||
// try to add H3 object data
|
||||
size_t index = static_cast<size_t>(data["index"].Float());
|
||||
|
||||
@ -413,7 +419,7 @@ bool ContentTypeHandler::loadMod(std::string modName, bool validate)
|
||||
}
|
||||
else
|
||||
{
|
||||
logMod->warn("no original data in loadMod(%s) at index %d", name, index);
|
||||
logMod->trace("no original data in loadMod(%s) at index %d", name, index);
|
||||
}
|
||||
performValidate(data, name);
|
||||
handler->loadObject(modName, name, data, index);
|
||||
@ -506,7 +512,7 @@ void CContentHandler::preloadData(CModInfo & mod)
|
||||
// print message in format [<8-symbols checksum>] <modname>
|
||||
logMod->info("\t\t[%08x]%s", mod.checksum, mod.name);
|
||||
|
||||
if (validate && mod.identifier != "core")
|
||||
if (validate && mod.identifier != CModHandler::scopeBuiltin())
|
||||
{
|
||||
if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier))
|
||||
mod.validation = CModInfo::FAILED;
|
||||
@ -698,13 +704,13 @@ CModHandler::CModHandler() : content(std::make_shared<CContentHandler>())
|
||||
modules.MITHRIL = false;
|
||||
for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i)
|
||||
{
|
||||
identifiers.registerObject("core", "resource", GameConstants::RESOURCE_NAMES[i], i);
|
||||
identifiers.registerObject(CModHandler::scopeBuiltin(), "resource", GameConstants::RESOURCE_NAMES[i], i);
|
||||
}
|
||||
|
||||
for(int i=0; i<GameConstants::PRIMARY_SKILLS; ++i)
|
||||
{
|
||||
identifiers.registerObject("core", "primSkill", PrimarySkill::names[i], i);
|
||||
identifiers.registerObject("core", "primarySkill", PrimarySkill::names[i], i);
|
||||
identifiers.registerObject(CModHandler::scopeBuiltin(), "primSkill", PrimarySkill::names[i], i);
|
||||
identifiers.registerObject(CModHandler::scopeBuiltin(), "primarySkill", PrimarySkill::names[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -905,6 +911,35 @@ std::vector<std::string> CModHandler::getModList(std::string path)
|
||||
return foundMods;
|
||||
}
|
||||
|
||||
bool CModHandler::isScopeReserved(const TModID & scope)
|
||||
{
|
||||
static const std::array<TModID, 3> reservedScopes = {
|
||||
"core", "map", "game"
|
||||
};
|
||||
|
||||
return std::find(reservedScopes.begin(), reservedScopes.end(), scope) != reservedScopes.end();
|
||||
}
|
||||
|
||||
const TModID & CModHandler::scopeBuiltin()
|
||||
{
|
||||
static const TModID scope = "core";
|
||||
return scope;
|
||||
}
|
||||
|
||||
const TModID & CModHandler::scopeGame()
|
||||
{
|
||||
static const TModID scope = "game";
|
||||
return scope;
|
||||
}
|
||||
|
||||
const TModID & CModHandler::scopeMap()
|
||||
{
|
||||
//TODO: implement accessing map dependencies for both H3 and VCMI maps
|
||||
// for now, allow access to any identifiers
|
||||
static const TModID scope = "game";
|
||||
return scope;
|
||||
}
|
||||
|
||||
void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods)
|
||||
{
|
||||
for(std::string modName : getModList(path))
|
||||
@ -916,6 +951,12 @@ void CModHandler::loadOneMod(std::string modName, std::string parent, const Json
|
||||
boost::to_lower(modName);
|
||||
std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
|
||||
|
||||
if ( isScopeReserved(modFullName))
|
||||
{
|
||||
logMod->error("Can not load mod %s - this name is reserved for internal use!", modFullName);
|
||||
return;
|
||||
}
|
||||
|
||||
if(CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
|
||||
{
|
||||
CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
|
||||
@ -944,7 +985,7 @@ void CModHandler::loadMods(bool onlyEssential)
|
||||
loadMods("", "", modConfig["activeMods"], true);
|
||||
}
|
||||
|
||||
coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json")));
|
||||
coreMod = CModInfo(CModHandler::scopeBuiltin(), modConfig[CModHandler::scopeBuiltin()], JsonNode(ResourceID("config/gameConfig.json")));
|
||||
coreMod.name = "Original game files";
|
||||
}
|
||||
|
||||
@ -991,7 +1032,7 @@ static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoade
|
||||
|
||||
// second - add mod.json into checksum because filesystem does not contains this file
|
||||
// FIXME: remove workaround for core mod
|
||||
if (modName != "core")
|
||||
if (modName != CModHandler::scopeBuiltin())
|
||||
{
|
||||
ResourceID modConfFile(CModInfo::getModFile(modName), EResType::TEXT);
|
||||
ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32();
|
||||
@ -1017,7 +1058,7 @@ void CModHandler::loadModFilesystems()
|
||||
{
|
||||
activeMods = validateAndSortDependencies(activeMods);
|
||||
|
||||
coreMod.updateChecksum(calculateModChecksum("core", CResourceHandler::get("core")));
|
||||
coreMod.updateChecksum(calculateModChecksum(CModHandler::scopeBuiltin(), CResourceHandler::get(CModHandler::scopeBuiltin())));
|
||||
|
||||
for(std::string & modName : activeMods)
|
||||
{
|
||||
@ -1057,7 +1098,7 @@ void CModHandler::load()
|
||||
allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName)));
|
||||
}
|
||||
|
||||
// first - load virtual "core" mod that contains all data
|
||||
// first - load virtual builtin mod that contains all data
|
||||
// TODO? move all data into real mods? RoE, AB, SoD, WoG
|
||||
content->preloadData(coreMod);
|
||||
for(const TModID & modName : activeMods)
|
||||
@ -1096,7 +1137,7 @@ void CModHandler::afterLoad(bool onlyEssential)
|
||||
|
||||
modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData();
|
||||
}
|
||||
modSettings["core"] = coreMod.saveLocalData();
|
||||
modSettings[CModHandler::scopeBuiltin()] = coreMod.saveLocalData();
|
||||
|
||||
if(!onlyEssential)
|
||||
{
|
||||
|
@ -14,6 +14,12 @@
|
||||
#include "VCMI_Lib.h"
|
||||
#include "JsonNode.h"
|
||||
|
||||
#ifdef __UCLIBC__
|
||||
#undef major
|
||||
#undef minor
|
||||
#undef patch
|
||||
#endif
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CModHandler;
|
||||
@ -277,7 +283,19 @@ class DLL_LINKAGE CModHandler
|
||||
void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods);
|
||||
void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods);
|
||||
public:
|
||||
|
||||
|
||||
/// returns true if scope is reserved for internal use and can not be used by mods
|
||||
static bool isScopeReserved(const TModID & scope);
|
||||
|
||||
/// reserved scope name for referencing built-in (e.g. H3) objects
|
||||
static const TModID & scopeBuiltin();
|
||||
|
||||
/// reserved scope name for accessing objects from any loaded mod
|
||||
static const TModID & scopeGame();
|
||||
|
||||
/// reserved scope name for accessing object for map loading
|
||||
static const TModID & scopeMap();
|
||||
|
||||
class DLL_LINKAGE Incompatibility: public std::exception
|
||||
{
|
||||
public:
|
||||
|
@ -625,7 +625,7 @@ void LayerTransitionRule::process(
|
||||
else if(destination.node->accessible != CGPathNode::ACCESSIBLE)
|
||||
{
|
||||
/// Hero that fly can only land on accessible tiles
|
||||
if(!destination.isGuardianTile && destination.nodeObject)
|
||||
if(destination.nodeObject)
|
||||
destination.blocked = true;
|
||||
}
|
||||
|
||||
|
@ -267,7 +267,7 @@ std::vector<bool> CSkillHandler::getDefaultAllowed() const
|
||||
|
||||
si32 CSkillHandler::decodeSkill(const std::string & identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "skill", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "skill", identifier);
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
else
|
||||
|
@ -1010,7 +1010,7 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
|
||||
|
||||
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), objects.size());
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), objects.size());
|
||||
|
||||
objects.push_back(object);
|
||||
|
||||
@ -1049,7 +1049,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
|
||||
|
||||
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index);
|
||||
|
||||
if (objects.size() > index)
|
||||
assert(objects[index] == nullptr); // ensure that this id was not loaded before
|
||||
@ -1083,7 +1083,7 @@ void CTownHandler::loadRandomFaction()
|
||||
static const ResourceID randomFactionPath("config/factions/random.json");
|
||||
|
||||
JsonNode randomFactionJson(randomFactionPath);
|
||||
randomFactionJson.setMeta("core", true);
|
||||
randomFactionJson.setMeta(CModHandler::scopeBuiltin(), true);
|
||||
loadBuildings(randomTown, randomFactionJson["random"]["town"]["buildings"]);
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ namespace GameConstants
|
||||
|
||||
si32 HeroTypeID::decode(const std::string & identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", identifier);
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
else
|
||||
@ -86,7 +86,7 @@ const Artifact * ArtifactID::toArtifact(const ArtifactService * service) const
|
||||
|
||||
si32 ArtifactID::decode(const std::string & identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "artifact", identifier);
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
else
|
||||
@ -110,7 +110,7 @@ const Creature * CreatureID::toCreature(const CreatureService * creatures) const
|
||||
|
||||
si32 CreatureID::decode(const std::string & identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", identifier);
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
else
|
||||
@ -139,7 +139,7 @@ const spells::Spell * SpellID::toSpell(const spells::Service * service) const
|
||||
|
||||
si32 SpellID::decode(const std::string & identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "spell", identifier);
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
else
|
||||
@ -201,7 +201,7 @@ const FactionID FactionID::NEUTRAL = FactionID(9);
|
||||
|
||||
si32 FactionID::decode(const std::string & identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "faction", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "faction", identifier);
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
else
|
||||
@ -289,7 +289,7 @@ const BattleFieldInfo * BattleField::getInfo() const
|
||||
|
||||
BattleField BattleField::fromString(std::string identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "battlefield", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "battlefield", identifier);
|
||||
|
||||
if(rawId)
|
||||
return BattleField(rawId.get());
|
||||
@ -309,7 +309,7 @@ Obstacle::operator std::string() const
|
||||
|
||||
Obstacle Obstacle::fromString(std::string identifier)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "obstacle", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "obstacle", identifier);
|
||||
|
||||
if(rawId)
|
||||
return Obstacle(rawId.get());
|
||||
|
@ -14,6 +14,10 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
std::string IHandlerBase::getScopeBuiltin() const
|
||||
{
|
||||
return CModHandler::scopeBuiltin();
|
||||
}
|
||||
|
||||
void IHandlerBase::registerObject(std::string scope, std::string type_name, std::string name, si32 index)
|
||||
{
|
||||
|
@ -21,6 +21,8 @@ class Entity;
|
||||
class DLL_LINKAGE IHandlerBase
|
||||
{
|
||||
protected:
|
||||
std::string getScopeBuiltin() const;
|
||||
|
||||
/// Calls modhandler. Mostly needed to avoid large number of includes in headers
|
||||
void registerObject(std::string scope, std::string type_name, std::string name, si32 index);
|
||||
std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const;
|
||||
@ -92,7 +94,7 @@ public:
|
||||
|
||||
void loadObject(std::string scope, std::string name, const JsonNode & data) override
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), objects.size());
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, getScopeBuiltin(), name), objects.size());
|
||||
|
||||
objects.push_back(object);
|
||||
|
||||
@ -102,7 +104,7 @@ public:
|
||||
|
||||
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name), index);
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, getScopeBuiltin(), name), index);
|
||||
|
||||
assert(objects[index] == nullptr); // ensure that this id was not loaded before
|
||||
objects[index] = object;
|
||||
|
@ -999,7 +999,7 @@ namespace
|
||||
bool testFilePresence(std::string scope, ResourceID resource)
|
||||
{
|
||||
std::set<std::string> allowedScopes;
|
||||
if(scope != "core" && !scope.empty()) // all real mods may have dependencies
|
||||
if(scope != CModHandler::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
|
||||
{
|
||||
//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
|
||||
bool found = true;
|
||||
@ -1008,7 +1008,7 @@ namespace
|
||||
if(!found)
|
||||
return false;
|
||||
|
||||
allowedScopes.insert("core"); // all mods can use H3 files
|
||||
allowedScopes.insert(CModHandler::scopeBuiltin()); // all mods can use H3 files
|
||||
}
|
||||
allowedScopes.insert(scope); // mods can use their own files
|
||||
|
||||
|
@ -703,13 +703,18 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs)
|
||||
//bonus system
|
||||
h->detachFrom(gs->globalEffects);
|
||||
h->attachTo(*gs->getPlayerState(player));
|
||||
h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front();
|
||||
|
||||
auto oldOffset = h->getVisitableOffset();
|
||||
gs->map->removeBlockVisTiles(h,true);
|
||||
h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front();
|
||||
auto newOffset = h->getVisitableOffset();
|
||||
|
||||
h->setOwner(player);
|
||||
h->movement = h->maxMovePoints(true);
|
||||
h->pos = h->pos - oldOffset + newOffset;
|
||||
gs->map->heroesOnMap.push_back(h);
|
||||
gs->getPlayerState(h->getOwner())->heroes.push_back(h);
|
||||
|
||||
gs->map->addBlockVisTiles(h);
|
||||
h->inTownGarrison = false;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ ScriptPtr ScriptHandler::loadFromJson(vstd::CLoggerBase * logger, const std::str
|
||||
|
||||
void ScriptHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
|
||||
{
|
||||
auto object = loadFromJson(logMod, scope, data, normalizeIdentifier(scope, "core", name));
|
||||
auto object = loadFromJson(logMod, scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name));
|
||||
objects[object->identifier] = object;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
TerrainTypeHandler::TerrainTypeHandler()
|
||||
{
|
||||
auto allConfigs = VLC->modh->getActiveMods();
|
||||
allConfigs.insert(allConfigs.begin(), "core");
|
||||
allConfigs.insert(allConfigs.begin(), CModHandler::scopeBuiltin());
|
||||
|
||||
initRivers(allConfigs);
|
||||
recreateRiverMaps();
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "../GameConstants.h"
|
||||
#include "../VCMIDirs.h"
|
||||
#include "../CStopWatch.h"
|
||||
#include "../CModHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -207,7 +208,7 @@ void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives
|
||||
|
||||
const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
|
||||
|
||||
addFilesystem("data", "core", createFileSystem("", fsConfig["filesystem"], extractArchives));
|
||||
addFilesystem("data", CModHandler::scopeBuiltin(), createFileSystem("", fsConfig["filesystem"], extractArchives));
|
||||
}
|
||||
|
||||
void CResourceHandler::addFilesystem(const std::string & parent, const std::string & identifier, ISimpleResourceLoader * loader)
|
||||
|
@ -1413,7 +1413,7 @@ void CGHeroInstance::setHeroTypeName(const std::string & identifier)
|
||||
{
|
||||
if(ID == Obj::HERO || ID == Obj::PRISON)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", identifier);
|
||||
|
||||
if(rawId)
|
||||
subID = rawId.get();
|
||||
@ -1434,7 +1434,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
|
||||
handler.serializeString("biography", biography);
|
||||
handler.serializeInt("experience", exp, 0);
|
||||
|
||||
if (!handler.saving)
|
||||
if(!handler.saving && exp != 0xffffffff) //do not gain levels if experience is not initialized
|
||||
{
|
||||
while (gainsLevel())
|
||||
{
|
||||
|
@ -1466,7 +1466,7 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
{
|
||||
auto decodeBuilding = [this](const std::string & identifier) -> si32
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", getTown()->getBuildingScope(), identifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), getTown()->getBuildingScope(), identifier);
|
||||
|
||||
if(rawId)
|
||||
return rawId.get();
|
||||
|
@ -170,7 +170,7 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons
|
||||
logGlobal->error("Handler with name %s was not found!", obj->handlerName);
|
||||
return;
|
||||
}
|
||||
const auto convertedId = VLC->modh->normalizeIdentifier(entry.meta, "core", identifier);
|
||||
const auto convertedId = VLC->modh->normalizeIdentifier(entry.meta, CModHandler::scopeBuiltin(), identifier);
|
||||
const auto & entryIndex = entry["index"];
|
||||
bool useSelectNextID = !isSubobject || entryIndex.isNull();
|
||||
|
||||
@ -259,14 +259,14 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co
|
||||
|
||||
void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name));
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name));
|
||||
objects[object->id] = object;
|
||||
VLC->modh->identifiers.registerObject(scope, "object", name, object->id);
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
|
||||
{
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, "core", name));
|
||||
auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name));
|
||||
assert(objects[(si32)index] == nullptr); // ensure that this id was not loaded before
|
||||
objects[(si32)index] = object;
|
||||
VLC->modh->identifiers.registerObject(scope, "object", name, object->id);
|
||||
@ -308,8 +308,9 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype)
|
||||
if (objects.at(type)->subObjects.count(subtype))
|
||||
return objects.at(type)->subObjects.at(subtype);
|
||||
}
|
||||
logGlobal->error("Failed to find object of type %d:%d", type, subtype);
|
||||
throw std::runtime_error("Object type handler not found");
|
||||
std::string errorString = "Failed to find object of type " + std::to_string(type) + "::" + std::to_string(subtype);
|
||||
logGlobal->error(errorString);
|
||||
throw std::runtime_error(errorString);
|
||||
}
|
||||
|
||||
TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std::string type, std::string subtype) const
|
||||
@ -325,8 +326,9 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std::
|
||||
return object->subObjects.at(subId);
|
||||
}
|
||||
}
|
||||
logGlobal->error("Failed to find object of type %s::%s", type, subtype);
|
||||
throw std::runtime_error("Object type handler not found");
|
||||
std::string errorString = "Failed to find object of type " + type + "::" + subtype;
|
||||
logGlobal->error(errorString);
|
||||
throw std::runtime_error(errorString);
|
||||
}
|
||||
|
||||
TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID compoundIdentifier) const
|
||||
|
@ -195,7 +195,9 @@ std::set<int3> CGObjectInstance::getBlockedOffsets() const
|
||||
|
||||
void CGObjectInstance::setType(si32 ID, si32 subID)
|
||||
{
|
||||
const TerrainTile &tile = cb->gameState()->map->getTile(visitablePos());
|
||||
auto position = visitablePos();
|
||||
auto oldOffset = getVisitableOffset();
|
||||
auto &tile = cb->gameState()->map->getTile(position);
|
||||
|
||||
//recalculate blockvis tiles - new appearance might have different blockmap than before
|
||||
cb->gameState()->map->removeBlockVisTiles(this, true);
|
||||
@ -206,14 +208,23 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
|
||||
return;
|
||||
}
|
||||
if(!handler->getTemplates(tile.terType->id).empty())
|
||||
{
|
||||
appearance = handler->getTemplates(tile.terType->id)[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->name);
|
||||
appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
|
||||
}
|
||||
|
||||
if(this->ID == Obj::PRISON && ID == Obj::HERO)
|
||||
{
|
||||
//adjust for the prison offset
|
||||
pos = visitablePos();
|
||||
auto newOffset = getVisitableOffset();
|
||||
// FIXME: potentially unused code - setType is NOT called when releasing hero from prison
|
||||
// instead, appearance update & pos adjustment occurs in GiveHero::applyGs
|
||||
|
||||
// adjust position since hero and prison may have different visitable offset
|
||||
pos = pos - oldOffset + newOffset;
|
||||
}
|
||||
|
||||
this->ID = Obj(ID);
|
||||
|
@ -1028,7 +1028,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
|
||||
if(doRequest)
|
||||
{
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", fullIdentifier, false);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), fullIdentifier, false);
|
||||
|
||||
if(rawId)
|
||||
{
|
||||
|
@ -970,22 +970,22 @@ void CGVisitableOPH::initObj(CRandomGenerator & rand)
|
||||
break;
|
||||
case Obj::LIBRARY_OF_ENLIGHTENMENT:
|
||||
{
|
||||
selectMode = SELECT_FIRST;
|
||||
onVisited.addTxt(MetaString::ADVOB_TXT, 67);
|
||||
onEmpty.addTxt(MetaString::ADVOB_TXT, 68);
|
||||
|
||||
// Don't like this one but don't see any easier approach
|
||||
CVisitInfo visit;
|
||||
visit.reward.primary[PrimarySkill::ATTACK] = 2;
|
||||
visit.reward.primary[PrimarySkill::DEFENSE] = 2;
|
||||
visit.reward.primary[PrimarySkill::KNOWLEDGE] = 2;
|
||||
visit.reward.primary[PrimarySkill::SPELL_POWER] = 2;
|
||||
visit.message.addTxt(MetaString::ADVOB_TXT, 66);
|
||||
|
||||
static_assert(SecSkillLevel::LEVELS_SIZE == 4, "Behavior of Library of Enlignment may not be correct");
|
||||
for (int i=0; i<SecSkillLevel::LEVELS_SIZE; i++)
|
||||
{
|
||||
visit.limiter.minLevel = 10 - i * 2;
|
||||
visit.limiter.secondary[SecondarySkill::DIPLOMACY] = i;
|
||||
visit.message.addTxt(MetaString::ADVOB_TXT, 66);
|
||||
info.push_back(visit);
|
||||
}
|
||||
break;
|
||||
|
@ -1804,7 +1804,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
bonusType = RANDOM;
|
||||
if(!json["rewardPrimSkill"].String().empty())
|
||||
{
|
||||
auto raw = VLC->modh->identifiers.getIdentifier("core", "primSkill", json["rewardPrimSkill"].String());
|
||||
auto raw = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "primSkill", json["rewardPrimSkill"].String());
|
||||
if(raw)
|
||||
{
|
||||
bonusType = PRIM_SKILL;
|
||||
@ -1813,7 +1813,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
}
|
||||
else if(!json["rewardSkill"].String().empty())
|
||||
{
|
||||
auto raw = VLC->modh->identifiers.getIdentifier("core", "skill", json["rewardSkill"].String());
|
||||
auto raw = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "skill", json["rewardSkill"].String());
|
||||
if(raw)
|
||||
{
|
||||
bonusType = SECONDARY_SKILL;
|
||||
@ -1822,7 +1822,7 @@ void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
}
|
||||
else if(!json["rewardSpell"].String().empty())
|
||||
{
|
||||
auto raw = VLC->modh->identifiers.getIdentifier("core", "spell", json["rewardSpell"].String());
|
||||
auto raw = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "spell", json["rewardSpell"].String());
|
||||
if(raw)
|
||||
{
|
||||
bonusType = SPELL;
|
||||
|
@ -287,7 +287,15 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
|
||||
if(withTerrain && !node["allowedTerrains"].isNull())
|
||||
{
|
||||
for(auto& entry : node["allowedTerrains"].Vector())
|
||||
allowedTerrains.insert(VLC->terrainTypeHandler->getInfoByName(entry.String())->id);
|
||||
{
|
||||
try {
|
||||
allowedTerrains.insert(VLC->terrainTypeHandler->getInfoByName(entry.String())->id);
|
||||
}
|
||||
catch (const std::out_of_range & )
|
||||
{
|
||||
logGlobal->warn("Failed to find terrain '%s' for object '%s'", entry.String(), animationFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -300,7 +308,7 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
|
||||
}
|
||||
|
||||
if(withTerrain && allowedTerrains.empty())
|
||||
logGlobal->warn("Loaded template without allowed terrains!");
|
||||
logGlobal->warn("Loaded template %s without allowed terrains!", animationFile);
|
||||
|
||||
auto charToTile = [&](const char & ch) -> ui8
|
||||
{
|
||||
|
@ -256,6 +256,8 @@ CMap::CMap()
|
||||
|
||||
CMap::~CMap()
|
||||
{
|
||||
getEditManager()->getUndoManager().clearAll();
|
||||
|
||||
if(terrain)
|
||||
{
|
||||
for(int z = 0; z < levels(); z++)
|
||||
|
@ -215,7 +215,7 @@ namespace TriggeredEventsDetail
|
||||
|
||||
event.metaType = decodeMetaclass(metaTypeName);
|
||||
|
||||
auto type = VLC->modh->identifiers.getIdentifier("core", fullIdentifier, false);
|
||||
auto type = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), fullIdentifier, false);
|
||||
|
||||
if(type)
|
||||
event.objectType = type.get();
|
||||
@ -1121,7 +1121,7 @@ void CMapLoaderJson::MapObjectLoader::construct()
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = VLC->objtypeh->getHandlerFor( "map", typeName, subtypeName);
|
||||
auto handler = VLC->objtypeh->getHandlerFor( CModHandler::scopeMap(), typeName, subtypeName);
|
||||
|
||||
auto appearance = new ObjectTemplate;
|
||||
|
||||
@ -1158,7 +1158,7 @@ void CMapLoaderJson::MapObjectLoader::configure()
|
||||
if(art->ID == Obj::SPELL_SCROLL)
|
||||
{
|
||||
auto spellIdentifier = configuration["options"]["spell"].String();
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", spellIdentifier);
|
||||
auto rawId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeBuiltin(), "spell", spellIdentifier);
|
||||
if(rawId)
|
||||
spellID = rawId.get();
|
||||
else
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "CRmgTemplate.h"
|
||||
|
||||
#include "../serializer/JsonDeserializer.h"
|
||||
#include "../CModHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -30,7 +31,7 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
|
||||
try
|
||||
{
|
||||
JsonDeserializer handler(nullptr, data);
|
||||
auto fullKey = normalizeIdentifier(scope, "core", name); //actually it's not used
|
||||
auto fullKey = normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name); //actually it's not used
|
||||
templates[fullKey].setId(fullKey);
|
||||
templates[fullKey].serializeJson(handler);
|
||||
templates[fullKey].setName(name);
|
||||
|
@ -530,7 +530,12 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
|
||||
}
|
||||
|
||||
for (auto zone : zones)
|
||||
{
|
||||
if(zone.second->area().empty())
|
||||
throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size");
|
||||
|
||||
moveZoneToCenterOfMass(zone.second);
|
||||
}
|
||||
|
||||
//assign actual tiles to each zone using nonlinear norm for fine edges
|
||||
|
||||
|
@ -763,7 +763,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
|
||||
{
|
||||
if(counteredSpell.second.Bool())
|
||||
{
|
||||
VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
|
||||
VLC->modh->identifiers.requestIdentifier(counteredSpell.second.meta, counteredSpell.first, [=](si32 id)
|
||||
{
|
||||
spell->counteredSpells.push_back(SpellID(id));
|
||||
});
|
||||
|
BIN
mapeditor/icons/edit-redo.png
Normal file
BIN
mapeditor/icons/edit-redo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
mapeditor/icons/edit-undo.png
Normal file
BIN
mapeditor/icons/edit-undo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@ -555,6 +555,9 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
|
||||
if(key == "Name")
|
||||
o->name = value.toString().toStdString();
|
||||
|
||||
if(key == "Experience")
|
||||
o->exp = value.toInt();
|
||||
|
||||
if(key == "Hero type")
|
||||
{
|
||||
for(auto t : VLC->heroh->objects)
|
||||
|
@ -172,11 +172,22 @@ void TownBulidingsWidget::addBuildings(const CTown & ctown)
|
||||
std::set<BuildingID> TownBulidingsWidget::getBuildingsFromModel(int modelColumn, Qt::CheckState checkState)
|
||||
{
|
||||
std::set<BuildingID> result;
|
||||
for(int i = 0; i < model.rowCount(); ++i)
|
||||
std::vector<QModelIndex> stack;
|
||||
stack.push_back(QModelIndex());
|
||||
while(!stack.empty())
|
||||
{
|
||||
if(auto * item = model.item(i, modelColumn))
|
||||
if(item->checkState() == checkState)
|
||||
result.emplace(item->data(Qt::UserRole).toInt());
|
||||
auto pindex = stack.back();
|
||||
stack.pop_back();
|
||||
for(int i = 0; i < model.rowCount(pindex); ++i)
|
||||
{
|
||||
QModelIndex index = model.index(i, modelColumn, pindex);
|
||||
if(auto * item = model.itemFromIndex(index))
|
||||
if(item->checkState() == checkState)
|
||||
result.emplace(item->data(Qt::UserRole).toInt());
|
||||
index = model.index(i, 0, pindex); //children are linked to first column of the model
|
||||
if(model.hasChildren(index))
|
||||
stack.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -120,6 +120,10 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
ui(new Ui::MainWindow),
|
||||
controller(this)
|
||||
{
|
||||
// Set current working dir to executable folder.
|
||||
// This is important on Mac for relative paths to work inside DMG.
|
||||
QDir::setCurrent(QApplication::applicationDirPath());
|
||||
|
||||
for(auto & string : VCMIDirs::get().dataPaths())
|
||||
QDir::addSearchPath("icons", pathToQString(string / "mapeditor" / "icons"));
|
||||
QDir::addSearchPath("icons", pathToQString(VCMIDirs::get().userDataPath() / "mapeditor" / "icons"));
|
||||
@ -128,10 +132,6 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
loadUserSettings(); //For example window size
|
||||
setTitle();
|
||||
|
||||
// Set current working dir to executable folder.
|
||||
// This is important on Mac for relative paths to work inside DMG.
|
||||
QDir::setCurrent(QApplication::applicationDirPath());
|
||||
|
||||
ExtractionOptions extractionOptions;
|
||||
parseCommandLine(extractionOptions);
|
||||
|
||||
@ -550,7 +550,7 @@ void MainWindow::loadObjectsTree()
|
||||
|
||||
//model
|
||||
objectsModel.setHorizontalHeaderLabels(QStringList() << tr("Type"));
|
||||
objectBrowser = new ObjectBrowser(this);
|
||||
objectBrowser = new ObjectBrowserProxyModel(this);
|
||||
objectBrowser->setSourceModel(&objectsModel);
|
||||
objectBrowser->setDynamicSortFilter(false);
|
||||
objectBrowser->setRecursiveFilteringEnabled(true);
|
||||
@ -853,12 +853,11 @@ void MainWindow::on_toolErase_clicked()
|
||||
ui->tabWidget->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void MainWindow::preparePreview(const QModelIndex &index, bool createNew)
|
||||
void MainWindow::preparePreview(const QModelIndex &index)
|
||||
{
|
||||
scenePreview->clear();
|
||||
|
||||
auto data = objectsModel.itemFromIndex(objectBrowser->mapToSource(index))->data().toJsonObject();
|
||||
|
||||
if(!data.empty())
|
||||
{
|
||||
auto preview = data["preview"];
|
||||
@ -866,29 +865,12 @@ void MainWindow::preparePreview(const QModelIndex &index, bool createNew)
|
||||
{
|
||||
QPixmap objPreview = pixmapFromJson(preview);
|
||||
scenePreview->addPixmap(objPreview);
|
||||
|
||||
auto objId = data["id"].toInt();
|
||||
auto objSubId = data["subid"].toInt();
|
||||
auto templateId = data["template"].toInt();
|
||||
|
||||
if(controller.discardObject(mapLevel) || createNew)
|
||||
{
|
||||
auto factory = VLC->objtypeh->getHandlerFor(objId, objSubId);
|
||||
auto templ = factory->getTemplates()[templateId];
|
||||
controller.createObject(mapLevel, factory->create(templ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::treeViewSelected(const QModelIndex & index, const QModelIndex & deselected)
|
||||
{
|
||||
preparePreview(index, false);
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::on_treeView_activated(const QModelIndex &index)
|
||||
{
|
||||
ui->toolBrush->setChecked(false);
|
||||
ui->toolBrush2->setChecked(false);
|
||||
@ -896,11 +878,10 @@ void MainWindow::on_treeView_activated(const QModelIndex &index)
|
||||
ui->toolArea->setChecked(false);
|
||||
ui->toolLasso->setChecked(false);
|
||||
ui->mapView->selectionTool = MapView::SelectionTool::None;
|
||||
|
||||
preparePreview(index, true);
|
||||
|
||||
preparePreview(index);
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1)
|
||||
{
|
||||
if(!objectBrowser)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "resourceExtractor/ResourceConverter.h"
|
||||
|
||||
class ObjectBrowser;
|
||||
class ObjectBrowserProxyModel;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CMap;
|
||||
@ -77,8 +78,6 @@ private slots:
|
||||
|
||||
void on_toolErase_clicked();
|
||||
|
||||
void on_treeView_activated(const QModelIndex &index);
|
||||
|
||||
void on_terrainFilterCombo_currentTextChanged(const QString &arg1);
|
||||
|
||||
void on_filter_textChanged(const QString &arg1);
|
||||
@ -116,7 +115,7 @@ public slots:
|
||||
void displayStatus(const QString& message, int timeout = 2000);
|
||||
|
||||
private:
|
||||
void preparePreview(const QModelIndex &index, bool createNew);
|
||||
void preparePreview(const QModelIndex & index);
|
||||
void addGroupIntoCatalog(const std::string & groupName, bool staticOnly);
|
||||
void addGroupIntoCatalog(const std::string & groupName, bool useCustomName, bool staticOnly, int ID);
|
||||
|
||||
@ -136,7 +135,7 @@ private:
|
||||
|
||||
private:
|
||||
Ui::MainWindow * ui;
|
||||
ObjectBrowser * objectBrowser = nullptr;
|
||||
ObjectBrowserProxyModel * objectBrowser = nullptr;
|
||||
QGraphicsScene * scenePreview;
|
||||
|
||||
QString filename;
|
||||
|
@ -123,6 +123,9 @@
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionUndo"/>
|
||||
<addaction name="actionRedo"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionLevel"/>
|
||||
<addaction name="actionGrid"/>
|
||||
<addaction name="actionPass"/>
|
||||
@ -300,7 +303,7 @@
|
||||
<widget class="QLineEdit" name="filter"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeView">
|
||||
<widget class="ObjectBrowser" name="treeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
@ -316,8 +319,11 @@
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::NoDragDrop</enum>
|
||||
<enum>QAbstractItemView::DragOnly</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
@ -1010,6 +1016,9 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPlayers_settings">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Players settings</string>
|
||||
</property>
|
||||
@ -1018,6 +1027,10 @@
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>icons:edit-undo.png</normaloff>icons:edit-undo.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Undo</string>
|
||||
</property>
|
||||
@ -1035,6 +1048,10 @@
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>icons:edit-redo.png</normaloff>icons:edit-redo.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Redo</string>
|
||||
</property>
|
||||
@ -1192,6 +1209,11 @@
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>mapview.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ObjectBrowser</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>objectbrowser.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -128,10 +128,15 @@ void MapController::repairMap()
|
||||
assert(type->heroClass);
|
||||
//TODO: find a way to get proper type name
|
||||
if(obj->ID == Obj::HERO)
|
||||
{
|
||||
nih->typeName = "hero";
|
||||
nih->subTypeName = type->heroClass->identifier;
|
||||
}
|
||||
if(obj->ID == Obj::PRISON)
|
||||
{
|
||||
nih->typeName = "prison";
|
||||
nih->subTypeName = type->heroClass->identifier;
|
||||
nih->subTypeName = "prison";
|
||||
}
|
||||
|
||||
nih->type = type;
|
||||
if(nih->name.empty())
|
||||
@ -214,6 +219,7 @@ void MapController::setMap(std::unique_ptr<CMap> cmap)
|
||||
main->enableRedo(allowRedo);
|
||||
}
|
||||
);
|
||||
_map->getEditManager()->getUndoManager().clearAll();
|
||||
}
|
||||
|
||||
void MapController::sceneForceUpdate()
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include "mapcontroller.h"
|
||||
#include "../lib/mapObjects/CObjectClassesHandler.h"
|
||||
|
||||
MinimapView::MinimapView(QWidget * parent):
|
||||
QGraphicsView(parent)
|
||||
@ -64,12 +65,10 @@ MapView::MapView(QWidget * parent):
|
||||
|
||||
void MapView::cameraChanged(const QPointF & pos)
|
||||
{
|
||||
//ui->mapView->translate(pos.x(), pos.y());
|
||||
horizontalScrollBar()->setValue(pos.x());
|
||||
verticalScrollBar()->setValue(pos.y());
|
||||
}
|
||||
|
||||
|
||||
void MapView::setController(MapController * ctrl)
|
||||
{
|
||||
controller = ctrl;
|
||||
@ -85,7 +84,12 @@ void MapView::mouseMoveEvent(QMouseEvent *mouseEvent)
|
||||
|
||||
auto pos = mapToScene(mouseEvent->pos()); //TODO: do we need to check size?
|
||||
int3 tile(pos.x() / 32, pos.y() / 32, sc->level);
|
||||
|
||||
|
||||
//if scene will be scrolled without mouse movement, selection, object moving and rubber band will not be updated
|
||||
//to change this behavior, all this logic should be placed in viewportEvent
|
||||
if(rubberBand)
|
||||
rubberBand->setGeometry(QRect(mapFromScene(mouseStart), mouseEvent->pos()).normalized());
|
||||
|
||||
if(tile == tilePrev) //do not redraw
|
||||
return;
|
||||
|
||||
@ -161,13 +165,7 @@ void MapView::mouseMoveEvent(QMouseEvent *mouseEvent)
|
||||
|
||||
if(sh.x || sh.y)
|
||||
{
|
||||
if(sc->selectionObjectsView.newObject)
|
||||
{
|
||||
sc->selectionObjectsView.shift = QPoint(tile.x, tile.y);
|
||||
sc->selectionObjectsView.selectObject(sc->selectionObjectsView.newObject);
|
||||
sc->selectionObjectsView.selectionMode = SelectionObjectsLayer::MOVEMENT;
|
||||
}
|
||||
else if(mouseEvent->buttons() & Qt::LeftButton)
|
||||
if(!sc->selectionObjectsView.newObject && (mouseEvent->buttons() & Qt::LeftButton))
|
||||
{
|
||||
if(sc->selectionObjectsView.selectionMode == SelectionObjectsLayer::SELECTION)
|
||||
{
|
||||
@ -296,6 +294,11 @@ void MapView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
sc->selectionObjectsView.clear();
|
||||
sc->selectionObjectsView.selectionMode = SelectionObjectsLayer::SELECTION;
|
||||
|
||||
if(!rubberBand)
|
||||
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
|
||||
rubberBand->setGeometry(QRect(mapFromScene(mouseStart), QSize()));
|
||||
rubberBand->show();
|
||||
}
|
||||
}
|
||||
sc->selectionObjectsView.shift = QPoint(0, 0);
|
||||
@ -314,6 +317,9 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
|
||||
auto * sc = static_cast<MapScene*>(scene());
|
||||
if(!sc || !controller->map())
|
||||
return;
|
||||
|
||||
if(rubberBand)
|
||||
rubberBand->hide();
|
||||
|
||||
switch(selectionTool)
|
||||
{
|
||||
@ -324,19 +330,7 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
|
||||
bool tab = false;
|
||||
if(sc->selectionObjectsView.selectionMode == SelectionObjectsLayer::MOVEMENT)
|
||||
{
|
||||
if(sc->selectionObjectsView.newObject)
|
||||
{
|
||||
QString errorMsg;
|
||||
if(controller->canPlaceObject(sc->level, sc->selectionObjectsView.newObject, errorMsg))
|
||||
controller->commitObjectCreate(sc->level);
|
||||
else
|
||||
{
|
||||
QMessageBox::information(this, "Can't place object", errorMsg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
controller->commitObjectShift(sc->level);
|
||||
controller->commitObjectShift(sc->level);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -355,11 +349,103 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void MapView::dragEnterEvent(QDragEnterEvent * event)
|
||||
{
|
||||
if(!controller || !controller->map())
|
||||
return;
|
||||
|
||||
auto * sc = static_cast<MapScene*>(scene());
|
||||
if(!sc)
|
||||
return;
|
||||
|
||||
if(event->mimeData()->hasImage())
|
||||
{
|
||||
logGlobal->info("Drag'n'drop: dispatching object");
|
||||
QVariant vdata = event->mimeData()->imageData();
|
||||
auto data = vdata.toJsonObject();
|
||||
if(!data.empty())
|
||||
{
|
||||
auto preview = data["preview"];
|
||||
if(preview != QJsonValue::Undefined)
|
||||
{
|
||||
auto objId = data["id"].toInt();
|
||||
auto objSubId = data["subid"].toInt();
|
||||
auto templateId = data["template"].toInt();
|
||||
auto factory = VLC->objtypeh->getHandlerFor(objId, objSubId);
|
||||
auto templ = factory->getTemplates()[templateId];
|
||||
controller->discardObject(sc->level);
|
||||
controller->createObject(sc->level, factory->create(templ));
|
||||
}
|
||||
}
|
||||
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
void MapView::dropEvent(QDropEvent * event)
|
||||
{
|
||||
if(!controller || !controller->map())
|
||||
return;
|
||||
|
||||
auto * sc = static_cast<MapScene*>(scene());
|
||||
if(!sc)
|
||||
return;
|
||||
|
||||
if(sc->selectionObjectsView.newObject)
|
||||
{
|
||||
QString errorMsg;
|
||||
if(controller->canPlaceObject(sc->level, sc->selectionObjectsView.newObject, errorMsg))
|
||||
{
|
||||
controller->commitObjectCreate(sc->level);
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->discardObject(sc->level);
|
||||
QMessageBox::information(this, "Can't place object", errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void MapView::dragMoveEvent(QDragMoveEvent * event)
|
||||
{
|
||||
auto * sc = static_cast<MapScene*>(scene());
|
||||
if(!sc)
|
||||
return;
|
||||
|
||||
auto rect = event->answerRect();
|
||||
auto pos = mapToScene(rect.bottomRight()); //TODO: do we need to check size?
|
||||
int3 tile(pos.x() / 32 + 1, pos.y() / 32 + 1, sc->level);
|
||||
|
||||
if(sc->selectionObjectsView.newObject)
|
||||
{
|
||||
sc->selectionObjectsView.shift = QPoint(tile.x, tile.y);
|
||||
sc->selectionObjectsView.selectObject(sc->selectionObjectsView.newObject);
|
||||
sc->selectionObjectsView.selectionMode = SelectionObjectsLayer::MOVEMENT;
|
||||
sc->selectionObjectsView.draw();
|
||||
}
|
||||
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void MapView::dragLeaveEvent(QDragLeaveEvent * event)
|
||||
{
|
||||
if(!controller || !controller->map())
|
||||
return;
|
||||
|
||||
auto * sc = static_cast<MapScene*>(scene());
|
||||
if(!sc)
|
||||
return;
|
||||
|
||||
controller->discardObject(sc->level);
|
||||
}
|
||||
|
||||
|
||||
bool MapView::viewportEvent(QEvent *event)
|
||||
{
|
||||
if(auto * sc = static_cast<MapScene*>(scene()))
|
||||
{
|
||||
//auto rect = sceneRect();
|
||||
auto rect = mapToScene(viewport()->geometry()).boundingRect();
|
||||
controller->miniScene(sc->level)->viewport.setViewport(rect.x() / 32, rect.y() / 32, rect.width() / 32, rect.height() / 32);
|
||||
}
|
||||
|
@ -101,6 +101,10 @@ public slots:
|
||||
void mouseMoveEvent(QMouseEvent * mouseEvent) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void dragEnterEvent(QDragEnterEvent * event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dropEvent(QDropEvent * event) override;
|
||||
|
||||
void cameraChanged(const QPointF & pos);
|
||||
|
||||
@ -113,6 +117,7 @@ protected:
|
||||
|
||||
private:
|
||||
MapController * controller = nullptr;
|
||||
QRubberBand * rubberBand = nullptr;
|
||||
QPointF mouseStart;
|
||||
int3 tileStart;
|
||||
int3 tilePrev;
|
||||
@ -130,7 +135,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void mouseMoveEvent(QMouseEvent * mouseEvent) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent * event) override;
|
||||
|
||||
signals:
|
||||
void cameraPositionChanged(const QPointF & newPosition);
|
||||
|
@ -12,12 +12,12 @@
|
||||
#include "objectbrowser.h"
|
||||
#include "../lib/mapObjects/CObjectClassesHandler.h"
|
||||
|
||||
ObjectBrowser::ObjectBrowser(QObject *parent)
|
||||
ObjectBrowserProxyModel::ObjectBrowserProxyModel(QObject *parent)
|
||||
: QSortFilterProxyModel{parent}, terrain(Terrain::ANY_TERRAIN)
|
||||
{
|
||||
}
|
||||
|
||||
bool ObjectBrowser::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
|
||||
bool ObjectBrowserProxyModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
|
||||
{
|
||||
bool result = QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
|
||||
@ -57,7 +57,7 @@ bool ObjectBrowser::filterAcceptsRow(int source_row, const QModelIndex & source_
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ObjectBrowser::filterAcceptsRowText(int source_row, const QModelIndex &source_parent) const
|
||||
bool ObjectBrowserProxyModel::filterAcceptsRowText(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
if(source_parent.isValid())
|
||||
{
|
||||
@ -76,3 +76,52 @@ bool ObjectBrowser::filterAcceptsRowText(int source_row, const QModelIndex &sour
|
||||
return (filter.isNull() || filter.isEmpty() || item->text().contains(filter, Qt::CaseInsensitive));
|
||||
}
|
||||
|
||||
Qt::ItemFlags ObjectBrowserProxyModel::flags(const QModelIndex & index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QSortFilterProxyModel::flags(index);
|
||||
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsDragEnabled | defaultFlags;
|
||||
|
||||
return defaultFlags;
|
||||
}
|
||||
|
||||
QMimeData * ObjectBrowserProxyModel::mimeData(const QModelIndexList & indexes) const
|
||||
{
|
||||
assert(indexes.size() == 1);
|
||||
|
||||
auto * standardModel = qobject_cast<QStandardItemModel*>(sourceModel());
|
||||
assert(standardModel);
|
||||
|
||||
QModelIndex index = indexes.front();
|
||||
|
||||
if(!index.isValid())
|
||||
return nullptr;
|
||||
|
||||
QMimeData * mimeData = new QMimeData;
|
||||
mimeData->setImageData(standardModel->itemFromIndex(mapToSource(index))->data());
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
ObjectBrowser::ObjectBrowser(QWidget * parent):
|
||||
QTreeView(parent)
|
||||
{
|
||||
setDropIndicatorShown(false);
|
||||
}
|
||||
|
||||
void ObjectBrowser::startDrag(Qt::DropActions supportedActions)
|
||||
{
|
||||
logGlobal->info("Drag'n'drop: Start dragging object from ObjectBrowser");
|
||||
QDrag *drag = new QDrag(this);
|
||||
auto indexes = selectedIndexes();
|
||||
if(indexes.isEmpty())
|
||||
return;
|
||||
|
||||
QMimeData * mimeData = model()->mimeData(indexes);
|
||||
if(!mimeData)
|
||||
return;
|
||||
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
Qt::DropAction dropAction = drag->exec(supportedActions);
|
||||
}
|
||||
|
@ -13,10 +13,14 @@
|
||||
#include <QSortFilterProxyModel>
|
||||
#include "../lib/Terrain.h"
|
||||
|
||||
class ObjectBrowser : public QSortFilterProxyModel
|
||||
class ObjectBrowserProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
explicit ObjectBrowser(QObject *parent = nullptr);
|
||||
explicit ObjectBrowserProxyModel(QObject *parent = nullptr);
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
QMimeData * mimeData(const QModelIndexList & indexes) const override;
|
||||
|
||||
TerrainId terrain;
|
||||
QString filter;
|
||||
@ -25,3 +29,12 @@ protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override;
|
||||
bool filterAcceptsRowText(int source_row, const QModelIndex &source_parent) const;
|
||||
};
|
||||
|
||||
class ObjectBrowser : public QTreeView
|
||||
{
|
||||
public:
|
||||
ObjectBrowser(QWidget * parent);
|
||||
|
||||
protected:
|
||||
void startDrag(Qt::DropActions supportedActions) override;
|
||||
};
|
||||
|
@ -401,7 +401,7 @@ void SelectionObjectsLayer::draw()
|
||||
//show translation
|
||||
if(selectionMode == SelectionMode::MOVEMENT && (shift.x() || shift.y()))
|
||||
{
|
||||
painter.setOpacity(0.5);
|
||||
painter.setOpacity(0.7);
|
||||
auto newPos = QPoint(obj->getPosition().x, obj->getPosition().y) + shift;
|
||||
handler->drawObjectAt(painter, obj, newPos.x(), newPos.y());
|
||||
}
|
||||
@ -471,8 +471,11 @@ void SelectionObjectsLayer::selectObjects(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
for(int i = x1; i < x2; ++i)
|
||||
{
|
||||
for(auto & o : handler->getObjects(i, j, scene->level))
|
||||
selectObject(o.obj, false); //do not inform about each object added
|
||||
if(map->isInTheMap(int3(i, j, scene->level)))
|
||||
{
|
||||
for(auto & o : handler->getObjects(i, j, scene->level))
|
||||
selectObject(o.obj, false); //do not inform about each object added
|
||||
}
|
||||
}
|
||||
}
|
||||
onSelection();
|
||||
|
@ -148,7 +148,6 @@ public:
|
||||
void deselectObject(CGObjectInstance *);
|
||||
bool isSelected(const CGObjectInstance *) const;
|
||||
std::set<CGObjectInstance*> getSelection() const;
|
||||
void moveSelection(int x, int y);
|
||||
void clear();
|
||||
|
||||
QPoint shift;
|
||||
|
@ -37,8 +37,10 @@ WindowNewMap::WindowNewMap(QWidget *parent) :
|
||||
show();
|
||||
|
||||
//setup initial parameters
|
||||
mapGenOptions.setWidth(ui->widthTxt->text().toInt());
|
||||
mapGenOptions.setHeight(ui->heightTxt->text().toInt());
|
||||
int width = ui->widthTxt->text().toInt();
|
||||
int height = ui->heightTxt->text().toInt();
|
||||
mapGenOptions.setWidth(width ? width : 1);
|
||||
mapGenOptions.setHeight(height ? height : 1);
|
||||
bool twoLevel = ui->twoLevelCheck->isChecked();
|
||||
mapGenOptions.setHasTwoLevels(twoLevel);
|
||||
updateTemplateList();
|
||||
|
@ -511,14 +511,14 @@ int LuaContext::loadModule()
|
||||
|
||||
registar->pushMetatable(L);
|
||||
}
|
||||
else if(scope == "core")
|
||||
else if(scope == CModHandler::scopeBuiltin())
|
||||
{
|
||||
|
||||
// boost::algorithm::replace_all(modulePath, boost::is_any_of("\\/ "), "");
|
||||
|
||||
boost::algorithm::replace_all(modulePath, ".", "/");
|
||||
|
||||
auto loader = CResourceHandler::get("core");
|
||||
auto loader = CResourceHandler::get(CModHandler::scopeBuiltin());
|
||||
|
||||
modulePath = "scripts/lib/" + modulePath;
|
||||
|
||||
|
@ -6642,6 +6642,8 @@ void CGameHandler::runBattle()
|
||||
}
|
||||
}
|
||||
}
|
||||
// it is possible that due to opening spells one side was eliminated -> check for end of battle
|
||||
checkBattleStateChanges();
|
||||
|
||||
bool firstRound = true;//FIXME: why first round is -1?
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
||||
|
||||
#include "../lib/CGameState.h"
|
||||
|
||||
#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
#if defined(__GNUC__) && !defined(__UCLIBC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
@ -958,7 +958,7 @@ ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
#if defined(__GNUC__) && !defined(__UCLIBC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
void handleLinuxSignal(int sig)
|
||||
{
|
||||
const int STACKTRACE_SIZE = 100;
|
||||
@ -1013,6 +1013,10 @@ static void handleCommandOptions(int argc, char * argv[], boost::program_options
|
||||
std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SINGLE_PROCESS_APP
|
||||
options.emplace("run-by-client", po::variable_value{true, true});
|
||||
#endif
|
||||
|
||||
po::notify(options);
|
||||
|
||||
@ -1049,7 +1053,7 @@ int main(int argc, char * argv[])
|
||||
#endif
|
||||
// Installs a sig sev segmentation violation handler
|
||||
// to log stacktrace
|
||||
#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
#if defined(__GNUC__) && !defined(__UCLIBC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
signal(SIGSEGV, handleLinuxSignal);
|
||||
#endif
|
||||
|
||||
|
@ -89,7 +89,17 @@ bool SaveGame::applyGh(CGameHandler * gh)
|
||||
|
||||
bool EndTurn::applyGh(CGameHandler * gh)
|
||||
{
|
||||
PlayerColor player = GS(gh)->currentPlayer;
|
||||
PlayerColor currentPlayer = GS(gh)->currentPlayer;
|
||||
if(player != currentPlayer)
|
||||
{
|
||||
if(gh->getPlayerStatus(player) == EPlayerStatus::INGAME)
|
||||
throwAndComplain(gh, "Player attempted to end turn for another player!");
|
||||
|
||||
logGlobal->debug("Player attempted to end turn after game over. Ignoring this request.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
throwOnWrongPlayer(gh, player);
|
||||
if(gh->queries.topQuery(player))
|
||||
throwAndComplain(gh, "Cannot end turn before resolving queries!");
|
||||
|
@ -29,7 +29,7 @@ void ScriptFixture::loadScriptFromFile(const std::string & path)
|
||||
|
||||
void ScriptFixture::loadScript(const JsonNode & scriptConfig)
|
||||
{
|
||||
subject = VLC->scriptHandler->loadFromJson(&loggerMock, "core", scriptConfig, "test");
|
||||
subject = VLC->scriptHandler->loadFromJson(&loggerMock, CModHandler::scopeBuiltin(), scriptConfig, "test");
|
||||
|
||||
GTEST_ASSERT_NE(subject, nullptr);
|
||||
|
||||
|
@ -79,7 +79,7 @@ TEST_P(TimedApplyTest, ChangesBonuses)
|
||||
options["cumulative"].Bool() = cumulative;
|
||||
options["bonus"]["test1"] = testBonus1.toJsonNode();
|
||||
options["bonus"]["test2"] = testBonus2.toJsonNode();
|
||||
options.setMeta("core");
|
||||
options.setMeta(CModHandler::scopeBuiltin());
|
||||
setupEffect(options);
|
||||
|
||||
const uint32_t unitId = 42;
|
||||
|
16
vcmibuilder
16
vcmibuilder
@ -168,13 +168,19 @@ mkdir -p "$temp_dir"
|
||||
|
||||
if [[ -n "$gog_file" ]]
|
||||
then
|
||||
data_dir="$temp_dir"/app
|
||||
data_dir="$temp_dir"/gog
|
||||
mkdir -p "$data_dir"
|
||||
|
||||
# innoextract always reports error (iconv 84 error). Just test file for presence
|
||||
test -f "$gog_file" || fail "Error: gog.com executable was not found!"
|
||||
gog_file="$(cd "$(dirname "$gog_file")"; pwd)/$(basename "$gog_file")"
|
||||
cd "$data_dir" && innoextract "$gog_file"
|
||||
|
||||
# some versions of gog.com installer (or innoextract tool?) place game files inside /app directory
|
||||
if [[ -d "$data_dir"/app ]]
|
||||
then
|
||||
data_dir="$data_dir"/app
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$cd1_dir" ]]
|
||||
@ -248,10 +254,10 @@ fi
|
||||
|
||||
if [[ -n "$validate" ]]
|
||||
then
|
||||
test -f "$dest_dir"/Data/H3bitmap.lod || fail "Error: Heroes 3 data files are missing!"
|
||||
test -f "$dest_dir"/Data/H3sprite.lod || fail "Error: Heroes 3 data files are missing!"
|
||||
test -f "$dest_dir"/Data/VIDEO.VID || fail "Error: Heroes 3 data files (CD2) are missing!"
|
||||
test -d "$dest_dir"/Mods/vcmi/Data || fail "Error: VCMI data files are missing!"
|
||||
test -f "$dest_dir"/[Dd][Aa][Tt][Aa]/H3bitmap.lod || fail "Error: Heroes 3 data files are missing!"
|
||||
test -f "$dest_dir"/[Dd][Aa][Tt][Aa]/H3sprite.lod || fail "Error: Heroes 3 data files are missing!"
|
||||
test -f "$dest_dir"/[Dd][Aa][Tt][Aa]/VIDEO.VID || fail "Error: Heroes 3 data files (CD2) are missing!"
|
||||
#test -d "$dest_dir"/Mods/vcmi/Data || fail "Error: VCMI data files are missing!"
|
||||
fi
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
|
Loading…
Reference in New Issue
Block a user