1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-05-13 22:06:58 +02:00

Merge pull request #1542 from vcmi/beta

Release 1.1.1
This commit is contained in:
Ivan Savenko 2023-02-03 01:00:18 +02:00 committed by GitHub
commit 9c13ac8eb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 580 additions and 264 deletions

View File

@ -244,7 +244,7 @@ jobs:
- name: Trigger Android
uses: peter-evans/repository-dispatch@v1
if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }}
if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }}
with:
token: ${{ secrets.VCMI_ANDROID_ACCESS_TOKEN }}
repository: vcmi/vcmi-android

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,3 +0,0 @@
#!/usr/bin/env bash
"$1/ios/zip2ipa.sh" "$2"

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

@ -506,6 +506,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()
@ -618,6 +621,7 @@ elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
add_subdirectory(osx)
elseif(APPLE_IOS)
set(CPACK_GENERATOR ZIP)
set(CPACK_ARCHIVE_FILE_EXTENSION ipa)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_CURRENT_BINARY_DIR};${CMAKE_PROJECT_NAME};app;/")
else()

View File

@ -1,3 +1,34 @@
# 1.1.0 -> 1.1.1
### GENERAL:
* Fixed missing sound in Polish version from gog.com
* Fixed positioning of main menu buttons in localized versions of H3
* Fixed crash on transferring artifact to commander
* Fixed game freeze on receiving multiple artifact assembly dialogs after combat
* Fixed potential game freeze on end of music playback
* macOS/iOS: fixed sound glitches
* Android: upgraded version of SDL library
* Android: reworked right click gesture and relative pointer mode
* Improved map loading speed
### ADVENTURE MAP:
* Fixed hero movement lag in single-player games
* Fixed number of drowned troops on visiting Sirens to match H3
* iOS: pinch gesture visits current object (Spacebar behavior) instead of activating in-game console
### TOWNS:
* Fixed displaying growth bonus from Statue of Legion
* Growth bonus tooltip ordering now matches H3
* Buy All Units dialog will now buy units starting from the highest level
### LAUNCHER:
* Local mods can be disabled or uninstalled
* Fixed styling of Launcher interface
### MAP EDITOR:
* Fixed saving of roads and rivers
* Fixed placement of heroes on map
1.0.0 -> 1.1.0
GENERAL:

View File

@ -374,7 +374,8 @@ int main(int argc, char * argv[])
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
#endif // VCMI_ANDROID
GH.mainFPSmng->init(); //(!)init here AFTER SDL_Init() while using SDL for FPS management
//(!)init here AFTER SDL_Init() while using SDL for FPS management
GH.init();
SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
@ -430,11 +431,20 @@ int main(int argc, char * argv[])
CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
}
#ifdef VCMI_MAC
// Ctrl+click should be treated as a right click on Mac OS X
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
#endif
#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS
if(GH.isPointerRelativeMode)
{
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
}
#endif
#ifndef VCMI_NO_THREADED_LOAD
//we can properly play intro only in the main thread, so we have to move loading to the separate thread
boost::thread loading(init);
@ -1079,7 +1089,8 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
if (displayIndex < 0)
displayIndex = 0;
}
#ifdef VCMI_IOS
#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
SDL_GetWindowSize(mainWindow, &w, &h);
#else
if(!checkVideoMode(displayIndex, w, h))

View File

@ -234,8 +234,7 @@ elseif(APPLE_IOS)
set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
# add max version condition when https://gitlab.kitware.com/cmake/cmake/-/merge_requests/7562 is merged
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0")
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0" AND CMAKE_VERSION VERSION_LESS "3.25.0")
set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
endif()
endforeach()

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 =
@ -409,6 +409,8 @@ void CMusicHandler::release()
void CMusicHandler::playMusic(const std::string & musicURI, bool loop, bool fromStart)
{
boost::mutex::scoped_lock guard(mutex);
if (current && current->isPlaying() && current->isTrack(musicURI))
return;
@ -422,6 +424,8 @@ void CMusicHandler::playMusicFromSet(const std::string & musicSet, const std::st
void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
{
boost::mutex::scoped_lock guard(mutex);
auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end())
{
@ -441,8 +445,6 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
if (!initialized)
return;
boost::mutex::scoped_lock guard(mutex);
next = std::move(queued);
if (current.get() == nullptr || !current->stop(1000))
@ -487,13 +489,32 @@ void CMusicHandler::setVolume(ui32 percent)
void CMusicHandler::musicFinishedCallback()
{
boost::mutex::scoped_lock guard(mutex);
// boost::mutex::scoped_lock guard(mutex);
// FIXME: WORKAROUND FOR A POTENTIAL DEADLOCK
// It is possible for:
// 1) SDL thread to call this method on end of playback
// 2) VCMI code to call queueNext() method to queue new file
// this leads to:
// 1) SDL thread waiting to acquire music lock in this method (while keeping internal SDL mutex locked)
// 2) VCMI thread waiting to acquire internal SDL mutex (while keeping music mutex locked)
// Because of that (and lack of clear way to fix that)
// We will try to acquire lock here and if failed - do nothing
// This may break music playback till next song is enqued but won't deadlock the game
if (!mutex.try_lock())
{
logGlobal->error("Failed to acquire mutex! Unable to restart music!");
return;
}
if (current.get() != nullptr)
{
// if music is looped, play it again
if (current->play())
{
mutex.unlock();
return;
}
else
current.reset();
}
@ -503,6 +524,7 @@ void CMusicHandler::musicFinishedCallback()
current.reset(next.release());
current->play();
}
mutex.unlock();
}
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart):
@ -520,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);

View File

@ -1702,7 +1702,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"))
@ -2273,6 +2273,8 @@ void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
if (artWin)
artWin->artifactRemoved(al);
}
waitWhileDialog();
}
void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
@ -2287,6 +2289,8 @@ void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const Artifact
}
if(!GH.objsToBlit.empty())
GH.objsToBlit.back()->redraw();
waitWhileDialog();
}
void CPlayerInterface::artifactPossibleAssembling(const ArtifactLocation & dst)
@ -2546,6 +2550,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

@ -770,9 +770,15 @@ void CClient::reinitScripting()
#endif
}
#ifdef VCMI_ANDROID
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jclass cls)
{
logNetwork->info("Received clientSetupJNI");
CAndroidVMHelper::cacheVM(env);
}
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls)
{
logNetwork->info("Received server closed signal");
if (CSH) {
@ -780,13 +786,13 @@ extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerCl
}
}
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jobject cls)
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jclass cls)
{
logNetwork->info("Received server ready signal");
androidTestServerReadyFlag.store(true);
}
extern "C" JNIEXPORT bool JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jobject cls)
extern "C" JNIEXPORT jboolean JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jclass cls)
{
logGlobal->info("Received emergency save game request");
if(!LOCPLINT || !LOCPLINT->cb)

View File

@ -1275,6 +1275,7 @@ void CBattleInterface::battleFinished(const BattleResult& br)
void CBattleInterface::displayBattleFinished()
{
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
curInt->waitWhileDialog();
if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
{
close();

View File

@ -78,6 +78,13 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
processList(CIntObject::TEXTINPUT,activityFlag,&textInterested,cb);
}
void CGuiHandler::init()
{
mainFPSmng->init();
isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
}
void CGuiHandler::handleElementActivate(CIntObject * elem, ui16 activityFlag)
{
processLists(activityFlag,[&](std::list<CIntObject*> * lst){
@ -206,6 +213,78 @@ void CGuiHandler::handleEvents()
}
}
void CGuiHandler::convertTouchToMouse(SDL_Event * current)
{
int rLogicalWidth, rLogicalHeight;
SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
int adjustedMouseY = (int)(current->tfinger.y * rLogicalHeight);
int adjustedMouseX = (int)(current->tfinger.x * rLogicalWidth);
current->button.x = adjustedMouseX;
current->motion.x = adjustedMouseX;
current->button.y = adjustedMouseY;
current->motion.y = adjustedMouseY;
}
void CGuiHandler::fakeMoveCursor(float dx, float dy)
{
int x, y, w, h;
SDL_Event event;
SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
sme.state = SDL_GetMouseState(&x, &y);
SDL_GetWindowSize(mainWindow, &w, &h);
sme.x = CCS->curh->xpos + (int)(GH.pointerSpeedMultiplier * w * dx);
sme.y = CCS->curh->ypos + (int)(GH.pointerSpeedMultiplier * h * dy);
vstd::abetween(sme.x, 0, w);
vstd::abetween(sme.y, 0, h);
event.motion = sme;
SDL_PushEvent(&event);
}
void CGuiHandler::fakeMouseMove()
{
fakeMoveCursor(0, 0);
}
void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
{
SDL_Event event;
SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if(!down)
{
sme.type = SDL_MOUSEBUTTONUP;
}
sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
sme.x = CCS->curh->xpos;
sme.y = CCS->curh->ypos;
float xScale, yScale;
int w, h, rLogicalWidth, rLogicalHeight;
SDL_GetWindowSize(mainWindow, &w, &h);
SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
SDL_WarpMouse(
(int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2,
(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2));
SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
event.button = sme;
SDL_PushEvent(&event);
}
void CGuiHandler::handleCurrentEvent()
{
if(current->type == SDL_KEYDOWN || current->type == SDL_KEYUP)
@ -338,22 +417,79 @@ void CGuiHandler::handleCurrentEvent()
it->textEdited(current->edit);
}
}
//todo: muiltitouch
else if(current->type == SDL_MOUSEBUTTONUP)
{
switch(current->button.button)
if(!multifinger)
{
case SDL_BUTTON_LEFT:
handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
break;
case SDL_BUTTON_RIGHT:
handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
break;
case SDL_BUTTON_MIDDLE:
handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
break;
switch(current->button.button)
{
case SDL_BUTTON_LEFT:
handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
break;
case SDL_BUTTON_RIGHT:
handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
break;
case SDL_BUTTON_MIDDLE:
handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
break;
}
}
}
else if(current->type == SDL_FINGERMOTION)
{
if(isPointerRelativeMode)
{
fakeMoveCursor(current->tfinger.dx, current->tfinger.dy);
}
}
else if(current->type == SDL_FINGERDOWN)
{
auto fingerCount = SDL_GetNumTouchFingers(current->tfinger.touchId);
multifinger = fingerCount > 1;
if(isPointerRelativeMode)
{
if(current->tfinger.x > 0.5)
{
bool isRightClick = current->tfinger.y < 0.5;
fakeMouseButtonEventRelativeMode(true, isRightClick);
}
}
#ifndef VCMI_IOS
else if(fingerCount == 2)
{
convertTouchToMouse(current);
handleMouseMotion();
handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
}
#endif //VCMI_IOS
}
else if(current->type == SDL_FINGERUP)
{
auto fingerCount = SDL_GetNumTouchFingers(current->tfinger.touchId);
if(isPointerRelativeMode)
{
if(current->tfinger.x > 0.5)
{
bool isRightClick = current->tfinger.y < 0.5;
fakeMouseButtonEventRelativeMode(false, isRightClick);
}
}
#ifndef VCMI_IOS
else if(multifinger)
{
convertTouchToMouse(current);
handleMouseMotion();
handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
multifinger = fingerCount != 0;
}
#endif //VCMI_IOS
}
current = nullptr;
} //event end
@ -426,20 +562,6 @@ void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
}
}
void CGuiHandler::fakeMouseMove()
{
SDL_Event event;
SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
int x, y;
sme.state = SDL_GetMouseState(&x, &y);
sme.x = x;
sme.y = y;
event.motion = sme;
SDL_PushEvent(&event);
}
void CGuiHandler::renderFrame()
{
@ -481,7 +603,8 @@ void CGuiHandler::renderFrame()
CGuiHandler::CGuiHandler()
: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false)
: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false),
multifinger(false)
{
continueEventHandling = true;
curInt = nullptr;

View File

@ -88,6 +88,10 @@ private:
void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed);
void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
void convertTouchToMouse(SDL_Event * current);
void fakeMoveCursor(float dx, float dy);
void fakeMouseButtonEventRelativeMode(bool down, bool right);
public:
void handleElementActivate(CIntObject * elem, ui16 activityFlag);
void handleElementDeActivate(CIntObject * elem, ui16 activityFlag);
@ -101,6 +105,9 @@ public:
Point lastClick;
unsigned lastClickTime;
bool multifinger;
bool isPointerRelativeMode;
float pointerSpeedMultiplier;
ui8 defActionsDef; //default auto actions
bool captureChildren; //all newly created objects will get their parents from stack and will be added to parents children list
@ -109,6 +116,7 @@ public:
CGuiHandler();
~CGuiHandler();
void init();
void renderFrame();
void totalRedraw(); //forces total redraw (using showAll), sets a flag, method gets called at the end of the rendering

View File

@ -863,20 +863,10 @@ SDL_Color CSDL_Ext::makeColor(ui8 r, ui8 g, ui8 b, ui8 a)
void CSDL_Ext::startTextInput(SDL_Rect * where)
{
auto impl = [](SDL_Rect * where)
{
if (SDL_IsTextInputActive() == SDL_FALSE)
{
SDL_StartTextInput();
}
SDL_SetTextInputRect(where);
};
#ifdef VCMI_APPLE
dispatch_async(dispatch_get_main_queue(), ^{
#endif
#ifdef VCMI_IOS
// TODO ios: looks like SDL bug actually, try fixing there
auto renderer = SDL_GetRenderer(mainWindow);
float scaleX, scaleY;
@ -884,17 +874,26 @@ void CSDL_Ext::startTextInput(SDL_Rect * where)
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
SDL_RenderGetViewport(renderer, &viewport);
#ifdef VCMI_IOS
const auto nativeScale = iOS_utils::screenScale();
auto rectInScreenCoordinates = *where;
rectInScreenCoordinates.x = (viewport.x + rectInScreenCoordinates.x) * scaleX / nativeScale;
rectInScreenCoordinates.y = (viewport.y + rectInScreenCoordinates.y) * scaleY / nativeScale;
rectInScreenCoordinates.w = rectInScreenCoordinates.w * scaleX / nativeScale;
rectInScreenCoordinates.h = rectInScreenCoordinates.h * scaleY / nativeScale;
impl(&rectInScreenCoordinates);
#else
impl(where);
scaleX /= nativeScale;
scaleY /= nativeScale;
#endif
auto rectInScreenCoordinates = *where;
rectInScreenCoordinates.x = (viewport.x + rectInScreenCoordinates.x) * scaleX;
rectInScreenCoordinates.y = (viewport.y + rectInScreenCoordinates.y) * scaleY;
rectInScreenCoordinates.w = rectInScreenCoordinates.w * scaleX;
rectInScreenCoordinates.h = rectInScreenCoordinates.h * scaleY;
SDL_SetTextInputRect(&rectInScreenCoordinates);
if (SDL_IsTextInputActive() == SDL_FALSE)
{
SDL_StartTextInput();
}
#ifdef VCMI_APPLE
});
#endif

View File

@ -10,11 +10,13 @@
#import <UIKit/UIKit.h>
#include <SDL_events.h>
NS_ASSUME_NONNULL_BEGIN
@interface GameChatKeyboardHandler : NSObject
- (void)triggerInput;
+ (void)sendKeyEventWithKeyCode:(SDL_KeyCode)keyCode;
@end

View File

@ -10,20 +10,8 @@
#import "GameChatKeyboardHandler.h"
#include <SDL_events.h>
static int watchReturnKey(void * userdata, SDL_Event * event);
static void sendKeyEvent(SDL_KeyCode keyCode)
{
SDL_Event keyEvent;
keyEvent.key = (SDL_KeyboardEvent){
.type = SDL_KEYDOWN,
.keysym.sym = keyCode,
};
SDL_PushEvent(&keyEvent);
}
@interface GameChatKeyboardHandler ()
@property (nonatomic) BOOL wasChatMessageSent;
@ -31,28 +19,40 @@ static void sendKeyEvent(SDL_KeyCode keyCode)
@implementation GameChatKeyboardHandler
- (void)triggerInput {
+ (void)sendKeyEventWithKeyCode:(SDL_KeyCode)keyCode
{
SDL_Event keyEvent;
keyEvent.key = (SDL_KeyboardEvent){
.type = SDL_KEYDOWN,
.state = SDL_PRESSED,
.keysym.sym = keyCode,
};
SDL_PushEvent(&keyEvent);
}
- (instancetype)init {
self = [super init];
__auto_type notificationCenter = NSNotificationCenter.defaultCenter;
[notificationCenter addObserver:self selector:@selector(textDidBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
[notificationCenter addObserver:self selector:@selector(textDidEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
self.wasChatMessageSent = NO;
sendKeyEvent(SDLK_TAB);
return self;
}
#pragma mark - Notifications
- (void)textDidBeginEditing:(NSNotification *)n {
self.wasChatMessageSent = NO;
// watch for pressing Return to ignore sending Escape key after keyboard is closed
SDL_AddEventWatch(watchReturnKey, (__bridge void *)self);
}
- (void)textDidEndEditing:(NSNotification *)n {
[NSNotificationCenter.defaultCenter removeObserver:self];
// discard chat message
if(!self.wasChatMessageSent)
sendKeyEvent(SDLK_ESCAPE);
[[self class] sendKeyEventWithKeyCode:SDLK_ESCAPE];
}
@end

View File

@ -95,7 +95,7 @@
- (void)handlePinch:(UIGestureRecognizer *)gesture {
if(gesture.state != UIGestureRecognizerStateBegan || CSH->state != EClientState::GAMEPLAY)
return;
[self.gameChatHandler triggerInput];
[GameChatKeyboardHandler sendKeyEventWithKeyCode:SDLK_SPACE];
}
#pragma mark - UIGestureRecognizerDelegate

View File

@ -236,7 +236,11 @@ std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const Js
if(posy < 0)
posy = pos.h + posy;
return std::make_shared<CButton>(Point(posx, posy), button["name"].String(), help, command, (int)button["hotkey"].Float());
auto result = std::make_shared<CButton>(Point(posx, posy), button["name"].String(), help, command, (int)button["hotkey"].Float());
if (button["center"].Bool())
result->moveBy(Point(-result->pos.w/2, -result->pos.h/2));
return result;
}
CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)

View File

@ -488,9 +488,6 @@ CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput)
void CKeyboardFocusListener::focusGot()
{
CSDL_Ext::startTextInput(&textInput->pos);
#ifdef VCMI_ANDROID
textInput->notifyAndroidTextInputChanged(textInput->text);
#endif
usageIndex++;
}
@ -552,9 +549,6 @@ void CTextInput::keyPressed(const SDL_KeyboardEvent & key)
{
redraw();
cb(text);
#ifdef VCMI_ANDROID
notifyAndroidTextInputChanged(text);
#endif
}
}
@ -563,10 +557,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)
@ -592,10 +582,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)
@ -606,11 +592,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 &)
@ -657,24 +638,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

@ -198,9 +198,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

@ -180,7 +180,7 @@ void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
{
#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
if(sEvent.state == 0) // any "button" is enough on mobile
if(sEvent.state == 0 || GH.multifinger) // any "button" is enough on mobile
#else
if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms
#endif
@ -1044,7 +1044,7 @@ void CAdvMapInt::show(SDL_Surface * to)
{
++heroAnim;
}
if(animValHitCount == 8)
if(animValHitCount >= 8)
{
CGI->mh->updateWater();
animValHitCount = 0;

View File

@ -101,7 +101,7 @@ void QuickRecruitmentWindow::maxAllCards(std::vector<std::shared_ptr<CreaturePur
void QuickRecruitmentWindow::purchaseUnits()
{
for(auto selected : cards)
for(auto selected : boost::adaptors::reverse(cards))
{
if(selected->slider->getValue())
{

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

@ -11,12 +11,16 @@
[
{"type" : "lod", "path" : "Data/H3ab_bmp.lod"},
{"type" : "lod", "path" : "Data/H3bitmap.lod"},
{"type" : "lod", "path" : "Data/h3abp_bm.lod"}, // Polish version of H3 only
{"type" : "lod", "path" : "Data/H3pbitma.lod"}, // Polish version of H3 only
{"type" : "dir", "path" : "Data"}
],
"SPRITES/":
[
{"type" : "lod", "path" : "Data/H3ab_spr.lod"},
{"type" : "lod", "path" : "Data/H3sprite.lod"},
{"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Polish version of H3 only
{"type" : "lod", "path" : "Data/H3psprit.lod"}, // Polish version of H3 only
{"type" : "dir", "path" : "Sprites"}
],
"SOUNDS/":

View File

@ -10,29 +10,29 @@
"background" : "gamselbk",
//"scalable" : true, //background will be scaled to screen size
//"video" : {"x": 8, "y": 105, "name":"CREDITS.SMK" },//Floating WoG logo. Disabled due to different position in various versions of H3.
//"images" : [],//Optioal, contains any additional images in the same format as video
//"images" : [],//Optional, contains any additional images in the same format as video
"items" :
[
{
"name" : "main",
"buttons":
[
{"x": 540, "y": 10, "name":"MMENUNG", "hotkey" : 110, "help": 3, "command": "to new"},
{"x": 532, "y": 132, "name":"MMENULG", "hotkey" : 108, "help": 4, "command": "to load"},
{"x": 524, "y": 251, "name":"MMENUHS", "hotkey" : 104, "help": 5, "command": "highscores"},
{"x": 557, "y": 359, "name":"MMENUCR", "hotkey" : 99, "help": 6, "command": "to credits"},
{"x": 586, "y": 468, "name":"MMENUQT", "hotkey" : 27, "help": 7, "command": "exit"}
{"x": 644, "y": 70, "center" : true, "name":"MMENUNG", "hotkey" : 110, "help": 3, "command": "to new"},
{"x": 645, "y": 192, "center" : true, "name":"MMENULG", "hotkey" : 108, "help": 4, "command": "to load"},
{"x": 643, "y": 296, "center" : true, "name":"MMENUHS", "hotkey" : 104, "help": 5, "command": "highscores"},
{"x": 643, "y": 414, "center" : true, "name":"MMENUCR", "hotkey" : 99, "help": 6, "command": "to credits"},
{"x": 643, "y": 520, "center" : true, "name":"MMENUQT", "hotkey" : 27, "help": 7, "command": "exit"}
]
},
{
"name" : "new",
"buttons":
[
{"x": 545, "y": 4, "name":"GTSINGL", "hotkey" : 115, "help": 10, "command": "start single"},
{"x": 568, "y": 120, "name":"GTMULTI", "hotkey" : 109, "help": 12, "command": "start multi"},
{"x": 541, "y": 233, "name":"GTCAMPN", "hotkey" : 99, "help": 11, "command": "to campaign"},
{"x": 545, "y": 358, "name":"GTTUTOR", "hotkey" : 116, "help": 13, "command": "start tutorial"},
{"x": 582, "y": 464, "name":"GTBACK", "hotkey" : 27, "help": 14, "command": "to main"}
{"x": 649, "y": 65, "center" : true, "name":"GTSINGL", "hotkey" : 115, "help": 10, "command": "start single"},
{"x": 649, "y": 180, "center" : true, "name":"GTMULTI", "hotkey" : 109, "help": 12, "command": "start multi"},
{"x": 646, "y": 298, "center" : true, "name":"GTCAMPN", "hotkey" : 99, "help": 11, "command": "to campaign"},
{"x": 647, "y": 412, "center" : true, "name":"GTTUTOR", "hotkey" : 116, "help": 13, "command": "start tutorial"},
{"x": 645, "y": 517, "center" : true, "name":"GTBACK", "hotkey" : 27, "help": 14, "command": "to main"}
],
"images": [ {"x": 114, "y": 312, "name":"NEWGAME"} ]
},
@ -40,11 +40,11 @@
"name" : "load",
"buttons":
[
{"x": 545, "y": 8, "name":"GTSINGL", "hotkey" : 115, "help": 10, "command": "load single"},
{"x": 568, "y": 120, "name":"GTMULTI", "hotkey" : 109, "help": 12, "command": "load multi"},
{"x": 541, "y": 233, "name":"GTCAMPN", "hotkey" : 99, "help": 11, "command": "load campaign"},
{"x": 545, "y": 358, "name":"GTTUTOR", "hotkey" : 116, "help": 13, "command": "load tutorial"},
{"x": 582, "y": 464, "name":"GTBACK", "hotkey" : 27, "help": 14, "command": "to main"}
{"x": 649, "y": 65, "center" : true, "name":"GTSINGL", "hotkey" : 115, "help": 10, "command": "load single"},
{"x": 649, "y": 180, "center" : true, "name":"GTMULTI", "hotkey" : 109, "help": 12, "command": "load multi"},
{"x": 646, "y": 298, "center" : true, "name":"GTCAMPN", "hotkey" : 99, "help": 11, "command": "load campaign"},
{"x": 647, "y": 412, "center" : true, "name":"GTTUTOR", "hotkey" : 116, "help": 13, "command": "load tutorial"},
{"x": 645, "y": 517, "center" : true, "name":"GTBACK", "hotkey" : 27, "help": 14, "command": "to main"}
],
"images": [ {"x": 114, "y": 312, "name":"LOADGAME"} ]
},
@ -52,11 +52,11 @@
"name" : "campaign",
"buttons":
[
{"x": 535, "y": 4, "name":"CSSSOD", "hotkey" : 119, "command": "campaigns sod"},
{"x": 494, "y": 117, "name":"CSSROE", "hotkey" : 114, "command": "campaigns roe"},
{"x": 486, "y": 241, "name":"CSSARM", "hotkey" : 97, "command": "campaigns ab"},
{"x": 550, "y": 358, "name":"CSSCUS", "hotkey" : 99, "command": "start campaign"},
{"x": 582, "y": 464, "name":"GTBACK", "hotkey" : 27, "command": "to new"}
{"x": 634, "y": 67, "center" : true, "name":"CSSSOD", "hotkey" : 119, "command": "campaigns sod"},
{"x": 637, "y": 181, "center" : true, "name":"CSSROE", "hotkey" : 114, "command": "campaigns roe"},
{"x": 638, "y": 301, "center" : true, "name":"CSSARM", "hotkey" : 97, "command": "campaigns ab"},
{"x": 638, "y": 413, "center" : true, "name":"CSSCUS", "hotkey" : 99, "command": "start campaign"},
{"x": 639, "y": 518, "center" : true, "name":"CSSEXIT", "hotkey" : 27, "command": "to new"}
],
}
]

View File

@ -17,7 +17,20 @@
"type" : "object",
"default": {},
"additionalProperties" : false,
"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe", "saveRandomMaps", "saveFrequency", "notifications", "extraDump" ],
"required" : [
"playerName",
"showfps",
"music",
"sound",
"encoding",
"swipe",
"saveRandomMaps",
"saveFrequency",
"notifications",
"extraDump",
"userRelativePointer",
"relativePointerSpeedMultiplier"
],
"properties" : {
"playerName" : {
"type":"string",
@ -70,6 +83,14 @@
"extraDump" : {
"type" : "boolean",
"default" : false
},
"userRelativePointer" : {
"type" : "boolean",
"default" : false
},
"relativePointerSpeedMultiplier" : {
"type" : "number",
"default" : 1
}
}
},

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":
{

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
vcmi (1.1.1) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 03 Feb 2023 12:00:00 +0200
vcmi (1.1.0) jammy; urgency=medium
* New upstream release

1
debian/rules vendored
View File

@ -8,6 +8,7 @@ override_dh_auto_configure:
dh_auto_configure -- \
-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON \
-DCMAKE_INSTALL_RPATH=/usr/lib/$(DEB_HOST_MULTIARCH)/vcmi \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBIN_DIR=games \
-DFORCE_BUNDLED_FL=OFF \
-DENABLE_TEST=0

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

@ -1,9 +0,0 @@
#!/usr/bin/env bash
generatedZip="$1"
if [[ -z "$generatedZip" ]]; then
echo 'generated zip not provided as param'
exit 1
fi
mv "$generatedZip" "$(basename "$generatedZip" .zip).ipa"

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

@ -38,6 +38,7 @@
<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
<url type="faq">https://vcmi.eu/faq/</url>
<releases>
<release version="1.1.1" date="2023-02-03" />
<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" />

View File

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

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);
@ -101,7 +96,7 @@ jclass CAndroidVMHelper::findClass(const std::string & name, bool classloaded)
return get()->FindClass(name.c_str());
}
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jobject * cls)
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jclass cls)
{
CAndroidVMHelper::cacheVM(baseEnv);
CAndroidVMHelper envHelper;

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

@ -1576,7 +1576,7 @@ bool NBonus::hasOfType(const CBonusSystemNode *obj, Bonus::BonusType type, int s
return false;
}
std::string Bonus::Description() const
std::string Bonus::Description(boost::optional<si32> customValue) const
{
std::ostringstream str;
@ -1615,8 +1615,8 @@ std::string Bonus::Description() const
str << description;
}
if(val != 0)
str << " " << std::showpos << val;
if(auto value = customValue.value_or(val))
str << " " << std::showpos << value;
return str.str();
}

View File

@ -510,7 +510,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
return (high << 16) + low;
}
std::string Description() const;
std::string Description(boost::optional<si32> customValue = {}) const;
JsonNode toJsonNode() const;
std::string nameForBonus() const; // generate suitable name for bonus - e.g. for storing in json struct

View File

@ -152,12 +152,12 @@ void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStr
char filename[40];
reader.read(reinterpret_cast<ui8*>(filename), 40);
//for some reason entries in snd have format NAME\0WAVRUBBISH....
//we need to replace first \0 with dot and take the 3 chars with extension (and drop the rest)
// for some reason entries in snd have format NAME\0WAVRUBBISH....
// and Polish version does not have extension at all
// we need to replace first \0 with dot and add wav extension manuall - we don't expect other types here anyway
ArchiveEntry entry;
entry.name = filename; // till 1st \0
entry.name += '.';
entry.name += std::string(filename + entry.name.size(), 3);
entry.name += ".wav";
entry.offset = reader.readInt32();
entry.fullSize = reader.readInt32();

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

@ -553,24 +553,26 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
if(hasBuilt(BuildingID::HORDE_2))
ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth));
int dwellingBonus = 0;
if(const PlayerState *p = cb->getPlayerState(tempOwner, false))
//statue-of-legion-like bonus: % to base+castle
TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH_PERCENT));
for(const auto & b : *bonuses2)
{
dwellingBonus = getDwellingBonus(creatures[level].second, p->dwellings);
const auto growth = b->val * (base + castleBonus) / 100;
ret.entries.push_back(GrowthInfo::Entry(growth, b->Description(growth)));
}
if(dwellingBonus)
ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d
//other *-of-legion-like bonuses (%d to growth cumulative with grail)
TConstBonusListPtr bonuses = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH).And(Selector::subtype()(level)));
for(const auto & b : *bonuses)
ret.entries.push_back(GrowthInfo::Entry(b->val, b->Description()));
//statue-of-legion-like bonus: % to base+castle
TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH_PERCENT));
for(const auto & b : *bonuses2)
ret.entries.push_back(GrowthInfo::Entry(b->val * (base + castleBonus) / 100, b->Description()));
int dwellingBonus = 0;
if(const PlayerState *p = cb->getPlayerState(tempOwner, false))
{
dwellingBonus = getDwellingBonus(creatures[level].second, p->dwellings);
}
if(dwellingBonus)
ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d
if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth
ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2));

View File

@ -1956,7 +1956,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

@ -1292,10 +1292,10 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile)
out << tile.terType->typeCode << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
if(tile.roadType->id != Road::NO_ROAD)
out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
out << tile.roadType->code << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
if(tile.riverType->id != River::NO_RIVER)
out << tile.riverType << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
out << tile.riverType->code << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
return out.str();
}

View File

@ -31,9 +31,18 @@ using namespace boost::asio::ip;
#define LIL_ENDIAN
#endif
struct ConnectionBuffers
{
boost::asio::streambuf readBuffer;
boost::asio::streambuf writeBuffer;
};
void CConnection::init()
{
enableBufferedWrite = false;
enableBufferedRead = false;
connectionBuffers = std::make_unique<ConnectionBuffers>();
socket->set_option(boost::asio::ip::tcp::no_delay(true));
try
{
@ -72,6 +81,7 @@ CConnection::CConnection(std::string host, ui16 port, std::string Name, std::str
int i;
boost::system::error_code error = asio::error::host_not_found;
socket = std::make_shared<tcp::socket>(*io_service);
tcp::resolver resolver(*io_service);
tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
if(error)
@ -138,10 +148,39 @@ CConnection::CConnection(std::shared_ptr<TAcceptor> acceptor, std::shared_ptr<bo
}
init();
}
void CConnection::flushBuffers()
{
if(!enableBufferedWrite)
return;
try
{
asio::write(*socket, connectionBuffers->writeBuffer);
}
catch(...)
{
//connection has been lost
connected = false;
throw;
}
enableBufferedWrite = false;
}
int CConnection::write(const void * data, unsigned size)
{
try
{
if(enableBufferedWrite)
{
std::ostream ostream(&connectionBuffers->writeBuffer);
ostream.write(static_cast<const char *>(data), size);
return size;
}
int ret;
ret = static_cast<int>(asio::write(*socket,asio::const_buffers_1(asio::const_buffer(data,size))));
return ret;
@ -153,10 +192,29 @@ int CConnection::write(const void * data, unsigned size)
throw;
}
}
int CConnection::read(void * data, unsigned size)
{
try
{
if(enableBufferedRead)
{
auto available = connectionBuffers->readBuffer.size();
while(available < size)
{
auto bytesRead = socket->read_some(connectionBuffers->readBuffer.prepare(1024));
connectionBuffers->readBuffer.commit(bytesRead);
available = connectionBuffers->readBuffer.size();
}
std::istream istream(&connectionBuffers->readBuffer);
istream.read(static_cast<char *>(data), size);
return size;
}
int ret = static_cast<int>(asio::read(*socket,asio::mutable_buffers_1(asio::mutable_buffer(data,size))));
return ret;
}
@ -167,6 +225,7 @@ int CConnection::read(void * data, unsigned size)
throw;
}
}
CConnection::~CConnection()
{
if(handler)
@ -210,6 +269,8 @@ void CConnection::reportState(vstd::CLoggerBase * out)
CPack * CConnection::retrievePack()
{
enableBufferedRead = true;
CPack * pack = nullptr;
boost::unique_lock<boost::mutex> lock(*mutexRead);
iser & pack;
@ -222,6 +283,9 @@ CPack * CConnection::retrievePack()
{
pack->c = this->shared_from_this();
}
enableBufferedRead = false;
return pack;
}
@ -229,7 +293,12 @@ void CConnection::sendPack(const CPack * pack)
{
boost::unique_lock<boost::mutex> lock(*mutexWrite);
logNetwork->trace("Sending a pack of type %s", typeid(*pack).name());
enableBufferedWrite = true;
oser & pack;
flushBuffers();
}
void CConnection::disableStackSendingByID()

View File

@ -52,6 +52,7 @@ typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::so
VCMI_LIB_NAMESPACE_BEGIN
struct CPack;
struct ConnectionBuffers;
/// Main class for network communication
/// Allows establishing connection and bidirectional read-write
@ -63,8 +64,14 @@ class DLL_LINKAGE CConnection
int write(const void * data, unsigned size) override;
int read(void * data, unsigned size) override;
void flushBuffers();
std::shared_ptr<boost::asio::io_service> io_service; //can be empty if connection made from socket
bool enableBufferedWrite;
bool enableBufferedRead;
std::unique_ptr<ConnectionBuffers> connectionBuffers;
public:
BinaryDeserializer iser;
BinarySerializer oser;

View File

@ -109,7 +109,8 @@ void Initializer::initialize(CGLighthouse * o)
void Initializer::initialize(CGHeroInstance * o)
{
if(!o) return;
if(!o)
return;
o->tempOwner = defaultPlayer;
if(o->ID == Obj::PRISON)
@ -119,9 +120,9 @@ void Initializer::initialize(CGHeroInstance * o)
{
for(auto t : VLC->heroh->objects)
{
if(t->heroClass == VLC->heroh->classes.objects[o->subID].get())
if(t->heroClass->getId() == HeroClassID(o->subID))
{
o->type = VLC->heroh->objects[o->subID];
o->type = t;
break;
}
}

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::locale::to_lower(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

@ -3916,9 +3916,17 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
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);
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);
sendAndApply(&ma);

View File

@ -28,6 +28,8 @@
#include "../lib/mapping/CMap.h"
#include "../lib/rmg/CMapGenOptions.h"
#ifdef VCMI_ANDROID
#include <jni.h>
#include <android/log.h>
#include "lib/CAndroidVMHelper.h"
#elif !defined(VCMI_IOS)
#include "../lib/Interprocess.h"
@ -1118,15 +1120,21 @@ int main(int argc, char * argv[])
}
#ifdef VCMI_ANDROID
void CVCMIServer::create(const std::vector<std::string> & args)
extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_createServer(JNIEnv * env, jclass cls)
{
__android_log_write(ANDROID_LOG_INFO, "VCMI", "Got jni call to init server");
CAndroidVMHelper::cacheVM(env);
CVCMIServer::create();
}
void CVCMIServer::create()
{
const char * foo = "android-server";
std::vector<const void *> argv = {foo};
for(auto & a : args)
argv.push_back(a.c_str());
main(argv.size(), const_cast<char **>(foo));
main(argv.size(), reinterpret_cast<char **>(const_cast<void **>(&*argv.begin())));
}
#elif defined(SINGLE_PROCESS_APP)
void CVCMIServer::create(boost::condition_variable * cond, const std::vector<std::string> & args)
{

View File

@ -114,7 +114,7 @@ public:
ui8 getIdOfFirstUnallocatedPlayer() const;
#ifdef VCMI_ANDROID
static void create(const std::vector<std::string> & args);
static void create();
#elif defined(SINGLE_PROCESS_APP)
static void create(boost::condition_variable * cond, const std::vector<std::string> & args);
#endif

View File

@ -87,6 +87,11 @@ warning ()
warn_user=true
}
#checks whether specified directory exists. Also works with globs
dir_exists() {
[ -d "$1" ]
}
# check if selected options are correct.
if [[ -n "$data_dir" ]]
@ -177,7 +182,7 @@ then
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 ]]
if dir_exists "$data_dir"/app/[Dd][Aa][Tt][Aa]
then
data_dir="$data_dir"/app
fi