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

Merge branch 'develop' into client-commands-improvements

This commit is contained in:
Dydzio 2023-01-16 01:13:06 +01:00
commit e593b61201
45 changed files with 349 additions and 231 deletions

View File

@ -202,7 +202,6 @@ void ObjectClusterizer::clusterize()
Obj::WHIRLPOOL,
Obj::BUOY,
Obj::SIGN,
Obj::SIGN,
Obj::GARRISON,
Obj::MONSTER,
Obj::GARRISON2,

12
CI/conan/base/apple Normal file
View File

@ -0,0 +1,12 @@
[settings]
compiler=apple-clang
compiler.version=14
compiler.libcxx=libc++
build_type=Release
# required for Boost.Locale in versions >= 1.81
compiler.cppstd=11
[conf]
tools.apple:enable_bitcode = False
tools.cmake.cmaketoolchain:generator = Ninja

5
CI/conan/base/ios Normal file
View File

@ -0,0 +1,5 @@
include(apple)
[settings]
os=iOS
os.sdk=iphoneos

4
CI/conan/base/macos Normal file
View File

@ -0,0 +1,4 @@
include(apple)
[settings]
os=Macos

View File

@ -1,14 +1,5 @@
include(base/ios)
[settings]
os=iOS
os.version=12.0
os.sdk=iphoneos
arch=armv8
compiler=apple-clang
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,14 +1,8 @@
include(base/ios)
[settings]
os=iOS
os.version=10.0
os.sdk=iphoneos
arch=armv7
compiler=apple-clang
# Xcode 13.x is the last version that can build for armv7
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,13 +1,5 @@
include(base/macos)
[settings]
os=Macos
os.version=11.0
arch=armv8
compiler=apple-clang
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,13 +1,5 @@
include(base/macos)
[settings]
os=Macos
os.version=10.13
arch=x86_64
compiler=apple-clang
compiler.version=13
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
mkdir ~/.conan ; cd ~/.conan
curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.1/ios-arm64.xz' \
curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.2/ios-arm64.txz' \
| tar -xf -

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash
echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
brew install ninja
mkdir ~/.conan ; cd ~/.conan
curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.1/$DEPS_FILENAME.txz" \
curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.2/$DEPS_FILENAME.txz" \
| tar -xf -

View File

@ -563,6 +563,9 @@ if(WIN32)
FILES ${integration_loc}
DESTINATION ${BIN_DIR}/platforms
)
install(
FILES "$<TARGET_FILE:Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin>"
DESTINATION ${BIN_DIR}/styles)
endif()
endif()

View File

@ -89,7 +89,7 @@ CSoundHandler::CSoundHandler():
soundBase::battle02, soundBase::battle03, soundBase::battle04,
soundBase::battle05, soundBase::battle06, soundBase::battle07
};
//predefine terrain set
//TODO: support custom sounds for new terrains and load from json
horseSounds =
@ -542,6 +542,20 @@ MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string mu
}
MusicEntry::~MusicEntry()
{
if (playing)
{
assert(0);
logGlobal->error("Attempt to delete music while playing!");
Mix_HaltMusic();
}
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
{
assert(0);
logGlobal->error("Attempt to delete music while fading out!");
Mix_HaltMusic();
}
logGlobal->trace("Del-ing music file %s", currentName);
if (music)
Mix_FreeMusic(music);
@ -619,7 +633,7 @@ bool MusicEntry::play()
bool MusicEntry::stop(int fade_ms)
{
if (playing)
if (Mix_PlayingMusic())
{
playing = false;
loop = 0;

View File

@ -1623,7 +1623,7 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
else
for (directory_iterator dir(gamesDir); dir != enddir; ++dir)
{
if (is_regular(dir->status()))
if (is_regular_file(dir->status()))
{
std::string name = dir->path().filename().string();
if (starts_with(name, namePrefix) && ends_with(name, ".vcgm1"))
@ -2473,6 +2473,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
// (i == 0) means hero went through all the path
adventureInt->updateMoveHero(h, (i != 0));
adventureInt->updateNextHero(h);
// ugly workaround to force instant update of adventure map
adventureInt->animValHitCount = 8;
}
setMovementStatus(false);

View File

@ -777,6 +777,13 @@ void CClient::removeGUI()
}
#ifdef VCMI_ANDROID
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls)
{
logNetwork->info("Received clientSetupJNI");
CAndroidVMHelper::cacheVM(env);
}
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
{
logNetwork->info("Received server closed signal");

View File

@ -94,8 +94,8 @@ public:
// Keep the original palette, in order to do color switching operation
void savePalette();
void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr, ui8 alpha=255) const override;
void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha=255) const override;
void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override;
void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const override;
std::shared_ptr<IImage> scaleFast(float scale) const override;
void exportBitmap(const boost::filesystem::path & path) const override;
void playerColored(PlayerColor player) override;
@ -642,17 +642,16 @@ SDLImage::SDLImage(std::string filename)
}
}
void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) const
void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const
{
if(!surf)
return;
Rect destRect(posX, posY, surf->w, surf->h);
draw(where, &destRect, src);
}
void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) const
void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src) const
{
if (!surf)
return;

View File

@ -40,8 +40,8 @@ public:
using SpecialPalette = std::array<SDL_Color, 7>;
//draws image on surface "where" at position
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0;
virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha = 255) const = 0;
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const = 0;
virtual std::shared_ptr<IImage> scaleFast(float scale) const = 0;

View File

@ -70,13 +70,6 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect &
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha)
{
assert(image);
if (image)
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha);
}
void Canvas::draw(Canvas & image, const Point & pos)
{
blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);

View File

@ -51,10 +51,6 @@ public:
/// renders section of image bounded by sourceRect at specified position
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
/// renders section of image bounded by sourceRect at specified position at specific transparency value
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha);
/// renders another canvas onto this canvas
void draw(Canvas & image, const Point & pos);

View File

@ -278,13 +278,12 @@ void CHeroArtPlace::select ()
if (locked)
return;
selectSlot(true);
pickSlot(true);
if(ourArt->canBeDisassembled() && slotID < GameConstants::BACKPACK_START) //worn combined artifact -> locks have to disappear
{
for(int i = 0; i < GameConstants::BACKPACK_START; i++)
for(auto slot : ArtifactUtils::constituentWornSlots())
{
auto ap = ourOwner->getArtPlace(i);
auto ap = ourOwner->getArtPlace(slot);
if(ap)//getArtPlace may return null
ap->pickSlot(ourArt->isPart(ap->ourArt));
}
@ -309,9 +308,9 @@ void CHeroArtPlace::deselect ()
pickSlot(false);
if(ourArt && ourArt->canBeDisassembled()) //combined art returned to its slot -> restore locks
{
for(int i = 0; i < GameConstants::BACKPACK_START; i++)
for(auto slot : ArtifactUtils::constituentWornSlots())
{
auto place = ourOwner->getArtPlace(i);
auto place = ourOwner->getArtPlace(slot);
if(nullptr != place)//getArtPlace may return null
place->pickSlot(false);
@ -670,6 +669,16 @@ CArtifactsOfHero::CArtifactsOfHero(const Point & position, bool createCommonPart
CArtifactsOfHero::~CArtifactsOfHero()
{
dispose();
// Artifact located in artifactsTransitionPos should be returned
if(!curHero->artifactsTransitionPos.empty())
{
auto artPlace = getArtPlace(
ArtifactUtils::getArtifactDstPosition(curHero->artifactsTransitionPos.begin()->artifact, curHero, curHero->bearerType()));
assert(artPlace);
assert(artPlace->ourOwner);
artPlace->setMeAsDest();
artPlace->ourOwner->realizeCurrentTransaction();
}
}
void CArtifactsOfHero::updateParentWindow()
@ -716,85 +725,76 @@ void CArtifactsOfHero::realizeCurrentTransaction()
ArtifactLocation(commonInfo->dst.AOH->curHero, commonInfo->dst.slotID));
}
void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst)
{
bool isCurHeroSrc = src.isHolder(curHero),
isCurHeroDst = dst.isHolder(curHero);
if(isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START)
if(isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot))
updateSlot(src.slot);
if(isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START)
if(isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot))
updateSlot(dst.slot);
if(isCurHeroSrc || isCurHeroDst) //we need to update all slots, artifact might be combined and affect more slots
// We need to update all slots, artifact might be combined and affect more slots
if(isCurHeroSrc || isCurHeroDst)
updateWornSlots(false);
if (!src.isHolder(curHero) && !isCurHeroDst)
if(!isCurHeroSrc && !isCurHeroDst)
return;
if(commonInfo->src == src) //artifact was taken from us
// When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst
// however after first movement we pick the art from TRANSITION_POS and the second movement coming when
// we have a different artifact may look surprising... but it's valid.
// Used when doing dragAndDrop and artifact swap multiple times
if(src.slot == ArtifactPosition::TRANSITION_POS &&
commonInfo->src.slotID == ArtifactPosition::TRANSITION_POS &&
commonInfo->dst.slotID == ArtifactPosition::PRE_FIRST &&
isCurHeroDst)
{
assert(commonInfo->dst == dst //expected movement from slot ot slot
|| dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START //artifact moved back to backpack (eg. to make place for art we are moving)
auto art = curHero->getArt(ArtifactPosition::TRANSITION_POS);
assert(art);
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", art->artType->getIconIndex()));
markPossibleSlots(art);
commonInfo->src.art = art;
commonInfo->src.slotID = src.slot;
}
// Artifact was taken from us
else if(commonInfo->src == src)
{
// Expected movement from slot ot slot
assert(commonInfo->dst == dst
// Artifact moved back to backpack (eg. to make place for art we are moving)
|| dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START
|| dst.getHolderArtSet()->bearerType() != ArtBearer::HERO);
commonInfo->reset();
unmarkSlots();
}
else if(commonInfo->dst == src) //the dest artifact was moved -> we are picking it
// The dest artifact was moved after the swap -> we are picking it
else if(commonInfo->dst == src)
{
assert(dst.slot >= GameConstants::BACKPACK_START);
assert(dst.slot == ArtifactPosition::TRANSITION_POS);
commonInfo->reset();
CArtifactsOfHero::ArtPlacePtr ap;
for(CArtifactsOfHero *aoh : commonInfo->participants)
for(CArtifactsOfHero * aoh : commonInfo->participants)
{
if(dst.isHolder(aoh->curHero))
{
commonInfo->src.AOH = aoh;
if((ap = aoh->getArtPlace(dst.slot)))//getArtPlace may return null
break;
break;
}
}
if(ap)
{
ap->select();
}
else
{
commonInfo->src.art = dst.getArt();
commonInfo->src.slotID = dst.slot;
assert(commonInfo->src.AOH);
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", dst.getArt()->artType->getIconIndex()));
markPossibleSlots(dst.getArt());
}
}
else if(src.slot >= GameConstants::BACKPACK_START &&
src.slot < commonInfo->src.slotID &&
src.isHolder(commonInfo->src.AOH->curHero)) //artifact taken from before currently picked one
{
//int fixedSlot = src.hero->getArtPos(commonInfo->src.art);
vstd::advance(commonInfo->src.slotID, -1);
assert(commonInfo->src.valid());
}
else
{
//when moving one artifact onto another it leads to two art movements: dst->backapck; src->dst
// however after first movement we pick the art from backpack and the second movement coming when
// we have a different artifact may look surprising... but it's valid.
commonInfo->src.art = dst.getArt();
commonInfo->src.slotID = dst.slot;
assert(commonInfo->src.AOH);
CCS->curh->dragAndDropCursor(std::make_unique<CAnimImage>("artifact", dst.getArt()->artType->getIconIndex()));
}
updateParentWindow();
int shift = 0;
// if(dst.slot >= Arts::BACKPACK_START && dst.slot - Arts::BACKPACK_START < backpackPos)
// shift++;
//
if(src.slot < GameConstants::BACKPACK_START && dst.slot - GameConstants::BACKPACK_START < backpackPos)
shift++;
if(dst.slot < GameConstants::BACKPACK_START && src.slot - GameConstants::BACKPACK_START < backpackPos)
shift--;
if( (isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START)
|| (isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) )
scrollBackpack(shift); //update backpack slots
// If backpack is changed, update it
if((isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot))
|| (isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot)))
scrollBackpack(0);
}
void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al)
@ -808,11 +808,15 @@ void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al)
}
}
CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(int slot)
CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(ArtifactPosition slot)
{
if(slot == ArtifactPosition::TRANSITION_POS)
{
return nullptr;
}
if(slot < GameConstants::BACKPACK_START)
{
if(artWorn.find(ArtifactPosition(slot)) == artWorn.end())
if(artWorn.find(slot) == artWorn.end())
{
logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
return nullptr;

View File

@ -141,7 +141,7 @@ public:
void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst);
void artifactRemoved(const ArtifactLocation &al);
void artifactUpdateSlots(const ArtifactLocation &al);
ArtPlacePtr getArtPlace(int slot);//may return null
ArtPlacePtr getArtPlace(ArtifactPosition slot);//may return null
void setHero(const CGHeroInstance * hero);
const CGHeroInstance *getHero() const;

View File

@ -16,6 +16,7 @@
#include "../gui/SDL_Pixels.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../gui/ColorFilter.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
@ -339,7 +340,7 @@ void CAnimImage::playerColored(PlayerColor currPlayer)
anim->getImage(0, group)->playerColored(player);
}
CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group):
CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group, uint8_t alpha):
anim(std::make_shared<CAnimation>(name)),
group(Group),
frame(0),
@ -349,7 +350,7 @@ CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Del
flags(Flags),
xOffset(0),
yOffset(0),
alpha(255)
alpha(alpha)
{
anim->loadGroup(group);
last = anim->size(group);
@ -454,7 +455,12 @@ void CShowableAnim::blitImage(size_t frame, size_t group, SDL_Surface *to)
Rect src( xOffset, yOffset, pos.w, pos.h);
auto img = anim->getImage(frame, group);
if(img)
img->draw(to, pos.x, pos.y, &src, alpha);
{
const ColorFilter alphaFilter = ColorFilter::genAlphaShifter(vstd::lerp(0.0f, 1.0f, alpha/255.0f));
img->adjustPalette(alphaFilter);
img->draw(to, pos.x, pos.y, &src);
}
}
void CShowableAnim::rotate(bool on, bool vertical)

View File

@ -142,7 +142,7 @@ public:
//Set per-surface alpha, 0 = transparent, 255 = opaque
void setAlpha(ui32 alphaValue);
CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0);
CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0, uint8_t alpha = UINT8_MAX);
~CShowableAnim();
//set animation to group or part of group

View File

@ -524,9 +524,6 @@ CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput)
void CKeyboardFocusListener::focusGot()
{
CSDL_Ext::startTextInput(&textInput->pos);
#ifdef VCMI_ANDROID
textInput->notifyAndroidTextInputChanged(textInput->text);
#endif
usageIndex++;
}
@ -588,9 +585,6 @@ void CTextInput::keyPressed(const SDL_KeyboardEvent & key)
{
redraw();
cb(text);
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
}
@ -604,10 +598,6 @@ void CTextInput::setText(const std::string & nText, bool callCb)
CLabel::setText(nText);
if(callCb)
cb(text);
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key)
@ -633,10 +623,6 @@ void CTextInput::textInputed(const SDL_TextInputEvent & event)
cb(text);
}
newText.clear();
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
void CTextInput::textEdited(const SDL_TextEditingEvent & event)
@ -647,11 +633,6 @@ void CTextInput::textEdited(const SDL_TextEditingEvent & event)
newText = event.text;
redraw();
cb(text + newText);
#ifdef VCMI_ANDROID
auto editedText = text + newText;
notifyAndroidTextInputChanged(editedText);
#endif
}
void CTextInput::filenameFilter(std::string & text, const std::string &)
@ -698,24 +679,6 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i
}
}
#ifdef VCMI_ANDROID
void CTextInput::notifyAndroidTextInputChanged(std::string & text)
{
if(!focus)
return;
auto fun = [&text](JNIEnv * env, jclass cls, jmethodID method)
{
auto jtext = env->NewStringUTF(text.c_str());
env->CallStaticVoidMethod(cls, method, jtext);
env->DeleteLocalRef(jtext);
};
CAndroidVMHelper vmHelper;
vmHelper.callCustomMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "notifyTextInputChanged",
"(Ljava/lang/String;)V", fun, true);
}
#endif //VCMI_ANDROID
CFocusable::CFocusable()
:CFocusable(std::make_shared<IFocusListener>())
{

View File

@ -212,9 +212,6 @@ class CTextInput : public CLabel, public CFocusable
protected:
std::string visibleText() override;
#ifdef VCMI_ANDROID
void notifyAndroidTextInputChanged(std::string & text);
#endif
public:
CFunctionList<void(const std::string &)> cb;
CFunctionList<void(std::string &, const std::string &)> filters;

View File

@ -1038,7 +1038,7 @@ void CAdvMapInt::show(SDL_Surface * to)
{
++heroAnim;
}
if(animValHitCount == 8)
if(animValHitCount >= 8)
{
CGI->mh->updateWater();
animValHitCount = 0;

View File

@ -1247,12 +1247,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
updateWidgets();
}
CExchangeWindow::~CExchangeWindow()
{
artifs[0]->commonInfo = nullptr;
artifs[1]->commonInfo = nullptr;
}
const CGarrisonSlot * CExchangeWindow::getSelectedSlotID() const
{
return garr->getSelection();

View File

@ -379,7 +379,6 @@ public:
const CGarrisonSlot * getSelectedSlotID() const;
CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID);
~CExchangeWindow();
};
/// Here you can buy ships

View File

@ -13,7 +13,7 @@ class VCMI(ConanFile):
"minizip/[~1.2.12]",
"onetbb/[^2021.3]", # Nullkiller AI
"qt/[~5.15.2]", # launcher
"sdl/[~2.24.0]",
"sdl/[~2.26.1 || >=2.0.20 <=2.22.0]", # versions in between have broken sound
"sdl_image/[~2.0.5]",
"sdl_mixer/[~2.0.4]",
"sdl_ttf/[~2.0.18]",

View File

@ -37,7 +37,7 @@
},
"encoding" : {
"type" : "string",
"default" : "CP1252"
"default" : "auto"
},
"swipe" : {
"type" : "boolean",

View File

@ -54,7 +54,7 @@
"rockTerrain":
{
"type": "string",
"description": "The name of tock type terrain which will be used as borders in the underground"
"description": "The name of rock type terrain which will be used as borders in the underground"
},
"river":
{

View File

@ -20,8 +20,8 @@ The following platforms are supported and known to work, others might require ch
1. Check if your build environment can use the prebuilt binaries: basically, that your compiler version (or Xcode major version) matches the information below. If you're unsure, simply advance to the next step.
- macOS: libraries are built with Apple clang 13 (Xcode 13.4.1), should be consumable by Xcode and Xcode CLT 13.x
- iOS: libraries are built with Apple clang 13 (Xcode 13.4.1), should be consumable by Xcode 13.x
- macOS: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode and Xcode CLT 14.x (older library versions are also available for Xcode 13, see Releases in the respective repo)
- iOS: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode 14.x (older library versions are also available for Xcode 13, see Releases in the respective repo)
2. Download the binaries archive and unpack it to `~/.conan` directory:
@ -85,7 +85,8 @@ conan install . \
--no-imports \
--build=never \
--profile:build=default \
--profile:host=CI/conan/macos-intel
--profile:host=CI/conan/macos-intel \
-o with_apple_system_libs=True
cmake -S . -B build -G Xcode \
--toolchain conan-generated/conan_toolchain.cmake
@ -116,7 +117,8 @@ conan install . \
--no-imports \
--build=never \
--profile:build=default \
--profile:host=CI/conan/ios-arm64
--profile:host=CI/conan/ios-arm64 \
-o with_apple_system_libs=True
cmake --preset ios-conan
```

View File

@ -128,6 +128,11 @@ enable_pch(vcmilauncher)
if(APPLE_IOS)
set(ICONS_DESTINATION ${DATA_DIR})
# TODO: remove after fixing Conan's Qt recipe
if(XCODE_VERSION VERSION_GREATER_EQUAL 14.0)
target_link_libraries(vcmilauncher "-framework IOKit")
endif()
# workaround https://github.com/conan-io/conan-center-index/issues/13332
if(USING_CONAN)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/QIOSIntegrationPlugin.h

View File

@ -125,7 +125,7 @@ bool CModEntry::isCompatible() const
bool CModEntry::isEssential() const
{
return getValue("storedLocaly").toBool();
return getName() == "vcmi";
}
bool CModEntry::isInstalled() const

View File

@ -33,6 +33,8 @@ QString resolutionToString(const QSize & resolution)
/// Note that it is possible to specify enconding manually in settings.json
static const std::string knownEncodingsList[] = //TODO: remove hardcode
{
// Asks vcmi to automatically detect encoding
"auto",
// European Windows-125X encodings
"CP1250", // West European, covers mostly Slavic languages that use latin script
"CP1251", // Covers languages that use cyrillic scrypt

View File

@ -526,6 +526,11 @@
</item>
<item row="10" column="7" colspan="3">
<widget class="QComboBox" name="comboBoxEncoding">
<item>
<property name="text">
<string>Automatic detection</string>
</property>
</item>
<item>
<property name="text">
<string>Central European (Windows 1250)</string>

View File

@ -21,11 +21,6 @@ void CAndroidVMHelper::cacheVM(JNIEnv * env)
env->GetJavaVM(&vmCache);
}
void CAndroidVMHelper::cacheVM(JavaVM * vm)
{
vmCache = vm;
}
CAndroidVMHelper::CAndroidVMHelper()
{
auto res = vmCache->GetEnv((void **) &envPtr, JNI_VERSION_1_1);

View File

@ -42,8 +42,6 @@ public:
static void cacheVM(JNIEnv * env);
static void cacheVM(JavaVM * vm);
static constexpr const char * NATIVE_METHODS_DEFAULT_CLASS = "eu/vcmi/vcmi/NativeMethods";
};

View File

@ -824,7 +824,12 @@ bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestR
bool CArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const
{
if(slot >= GameConstants::BACKPACK_START)
if(slot == ArtifactPosition::TRANSITION_POS)
{
return true;
}
if(ArtifactUtils::isSlotBackpack(slot))
{
if(artType->isBig())
return false;
@ -851,7 +856,7 @@ void CArtifactInstance::putAt(ArtifactLocation al)
assert(canBePutAt(al));
al.getHolderArtSet()->setNewArtSlot(al.slot, this, false);
if(!ArtifactUtils::isSlotBackpack(al.slot))
if(ArtifactUtils::isSlotEquipment(al.slot))
al.getHolderNode()->attachTo(*this);
}
@ -859,7 +864,7 @@ void CArtifactInstance::removeFrom(ArtifactLocation al)
{
assert(al.getHolderArtSet()->getArt(al.slot) == this);
al.getHolderArtSet()->eraseArtSlot(al.slot);
if(!ArtifactUtils::isSlotBackpack(al.slot))
if(ArtifactUtils::isSlotEquipment(al.slot))
al.getHolderNode()->detachFrom(*this);
}
@ -998,6 +1003,8 @@ bool CArtifactInstance::isPart(const CArtifactInstance *supposedPart) const
bool CCombinedArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const
{
if(slot == ArtifactPosition::TRANSITION_POS)
return true;
bool canMainArtifactBePlaced = CArtifactInstance::canBePutAt(artSet, slot, assumeDestRemoved);
if(!canMainArtifactBePlaced)
return false; //no is no...
@ -1070,7 +1077,11 @@ void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, Artifac
void CCombinedArtifactInstance::putAt(ArtifactLocation al)
{
if(ArtifactUtils::isSlotBackpack(al.slot))
if(al.slot == ArtifactPosition::TRANSITION_POS)
{
CArtifactInstance::putAt(al);
}
else if(ArtifactUtils::isSlotBackpack(al.slot))
{
CArtifactInstance::putAt(al);
for(ConstituentInfo &ci : constituentsInfo)
@ -1108,7 +1119,7 @@ void CCombinedArtifactInstance::putAt(ArtifactLocation al)
void CCombinedArtifactInstance::removeFrom(ArtifactLocation al)
{
if(ArtifactUtils::isSlotBackpack(al.slot))
if(ArtifactUtils::isSlotBackpack(al.slot) || al.slot == ArtifactPosition::TRANSITION_POS)
{
CArtifactInstance::removeFrom(al);
}
@ -1329,6 +1340,12 @@ const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(Artifact
const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const
{
if(pos == ArtifactPosition::TRANSITION_POS)
{
// Always add to the end. Always take from the beginning.
assert(!artifactsTransitionPos.empty());
return &(*artifactsTransitionPos.begin());
}
if(vstd::contains(artifactsWorn, pos))
return &artifactsWorn.at(pos);
if(pos >= ArtifactPosition::AFTER_LAST )
@ -1355,7 +1372,13 @@ ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot)
{
assert(!vstd::contains(artifactsWorn, slot));
if (!ArtifactUtils::isSlotBackpack(slot))
if(slot == ArtifactPosition::TRANSITION_POS)
{
// Always add to the end. Always take from the beginning.
artifactsTransitionPos.push_back(ArtSlotInfo());
return artifactsTransitionPos.back();
}
if(!ArtifactUtils::isSlotBackpack(slot))
return artifactsWorn[slot];
ArtSlotInfo newSlot;
@ -1375,7 +1398,12 @@ void CArtifactSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art,
void CArtifactSet::eraseArtSlot(ArtifactPosition slot)
{
if(ArtifactUtils::isSlotBackpack(slot))
if(slot == ArtifactPosition::TRANSITION_POS)
{
assert(!artifactsTransitionPos.empty());
artifactsTransitionPos.erase(artifactsTransitionPos.begin());
}
else if(ArtifactUtils::isSlotBackpack(slot))
{
auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START);
@ -1612,4 +1640,9 @@ DLL_LINKAGE bool ArtifactUtils::isSlotBackpack(ArtifactPosition slot)
return slot >= GameConstants::BACKPACK_START;
}
DLL_LINKAGE bool ArtifactUtils::isSlotEquipment(ArtifactPosition slot)
{
return slot < GameConstants::BACKPACK_START && slot >= 0;
}
VCMI_LIB_NAMESPACE_END

View File

@ -317,6 +317,7 @@ class DLL_LINKAGE CArtifactSet
public:
std::vector<ArtSlotInfo> artifactsInBackpack; //hero's artifacts from bag
std::map<ArtifactPosition, ArtSlotInfo> artifactsWorn; //map<position,artifact_id>; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5
std::vector<ArtSlotInfo> artifactsTransitionPos; // Used as transition position for dragAndDrop artifact exchange
ArtSlotInfo & retrieveNewArtSlot(ArtifactPosition slot);
void setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked);
@ -392,6 +393,7 @@ namespace ArtifactUtils
DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot);
DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot);
DLL_LINKAGE bool isSlotBackpack(ArtifactPosition slot);
DLL_LINKAGE bool isSlotEquipment(ArtifactPosition slot);
}
VCMI_LIB_NAMESPACE_END

View File

@ -106,7 +106,84 @@ bool Unicode::isValidString(const char * data, size_t size)
static std::string getSelectedEncoding()
{
return settings["general"]["encoding"].String();
auto explicitSetting = settings["general"]["encoding"].String();
if (explicitSetting != "auto")
return explicitSetting;
return settings["session"]["encoding"].String();
}
/// Detects encoding of H3 text files based on matching against pregenerated footprints of H3 file
/// Can also detect language of H3 install, however right now this is not necessary
static void detectEncoding()
{
static const size_t knownCount = 6;
// "footprints" of data collected from known versions of H3
static const std::array<std::array<double, 16>, knownCount> knownFootprints =
{ {
{ { 0.0559, 0.0000, 0.1983, 0.0051, 0.0222, 0.0183, 0.4596, 0.2405, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 } },
{ { 0.0493, 0.0000, 0.1926, 0.0047, 0.0230, 0.0121, 0.4133, 0.2780, 0.0002, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0259, 0.0008 } },
{ { 0.0534, 0.0000, 0.1705, 0.0047, 0.0418, 0.0208, 0.4775, 0.2191, 0.0001, 0.0000, 0.0000, 0.0000, 0.0000, 0.0005, 0.0036, 0.0080 } },
{ { 0.0534, 0.0000, 0.1701, 0.0067, 0.0157, 0.0133, 0.4328, 0.2540, 0.0001, 0.0043, 0.0000, 0.0244, 0.0000, 0.0000, 0.0181, 0.0071 } },
{ { 0.0548, 0.0000, 0.1744, 0.0061, 0.0031, 0.0009, 0.0046, 0.0136, 0.0000, 0.0004, 0.0000, 0.0000, 0.0227, 0.0061, 0.4882, 0.2252 } },
{ { 0.0559, 0.0000, 0.1807, 0.0059, 0.0036, 0.0013, 0.0046, 0.0134, 0.0000, 0.0004, 0.0000, 0.0487, 0.0209, 0.0060, 0.4615, 0.1972 } }
} };
// languages of known footprints
static const std::array<std::string, knownCount> knownLanguages =
{ {
"English", "French", "German", "Polish", "Russian", "Ukrainian"
} };
// encoding that should be used for known footprints
static const std::array<std::string, knownCount> knownEncodings =
{ {
"CP1252", "CP1252", "CP1252", "CP1250", "CP1251", "CP1251"
} };
// load file that will be used for footprint generation
// this is one of the most text-heavy files in game and consists solely from translated texts
auto resource = CResourceHandler::get()->load(ResourceID("DATA/GENRLTXT.TXT", EResType::TEXT));
std::array<size_t, 256> charCount;
std::array<double, 16> footprint;
std::array<double, knownCount> deviations;
boost::range::fill(charCount, 0);
boost::range::fill(footprint, 0.0);
boost::range::fill(deviations, 0.0);
auto data = resource->readAll();
// compute how often each character occurs in input file
for (size_t i = 0; i < data.second; ++i)
charCount[data.first[i]] += 1;
// and convert computed data into weights
// to reduce amount of data, group footprint data into 16-char blocks.
// While this will reduce precision, it should not affect output
// since we expect only tiny differences compared to reference footprints
for (size_t i = 0; i < 256; ++i)
footprint[i/16] += double(charCount[i]) / data.second;
logGlobal->debug("Language footprint: %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f",
footprint[0], footprint[1], footprint[2], footprint[3], footprint[4], footprint[5], footprint[6], footprint[7],
footprint[8], footprint[9], footprint[10], footprint[11], footprint[12], footprint[13], footprint[14], footprint[15]
);
for (size_t i = 0; i < deviations.size(); ++i)
{
for (size_t j = 0; j < footprint.size(); ++j)
deviations[i] += std::abs((footprint[j] - knownFootprints[i][j]));
}
size_t bestIndex = boost::range::min_element(deviations) - deviations.begin();
for (size_t i = 0; i < deviations.size(); ++i)
logGlobal->debug("Comparing to %s: %f", knownLanguages[i], deviations[i]);
Settings s = settings.write["session"]["encoding"];
s->String() = knownEncodings[bestIndex];
}
std::string Unicode::toUnicode(const std::string &text)
@ -364,6 +441,9 @@ CGeneralTextHandler::CGeneralTextHandler():
znpc00 (*this, "vcmi.znpc00" ), // technically - wog
qeModCommands (*this, "vcmi.quickExchange" )
{
if (getSelectedEncoding().empty())
detectEncoding();
readToVector("core.vcdesc", "DATA/VCDESC.TXT" );
readToVector("core.lcdesc", "DATA/LCDESC.TXT" );
readToVector("core.tcommand", "DATA/TCOMMAND.TXT" );

View File

@ -983,6 +983,7 @@ class ArtifactPosition
public:
enum EArtifactPosition
{
TRANSITION_POS = -3,
FIRST_AVAILABLE = -2,
PRE_FIRST = -1, //sometimes used as error, sometimes as first free in backpack
HEAD, SHOULDERS, NECK, RIGHT_HAND, LEFT_HAND, TORSO, //5

View File

@ -112,18 +112,31 @@ std::unordered_map<ResourceID, bfs::path> CFilesystemLoader::listFiles(const std
std::vector<bfs::path> path; //vector holding relative path to our file
bfs::recursive_directory_iterator enddir;
#if BOOST_VERSION >= 107200 // 1.72
bfs::recursive_directory_iterator it(baseDirectory, bfs::directory_options::follow_directory_symlink);
#else
bfs::recursive_directory_iterator it(baseDirectory, bfs::symlink_option::recurse);
#endif
for(; it != enddir; ++it)
{
EResType::Type type;
#if BOOST_VERSION >= 107200
const auto currentDepth = it.depth();
#else
const auto currentDepth = it.level();
#endif
if (bfs::is_directory(it->status()))
{
path.resize(it.level() + 1);
path.resize(currentDepth + 1);
path.back() = it->path().filename();
// don't iterate into directory if depth limit reached
it.no_push(depth <= it.level());
#if BOOST_VERSION >= 107200
it.disable_recursion_pending(depth <= currentDepth);
#else
it.no_push(depth <= currentDepth);
#endif
type = EResType::DIRECTORY;
}
@ -134,7 +147,7 @@ std::unordered_map<ResourceID, bfs::path> CFilesystemLoader::listFiles(const std
{
//reconstruct relative filename (not possible via boost AFAIK)
bfs::path filename;
const size_t iterations = std::min((size_t)it.level(), path.size());
const size_t iterations = std::min(static_cast<size_t>(currentDepth), path.size());
if (iterations)
{
filename = path.front();

View File

@ -1963,7 +1963,13 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
for (auto i = h->Slots().begin(); i != h->Slots().end(); i++)
{
TQuantity drown = static_cast<TQuantity>(i->second->count * 0.3);
// 1-sized stacks are not affected by sirens
if (i->second->count == 1)
continue;
// tested H3 behavior: 30% (rounded up) of stack drowns
TQuantity drown = std::ceil(i->second->count * 0.3);
if(drown)
{
cb->changeStackCount(StackLocation(h, i->first), -drown);

View File

@ -33,36 +33,32 @@ void ResourceConverter::convertExtractedResourceFiles(ConversionOptions conversi
void ResourceConverter::doConvertPcxToPng(bool deleteOriginals)
{
std::string filename;
bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "IMAGES";
bfs::directory_iterator end_iter;
for(bfs::directory_iterator dir_itr(imagesPath); dir_itr != end_iter; ++dir_itr)
{
const auto filename = dir_itr->path().filename();
try
{
if (!bfs::is_regular_file(dir_itr->status()))
return;
std::string filePath = dir_itr->path().string();
std::string fileStem = dir_itr->path().stem().string();
filename = dir_itr->path().filename().string();
std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename);
std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename.string());
if(bfs::extension(filenameLowerCase) == ".pcx")
if(boost::algorithm::to_lower_copy(filename.extension().string()) == ".pcx")
{
auto img = BitmapHandler::loadBitmap(filenameLowerCase);
bfs::path pngFilePath = imagesPath / (fileStem + ".png");
bfs::path pngFilePath = imagesPath / (dir_itr->path().stem().string() + ".png");
img.save(pathToQString(pngFilePath), "PNG");
if(deleteOriginals)
bfs::remove(filePath);
bfs::remove(dir_itr->path());
}
}
catch(const std::exception & ex)
{
logGlobal->info(filename + " " + ex.what() + "\n");
logGlobal->info(filename.string() + " " + ex.what() + "\n");
}
}
}

View File

@ -3892,7 +3892,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
// Check if src/dest slots are appropriate for the artifacts exchanged.
// Moving to the backpack is always allowed.
if ((!srcArtifact || dst.slot < GameConstants::BACKPACK_START)
if ((!srcArtifact || !ArtifactUtils::isSlotBackpack(dst.slot))
&& srcArtifact && !srcArtifact->canBePutAt(dst, true))
COMPLAIN_RET("Cannot move artifact!");
@ -3907,24 +3907,37 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
if (src.slot == ArtifactPosition::MACH4 || dst.slot == ArtifactPosition::MACH4)
COMPLAIN_RET("Cannot move catapult!");
if (dst.slot >= GameConstants::BACKPACK_START)
if(ArtifactUtils::isSlotBackpack(dst.slot))
vstd::amin(dst.slot, ArtifactPosition(GameConstants::BACKPACK_START + (si32)dst.getHolderArtSet()->artifactsInBackpack.size()));
if (src.slot == dst.slot && src.artHolder == dst.artHolder)
COMPLAIN_RET("Won't move artifact: Dest same as source!");
if (dst.slot < GameConstants::BACKPACK_START && destArtifact) //moving art to another slot
if(!(src.slot == ArtifactPosition::TRANSITION_POS && dst.slot == ArtifactPosition::TRANSITION_POS))
{
//old artifact must be removed first
moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition(
(si32)dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START)));
}
auto hero = boost::get<ConstTransitivePtr<CGHeroInstance>>(dst.artHolder);
if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
if(src.slot == dst.slot && src.artHolder == dst.artHolder)
COMPLAIN_RET("Won't move artifact: Dest same as source!");
MoveArtifact ma(&src, &dst);
sendAndApply(&ma);
// Check if dst slot is occupied
if(!ArtifactUtils::isSlotBackpack(dst.slot) && destArtifact)
{
// Previous artifact must be removed first
moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition::TRANSITION_POS));
}
try
{
auto hero = boost::get<ConstTransitivePtr<CGHeroInstance>>(dst.artHolder);
if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot))
giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
}
catch (boost::bad_get const &)
{
// object other than hero received an art - ignore
}
MoveArtifact ma(&src, &dst);
if(dst.slot == ArtifactPosition::TRANSITION_POS)
ma.askAssemble = false;
sendAndApply(&ma);
}
return true;
}