1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-08 00:39:47 +02:00

Merge with vcmi/develop

This commit is contained in:
Ivan Savenko 2022-12-25 23:10:52 +02:00
commit e4e5278f60
180 changed files with 10264 additions and 7084 deletions

View File

@ -5,6 +5,7 @@ on:
branches:
- features/*
- beta
- master
pull_request:
schedule:
- cron: '0 2 * * *'
@ -159,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 }}
@ -172,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 }}
@ -223,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'
@ -241,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/beta' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }}
with:
token: ${{ secrets.VCMI_ANDROID_ACCESS_TOKEN }}
repository: vcmi/vcmi-android

View File

@ -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()

View File

@ -4,4 +4,6 @@
// This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
// Here you can add specific libraries and macros which are specific to this project.
// Here you can add specific libraries and macros which are specific to this project.
VCMI_LIB_USING_NAMESPACE

View File

@ -127,7 +127,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
continue;
}
}
catch(cannotFulfillGoalException)
catch(cannotFulfillGoalException &)
{
if(!heroPtr.validAndSet())
{
@ -173,7 +173,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
blockedIndexes.insert(node.parentIndex);
}
catch(goalFulfilledException)
catch(goalFulfilledException &)
{
if(!heroPtr.validAndSet())
{

View File

@ -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

View File

@ -33,10 +33,6 @@ if(APPLE)
endif()
endif()
if(APPLE_IOS)
set(BUILD_SINGLE_APP 1)
endif()
############################################
# User-provided options #
############################################
@ -53,6 +49,8 @@ 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_TRANSLATIONS "Enable generation of translations for launcher and 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")
@ -65,6 +63,8 @@ endif(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON)
option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON)
option(ENABLE_SINGLE_APP_BUILD "Builds client and server as single executable" OFF)
option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON)
# Used for Snap packages and also useful for debugging
if(NOT APPLE_IOS)
@ -75,11 +75,20 @@ endif()
set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name")
set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
if(APPLE_IOS AND NOT ENABLE_SINGLE_APP_BUILD)
set(ENABLE_SINGLE_APP_BUILD ON)
endif()
# ERM depends on LUA implicitly
if(ENABLE_ERM AND NOT ENABLE_LUA)
set(ENABLE_LUA ON)
endif()
# We don't want to deploy assets into build directory for iOS build
if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
set(COPY_CONFIG_ON_BUILD OFF)
endif()
############################################
# Miscellaneous options #
############################################
@ -163,8 +172,8 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION})
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
if(BUILD_SINGLE_APP)
add_compile_definitions(SINGLE_PROCESS_APP=1)
if(ENABLE_SINGLE_APP_BUILD)
add_definitions(-DSINGLE_PROCESS_APP=1)
endif()
if(APPLE_IOS)
@ -244,13 +253,17 @@ endif(MINGW OR MSVC)
if(CMAKE_COMPILER_IS_GNUCXX OR NOT WIN32) #so far all *nix compilers support such parameters
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpointer-arith -Wuninitialized")
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0 OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmismatched-tags")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing -Wno-switch -Wno-sign-compare -Wno-unused-local-typedefs")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-overloaded-virtual -Wno-type-limits -Wno-unknown-pragmas")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-varargs") # fuzzylite - Operation.h
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-mismatched-tags -Wno-unknown-warning-option -Wno-missing-braces")
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option")
endif()
if(UNIX)
@ -271,7 +284,7 @@ if(NOT WIN32 AND NOT APPLE_IOS)
endif()
if(ENABLE_LUA)
add_compile_definitions(SCRIPTING_ENABLED=1)
add_definitions(-DSCRIPTING_ENABLED=1)
endif()
############################################
@ -313,7 +326,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).
@ -325,6 +337,10 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
endif()
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
@ -390,6 +406,12 @@ else()
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)
@ -401,14 +423,14 @@ else()
if(NOT DATA_DIR)
set(DATA_DIR "${CMAKE_INSTALL_DATAROOTDIR}/vcmi" CACHE STRING "Where to install data files")
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}")
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
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}")
endif()
# iOS has flat libs directory structure
@ -429,13 +451,10 @@ if(APPLE_IOS)
endif()
include(VCMI_lib)
if(BUILD_SINGLE_APP)
add_subdirectory(lib_client)
add_subdirectory(lib)
set(VCMI_LIB_TARGET vcmi)
if(ENABLE_SINGLE_APP_BUILD)
add_subdirectory(lib_server)
set(VCMI_LIB_TARGET vcmi_lib_client)
else()
add_subdirectory(lib)
set(VCMI_LIB_TARGET vcmi)
endif()
if(ENABLE_ERM)

View File

@ -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}"
}
},
{

View File

@ -15,18 +15,6 @@
// Fixed width bool data type is important for serialization
static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#ifdef __GNUC__
# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__)
#endif
#if !defined(__clang__) && defined(__GNUC__) && (GCC_VERSION < 470)
# error VCMI requires at least gcc-4.7.2 for successful compilation or clang-3.1. Please update your compiler
#endif
#if defined(__GNUC__) && (GCC_VERSION == 470 || GCC_VERSION == 471)
# error This GCC version has buggy std::array::at version and should not be used. Please update to 4.7.2 or later
#endif
/* ---------------------------------------------------------------------------- */
/* Suppress some compiler warnings */
/* ---------------------------------------------------------------------------- */
@ -122,10 +110,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
# define STRONG_INLINE inline
#endif
#define TO_STRING_HELPER(x) #x
#define TO_STRING(x) TO_STRING_HELPER(x)
#define LINE_IN_FILE __FILE__ ":" TO_STRING(__LINE__)
#define _USE_MATH_DEFINES
#include <cstdio>
@ -233,7 +217,10 @@ typedef boost::lock_guard<boost::recursive_mutex> TLockGuardRec;
/* ---------------------------------------------------------------------------- */
// Import + Export macro declarations
#ifdef VCMI_WINDOWS
# ifdef __GNUC__
#ifdef VCMI_DLL_STATIC
# define DLL_IMPORT
# define DLL_EXPORT
#elif defined(__GNUC__)
# define DLL_IMPORT __attribute__((dllimport))
# define DLL_EXPORT __attribute__((dllexport))
# else
@ -503,9 +490,6 @@ namespace vstd
ptr = nullptr;
}
#if _MSC_VER >= 1800
using std::make_unique;
#else
template<typename T>
std::unique_ptr<T> make_unique()
{
@ -531,7 +515,6 @@ namespace vstd
{
return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4)));
}
#endif
template <typename Container>
typename Container::const_reference circularAt(const Container &r, size_t index)
@ -543,6 +526,12 @@ namespace vstd
return *itr;
}
template <typename Container, typename Item>
void erase(Container &c, const Item &item)
{
c.erase(boost::remove(c, item), c.end());
}
template<typename Range, typename Predicate>
void erase_if(Range &vec, Predicate pred)
{
@ -704,12 +693,6 @@ namespace vstd
return false;
}
template <typename Container, typename Pred>
void erase(Container &c, Pred pred)
{
c.erase(boost::remove_if(c, pred), c.end());
}
template<typename T>
void removeDuplicates(std::vector<T> &vec)
{

View File

@ -0,0 +1,10 @@
{
"core:arrowTower" :
{
"graphics" :
{
"iconSmall" : "vcmi/creatureIcons/towerSmall",
"iconLarge" : "vcmi/creatureIcons/towerLarge",
}
}
}

View 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",
}
}
}
}

View File

@ -16,13 +16,20 @@
"modType" : "Графіка",
},
"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"}

View File

@ -1,7 +1,8 @@
[![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/github/vcmi/vcmi?branch=develop&svg=true)](https://ci.appveyor.com/project/vcmi/vcmi)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.0.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.0.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.1.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.1.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project
VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities.

View File

@ -161,7 +161,26 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
// 1) Vampire mansion in Necropolis (not 1st color is transparent)
// 2) Battle background when fighting on grass/dirt, topmost sky part (NO transparent color)
// 3) New objects that may use 24-bit images for icons (e.g. witchking arts)
if (ret->format->palette)
// 4) special case - there are 2 .bmp images that have semi-transparency (CCELLGRD.BMP & CCELLSHD.BMP)
if (ret->format->palette &&
ret->format->palette->colors[0].r == 255 &&
ret->format->palette->colors[0].g == 0 &&
ret->format->palette->colors[0].b == 255 )
{
static SDL_Color shadow[3] =
{
{ 0, 0, 0, 0},// 100% - transparency
{ 0, 0, 0, 32},// 75% - shadow border,
{ 0, 0, 0, 128},// 50% - shadow body
};
CSDL_Ext::setColorKey(ret, ret->format->palette->colors[0]);
ret->format->palette->colors[0] = shadow[0];
ret->format->palette->colors[1] = shadow[1];
ret->format->palette->colors[4] = shadow[2];
}
else if (ret->format->palette)
{
CSDL_Ext::setDefaultColorKeyPresize(ret);
}
@ -173,6 +192,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
{
CSDL_Ext::setDefaultColorKey(ret);
}
return ret;
}

View File

@ -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

View File

@ -2,12 +2,22 @@ set(client_SRCS
StdInc.cpp
../CCallback.cpp
battle/CBattleAnimations.cpp
battle/CBattleInterfaceClasses.cpp
battle/CBattleInterface.cpp
battle/CCreatureAnimation.cpp
battle/BattleActionsController.cpp
battle/BattleAnimationClasses.cpp
battle/BattleControlPanel.cpp
battle/BattleEffectsController.cpp
battle/BattleFieldController.cpp
battle/BattleInterfaceClasses.cpp
battle/BattleInterface.cpp
battle/BattleObstacleController.cpp
battle/BattleProjectileController.cpp
battle/BattleRenderer.cpp
battle/BattleSiegeController.cpp
battle/BattleStacksController.cpp
battle/CreatureAnimation.cpp
gui/CAnimation.cpp
gui/Canvas.cpp
gui/CCursorHandler.cpp
gui/CGuiHandler.cpp
gui/CIntObject.cpp
@ -15,6 +25,7 @@ set(client_SRCS
gui/Geometries.cpp
gui/SDL_Extensions.cpp
gui/NotificationHandler.cpp
gui/InterfaceObjectConfigurable.cpp
widgets/AdventureMapClasses.cpp
widgets/Buttons.cpp
@ -75,12 +86,22 @@ set(client_SRCS
set(client_HEADERS
StdInc.h
battle/CBattleAnimations.h
battle/CBattleInterfaceClasses.h
battle/CBattleInterface.h
battle/CCreatureAnimation.h
battle/BattleActionsController.h
battle/BattleAnimationClasses.h
battle/BattleControlPanel.h
battle/BattleEffectsController.h
battle/BattleFieldController.h
battle/BattleInterfaceClasses.h
battle/BattleInterface.h
battle/BattleObstacleController.h
battle/BattleProjectileController.h
battle/BattleRenderer.h
battle/BattleSiegeController.h
battle/BattleStacksController.h
battle/CreatureAnimation.h
gui/CAnimation.h
gui/Canvas.h
gui/CCursorHandler.h
gui/CGuiHandler.h
gui/CIntObject.h
@ -90,6 +111,7 @@ set(client_HEADERS
gui/SDL_Extensions.h
gui/SDL_Pixels.h
gui/NotificationHandler.h
gui/InterfaceObjectConfigurable.h
widgets/AdventureMapClasses.h
widgets/Buttons.h
@ -177,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)
@ -243,7 +268,7 @@ elseif(APPLE_IOS)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
endif()
if(BUILD_SINGLE_APP)
if(ENABLE_SINGLE_APP_BUILD)
target_link_libraries(vcmiclient PRIVATE vcmiserver)
if(ENABLE_LAUNCHER)
target_link_libraries(vcmiclient PRIVATE vcmilauncher)

View File

@ -12,8 +12,11 @@
#include <vcmi/Artifact.h>
#include "windows/CAdvmapInterface.h"
#include "battle/CBattleInterface.h"
#include "battle/CBattleInterfaceClasses.h"
#include "battle/BattleInterface.h"
#include "battle/BattleEffectsController.h"
#include "battle/BattleFieldController.h"
#include "battle/BattleInterfaceClasses.h"
#include "battle/BattleControlPanel.h"
#include "../CCallback.h"
#include "windows/CCastleInterface.h"
#include "gui/CCursorHandler.h"
@ -29,7 +32,6 @@
#include "windows/CTradeWindow.h"
#include "windows/CSpellWindow.h"
#include "../lib/CConfigHandler.h"
#include "battle/CCreatureAnimation.h"
#include "Graphics.h"
#include "windows/GUIClasses.h"
#include "../lib/CArtHandler.h"
@ -55,6 +57,7 @@
#include "../lib/CPlayerState.h"
#include "../lib/GameConstants.h"
#include "gui/CGuiHandler.h"
#include "gui/CAnimation.h"
#include "windows/InfoWindows.h"
#include "../lib/UnlockGuard.h"
#include "../lib/CPathfinder.h"
@ -90,7 +93,7 @@ boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
CPlayerInterface * LOCPLINT;
CBattleInterface * CPlayerInterface::battleInt;
BattleInterface * CPlayerInterface::battleInt;
enum EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
@ -715,34 +718,14 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
{
case UnitChanges::EOperation::RESET_STATE:
{
const battle::Unit * unit = cb->battleGetUnitByID(info.id);
const CStack * stack = cb->battleGetStackByID(info.id );
if(!unit)
if(!stack)
{
logGlobal->error("Invalid unit ID %d", info.id);
continue;
}
auto iter = battleInt->creAnims.find(info.id);
if(iter == battleInt->creAnims.end())
{
logGlobal->error("Unit %d have no animation", info.id);
continue;
}
auto animation = iter->second;
if(unit->alive() && animation->isDead())
animation->setType(CCreatureAnim::HOLDING);
if (unit->isClone())
{
std::unique_ptr<ColorShifterDeepBlue> shifter(new ColorShifterDeepBlue());
animation->shiftColor(shifter.get());
}
//TODO: handle more cases
battleInt->stackReset(stack);
}
break;
case UnitChanges::EOperation::REMOVE:
@ -756,7 +739,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
logGlobal->error("Invalid unit ID %d", info.id);
continue;
}
battleInt->unitAdded(unit);
battleInt->stackAdded(unit);
}
break;
default:
@ -765,7 +748,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
}
}
battleInt->displayCustomEffects(customEffects);
battleInt->effectsController->displayCustomEffects(customEffects);
}
void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
@ -773,7 +756,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
bool needUpdate = false;
std::vector<std::shared_ptr<const CObstacleInstance>> newObstacles;
for(auto & change : obstacles)
{
@ -781,19 +764,16 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
{
auto instance = cb->battleGetObstacleByID(change.id);
if(instance)
battleInt->obstaclePlaced(*instance);
newObstacles.push_back(instance);
else
logNetwork->error("Invalid obstacle instance %d", change.id);
}
else
{
needUpdate = true;
}
}
if(needUpdate)
//update accessible hexes
battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
if (!newObstacles.empty())
battleInt->obstaclePlaced(newObstacles);
battleInt->fieldController->redrawBackgroundWithHexes();
}
void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
@ -858,17 +838,17 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
autofightingAI.reset();
}
CBattleInterface *b = battleInt;
BattleInterface *b = battleInt;
if(!b)
{
return BattleAction::makeDefend(stack); // probably battle is finished already
}
if(CBattleInterface::givenCommand.get())
if(BattleInterface::givenCommand.get())
{
logGlobal->error("Command buffer must be clean! (we don't want to use old command)");
vstd::clear_pointer(CBattleInterface::givenCommand.data);
vstd::clear_pointer(BattleInterface::givenCommand.data);
}
{
@ -877,17 +857,17 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
//Regeneration & mana drain go there
}
//wait till BattleInterface sets its command
boost::unique_lock<boost::mutex> lock(CBattleInterface::givenCommand.mx);
while(!CBattleInterface::givenCommand.data)
boost::unique_lock<boost::mutex> lock(BattleInterface::givenCommand.mx);
while(!BattleInterface::givenCommand.data)
{
CBattleInterface::givenCommand.cond.wait(lock);
BattleInterface::givenCommand.cond.wait(lock);
if (!battleInt) //battle ended while we were waiting for movement (eg. because of spell)
throw boost::thread_interrupted(); //will shut the thread peacefully
}
//tidy up
BattleAction ret = *(CBattleInterface::givenCommand.data);
vstd::clear_pointer(CBattleInterface::givenCommand.data);
BattleAction ret = *(BattleInterface::givenCommand.data);
vstd::clear_pointer(BattleInterface::givenCommand.data);
if(ret.actionType == EActionType::CANCEL)
{
@ -912,7 +892,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
if(!battleInt)
{
GH.pushIntT<CBattleResultWindow>(*br, *this);
GH.pushIntT<BattleResultWindow>(*br, *this);
// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
// Otherwise NewTurn causes freeze.
waitWhileDialog();
@ -962,7 +942,7 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
//TODO why is this different (no return on LOPLINT != this) ?
RETURN_IF_QUICK_COMBAT;
battleInt->battleTriggerEffect(bte);
battleInt->effectsController->battleTriggerEffect(bte);
}
void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
{
@ -977,7 +957,7 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
if(elem.isEffect())
{
if(defender && !elem.isSecondary())
battleInt->displayEffect(elem.effect, defender->getPosition());
battleInt->effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), defender->getPosition());
}
if(elem.isSpell())
{
@ -1012,28 +992,26 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
if(ba->lucky()) //lucky hit
{
battleInt->console->addText(attacker->formatGeneralMessage(-45));
battleInt->displayEffect(18, attacker->getPosition());
CCS->soundh->playSound(soundBase::GOODLUCK);
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-45));
battleInt->effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, attacker->getPosition());
}
if(ba->unlucky()) //unlucky hit
{
battleInt->console->addText(attacker->formatGeneralMessage(-44));
battleInt->displayEffect(48, attacker->getPosition());
CCS->soundh->playSound(soundBase::BADLUCK);
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-44));
battleInt->effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, attacker->getPosition());
}
if(ba->deathBlow())
{
battleInt->console->addText(attacker->formatGeneralMessage(365));
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(365));
for(auto & elem : ba->bsa)
{
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->displayEffect(73, attacked->getPosition());
battleInt->effectsController->displayEffect(EBattleEffect::DEATH_BLOW, attacked->getPosition());
}
CCS->soundh->playSound(soundBase::deathBlow);
}
battleInt->displayCustomEffects(ba->customEffects);
battleInt->effectsController->displayCustomEffects(ba->customEffects);
battleInt->waitForAnims();

View File

@ -40,9 +40,8 @@ class CButton;
class CToggleGroup;
class CAdvMapInt;
class CCastleInterface;
class CBattleInterface;
class BattleInterface;
class CComponent;
class CCreatureAnimation;
class CSelectableComponent;
class CSlider;
class CInGameConsole;
@ -85,7 +84,7 @@ public:
static const int SAVES_COUNT = 5;
CCastleInterface * castleInt; //nullptr if castle window isn't opened
static CBattleInterface * battleInt; //nullptr if no battle
static BattleInterface * battleInt; //nullptr if no battle
CInGameConsole * cingconsole;
std::shared_ptr<CCallback> cb; //to communicate with engine

View File

@ -25,12 +25,15 @@
#include "../lib/CAndroidVMHelper.h"
#elif defined(VCMI_IOS)
#include "ios/utils.h"
#include "../server/CVCMIServer.h"
#include <dispatch/dispatch.h>
#else
#include "../lib/Interprocess.h"
#endif
#ifdef SINGLE_PROCESS_APP
#include "../server/CVCMIServer.h"
#endif
#include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CThreadHelper.h"
@ -142,7 +145,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
else
myNames.push_back(settings["general"]["playerName"].String());
#if !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
shm.reset();
if(!settings["session"]["disable-shm"].Bool())

View File

@ -30,8 +30,6 @@ template<typename T> class CApplier;
VCMI_LIB_NAMESPACE_END
struct SharedMemory;
class CClient;
class CBaseForLobbyApply;

View File

@ -42,7 +42,7 @@
#include "mainmenu/CMainMenu.h"
#include "mainmenu/CCampaignScreen.h"
#include "lobby/CBonusSelection.h"
#include "battle/CBattleInterface.h"
#include "battle/BattleInterface.h"
#include "../lib/CThreadHelper.h"
#include "../lib/registerTypes/RegisterTypes.h"
#include "gui/CGuiHandler.h"
@ -598,7 +598,7 @@ void CClient::battleStarted(const BattleInfo * info)
if(!!att || !!def)
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
GH.pushIntT<CBattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def);
GH.pushIntT<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def);
}
else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
{
@ -606,7 +606,7 @@ void CClient::battleStarted(const BattleInfo * info)
auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
spectratorInt->cb->setBattle(info);
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
GH.pushIntT<CBattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def, spectratorInt);
GH.pushIntT<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def, spectratorInt);
}
}

View File

@ -20,7 +20,7 @@ CreatureCostBox::CreatureCostBox(Rect position, std::string titleText)
type |= REDRAW_PARENT;
pos = position + pos;
title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, CENTER, Colors::WHITE, titleText);
title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, titleText);
}
void CreatureCostBox::set(TResources res)
@ -39,7 +39,7 @@ void CreatureCostBox::createItems(TResources res)
while(iter.valid())
{
ImagePtr image = std::make_shared<CAnimImage>("RESOURCE", iter->resType);
LabelPtr text = std::make_shared<CLabel>(15, 43, FONT_SMALL, CENTER, Colors::WHITE, "0");
LabelPtr text = std::make_shared<CLabel>(15, 43, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "0");
resources.insert(std::make_pair(iter->resType, std::make_pair(text, image)));
iter++;

View File

@ -29,7 +29,6 @@
#include "../lib/VCMI_Lib.h"
#include "../CCallback.h"
#include "../lib/CGeneralTextHandler.h"
#include "CBitmapHandler.h"
#include "../lib/CGameState.h"
#include "../lib/JsonNode.h"
#include "../lib/vcmi_endian.h"

View File

@ -30,7 +30,7 @@ struct SDL_Surface;
struct SDL_Color;
class CAnimation;
enum EFonts
enum EFonts : int
{
FONT_BIG, FONT_CALLI, FONT_CREDITS, FONT_HIGH_SCORE, FONT_MEDIUM, FONT_SMALL, FONT_TIMES, FONT_TINY, FONT_VERD
};

View File

@ -30,7 +30,7 @@
#include "windows/GUIClasses.h"
#include "../lib/CConfigHandler.h"
#include "gui/SDL_Extensions.h"
#include "battle/CBattleInterface.h"
#include "battle/BattleInterface.h"
#include "../lib/mapping/CCampaignHandler.h"
#include "../lib/CGameState.h"
#include "../lib/CStack.h"

View File

@ -0,0 +1,775 @@
/*
* BattleActionsController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleActionsController.h"
#include "BattleControlPanel.h"
#include "BattleStacksController.h"
#include "BattleInterface.h"
#include "BattleFieldController.h"
#include "BattleSiegeController.h"
#include "BattleInterfaceClasses.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CIntObject.h"
#include "../windows/CCreatureWindow.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
#include "../../lib/battle/BattleAction.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/spells/ISpellMechanics.h"
#include "../../lib/spells/Problem.h"
#include "../../lib/CGeneralTextHandler.h"
static std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
{
if (dmgRange.first != dmgRange.second)
return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
else
return (boost::format("%d") % dmgRange.first).str();
}
BattleActionsController::BattleActionsController(BattleInterface & owner):
owner(owner),
creatureCasting(false),
spellDestSelectMode(false),
spellToCast(nullptr),
currentSpell(nullptr)
{
currentAction = PossiblePlayerBattleAction::INVALID;
selectedAction = PossiblePlayerBattleAction::INVALID;
}
void BattleActionsController::endCastingSpell()
{
if(spellDestSelectMode)
{
spellToCast.reset();
currentSpell = nullptr;
spellDestSelectMode = false;
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
if(owner.stacksController->getActiveStack())
{
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
owner.myTurn = true;
}
}
else
{
if(owner.stacksController->getActiveStack())
{
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
GH.fakeMouseMove();
}
}
}
void BattleActionsController::enterCreatureCastingMode()
{
//silently check for possible errors
if (!owner.myTurn)
return;
if (owner.tacticsMode)
return;
//hero is casting a spell
if (spellDestSelectMode)
return;
if (!owner.stacksController->getActiveStack())
return;
if (!owner.stacksController->activeStackSpellcaster())
return;
//random spellcaster
if (owner.stacksController->activeStackSpellToCast() == SpellID::NONE)
return;
if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
{
const spells::Caster * caster = owner.stacksController->getActiveStack();
const CSpell * spell = owner.stacksController->activeStackSpellToCast().toSpell();
spells::Target target;
target.emplace_back();
spells::BattleCast cast(owner.curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
auto m = spell->battleMechanics(&cast);
spells::detail::ProblemImpl ignored;
const bool isCastingPossible = m->canBeCastAt(target, ignored);
if (isCastingPossible)
{
owner.myTurn = false;
owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner.stacksController->activeStackSpellToCast());
owner.stacksController->setSelectedStack(nullptr);
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
}
}
else
{
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
{
return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
(x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
(x != PossiblePlayerBattleAction::OBSTACLE);
};
vstd::erase_if(possibleActions, actionFilterPredicate);
GH.fakeMouseMove();
}
}
std::vector<PossiblePlayerBattleAction> BattleActionsController::getPossibleActionsForStack(const CStack *stack) const
{
BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
data.creatureSpellToCast = owner.stacksController->activeStackSpellToCast();
data.tacticsMode = owner.tacticsMode;
auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data);
return std::vector<PossiblePlayerBattleAction>(allActions);
}
void BattleActionsController::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
{
if(owner.tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
{
switch(item)
{
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
case PossiblePlayerBattleAction::ANY_LOCATION:
case PossiblePlayerBattleAction::NO_LOCATION:
case PossiblePlayerBattleAction::FREE_LOCATION:
case PossiblePlayerBattleAction::OBSTACLE:
if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
return 1;
else
return 100;//bottom priority
break;
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
return 2; break;
case PossiblePlayerBattleAction::RISE_DEMONS:
return 3; break;
case PossiblePlayerBattleAction::SHOOT:
return 4; break;
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
return 5; break;
case PossiblePlayerBattleAction::ATTACK:
return 6; break;
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
return 7; break;
case PossiblePlayerBattleAction::MOVE_STACK:
return 8; break;
case PossiblePlayerBattleAction::CATAPULT:
return 9; break;
case PossiblePlayerBattleAction::HEAL:
return 10; break;
default:
return 200; break;
}
};
auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
{
return assignPriority(lhs) > assignPriority(rhs);
};
std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
}
void BattleActionsController::castThisSpell(SpellID spellID)
{
spellToCast = std::make_shared<BattleAction>();
spellToCast->actionType = EActionType::HERO_SPELL;
spellToCast->actionSubtype = spellID; //spell number
spellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
spellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
spellDestSelectMode = true;
creatureCasting = false;
//choosing possible targets
const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance;
assert(castingHero); // code below assumes non-null hero
currentSpell = spellID.toSpell();
PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(currentSpell, castingHero, spells::Mode::HERO);
if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
{
spellToCast->aimToHex(BattleHex::INVALID);
owner.curInt->cb->battleMakeAction(spellToCast.get());
endCastingSpell();
}
else
{
possibleActions.clear();
possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
GH.fakeMouseMove();//update cursor
}
}
void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
{
if (!owner.myTurn || !owner.battleActionsStarted) //we are not permit to do anything
return;
// This function handles mouse move over hexes and l-clicking on them.
// First we decide what happens if player clicks on this hex and set appropriately
// consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
//
// Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
std::string newConsoleMsg;
//used when hovering -> tooltip message and cursor to be set
bool setCursor = true; //if we want to suppress setting cursor
ECursor::ECursorTypes cursorType = ECursor::COMBAT;
int cursorFrame = ECursor::COMBAT_POINTER; //TODO: is this line used?
//used when l-clicking -> action to be called upon the click
std::function<void()> realizeAction;
//Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
const CStack * shere = owner.curInt->cb->battleGetStackByPos(myNumber, true);
if(!shere)
shere = owner.curInt->cb->battleGetStackByPos(myNumber, false);
if(!owner.stacksController->getActiveStack())
return;
bool ourStack = false;
if (shere)
ourStack = shere->owner == owner.curInt->playerID;
//stack may have changed, update selection border
owner.stacksController->setHoveredStack(shere);
localActions.clear();
illegalActions.clear();
reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
const bool forcedAction = possibleActions.size() == 1;
for (PossiblePlayerBattleAction action : possibleActions)
{
bool legalAction = false; //this action is legal and can be performed
bool notLegal = false; //this action is not legal and should display message
switch (action)
{
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
if (shere && ourStack)
legalAction = true;
break;
case PossiblePlayerBattleAction::MOVE_TACTICS:
case PossiblePlayerBattleAction::MOVE_STACK:
{
if (!(shere && shere->alive())) //we can walk on dead stacks
{
if(canStackMoveHere(owner.stacksController->getActiveStack(), myNumber))
legalAction = true;
}
break;
}
case PossiblePlayerBattleAction::ATTACK:
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
{
if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), shere, myNumber))
{
if (owner.fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
{
owner.fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
legalAction = true;
}
}
}
break;
case PossiblePlayerBattleAction::SHOOT:
if(owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), myNumber))
legalAction = true;
break;
case PossiblePlayerBattleAction::ANY_LOCATION:
if (myNumber > -1) //TODO: this should be checked for all actions
{
if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
legalAction = true;
}
break;
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
if(shere && isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
legalAction = true;
break;
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
{
if(shere && ourStack && shere != owner.stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
{
int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
if(spellID > -1)
{
legalAction = true;
}
}
}
break;
case PossiblePlayerBattleAction::OBSTACLE:
if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
legalAction = true;
break;
case PossiblePlayerBattleAction::TELEPORT:
{
//todo: move to mechanics
ui8 skill = 0;
if (creatureCasting)
skill = owner.stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
else
skill = owner.getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
//TODO: explicitely save power, skill
if (owner.curInt->cb->battleCanTeleportTo(owner.stacksController->getSelectedStack(), myNumber, skill))
legalAction = true;
else
notLegal = true;
}
break;
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
if (shere && shere != owner.stacksController->getSelectedStack() && ourStack && shere->alive())
legalAction = true;
else
notLegal = true;
break;
case PossiblePlayerBattleAction::FREE_LOCATION:
legalAction = true;
if(!isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
{
legalAction = false;
notLegal = true;
}
break;
case PossiblePlayerBattleAction::CATAPULT:
if (owner.siegeController && owner.siegeController->isAttackableByCatapult(myNumber))
legalAction = true;
break;
case PossiblePlayerBattleAction::HEAL:
if (shere && ourStack && shere->canBeHealed())
legalAction = true;
break;
case PossiblePlayerBattleAction::RISE_DEMONS:
if (shere && ourStack && !shere->alive())
{
if (!(shere->hasBonusOfType(Bonus::UNDEAD)
|| shere->hasBonusOfType(Bonus::NON_LIVING)
|| shere->hasBonusOfType(Bonus::GARGOYLE)
|| shere->summoned
|| shere->isClone()
|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
))
legalAction = true;
}
break;
}
if (legalAction)
localActions.push_back (action);
else if (notLegal || forcedAction)
illegalActions.push_back (action);
}
illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
currentAction = selectedAction;
else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
currentAction = localActions.front();
else //no legal action possible
{
currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
if (vstd::contains(illegalActions, selectedAction))
illegalAction = selectedAction;
else if (illegalActions.size())
illegalAction = illegalActions.front();
else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
{
currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
}
else
illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
}
bool isCastingPossible = false;
bool secondaryTarget = false;
if (currentAction > PossiblePlayerBattleAction::INVALID)
{
switch (currentAction) //display console message, realize selected action
{
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
realizeAction = [=](){ owner.stackActivated(shere); };
break;
case PossiblePlayerBattleAction::MOVE_TACTICS:
case PossiblePlayerBattleAction::MOVE_STACK:
if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
{
cursorFrame = ECursor::COMBAT_FLY;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
}
else
{
cursorFrame = ECursor::COMBAT_MOVE;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
}
realizeAction = [=]()
{
if(owner.stacksController->getActiveStack()->doubleWide())
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
BattleHex shiftedDest = myNumber.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
if(vstd::contains(acc, myNumber))
owner.giveCommand(EActionType::WALK, myNumber);
else if(vstd::contains(acc, shiftedDest))
owner.giveCommand(EActionType::WALK, shiftedDest);
}
else
{
owner.giveCommand(EActionType::WALK, myNumber);
}
};
break;
case PossiblePlayerBattleAction::ATTACK:
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
{
owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
realizeAction = [=]()
{
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
{
auto command = new BattleAction(BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
owner.sendCommand(command, owner.stacksController->getActiveStack());
}
};
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
}
break;
case PossiblePlayerBattleAction::SHOOT:
{
if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), myNumber))
cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
else
cursorFrame = ECursor::COMBAT_SHOOT;
realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);};
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
//printing - Shoot %s (%d shots left, %s damage)
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner.stacksController->getActiveStack()->shots.available() % estDmgText).str();
}
break;
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->name % shere->getName()); //Cast %s on %s
switch (currentSpell->id)
{
case SpellID::SACRIFICE:
case SpellID::TELEPORT:
owner.stacksController->setSelectedStack(shere); //remember first target
secondaryTarget = true;
break;
}
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::ANY_LOCATION:
currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
currentSpell = nullptr;
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
creatureCasting = true;
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::TELEPORT:
newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
cursorFrame = ECursor::COMBAT_TELEPORT;
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::OBSTACLE:
newConsoleMsg = CGI->generaltexth->allTexts[550];
//TODO: remove obstacle cursor
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::SACRIFICE:
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
cursorFrame = ECursor::COMBAT_SACRIFICE;
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::FREE_LOCATION:
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::HEAL:
cursorFrame = ECursor::COMBAT_HEAL;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
break;
case PossiblePlayerBattleAction::RISE_DEMONS:
cursorType = ECursor::SPELLBOOK;
realizeAction = [=]()
{
owner.giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
};
break;
case PossiblePlayerBattleAction::CATAPULT:
cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };
break;
case PossiblePlayerBattleAction::CREATURE_INFO:
{
cursorFrame = ECursor::COMBAT_QUERY;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
break;
}
}
}
else //no possible valid action, display message
{
switch (illegalAction)
{
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
cursorFrame = ECursor::COMBAT_BLOCKED;
newConsoleMsg = CGI->generaltexth->allTexts[23];
break;
case PossiblePlayerBattleAction::TELEPORT:
cursorFrame = ECursor::COMBAT_BLOCKED;
newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
break;
case PossiblePlayerBattleAction::SACRIFICE:
newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
break;
case PossiblePlayerBattleAction::FREE_LOCATION:
cursorFrame = ECursor::COMBAT_BLOCKED;
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here
break;
default:
if (myNumber == -1)
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); //set neutral cursor over menu etc.
else
cursorFrame = ECursor::COMBAT_BLOCKED;
break;
}
}
if (isCastingPossible) //common part
{
switch (currentAction) //don't use that with teleport / sacrifice
{
case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
case PossiblePlayerBattleAction::SACRIFICE:
break;
default:
cursorType = ECursor::SPELLBOOK;
cursorFrame = 0;
if (newConsoleMsg.empty() && currentSpell)
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
break;
}
realizeAction = [=]()
{
if(secondaryTarget) //select that target now
{
possibleActions.clear();
switch (currentSpell->id.toEnum())
{
case SpellID::TELEPORT: //don't cast spell yet, only select target
spellToCast->aimToUnit(shere);
possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
break;
case SpellID::SACRIFICE:
spellToCast->aimToHex(myNumber);
possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
break;
}
}
else
{
if (creatureCasting)
{
if (currentSpell)
{
owner.giveCommand(EActionType::MONSTER_SPELL, myNumber, owner.stacksController->activeStackSpellToCast());
}
else //unknown random spell
{
owner.giveCommand(EActionType::MONSTER_SPELL, myNumber);
}
}
else
{
assert(currentSpell);
switch (currentSpell->id.toEnum())
{
case SpellID::SACRIFICE:
spellToCast->aimToUnit(shere);//victim
break;
default:
spellToCast->aimToHex(myNumber);
break;
}
owner.curInt->cb->battleMakeAction(spellToCast.get());
endCastingSpell();
}
owner.stacksController->setSelectedStack(nullptr);
}
};
}
{
if (eventType == CIntObject::MOVE)
{
if (setCursor)
CCS->curh->changeGraphic(cursorType, cursorFrame);
if (!currentConsoleMsg.empty())
owner.controlPanel->console->clearIfMatching(currentConsoleMsg);
if (!newConsoleMsg.empty())
owner.controlPanel->console->write(newConsoleMsg);
currentConsoleMsg = newConsoleMsg;
}
if (eventType == CIntObject::LCLICK && realizeAction)
{
//opening creature window shouldn't affect myTurn...
if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
{
owner.myTurn = false; //tends to crash with empty calls
}
realizeAction();
if (!secondaryTarget) //do not replace teleport or sacrifice cursor
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
owner.controlPanel->console->clear();
}
}
}
bool BattleActionsController::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
{
creatureCasting = owner.stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
bool isCastingPossible = true;
int spellID = -1;
if (creatureCasting)
{
if (owner.stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
spellID = owner.stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
}
else //hero casting
{
spellID = spellToCast->actionSubtype;
}
currentSpell = nullptr;
if (spellID >= 0)
currentSpell = CGI->spellh->objects[spellID];
if (currentSpell)
{
const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(owner.curInt->cb->battleGetMyHero());
if (caster == nullptr)
{
isCastingPossible = false;//just in case
}
else
{
const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
spells::Target target;
target.emplace_back(myNumber);
spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
auto m = currentSpell->battleMechanics(&cast);
spells::detail::ProblemImpl problem; //todo: display problem in status bar
isCastingPossible = m->canBeCastAt(target, problem);
}
}
else
isCastingPossible = false;
if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
isCastingPossible = false;
return isCastingPossible;
}
bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove);
BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
if (vstd::contains(acc, myNumber))
return true;
else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
return true;
else
return false;
}
void BattleActionsController::activateStack()
{
const CStack * s = owner.stacksController->getActiveStack();
if(s)
possibleActions = getPossibleActionsForStack(s);
}
bool BattleActionsController::spellcastingModeActive() const
{
return spellDestSelectMode;
}
SpellID BattleActionsController::selectedSpell() const
{
if (!spellToCast)
return SpellID::NONE;
return SpellID(spellToCast->actionSubtype);
}

View File

@ -0,0 +1,96 @@
/*
* BattleActionsController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/CBattleInfoCallback.h"
VCMI_LIB_NAMESPACE_BEGIN
class BattleAction;
VCMI_LIB_NAMESPACE_END
class BattleInterface;
enum class MouseHoveredHexContext
{
UNOCCUPIED_HEX,
OCCUPIED_HEX
};
/// Class that controls actions that can be performed by player, e.g. moving stacks, attacking, etc
/// As well as all relevant feedback for these actions in user interface
class BattleActionsController
{
BattleInterface & owner;
/// all actions possible to call at the moment by player
std::vector<PossiblePlayerBattleAction> possibleActions;
/// actions possible to take on hovered hex
std::vector<PossiblePlayerBattleAction> localActions;
/// these actions display message in case of illegal target
std::vector<PossiblePlayerBattleAction> illegalActions;
/// action that will be performed on l-click
PossiblePlayerBattleAction currentAction;
/// last action chosen (and saved) by player
PossiblePlayerBattleAction selectedAction;
/// if there are not possible actions to choose from, this action should be show as "illegal" in UI
PossiblePlayerBattleAction illegalAction;
/// if true, stack currently aims to cats a spell
bool creatureCasting;
/// if true, player is choosing destination for his spell - only for GUI / console
bool spellDestSelectMode;
/// spell for which player is choosing destination
std::shared_ptr<BattleAction> spellToCast;
/// spell for which player is choosing destination, pointer for convenience
const CSpell *currentSpell;
/// cached message that was set by this class in status bar
std::string currentConsoleMsg;
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
public:
BattleActionsController(BattleInterface & owner);
/// initialize list of potential actions for new active stack
void activateStack();
/// initialize potential actions for spells that can be cast by active stack
void enterCreatureCastingMode();
/// initialize potential actions for selected spell
void castThisSpell(SpellID spellID);
/// ends casting spell (eg. when spell has been cast or canceled)
void endCastingSpell();
/// update UI (e.g. status bar/cursor) according to new active hex
void handleHex(BattleHex myNumber, int eventType);
/// returns currently selected spell or SpellID::NONE on error
SpellID selectedSpell() const;
/// returns true if UI is currently in target selection mode
bool spellcastingModeActive() const;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,350 @@
/*
* BattleAnimations.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/BattleHex.h"
#include "../../lib/CSoundBase.h"
#include "../widgets/Images.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
VCMI_LIB_NAMESPACE_END
class BattleInterface;
class CreatureAnimation;
class CBattleAnimation;
struct CatapultProjectileInfo;
struct StackAttackedInfo;
/// Base class of battle animations
class CBattleAnimation
{
protected:
BattleInterface & owner;
bool initialized;
std::vector<CBattleAnimation *> & pendingAnimations();
std::shared_ptr<CreatureAnimation> stackAnimation(const CStack * stack) const;
bool stackFacingRight(const CStack * stack);
void setStackFacingRight(const CStack * stack, bool facingRight);
virtual bool init() = 0; //to be called - if returned false, call again until returns true
bool checkInitialConditions(); //determines if this animation is earliest of all
public:
ui32 ID; //unique identifier
bool isInitialized();
bool tryInitialize();
virtual void nextFrame() {} //call every new frame
virtual ~CBattleAnimation();
CBattleAnimation(BattleInterface & owner);
};
/// Sub-class which is responsible for managing the battle stack animation.
class CBattleStackAnimation : public CBattleAnimation
{
public:
std::shared_ptr<CreatureAnimation> myAnim; //animation for our stack, managed by CBattleInterface
const CStack * stack; //id of stack whose animation it is
CBattleStackAnimation(BattleInterface & owner, const CStack * _stack);
void shiftColor(const ColorShifter * shifter);
void rotateStack(BattleHex hex);
};
/// This class is responsible for managing the battle attack animation
class CAttackAnimation : public CBattleStackAnimation
{
bool soundPlayed;
protected:
BattleHex dest; //attacked hex
bool shooting;
CCreatureAnim::EAnimType group; //if shooting is true, print this animation group
const CStack *attackedStack;
const CStack *attackingStack;
int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
const CCreature * getCreature() const;
public:
void nextFrame() override;
bool checkInitialConditions();
CAttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
~CAttackAnimation();
};
/// Animation of a defending unit
class CDefenceAnimation : public CBattleStackAnimation
{
CCreatureAnim::EAnimType getMyAnimType();
std::string getMySound();
void startAnimation();
const CStack * attacker; //attacking stack
bool rangedAttack; //if true, stack has been attacked by shooting
bool killed; //if true, stack has been killed
float timeToWait; // for how long this animation should be paused
public:
bool init() override;
void nextFrame() override;
CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInterface & owner);
~CDefenceAnimation();
};
class CDummyAnimation : public CBattleAnimation
{
private:
int counter;
int howMany;
public:
bool init() override;
void nextFrame() override;
CDummyAnimation(BattleInterface & owner, int howManyFrames);
};
/// Hand-to-hand attack
class CMeleeAttackAnimation : public CAttackAnimation
{
public:
bool init() override;
CMeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked);
};
/// Base class for all animations that play during stack movement
class CStackMoveAnimation : public CBattleStackAnimation
{
public:
BattleHex currentHex;
protected:
CStackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex _currentHex);
};
/// Move animation of a creature
class CMovementAnimation : public CStackMoveAnimation
{
private:
std::vector<BattleHex> destTiles; //full path, includes already passed hexes
ui32 curentMoveIndex; // index of nextHex in destTiles
BattleHex oldPos; //position of stack before move
double begX, begY; // starting position
double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
double timeToMove; // full length of movement animation
double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
public:
bool init() override;
void nextFrame() override;
CMovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
~CMovementAnimation();
};
/// Move end animation of a creature
class CMovementEndAnimation : public CStackMoveAnimation
{
public:
bool init() override;
CMovementEndAnimation(BattleInterface & owner, const CStack * _stack, BattleHex destTile);
~CMovementEndAnimation();
};
/// Move start animation of a creature
class CMovementStartAnimation : public CStackMoveAnimation
{
public:
bool init() override;
CMovementStartAnimation(BattleInterface & owner, const CStack * _stack);
};
/// Class responsible for animation of stack chaning direction (left <-> right)
class CReverseAnimation : public CStackMoveAnimation
{
public:
bool priority; //true - high, false - low
bool init() override;
void setupSecondPart();
CReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest, bool _priority);
~CReverseAnimation();
};
class CRangedAttackAnimation : public CAttackAnimation
{
void setAnimationGroup();
void initializeProjectile();
void emitProjectile();
void emitExplosion();
protected:
bool projectileEmitted;
virtual CCreatureAnim::EAnimType getUpwardsGroup() const = 0;
virtual CCreatureAnim::EAnimType getForwardGroup() const = 0;
virtual CCreatureAnim::EAnimType getDownwardsGroup() const = 0;
virtual void createProjectile(const Point & from, const Point & dest) const = 0;
virtual uint32_t getAttackClimaxFrame() const = 0;
public:
CRangedAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
~CRangedAttackAnimation();
bool init() override;
void nextFrame() override;
};
/// Shooting attack
class CShootingAnimation : public CRangedAttackAnimation
{
CCreatureAnim::EAnimType getUpwardsGroup() const override;
CCreatureAnim::EAnimType getForwardGroup() const override;
CCreatureAnim::EAnimType getDownwardsGroup() const override;
void createProjectile(const Point & from, const Point & dest) const override;
uint32_t getAttackClimaxFrame() const override;
public:
CShootingAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
};
/// Catapult attack
class CCatapultAnimation : public CShootingAnimation
{
private:
bool explosionEmitted;
int catapultDamage;
public:
CCatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);
void createProjectile(const Point & from, const Point & dest) const override;
void nextFrame() override;
};
class CCastAnimation : public CRangedAttackAnimation
{
const CSpell * spell;
CCreatureAnim::EAnimType findValidGroup( const std::vector<CCreatureAnim::EAnimType> candidates ) const;
CCreatureAnim::EAnimType getUpwardsGroup() const override;
CCreatureAnim::EAnimType getForwardGroup() const override;
CCreatureAnim::EAnimType getDownwardsGroup() const override;
void createProjectile(const Point & from, const Point & dest) const override;
uint32_t getAttackClimaxFrame() const override;
public:
CCastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
};
struct CPointEffectParameters
{
std::vector<Point> positions;
std::vector<BattleHex> tiles;
std::string animation;
soundBase::soundID sound = soundBase::invalid;
BattleHex boundHex = BattleHex::INVALID;
bool aligntoBottom = false;
bool waitForSound = false;
bool screenFill = false;
};
/// Class that plays effect at one or more positions along with (single) sound effect
class CPointEffectAnimation : public CBattleAnimation
{
soundBase::soundID sound;
bool soundPlayed;
bool soundFinished;
bool effectFinished;
int effectFlags;
std::shared_ptr<CAnimation> animation;
std::vector<Point> positions;
std::vector<BattleHex> battlehexes;
bool alignToBottom() const;
bool waitForSound() const;
bool forceOnTop() const;
bool screenFill() const;
void onEffectFinished();
void onSoundFinished();
void clearEffect();
void playSound();
void playEffect();
public:
enum EEffectFlags
{
ALIGN_TO_BOTTOM = 1,
WAIT_FOR_SOUND = 2,
FORCE_ON_TOP = 4,
SCREEN_FILL = 8,
};
/// Create animation with screen-wide effect
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, int effects = 0);
/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, Point pos , int effects = 0);
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector<Point> pos , int effects = 0);
/// Create animation positioned at certain hex(es)
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, BattleHex hex , int effects = 0);
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector<BattleHex> hex, int effects = 0);
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, Point pos, BattleHex hex, int effects = 0);
~CPointEffectAnimation();
bool init() override;
void nextFrame() override;
};
/// Base class (e.g. for use in dynamic_cast's) for "animations" that wait for certain event
class CWaitingAnimation : public CBattleAnimation
{
protected:
CWaitingAnimation(BattleInterface & owner);
public:
void nextFrame() override;
};
/// Class that waits till projectile of certain shooter hits a target
class CWaitingProjectileAnimation : public CWaitingAnimation
{
const CStack * shooter;
public:
CWaitingProjectileAnimation(BattleInterface & owner, const CStack * shooter);
bool init() override;
};

View File

@ -0,0 +1,328 @@
/*
* BattleControlPanel.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleControlPanel.h"
#include "BattleInterface.h"
#include "BattleInterfaceClasses.h"
#include "BattleStacksController.h"
#include "BattleActionsController.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../windows/CSpellWindow.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../../CCallback.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/CStack.h"
#include "../../lib/CConfigHandler.h"
BattleControlPanel::BattleControlPanel(BattleInterface & owner, const Point & position):
owner(owner)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos += position;
//preparing buttons and console
bOptions = std::make_shared<CButton> (Point( 3, 5), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&BattleControlPanel::bOptionsf,this), SDLK_o);
bSurrender = std::make_shared<CButton> (Point( 54, 5), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&BattleControlPanel::bSurrenderf,this), SDLK_s);
bFlee = std::make_shared<CButton> (Point(105, 5), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&BattleControlPanel::bFleef,this), SDLK_r);
bAutofight = std::make_shared<CButton> (Point(157, 5), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&BattleControlPanel::bAutofightf,this), SDLK_a);
bSpell = std::make_shared<CButton> (Point(645, 5), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&BattleControlPanel::bSpellf,this), SDLK_c);
bWait = std::make_shared<CButton> (Point(696, 5), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&BattleControlPanel::bWaitf,this), SDLK_w);
bDefence = std::make_shared<CButton> (Point(747, 5), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&BattleControlPanel::bDefencef,this), SDLK_d);
bConsoleUp = std::make_shared<CButton> (Point(624, 5), "ComSlide.def", std::make_pair("", ""), std::bind(&BattleControlPanel::bConsoleUpf,this), SDLK_UP);
bConsoleDown = std::make_shared<CButton>(Point(624, 24), "ComSlide.def", std::make_pair("", ""), std::bind(&BattleControlPanel::bConsoleDownf,this), SDLK_DOWN);
bDefence->assignedKeys.insert(SDLK_SPACE);
bConsoleUp->setImageOrder(0, 1, 0, 0);
bConsoleDown->setImageOrder(2, 3, 2, 2);
console = std::make_shared<BattleConsole>(Rect(211, 4, 406,38));
GH.statusbar = console;
if ( owner.tacticsMode )
tacticPhaseStarted();
else
tacticPhaseEnded();
}
void BattleControlPanel::show(SDL_Surface * to)
{
//show menu before all other elements to keep it in background
menu->show(to);
CIntObject::show(to);
}
void BattleControlPanel::showAll(SDL_Surface * to)
{
//show menu before all other elements to keep it in background
menu->showAll(to);
CIntObject::showAll(to);
}
void BattleControlPanel::tacticPhaseStarted()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
btactNext = std::make_shared<CButton>(Point(213, 4), "icm011.def", std::make_pair("", ""), [&]() { bTacticNextStack();}, SDLK_SPACE);
btactEnd = std::make_shared<CButton>(Point(419, 4), "icm012.def", std::make_pair("", ""), [&](){ bTacticPhaseEnd();}, SDLK_RETURN);
menu = std::make_shared<CPicture>("COPLACBR.BMP", 0, 0);
menu->colorize(owner.curInt->playerID);
menu->recActions &= ~(SHOWALL | UPDATE);
}
void BattleControlPanel::tacticPhaseEnded()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
btactNext.reset();
btactEnd.reset();
menu = std::make_shared<CPicture>("CBAR.BMP", 0, 0);
menu->colorize(owner.curInt->playerID);
menu->recActions &= ~(SHOWALL | UPDATE);
}
void BattleControlPanel::bOptionsf()
{
if (owner.actionsController->spellcastingModeActive())
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
GH.pushIntT<BattleOptionsWindow>(owner);
}
void BattleControlPanel::bSurrenderf()
{
if (owner.actionsController->spellcastingModeActive())
return;
int cost = owner.curInt->cb->battleGetSurrenderCost();
if(cost >= 0)
{
std::string enemyHeroName = owner.curInt->cb->battleGetEnemyHero().name;
if(enemyHeroName.empty())
{
logGlobal->warn("Surrender performed without enemy hero, should not happen!");
enemyHeroName = "#ENEMY#";
}
std::string surrenderMessage = boost::str(boost::format(CGI->generaltexth->allTexts[32]) % enemyHeroName % cost); //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
owner.curInt->showYesNoDialog(surrenderMessage, [this](){ reallySurrender(); }, nullptr);
}
}
void BattleControlPanel::bFleef()
{
if (owner.actionsController->spellcastingModeActive())
return;
if ( owner.curInt->cb->battleCanFlee() )
{
CFunctionList<void()> ony = std::bind(&BattleControlPanel::reallyFlee,this);
owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
}
else
{
std::vector<std::shared_ptr<CComponent>> comps;
std::string heroName;
//calculating fleeing hero's name
if (owner.attackingHeroInstance)
if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
heroName = owner.attackingHeroInstance->name;
if (owner.defendingHeroInstance)
if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
heroName = owner.defendingHeroInstance->name;
//calculating text
auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present. %s can not retreat!
//printing message
owner.curInt->showInfoDialog(boost::to_string(txt), comps);
}
}
void BattleControlPanel::reallyFlee()
{
owner.giveCommand(EActionType::RETREAT);
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
}
void BattleControlPanel::reallySurrender()
{
if (owner.curInt->cb->getResourceAmount(Res::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
{
owner.curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
}
else
{
owner.giveCommand(EActionType::SURRENDER);
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
}
}
void BattleControlPanel::bAutofightf()
{
if (owner.actionsController->spellcastingModeActive())
return;
//Stop auto-fight mode
if(owner.curInt->isAutoFightOn)
{
assert(owner.curInt->autofightingAI);
owner.curInt->isAutoFightOn = false;
logGlobal->trace("Stopping the autofight...");
}
else if(!owner.curInt->autofightingAI)
{
owner.curInt->isAutoFightOn = true;
blockUI(true);
auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
ai->init(owner.curInt->env, owner.curInt->cb);
ai->battleStart(owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.curInt->cb->battleGetMySide());
owner.curInt->autofightingAI = ai;
owner.curInt->cb->registerBattleInterface(ai);
owner.requestAutofightingAIToTakeAction();
}
}
void BattleControlPanel::bSpellf()
{
if (owner.actionsController->spellcastingModeActive())
return;
if (!owner.myTurn)
return;
auto myHero = owner.currentHero();
if(!myHero)
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
ESpellCastProblem::ESpellCastProblem spellCastProblem = owner.curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
if(spellCastProblem == ESpellCastProblem::OK)
{
GH.pushIntT<CSpellWindow>(myHero, owner.curInt.get());
}
else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
{
//TODO: move to spell mechanics, add more information to spell cast problem
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
if (!blockingBonus)
return;
if (blockingBonus->source == Bonus::ARTIFACT)
{
const auto artID = ArtifactID(blockingBonus->sid);
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
//TODO check who *really* is source of bonus
std::string heroName = myHero->hasArt(artID) ? myHero->name : owner.enemyHero().name;
//%s wields the %s, an ancient artifact which creates a p dead to all magic.
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
% heroName % CGI->artifacts()->getByIndex(artID)->getName()));
}
}
}
void BattleControlPanel::bWaitf()
{
if (owner.actionsController->spellcastingModeActive())
return;
if (owner.stacksController->getActiveStack() != nullptr)
owner.giveCommand(EActionType::WAIT);
}
void BattleControlPanel::bDefencef()
{
if (owner.actionsController->spellcastingModeActive())
return;
if (owner.stacksController->getActiveStack() != nullptr)
owner.giveCommand(EActionType::DEFEND);
}
void BattleControlPanel::bConsoleUpf()
{
if (owner.actionsController->spellcastingModeActive())
return;
console->scrollUp();
}
void BattleControlPanel::bConsoleDownf()
{
if (owner.actionsController->spellcastingModeActive())
return;
console->scrollDown();
}
void BattleControlPanel::bTacticNextStack()
{
owner.tacticNextStack(nullptr);
}
void BattleControlPanel::bTacticPhaseEnd()
{
owner.tacticPhaseEnd();
}
void BattleControlPanel::blockUI(bool on)
{
bool canCastSpells = false;
auto hero = owner.curInt->cb->battleGetMyHero();
if(hero)
{
ESpellCastProblem::ESpellCastProblem spellcastingProblem = owner.curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO);
//if magic is blocked, we leave button active, so the message can be displayed after button click
canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
}
bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
bOptions->block(on);
bFlee->block(on || !owner.curInt->cb->battleCanFlee());
bSurrender->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
// block only if during enemy turn and auto-fight is off
// otherwise - crash on accessing non-exisiting active stack
bAutofight->block(!owner.curInt->isAutoFightOn && !owner.stacksController->getActiveStack());
if (owner.tacticsMode && btactEnd && btactNext)
{
btactNext->block(on);
btactEnd->block(on);
}
else
{
bConsoleUp->block(on);
bConsoleDown->block(on);
}
bSpell->block(on || owner.tacticsMode || !canCastSpells);
bWait->block(on || owner.tacticsMode || !canWait);
bDefence->block(on || owner.tacticsMode);
}

View File

@ -0,0 +1,76 @@
/*
* BattleControlPanel.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../gui/CIntObject.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
VCMI_LIB_NAMESPACE_END
class CButton;
class BattleInterface;
class BattleConsole;
/// GUI object that handles functionality of panel at the bottom of combat screen
class BattleControlPanel : public CIntObject
{
BattleInterface & owner;
std::shared_ptr<CPicture> menu;
std::shared_ptr<CButton> bOptions;
std::shared_ptr<CButton> bSurrender;
std::shared_ptr<CButton> bFlee;
std::shared_ptr<CButton> bAutofight;
std::shared_ptr<CButton> bSpell;
std::shared_ptr<CButton> bWait;
std::shared_ptr<CButton> bDefence;
std::shared_ptr<CButton> bConsoleUp;
std::shared_ptr<CButton> bConsoleDown;
std::shared_ptr<CButton> btactNext;
std::shared_ptr<CButton> btactEnd;
/// button press handling functions
void bOptionsf();
void bSurrenderf();
void bFleef();
void bAutofightf();
void bSpellf();
void bWaitf();
void bDefencef();
void bConsoleUpf();
void bConsoleDownf();
void bTacticNextStack();
void bTacticPhaseEnd();
/// functions for handling actions after they were confirmed by popup window
void reallyFlee();
void reallySurrender();
public:
std::shared_ptr<BattleConsole> console;
/// block all UI elements when player is not allowed to act, e.g. during enemy turn
void blockUI(bool on);
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
/// Toggle UI to displaying tactics phase
void tacticPhaseStarted();
/// Toggle UI to displaying battle log in place of tactics UI
void tacticPhaseEnded();
BattleControlPanel(BattleInterface & owner, const Point & position);
};

View File

@ -0,0 +1,140 @@
/*
* BattleEffectsController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleEffectsController.h"
#include "BattleAnimationClasses.h"
#include "BattleControlPanel.h"
#include "BattleInterface.h"
#include "BattleInterfaceClasses.h"
#include "BattleFieldController.h"
#include "BattleStacksController.h"
#include "BattleRenderer.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../../CCallback.h"
#include "../../lib/battle/BattleAction.h"
#include "../../lib/NetPacks.h"
#include "../../lib/CStack.h"
#include "../../lib/IGameEventsReceiver.h"
#include "../../lib/CGeneralTextHandler.h"
BattleEffectsController::BattleEffectsController(BattleInterface & owner):
owner(owner)
{}
void BattleEffectsController::displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile)
{
displayEffect(effect, soundBase::invalid, destTile);
}
void BattleEffectsController::displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile)
{
std::string customAnim = graphics->battleACToDef[effect][0];
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::soundID(soundID), customAnim, destTile));
}
void BattleEffectsController::displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects)
{
for(const CustomEffectInfo & one : customEffects)
{
const CStack * s = owner.curInt->cb->battleGetStackByID(one.stack, false);
assert(s);
assert(one.effect != 0);
displayEffect(EBattleEffect::EBattleEffect(one.effect), soundBase::soundID(one.sound), s->getPosition());
}
}
void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
{
const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID);
if(!stack)
{
logGlobal->error("Invalid stack ID %d", bte.stackID);
return;
}
//don't show animation when no HP is regenerated
switch(bte.effect)
{
//TODO: move to bonus type handler
case Bonus::HP_REGENERATION:
displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, stack->getPosition());
break;
case Bonus::MANA_DRAIN:
displayEffect(EBattleEffect::MANA_DRAIN, soundBase::MANADRAI, stack->getPosition());
break;
case Bonus::POISON:
displayEffect(EBattleEffect::POISON, soundBase::POISON, stack->getPosition());
break;
case Bonus::FEAR:
displayEffect(EBattleEffect::FEAR, soundBase::FEAR, stack->getPosition());
break;
case Bonus::MORALE:
{
std::string hlp = CGI->generaltexth->allTexts[33];
boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
displayEffect(EBattleEffect::GOOD_MORALE, soundBase::GOODMRLE, stack->getPosition());
owner.controlPanel->console->addText(hlp);
break;
}
default:
return;
}
//waitForAnims(); //fixme: freezes game :?
}
void BattleEffectsController::startAction(const BattleAction* action)
{
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
switch(action->actionType)
{
case EActionType::WAIT:
owner.controlPanel->console->addText(stack->formatGeneralMessage(136));
break;
case EActionType::BAD_MORALE:
owner.controlPanel->console->addText(stack->formatGeneralMessage(-34));
displayEffect(EBattleEffect::BAD_MORALE, soundBase::BADMRLE, stack->getPosition());
break;
}
//displaying special abilities
auto actionTarget = action->getTarget(owner.curInt->cb.get());
switch(action->actionType)
{
case EActionType::STACK_HEAL:
displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, actionTarget.at(0).hexValue);
break;
}
}
void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer)
{
for (auto & elem : battleEffects)
{
renderer.insert( EBattleFieldLayer::EFFECTS, elem.position, [&elem](BattleRenderer::RendererRef canvas)
{
int currentFrame = static_cast<int>(floor(elem.currentFrame));
currentFrame %= elem.animation->size();
auto img = elem.animation->getImage(currentFrame);
canvas.draw(img, Point(elem.x, elem.y));
});
}
}

View File

@ -0,0 +1,84 @@
/*
* BattleEffectsController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/BattleHex.h"
VCMI_LIB_NAMESPACE_BEGIN
class BattleAction;
struct CustomEffectInfo;
struct BattleTriggerEffect;
VCMI_LIB_NAMESPACE_END
class CAnimation;
class Canvas;
class BattleInterface;
class BattleRenderer;
class CPointEffectAnimation;
namespace EBattleEffect
{
enum EBattleEffect
{
// list of battle effects that have hardcoded triggers
FEAR = 15,
GOOD_LUCK = 18,
GOOD_MORALE = 20,
BAD_MORALE = 30,
BAD_LUCK = 48,
RESURRECT = 50,
DRAIN_LIFE = 52, // hardcoded constant in CGameHandler
POISON = 67,
DEATH_BLOW = 73,
REGENERATION = 74,
MANA_DRAIN = 77,
INVALID = -1,
};
}
/// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,...
struct BattleEffect
{
int x, y; //position on the screen
float currentFrame;
std::shared_ptr<CAnimation> animation;
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
BattleHex position; //Indicates if effect which hex the effect is drawn on
};
/// Controls rendering of effects in battle, e.g. from spells, abilities and various other actions like morale
class BattleEffectsController
{
BattleInterface & owner;
/// list of current effects that are being displayed on screen (spells & creature abilities)
std::vector<BattleEffect> battleEffects;
public:
BattleEffectsController(BattleInterface & owner);
void startAction(const BattleAction* action);
void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
//displays custom effect on the battlefield
void displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile);
void displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile);
//void displayEffects(EBattleEffect::EBattleEffect effect, uint32_t soundID, const std::vector<BattleHex> & destTiles);
void battleTriggerEffect(const BattleTriggerEffect & bte);
void collectRenderableObjects(BattleRenderer & renderer);
friend class CPointEffectAnimation;
};

View File

@ -0,0 +1,634 @@
/*
* BattleFieldController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleFieldController.h"
#include "BattleInterface.h"
#include "BattleActionsController.h"
#include "BattleInterfaceClasses.h"
#include "BattleEffectsController.h"
#include "BattleSiegeController.h"
#include "BattleStacksController.h"
#include "BattleObstacleController.h"
#include "BattleProjectileController.h"
#include "BattleRenderer.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../../CCallback.h"
#include "../../lib/BattleFieldHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CStack.h"
#include "../../lib/spells/ISpellMechanics.h"
BattleFieldController::BattleFieldController(BattleInterface & owner):
owner(owner),
attackingHex(BattleHex::INVALID)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.w = owner.pos.w;
pos.h = owner.pos.h;
//preparing cells and hexes
cellBorder = IImage::createFromFile("CCELLGRD.BMP");
cellShade = IImage::createFromFile("CCELLSHD.BMP");
if(!owner.siegeController)
{
auto bfieldType = owner.curInt->cb->battleGetBattlefieldType();
if(bfieldType == BattleField::NONE)
logGlobal->error("Invalid battlefield returned for current battle");
else
background = IImage::createFromFile(bfieldType.getInfo()->graphics);
}
else
{
std::string backgroundName = owner.siegeController->getBattleBackgroundName();
background = IImage::createFromFile(backgroundName);
}
//preparing graphic with cell borders
cellBorders = std::make_unique<Canvas>(Point(background->width(), background->height()));
for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
{
if ( i % GameConstants::BFIELD_WIDTH == 0)
continue;
if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
continue;
cellBorders->draw(cellBorder, hexPositionLocal(i).topLeft());
}
backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
{
auto hex = std::make_shared<ClickableHex>();
hex->myNumber = h;
hex->pos = hexPositionAbsolute(h);
hex->myInterface = &owner;
bfield.push_back(hex);
}
auto accessibility = owner.curInt->cb->getAccesibility();
for(int i = 0; i < accessibility.size(); i++)
stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
}
void BattleFieldController::renderBattlefield(Canvas & canvas)
{
showBackground(canvas);
BattleRenderer renderer(owner);
renderer.execute(canvas);
owner.projectilesController->showProjectiles(canvas);
}
void BattleFieldController::showBackground(Canvas & canvas)
{
if (owner.stacksController->getActiveStack() != nullptr ) //&& creAnims[stacksController->getActiveStack()->ID]->isIdle() //show everything with range
showBackgroundImageWithHexes(canvas);
else
showBackgroundImage(canvas);
showHighlightedHexes(canvas);
}
void BattleFieldController::showBackgroundImage(Canvas & canvas)
{
canvas.draw(background, owner.pos.topLeft());
owner.obstacleController->showAbsoluteObstacles(canvas, pos.topLeft());
if ( owner.siegeController )
owner.siegeController->showAbsoluteObstacles(canvas, pos.topLeft());
if (settings["battle"]["cellBorders"].Bool())
canvas.draw(*cellBorders, owner.pos.topLeft());
}
void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
{
canvas.draw(*backgroundWithHexes.get(), owner.pos.topLeft());
}
void BattleFieldController::redrawBackgroundWithHexes()
{
const CStack *activeStack = owner.stacksController->getActiveStack();
std::vector<BattleHex> attackableHexes;
if (activeStack)
occupyableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
auto accessibility = owner.curInt->cb->getAccesibility();
for(int i = 0; i < accessibility.size(); i++)
stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
//prepare background graphic with hexes and shaded hexes
backgroundWithHexes->draw(background, Point(0,0));
owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes, Point(0,0));
if ( owner.siegeController )
owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes, Point(0,0));
if (settings["battle"]["stackRange"].Bool())
{
std::vector<BattleHex> hexesToShade = occupyableHexes;
hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
for (BattleHex hex : hexesToShade)
{
backgroundWithHexes->draw(cellShade, hexPositionLocal(hex).topLeft());
}
}
if(settings["battle"]["cellBorders"].Bool())
backgroundWithHexes->draw(*cellBorders, Point(0, 0));
}
void BattleFieldController::showHighlightedHex(Canvas & canvas, BattleHex hex, bool darkBorder)
{
Point hexPos = hexPositionAbsolute(hex).topLeft();
canvas.draw(cellShade, hexPos);
if(!darkBorder && settings["battle"]["cellBorders"].Bool())
canvas.draw(cellBorder, hexPos);
}
std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
{
std::set<BattleHex> result;
if ( !owner.stacksController->getActiveStack())
return result;
if ( !settings["battle"]["stackRange"].Bool())
return result;
auto hoveredHex = getHoveredHex();
std::set<BattleHex> set = owner.curInt->cb->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex, attackingHex);
for(BattleHex hex : set)
result.insert(hex);
// display the movement shadow of the stack at b (i.e. stack under mouse)
const CStack * const shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, false);
if(shere && shere != owner.stacksController->getActiveStack() && shere->alive())
{
std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
for(BattleHex hex : v)
result.insert(hex);
}
return result;
}
std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
{
std::set<BattleHex> result;
auto hoveredHex = getHoveredHex();
if(!settings["battle"]["mouseShadow"].Bool())
return result;
const spells::Caster *caster = nullptr;
const CSpell *spell = nullptr;
spells::Mode mode = spells::Mode::HERO;
if(owner.actionsController->spellcastingModeActive())//hero casts spell
{
spell = owner.actionsController->selectedSpell().toSpell();
caster = owner.getActiveHero();
}
else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
{
spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
caster = owner.stacksController->getActiveStack();
mode = spells::Mode::CREATURE_ACTIVE;
}
if(caster && spell) //when casting spell
{
// printing shaded hex(es)
spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
for(BattleHex shadedHex : shaded)
{
if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
result.insert(shadedHex);
}
}
else if(owner.active) //always highlight pointed hex
{
if(hoveredHex.getX() != 0 && hoveredHex.getX() != GameConstants::BFIELD_WIDTH - 1)
result.insert(hoveredHex);
}
return result;
}
void BattleFieldController::showHighlightedHexes(Canvas & canvas)
{
std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
std::set<BattleHex> hoveredMouse = getHighlightedHexesSpellRange();
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
bool stack = hoveredStack.count(b);
bool mouse = hoveredMouse.count(b);
if ( stack && mouse )
{
// area where enemy stack can move AND affected by mouse cursor - create darker highlight by blitting twice
showHighlightedHex(canvas, b, true);
showHighlightedHex(canvas, b, true);
}
if ( !stack && mouse )
{
showHighlightedHex(canvas, b, true);
}
if ( stack && !mouse )
{
showHighlightedHex(canvas, b, false);
}
}
}
Rect BattleFieldController::hexPositionLocal(BattleHex hex) const
{
int x = 14 + ((hex.getY())%2==0 ? 22 : 0) + 44*hex.getX();
int y = 86 + 42 *hex.getY();
int w = cellShade->width();
int h = cellShade->height();
return Rect(x, y, w, h);
}
Rect BattleFieldController::hexPositionAbsolute(BattleHex hex) const
{
return hexPositionLocal(hex) + owner.pos.topLeft();
}
bool BattleFieldController::isPixelInHex(Point const & position)
{
return !cellShade->isTransparent(position);
}
BattleHex BattleFieldController::getHoveredHex()
{
for ( auto const & hex : bfield)
if (hex->hovered && hex->strictHovered)
return hex->myNumber;
return BattleHex::INVALID;
}
void BattleFieldController::setBattleCursor(BattleHex myNumber)
{
Rect hoveredHexPos = hexPositionAbsolute(myNumber);
CCursorHandler *cursor = CCS->curh;
const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
const double hexMidX = hoveredHexPos.x + hoveredHexPos.w/2.0;
const double hexMidY = hoveredHexPos.y + hoveredHexPos.h/2.0;
const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
std::vector<int> sectorCursor; // From left to bottom left.
sectorCursor.push_back(8);
sectorCursor.push_back(9);
sectorCursor.push_back(10);
sectorCursor.push_back(11);
sectorCursor.push_back(12);
sectorCursor.push_back(7);
const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
bool aboveAttackable = true, belowAttackable = true;
// Exclude directions which cannot be attacked from.
// Check to the left.
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
{
sectorCursor[0] = -1;
}
// Check top left, top right as well as above for 2-hex creatures.
if (myNumber/GameConstants::BFIELD_WIDTH == 0)
{
sectorCursor[1] = -1;
sectorCursor[2] = -1;
aboveAttackable = false;
}
else
{
if (doubleWide)
{
bool attackRow[4] = {true, true, true, true};
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
attackRow[0] = false;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
attackRow[1] = false;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
attackRow[2] = false;
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
attackRow[3] = false;
if (!(attackRow[0] && attackRow[1]))
sectorCursor[1] = -1;
if (!(attackRow[1] && attackRow[2]))
aboveAttackable = false;
if (!(attackRow[2] && attackRow[3]))
sectorCursor[2] = -1;
}
else
{
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
sectorCursor[1] = -1;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
sectorCursor[2] = -1;
}
}
// Check to the right.
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
{
sectorCursor[3] = -1;
}
// Check bottom right, bottom left as well as below for 2-hex creatures.
if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
{
sectorCursor[4] = -1;
sectorCursor[5] = -1;
belowAttackable = false;
}
else
{
if (doubleWide)
{
bool attackRow[4] = {true, true, true, true};
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
attackRow[0] = false;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
attackRow[1] = false;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
attackRow[2] = false;
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
attackRow[3] = false;
if (!(attackRow[0] && attackRow[1]))
sectorCursor[5] = -1;
if (!(attackRow[1] && attackRow[2]))
belowAttackable = false;
if (!(attackRow[2] && attackRow[3]))
sectorCursor[4] = -1;
}
else
{
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
sectorCursor[4] = -1;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
sectorCursor[5] = -1;
}
}
// Determine index from sector.
int cursorIndex;
if (doubleWide)
{
sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
if (sector < 1.5)
cursorIndex = static_cast<int>(sector);
else if (sector >= 1.5 && sector < 2.5)
cursorIndex = 2;
else if (sector >= 2.5 && sector < 4.5)
cursorIndex = (int) sector + 1;
else if (sector >= 4.5 && sector < 5.5)
cursorIndex = 6;
else
cursorIndex = (int) sector + 2;
}
else
{
cursorIndex = static_cast<int>(sector);
}
// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
if (!vstd::contains_if (sectorCursor, [](int sc) { return sc != -1; }))
{
logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
attackingHex = -1;
return;
}
// Find the closest direction attackable, starting with the right one.
// FIXME: Is this really how the original H3 client does it?
int i = 0;
while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
cursor->changeGraphic(ECursor::COMBAT, sectorCursor[index]);
switch (index)
{
case 0:
attackingHex = myNumber - 1; //left
break;
case 1:
attackingHex = myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //top left
break;
case 2:
attackingHex = myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection; //top right
break;
case 3:
attackingHex = myNumber + 1; //right
break;
case 4:
attackingHex = myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection; //bottom right
break;
case 5:
attackingHex = myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //bottom left
break;
}
BattleHex hex(attackingHex);
if (!hex.isValid())
attackingHex = -1;
}
BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
{
//TODO far too much repeating code
BattleHex destHex;
switch(CCS->curh->frame)
{
case 12: //from bottom right
{
bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if (vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 7: //from bottom left
{
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
if (vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 8: //from left
{
if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::DEFENDER)
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
if (vstd::contains(acc, myNumber))
return myNumber - 1;
else
return myNumber - 2;
}
else
{
return myNumber - 1;
}
break;
}
case 9: //from top left
{
destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 10: //from top right
{
bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 11: //from right
{
if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
if(vstd::contains(acc, myNumber))
return myNumber + 1;
else
return myNumber + 2;
}
else
{
return myNumber + 1;
}
break;
}
case 13: //from bottom
{
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 14: //from top
{
destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
if (vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
}
return -1;
}
bool BattleFieldController::isTileAttackable(const BattleHex & number) const
{
for (auto & elem : occupyableHexes)
{
if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
return true;
}
return false;
}
bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const
{
return stackCountOutsideHexes[number];
}

View File

@ -0,0 +1,91 @@
/*
* BattleFieldController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/BattleHex.h"
#include "../gui/CIntObject.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
VCMI_LIB_NAMESPACE_END
struct Rect;
struct Point;
class ClickableHex;
class Canvas;
class IImage;
class BattleInterface;
/// Handles battlefield grid as well as rendering of background layer of battle interface
class BattleFieldController : public CIntObject
{
BattleInterface & owner;
std::shared_ptr<IImage> background;
std::shared_ptr<IImage> cellBorder;
std::shared_ptr<IImage> cellShade;
/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
std::unique_ptr<Canvas> backgroundWithHexes;
/// Canvas that contains cell borders of all tiles in the battlefield
std::unique_ptr<Canvas> cellBorders;
/// hex from which the stack would perform attack with current cursor
BattleHex attackingHex;
/// hexes to which currently active stack can move
std::vector<BattleHex> occupyableHexes;
/// hexes that when in front of a unit cause it's amount box to move back
std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
std::vector<std::shared_ptr<ClickableHex>> bfield;
void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder);
std::set<BattleHex> getHighlightedHexesStackRange();
std::set<BattleHex> getHighlightedHexesSpellRange();
void showBackground(Canvas & canvas);
void showBackgroundImage(Canvas & canvas);
void showBackgroundImageWithHexes(Canvas & canvas);
void showHighlightedHexes(Canvas & canvas);
public:
BattleFieldController(BattleInterface & owner);
void redrawBackgroundWithHexes();
void renderBattlefield(Canvas & canvas);
/// Returns position of hex relative to owner (BattleInterface)
Rect hexPositionLocal(BattleHex hex) const;
/// Returns position of hex relative to game window
Rect hexPositionAbsolute(BattleHex hex) const;
/// Checks whether selected pixel is transparent, uses local coordinates of a hex
bool isPixelInHex(Point const & position);
/// Returns ID of currently hovered hex or BattleHex::INVALID if none
BattleHex getHoveredHex();
/// returns true if selected tile can be attacked in melee by current stack
bool isTileAttackable(const BattleHex & number) const;
/// returns true if stack should render its stack count image in default position - outside own hex
bool stackCountOutsideHex(const BattleHex & number) const;
void setBattleCursor(BattleHex myNumber);
BattleHex fromWhichHexAttack(BattleHex myNumber);
};

View File

@ -0,0 +1,964 @@
/*
* BattleInterface.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleInterface.h"
#include "BattleAnimationClasses.h"
#include "BattleActionsController.h"
#include "BattleInterfaceClasses.h"
#include "CreatureAnimation.h"
#include "BattleProjectileController.h"
#include "BattleEffectsController.h"
#include "BattleObstacleController.h"
#include "BattleSiegeController.h"
#include "BattleFieldController.h"
#include "BattleControlPanel.h"
#include "BattleStacksController.h"
#include "BattleRenderer.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../windows/CAdvmapInterface.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CondSh.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/NetPacks.h"
#include "../../lib/UnlockGuard.h"
CondSh<bool> BattleInterface::animsAreDisplayed(false);
CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
const CGHeroInstance *hero1, const CGHeroInstance *hero2,
const SDL_Rect & myRect,
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
attackerInt(att), defenderInt(defen), curInt(att),
myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
{
OBJ_CONSTRUCTION;
if(spectatorInt)
{
curInt = spectatorInt;
}
else if(!curInt)
{
//May happen when we are defending during network MP game -> attacker interface is just not present
curInt = defenderInt;
}
animsAreDisplayed.setn(false);
pos = myRect;
strongInterest = true;
givenCommand.setn(nullptr);
//hot-seat -> check tactics for both players (defender may be local human)
if(attackerInt && attackerInt->cb->battleGetTacticDist())
tacticianInterface = attackerInt;
else if(defenderInt && defenderInt->cb->battleGetTacticDist())
tacticianInterface = defenderInt;
//if we found interface of player with tactics, then enter tactics mode
tacticsMode = static_cast<bool>(tacticianInterface);
//create stack queue
bool embedQueue;
std::string queueSize = settings["battle"]["queueSize"].String();
if(queueSize == "auto")
embedQueue = screen->h < 700;
else
embedQueue = screen->h < 700 || queueSize == "small";
queue = std::make_shared<StackQueue>(embedQueue, *this);
if(!embedQueue)
{
if (settings["battle"]["showQueue"].Bool())
pos.y += queue->pos.h / 2; //center whole window
queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
}
CPlayerInterface::battleInt = this;
//initializing armies
this->army1 = army1;
this->army2 = army2;
const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
if(town && town->hasFort())
siegeController.reset(new BattleSiegeController(*this, town));
controlPanel = std::make_shared<BattleControlPanel>(*this, Point(0, 556));
projectilesController.reset(new BattleProjectileController(*this));
fieldController.reset( new BattleFieldController(*this));
stacksController.reset( new BattleStacksController(*this));
actionsController.reset( new BattleActionsController(*this));
effectsController.reset(new BattleEffectsController(*this));
//loading hero animations
if(hero1) // attacking hero
{
std::string battleImage;
if(!hero1->type->battleImage.empty())
{
battleImage = hero1->type->battleImage;
}
else
{
if(hero1->sex)
battleImage = hero1->type->heroClass->imageBattleFemale;
else
battleImage = hero1->type->heroClass->imageBattleMale;
}
attackingHero = std::make_shared<BattleHero>(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, *this);
auto img = attackingHero->animation->getImage(0, 0, true);
if(img)
attackingHero->pos = genRect(img->height(), img->width(), pos.x - 43, pos.y - 19);
}
if(hero2) // defending hero
{
std::string battleImage;
if(!hero2->type->battleImage.empty())
{
battleImage = hero2->type->battleImage;
}
else
{
if(hero2->sex)
battleImage = hero2->type->heroClass->imageBattleFemale;
else
battleImage = hero2->type->heroClass->imageBattleMale;
}
defendingHero = std::make_shared<BattleHero>(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, *this);
auto img = defendingHero->animation->getImage(0, 0, true);
if(img)
defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
}
obstacleController.reset(new BattleObstacleController(*this));
if(tacticsMode)
tacticNextStack(nullptr);
CCS->musich->stopMusic();
battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
auto onIntroPlayed = [&]()
{
if(LOCPLINT->battleInt)
{
CCS->musich->playMusicFromSet("battle", true, true);
battleActionsStarted = true;
activateStack();
controlPanel->blockUI(settings["session"]["spectate"].Bool() || stacksController->getActiveStack() == nullptr);
battleIntroSoundChannel = -1;
}
};
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
addUsedEvents(RCLICK | MOVE | KEYBOARD);
controlPanel->blockUI(true);
queue->update();
}
BattleInterface::~BattleInterface()
{
CPlayerInterface::battleInt = nullptr;
givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
if (active) //dirty fix for #485
{
deactivate();
}
if (adventureInt && adventureInt->selection)
{
//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
}
animsAreDisplayed.setn(false);
}
void BattleInterface::setPrintCellBorders(bool set)
{
Settings cellBorders = settings.write["battle"]["cellBorders"];
cellBorders->Bool() = set;
fieldController->redrawBackgroundWithHexes();
GH.totalRedraw();
}
void BattleInterface::setPrintStackRange(bool set)
{
Settings stackRange = settings.write["battle"]["stackRange"];
stackRange->Bool() = set;
fieldController->redrawBackgroundWithHexes();
GH.totalRedraw();
}
void BattleInterface::setPrintMouseShadow(bool set)
{
Settings shadow = settings.write["battle"]["mouseShadow"];
shadow->Bool() = set;
}
void BattleInterface::activate()
{
controlPanel->activate();
if (curInt->isAutoFightOn)
return;
CIntObject::activate();
if (attackingHero)
attackingHero->activate();
if (defendingHero)
defendingHero->activate();
fieldController->activate();
if (settings["battle"]["showQueue"].Bool())
queue->activate();
LOCPLINT->cingconsole->activate();
}
void BattleInterface::deactivate()
{
controlPanel->deactivate();
CIntObject::deactivate();
fieldController->deactivate();
if (attackingHero)
attackingHero->deactivate();
if (defendingHero)
defendingHero->deactivate();
if (settings["battle"]["showQueue"].Bool())
queue->deactivate();
LOCPLINT->cingconsole->deactivate();
}
void BattleInterface::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
{
if(settings["battle"]["showQueue"].Bool()) //hide queue
hideQueue();
else
showQueue();
}
else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
{
actionsController->enterCreatureCastingMode();
}
else if(key.keysym.sym == SDLK_ESCAPE)
{
if(!battleActionsStarted)
CCS->soundh->stopSound(battleIntroSoundChannel);
else
actionsController->endCastingSpell();
}
}
void BattleInterface::mouseMoved(const SDL_MouseMotionEvent &event)
{
BattleHex selectedHex = fieldController->getHoveredHex();
actionsController->handleHex(selectedHex, MOVE);
controlPanel->mouseMoved(event);
}
void BattleInterface::clickRight(tribool down, bool previousState)
{
if (!down)
{
actionsController->endCastingSpell();
}
}
void BattleInterface::stackReset(const CStack * stack)
{
stacksController->stackReset(stack);
}
void BattleInterface::stackAdded(const CStack * stack)
{
stacksController->stackAdded(stack);
}
void BattleInterface::stackRemoved(uint32_t stackID)
{
stacksController->stackRemoved(stackID);
fieldController->redrawBackgroundWithHexes();
queue->update();
}
void BattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
{
stacksController->stackActivated(stack);
}
void BattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{
stacksController->stackMoved(stack, destHex, distance);
}
void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
{
stacksController->stacksAreAttacked(attackedInfos);
std::array<int, 2> killedBySide = {0, 0};
int targets = 0;
for(const StackAttackedInfo & attackedInfo : attackedInfos)
{
++targets;
ui8 side = attackedInfo.defender->side;
killedBySide.at(side) += attackedInfo.amountKilled;
}
for(ui8 side = 0; side < 2; side++)
{
if(killedBySide.at(side) > killedBySide.at(1-side))
setHeroAnimation(side, CCreatureAnim::HERO_DEFEAT);
else if(killedBySide.at(side) < killedBySide.at(1-side))
setHeroAnimation(side, CCreatureAnim::HERO_VICTORY);
}
}
void BattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
{
stacksController->stackAttacking(attacker, dest, attacked, shooting);
}
void BattleInterface::newRoundFirst( int round )
{
waitForAnims();
}
void BattleInterface::newRound(int number)
{
controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
}
void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
{
const CStack * actor = nullptr;
if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
{
actor = stacksController->getActiveStack();
}
auto side = curInt->cb->playerToSide(curInt->playerID);
if(!side)
{
logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
return;
}
auto ba = new BattleAction(); //is deleted in CPlayerInterface::stacksController->getActiveStack()()
ba->side = side.get();
ba->actionType = action;
ba->aimToHex(tile);
ba->actionSubtype = additional;
sendCommand(ba, actor);
}
void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
{
command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2);
if(!tacticsMode)
{
logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
myTurn = false;
stacksController->setActiveStack(nullptr);
givenCommand.setn(command);
}
else
{
curInt->cb->battleMakeTacticAction(command);
vstd::clear_pointer(command);
stacksController->setActiveStack(nullptr);
//next stack will be activated when action ends
}
}
const CGHeroInstance * BattleInterface::getActiveHero()
{
const CStack *attacker = stacksController->getActiveStack();
if(!attacker)
{
return nullptr;
}
if(attacker->side == BattleSide::ATTACKER)
{
return attackingHeroInstance;
}
return defendingHeroInstance;
}
void BattleInterface::hexLclicked(int whichOne)
{
actionsController->handleHex(whichOne, LCLICK);
}
void BattleInterface::stackIsCatapulting(const CatapultAttack & ca)
{
if (siegeController)
siegeController->stackIsCatapulting(ca);
}
void BattleInterface::gateStateChanged(const EGateState state)
{
if (siegeController)
siegeController->gateStateChanged(state);
}
void BattleInterface::battleFinished(const BattleResult& br)
{
bresult = &br;
{
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
animsAreDisplayed.waitUntil(false);
}
stacksController->setActiveStack(nullptr);
displayBattleFinished();
}
void BattleInterface::displayBattleFinished()
{
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
{
close();
return;
}
GH.pushInt(std::make_shared<BattleResultWindow>(*bresult, *(this->curInt)));
curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
CPlayerInterface::battleInt = nullptr;
}
void BattleInterface::spellCast(const BattleSpellCast * sc)
{
const SpellID spellID = sc->spellID;
const CSpell * spell = spellID.toSpell();
assert(spell);
if(!spell)
return;
const std::string & castSoundPath = spell->getCastSound();
if (!castSoundPath.empty())
CCS->soundh->playSound(castSoundPath);
if ( sc->activeCast )
{
const CStack * casterStack = curInt->cb->battleGetStackByID(sc->casterStack);
if(casterStack != nullptr )
{
displaySpellCast(spellID, casterStack->getPosition());
stacksController->addNewAnim(new CCastAnimation(*this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile), spell));
}
else
if (sc->tile.isValid() && !spell->animationInfo.projectile.empty())
{
// this is spell cast by hero with valid destination & valid projectile -> play animation
const CStack * target = curInt->cb->battleGetStackByPos(sc->tile);
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position
Point destcoord = stacksController->getStackPositionAtHex(sc->tile, target); //position attacked by projectile
destcoord += Point(250, 240); // FIXME: what are these constants?
projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
projectilesController->emitStackProjectile( nullptr );
// wait fo projectile to end
stacksController->addNewAnim(new CWaitingProjectileAnimation(*this, nullptr));
}
}
waitForAnims(); //wait for projectile animation
displaySpellHit(spellID, sc->tile);
//queuing affect animation
for(auto & elem : sc->affectedCres)
{
auto stack = curInt->cb->battleGetStackByID(elem, false);
if(stack)
displaySpellEffect(spellID, stack->getPosition());
}
//queuing additional animation
for(auto & elem : sc->customEffects)
{
auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
if(stack)
effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition());
}
waitForAnims();
//mana absorption
if (sc->manaGained > 0)
{
Point leftHero = Point(15, 30) + pos;
Point rightHero = Point(755, 30) + pos;
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
}
}
void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
{
if(stacksController->getActiveStack() != nullptr)
fieldController->redrawBackgroundWithHexes();
}
void BattleInterface::setHeroAnimation(ui8 side, int phase)
{
if(side == BattleSide::ATTACKER)
{
if(attackingHero)
attackingHero->setPhase(phase);
}
else
{
if(defendingHero)
defendingHero->setPhase(phase);
}
}
void BattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
{
for(const auto & line : battleLog)
{
std::string formatted = line.toString();
boost::algorithm::trim(formatted);
if(!controlPanel->console->addText(formatted))
logGlobal->warn("Too long battle log line");
}
}
void BattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit)
{
for(const CSpell::TAnimation & animation : q)
{
if(animation.pause > 0)
stacksController->addNewAnim(new CDummyAnimation(*this, animation.pause));
else
{
int flags = 0;
if (isHit)
flags |= CPointEffectAnimation::FORCE_ON_TOP;
if (animation.verticalPosition == VerticalPosition::BOTTOM)
flags |= CPointEffectAnimation::ALIGN_TO_BOTTOM;
if (!destinationTile.isValid())
flags |= CPointEffectAnimation::SCREEN_FILL;
if (!destinationTile.isValid())
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, animation.resourceName, flags));
else
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, animation.resourceName, destinationTile, flags));
}
}
}
void BattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
if(spell)
displaySpellAnimationQueue(spell->animationInfo.cast, destinationTile, false);
}
void BattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
{
const CSpell *spell = spellID.toSpell();
if(spell)
displaySpellAnimationQueue(spell->animationInfo.affect, destinationTile, false);
}
void BattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
if(spell)
displaySpellAnimationQueue(spell->animationInfo.hit, destinationTile, true);
}
void BattleInterface::setAnimSpeed(int set)
{
Settings speed = settings.write["battle"]["animationSpeed"];
speed->Float() = float(set) / 100;
}
int BattleInterface::getAnimSpeed() const
{
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
}
CPlayerInterface *BattleInterface::getCurrentPlayerInterface() const
{
return curInt.get();
}
void BattleInterface::trySetActivePlayer( PlayerColor player )
{
if ( attackerInt && attackerInt->playerID == player )
curInt = attackerInt;
if ( defenderInt && defenderInt->playerID == player )
curInt = defenderInt;
}
void BattleInterface::activateStack()
{
if(!battleActionsStarted)
return; //"show" function should re-call this function
stacksController->activateStack();
const CStack * s = stacksController->getActiveStack();
if(!s)
return;
myTurn = true;
queue->update();
fieldController->redrawBackgroundWithHexes();
actionsController->activateStack();
GH.fakeMouseMove();
}
void BattleInterface::endAction(const BattleAction* action)
{
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
if(action->actionType == EActionType::HERO_SPELL)
setHeroAnimation(action->side, CCreatureAnim::HERO_HOLDING);
stacksController->endAction(action);
queue->update();
if (tacticsMode) //stack ended movement in tactics phase -> select the next one
tacticNextStack(stack);
if(action->actionType == EActionType::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
fieldController->redrawBackgroundWithHexes();
// if (stacksController->getActiveStack() && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
// {
// logGlobal->warn("Something wrong... interface was deactivated but there is no animation. Reactivating...");
// controlPanel->blockUI(false);
// }
// else
// {
// block UI if no active stack (e.g. enemy turn);
controlPanel->blockUI(stacksController->getActiveStack() == nullptr);
// }
}
void BattleInterface::hideQueue()
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = false;
queue->deactivate();
if (!queue->embedded)
{
moveBy(Point(0, -queue->pos.h / 2));
GH.totalRedraw();
}
}
void BattleInterface::showQueue()
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = true;
queue->activate();
if (!queue->embedded)
{
moveBy(Point(0, +queue->pos.h / 2));
GH.totalRedraw();
}
}
void BattleInterface::startAction(const BattleAction* action)
{
controlPanel->blockUI(true);
if(action->actionType == EActionType::END_TACTIC_PHASE)
{
controlPanel->tacticPhaseEnded();
return;
}
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
if (stack)
{
queue->update();
}
else
{
assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
}
stacksController->startAction(action);
redraw(); // redraw after deactivation, including proper handling of hovered hexes
if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
{
setHeroAnimation(action->side, CCreatureAnim::HERO_CAST_SPELL);
return;
}
if (!stack)
{
logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
return;
}
effectsController->startAction(action);
}
void BattleInterface::waitForAnims()
{
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
animsAreDisplayed.waitWhileTrue();
}
void BattleInterface::tacticPhaseEnd()
{
stacksController->setActiveStack(nullptr);
controlPanel->blockUI(true);
tacticsMode = false;
}
static bool immobile(const CStack *s)
{
return !s->Speed(0, true); //should bound stacks be immobile?
}
void BattleInterface::tacticNextStack(const CStack * current)
{
if (!current)
current = stacksController->getActiveStack();
//no switching stacks when the current one is moving
waitForAnims();
TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
vstd::erase_if (stacksOfMine, &immobile);
if (stacksOfMine.empty())
{
tacticPhaseEnd();
return;
}
auto it = vstd::find(stacksOfMine, current);
if (it != stacksOfMine.end() && ++it != stacksOfMine.end())
stackActivated(*it);
else
stackActivated(stacksOfMine.front());
}
void BattleInterface::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi)
{
obstacleController->obstaclePlaced(oi);
}
const CGHeroInstance *BattleInterface::currentHero() const
{
if (attackingHeroInstance && attackingHeroInstance->tempOwner == curInt->playerID)
return attackingHeroInstance;
if (defendingHeroInstance && defendingHeroInstance->tempOwner == curInt->playerID)
return defendingHeroInstance;
return nullptr;
}
InfoAboutHero BattleInterface::enemyHero() const
{
InfoAboutHero ret;
if (attackingHeroInstance->tempOwner == curInt->playerID)
curInt->cb->getHeroInfo(defendingHeroInstance, ret);
else
curInt->cb->getHeroInfo(attackingHeroInstance, ret);
return ret;
}
void BattleInterface::requestAutofightingAIToTakeAction()
{
assert(curInt->isAutoFightOn);
boost::thread aiThread([&]()
{
auto ba = make_unique<BattleAction>(curInt->autofightingAI->activeStack(stacksController->getActiveStack()));
if(curInt->cb->battleIsFinished())
{
return; // battle finished with spellcast
}
if (curInt->isAutoFightOn)
{
if (tacticsMode)
{
// Always end tactics mode. Player interface is blocked currently, so it's not possible that
// the AI can take any action except end tactics phase (AI actions won't be triggered)
//TODO implement the possibility that the AI will be triggered for further actions
//TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
stacksController->setActiveStack(nullptr);
controlPanel->blockUI(true);
tacticsMode = false;
}
else
{
givenCommand.setn(ba.release());
}
}
else
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
activateStack();
}
});
aiThread.detach();
}
void BattleInterface::showAll(SDL_Surface *to)
{
show(to);
}
void BattleInterface::show(SDL_Surface *to)
{
Canvas canvas(to);
assert(to);
SDL_Rect buf;
SDL_GetClipRect(to, &buf);
SDL_SetClipRect(to, &pos);
++animCount;
fieldController->renderBattlefield(canvas);
if(battleActionsStarted)
stacksController->updateBattleAnimations();
SDL_SetClipRect(to, &buf); //restoring previous clip_rect
showInterface(to);
//activation of next stack, if any
//TODO: should be moved to the very start of this method?
//activateStack();
}
void BattleInterface::collectRenderableObjects(BattleRenderer & renderer)
{
if (attackingHero)
{
renderer.insert(EBattleFieldLayer::HEROES, BattleHex(0),[this](BattleRenderer::RendererRef canvas)
{
attackingHero->render(canvas);
});
}
if (defendingHero)
{
renderer.insert(EBattleFieldLayer::HEROES, BattleHex(GameConstants::BFIELD_WIDTH-1),[this](BattleRenderer::RendererRef canvas)
{
defendingHero->render(canvas);
});
}
}
void BattleInterface::showInterface(SDL_Surface * to)
{
//showing in-game console
LOCPLINT->cingconsole->show(to);
controlPanel->showAll(to);
Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
if (settings["battle"]["showQueue"].Bool())
{
if (!queue->embedded)
{
posWithQueue.y -= queue->pos.h;
posWithQueue.h += queue->pos.h;
}
queue->showAll(to);
}
//printing border around interface
if (screen->w != 800 || screen->h !=600)
{
CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
}
}
void BattleInterface::castThisSpell(SpellID spellID)
{
actionsController->castThisSpell(spellID);
}

View File

@ -0,0 +1,209 @@
/*
* BattleInterface.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../gui/CIntObject.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
VCMI_LIB_NAMESPACE_BEGIN
class CCreatureSet;
class CGHeroInstance;
class CStack;
struct BattleResult;
struct BattleSpellCast;
struct CObstacleInstance;
template <typename T> struct CondSh;
struct SetStackEffect;
class BattleAction;
class CGTownInstance;
struct CatapultAttack;
struct BattleTriggerEffect;
struct BattleHex;
struct InfoAboutHero;
struct CustomEffectInfo;
VCMI_LIB_NAMESPACE_END
class BattleHero;
class Canvas;
class BattleResultWindow;
class StackQueue;
class CPlayerInterface;
class ClickableHex;
class CAnimation;
struct BattleEffect;
class IImage;
class StackQueue;
class BattleProjectileController;
class BattleSiegeController;
class BattleObstacleController;
class BattleFieldController;
class BattleRenderer;
class BattleControlPanel;
class BattleStacksController;
class BattleActionsController;
class BattleEffectsController;
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
struct StackAttackedInfo
{
const CStack *defender; //attacked stack
int64_t dmg; //damage dealt
unsigned int amountKilled; //how many creatures in stack has been killed
const CStack *attacker; //attacking stack
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
bool killed; //if true, stack has been killed
bool rebirth; //if true, play rebirth animation after all
bool cloneKilled;
};
/// Big class which handles the overall battle interface actions and it is also responsible for
/// drawing everything correctly.
class BattleInterface : public WindowBase
{
private:
std::shared_ptr<BattleHero> attackingHero;
std::shared_ptr<BattleHero> defendingHero;
std::shared_ptr<StackQueue> queue;
std::shared_ptr<BattleControlPanel> controlPanel;
std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
std::shared_ptr<CPlayerInterface> curInt; //current player interface
const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
ui8 animCount;
bool tacticsMode;
bool battleActionsStarted; //used for delaying battle actions until intro sound stops
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
void requestAutofightingAIToTakeAction();
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
void showInterface(SDL_Surface * to);
void setHeroAnimation(ui8 side, int phase);
public:
std::unique_ptr<BattleProjectileController> projectilesController;
std::unique_ptr<BattleSiegeController> siegeController;
std::unique_ptr<BattleObstacleController> obstacleController;
std::unique_ptr<BattleFieldController> fieldController;
std::unique_ptr<BattleStacksController> stacksController;
std::unique_ptr<BattleActionsController> actionsController;
std::unique_ptr<BattleEffectsController> effectsController;
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
bool myTurn; //if true, interface is active (commands can be ordered)
bool moveStarted; //if true, the creature that is already moving is going to make its first step
int moveSoundHander; // sound handler used when moving a unit
const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
virtual ~BattleInterface();
void setPrintCellBorders(bool set); //if true, cell borders will be printed
void setPrintStackRange(bool set); //if true,range of active stack will be printed
void setPrintMouseShadow(bool set); //if true, hex under mouse will be shaded
void setAnimSpeed(int set); //speed of animation; range 1..100
int getAnimSpeed() const; //speed of animation; range 1..100
CPlayerInterface *getCurrentPlayerInterface() const;
void tacticNextStack(const CStack *current);
void tacticPhaseEnd();
void waitForAnims();
//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
void activate() override;
void deactivate() override;
void keyPressed(const SDL_KeyboardEvent & key) override;
void mouseMoved(const SDL_MouseMotionEvent &sEvent) override;
void clickRight(tribool down, bool previousState) override;
void show(SDL_Surface *to) override;
void showAll(SDL_Surface *to) override;
void collectRenderableObjects(BattleRenderer & renderer);
//call-ins
void startAction(const BattleAction* action);
void stackReset(const CStack * stack);
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void newRoundFirst( int round );
void newRound(int number); //caled when round is ended; number is the number of round
void hexLclicked(int whichOne); //hex only call-in
void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
void displayBattleFinished(); //displays battle result
void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
void displayBattleLog(const std::vector<MetaString> & battleLog);
void displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit);
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void endAction(const BattleAction* action);
void hideQueue();
void showQueue();
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
void gateStateChanged(const EGateState state);
const CGHeroInstance *currentHero() const;
InfoAboutHero enemyHero() const;
// TODO: cleanup this list
friend class CPlayerInterface;
friend class CInGameConsole;
friend class StackQueue;
friend class BattleResultWindow;
friend class BattleHero;
friend class CBattleStackAnimation;
friend class CReverseAnimation;
friend class CDefenceAnimation;
friend class CMovementAnimation;
friend class CMovementStartAnimation;
friend class CAttackAnimation;
friend class CMeleeAttackAnimation;
friend class CShootingAnimation;
friend class CCastAnimation;
friend class ClickableHex;
friend class BattleProjectileController;
friend class BattleSiegeController;
friend class BattleObstacleController;
friend class BattleFieldController;
friend class BattleControlPanel;
friend class BattleStacksController;
friend class BattleActionsController;
friend class BattleEffectsController;
};

View File

@ -1,5 +1,5 @@
/*
* CBattleInterfaceClasses.cpp, part of VCMI engine
* BattleInterfaceClasses.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -8,11 +8,15 @@
*
*/
#include "StdInc.h"
#include "CBattleInterfaceClasses.h"
#include "BattleInterfaceClasses.h"
#include "CBattleInterface.h"
#include "BattleInterface.h"
#include "BattleActionsController.h"
#include "BattleSiegeController.h"
#include "BattleFieldController.h"
#include "BattleStacksController.h"
#include "BattleControlPanel.h"
#include "../CBitmapHandler.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CMusicHandler.h"
@ -20,10 +24,12 @@
#include "../CVideoHandler.h"
#include "../Graphics.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h"
#include "../widgets/AdventureMapClasses.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../windows/CCreatureWindow.h"
#include "../windows/CSpellWindow.h"
@ -40,34 +46,35 @@
#include "../../lib/CondSh.h"
#include "../../lib/mapObjects/CGTownInstance.h"
void CBattleConsole::showAll(SDL_Surface * to)
void BattleConsole::showAll(SDL_Surface * to)
{
Point textPos(pos.x + pos.w/2, pos.y + 17);
Point consolePos(pos.x + 10, pos.y + 17);
Point textPos (pos.x + pos.w/2, pos.y + 17);
if(ingcAlter.size())
if (!consoleText.empty())
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(ingcAlter, pos.w, FONT_SMALL), Colors::WHITE, textPos);
graphics->fonts[FONT_SMALL]->renderTextLinesLeft(to, CMessage::breakText(consoleText, pos.w, FONT_SMALL), Colors::WHITE, consolePos);
}
else if(alterTxt.size())
else if(!hoverText.empty())
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(alterTxt, pos.w, FONT_SMALL), Colors::WHITE, textPos);
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(hoverText, pos.w, FONT_SMALL), Colors::WHITE, textPos);
}
else if(texts.size())
else if(logEntries.size())
{
if(texts.size()==1)
if(logEntries.size()==1)
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[0], pos.w, FONT_SMALL), Colors::WHITE, textPos);
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(logEntries[0], pos.w, FONT_SMALL), Colors::WHITE, textPos);
}
else
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[lastShown - 1], pos.w, FONT_SMALL), Colors::WHITE, textPos);
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(logEntries[scrollPosition - 1], pos.w, FONT_SMALL), Colors::WHITE, textPos);
textPos.y += 16;
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[lastShown], pos.w, FONT_SMALL), Colors::WHITE, textPos);
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(logEntries[scrollPosition], pos.w, FONT_SMALL), Colors::WHITE, textPos);
}
}
}
bool CBattleConsole::addText(const std::string & text)
bool BattleConsole::addText(const std::string & text)
{
logGlobal->trace("CBattleConsole message: %s", text);
if(text.size()>70)
@ -77,57 +84,84 @@ bool CBattleConsole::addText(const std::string & text)
{
if(text[i] == 10)
{
texts.push_back( text.substr(firstInToken, i-firstInToken) );
logEntries.push_back( text.substr(firstInToken, i-firstInToken) );
firstInToken = (int)i+1;
}
}
texts.push_back( text.substr(firstInToken, text.size()) );
lastShown = (int)texts.size()-1;
logEntries.push_back( text.substr(firstInToken, text.size()) );
scrollPosition = (int)logEntries.size()-1;
return true;
}
void CBattleConsole::alterText(const std::string &text)
void BattleConsole::scrollUp(ui32 by)
{
//char buf[500];
//sprintf(buf, text.c_str());
//alterTxt = buf;
alterTxt = text;
if(scrollPosition > static_cast<int>(by))
scrollPosition -= by;
}
void CBattleConsole::eraseText(ui32 pos)
void BattleConsole::scrollDown(ui32 by)
{
if(pos < texts.size())
if(scrollPosition + by < logEntries.size())
scrollPosition += by;
}
BattleConsole::BattleConsole(const Rect & position)
: scrollPosition(-1)
, enteringText(false)
{
pos += position.topLeft();
pos.w = position.w;
pos.h = position.h;
}
void BattleConsole::deactivate()
{
if (enteringText)
LOCPLINT->cingconsole->endEnteringText(false);
CIntObject::deactivate();
}
void BattleConsole::setEnteringMode(bool on)
{
consoleText.clear();
if (on)
{
texts.erase(texts.begin() + pos);
if(lastShown == texts.size())
--lastShown;
assert(enteringText == false);
CSDL_Ext::startTextInput(&pos);
}
else
{
assert(enteringText == true);
CSDL_Ext::stopTextInput();
}
enteringText = on;
}
void CBattleConsole::changeTextAt(const std::string & text, ui32 pos)
void BattleConsole::setEnteredText(const std::string & text)
{
if(pos >= texts.size()) //no such pos
return;
texts[pos] = text;
assert(enteringText == true);
consoleText = text;
}
void CBattleConsole::scrollUp(ui32 by)
void BattleConsole::write(const std::string & Text)
{
if(lastShown > static_cast<int>(by))
lastShown -= by;
hoverText = Text;
}
void CBattleConsole::scrollDown(ui32 by)
void BattleConsole::clearIfMatching(const std::string & Text)
{
if(lastShown + by < texts.size())
lastShown += by;
if (hoverText == Text)
clear();
}
CBattleConsole::CBattleConsole() : lastShown(-1), alterTxt(""), whoSetAlter(0)
{}
void BattleConsole::clear()
{
write({});
}
void CBattleHero::show(SDL_Surface * to)
void BattleHero::render(Canvas & canvas)
{
auto flagFrame = flagAnimation->getImage(flagAnim, 0, true);
@ -135,35 +169,18 @@ void CBattleHero::show(SDL_Surface * to)
return;
//animation of flag
SDL_Rect temp_rect;
Point flagPosition = pos.topLeft();
if(flip)
{
temp_rect = genRect(
flagFrame->height(),
flagFrame->width(),
pos.x + 61,
pos.y + 39);
}
flagPosition += Point(61, 39);
else
{
temp_rect = genRect(
flagFrame->height(),
flagFrame->width(),
pos.x + 72,
pos.y + 39);
}
flagPosition += Point(72, 39);
flagFrame->draw(screen, &temp_rect, nullptr); //FIXME: why screen?
//animation of hero
SDL_Rect rect = pos;
auto heroFrame = animation->getImage(currentFrame, phase, true);
if(!heroFrame)
return;
heroFrame->draw(to, &rect, nullptr);
canvas.draw(flagFrame, flagPosition);
canvas.draw(heroFrame, pos.topLeft());
if(++animCount >= 4)
{
@ -176,14 +193,14 @@ void CBattleHero::show(SDL_Surface * to)
}
}
void CBattleHero::setPhase(int newPhase)
void BattleHero::setPhase(int newPhase)
{
nextPhase = newPhase;
switchToNextPhase(); //immediately switch to next phase and then restore idling phase
nextPhase = 0;
}
void CBattleHero::hover(bool on)
void BattleHero::hover(bool on)
{
//TODO: Make lines below work properly
if (on)
@ -192,9 +209,9 @@ void CBattleHero::hover(bool on)
CCS->curh->changeGraphic(ECursor::COMBAT, 0);
}
void CBattleHero::clickLeft(tribool down, bool previousState)
void BattleHero::clickLeft(tribool down, bool previousState)
{
if(myOwner->spellDestSelectMode) //we are casting a spell
if(myOwner->actionsController->spellcastingModeActive()) //we are casting a spell
return;
if(boost::logic::indeterminate(down))
@ -205,18 +222,18 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
{
for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
{
if(myOwner->bfield[it]->hovered && myOwner->bfield[it]->strictHovered)
return;
}
BattleHex hoveredHex = myOwner->fieldController->getHoveredHex();
//do nothing when any hex is hovered - hero's animation overlaps battlefield
if ( hoveredHex != BattleHex::INVALID )
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
GH.pushIntT<CSpellWindow>(myHero, myOwner->getCurrentPlayerInterface());
}
}
void CBattleHero::clickRight(tribool down, bool previousState)
void BattleHero::clickRight(tribool down, bool previousState)
{
if(boost::logic::indeterminate(down))
return;
@ -230,11 +247,11 @@ void CBattleHero::clickRight(tribool down, bool previousState)
{
auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance;
targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
GH.pushIntT<CHeroInfoWindow>(targetHero, &windowPosition);
GH.pushIntT<HeroInfoWindow>(targetHero, &windowPosition);
}
}
void CBattleHero::switchToNextPhase()
void BattleHero::switchToNextPhase()
{
if(phase != nextPhase)
{
@ -248,10 +265,10 @@ void CBattleHero::switchToNextPhase()
currentFrame = firstFrame;
}
CBattleHero::CBattleHero(const std::string & animationPath, bool flipG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner):
BattleHero::BattleHero(const std::string & animationPath, bool flipG, PlayerColor player, const CGHeroInstance * hero, const BattleInterface & owner):
flip(flipG),
myHero(hero),
myOwner(owner),
myOwner(&owner),
phase(1),
nextPhase(0),
flagAnim(0),
@ -275,9 +292,7 @@ CBattleHero::CBattleHero(const std::string & animationPath, bool flipG, PlayerCo
switchToNextPhase();
}
CBattleHero::~CBattleHero() = default;
CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero & hero, Point * position)
HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, "CHRPOP")
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@ -297,49 +312,46 @@ CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero & hero, Point * position)
icons.push_back(std::make_shared<CAnimImage>("PortraitsLarge", hero.portrait, 0, 10, 6));
//primary stats
labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 87, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[381] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 99, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[382] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 111, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[383] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 87, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[381] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 99, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[382] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 111, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[383] + ":"));
labels.push_back(std::make_shared<CLabel>(69, 87, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(attack)));
labels.push_back(std::make_shared<CLabel>(69, 99, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(defense)));
labels.push_back(std::make_shared<CLabel>(69, 111, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(power)));
labels.push_back(std::make_shared<CLabel>(69, 123, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(knowledge)));
labels.push_back(std::make_shared<CLabel>(69, 87, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(attack)));
labels.push_back(std::make_shared<CLabel>(69, 99, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(defense)));
labels.push_back(std::make_shared<CLabel>(69, 111, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(power)));
labels.push_back(std::make_shared<CLabel>(69, 123, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(knowledge)));
//morale+luck
labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
icons.push_back(std::make_shared<CAnimImage>("IMRL22", morale + 3, 0, 47, 131));
icons.push_back(std::make_shared<CAnimImage>("ILCK22", luck + 3, 0, 47, 143));
//spell points
labels.push_back(std::make_shared<CLabel>(39, 174, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[387]));
labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
labels.push_back(std::make_shared<CLabel>(39, 174, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[387]));
labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
}
CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInterface *owner)
BattleOptionsWindow::BattleOptionsWindow(BattleInterface & owner):
CWindowObject(PLAYER_COLORED, "comopbck.bmp")
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
pos = position;
background = std::make_shared<CPicture>("comopbck.bmp");
background->colorize(owner->getCurrentPlayerInterface()->playerID);
auto viewGrid = std::make_shared<CToggleButton>(Point(25, 56), "sysopchk.def", CGI->generaltexth->zelp[427], [=](bool on){owner->setPrintCellBorders(on);} );
auto viewGrid = std::make_shared<CToggleButton>(Point(25, 56), "sysopchk.def", CGI->generaltexth->zelp[427], [&](bool on){owner.setPrintCellBorders(on);} );
viewGrid->setSelected(settings["battle"]["cellBorders"].Bool());
toggles.push_back(viewGrid);
auto movementShadow = std::make_shared<CToggleButton>(Point(25, 89), "sysopchk.def", CGI->generaltexth->zelp[428], [=](bool on){owner->setPrintStackRange(on);});
auto movementShadow = std::make_shared<CToggleButton>(Point(25, 89), "sysopchk.def", CGI->generaltexth->zelp[428], [&](bool on){owner.setPrintStackRange(on);});
movementShadow->setSelected(settings["battle"]["stackRange"].Bool());
toggles.push_back(movementShadow);
auto mouseShadow = std::make_shared<CToggleButton>(Point(25, 122), "sysopchk.def", CGI->generaltexth->zelp[429], [=](bool on){owner->setPrintMouseShadow(on);});
auto mouseShadow = std::make_shared<CToggleButton>(Point(25, 122), "sysopchk.def", CGI->generaltexth->zelp[429], [&](bool on){owner.setPrintMouseShadow(on);});
mouseShadow->setSelected(settings["battle"]["mouseShadow"].Bool());
toggles.push_back(mouseShadow);
animSpeeds = std::make_shared<CToggleGroup>([=](int value){ owner->setAnimSpeed(value);});
animSpeeds = std::make_shared<CToggleGroup>([&](int value){ owner.setAnimSpeed(value);});
std::shared_ptr<CToggleButton> toggle;
toggle = std::make_shared<CToggleButton>(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422]);
@ -351,7 +363,7 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt
toggle = std::make_shared<CToggleButton>(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424]);
animSpeeds->addToggle(100, toggle);
animSpeeds->setSelected(owner->getAnimSpeed());
animSpeeds->setSelected(owner.getAnimSpeed());
setToDefault = std::make_shared<CButton>(Point(246, 359), "codefaul.def", CGI->generaltexth->zelp[393], [&](){ bDefaultf(); });
setToDefault->setImageOrder(1, 0, 2, 3);
@ -359,42 +371,42 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt
exit->setImageOrder(1, 0, 2, 3);
//creating labels
labels.push_back(std::make_shared<CLabel>(242, 32, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[392]));//window title
labels.push_back(std::make_shared<CLabel>(122, 214, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[393]));//animation speed
labels.push_back(std::make_shared<CLabel>(122, 293, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[394]));//music volume
labels.push_back(std::make_shared<CLabel>(122, 359, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[395]));//effects' volume
labels.push_back(std::make_shared<CLabel>(353, 66, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[396]));//auto - combat options
labels.push_back(std::make_shared<CLabel>(353, 265, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[397]));//creature info
labels.push_back(std::make_shared<CLabel>(242, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[392]));//window title
labels.push_back(std::make_shared<CLabel>(122, 214, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[393]));//animation speed
labels.push_back(std::make_shared<CLabel>(122, 293, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[394]));//music volume
labels.push_back(std::make_shared<CLabel>(122, 359, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[395]));//effects' volume
labels.push_back(std::make_shared<CLabel>(353, 66, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[396]));//auto - combat options
labels.push_back(std::make_shared<CLabel>(353, 265, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[397]));//creature info
//auto - combat options
labels.push_back(std::make_shared<CLabel>(283, 86, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[398]));//creatures
labels.push_back(std::make_shared<CLabel>(283, 116, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[399]));//spells
labels.push_back(std::make_shared<CLabel>(283, 146, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[400]));//catapult
labels.push_back(std::make_shared<CLabel>(283, 176, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[151]));//ballista
labels.push_back(std::make_shared<CLabel>(283, 206, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[401]));//first aid tent
labels.push_back(std::make_shared<CLabel>(283, 86, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[398]));//creatures
labels.push_back(std::make_shared<CLabel>(283, 116, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[399]));//spells
labels.push_back(std::make_shared<CLabel>(283, 146, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[400]));//catapult
labels.push_back(std::make_shared<CLabel>(283, 176, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[151]));//ballista
labels.push_back(std::make_shared<CLabel>(283, 206, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[401]));//first aid tent
//creature info
labels.push_back(std::make_shared<CLabel>(283, 285, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[402]));//all stats
labels.push_back(std::make_shared<CLabel>(283, 315, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[403]));//spells only
labels.push_back(std::make_shared<CLabel>(283, 285, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[402]));//all stats
labels.push_back(std::make_shared<CLabel>(283, 315, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[403]));//spells only
//general options
labels.push_back(std::make_shared<CLabel>(61, 57, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[404]));
labels.push_back(std::make_shared<CLabel>(61, 90, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[405]));
labels.push_back(std::make_shared<CLabel>(61, 123, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[406]));
labels.push_back(std::make_shared<CLabel>(61, 156, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[407]));
labels.push_back(std::make_shared<CLabel>(61, 57, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[404]));
labels.push_back(std::make_shared<CLabel>(61, 90, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[405]));
labels.push_back(std::make_shared<CLabel>(61, 123, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[406]));
labels.push_back(std::make_shared<CLabel>(61, 156, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[407]));
}
void CBattleOptionsWindow::bDefaultf()
void BattleOptionsWindow::bDefaultf()
{
//TODO: implement
}
void CBattleOptionsWindow::bExitf()
void BattleOptionsWindow::bExitf()
{
close();
}
CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterface & _owner)
BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner)
: owner(_owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@ -408,25 +420,25 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
if(br.winner == 0) //attacker won
{
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
}
else
{
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
}
if(br.winner == 1)
{
labels.push_back(std::make_shared<CLabel>(412, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
labels.push_back(std::make_shared<CLabel>(412, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
}
else
{
labels.push_back(std::make_shared<CLabel>(408, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
labels.push_back(std::make_shared<CLabel>(408, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
}
labels.push_back(std::make_shared<CLabel>(232, 302, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[407]));
labels.push_back(std::make_shared<CLabel>(232, 332, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[408]));
labels.push_back(std::make_shared<CLabel>(232, 428, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[409]));
labels.push_back(std::make_shared<CLabel>(232, 302, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[407]));
labels.push_back(std::make_shared<CLabel>(232, 332, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[408]));
labels.push_back(std::make_shared<CLabel>(232, 428, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[409]));
std::string sideNames[2] = {"N/A", "N/A"};
@ -462,15 +474,15 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
}
//printing attacker and defender's names
labels.push_back(std::make_shared<CLabel>(89, 37, FONT_SMALL, TOPLEFT, Colors::WHITE, sideNames[0]));
labels.push_back(std::make_shared<CLabel>(381, 53, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, sideNames[1]));
labels.push_back(std::make_shared<CLabel>(89, 37, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, sideNames[0]));
labels.push_back(std::make_shared<CLabel>(381, 53, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, sideNames[1]));
//printing casualties
for(int step = 0; step < 2; ++step)
{
if(br.casualties[step].size()==0)
{
labels.push_back(std::make_shared<CLabel>(235, 360 + 97 * step, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[523]));
labels.push_back(std::make_shared<CLabel>(235, 360 + 97 * step, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[523]));
}
else
{
@ -485,7 +497,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
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, CENTER, Colors::WHITE, amount.str()));
labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, amount.str()));
xPos += 42;
}
}
@ -522,7 +534,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
boost::algorithm::replace_first(str, "%d", boost::lexical_cast<std::string>(br.exp[weAreAttacker ? 0 : 1]));
}
description = std::make_shared<CTextBox>(str, Rect(69, 203, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE);
description = std::make_shared<CTextBox>(str, Rect(69, 203, 330, 68), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
}
else // we lose
{
@ -550,31 +562,29 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
CCS->musich->playMusic(musicName, false, true);
CCS->videoh->open(videoName);
labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text]));
labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text]));
}
}
CBattleResultWindow::~CBattleResultWindow() = default;
void CBattleResultWindow::activate()
void BattleResultWindow::activate()
{
owner.showingDialog->set(true);
CIntObject::activate();
}
void CBattleResultWindow::show(SDL_Surface * to)
void BattleResultWindow::show(SDL_Surface * to)
{
CIntObject::show(to);
CCS->videoh->update(pos.x + 107, pos.y + 70, screen, true, false);
}
void CBattleResultWindow::bExitf()
void BattleResultWindow::bExitf()
{
CPlayerInterface &intTmp = owner; //copy reference because "this" will be destructed soon
close();
if(dynamic_cast<CBattleInterface*>(GH.topInt().get()))
if(dynamic_cast<BattleInterface*>(GH.topInt().get()))
GH.popInts(1); //pop battle interface if present
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
@ -583,113 +593,48 @@ void CBattleResultWindow::bExitf()
CCS->videoh->close();
}
Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBattleInterface * cbi)
{
assert(cbi);
Point ret(-500, -500); //returned value
if(stack && stack->initialPosition < 0) //creatures in turrets
{
switch(stack->initialPosition)
{
case -2: //keep
ret = cbi->siegeH->town->town->clientInfo.siegePositions[18];
break;
case -3: //lower turret
ret = cbi->siegeH->town->town->clientInfo.siegePositions[19];
break;
case -4: //upper turret
ret = cbi->siegeH->town->town->clientInfo.siegePositions[20];
break;
}
}
else
{
static const Point basePos(-190, -139); // position of creature in topleft corner
static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left
ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX();
ret.y = basePos.y + 42 * hexNum.getY();
if (stack)
{
if(cbi->creDir[stack->ID])
ret.x += imageShiftX;
else
ret.x -= imageShiftX;
//shifting position for double - hex creatures
if(stack->doubleWide())
{
if(stack->side == BattleSide::ATTACKER)
{
if(cbi->creDir[stack->ID])
ret.x -= 44;
}
else
{
if(!cbi->creDir[stack->ID])
ret.x += 44;
}
}
}
}
//returning
return ret + CPlayerInterface::battleInt->pos;
}
void CClickableHex::hover(bool on)
void ClickableHex::hover(bool on)
{
hovered = on;
//Hoverable::hover(on);
if(!on && setAlterText)
{
myInterface->console->alterTxt = std::string();
myInterface->controlPanel->console->clear();
setAlterText = false;
}
}
CClickableHex::CClickableHex() : setAlterText(false), myNumber(-1), accessible(true), strictHovered(false), myInterface(nullptr)
ClickableHex::ClickableHex() : setAlterText(false), myNumber(-1), strictHovered(false), myInterface(nullptr)
{
addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
}
void CClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
{
if(myInterface->cellShade)
{
if(CSDL_Ext::SDL_GetPixel(myInterface->cellShade, sEvent.x-pos.x, sEvent.y-pos.y) == 0) //hovered pixel is outside hex
{
strictHovered = false;
}
else //hovered pixel is inside hex
{
strictHovered = true;
}
}
strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y));
if(hovered && strictHovered) //print attacked creature to console
{
const CStack * attackedStack = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber);
if(myInterface->console->alterTxt.size() == 0 &&attackedStack != nullptr &&
if( attackedStack != nullptr &&
attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
attackedStack->alive())
{
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, 220);
attackedStack->addNameReplacement(text);
myInterface->console->alterTxt = text.toString();
myInterface->controlPanel->console->write(text.toString());
setAlterText = true;
}
}
else if(setAlterText)
{
myInterface->console->alterTxt = std::string();
myInterface->controlPanel->console->clear();
setAlterText = false;
}
}
void CClickableHex::clickLeft(tribool down, bool previousState)
void ClickableHex::clickLeft(tribool down, bool previousState)
{
if(!down && hovered && strictHovered) //we've been really clicked!
{
@ -697,7 +642,7 @@ void CClickableHex::clickLeft(tribool down, bool previousState)
}
}
void CClickableHex::clickRight(tribool down, bool previousState)
void ClickableHex::clickRight(tribool down, bool previousState)
{
const CStack * myst = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber); //stack info
if(hovered && strictHovered && myst!=nullptr)
@ -710,9 +655,9 @@ void CClickableHex::clickRight(tribool down, bool previousState)
}
}
CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
: embedded(Embedded),
owner(_owner)
owner(owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
if(embedded)
@ -747,13 +692,11 @@ CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
}
}
CStackQueue::~CStackQueue() = default;
void CStackQueue::update()
void StackQueue::update()
{
std::vector<battle::Units> queueData;
owner->getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
owner.getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
size_t boxIndex = 0;
@ -767,12 +710,12 @@ void CStackQueue::update()
stackBoxes[boxIndex]->setUnit(nullptr);
}
int32_t CStackQueue::getSiegeShooterIconID()
int32_t StackQueue::getSiegeShooterIconID()
{
return owner->siegeH->town->town->faction->index;
return owner.siegeController->getSiegedTown()->town->faction->index;
}
CStackQueue::StackBox::StackBox(CStackQueue * owner):
StackQueue::StackBox::StackBox(StackQueue * owner):
owner(owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@ -784,12 +727,12 @@ CStackQueue::StackBox::StackBox(CStackQueue * owner):
if(owner->embedded)
{
icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 5, 2);
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 7, FONT_SMALL, CENTER, Colors::WHITE);
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 7, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
}
else
{
icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 9, 1);
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, CENTER, Colors::WHITE);
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
int icon_x = pos.w - 17;
int icon_y = pos.h - 18;
@ -799,7 +742,7 @@ CStackQueue::StackBox::StackBox(CStackQueue * owner):
}
}
void CStackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
{
if(unit)
{

View File

@ -1,5 +1,5 @@
/*
* CBattleInterfaceClasses.h, part of VCMI engine
* BattleInterfaceClasses.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -26,8 +26,9 @@ class Unit;
VCMI_LIB_NAMESPACE_END
class Canvas;
struct SDL_Surface;
class CBattleInterface;
class BattleInterface;
class CPicture;
class CFilledTexture;
class CButton;
@ -39,27 +40,43 @@ class CAnimImage;
class CPlayerInterface;
/// Class which shows the console at the bottom of the battle screen and manages the text of the console
class CBattleConsole : public CIntObject
class BattleConsole : public CIntObject, public IStatusBar
{
private:
std::vector< std::string > texts; //a place where texts are stored
int lastShown; //last shown line of text
/// List of all texts added during battle, essentially - log of entire battle
std::vector< std::string > logEntries;
/// Current scrolling position, to allow showing older entries via scroll buttons
int scrollPosition;
/// current hover text set on mouse move, takes priority over log entries
std::string hoverText;
/// current text entered via in-game console, takes priority over both log entries and hover text
std::string consoleText;
/// if true then we are currently entering console text
bool enteringText;
public:
std::string alterTxt; //if it's not empty, this text is displayed
std::string ingcAlter; //alternative text set by in-game console - very important!
int whoSetAlter; //who set alter text; 0 - battle interface or none, 1 - button
CBattleConsole();
void showAll(SDL_Surface * to = 0) override;
BattleConsole(const Rect & position);
void showAll(SDL_Surface * to) override;
void deactivate() override;
bool addText(const std::string &text); //adds text at the last position; returns false if failed (e.g. text longer than 70 characters)
void alterText(const std::string &text); //place string at alterTxt
void eraseText(ui32 pos); //erases added text at position pos
void changeTextAt(const std::string &text, ui32 pos); //if we have more than pos texts, pos-th is changed to given one
void scrollUp(ui32 by = 1); //scrolls console up by 'by' positions
void scrollDown(ui32 by = 1); //scrolls console up by 'by' positions
// IStatusBar interface
void write(const std::string & Text) override;
void clearIfMatching(const std::string & Text) override;
void clear() override;
void setEnteringMode(bool on) override;
void setEnteredText(const std::string & text) override;
};
/// Hero battle animation
class CBattleHero : public CIntObject
class BattleHero : public CIntObject
{
void switchToNextPhase();
public:
@ -69,50 +86,48 @@ public:
std::shared_ptr<CAnimation> flagAnimation;
const CGHeroInstance * myHero; //this animation's hero instance
const CBattleInterface * myOwner; //battle interface to which this animation is assigned
const BattleInterface * myOwner; //battle interface to which this animation is assigned
int phase; //stage of animation
int nextPhase; //stage of animation to be set after current phase is fully displayed
int currentFrame, firstFrame, lastFrame; //frame of animation
size_t flagAnim;
ui8 animCount; //for flag animation
void show(SDL_Surface * to) override; //prints next frame of animation to to
void render(Canvas & canvas); //prints next frame of animation to to
void setPhase(int newPhase); //sets phase of hero animation
void hover(bool on) override;
void clickLeft(tribool down, bool previousState) override; //call-in
void clickRight(tribool down, bool previousState) override; //call-in
CBattleHero(const std::string & animationPath, bool filpG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner);
~CBattleHero();
BattleHero(const std::string & animationPath, bool filpG, PlayerColor player, const CGHeroInstance * hero, const BattleInterface & owner);
};
class CHeroInfoWindow : public CWindowObject
class HeroInfoWindow : public CWindowObject
{
private:
std::vector<std::shared_ptr<CLabel>> labels;
std::vector<std::shared_ptr<CAnimImage>> icons;
public:
CHeroInfoWindow(const InfoAboutHero & hero, Point * position);
HeroInfoWindow(const InfoAboutHero & hero, Point * position);
};
/// Class which manages the battle options window
class CBattleOptionsWindow : public WindowBase
class BattleOptionsWindow : public CWindowObject
{
private:
std::shared_ptr<CPicture> background;
std::shared_ptr<CButton> setToDefault;
std::shared_ptr<CButton> exit;
std::shared_ptr<CToggleGroup> animSpeeds;
std::vector<std::shared_ptr<CLabel>> labels;
std::vector<std::shared_ptr<CToggleButton>> toggles;
public:
CBattleOptionsWindow(const SDL_Rect & position, CBattleInterface * owner);
BattleOptionsWindow(BattleInterface & owner);
void bDefaultf(); //default button callback
void bExitf(); //exit button callback
};
/// Class which is responsible for showing the battle result window
class CBattleResultWindow : public WindowBase
class BattleResultWindow : public WindowBase
{
private:
std::shared_ptr<CPicture> background;
@ -122,8 +137,7 @@ private:
std::shared_ptr<CTextBox> description;
CPlayerInterface & owner;
public:
CBattleResultWindow(const BattleResult & br, CPlayerInterface & _owner);
~CBattleResultWindow();
BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner);
void bExitf(); //exit button callback
@ -132,32 +146,29 @@ public:
};
/// Class which stands for a single hex field on a battlefield
class CClickableHex : public CIntObject
class ClickableHex : public CIntObject
{
private:
bool setAlterText; //if true, this hex has set alternative text in console and will clean it
public:
ui32 myNumber; //number of hex in commonly used format
bool accessible; //if true, this hex is accessible for units
//CStack * ourStack;
bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering)
CBattleInterface * myInterface; //interface that owns me
static Point getXYUnitAnim(BattleHex hexNum, const CStack * creature, CBattleInterface * cbi); //returns (x, y) of left top corner of animation
BattleInterface * myInterface; //interface that owns me
//for user interactions
void hover (bool on) override;
void mouseMoved (const SDL_MouseMotionEvent &sEvent) override;
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
CClickableHex();
ClickableHex();
};
/// Shows the stack queue
class CStackQueue : public CIntObject
class StackQueue : public CIntObject
{
class StackBox : public CIntObject
{
CStackQueue * owner;
StackQueue * owner;
public:
std::shared_ptr<CPicture> background;
std::shared_ptr<CAnimImage> icon;
@ -165,13 +176,13 @@ class CStackQueue : public CIntObject
std::shared_ptr<CAnimImage> stateIcon;
void setUnit(const battle::Unit * unit, size_t turn = 0);
StackBox(CStackQueue * owner);
StackBox(StackQueue * owner);
};
static const int QUEUE_SIZE = 10;
std::shared_ptr<CFilledTexture> background;
std::vector<std::shared_ptr<StackBox>> stackBoxes;
CBattleInterface * owner;
BattleInterface & owner;
std::shared_ptr<CAnimation> icons;
std::shared_ptr<CAnimation> stateIcons;
@ -180,7 +191,6 @@ class CStackQueue : public CIntObject
public:
const bool embedded;
CStackQueue(bool Embedded, CBattleInterface * _owner);
~CStackQueue();
StackQueue(bool Embedded, BattleInterface & owner);
void update();
};

View File

@ -0,0 +1,190 @@
/*
* BattleObstacleController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleObstacleController.h"
#include "BattleInterface.h"
#include "BattleFieldController.h"
#include "BattleAnimationClasses.h"
#include "BattleStacksController.h"
#include "BattleRenderer.h"
#include "../CPlayerInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../../CCallback.h"
#include "../../lib/battle/CObstacleInstance.h"
#include "../../lib/ObstacleHandler.h"
BattleObstacleController::BattleObstacleController(BattleInterface & owner):
owner(owner)
{
auto obst = owner.curInt->cb->battleGetAllObstacles();
for(auto & elem : obst)
{
if ( elem->obstacleType == CObstacleInstance::MOAT )
continue; // handled by siege controller;
loadObstacleImage(*elem);
}
}
void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
{
std::string animationName;
if (auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&oi))
{
animationName = spellObstacle->animation;
}
else
{
assert( oi.obstacleType == CObstacleInstance::USUAL || oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE);
animationName = oi.getInfo().animation;
}
if (animationsCache.count(animationName) == 0)
{
if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
{
// obstacle uses single bitmap image for animations
auto animation = std::make_shared<CAnimation>();
animation->setCustom(animationName, 0, 0);
animationsCache[animationName] = animation;
animation->preload();
}
else
{
auto animation = std::make_shared<CAnimation>(animationName);
animationsCache[animationName] = animation;
animation->preload();
}
}
obstacleAnimations[oi.uniqueID] = animationsCache[animationName];
}
void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & obstacles)
{
assert(obstaclesBeingPlaced.empty());
for (auto const & oi : obstacles)
obstaclesBeingPlaced.push_back(oi->uniqueID);
for (auto const & oi : obstacles)
{
auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(oi.get());
if (!spellObstacle)
{
logGlobal->error("I don't know how to animate appearing obstacle of type %d", (int)oi->obstacleType);
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
continue;
}
auto animation = std::make_shared<CAnimation>(spellObstacle->appearAnimation);
animation->preload();
auto first = animation->getImage(0, 0);
if(!first)
{
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
continue;
}
//TODO: sound
//soundBase::QUIKSAND
//soundBase::LANDMINE
//we assume here that effect graphics have the same size as the usual obstacle image
// -> if we know how to blit obstacle, let's blit the effect in the same place
Point whereTo = getObstaclePosition(first, *oi);
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::invalid, spellObstacle->appearAnimation, whereTo, oi->pos, CPointEffectAnimation::WAIT_FOR_SOUND));
//so when multiple obstacles are added, they show up one after another
owner.waitForAnims();
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
loadObstacleImage(*spellObstacle);
}
}
void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas, const Point & offset)
{
//Blit absolute obstacles
for(auto & oi : owner.curInt->cb->battleGetAllObstacles())
{
if(oi->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
{
auto img = getObstacleImage(*oi);
if(img)
canvas.draw(img, Point(offset.x + oi->getInfo().width, offset.y + oi->getInfo().height));
}
}
}
void BattleObstacleController::collectRenderableObjects(BattleRenderer & renderer)
{
for (auto obstacle : owner.curInt->cb->battleGetAllObstacles())
{
if (obstacle->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
continue;
if (obstacle->obstacleType == CObstacleInstance::MOAT)
continue;
renderer.insert(EBattleFieldLayer::OBSTACLES, obstacle->pos, [this, obstacle]( BattleRenderer::RendererRef canvas ){
auto img = getObstacleImage(*obstacle);
if(img)
{
Point p = getObstaclePosition(img, *obstacle);
canvas.draw(img, p);
}
});
}
}
std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstacleInstance & oi)
{
int frameIndex = (owner.animCount+1) *25 / owner.getAnimSpeed();
std::shared_ptr<CAnimation> animation;
if (obstacleAnimations.count(oi.uniqueID) == 0)
{
if (boost::range::find(obstaclesBeingPlaced, oi.uniqueID) != obstaclesBeingPlaced.end())
{
// obstacle is not loaded yet, don't show anything
return nullptr;
}
else
{
assert(0); // how?
loadObstacleImage(oi);
}
}
animation = obstacleAnimations[oi.uniqueID];
assert(animation);
if(animation)
{
frameIndex %= animation->size(0);
return animation->getImage(frameIndex, 0);
}
return nullptr;
}
Point BattleObstacleController::getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle)
{
int offset = obstacle.getAnimationYOffset(image->height());
Rect r = owner.fieldController->hexPositionAbsolute(obstacle.pos);
r.y += 42 - image->height() + offset;
return r.topLeft();
}

View File

@ -0,0 +1,58 @@
/*
* BattleObstacleController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
struct BattleHex;
struct CObstacleInstance;
VCMI_LIB_NAMESPACE_END
class IImage;
class Canvas;
class CAnimation;
class BattleInterface;
class BattleRenderer;
struct Point;
/// Controls all currently active projectiles on the battlefield
/// (with exception of moat, which is apparently handled by siege controller)
class BattleObstacleController
{
BattleInterface & owner;
/// cached animations of all obstacles in current battle
std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
/// list of all obstacles that are currently being rendered
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
/// semi-debug member, contains obstacles that should not yet be visible due to ongoing placement animation
/// used only for sanity checks to ensure that there are no invisible obstacles
std::vector<si32> obstaclesBeingPlaced;
void loadObstacleImage(const CObstacleInstance & oi);
std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
public:
BattleObstacleController(BattleInterface & owner);
/// call-in from network pack, add newly placed obstacles with any required animations
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & oi);
/// renders all "absolute" obstacles
void showAbsoluteObstacles(Canvas & canvas, const Point & offset);
/// adds all non-"absolute" visible obstacles for rendering
void collectRenderableObjects(BattleRenderer & renderer);
};

View File

@ -0,0 +1,373 @@
/*
* BattleProjectileController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleProjectileController.h"
#include "BattleInterface.h"
#include "BattleSiegeController.h"
#include "BattleStacksController.h"
#include "CreatureAnimation.h"
#include "../gui/Geometries.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CGuiHandler.h"
#include "../CGameInfo.h"
#include "../../lib/CStack.h"
#include "../../lib/mapObjects/CGTownInstance.h"
static double calculateCatapultParabolaY(const Point & from, const Point & dest, int x)
{
double facA = 0.005; // seems to be constant
// system of 2 linear equations, solutions of which are missing coefficients
// for quadratic equation a*x*x + b*x + c
double eq[2][3] = {
{ static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x },
{ static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x }
};
// solve system via determinants
double det = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1];
double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1];
double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2];
double facB = detB / det;
double facC = detC / det;
return facA *pow(x, 2.0) + facB *x + facC;
}
void ProjectileMissile::show(Canvas & canvas)
{
logAnim->info("Projectile rendering, %d / %d", step, steps);
size_t group = reverse ? 1 : 0;
auto image = animation->getImage(frameNum, group, true);
if(image)
{
float progress = float(step) / steps;
Point pos {
CSDL_Ext::lerp(from.x, dest.x, progress) - image->width() / 2,
CSDL_Ext::lerp(from.y, dest.y, progress) - image->height() / 2,
};
canvas.draw(image, pos);
}
++step;
}
void ProjectileAnimatedMissile::show(Canvas & canvas)
{
ProjectileMissile::show(canvas);
frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
size_t animationSize = animation->size(reverse ? 1 : 0);
while (frameProgress > animationSize)
frameProgress -= animationSize;
frameNum = std::floor(frameProgress);
}
void ProjectileCatapult::show(Canvas & canvas)
{
auto image = animation->getImage(frameNum, 0, true);
if(image)
{
float progress = float(step) / steps;
int posX = CSDL_Ext::lerp(from.x, dest.x, progress);
int posY = calculateCatapultParabolaY(from, dest, posX);
Point pos(posX, posY);
canvas.draw(image, pos);
frameNum = (frameNum + 1) % animation->size(0);
}
++step;
}
void ProjectileRay::show(Canvas & canvas)
{
float progress = float(step) / steps;
Point curr {
CSDL_Ext::lerp(from.x, dest.x, progress),
CSDL_Ext::lerp(from.y, dest.y, progress),
};
Point length = curr - from;
//select axis to draw ray on, we want angle to be less than 45 degrees so individual sub-rays won't overlap each other
if (std::abs(length.x) > std::abs(length.y)) // draw in horizontal axis
{
int y1 = from.y - rayConfig.size() / 2;
int y2 = curr.y - rayConfig.size() / 2;
int x1 = from.x;
int x2 = curr.x;
for (size_t i = 0; i < rayConfig.size(); ++i)
{
auto ray = rayConfig[i];
SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1};
SDL_Color endColor { ray.r2, ray.g2, ray.b2, ray.a2};
canvas.drawLine(Point(x1, y1 + i), Point(x2, y2+i), beginColor, endColor);
}
}
else // draw in vertical axis
{
int x1 = from.x - rayConfig.size() / 2;
int x2 = curr.x - rayConfig.size() / 2;
int y1 = from.y;
int y2 = curr.y;
for (size_t i = 0; i < rayConfig.size(); ++i)
{
auto ray = rayConfig[i];
SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1};
SDL_Color endColor { ray.r2, ray.g2, ray.b2, ray.a2};
canvas.drawLine(Point(x1 + i, y1), Point(x2 + i, y2), beginColor, endColor);
}
}
++step;
}
BattleProjectileController::BattleProjectileController(BattleInterface & owner):
owner(owner)
{}
const CCreature & BattleProjectileController::getShooter(const CStack * stack) const
{
const CCreature * creature = stack->getCreature();
if(creature->idNumber == CreatureID::ARROW_TOWERS)
creature = owner.siegeController->getTurretCreature();
if(creature->animation.missleFrameAngles.empty())
{
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->nameSing);
creature = CGI->creh->objects[CreatureID::ARCHER];
}
return *creature;
}
bool BattleProjectileController::stackUsesRayProjectile(const CStack * stack) const
{
return !getShooter(stack).animation.projectileRay.empty();
}
bool BattleProjectileController::stackUsesMissileProjectile(const CStack * stack) const
{
return !getShooter(stack).animation.projectileImageName.empty();
}
void BattleProjectileController::initStackProjectile(const CStack * stack)
{
if (!stackUsesMissileProjectile(stack))
return;
const CCreature & creature = getShooter(stack);
projectilesCache[creature.animation.projectileImageName] = createProjectileImage(creature.animation.projectileImageName);
}
std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const std::string & path )
{
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(path);
projectile->preload();
if(projectile->size(1) != 0)
logAnim->error("Expected empty group 1 in stack projectile");
else
projectile->createFlippedGroup(0, 1);
return projectile;
}
std::shared_ptr<CAnimation> BattleProjectileController::getProjectileImage(const CStack * stack)
{
const CCreature & creature = getShooter(stack);
std::string imageName = creature.animation.projectileImageName;
if (!projectilesCache.count(imageName))
initStackProjectile(stack);
return projectilesCache[imageName];
}
void BattleProjectileController::emitStackProjectile(const CStack * stack)
{
int stackID = stack ? stack->ID : -1;
for (auto projectile : projectiles)
{
if ( !projectile->playing && projectile->shooterID == stackID)
{
projectile->playing = true;
return;
}
}
}
void BattleProjectileController::showProjectiles(Canvas & canvas)
{
for ( auto projectile: projectiles)
{
if ( projectile->playing )
projectile->show(canvas);
}
vstd::erase_if(projectiles, [&](const std::shared_ptr<ProjectileBase> & projectile){
return projectile->step > projectile->steps;
});
}
bool BattleProjectileController::hasActiveProjectile(const CStack * stack) const
{
int stackID = stack ? stack->ID : -1;
for(auto const & instance : projectiles)
{
if(instance->shooterID == stackID)
{
return true;
}
}
return false;
}
int BattleProjectileController::computeProjectileFlightTime( Point from, Point dest, double animSpeed)
{
double distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y);
double distance = sqrt(distanceSquared);
int steps = std::round(distance / animSpeed);
if (steps > 0)
return steps;
return 1;
}
int BattleProjectileController::computeProjectileFrameID( Point from, Point dest, const CStack * stack)
{
const CCreature & creature = getShooter(stack);
auto & angles = creature.animation.missleFrameAngles;
auto animation = getProjectileImage(stack);
// only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used
size_t maxFrame = std::min<size_t>(angles.size(), animation->size(0));
assert(maxFrame > 0);
double projectileAngle = -atan2(dest.y - from.y, std::abs(dest.x - from.x));
// values in angles array indicate position from which this frame was rendered, in degrees.
// possible range is 90 ... -90, where projectile for +90 will be used for shooting upwards, +0 for shots towards right and -90 for downwards shots
// find frame that has closest angle to one that we need for this shot
int bestID = 0;
double bestDiff = fabs( angles[0] / 180 * M_PI - projectileAngle );
for (int i=1; i<maxFrame; i++)
{
double currentDiff = fabs( angles[i] / 180 * M_PI - projectileAngle );
if (currentDiff < bestDiff)
{
bestID = i;
bestDiff = currentDiff;
}
}
return bestID;
}
void BattleProjectileController::createCatapultProjectile(const CStack * shooter, Point from, Point dest)
{
auto catapultProjectile = new ProjectileCatapult();
catapultProjectile->animation = getProjectileImage(shooter);
catapultProjectile->frameNum = 0;
catapultProjectile->step = 0;
catapultProjectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
catapultProjectile->from = from;
catapultProjectile->dest = dest;
catapultProjectile->shooterID = shooter->ID;
catapultProjectile->step = 0;
catapultProjectile->playing = false;
projectiles.push_back(std::shared_ptr<ProjectileBase>(catapultProjectile));
}
void BattleProjectileController::createProjectile(const CStack * shooter, Point from, Point dest)
{
const CCreature & shooterInfo = getShooter(shooter);
std::shared_ptr<ProjectileBase> projectile;
if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter))
{
logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo.nameSing);
}
if (stackUsesRayProjectile(shooter))
{
auto rayProjectile = new ProjectileRay();
projectile.reset(rayProjectile);
rayProjectile->rayConfig = shooterInfo.animation.projectileRay;
}
else if (stackUsesMissileProjectile(shooter))
{
auto missileProjectile = new ProjectileMissile();
projectile.reset(missileProjectile);
missileProjectile->animation = getProjectileImage(shooter);
missileProjectile->reverse = !owner.stacksController->facingRight(shooter);
missileProjectile->frameNum = computeProjectileFrameID(from, dest, shooter);
}
projectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
projectile->from = from;
projectile->dest = dest;
projectile->shooterID = shooter->ID;
projectile->step = 0;
projectile->playing = false;
projectiles.push_back(projectile);
}
void BattleProjectileController::createSpellProjectile(const CStack * shooter, Point from, Point dest, const CSpell * spell)
{
double projectileAngle = std::abs(atan2(dest.x - from.x, dest.y - from.y));
std::string animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
assert(!animToDisplay.empty());
if(!animToDisplay.empty())
{
auto projectile = new ProjectileAnimatedMissile();
projectile->animation = createProjectileImage(animToDisplay);
projectile->frameProgress = 0;
projectile->frameNum = 0;
projectile->reverse = from.x > dest.x;
projectile->from = from;
projectile->dest = dest;
projectile->shooterID = shooter ? shooter->ID : -1;
projectile->step = 0;
projectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getSpellEffectSpeed());
projectile->playing = false;
projectiles.push_back(std::shared_ptr<ProjectileBase>(projectile));
}
}

View File

@ -0,0 +1,118 @@
/*
* BattleSiegeController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/CCreatureHandler.h"
#include "../gui/Geometries.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
class CSpell;
VCMI_LIB_NAMESPACE_END
struct Point;
class CAnimation;
class Canvas;
class BattleInterface;
/// Base class for projectiles
struct ProjectileBase
{
virtual ~ProjectileBase() = default;
virtual void show(Canvas & canvas) = 0;
Point from; // initial position on the screen
Point dest; // target position on the screen
int step; // current step counter
int steps; // total number of steps/frames to show
int shooterID; // ID of shooter stack
bool playing; // if set to true, projectile animation is playing, e.g. flying to target
};
/// Projectile for most shooters - render pre-selected frame moving in straight line from origin to destination
struct ProjectileMissile : ProjectileBase
{
void show(Canvas & canvas) override;
std::shared_ptr<CAnimation> animation;
int frameNum; // frame to display from projectile animation
bool reverse; // if true, projectile will be flipped by vertical axis
};
/// Projectile for spell - render animation moving in straight line from origin to destination
struct ProjectileAnimatedMissile : ProjectileMissile
{
void show(Canvas & canvas) override;
float frameProgress;
};
/// Projectile for catapult - render spinning projectile moving at parabolic trajectory to its destination
struct ProjectileCatapult : ProjectileBase
{
void show(Canvas & canvas) override;
std::shared_ptr<CAnimation> animation;
int frameNum; // frame to display from projectile animation
};
/// Projectile for mages/evil eye - render ray expanding from origin position to destination
struct ProjectileRay : ProjectileBase
{
void show(Canvas & canvas) override;
std::vector<CCreature::CreatureAnimation::RayColor> rayConfig;
};
/// Class that manages all ongoing projectiles in the game
/// ... even though in H3 only 1 projectile can be on screen at any point of time
class BattleProjectileController
{
BattleInterface & owner;
/// all projectiles loaded during current battle
std::map<std::string, std::shared_ptr<CAnimation>> projectilesCache;
/// projectiles currently flying on battlefield
std::vector<std::shared_ptr<ProjectileBase>> projectiles;
std::shared_ptr<CAnimation> getProjectileImage(const CStack * stack);
std::shared_ptr<CAnimation> createProjectileImage(const std::string & path );
void initStackProjectile(const CStack * stack);
bool stackUsesRayProjectile(const CStack * stack) const;
bool stackUsesMissileProjectile(const CStack * stack) const;
void showProjectile(Canvas & canvas, std::shared_ptr<ProjectileBase> projectile);
const CCreature & getShooter(const CStack * stack) const;
int computeProjectileFrameID( Point from, Point dest, const CStack * stack);
int computeProjectileFlightTime( Point from, Point dest, double speed);
public:
BattleProjectileController(BattleInterface & owner);
/// renders all currently active projectiles
void showProjectiles(Canvas & canvas);
/// returns true if stack has projectile that is yet to hit target
bool hasActiveProjectile(const CStack * stack) const;
/// starts rendering previously created projectile
void emitStackProjectile(const CStack * stack);
/// creates (but not emits!) projectile and initializes it based on parameters
void createProjectile(const CStack * shooter, Point from, Point dest);
void createSpellProjectile(const CStack * shooter, Point from, Point dest, const CSpell * spell);
void createCatapultProjectile(const CStack * shooter, Point from, Point dest);
};

View File

@ -0,0 +1,71 @@
/*
* BattleFieldController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleRenderer.h"
#include "BattleInterface.h"
#include "BattleEffectsController.h"
#include "BattleSiegeController.h"
#include "BattleStacksController.h"
#include "BattleObstacleController.h"
void BattleRenderer::collectObjects()
{
owner.collectRenderableObjects(*this);
owner.effectsController->collectRenderableObjects(*this);
owner.obstacleController->collectRenderableObjects(*this);
owner.stacksController->collectRenderableObjects(*this);
if (owner.siegeController)
owner.siegeController->collectRenderableObjects(*this);
}
void BattleRenderer::sortObjects()
{
auto getRow = [](const RenderableInstance & object) -> int
{
if (object.tile.isValid())
return object.tile.getY();
if ( object.tile == BattleHex::HEX_BEFORE_ALL )
return -1;
assert( object.tile == BattleHex::HEX_AFTER_ALL || object.tile == BattleHex::INVALID);
return GameConstants::BFIELD_HEIGHT;
};
std::stable_sort(objects.begin(), objects.end(), [&](const RenderableInstance & left, const RenderableInstance & right){
if ( getRow(left) != getRow(right))
return getRow(left) < getRow(right);
return left.layer < right.layer;
});
}
void BattleRenderer::renderObjects(BattleRenderer::RendererRef targetCanvas)
{
for (auto const & object : objects)
object.functor(targetCanvas);
}
BattleRenderer::BattleRenderer(BattleInterface & owner):
owner(owner)
{
}
void BattleRenderer::insert(EBattleFieldLayer layer, BattleHex tile, BattleRenderer::RenderFunctor functor)
{
objects.push_back({functor, layer, tile});
}
void BattleRenderer::execute(BattleRenderer::RendererRef targetCanvas)
{
collectObjects();
sortObjects();
renderObjects(targetCanvas);
}

View File

@ -0,0 +1,54 @@
/*
* BattleFieldController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/BattleHex.h"
class Canvas;
class BattleInterface;
enum class EBattleFieldLayer {
// confirmed ordering requirements:
OBSTACLES = 0,
CORPSES = 0,
WALLS = 1,
HEROES = 1,
STACKS = 1, // after corpses, obstacles
BATTLEMENTS = 2, // after stacks
STACK_AMOUNTS = 2, // after stacks, obstacles, corpses
EFFECTS = 3, // after obstacles, battlements
};
class BattleRenderer
{
public:
using RendererRef = Canvas &;
using RenderFunctor = std::function<void(RendererRef)>;
private:
BattleInterface & owner;
struct RenderableInstance
{
RenderFunctor functor;
EBattleFieldLayer layer;
BattleHex tile;
};
std::vector<RenderableInstance> objects;
void collectObjects();
void sortObjects();
void renderObjects(RendererRef targetCanvas);
public:
BattleRenderer(BattleInterface & owner);
void insert(EBattleFieldLayer layer, BattleHex tile, RenderFunctor functor);
void execute(RendererRef targetCanvas);
};

View File

@ -0,0 +1,368 @@
/*
* BattleSiegeController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleSiegeController.h"
#include "BattleAnimationClasses.h"
#include "BattleInterface.h"
#include "BattleInterfaceClasses.h"
#include "BattleStacksController.h"
#include "BattleFieldController.h"
#include "BattleRenderer.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../../CCallback.h"
#include "../../lib/NetPacks.h"
#include "../../lib/CStack.h"
#include "../../lib/mapObjects/CGTownInstance.h"
std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const
{
auto getImageIndex = [&]() -> int
{
switch (state)
{
case EWallState::INTACT :
return 1;
case EWallState::DAMAGED :
// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
if(what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
return 1;
else
return 2;
case EWallState::DESTROYED :
if (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
return 2;
else
return 3;
}
return 1;
};
const std::string & prefix = town->town->clientInfo.siegePrefix;
std::string addit = boost::lexical_cast<std::string>(getImageIndex());
switch(what)
{
case EWallVisual::BACKGROUND_WALL:
{
switch(town->town->faction->index)
{
case ETownType::RAMPART:
case ETownType::NECROPOLIS:
case ETownType::DUNGEON:
case ETownType::STRONGHOLD:
return prefix + "TPW1.BMP";
default:
return prefix + "TPWL.BMP";
}
}
case EWallVisual::KEEP:
return prefix + "MAN" + addit + ".BMP";
case EWallVisual::BOTTOM_TOWER:
return prefix + "TW1" + addit + ".BMP";
case EWallVisual::BOTTOM_WALL:
return prefix + "WA1" + addit + ".BMP";
case EWallVisual::WALL_BELLOW_GATE:
return prefix + "WA3" + addit + ".BMP";
case EWallVisual::WALL_OVER_GATE:
return prefix + "WA4" + addit + ".BMP";
case EWallVisual::UPPER_WALL:
return prefix + "WA6" + addit + ".BMP";
case EWallVisual::UPPER_TOWER:
return prefix + "TW2" + addit + ".BMP";
case EWallVisual::GATE:
return prefix + "DRW" + addit + ".BMP";
case EWallVisual::GATE_ARCH:
return prefix + "ARCH.BMP";
case EWallVisual::BOTTOM_STATIC_WALL:
return prefix + "WA2.BMP";
case EWallVisual::UPPER_STATIC_WALL:
return prefix + "WA5.BMP";
case EWallVisual::MOAT:
return prefix + "MOAT.BMP";
case EWallVisual::MOAT_BANK:
return prefix + "MLIP.BMP";
case EWallVisual::KEEP_BATTLEMENT:
return prefix + "MANC.BMP";
case EWallVisual::BOTTOM_BATTLEMENT:
return prefix + "TW1C.BMP";
case EWallVisual::UPPER_BATTLEMENT:
return prefix + "TW2C.BMP";
default:
return "";
}
}
void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what, const Point & offset)
{
auto & ci = town->town->clientInfo;
auto const & pos = ci.siegePositions[what];
if ( wallPieceImages[what])
canvas.draw(wallPieceImages[what], offset + Point(pos.x, pos.y));
}
std::string BattleSiegeController::getBattleBackgroundName() const
{
const std::string & prefix = town->town->clientInfo.siegePrefix;
return prefix + "BACK.BMP";
}
bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
{
//FIXME: use this instead of buildings test?
//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
switch (what)
{
case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER;
case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS;
case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED;
case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED;
case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED;
default: return true;
}
}
BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual what) const
{
static const std::array<BattleHex, 18> wallsPositions = {
BattleHex::INVALID, // BACKGROUND, // handled separately
BattleHex::HEX_BEFORE_ALL, // BACKGROUND_WALL,
135, // KEEP,
BattleHex::HEX_AFTER_ALL, // BOTTOM_TOWER,
182, // BOTTOM_WALL,
130, // WALL_BELLOW_GATE,
78, // WALL_OVER_GATE,
12, // UPPER_WALL,
BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER,
BattleHex::HEX_BEFORE_ALL, // GATE, // 94
112, // GATE_ARCH,
165, // BOTTOM_STATIC_WALL,
45, // UPPER_STATIC_WALL,
BattleHex::INVALID, // MOAT, // printed as absolute obstacle
BattleHex::INVALID, // MOAT_BANK, // printed as absolute obstacle
135, // KEEP_BATTLEMENT,
BattleHex::HEX_AFTER_ALL, // BOTTOM_BATTLEMENT,
BattleHex::HEX_BEFORE_ALL, // UPPER_BATTLEMENT,
};
return wallsPositions[what];
}
BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTownInstance *siegeTown):
owner(owner),
town(siegeTown)
{
assert(owner.fieldController.get() == nullptr); // must be created after this
for (int g = 0; g < wallPieceImages.size(); ++g)
{
if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state
continue;
if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
continue;
wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::INTACT));
}
}
const CCreature *BattleSiegeController::getTurretCreature() const
{
return CGI->creh->objects[town->town->clientInfo.siegeShooter];
}
Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const
{
// Turret positions are read out of the config/wall_pos.txt
int posID = 0;
switch (position)
{
case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature
posID = EWallVisual::CREATURE_KEEP;
break;
case BattleHex::CASTLE_BOTTOM_TOWER: // bottom creature
posID = EWallVisual::CREATURE_BOTTOM_TOWER;
break;
case BattleHex::CASTLE_UPPER_TOWER: // upper creature
posID = EWallVisual::CREATURE_UPPER_TOWER;
break;
}
if (posID != 0)
{
Point result = owner.pos.topLeft();
result.x += town->town->clientInfo.siegePositions[posID].x;
result.y += town->town->clientInfo.siegePositions[posID].y;
return result;
}
assert(0);
return Point(0,0);
}
void BattleSiegeController::gateStateChanged(const EGateState state)
{
auto oldState = owner.curInt->cb->battleGetGateState();
bool playSound = false;
auto stateId = EWallState::NONE;
switch(state)
{
case EGateState::CLOSED:
if (oldState != EGateState::BLOCKED)
playSound = true;
break;
case EGateState::BLOCKED:
if (oldState != EGateState::CLOSED)
playSound = true;
break;
case EGateState::OPENED:
playSound = true;
stateId = EWallState::DAMAGED;
break;
case EGateState::DESTROYED:
stateId = EWallState::DESTROYED;
break;
}
if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)
wallPieceImages[EWallVisual::GATE] = nullptr;
if (stateId != EWallState::NONE)
wallPieceImages[EWallVisual::GATE] = IImage::createFromFile(getWallPieceImageName(EWallVisual::GATE, stateId));
if (playSound)
CCS->soundh->playSound(soundBase::DRAWBRG);
}
void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas, const Point & offset)
{
if (getWallPieceExistance(EWallVisual::MOAT))
showWallPiece(canvas, EWallVisual::MOAT, offset);
if (getWallPieceExistance(EWallVisual::MOAT_BANK))
showWallPiece(canvas, EWallVisual::MOAT_BANK, offset);
}
BattleHex BattleSiegeController::getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const
{
switch(wallPiece)
{
case EWallVisual::KEEP_BATTLEMENT: return BattleHex::CASTLE_CENTRAL_TOWER;
case EWallVisual::BOTTOM_BATTLEMENT: return BattleHex::CASTLE_BOTTOM_TOWER;
case EWallVisual::UPPER_BATTLEMENT: return BattleHex::CASTLE_UPPER_TOWER;
}
assert(0);
return BattleHex::INVALID;
}
const CStack * BattleSiegeController::getTurretStack(EWallVisual::EWallVisual wallPiece) const
{
for (auto & stack : owner.curInt->cb->battleGetAllStacks(true))
{
if ( stack->initialPosition == getTurretBattleHex(wallPiece))
return stack;
}
assert(0);
return nullptr;
}
void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer)
{
for (int i = EWallVisual::WALL_FIRST; i <= EWallVisual::WALL_LAST; ++i)
{
auto wallPiece = EWallVisual::EWallVisual(i);
if ( !getWallPieceExistance(wallPiece))
continue;
if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)
continue;
if (wallPiece == EWallVisual::KEEP_BATTLEMENT ||
wallPiece == EWallVisual::BOTTOM_BATTLEMENT ||
wallPiece == EWallVisual::UPPER_BATTLEMENT)
{
renderer.insert( EBattleFieldLayer::STACKS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
owner.stacksController->showStack(canvas, getTurretStack(wallPiece));
});
renderer.insert( EBattleFieldLayer::BATTLEMENTS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
showWallPiece(canvas, wallPiece, owner.pos.topLeft());
});
}
renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
showWallPiece(canvas, wallPiece, owner.pos.topLeft());
});
}
}
bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
{
if (owner.tacticsMode)
return false;
auto wallPart = owner.curInt->cb->battleHexToWallPart(hex);
if (!owner.curInt->cb->isWallPartPotentiallyAttackable(wallPart))
return false;
auto state = owner.curInt->cb->battleGetWallState(static_cast<int>(wallPart));
return state != EWallState::DESTROYED && state != EWallState::NONE;
}
void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
{
if (ca.attacker != -1)
{
const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker);
for (auto attackInfo : ca.attackedParts)
{
owner.stacksController->addNewAnim(new CCatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
}
}
else
{
std::vector<Point> positions;
//no attacker stack, assume spell-related (earthquake) - only hit animation
for (auto attackInfo : ca.attackedParts)
positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::WALLHIT, "SGEXPL.DEF", positions));
}
owner.waitForAnims();
for (auto attackInfo : ca.attackedParts)
{
int wallId = attackInfo.attackedPart + EWallVisual::DESTRUCTIBLE_FIRST;
//gate state changing handled separately
if (wallId == EWallVisual::GATE)
continue;
auto wallState = EWallState::EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart));
wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
}
}
const CGTownInstance *BattleSiegeController::getSiegedTown() const
{
return town;
}

View File

@ -0,0 +1,110 @@
/*
* BattleObstacleController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/GameConstants.h"
#include "../../lib/battle/BattleHex.h"
VCMI_LIB_NAMESPACE_BEGIN
struct CatapultAttack;
class CCreature;
class CStack;
class CGTownInstance;
VCMI_LIB_NAMESPACE_END
struct Point;
class Canvas;
class BattleInterface;
class BattleRenderer;
class IImage;
namespace EWallVisual
{
enum EWallVisual
{
BACKGROUND,
BACKGROUND_WALL,
KEEP,
BOTTOM_TOWER,
BOTTOM_WALL,
WALL_BELLOW_GATE,
WALL_OVER_GATE,
UPPER_WALL,
UPPER_TOWER,
GATE,
GATE_ARCH,
BOTTOM_STATIC_WALL,
UPPER_STATIC_WALL,
MOAT,
MOAT_BANK,
KEEP_BATTLEMENT,
BOTTOM_BATTLEMENT,
UPPER_BATTLEMENT,
CREATURE_KEEP,
CREATURE_BOTTOM_TOWER,
CREATURE_UPPER_TOWER,
WALL_FIRST = BACKGROUND_WALL,
WALL_LAST = UPPER_BATTLEMENT,
// these entries are mapped to EWallPart enum
DESTRUCTIBLE_FIRST = KEEP,
DESTRUCTIBLE_LAST = GATE,
};
}
class BattleSiegeController
{
BattleInterface & owner;
/// besieged town
const CGTownInstance *town;
/// sections of castle walls, in their currently visible state
std::array<std::shared_ptr<IImage>, EWallVisual::WALL_LAST + 1> wallPieceImages;
/// return URI for image for a wall piece
std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const;
/// returns BattleHex to which chosen wall piece is bound
BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
/// returns true if chosen wall piece should be present in current battle
bool getWallPieceExistance(EWallVisual::EWallVisual what) const;
void showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what, const Point & offset);
BattleHex getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const;
const CStack * getTurretStack(EWallVisual::EWallVisual wallPiece) const;
public:
BattleSiegeController(BattleInterface & owner, const CGTownInstance *siegeTown);
/// call-ins from server
void gateStateChanged(const EGateState state);
void stackIsCatapulting(const CatapultAttack & ca);
/// call-ins from other battle controllers
void showAbsoluteObstacles(Canvas & canvas, const Point & offset);
void collectRenderableObjects(BattleRenderer & renderer);
/// queries from other battle controllers
bool isAttackableByCatapult(BattleHex hex) const;
std::string getBattleBackgroundName() const;
const CCreature *getTurretCreature() const;
Point getTurretCreaturePosition( BattleHex position ) const;
const CGTownInstance *getSiegedTown() const;
};

View File

@ -0,0 +1,589 @@
/*
* BattleStacksController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BattleStacksController.h"
#include "BattleSiegeController.h"
#include "BattleInterfaceClasses.h"
#include "BattleInterface.h"
#include "BattleAnimationClasses.h"
#include "BattleFieldController.h"
#include "BattleEffectsController.h"
#include "BattleProjectileController.h"
#include "BattleControlPanel.h"
#include "BattleRenderer.h"
#include "CreatureAnimation.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Canvas.h"
#include "../../CCallback.h"
#include "../../lib/battle/BattleHex.h"
#include "../../lib/CGameState.h"
#include "../../lib/CStack.h"
#include "../../lib/CondSh.h"
static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnimation> anim)
{
std::shared_ptr<CreatureAnimation> animation = anim.lock();
if(!animation)
return;
if (animation->isIdle())
{
const CCreature *creature = stack->getCreature();
if (animation->framesInGroup(CCreatureAnim::MOUSEON) > 0)
{
if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets *10)
animation->playOnce(CCreatureAnim::MOUSEON);
else
animation->setType(CCreatureAnim::HOLDING);
}
else
{
animation->setType(CCreatureAnim::HOLDING);
}
}
// always reset callback
animation->onAnimationReset += std::bind(&onAnimationFinished, stack, anim);
}
BattleStacksController::BattleStacksController(BattleInterface & owner):
owner(owner),
activeStack(nullptr),
mouseHoveredStack(nullptr),
stackToActivate(nullptr),
selectedStack(nullptr),
stackCanCastSpell(false),
creatureSpellToCast(uint32_t(-1)),
animIDhelper(0)
{
//preparing graphics for displaying amounts of creatures
amountNormal = IImage::createFromFile("CMNUMWIN.BMP");
amountPositive = IImage::createFromFile("CMNUMWIN.BMP");
amountNegative = IImage::createFromFile("CMNUMWIN.BMP");
amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
static const ColorShifterMultiplyAndAddExcept shifterNormal ({150, 50, 255, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const ColorShifterMultiplyAndAddExcept shifterPositive({ 50, 255, 50, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const ColorShifterMultiplyAndAddExcept shifterNegative({255, 50, 50, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const ColorShifterMultiplyAndAddExcept shifterNeutral ({255, 255, 50, 255}, {0,0,0,0}, {255, 231, 132, 255});
amountNormal->adjustPalette(&shifterNormal);
amountPositive->adjustPalette(&shifterPositive);
amountNegative->adjustPalette(&shifterNegative);
amountEffNeutral->adjustPalette(&shifterNeutral);
std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
for(const CStack * s : stacks)
{
stackAdded(s);
}
}
BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack) const
{
if ( !stackAnimation.at(stack->ID)->isMoving())
return stack->getPosition();
if (stack->hasBonusOfType(Bonus::FLYING))
return BattleHex::HEX_AFTER_ALL;
for (auto & anim : currentAnimations)
{
// certainly ugly workaround but fixes quite annoying bug
// stack position will be updated only *after* movement is finished
// before this - stack is always at its initial position. Thus we need to find
// its current position. Which can be found only in this class
if (CStackMoveAnimation *move = dynamic_cast<CStackMoveAnimation*>(anim))
{
if (move->stack == stack)
return move->currentHex;
}
}
return stack->getPosition();
}
void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
{
auto stacks = owner.curInt->cb->battleGetAllStacks(false);
for (auto stack : stacks)
{
if (stackAnimation.find(stack->ID) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
continue;
//FIXME: hack to ignore ghost stacks
if ((stackAnimation[stack->ID]->getType() == CCreatureAnim::DEAD || stackAnimation[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
continue;
auto layer = stackAnimation[stack->ID]->isDead() ? EBattleFieldLayer::CORPSES : EBattleFieldLayer::STACKS;
auto location = getStackCurrentPosition(stack);
renderer.insert(layer, location, [this, stack]( BattleRenderer::RendererRef renderer ){
showStack(renderer, stack);
});
if (stackNeedsAmountBox(stack))
{
renderer.insert(EBattleFieldLayer::STACK_AMOUNTS, location, [this, stack]( BattleRenderer::RendererRef renderer ){
showStackAmountBox(renderer, stack);
});
}
}
}
void BattleStacksController::stackReset(const CStack * stack)
{
auto iter = stackAnimation.find(stack->ID);
if(iter == stackAnimation.end())
{
logGlobal->error("Unit %d have no animation", stack->ID);
return;
}
auto animation = iter->second;
if(stack->alive() && animation->isDeadOrDying())
animation->setType(CCreatureAnim::HOLDING);
static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0});
if (stack->isClone())
{
animation->shiftColor(&shifterClone);
}
//TODO: handle more cases
}
void BattleStacksController::stackAdded(const CStack * stack)
{
// Tower shooters have only their upper half visible
static const int turretCreatureAnimationHeight = 235;
stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
Point coords = getStackPositionAtHex(stack->getPosition(), stack);
if(stack->initialPosition < 0) //turret
{
assert(owner.siegeController);
const CCreature *turretCreature = owner.siegeController->getTurretCreature();
stackAnimation[stack->ID] = AnimationControls::getAnimation(turretCreature);
stackAnimation[stack->ID]->pos.h = turretCreatureAnimationHeight;
coords = owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
}
else
{
stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]);
stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight();
}
stackAnimation[stack->ID]->pos.x = coords.x;
stackAnimation[stack->ID]->pos.y = coords.y;
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
stackAnimation[stack->ID]->setType(CCreatureAnim::HOLDING);
}
void BattleStacksController::setActiveStack(const CStack *stack)
{
if (activeStack) // update UI
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder());
activeStack = stack;
if (activeStack) // update UI
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
owner.controlPanel->blockUI(activeStack == nullptr);
}
void BattleStacksController::setHoveredStack(const CStack *stack)
{
if ( stack == mouseHoveredStack )
return;
if (mouseHoveredStack)
stackAnimation[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getNoBorder());
// stack must be alive and not active (which uses gold border instead)
if (stack && stack->alive() && stack != activeStack)
{
mouseHoveredStack = stack;
if (mouseHoveredStack)
{
stackAnimation[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[mouseHoveredStack->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
stackAnimation[mouseHoveredStack->ID]->playOnce(CCreatureAnim::MOUSEON);
}
}
else
mouseHoveredStack = nullptr;
}
bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
{
BattleHex currentActionTarget;
if(owner.curInt->curAction)
{
auto target = owner.curInt->curAction->getTarget(owner.curInt->cb.get());
if(!target.empty())
currentActionTarget = target.at(0).hexValue;
}
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
return false;
if (!owner.battleActionsStarted) // do not perform any further checks since they are related to actions that will only occur after intro music
return true;
if(!stack->alive())
return false;
if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
return false;
for(auto anim : currentAnimations) //no matter what other conditions below are, hide box when creature is playing hit animation
{
auto hitAnimation = dynamic_cast<CDefenceAnimation*>(anim);
if(hitAnimation && (hitAnimation->stack->ID == stack->ID))
return false;
}
if(owner.curInt->curAction)
{
if(owner.curInt->curAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc)
{
if(owner.curInt->curAction->actionType == EActionType::WALK || owner.curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots
return false;
else if(owner.curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished
return false;
}
if(owner.curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target
return false;
}
return true;
}
std::shared_ptr<IImage> BattleStacksController::getStackAmountBox(const CStack * stack)
{
std::vector<si32> activeSpells = stack->activeSpells();
if ( activeSpells.empty())
return amountNormal;
int effectsPositivness = 0;
for ( auto const & spellID : activeSpells)
effectsPositivness += CGI->spellh->objects.at(spellID)->positiveness;
if (effectsPositivness > 0)
return amountPositive;
if (effectsPositivness < 0)
return amountNegative;
return amountEffNeutral;
}
void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack * stack)
{
//blitting amount background box
auto amountBG = getStackAmountBox(stack);
const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1;
const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
const BattleHex nextPos = stack->getPosition() + sideShift;
const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
const bool moveInside = !edge && !owner.fieldController->stackCountOutsideHex(nextPos);
int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
(stack->doubleWide() ? 44 : 0) * sideShift +
(moveInside ? amountBG->width() + 10 : 0) * reverseSideShift;
int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15);
canvas.draw(amountBG, stackAnimation[stack->ID]->pos.topLeft() + Point(xAdd, yAdd));
//blitting amount
Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd);
canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, makeNumberShort(stack->getCount()));
}
void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
{
stackAnimation[stack->ID]->nextFrame(canvas, facingRight(stack)); // do actual blit
stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
}
void BattleStacksController::updateBattleAnimations()
{
for (auto & elem : currentAnimations)
{
if (!elem)
continue;
if (elem->isInitialized())
elem->nextFrame();
else
elem->tryInitialize();
}
bool hadAnimations = !currentAnimations.empty();
vstd::erase(currentAnimations, nullptr);
if (hadAnimations && currentAnimations.empty())
{
//anims ended
owner.controlPanel->blockUI(activeStack == nullptr);
owner.animsAreDisplayed.setn(false);
}
}
void BattleStacksController::addNewAnim(CBattleAnimation *anim)
{
currentAnimations.push_back(anim);
owner.animsAreDisplayed.setn(true);
}
void BattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
{
stackToActivate = stack;
owner.waitForAnims();
if (stackToActivate) //during waiting stack may have gotten activated through show
owner.activateStack();
}
void BattleStacksController::stackRemoved(uint32_t stackID)
{
if (getActiveStack() && getActiveStack()->ID == stackID)
{
BattleAction *action = new BattleAction();
action->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
action->actionType = EActionType::CANCEL;
action->stackNumber = getActiveStack()->ID;
owner.givenCommand.setn(action);
setActiveStack(nullptr);
}
}
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
{
for(auto & attackedInfo : attackedInfos)
{
//if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
addNewAnim(new CDefenceAnimation(attackedInfo, owner));
if(attackedInfo.rebirth)
{
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition()); //TODO: play reverse death animation
}
}
owner.waitForAnims();
for (auto & attackedInfo : attackedInfos)
{
if (attackedInfo.rebirth)
stackAnimation[attackedInfo.defender->ID]->setType(CCreatureAnim::HOLDING);
if (attackedInfo.cloneKilled)
stackRemoved(attackedInfo.defender->ID);
}
}
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{
addNewAnim(new CMovementAnimation(owner, stack, destHex, distance));
owner.waitForAnims();
}
void BattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
{
if (shooting)
{
addNewAnim(new CShootingAnimation(owner, attacker, dest, attacked));
}
else
{
addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, attacked));
}
//waitForAnims();
}
bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const
{
Point begPosition = getStackPositionAtHex(oldPos,stack);
Point endPosition = getStackPositionAtHex(nextHex, stack);
if((begPosition.x > endPosition.x) && facingRight(stack))
return true;
else if((begPosition.x < endPosition.x) && !facingRight(stack))
return true;
return false;
}
void BattleStacksController::endAction(const BattleAction* action)
{
//check if we should reverse stacks
//for some strange reason, it's not enough
TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
for (const CStack *s : stacks)
{
bool shouldFaceRight = s && s->side == BattleSide::ATTACKER;
if (s && facingRight(s) != shouldFaceRight && s->alive() && stackAnimation[s->ID]->isIdle())
{
addNewAnim(new CReverseAnimation(owner, s, s->getPosition(), false));
}
}
}
void BattleStacksController::startAction(const BattleAction* action)
{
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
setHoveredStack(nullptr);
auto actionTarget = action->getTarget(owner.curInt->cb.get());
if(action->actionType == EActionType::WALK
|| (action->actionType == EActionType::WALK_AND_ATTACK && actionTarget.at(0).hexValue != stack->getPosition()))
{
assert(stack);
owner.moveStarted = true;
if (stackAnimation[action->stackNumber]->framesInGroup(CCreatureAnim::MOVE_START))
addNewAnim(new CMovementStartAnimation(owner, stack));
//if(shouldRotate(stack, stack->getPosition(), actionTarget.at(0).hexValue))
// addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
}
}
void BattleStacksController::activateStack()
{
if ( !currentAnimations.empty())
return;
if ( !stackToActivate)
return;
owner.trySetActivePlayer(stackToActivate->owner);
setActiveStack(stackToActivate);
stackToActivate = nullptr;
const CStack * s = getActiveStack();
if(!s)
return;
//set casting flag to true if creature can use it to not check it every time
const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
if(s->canCast() && (spellcaster || randomSpellcaster))
{
stackCanCastSpell = true;
if(randomSpellcaster)
creatureSpellToCast = -1; //spell will be set later on cast
else
creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
//TODO: what if creature can cast BOTH random genie spell and aimed spell?
//TODO: faerie dragon type spell should be selected by server
}
else
{
stackCanCastSpell = false;
creatureSpellToCast = -1;
}
}
void BattleStacksController::setSelectedStack(const CStack *stack)
{
selectedStack = stack;
}
const CStack* BattleStacksController::getSelectedStack() const
{
return selectedStack;
}
const CStack* BattleStacksController::getActiveStack() const
{
return activeStack;
}
bool BattleStacksController::facingRight(const CStack * stack) const
{
return stackFacingRight.at(stack->ID);
}
bool BattleStacksController::activeStackSpellcaster()
{
return stackCanCastSpell;
}
SpellID BattleStacksController::activeStackSpellToCast()
{
if (!stackCanCastSpell)
return SpellID::NONE;
return SpellID(creatureSpellToCast);
}
Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CStack * stack) const
{
Point ret(-500, -500); //returned value
if(stack && stack->initialPosition < 0) //creatures in turrets
return owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
static const Point basePos(-190, -139); // position of creature in topleft corner
static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left
ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX();
ret.y = basePos.y + 42 * hexNum.getY();
if (stack)
{
if(facingRight(stack))
ret.x += imageShiftX;
else
ret.x -= imageShiftX;
//shifting position for double - hex creatures
if(stack->doubleWide())
{
if(stack->side == BattleSide::ATTACKER)
{
if(facingRight(stack))
ret.x -= 44;
}
else
{
if(!facingRight(stack))
ret.x += 44;
}
}
}
//returning
return ret + owner.pos.topLeft();
}

View File

@ -0,0 +1,121 @@
/*
* BattleStacksController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../gui/Geometries.h"
VCMI_LIB_NAMESPACE_BEGIN
struct BattleHex;
class BattleAction;
class CStack;
class SpellID;
VCMI_LIB_NAMESPACE_END
struct StackAttackedInfo;
class Canvas;
class BattleInterface;
class CBattleAnimation;
class CreatureAnimation;
class CBattleAnimation;
class BattleRenderer;
class IImage;
/// Class responsible for handling stacks in battle
/// Handles ordering of stacks animation
/// As well as rendering of stacks, their amount boxes
/// And any other effect applied to stacks
class BattleStacksController
{
BattleInterface & owner;
std::shared_ptr<IImage> amountNormal;
std::shared_ptr<IImage> amountNegative;
std::shared_ptr<IImage> amountPositive;
std::shared_ptr<IImage> amountEffNeutral;
/// currently displayed animations <anim, initialized>
std::vector<CBattleAnimation *> currentAnimations;
/// animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
std::map<int32_t, std::shared_ptr<CreatureAnimation>> stackAnimation;
/// <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
std::map<int, bool> stackFacingRight;
/// number of active stack; nullptr - no one
const CStack *activeStack;
/// stack below mouse pointer, used for border animation
const CStack *mouseHoveredStack;
///when animation is playing, we should wait till the end to make the next stack active; nullptr of none
const CStack *stackToActivate;
/// stack that was selected for multi-target spells - Teleport / Sacrifice
const CStack *selectedStack;
/// if true, active stack could possibly cast some target spell
bool stackCanCastSpell;
si32 creatureSpellToCast;
/// for giving IDs for animations
ui32 animIDhelper;
bool stackNeedsAmountBox(const CStack * stack) const;
void showStackAmountBox(Canvas & canvas, const CStack * stack);
BattleHex getStackCurrentPosition(const CStack * stack) const;
std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
public:
BattleStacksController(BattleInterface & owner);
bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const;
bool facingRight(const CStack * stack) const;
void stackReset(const CStack * stack);
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void startAction(const BattleAction* action);
void endAction(const BattleAction* action);
bool activeStackSpellcaster();
SpellID activeStackSpellToCast();
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
void setActiveStack(const CStack *stack);
void setHoveredStack(const CStack *stack);
void setSelectedStack(const CStack *stack);
void showAliveStack(Canvas & canvas, const CStack * stack);
void showStack(Canvas & canvas, const CStack * stack);
void collectRenderableObjects(BattleRenderer & renderer);
void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
void updateBattleAnimations();
const CStack* getActiveStack() const;
const CStack* getSelectedStack() const;
/// returns position of animation needed to place stack in specific hex
Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const;
friend class CBattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
};

File diff suppressed because it is too large Load Diff

View File

@ -1,262 +0,0 @@
/*
* CBattleAnimations.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/BattleHex.h"
#include "../widgets/Images.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
VCMI_LIB_NAMESPACE_END
class CBattleInterface;
class CCreatureAnimation;
struct CatapultProjectileInfo;
struct StackAttackedInfo;
/// Base class of battle animations
class CBattleAnimation
{
protected:
CBattleInterface * owner;
public:
virtual bool init() = 0; //to be called - if returned false, call again until returns true
virtual void nextFrame() {} //call every new frame
virtual void endAnim(); //to be called mostly internally; in this class it removes animation from pendingAnims list
virtual ~CBattleAnimation();
bool isEarliest(bool perStackConcurrency); //determines if this animation is earliest of all
ui32 ID; //unique identifier
CBattleAnimation(CBattleInterface * _owner);
};
/// Sub-class which is responsible for managing the battle stack animation.
class CBattleStackAnimation : public CBattleAnimation
{
public:
std::shared_ptr<CCreatureAnimation> myAnim; //animation for our stack, managed by CBattleInterface
const CStack * stack; //id of stack whose animation it is
CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack);
void shiftColor(const ColorShifter * shifter);
};
/// This class is responsible for managing the battle attack animation
class CAttackAnimation : public CBattleStackAnimation
{
bool soundPlayed;
protected:
BattleHex dest; //attacked hex
bool shooting;
CCreatureAnim::EAnimType group; //if shooting is true, print this animation group
const CStack *attackedStack;
const CStack *attackingStack;
int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
public:
void nextFrame() override;
void endAnim() override;
bool checkInitialConditions();
CAttackAnimation(CBattleInterface *_owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
};
/// Animation of a defending unit
class CDefenceAnimation : public CBattleStackAnimation
{
CCreatureAnim::EAnimType getMyAnimType();
std::string getMySound();
void startAnimation();
const CStack * attacker; //attacking stack
bool rangedAttack; //if true, stack has been attacked by shooting
bool killed; //if true, stack has been killed
float timeToWait; // for how long this animation should be paused
public:
bool init() override;
void nextFrame() override;
void endAnim() override;
CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner);
virtual ~CDefenceAnimation(){};
};
class CDummyAnimation : public CBattleAnimation
{
private:
int counter;
int howMany;
public:
bool init() override;
void nextFrame() override;
void endAnim() override;
CDummyAnimation(CBattleInterface * _owner, int howManyFrames);
virtual ~CDummyAnimation(){}
};
/// Hand-to-hand attack
class CMeleeAttackAnimation : public CAttackAnimation
{
public:
bool init() override;
void endAnim() override;
CMeleeAttackAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked);
virtual ~CMeleeAttackAnimation(){};
};
/// Move animation of a creature
class CMovementAnimation : public CBattleStackAnimation
{
private:
std::vector<BattleHex> destTiles; //full path, includes already passed hexes
ui32 curentMoveIndex; // index of nextHex in destTiles
BattleHex oldPos; //position of stack before move
double begX, begY; // starting position
double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
double timeToMove; // full length of movement animation
double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
public:
BattleHex nextHex; // next hex, to which creature move right now
bool init() override;
void nextFrame() override;
void endAnim() override;
CMovementAnimation(CBattleInterface *_owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
virtual ~CMovementAnimation(){};
};
/// Move end animation of a creature
class CMovementEndAnimation : public CBattleStackAnimation
{
private:
BattleHex destinationTile;
public:
bool init() override;
void endAnim() override;
CMovementEndAnimation(CBattleInterface * _owner, const CStack * _stack, BattleHex destTile);
virtual ~CMovementEndAnimation(){};
};
/// Move start animation of a creature
class CMovementStartAnimation : public CBattleStackAnimation
{
public:
bool init() override;
void endAnim() override;
CMovementStartAnimation(CBattleInterface * _owner, const CStack * _stack);
virtual ~CMovementStartAnimation(){};
};
/// Class responsible for animation of stack chaning direction (left <-> right)
class CReverseAnimation : public CBattleStackAnimation
{
BattleHex hex;
public:
bool priority; //true - high, false - low
bool init() override;
static void rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex);
void setupSecondPart();
void endAnim() override;
CReverseAnimation(CBattleInterface * _owner, const CStack * stack, BattleHex dest, bool _priority);
virtual ~CReverseAnimation(){};
};
/// Small struct which contains information about the position and the velocity of a projectile
struct ProjectileInfo
{
double x0, y0; //initial position on the screen
double x, y; //position on the screen
double dx, dy; //change in position in one step
int step, lastStep; //to know when finish showing this projectile
int creID; //ID of creature that shot this projectile
int stackID; //ID of stack
int frameNum; //frame to display form projectile animation
//bool spin; //if true, frameNum will be increased
int animStartDelay; //frame of shooter animation when projectile should appear
bool shotDone; // actual shot already done, projectile is flying
bool reverse; //if true, projectile will be flipped by vertical asix
std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
};
class CRangedAttackAnimation : public CAttackAnimation
{
public:
CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
protected:
};
/// Shooting attack
class CShootingAnimation : public CRangedAttackAnimation
{
private:
int catapultDamage;
public:
bool init() override;
void nextFrame() override;
void endAnim() override;
//last two params only for catapult attacks
CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest,
const CStack * _attacked, bool _catapult = false, int _catapultDmg = 0);
virtual ~CShootingAnimation(){};
};
class CCastAnimation : public CRangedAttackAnimation
{
public:
CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
bool init() override;
void nextFrame() override;
void endAnim() override;
};
/// This class manages effect animation
class CEffectAnimation : public CBattleAnimation
{
private:
BattleHex destTile;
std::shared_ptr<CAnimation> customAnim;
int x, y, dx, dy;
bool Vflip;
bool alignToBottom;
public:
bool init() override;
void nextFrame() override;
void endAnim() override;
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx = 0, int _dy = 0);
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
virtual ~CEffectAnimation(){};
};

File diff suppressed because it is too large Load Diff

View File

@ -1,414 +0,0 @@
/*
* CBattleInterface.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include <vcmi/spells/Magic.h>
#include "../../lib/ConstTransitivePtr.h" //may be redundant
#include "../../lib/GameConstants.h"
#include "CBattleAnimations.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
#include "../../lib/CCreatureHandler.h"
#include "../../lib/battle/CBattleInfoCallback.h"
VCMI_LIB_NAMESPACE_BEGIN
class CCreatureSet;
class CGHeroInstance;
class CStack;
struct BattleResult;
struct BattleSpellCast;
struct CObstacleInstance;
template <typename T> struct CondSh;
struct SetStackEffect;
class BattleAction;
class CGTownInstance;
struct CatapultAttack;
struct BattleTriggerEffect;
struct BattleHex;
struct InfoAboutHero;
class CBattleGameInterface;
struct CustomEffectInfo;
class CSpell;
VCMI_LIB_NAMESPACE_END
class CLabel;
class CCallback;
class CButton;
class CToggleButton;
class CToggleGroup;
struct CatapultProjectileInfo;
class CBattleAnimation;
class CBattleHero;
class CBattleConsole;
class CBattleResultWindow;
class CStackQueue;
class CPlayerInterface;
class CCreatureAnimation;
struct ProjectileInfo;
class CClickableHex;
class CAnimation;
class IImage;
class CStackQueue;
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
struct StackAttackedInfo
{
const CStack *defender; //attacked stack
int64_t dmg; //damage dealt
unsigned int amountKilled; //how many creatures in stack has been killed
const CStack *attacker; //attacking stack
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
bool killed; //if true, stack has been killed
bool rebirth; //if true, play rebirth animation after all
bool cloneKilled;
};
/// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,...
struct BattleEffect
{
int x, y; //position on the screen
float currentFrame;
std::shared_ptr<CAnimation> animation;
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
BattleHex position; //Indicates if effect which hex the effect is drawn on
};
struct BattleObjectsByHex
{
typedef std::vector<int> TWallList;
typedef std::vector<const CStack *> TStackList;
typedef std::vector<const BattleEffect *> TEffectList;
typedef std::vector<std::shared_ptr<const CObstacleInstance>> TObstacleList;
struct HexData
{
TWallList walls;
TStackList dead;
TStackList alive;
TEffectList effects;
TObstacleList obstacles;
};
HexData beforeAll;
HexData afterAll;
std::array<HexData, GameConstants::BFIELD_SIZE> hex;
};
/// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon
struct CatapultProjectileInfo
{
CatapultProjectileInfo(Point from, Point dest);
double facA, facB, facC;
double calculateY(double x);
};
enum class MouseHoveredHexContext
{
UNOCCUPIED_HEX,
OCCUPIED_HEX
};
/// Big class which handles the overall battle interface actions and it is also responsible for
/// drawing everything correctly.
class CBattleInterface : public WindowBase
{
private:
SDL_Surface *background, *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral, *cellBorders, *backgroundWithHexes;
std::shared_ptr<CButton> bOptions;
std::shared_ptr<CButton> bSurrender;
std::shared_ptr<CButton> bFlee;
std::shared_ptr<CButton> bAutofight;
std::shared_ptr<CButton> bSpell;
std::shared_ptr<CButton> bWait;
std::shared_ptr<CButton> bDefence;
std::shared_ptr<CButton> bConsoleUp;
std::shared_ptr<CButton> bConsoleDown;
std::shared_ptr<CButton> btactNext;
std::shared_ptr<CButton> btactEnd;
std::shared_ptr<CBattleConsole> console;
std::shared_ptr<CBattleHero> attackingHero;
std::shared_ptr<CBattleHero> defendingHero;
std::shared_ptr<CStackQueue> queue;
const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay;
std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
ui8 animCount;
const CStack *activeStack; //number of active stack; nullptr - no one
const CStack *mouseHoveredStack; // stack below mouse pointer, used for border animation
const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
const CStack *selectedStack; //for Teleport / Sacrifice
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
std::vector<BattleHex> occupyableHexes, //hexes available for active stack
attackableHexes; //hexes attackable by active stack
std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes; // hexes that when in front of a unit cause it's amount box to move back
BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago
BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon)
int attackingHex; //hex from which the stack would perform attack with current cursor
std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
bool tacticsMode;
bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
bool creatureCasting; //if true, stack currently aims to cats a spell
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
const CSpell *sp; //spell pointer for convenience
si32 creatureSpellToCast;
std::vector<PossiblePlayerBattleAction> possibleActions; //all actions possible to call at the moment by player
std::vector<PossiblePlayerBattleAction> localActions; //actions possible to take on hovered hex
std::vector<PossiblePlayerBattleAction> illegalActions; //these actions display message in case of illegal target
PossiblePlayerBattleAction currentAction; //action that will be performed on l-click
PossiblePlayerBattleAction selectedAction; //last action chosen (and saved) by player
PossiblePlayerBattleAction illegalAction; //most likely action that can't be performed here
bool battleActionsStarted; //used for delaying battle actions until intro sound stops
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
void setActiveStack(const CStack *stack);
void setHoveredStack(const CStack *stack);
void requestAutofightingAIToTakeAction();
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn
void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled)
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
//force active stack to cast a spell if possible
void enterCreatureCastingMode();
std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles
bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
/// Class which is responsible for drawing the wall of a siege during battle
class SiegeHelper
{
private:
SDL_Surface* walls[18];
const CBattleInterface *owner;
public:
const CGTownInstance *town; //besieged town
SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface *_owner);
~SiegeHelper();
std::string getSiegeName(ui16 what) const;
std::string getSiegeName(ui16 what, int state) const; // state uses EWallState enum
void printPartOfWall(SDL_Surface *to, int what);
enum EWallVisual
{
BACKGROUND = 0,
BACKGROUND_WALL = 1,
KEEP,
BOTTOM_TOWER,
BOTTOM_WALL,
WALL_BELLOW_GATE,
WALL_OVER_GATE,
UPPER_WALL,
UPPER_TOWER,
GATE,
GATE_ARCH,
BOTTOM_STATIC_WALL,
UPPER_STATIC_WALL,
MOAT,
BACKGROUND_MOAT,
KEEP_BATTLEMENT,
BOTTOM_BATTLEMENT,
UPPER_BATTLEMENT
};
friend class CBattleInterface;
} *siegeH;
std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
std::shared_ptr<CPlayerInterface> curInt; //current player interface
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
/** Methods for displaying battle screen */
void showBackground(SDL_Surface *to);
void showBackgroundImage(SDL_Surface *to);
void showAbsoluteObstacles(SDL_Surface *to);
void showHighlightedHexes(SDL_Surface *to);
void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder = false);
void showInterface(SDL_Surface *to);
void showBattlefieldObjects(SDL_Surface *to);
void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
void showObstacles(SDL_Surface *to, std::vector<std::shared_ptr<const CObstacleInstance>> &obstacles);
void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
void showProjectiles(SDL_Surface *to);
BattleObjectsByHex sortObjectsByHex();
void updateBattleAnimations();
std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
void redrawBackgroundWithHexes(const CStack *activeStack);
/** End of battle screen blitting methods */
void setHeroAnimation(ui8 side, int phase);
public:
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized>
void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
ui32 animIDhelper; //for giving IDs for animations
CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
virtual ~CBattleInterface();
//std::vector<TimeInterested*> timeinterested; //animation handling
void setPrintCellBorders(bool set); //if true, cell borders will be printed
void setPrintStackRange(bool set); //if true,range of active stack will be printed
void setPrintMouseShadow(bool set); //if true, hex under mouse will be shaded
void setAnimSpeed(int set); //speed of animation; range 1..100
int getAnimSpeed() const; //speed of animation; range 1..100
CPlayerInterface *getCurrentPlayerInterface() const;
bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
std::vector<std::shared_ptr<CClickableHex>> bfield; //11 lines, 17 hexes on each
SDL_Surface *cellBorder, *cellShade;
bool myTurn; //if true, interface is active (commands can be ordered)
bool moveStarted; //if true, the creature that is already moving is going to make its first step
int moveSoundHander; // sound handler used when moving a unit
const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
// block all UI elements, e.g. during enemy turn
// unlike activate/deactivate this method will correctly grey-out all elements
void blockUI(bool on);
//button handle funcs:
void bOptionsf();
void bSurrenderf();
void bFleef();
void reallyFlee(); //performs fleeing without asking player
void reallySurrender(); //performs surrendering without asking player
void bAutofightf();
void bSpellf();
void bWaitf();
void bDefencef();
void bConsoleUpf();
void bConsoleDownf();
void bTacticNextStack(const CStack *current = nullptr);
void bEndTacticPhase();
//end of button handle funcs
//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
void activate() override;
void deactivate() override;
void keyPressed(const SDL_KeyboardEvent & key) override;
void mouseMoved(const SDL_MouseMotionEvent &sEvent) override;
void clickRight(tribool down, bool previousState) override;
void show(SDL_Surface *to) override;
void showAll(SDL_Surface *to) override;
//call-ins
void startAction(const BattleAction* action);
void unitAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void waitForAnims();
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void newRoundFirst( int round );
void newRound(int number); //caled when round is ended; number is the number of round
void hexLclicked(int whichOne); //hex only call-in
void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
void displayBattleFinished(); //displays battle result
void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
void displayBattleLog(const std::vector<MetaString> & battleLog);
void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
void displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield
void displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile);
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void battleTriggerEffect(const BattleTriggerEffect & bte);
void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
void endAction(const BattleAction* action);
void hideQueue();
void showQueue();
Rect hexPosition(BattleHex hex) const;
void handleHex(BattleHex myNumber, int eventType);
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
BattleHex fromWhichHexAttack(BattleHex myNumber);
void obstaclePlaced(const CObstacleInstance & oi);
void gateStateChanged(const EGateState state);
void initStackProjectile(const CStack * stack);
const CGHeroInstance *currentHero() const;
InfoAboutHero enemyHero() const;
friend class CPlayerInterface;
friend class CButton;
friend class CInGameConsole;
friend class CStackQueue;
friend class CBattleResultWindow;
friend class CBattleHero;
friend class CEffectAnimation;
friend class CBattleStackAnimation;
friend class CReverseAnimation;
friend class CDefenceAnimation;
friend class CMovementAnimation;
friend class CMovementStartAnimation;
friend class CAttackAnimation;
friend class CMeleeAttackAnimation;
friend class CShootingAnimation;
friend class CCastAnimation;
friend class CClickableHex;
};

View File

@ -8,12 +8,12 @@
*
*/
#include "StdInc.h"
#include "CCreatureAnimation.h"
#include "CreatureAnimation.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/Canvas.h"
static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
@ -34,13 +34,13 @@ SDL_Color AnimationControls::getNoBorder()
return creatureNoBorder;
}
std::shared_ptr<CCreatureAnimation> AnimationControls::getAnimation(const CCreature * creature)
std::shared_ptr<CreatureAnimation> AnimationControls::getAnimation(const CCreature * creature)
{
auto func = std::bind(&AnimationControls::getCreatureAnimationSpeed, creature, _1, _2);
return std::make_shared<CCreatureAnimation>(creature->animDefName, func);
return std::make_shared<CreatureAnimation>(creature->animDefName, func);
}
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t group)
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, size_t group)
{
CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group);
@ -113,6 +113,11 @@ float AnimationControls::getProjectileSpeed()
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 100);
}
float AnimationControls::getCatapultSpeed()
{
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 20);
}
float AnimationControls::getSpellEffectSpeed()
{
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
@ -128,12 +133,12 @@ float AnimationControls::getFlightDistance(const CCreature * creature)
return static_cast<float>(creature->animation.flightAnimationDistance * 200);
}
CCreatureAnim::EAnimType CCreatureAnimation::getType() const
CCreatureAnim::EAnimType CreatureAnimation::getType() const
{
return type;
}
void CCreatureAnimation::setType(CCreatureAnim::EAnimType type)
void CreatureAnimation::setType(CCreatureAnim::EAnimType type)
{
this->type = type;
currentFrame = 0;
@ -142,7 +147,7 @@ void CCreatureAnimation::setType(CCreatureAnim::EAnimType type)
play();
}
void CCreatureAnimation::shiftColor(const ColorShifter* shifter)
void CreatureAnimation::shiftColor(const ColorShifter* shifter)
{
if(forward)
forward->shiftColor(shifter);
@ -151,7 +156,7 @@ void CCreatureAnimation::shiftColor(const ColorShifter* shifter)
reverse->shiftColor(shifter);
}
CCreatureAnimation::CCreatureAnimation(const std::string & name_, TSpeedController controller)
CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
: name(name_),
speed(0.1f),
currentFrame(0),
@ -198,7 +203,7 @@ CCreatureAnimation::CCreatureAnimation(const std::string & name_, TSpeedControll
play();
}
void CCreatureAnimation::endAnimation()
void CreatureAnimation::endAnimation()
{
once = false;
auto copy = onAnimationReset;
@ -206,7 +211,7 @@ void CCreatureAnimation::endAnimation()
copy();
}
bool CCreatureAnimation::incrementFrame(float timePassed)
bool CreatureAnimation::incrementFrame(float timePassed)
{
elapsedTime += timePassed;
currentFrame += timePassed * speed;
@ -231,27 +236,27 @@ bool CCreatureAnimation::incrementFrame(float timePassed)
return false;
}
void CCreatureAnimation::setBorderColor(SDL_Color palette)
void CreatureAnimation::setBorderColor(SDL_Color palette)
{
border = palette;
}
int CCreatureAnimation::getWidth() const
int CreatureAnimation::getWidth() const
{
return fullWidth;
}
int CCreatureAnimation::getHeight() const
int CreatureAnimation::getHeight() const
{
return fullHeight;
}
float CCreatureAnimation::getCurrentFrame() const
float CreatureAnimation::getCurrentFrame() const
{
return currentFrame;
}
void CCreatureAnimation::playOnce( CCreatureAnim::EAnimType type )
void CreatureAnimation::playOnce( CCreatureAnim::EAnimType type )
{
setType(type);
once = true;
@ -289,20 +294,20 @@ static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
);
}
void CCreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
void CreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
{
target[0] = genBorderColor(getBorderStrength(elapsedTime), border);
target[1] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border));
target[2] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border));
}
void CCreatureAnimation::nextFrame(SDL_Surface * dest, bool attacker)
void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight)
{
size_t frame = static_cast<size_t>(floor(currentFrame));
std::shared_ptr<IImage> image;
if(attacker)
if(facingRight)
image = forward->getImage(frame, type);
else
image = reverse->getImage(frame, type);
@ -314,49 +319,63 @@ void CCreatureAnimation::nextFrame(SDL_Surface * dest, bool attacker)
image->setBorderPallete(borderPallete);
image->draw(dest, pos.x, pos.y);
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
}
}
int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
int CreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
{
return static_cast<int>(forward->size(group));
}
bool CCreatureAnimation::isDead() const
bool CreatureAnimation::isDead() const
{
return getType() == CCreatureAnim::DEAD
|| getType() == CCreatureAnim::DEATH
|| getType() == CCreatureAnim::DEAD_RANGED
|| getType() == CCreatureAnim::DEATH_RANGED;
|| getType() == CCreatureAnim::DEAD_RANGED;
}
bool CCreatureAnimation::isIdle() const
bool CreatureAnimation::isDying() const
{
return getType() == CCreatureAnim::DEATH
|| getType() == CCreatureAnim::DEATH_RANGED;
}
bool CreatureAnimation::isDeadOrDying() const
{
return getType() == CCreatureAnim::DEAD
|| getType() == CCreatureAnim::DEATH
|| getType() == CCreatureAnim::DEAD_RANGED
|| getType() == CCreatureAnim::DEATH_RANGED;
}
bool CreatureAnimation::isIdle() const
{
return getType() == CCreatureAnim::HOLDING
|| getType() == CCreatureAnim::MOUSEON;
}
bool CCreatureAnimation::isMoving() const
bool CreatureAnimation::isMoving() const
{
return getType() == CCreatureAnim::MOVE_START
|| getType() == CCreatureAnim::MOVING
|| getType() == CCreatureAnim::MOVE_END;
|| getType() == CCreatureAnim::MOVE_END
|| getType() == CCreatureAnim::TURN_L
|| getType() == CCreatureAnim::TURN_R;
}
bool CCreatureAnimation::isShooting() const
bool CreatureAnimation::isShooting() const
{
return getType() == CCreatureAnim::SHOOT_UP
|| getType() == CCreatureAnim::SHOOT_FRONT
|| getType() == CCreatureAnim::SHOOT_DOWN;
}
void CCreatureAnimation::pause()
void CreatureAnimation::pause()
{
speed = 0;
}
void CCreatureAnimation::play()
void CreatureAnimation::play()
{
//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
speed = 0;

View File

@ -14,7 +14,8 @@
#include "../gui/CAnimation.h"
class CIntObject;
class CCreatureAnimation;
class CreatureAnimation;
class Canvas;
/// Namespace for some common controls of animations
namespace AnimationControls
@ -25,15 +26,18 @@ namespace AnimationControls
SDL_Color getNoBorder();
/// creates animation object with preset speed control
std::shared_ptr<CCreatureAnimation> getAnimation(const CCreature * creature);
std::shared_ptr<CreatureAnimation> getAnimation(const CCreature * creature);
/// returns animation speed of specific group, taking in mind game setting (in frames per second)
float getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t groupID);
float getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, size_t groupID);
/// returns how far projectile should move each frame
/// TODO: make it time-based
float getProjectileSpeed();
/// returns speed of catapult projectile
float getCatapultSpeed();
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
float getSpellEffectSpeed();
@ -46,44 +50,51 @@ namespace AnimationControls
/// Class which manages animations of creatures/units inside battles
/// TODO: split into constant image container and class that does *control* of animation
class CCreatureAnimation : public CIntObject
class CreatureAnimation : public CIntObject
{
public:
typedef std::function<float(CCreatureAnimation *, size_t)> TSpeedController;
typedef std::function<float(CreatureAnimation *, size_t)> TSpeedController;
private:
std::string name;
/// animation for rendering stack in default orientation - facing right
std::shared_ptr<CAnimation> forward;
/// animation that has all its frames flipped for rendering stack facing left
std::shared_ptr<CAnimation> reverse;
int fullWidth;
int fullHeight;
// speed of animation, measure in frames per second
/// speed of animation, measure in frames per second
float speed;
// currently displayed frame. Float to allow H3-style animations where frames
// don't display for integer number of frames
/// currently displayed frame. Float to allow H3-style animations where frames
/// don't display for integer number of frames
float currentFrame;
// cumulative, real-time duration of animation. Used for effects like selection border
float elapsedTime;
CCreatureAnim::EAnimType type; //type of animation being displayed
// border color, disabled if alpha = 0
/// cumulative, real-time duration of animation. Used for effects like selection border
float elapsedTime;
///type of animation being displayed
CCreatureAnim::EAnimType type;
/// border color, disabled if alpha = 0
SDL_Color border;
TSpeedController speedController;
bool once; // animation will be played once and the reset to idling
/// animation will be played once and the reset to idling
bool once;
void endAnimation();
void genBorderPalette(IImage::BorderPallete & target);
public:
// function(s) that will be called when animation ends, after reset to 1st frame
// NOTE that these function will be fired only once
/// function(s) that will be called when animation ends, after reset to 1st frame
/// NOTE that these functions will be fired only once
CFunctionList<void()> onAnimationReset;
int getWidth() const;
@ -93,31 +104,40 @@ public:
/// name - path to .def file, relative to SPRITES/ directory
/// controller - function that will return for how long *each* frame
/// in specified group of animation should be played, measured in seconds
CCreatureAnimation(const std::string & name_, TSpeedController speedController);
CreatureAnimation(const std::string & name_, TSpeedController speedController);
void setType(CCreatureAnim::EAnimType type); //sets type of animation and cleares framecount
CCreatureAnim::EAnimType getType() const; //returns type of animation
/// sets type of animation and resets framecount
void setType(CCreatureAnim::EAnimType type);
void nextFrame(SDL_Surface * dest, bool attacker);
/// returns currently rendered type of animation
CCreatureAnim::EAnimType getType() const;
// should be called every frame, return true when animation was reset to beginning
void nextFrame(Canvas & canvas, bool facingRight);
/// should be called every frame, return true when animation was reset to beginning
bool incrementFrame(float timePassed);
void setBorderColor(SDL_Color palette);
// tint color effect
/// apply color tint effect
void shiftColor(const ColorShifter * shifter);
float getCurrentFrame() const; // Gets the current frame ID relative to frame group.
/// Gets the current frame ID within current group.
float getCurrentFrame() const;
void playOnce(CCreatureAnim::EAnimType type); //plays once given stage of animation, then resets to 2
/// plays once given type of animation, then resets to idle
void playOnce(CCreatureAnim::EAnimType type);
int framesInGroup(CCreatureAnim::EAnimType group) const;
/// returns number of frames in selected animation type
int framesInGroup(CCreatureAnim::EAnimType type) const;
void pause();
void play();
//helpers. TODO: move them somewhere else
/// helpers to classify current type of animation
bool isDead() const;
bool isDying() const;
bool isDeadOrDying() const;
bool isIdle() const;
bool isMoving() const;
bool isShooting() const;

View File

@ -92,14 +92,14 @@ 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, Rect *src=nullptr, ui8 alpha=255) const override;
void draw(SDL_Surface * where, SDL_Rect * dest, SDL_Rect * src, ui8 alpha=255) const override;
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;
std::shared_ptr<IImage> scaleFast(float scale) const override;
void exportBitmap(const boost::filesystem::path & path) const override;
void playerColored(PlayerColor player) override;
void setFlagColor(PlayerColor player) override;
int width() const override;
int height() const override;
bool isTransparent(const Point & coords) const override;
Point dimensions() const override;
void horizontalFlip() override;
void verticalFlip() override;
@ -134,6 +134,11 @@ public:
~SDLImageLoader();
};
std::shared_ptr<IImage> IImage::createFromFile( const std::string & path )
{
return std::shared_ptr<IImage>(new SDLImage(path));
}
// Extremely simple file cache. TODO: smarter, more general solution
class CFileCache
{
@ -554,6 +559,15 @@ SDLImageLoader::~SDLImageLoader()
IImage::IImage() = default;
IImage::~IImage() = default;
int IImage::width() const
{
return dimensions().x;
}
int IImage::height() const
{
return dimensions().y;
}
SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group)
: surf(nullptr),
@ -640,7 +654,7 @@ SDLImage::SDLImage(std::string filename)
}
}
void SDLImage::draw(SDL_Surface *where, int posX, int posY, Rect *src, ui8 alpha) const
void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) const
{
if(!surf)
return;
@ -650,7 +664,7 @@ void SDLImage::draw(SDL_Surface *where, int posX, int posY, Rect *src, ui8 alpha
draw(where, &destRect, src);
}
void SDLImage::draw(SDL_Surface* where, SDL_Rect* dest, SDL_Rect* src, ui8 alpha) const
void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) const
{
if (!surf)
return;
@ -730,14 +744,14 @@ void SDLImage::setFlagColor(PlayerColor player)
CSDL_Ext::setPlayerColor(surf, player);
}
int SDLImage::width() const
bool SDLImage::isTransparent(const Point & coords) const
{
return fullSize.x;
return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
}
int SDLImage::height() const
Point SDLImage::dimensions() const
{
return fullSize.y;
return fullSize;
}
void SDLImage::horizontalFlip()

View File

@ -40,8 +40,8 @@ public:
using BorderPallete = std::array<SDL_Color, 3>;
//draws image on surface "where" at position
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, Rect * src = nullptr, ui8 alpha = 255) const=0;
virtual void draw(SDL_Surface * where, SDL_Rect * dest, SDL_Rect * src, ui8 alpha = 255) const = 0;
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 std::shared_ptr<IImage> scaleFast(float scale) const = 0;
@ -53,8 +53,12 @@ public:
//set special color for flag
virtual void setFlagColor(PlayerColor player)=0;
virtual int width() const=0;
virtual int height() const=0;
//test transparency of specific pixel
virtual bool isTransparent(const Point & coords) const = 0;
virtual Point dimensions() const = 0;
int width() const;
int height() const;
//only indexed bitmaps, 16 colors maximum
virtual void shiftPalette(int from, int howMany) = 0;
@ -69,6 +73,9 @@ public:
IImage();
virtual ~IImage();
/// loads image from specified file. Returns 0-sized images on failure
static std::shared_ptr<IImage> createFromFile( const std::string & path );
};
/// Class for handling animation

View File

@ -21,7 +21,7 @@
#include "../../lib/CConfigHandler.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../battle/CBattleInterface.h"
#include "../battle/BattleInterface.h"
extern std::queue<SDL_Event> SDLEventsQueue;
extern boost::mutex eventsM;

View File

@ -20,7 +20,7 @@ template <typename T> struct CondSh;
VCMI_LIB_NAMESPACE_END
class CFramerateManager;
class CGStatusBar;
class IStatusBar;
class CIntObject;
class IUpdateable;
class IShowActivatable;
@ -65,7 +65,7 @@ class CGuiHandler
public:
CFramerateManager * mainFPSmng; //to keep const framerate
std::list<std::shared_ptr<IShowActivatable>> listInt; //list of interfaces - front=foreground; back = background (includes adventure map, window interfaces, all kind of active dialogs, and so on)
std::shared_ptr<CGStatusBar> statusbar;
std::shared_ptr<IStatusBar> statusbar;
private:
std::vector<std::shared_ptr<IShowActivatable>> disposed;
@ -165,6 +165,7 @@ struct SSetCaptureState
};
#define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
#define OBJ_CONSTRUCTION_TARGETED(obj) SObjectConstruction obj__i(obj)
#define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
#define OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(actions) SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)

View File

@ -373,3 +373,6 @@ void WindowBase::close()
logGlobal->error("Only top interface must be closed");
GH.popInts(1);
}
IStatusBar::~IStatusBar()
{}

View File

@ -165,8 +165,6 @@ public:
//request complete redraw of this object
void redraw() override;
enum EAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
bool isItInLoc(const SDL_Rect &rect, int x, int y);
bool isItInLoc(const SDL_Rect &rect, const Point &p);
const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
@ -217,3 +215,25 @@ public:
protected:
void close();
};
class IStatusBar
{
public:
virtual ~IStatusBar();
/// set current text for the status bar
virtual void write(const std::string & text) = 0;
/// remove any current text from the status bar
virtual void clear() = 0;
/// remove text from status bar if current text matches tested text
virtual void clearIfMatching(const std::string & testedText) = 0;
/// enables mode for entering text instead of showing hover text
virtual void setEnteringMode(bool on) = 0;
/// overrides hover text from controls with text entered into in-game console (for chat/cheats)
virtual void setEnteredText(const std::string & text) = 0;
};

84
client/gui/Canvas.cpp Normal file
View File

@ -0,0 +1,84 @@
/*
* Canvas.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Canvas.h"
#include "SDL_Extensions.h"
#include "Geometries.h"
#include "CAnimation.h"
#include "../Graphics.h"
Canvas::Canvas(SDL_Surface * surface):
surface(surface)
{
surface->refcount++;
}
Canvas::Canvas(Canvas & other):
surface(other.surface)
{
surface->refcount++;
}
Canvas::Canvas(const Point & size)
{
surface = CSDL_Ext::newSurface(size.x, size.y);
}
Canvas::~Canvas()
{
SDL_FreeSurface(surface);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos)
{
assert(image);
if (image)
image->draw(surface, pos.x, pos.y);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect)
{
assert(image);
if (image)
image->draw(surface, pos.x, pos.y, &sourceRect);
}
void Canvas::draw(Canvas & image, const Point & pos)
{
blitAt(image.surface, pos.x, pos.y, surface);
}
void Canvas::drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest)
{
CSDL_Ext::drawLine(surface, from.x, from.y, dest.x, dest.y, colorFrom, colorDest);
}
void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::string & text )
{
switch (alignment)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, position);
}
}
void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::vector<std::string> & text )
{
switch (alignment)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, position);
}
}

55
client/gui/Canvas.h Normal file
View File

@ -0,0 +1,55 @@
/*
* Canvas.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "Geometries.h"
struct SDL_Color;
struct SDL_Surface;
class IImage;
enum EFonts : int;
/// Class that represents surface for drawing on
class Canvas
{
SDL_Surface * surface;
Canvas & operator = (Canvas & other) = delete;
public:
/// constructs canvas using existing surface. Caller maintains ownership on the surface
Canvas(SDL_Surface * surface);
/// copy contructor
Canvas(Canvas & other);
/// constructs canvas of specified size
Canvas(const Point & size);
~Canvas();
/// renders image onto this canvas at specified position
void draw(std::shared_ptr<IImage> image, const Point & pos);
/// renders section of image bounded by sourceRect at specified position
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
/// renders another canvas onto this canvas
void draw(Canvas & image, const Point & pos);
/// renders continuous, 1-pixel wide line with color gradient
void drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest);
/// renders single line of text with specified parameters
void drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::string & text );
/// renders multiple lines of text with specified parameters
void drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::vector<std::string> & text );
};

View File

@ -12,6 +12,8 @@
#include <SDL_video.h>
#include "../../lib/int3.h"
enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
struct SDL_MouseMotionEvent;
// A point with x/y coordinate, used mostly for graphic rendering

View File

@ -0,0 +1,389 @@
/*
* InterfaceBuilder.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "InterfaceObjectConfigurable.h"
#include "../CGameInfo.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../lib/CGeneralTextHandler.h"
InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
InterfaceObjectConfigurable(used, offset)
{
init(config);
}
InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset):
CIntObject(used, offset)
{
REGISTER_BUILDER("picture", &InterfaceObjectConfigurable::buildPicture);
REGISTER_BUILDER("image", &InterfaceObjectConfigurable::buildImage);
REGISTER_BUILDER("texture", &InterfaceObjectConfigurable::buildTexture);
REGISTER_BUILDER("animation", &InterfaceObjectConfigurable::buildAnimation);
REGISTER_BUILDER("label", &InterfaceObjectConfigurable::buildLabel);
REGISTER_BUILDER("toggleGroup", &InterfaceObjectConfigurable::buildToggleGroup);
REGISTER_BUILDER("toggleButton", &InterfaceObjectConfigurable::buildToggleButton);
REGISTER_BUILDER("button", &InterfaceObjectConfigurable::buildButton);
REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup);
REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider);
}
void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f)
{
builders[type] = f;
}
void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function<void(int)> callback)
{
callbacks[callbackName] = callback;
}
void InterfaceObjectConfigurable::init(const JsonNode &config)
{
OBJ_CONSTRUCTION;
logGlobal->debug("Building configurable interface object");
for(auto & item : config["variables"].Struct())
{
logGlobal->debug("Read variable named %s", item.first);
variables[item.first] = item.second;
}
int unnamedObjectId = 0;
const std::string unnamedObjectPrefix = "__widget_";
for(const auto & item : config["items"].Vector())
{
std::string name = item["name"].isNull()
? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
: item["name"].String();
logGlobal->debug("Building widget with name %s", name);
widgets[name] = buildWidget(item);
}
}
std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
{
if(config.isNull())
return "";
if(config.isNumber())
{
logGlobal->debug("Reading text from generaltext handler id:%d", config.Integer());
return CGI->generaltexth->allTexts[config.Integer()];
}
const std::string delimiter = "/";
std::string s = config.String();
logGlobal->debug("Reading text from translations by key: %s", s);
JsonNode translated = CGI->generaltexth->localizedTexts;
for(size_t p = s.find(delimiter); p != std::string::npos; p = s.find(delimiter))
{
translated = translated[s.substr(0, p)];
s.erase(0, p + delimiter.length());
}
if(s == config.String())
{
logGlobal->warn("Reading non-translated text: %s", s);
return s;
}
return translated[s].String();
}
Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
{
Point p;
logGlobal->debug("Reading point");
p.x = config["x"].Integer();
p.y = config["y"].Integer();
return p;
}
Rect InterfaceObjectConfigurable::readRect(const JsonNode & config) const
{
Rect p;
logGlobal->debug("Reading rect");
p.x = config["x"].Integer();
p.y = config["y"].Integer();
p.w = config["w"].Integer();
p.h = config["h"].Integer();
return p;
}
ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const
{
logGlobal->debug("Reading text alignment");
if(!config.isNull())
{
if(config.String() == "center")
return ETextAlignment::CENTER;
if(config.String() == "left")
return ETextAlignment::TOPLEFT;
if(config.String() == "right")
return ETextAlignment::BOTTOMRIGHT;
}
logGlobal->debug("Uknown text alignment attribute");
return ETextAlignment::CENTER;
}
SDL_Color InterfaceObjectConfigurable::readColor(const JsonNode & config) const
{
logGlobal->debug("Reading color");
if(!config.isNull())
{
if(config.String() == "yellow")
return Colors::YELLOW;
if(config.String() == "white")
return Colors::WHITE;
if(config.String() == "gold")
return Colors::METALLIC_GOLD;
if(config.String() == "green")
return Colors::GREEN;
if(config.String() == "orange")
return Colors::ORANGE;
if(config.String() == "bright-yellow")
return Colors::BRIGHT_YELLOW;
}
logGlobal->debug("Uknown color attribute");
return Colors::DEFAULT_KEY_COLOR;
}
EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
{
logGlobal->debug("Reading font");
if(!config.isNull())
{
if(config.String() == "big")
return EFonts::FONT_BIG;
if(config.String() == "medium")
return EFonts::FONT_MEDIUM;
if(config.String() == "small")
return EFonts::FONT_SMALL;
if(config.String() == "tiny")
return EFonts::FONT_TINY;
}
logGlobal->debug("Uknown font attribute");
return EFonts::FONT_TIMES;
}
std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(const JsonNode & config) const
{
logGlobal->debug("Reading hint text");
std::pair<std::string, std::string> result;
if(!config.isNull())
{
if(config.isNumber())
{
logGlobal->debug("Reading hint text (zelp) from generaltext handler id:%d", config.Integer());
return CGI->generaltexth->zelp[config.Integer()];
}
if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
{
result.first = readText(config["hover"]);
result.second = readText(config["help"]);
return result;
}
if(config.getType() == JsonNode::JsonType::DATA_STRING)
{
logGlobal->debug("Reading non-translated hint: %s", config.String());
result.first = result.second = config.String();
}
}
return result;
}
std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
{
logGlobal->debug("Building widget CPicture");
auto image = config["image"].String();
auto position = readPosition(config["position"]);
auto pic = std::make_shared<CPicture>(image, position.x, position.y);
if(!config["visible"].isNull())
pic->visible = config["visible"].Bool();
return pic;
}
std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode & config) const
{
logGlobal->debug("Building widget CLabel");
auto font = readFont(config["font"]);
auto alignment = readTextAlignment(config["alignment"]);
auto color = readColor(config["color"]);
auto text = readText(config["text"]);
auto position = readPosition(config["position"]);
return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text);
}
std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(const JsonNode & config) const
{
logGlobal->debug("Building widget CToggleGroup");
auto position = readPosition(config["position"]);
auto group = std::make_shared<CToggleGroup>(0);
group->pos += position;
if(!config["items"].isNull())
{
OBJ_CONSTRUCTION_TARGETED(group.get());
int itemIdx = -1;
for(const auto & item : config["items"].Vector())
{
itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
}
}
if(!config["selected"].isNull())
group->setSelected(config["selected"].Integer());
if(!config["callback"].isNull())
group->addCallback(callbacks.at(config["callback"].String()));
return group;
}
std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(const JsonNode & config) const
{
logGlobal->debug("Building widget CToggleButton");
auto position = readPosition(config["position"]);
auto image = config["image"].String();
auto zelp = readHintText(config["zelp"]);
auto button = std::make_shared<CToggleButton>(position, image, zelp);
if(!config["selected"].isNull())
button->setSelected(config["selected"].Bool());
if(!config["imageOrder"].isNull())
{
auto imgOrder = config["imageOrder"].Vector();
assert(imgOrder.size() >= 4);
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
}
if(!config["callback"].isNull())
button->addCallback(callbacks.at(config["callback"].String()));
return button;
}
std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode & config) const
{
logGlobal->debug("Building widget CButton");
auto position = readPosition(config["position"]);
auto image = config["image"].String();
auto zelp = readHintText(config["zelp"]);
auto button = std::make_shared<CButton>(position, image, zelp);
if(!config["items"].isNull())
{
for(const auto & item : config["items"].Vector())
{
button->addOverlay(buildWidget(item));
}
}
if(!config["callback"].isNull())
button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0));
return button;
}
std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
{
logGlobal->debug("Building widget CLabelGroup");
auto font = readFont(config["font"]);
auto alignment = readTextAlignment(config["alignment"]);
auto color = readColor(config["color"]);
auto group = std::make_shared<CLabelGroup>(font, alignment, color);
if(!config["items"].isNull())
{
for(const auto & item : config["items"].Vector())
{
auto position = readPosition(item["position"]);
auto text = readText(item["text"]);
group->add(position.x, position.y, text);
}
}
return group;
}
std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode & config) const
{
logGlobal->debug("Building widget CSlider");
auto position = readPosition(config["position"]);
int length = config["size"].Integer();
auto style = config["style"].String() == "brown" ? CSlider::BROWN : CSlider::BLUE;
auto itemsVisible = config["itemsVisible"].Integer();
auto itemsTotal = config["itemsTotal"].Integer();
auto value = config["selected"].Integer();
bool horizontal = config["orientation"].String() == "horizontal";
return std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
}
std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNode & config) const
{
logGlobal->debug("Building widget CAnimImage");
auto position = readPosition(config["position"]);
auto image = config["image"].String();
int group = config["group"].isNull() ? 0 : config["group"].Integer();
int frame = config["frame"].isNull() ? 0 : config["frame"].Integer();
return std::make_shared<CAnimImage>(image, frame, group, position.x, position.y);
}
std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
{
logGlobal->debug("Building widget CFilledTexture");
auto image = config["image"].String();
auto rect = readRect(config["rect"]);
return std::make_shared<CFilledTexture>(image, rect);
}
std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const
{
logGlobal->debug("Building widget CShowableAnim");
auto position = readPosition(config["position"]);
auto image = config["image"].String();
ui8 flags = 0;
if(!config["repeat"].Bool())
flags |= CShowableAnim::EFlags::PLAY_ONCE;
int group = config["group"].isNull() ? 0 : config["group"].Integer();
auto anim = std::make_shared<CShowableAnim>(position.x, position.y, image, flags, 4, group);
if(!config["alpha"].isNull())
anim->setAlpha(config["alpha"].Integer());
if(!config["callback"].isNull())
anim->callback = std::bind(callbacks.at(config["callback"].String()), 0);
if(!config["frames"].isNull())
{
auto b = config["frames"]["start"].Integer();
auto e = config["frames"]["end"].Integer();
anim->set(group, b, e);
}
return anim;
}
std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode config) const
{
assert(!config.isNull());
logGlobal->debug("Building widget from config");
//overrides from variables
for(auto & item : config["overrides"].Struct())
{
logGlobal->debug("Config attribute %s was overriden by variable %s", item.first, item.second.String());
config[item.first] = variables[item.second.String()];
}
auto type = config["type"].String();
auto buildIterator = builders.find(type);
if(buildIterator != builders.end())
return (buildIterator->second)(config);
logGlobal->error("Builder with type %s is not registered", type);
return nullptr;
}

View File

@ -0,0 +1,85 @@
/*
* InterfaceBuilder.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CIntObject.h"
#include "../../lib/JsonNode.h"
class CPicture;
class CLabel;
class CToggleGroup;
class CToggleButton;
class CButton;
class CLabelGroup;
class CSlider;
class CAnimImage;
class CShowableAnim;
class CFilledTexture;
#define REGISTER_BUILDER(type, method) registerBuilder(type, std::bind(method, this, std::placeholders::_1))
class InterfaceObjectConfigurable: public CIntObject
{
public:
InterfaceObjectConfigurable(int used=0, Point offset=Point());
InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
protected:
using BuilderFunction = std::function<std::shared_ptr<CIntObject>(const JsonNode &)>;
void registerBuilder(const std::string &, BuilderFunction);
//must be called after adding callbacks
void init(const JsonNode & config);
void addCallback(const std::string & callbackName, std::function<void(int)> callback);
JsonNode variables;
template<class T>
const std::shared_ptr<T> widget(const std::string & name) const
{
auto iter = widgets.find(name);
if(iter == widgets.end())
return nullptr;
return std::dynamic_pointer_cast<T>(iter->second);
}
//basic serializers
Point readPosition(const JsonNode &) const;
Rect readRect(const JsonNode &) const;
ETextAlignment readTextAlignment(const JsonNode &) const;
SDL_Color readColor(const JsonNode &) const;
EFonts readFont(const JsonNode &) const;
std::string readText(const JsonNode &) const;
std::pair<std::string, std::string> readHintText(const JsonNode &) const;
//basic widgets
std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;
std::shared_ptr<CToggleGroup> buildToggleGroup(const JsonNode &) const;
std::shared_ptr<CToggleButton> buildToggleButton(const JsonNode &) const;
std::shared_ptr<CButton> buildButton(const JsonNode &) const;
std::shared_ptr<CLabelGroup> buildLabelGroup(const JsonNode &) const;
std::shared_ptr<CSlider> buildSlider(const JsonNode &) const;
std::shared_ptr<CAnimImage> buildImage(const JsonNode &) const;
std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
//composite widgets
std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
private:
std::map<std::string, BuilderFunction> builders;
std::map<std::string, std::shared_ptr<CIntObject>> widgets;
std::map<std::string, std::function<void(int)>> callbacks;
};

View File

@ -1,5 +1,5 @@
/*
* NotificationHandler.cpp, part of VCMI engine
* NotificationHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*

View File

@ -188,24 +188,6 @@ Uint32 CSDL_Ext::SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y
}
}
void CSDL_Ext::alphaTransform(SDL_Surface *src)
{
assert(src->format->BitsPerPixel == 8);
SDL_Color colors[] =
{
{ 0, 0, 0, 0}, { 0, 0, 0, 32}, { 0, 0, 0, 64},
{ 0, 0, 0, 128}, { 0, 0, 0, 128}
};
for (size_t i=0; i< ARRAY_COUNT(colors); i++ )
{
SDL_Color & palColor = src->format->palette->colors[i];
palColor = colors[i];
}
SDL_SetColorKey(src, SDL_TRUE, 0);
}
template<int bpp>
int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect)
{
@ -362,23 +344,17 @@ void CSDL_Ext::update(SDL_Surface * what)
logGlobal->error("%s SDL_UpdateTexture %s", __FUNCTION__, SDL_GetError());
}
template<typename Int>
Int lerp(Int a, Int b, float f)
{
return a + std::round((b - a) * f);
}
static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
{
for(int x = x1; x <= x2; x++)
{
float f = float(x - x1) / float(x2 - x1);
int y = lerp(y1, y2, f);
int y = CSDL_Ext::lerp(y1, y2, f);
uint8_t r = lerp(color1.r, color2.r, f);
uint8_t g = lerp(color1.g, color2.g, f);
uint8_t b = lerp(color1.b, color2.b, f);
uint8_t a = lerp(color1.a, color2.a, f);
uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f);
uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f);
uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f);
uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f);
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
@ -390,12 +366,12 @@ static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
for(int y = y1; y <= y2; y++)
{
float f = float(y - y1) / float(y2 - y1);
int x = lerp(x1, x2, f);
int x = CSDL_Ext::lerp(x1, x2, f);
uint8_t r = lerp(color1.r, color2.r, f);
uint8_t g = lerp(color1.g, color2.g, f);
uint8_t b = lerp(color1.b, color2.b, f);
uint8_t a = lerp(color1.a, color2.a, f);
uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f);
uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f);
uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f);
uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f);
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
@ -550,13 +526,10 @@ bool CSDL_Ext::isTransparent( SDL_Surface * srf, int x, int y )
SDL_GetRGBA(SDL_GetPixel(srf, x, y), srf->format, &color.r, &color.g, &color.b, &color.a);
// color is considered transparent here if
// a) image has aplha: less than 50% transparency
// b) no alpha: color is cyan
if (srf->format->Amask)
return color.a < 128; // almost transparent
else
return (color.r == 0 && color.g == 255 && color.b == 255);
bool pixelTransparent = color.a < 128;
bool pixelCyan = (color.r == 0 && color.g == 255 && color.b == 255);
return pixelTransparent || pixelCyan;
}
void CSDL_Ext::VflipSurf(SDL_Surface * surf)

View File

@ -49,6 +49,12 @@ inline bool isShiftKeyDown()
}
namespace CSDL_Ext
{
template<typename Int>
Int lerp(Int a, Int b, float f)
{
return a + std::round((b - a) * f);
}
//todo: should this better be assignment operator?
STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source)
{
@ -152,40 +158,54 @@ struct ColorPutter
typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Surface * dst, SDL_Rect dstRect, ui8 rotation);
/// Base class for applying palette transformation on images
class ColorShifter
{
public:
virtual ~ColorShifter() = default;
virtual SDL_Color shiftColor(SDL_Color clr) const = 0;
~ColorShifter() = default;
virtual SDL_Color shiftColor(SDL_Color input) const = 0;
};
class ColorShifterLightBlue : public ColorShifter
/// Generic class for palette transformation
/// formula:
/// result = input * factor + added
class ColorShifterMultiplyAndAdd : public ColorShifter
{
SDL_Color added;
SDL_Color factor;
public:
SDL_Color shiftColor(SDL_Color clr) const override
ColorShifterMultiplyAndAdd(SDL_Color factor, SDL_Color added) :
factor(factor),
added(added)
{}
SDL_Color shiftColor(SDL_Color input) const override
{
clr.b = clr.b + (255 - clr.b) / 2;
return clr;
return {
uint8_t(std::min(255.f, std::round(input.r * float(factor.r) / 255.f + added.r))),
uint8_t(std::min(255.f, std::round(input.g * float(factor.g) / 255.f + added.g))),
uint8_t(std::min(255.f, std::round(input.b * float(factor.b) / 255.f + added.b))),
uint8_t(std::min(255.f, std::round(input.a * float(factor.a) / 255.f + added.a)))
};
}
};
class ColorShifterDeepBlue : public ColorShifter
/// Color shifter that allows to specify color to be excempt from changes
class ColorShifterMultiplyAndAddExcept : public ColorShifterMultiplyAndAdd
{
SDL_Color ignored;
public:
SDL_Color shiftColor(SDL_Color clr) const override
{
clr.b = 255;
return clr;
}
};
ColorShifterMultiplyAndAddExcept(SDL_Color factor, SDL_Color added, SDL_Color ignored) :
ColorShifterMultiplyAndAdd(factor, added),
ignored(ignored)
{}
class ColorShifterDeepRed : public ColorShifter
{
public:
SDL_Color shiftColor(SDL_Color clr) const override
SDL_Color shiftColor(SDL_Color input) const override
{
clr.r = 255;
return clr;
if ( input.r == ignored.r && input.g == ignored.g && input.b == ignored.b && input.a == ignored.a)
return input;
return ColorShifterMultiplyAndAdd::shiftColor(input);
}
};
@ -222,7 +242,6 @@ namespace CSDL_Ext
SDL_Surface * verticalFlip(SDL_Surface * toRot); //vertical flip
SDL_Surface * horizontalFlip(SDL_Surface * toRot); //horizontal flip
Uint32 SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y, bool colorByte = false);
void alphaTransform(SDL_Surface * src); //adds transparency and shadows (partial handling only; see examples of using for details)
bool isTransparent(SDL_Surface * srf, int x, int y); //checks if surface is transparent at given position
Uint8 *getPxPtr(const SDL_Surface * const &srf, const int x, const int y);

View File

@ -18,7 +18,6 @@
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
@ -76,25 +75,25 @@ CBonusSelection::CBonusSelection()
buttonRestart = std::make_shared<CButton>(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN);
buttonBack = std::make_shared<CButton>(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE);
campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, EAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 735, 26);
labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, EAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
campaignDescription = std::make_shared<CTextBox>(getCampaign()->camp->header.description, Rect(480, 86, 286, 117), 1);
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, EAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName());
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, EAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName());
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
mapDescription = std::make_shared<CTextBox>("", Rect(480, 280, 286, 117), 1);
labelChooseBonus = std::make_shared<CLabel>(511, 432, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]);
labelChooseBonus = std::make_shared<CLabel>(511, 432, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]);
groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
flagbox = std::make_shared<CFlagBox>(Rect(486, 407, 335, 23));
std::vector<std::string> difficulty;
boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" "));
labelDifficulty = std::make_shared<CLabel>(689, 432, FONT_MEDIUM, EAlignment::TOPLEFT, Colors::WHITE, difficulty.back());
labelDifficulty = std::make_shared<CLabel>(689, 432, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, difficulty.back());
for(size_t b = 0; b < difficultyIcons.size(); ++b)
{
@ -529,7 +528,7 @@ void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState)
if(indeterminate(down))
return;
if(!down && selectable && !CSDL_Ext::isTransparent(*graphicsNotSelected, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y))
if(!down && selectable && !CSDL_Ext::isTransparent(graphicsNotSelected->getSurface(), GH.current->motion.x - pos.x, GH.current->motion.y - pos.y))
{
CSH->setCampaignMap(idOfMapAndRegion);
}
@ -539,7 +538,7 @@ void CBonusSelection::CRegion::clickRight(tribool down, bool previousState)
{
// FIXME: For some reason "down" is only ever contain indeterminate_value
auto text = CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionText;
if(!CSDL_Ext::isTransparent(*graphicsNotSelected, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y) && text.size())
if(!CSDL_Ext::isTransparent(graphicsNotSelected->getSurface(), GH.current->motion.x - pos.x, GH.current->motion.y - pos.y) && text.size())
{
CRClickPopup::createAndPush(text);
}

View File

@ -66,10 +66,10 @@ void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to)
void CSavingScreen::saveGame()
{
if(!(tabSel && tabSel->inputName && tabSel->inputName->text.size()))
if(!(tabSel && tabSel->inputName && tabSel->inputName->getText().size()))
return;
std::string path = "Saves/" + tabSel->inputName->text;
std::string path = "Saves/" + tabSel->inputName->getText();
auto overWrite = [this, path]() -> void
{
@ -82,7 +82,7 @@ void CSavingScreen::saveGame()
if(CResourceHandler::get("local")->existsResource(ResourceID(path, EResType::CLIENT_SAVEGAME)))
{
std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->text);
boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->getText());
LOCPLINT->showYesNoDialog(hlp, overWrite, nullptr);
}
else

View File

@ -21,7 +21,6 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
@ -118,8 +117,8 @@ InfoCard::InfoCard()
pos.x += 393;
pos.y += 6;
labelSaveDate = std::make_shared<CLabel>(158, 19, FONT_SMALL, TOPLEFT, Colors::WHITE);
mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, TOPLEFT, Colors::YELLOW);
labelSaveDate = std::make_shared<CLabel>(158, 19, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW);
Rect descriptionRect(26, 149, 320, 115);
mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
playerListBg = std::make_shared<CPicture>("CHATPLUG.bmp", 16, 276);
@ -127,7 +126,7 @@ InfoCard::InfoCard()
if(SEL->screenType == ESelectionScreen::campaignList)
{
labelCampaignDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
labelCampaignDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
}
else
{
@ -155,24 +154,24 @@ InfoCard::InfoCard()
}
flagbox = std::make_shared<CFlagBox>(Rect(24, 400, 335, 23));
labelMapDiff = std::make_shared<CLabel>(33, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[494]);
labelPlayerDifficulty = std::make_shared<CLabel>(133, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[492] + ":");
labelRating = std::make_shared<CLabel>(290, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[218] + ":");
labelScenarioName = std::make_shared<CLabel>(26, 22, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[495]);
labelScenarioDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
labelVictoryCondition = std::make_shared<CLabel>(26, 283, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[497]);
labelLossCondition = std::make_shared<CLabel>(26, 339, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[498]);
labelMapDiff = std::make_shared<CLabel>(33, 430, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[494]);
labelPlayerDifficulty = std::make_shared<CLabel>(133, 430, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[492] + ":");
labelRating = std::make_shared<CLabel>(290, 430, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[218] + ":");
labelScenarioName = std::make_shared<CLabel>(26, 22, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[495]);
labelScenarioDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
labelVictoryCondition = std::make_shared<CLabel>(26, 283, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[497]);
labelLossCondition = std::make_shared<CLabel>(26, 339, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[498]);
iconsVictoryCondition = std::make_shared<CAnimImage>("SCNRVICT", 0, 0, 24, 302);
iconsLossCondition = std::make_shared<CAnimImage>("SCNRLOSS", 0, 0, 24, 359);
labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, TOPLEFT, Colors::WHITE);
labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, TOPLEFT, Colors::WHITE);
labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
labelDifficulty = std::make_shared<CLabel>(62, 472, FONT_SMALL, CENTER, Colors::WHITE);
labelDifficultyPercent = std::make_shared<CLabel>(311, 472, FONT_SMALL, CENTER, Colors::WHITE);
labelDifficulty = std::make_shared<CLabel>(62, 472, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelDifficultyPercent = std::make_shared<CLabel>(311, 472, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
disableLabelRedraws();
}
setChat(false);
@ -220,8 +219,8 @@ void InfoCard::changeSelection()
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
// FIXME: We recreate them each time because CLabelGroup don't use smart pointers
labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
if(!showChat)
{
labelGroupPlayersAssigned->disable();
@ -322,9 +321,9 @@ CChatBox::CChatBox(const Rect & rect)
void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->text.size())
if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->getText().size())
{
CSH->sendMessage(inputBox->text);
CSH->sendMessage(inputBox->getText());
inputBox->setText("");
}
else
@ -334,7 +333,7 @@ void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
void CChatBox::addNewMessage(const std::string & text)
{
CCS->soundh->playSound("CHAT");
chatHistory->setText(chatHistory->label->text + text + "\n");
chatHistory->setText(chatHistory->label->getText() + text + "\n");
if(chatHistory->slider)
chatHistory->slider->moveToMax();
}
@ -347,8 +346,8 @@ CFlagBox::CFlagBox(const Rect & rect)
pos.h = rect.h;
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
labelAllies = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
labelEnemies = std::make_shared<CLabel>(133, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
labelAllies = std::make_shared<CLabel>(0, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
labelEnemies = std::make_shared<CLabel>(133, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
iconsTeamFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
iconsTeamFlags->preload();
@ -390,8 +389,8 @@ CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr<CAnimation> ico
pos.w = 256;
pos.h = 90 + 50 * SEL->getMapInfo()->mapHeader->howManyTeams;
labelTeamAlignment = std::make_shared<CLabel>(128, 30, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[657]);
labelGroupTeams = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelTeamAlignment = std::make_shared<CLabel>(128, 30, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[657]);
labelGroupTeams = std::make_shared<CLabelGroup>(FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
for(int i = 0; i < SEL->getMapInfo()->mapHeader->howManyTeams; i++)
{
std::vector<ui8> flags;

View File

@ -12,7 +12,6 @@
#include "CSelectionBase.h"
#include "OptionsTab.h"
#include "../CBitmapHandler.h"
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
@ -39,18 +38,18 @@ OptionsTab::OptionsTab() : humanPlayers(0)
OBJ_CONSTRUCTION;
background = std::make_shared<CPicture>("ADVOPTBK", 0, 6);
pos = background->pos;
labelTitle = std::make_shared<CLabel>(222, 30, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[515]);
labelSubTitle = std::make_shared<CMultiLineLabel>(Rect(60, 44, 320, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[516]);
labelTitle = std::make_shared<CLabel>(222, 30, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[515]);
labelSubTitle = std::make_shared<CMultiLineLabel>(Rect(60, 44, 320, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[516]);
labelPlayerNameAndHandicap = std::make_shared<CMultiLineLabel>(Rect(58, 86, 100, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[517]);
labelStartingTown = std::make_shared<CMultiLineLabel>(Rect(163, 86, 70, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[518]);
labelStartingHero = std::make_shared<CMultiLineLabel>(Rect(239, 86, 70, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[519]);
labelStartingBonus = std::make_shared<CMultiLineLabel>(Rect(315, 86, 70, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[520]);
labelPlayerNameAndHandicap = std::make_shared<CMultiLineLabel>(Rect(58, 86, 100, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[517]);
labelStartingTown = std::make_shared<CMultiLineLabel>(Rect(163, 86, 70, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[518]);
labelStartingHero = std::make_shared<CMultiLineLabel>(Rect(239, 86, 70, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[519]);
labelStartingBonus = std::make_shared<CMultiLineLabel>(Rect(315, 86, 70, (int)graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[520]);
if(SEL->screenType == ESelectionScreen::newGame || SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::scenarioInfo)
{
sliderTurnDuration = std::make_shared<CSlider>(Point(55, 551), 194, std::bind(&IServerAPI::setTurnLength, CSH, _1), 1, (int)GameConstants::POSSIBLE_TURNTIME.size(), (int)GameConstants::POSSIBLE_TURNTIME.size(), true, CSlider::BLUE);
labelPlayerTurnDuration = std::make_shared<CLabel>(222, 538, FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[521]);
labelTurnDurationValue = std::make_shared<CLabel>(319, 559, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelPlayerTurnDuration = std::make_shared<CLabel>(222, 538, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[521]);
labelTurnDurationValue = std::make_shared<CLabel>(319, 559, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
}
}
@ -369,8 +368,8 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeader()
backgroundTexture = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
updateShadow();
labelTitle = std::make_shared<CLabel>(pos.w / 2 + 8, 21, FONT_MEDIUM, CENTER, Colors::YELLOW, getTitle());
labelSubTitle = std::make_shared<CLabel>(pos.w / 2, 88, FONT_SMALL, CENTER, Colors::WHITE, getSubtitle());
labelTitle = std::make_shared<CLabel>(pos.w / 2 + 8, 21, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, getTitle());
labelSubTitle = std::make_shared<CLabel>(pos.w / 2, 88, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getSubtitle());
image = std::make_shared<CAnimImage>(getImageName(), getImageIndex(), 0, pos.w / 2 - 24, 45);
}
@ -378,7 +377,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
{
pos = Rect(0, 0, 228, 290);
genHeader();
labelAssociatedCreatures = std::make_shared<CLabel>(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
labelAssociatedCreatures = std::make_shared<CLabel>(pos.w / 2 + 8, 122, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle;
std::vector<std::shared_ptr<CComponent>> components;
const CTown * town = (*CGI->townh)[factionIndex]->town;
@ -395,11 +394,11 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()
{
pos = Rect(0, 0, 292, 226);
genHeader();
labelHeroSpeciality = std::make_shared<CLabel>(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
labelHeroSpeciality = std::make_shared<CLabel>(pos.w / 2 + 4, 117, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
auto heroIndex = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
imageSpeciality = std::make_shared<CAnimImage>("UN44", (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134);
labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->specName);
labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->specName);
}
void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
@ -407,7 +406,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
pos = Rect(0, 0, 228, 162);
genHeader();
textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, CENTER, Colors::WHITE);
textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
}
OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
@ -416,7 +415,7 @@ OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings,
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
image = std::make_shared<CAnimImage>(getImageName(), getImageIndex());
subtitle = std::make_shared<CLabel>(23, 39, FONT_TINY, CENTER, Colors::WHITE, getName());
subtitle = std::make_shared<CLabel>(23, 39, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, getName());
pos = image->pos;
}
@ -468,20 +467,20 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
else
whoCanPlay = HUMAN;
static const char * flags[] =
{
static const std::array<std::string, PlayerColor::PLAYER_LIMIT_I> flags =
{{
"AOFLGBR.DEF", "AOFLGBB.DEF", "AOFLGBY.DEF", "AOFLGBG.DEF",
"AOFLGBO.DEF", "AOFLGBP.DEF", "AOFLGBT.DEF", "AOFLGBS.DEF"
};
static const char * bgs[] =
{
}};
static const std::array<std::string, PlayerColor::PLAYER_LIMIT_I> bgs =
{{
"ADOPRPNL.bmp", "ADOPBPNL.bmp", "ADOPYPNL.bmp", "ADOPGPNL.bmp",
"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"
};
}};
background = std::make_shared<CPicture>(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, s.name);
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
background = std::make_shared<CPicture>(bgs[s.color.getNum()], 0, 0);
labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, s.name);
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
if(SEL->screenType == ESelectionScreen::newGame)
{

View File

@ -27,127 +27,68 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/rmg/CMapGenOptions.h"
#include "../../lib/CModHandler.h"
#include "../../lib/rmg/CRmgTemplateStorage.h"
RandomMapTab::RandomMapTab()
RandomMapTab::RandomMapTab():
InterfaceObjectConfigurable()
{
recActions = 0;
mapGenOptions = std::make_shared<CMapGenOptions>();
OBJ_CONSTRUCTION;
background = std::make_shared<CPicture>("RANMAPBK", 0, 6);
labelHeadlineBig = std::make_shared<CLabel>(222, 36, FONT_BIG, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[738]);
labelHeadlineSmall = std::make_shared<CLabel>(222, 56, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[739]);
labelMapSize = std::make_shared<CLabel>(104, 97, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[752]);
groupMapSize = std::make_shared<CToggleGroup>(0);
groupMapSize->pos.y += 81;
groupMapSize->pos.x += 158;
const std::vector<std::string> mapSizeBtns = {"RANSIZS", "RANSIZM", "RANSIZL", "RANSIZX"};
addButtonsToGroup(groupMapSize.get(), mapSizeBtns, 0, 3, 47, 198);
groupMapSize->setSelected(1);
groupMapSize->addCallback([&](int btnId)
const JsonNode config(ResourceID("config/widgets/randomMapTab.json"));
addCallback("toggleMapSize", [&](int btnId)
{
auto mapSizeVal = getPossibleMapSizes();
mapGenOptions->setWidth(mapSizeVal[btnId]);
mapGenOptions->setHeight(mapSizeVal[btnId]);
if(mapGenOptions->getMapTemplate())
if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}))
setTemplate(nullptr);
updateMapInfoByHost();
});
buttonTwoLevels = std::make_shared<CToggleButton>(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
buttonTwoLevels->setSelected(true);
buttonTwoLevels->addCallback([&](bool on)
addCallback("toggleTwoLevels", [&](bool on)
{
mapGenOptions->setHasTwoLevels(on);
if(mapGenOptions->getMapTemplate())
if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}))
setTemplate(nullptr);
updateMapInfoByHost();
});
labelGroupForOptions = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE);
// Create number defs list
std::vector<std::string> numberDefs;
for(int i = 0; i <= 8; ++i)
{
numberDefs.push_back("RANNUM" + boost::lexical_cast<std::string>(i));
}
const int NUMBERS_WIDTH = 32;
const int BTNS_GROUP_LEFT_MARGIN = 67;
labelGroupForOptions->add(68, 133, CGI->generaltexth->allTexts[753]);
groupMaxPlayers = std::make_shared<CToggleGroup>(0);
groupMaxPlayers->pos.y += 153;
groupMaxPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupMaxPlayers.get(), numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
groupMaxPlayers->addCallback([&](int btnId)
addCallback("setPlayersCount", [&](int btnId)
{
mapGenOptions->setPlayerCount(btnId);
deactivateButtonsFrom(groupMaxTeams.get(), btnId);
// deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
deactivateButtonsFrom(groupCompOnlyPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
validatePlayersCnt(btnId);
setMapGenOptions(mapGenOptions);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 199, CGI->generaltexth->allTexts[754]);
groupMaxTeams = std::make_shared<CToggleGroup>(0);
groupMaxTeams->pos.y += 219;
groupMaxTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupMaxTeams.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
groupMaxTeams->addCallback([&](int btnId)
addCallback("setTeamsCount", [&](int btnId)
{
mapGenOptions->setTeamCount(btnId);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 265, CGI->generaltexth->allTexts[755]);
groupCompOnlyPlayers = std::make_shared<CToggleGroup>(0);
groupCompOnlyPlayers->pos.y += 285;
groupCompOnlyPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupCompOnlyPlayers.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
groupCompOnlyPlayers->addCallback([&](int btnId)
addCallback("setCompOnlyPlayers", [&](int btnId)
{
mapGenOptions->setCompOnlyPlayerCount(btnId);
// deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
deactivateButtonsFrom(groupMaxPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId));
validateCompOnlyPlayersCnt(btnId);
setMapGenOptions(mapGenOptions);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 331, CGI->generaltexth->allTexts[756]);
groupCompOnlyTeams = std::make_shared<CToggleGroup>(0);
groupCompOnlyTeams->pos.y += 351;
groupCompOnlyTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupCompOnlyTeams.get(), numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
deactivateButtonsFrom(groupCompOnlyTeams.get(), 1);
groupCompOnlyTeams->addCallback([&](int btnId)
addCallback("setCompOnlyTeams", [&](int btnId)
{
mapGenOptions->setCompOnlyTeamCount(btnId);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 398, CGI->generaltexth->allTexts[757]);
const int WIDE_BTN_WIDTH = 85;
groupWaterContent = std::make_shared<CToggleGroup>(0);
groupWaterContent->pos.y += 419;
groupWaterContent->pos.x += BTNS_GROUP_LEFT_MARGIN;
const std::vector<std::string> waterContentBtns = {"RANNONE", "RANNORM", "RANISLD"};
addButtonsWithRandToGroup(groupWaterContent.get(), waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
groupWaterContent->addCallback([&](int btnId)
addCallback("setWaterContent", [&](int btnId)
{
mapGenOptions->setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 465, CGI->generaltexth->allTexts[758]);
groupMonsterStrength = std::make_shared<CToggleGroup>(0);
groupMonsterStrength->pos.y += 485;
groupMonsterStrength->pos.x += BTNS_GROUP_LEFT_MARGIN;
const std::vector<std::string> monsterStrengthBtns = {"RANWEAK", "RANNORM", "RANSTRG"};
addButtonsWithRandToGroup(groupMonsterStrength.get(), monsterStrengthBtns, 2, 4, WIDE_BTN_WIDTH, 248, 251, EMonsterStrength::RANDOM, false);
groupMonsterStrength->addCallback([&](int btnId)
addCallback("setMonsterStrength", [&](int btnId)
{
if(btnId < 0)
mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
@ -155,9 +96,31 @@ RandomMapTab::RandomMapTab()
mapGenOptions->setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId)); //value 2 to 4
updateMapInfoByHost();
});
buttonShowRandomMaps = std::make_shared<CButton>(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
//new callbacks available only from mod
addCallback("templateSelection", [&](int)
{
GH.pushIntT<TemplatesDropBox>(*this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()});
});
addCallback("teamAlignments", [&](int)
{
GH.pushIntT<TeamAlignmentsWidget>(*this);
});
for(auto road : VLC->terrainTypeHandler->roads())
{
std::string cbRoadType = "selectRoad_" + road.name;
addCallback(cbRoadType, [&, road](bool on)
{
mapGenOptions->setRoadEnabled(road.name, on);
updateMapInfoByHost();
});
}
init(config);
updateMapInfoByHost();
}
@ -192,6 +155,7 @@ void RandomMapTab::updateMapInfoByHost()
mapInfo->mapHeader->howManyTeams = playersToGen;
std::set<TeamID> occupiedTeams;
for(int i = 0; i < playersToGen; ++i)
{
PlayerInfo player;
@ -205,60 +169,153 @@ void RandomMapTab::updateMapInfoByHost()
{
player.canHumanPlay = true;
}
player.team = TeamID(i);
auto team = mapGenOptions->getPlayersSettings().at(PlayerColor(i)).getTeam();
player.team = team;
occupiedTeams.insert(team);
player.hasMainTown = true;
player.generateHeroAtMainTown = true;
mapInfo->mapHeader->players.push_back(player);
}
for(auto & player : mapInfo->mapHeader->players)
{
for(int i = 0; player.team == TeamID::NO_TEAM; ++i)
{
TeamID team(i);
if(!occupiedTeams.count(team))
{
player.team = team;
occupiedTeams.insert(team);
}
}
}
mapInfoChanged(mapInfo, mapGenOptions);
}
void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
{
groupMapSize->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
buttonTwoLevels->setSelected(opts->getHasTwoLevels());
groupMaxPlayers->setSelected(opts->getPlayerCount());
groupMaxTeams->setSelected(opts->getTeamCount());
groupCompOnlyPlayers->setSelected(opts->getCompOnlyPlayerCount());
groupCompOnlyTeams->setSelected(opts->getCompOnlyTeamCount());
groupWaterContent->setSelected(opts->getWaterContent());
groupMonsterStrength->setSelected(opts->getMonsterStrength());
}
void RandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex, int randIndex, bool animIdfromBtnId) const
{
addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex, animIdfromBtnId);
// Buttons are relative to button group, TODO better solution?
SObjectConstruction obj__i(group);
const std::string RANDOM_DEF = "RANRAND";
group->addToggle(randIndex, std::make_shared<CToggleButton>(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
group->setSelected(randIndex);
}
void RandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, bool animIdfromBtnId) const
{
// Buttons are relative to button group, TODO better solution?
SObjectConstruction obj__i(group);
int cnt = nEnd - nStart + 1;
for(int i = 0; i < cnt; ++i)
mapGenOptions = opts;
//prepare allowed options
for(int i = 0; i <= PlayerColor::PLAYER_LIMIT_I; ++i)
{
auto button = std::make_shared<CToggleButton>(Point(i * btnWidth, 0), animIdfromBtnId ? defs[i + nStart] : defs[i], CGI->generaltexth->zelp[helpStartIndex + i]);
// For blocked state we should use pressed image actually
button->setImageOrder(0, 1, 1, 3);
group->addToggle(i + nStart, button);
playerCountAllowed.insert(i);
compCountAllowed.insert(i);
playerTeamsAllowed.insert(i);
compTeamsAllowed.insert(i);
}
auto * tmpl = mapGenOptions->getMapTemplate();
if(tmpl)
{
playerCountAllowed = tmpl->getPlayers().getNumbers();
compCountAllowed = tmpl->getCpuPlayers().getNumbers();
}
if(mapGenOptions->getPlayerCount() != CMapGenOptions::RANDOM_SIZE)
{
vstd::erase_if(compCountAllowed,
[opts](int el){
return PlayerColor::PLAYER_LIMIT_I - opts->getPlayerCount() < el;
});
vstd::erase_if(playerTeamsAllowed,
[opts](int el){
return opts->getPlayerCount() <= el;
});
if(!playerTeamsAllowed.count(opts->getTeamCount()))
opts->setTeamCount(CMapGenOptions::RANDOM_SIZE);
}
if(mapGenOptions->getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE)
{
vstd::erase_if(playerCountAllowed,
[opts](int el){
return PlayerColor::PLAYER_LIMIT_I - opts->getCompOnlyPlayerCount() < el;
});
vstd::erase_if(compTeamsAllowed,
[opts](int el){
return opts->getCompOnlyPlayerCount() <= el;
});
if(!compTeamsAllowed.count(opts->getCompOnlyTeamCount()))
opts->setCompOnlyTeamCount(CMapGenOptions::RANDOM_SIZE);
}
if(auto w = widget<CToggleGroup>("groupMapSize"))
w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
if(auto w = widget<CToggleButton>("buttonTwoLevels"))
w->setSelected(opts->getHasTwoLevels());
if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
{
w->setSelected(opts->getPlayerCount());
deactivateButtonsFrom(*w, playerCountAllowed);
}
if(auto w = widget<CToggleGroup>("groupMaxTeams"))
{
w->setSelected(opts->getTeamCount());
deactivateButtonsFrom(*w, playerTeamsAllowed);
}
if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
{
w->setSelected(opts->getCompOnlyPlayerCount());
deactivateButtonsFrom(*w, compCountAllowed);
}
if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
{
w->setSelected(opts->getCompOnlyTeamCount());
deactivateButtonsFrom(*w, compTeamsAllowed);
}
if(auto w = widget<CToggleGroup>("groupWaterContent"))
{
w->setSelected(opts->getWaterContent());
if(opts->getMapTemplate())
{
std::set<int> allowedWater(opts->getMapTemplate()->getWaterContentAllowed().begin(), opts->getMapTemplate()->getWaterContentAllowed().end());
deactivateButtonsFrom(*w, allowedWater);
}
else
deactivateButtonsFrom(*w, {-1});
}
if(auto w = widget<CToggleGroup>("groupMonsterStrength"))
w->setSelected(opts->getMonsterStrength());
if(auto w = widget<CButton>("templateButton"))
{
if(tmpl)
w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
else
w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
}
for(auto r : VLC->terrainTypeHandler->roads())
{
if(auto w = widget<CToggleButton>(r.name))
{
w->setSelected(opts->isRoadEnabled(r.name));
}
}
}
void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
{
logGlobal->debug("Blocking buttons from %d", startId);
for(auto toggle : group->buttons)
mapGenOptions->setMapTemplate(tmpl);
setMapGenOptions(mapGenOptions);
if(auto w = widget<CButton>("templateButton"))
{
if(tmpl)
w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
else
w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
}
updateMapInfoByHost();
}
void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed)
{
logGlobal->debug("Blocking buttons");
for(auto toggle : group.buttons)
{
if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
{
if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
if(allowed.count(CMapGenOptions::RANDOM_SIZE)
|| allowed.count(toggle.first)
|| toggle.first == CMapGenOptions::RANDOM_SIZE)
{
button->block(false);
}
@ -270,45 +327,243 @@ void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
}
}
void RandomMapTab::validatePlayersCnt(int playersCnt)
{
if(playersCnt == CMapGenOptions::RANDOM_SIZE)
{
return;
}
if(mapGenOptions->getTeamCount() >= playersCnt)
{
mapGenOptions->setTeamCount(playersCnt - 1);
groupMaxTeams->setSelected(mapGenOptions->getTeamCount());
}
// total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3)
if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I)
{
mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
groupCompOnlyPlayers->setSelected(mapGenOptions->getCompOnlyPlayerCount());
}
validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
}
void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
{
if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
{
return;
}
if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
{
int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
updateMapInfoByHost();
groupCompOnlyTeams->setSelected(compOnlyTeamCount);
}
}
std::vector<int> RandomMapTab::getPossibleMapSizes()
{
return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE};
return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
}
TemplatesDropBox::ListItem::ListItem(const JsonNode & config, TemplatesDropBox & _dropBox, Point position)
: InterfaceObjectConfigurable(LCLICK | HOVER, position),
dropBox(_dropBox)
{
OBJ_CONSTRUCTION;
init(config);
if(auto w = widget<CPicture>("hoverImage"))
{
pos.w = w->pos.w;
pos.h = w->pos.h;
}
type |= REDRAW_PARENT;
}
void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item)
{
if(auto w = widget<CLabel>("labelName"))
{
item = _item;
if(item)
{
w->setText(item->getName());
}
else
{
if(idx)
w->setText("");
else
w->setText(readText(dropBox.variables["defaultTemplate"]));
}
}
}
void TemplatesDropBox::ListItem::hover(bool on)
{
auto h = widget<CPicture>("hoverImage");
auto w = widget<CLabel>("labelName");
if(h && w)
{
if(w->getText().empty())
{
hovered = false;
h->visible = false;
}
else
{
h->visible = on;
}
}
redraw();
}
void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
{
if(down && hovered)
{
dropBox.setTemplate(item);
}
}
TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
InterfaceObjectConfigurable(LCLICK | HOVER),
randomMapTab(randomMapTab)
{
REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem);
curItems = VLC->tplh->getTemplates();
vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);});
curItems.insert(curItems.begin(), nullptr); //default template
const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json"));
addCallback("sliderMove", std::bind(&TemplatesDropBox::sliderMove, this, std::placeholders::_1));
OBJ_CONSTRUCTION;
pos = randomMapTab.pos.topLeft();
pos.w = randomMapTab.pos.w;
pos.h = randomMapTab.pos.h;
init(config);
if(auto w = widget<CSlider>("slider"))
{
w->setAmount(curItems.size());
}
updateListItems();
}
std::shared_ptr<CIntObject> TemplatesDropBox::buildListItem(const JsonNode & config)
{
auto position = readPosition(config["position"]);
listItems.push_back(std::make_shared<ListItem>(config, *this, position));
return listItems.back();
}
void TemplatesDropBox::sliderMove(int slidPos)
{
auto w = widget<CSlider>("slider");
if(!w)
return; // ignore spurious call when slider is being created
updateListItems();
redraw();
}
void TemplatesDropBox::hover(bool on)
{
hovered = on;
}
void TemplatesDropBox::clickLeft(tribool down, bool previousState)
{
if(down && !hovered)
{
assert(GH.topInt().get() == this);
GH.popInt(GH.topInt());
}
}
void TemplatesDropBox::updateListItems()
{
if(auto w = widget<CSlider>("slider"))
{
int elemIdx = w->getValue();
for(auto item : listItems)
{
if(elemIdx < curItems.size())
{
item->updateItem(elemIdx, curItems[elemIdx]);
elemIdx++;
}
else
{
item->updateItem(elemIdx);
}
}
}
}
void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
{
randomMapTab.setTemplate(tmpl);
assert(GH.topInt().get() == this);
GH.popInt(GH.topInt());
}
TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
InterfaceObjectConfigurable(),
randomMapTab(randomMapTab)
{
const JsonNode config(ResourceID("config/widgets/randomMapTeamsWidget.json"));
variables = config["variables"];
int humanPlayers = randomMapTab.obtainMapGenOptions().getPlayerCount();
int cpuPlayers = randomMapTab.obtainMapGenOptions().getCompOnlyPlayerCount();
int totalPlayers = humanPlayers == CMapGenOptions::RANDOM_SIZE || cpuPlayers == CMapGenOptions::RANDOM_SIZE
? PlayerColor::PLAYER_LIMIT_I : humanPlayers + cpuPlayers;
assert(totalPlayers <= PlayerColor::PLAYER_LIMIT_I);
auto settings = randomMapTab.obtainMapGenOptions().getPlayersSettings();
variables["totalPlayers"].Integer() = totalPlayers;
pos.w = variables["windowSize"]["x"].Integer() + totalPlayers * variables["cellMargin"]["x"].Integer();
pos.h = variables["windowSize"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
variables["backgroundRect"]["x"].Integer() = pos.x;
variables["backgroundRect"]["y"].Integer() = pos.y;
variables["backgroundRect"]["w"].Integer() = pos.w;
variables["backgroundRect"]["h"].Integer() = pos.h;
variables["okButtonPosition"]["x"].Integer() = variables["buttonsOffset"]["ok"]["x"].Integer();
variables["okButtonPosition"]["y"].Integer() = variables["buttonsOffset"]["ok"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
variables["cancelButtonPosition"]["x"].Integer() = variables["buttonsOffset"]["cancel"]["x"].Integer();
variables["cancelButtonPosition"]["y"].Integer() = variables["buttonsOffset"]["cancel"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
addCallback("ok", [&](int)
{
for(int plId = 0; plId < players.size(); ++plId)
{
randomMapTab.obtainMapGenOptions().setPlayerTeam(PlayerColor(plId), TeamID(players[plId]->getSelected()));
}
randomMapTab.updateMapInfoByHost();
assert(GH.topInt().get() == this);
GH.popInt(GH.topInt());
});
addCallback("cancel", [&](int)
{
assert(GH.topInt().get() == this);
GH.popInt(GH.topInt());
});
init(config);
center(pos);
OBJ_CONSTRUCTION;
for(int plId = 0; plId < totalPlayers; ++plId)
{
players.push_back(std::make_shared<CToggleGroup>([&, totalPlayers, plId](int sel)
{
variables["player_id"].Integer() = plId;
OBJ_CONSTRUCTION_TARGETED(players[plId].get());
for(int teamId = 0; teamId < totalPlayers; ++teamId)
{
auto button = std::dynamic_pointer_cast<CToggleButton>(players[plId]->buttons[teamId]);
assert(button);
if(sel == teamId)
{
button->addOverlay(buildWidget(variables["flagsAnimation"]));
}
else
{
button->addOverlay(nullptr);
}
}
}));
OBJ_CONSTRUCTION_TARGETED(players.back().get());
for(int teamId = 0; teamId < totalPlayers; ++teamId)
{
variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer();
variables["point"]["y"].Integer() = variables["cellOffset"]["y"].Integer() + teamId * variables["cellMargin"]["y"].Integer();
auto button = buildWidget(variables["button"]);
players.back()->addToggle(teamId, std::dynamic_pointer_cast<CToggleBase>(button));
}
auto team = settings.at(PlayerColor(plId)).getTeam();
if(team == TeamID::NO_TEAM)
players.back()->setSelected(plId);
else
players.back()->setSelected(team.getNum());
}
}

View File

@ -13,6 +13,8 @@
#include "../../lib/FunctionList.h"
#include "../../lib/GameConstants.h"
#include "../../lib/rmg/CRmgTemplate.h"
#include "../gui/InterfaceObjectConfigurable.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -23,42 +25,80 @@ VCMI_LIB_NAMESPACE_END
class CToggleButton;
class CLabel;
class CLabelGroup;
class CSlider;
class CPicture;
class RandomMapTab : public CIntObject
class RandomMapTab : public InterfaceObjectConfigurable
{
public:
RandomMapTab();
void updateMapInfoByHost();
void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
void setTemplate(const CRmgTemplate *);
CMapGenOptions & obtainMapGenOptions() {return *mapGenOptions;}
CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
private:
void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex, int randIndex = -1, bool animIdfromBtnId = true) const;
void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, bool animIdfromBtnId = true) const;
void deactivateButtonsFrom(CToggleGroup * group, int startId);
void validatePlayersCnt(int playersCnt);
void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
void deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed);
std::vector<int> getPossibleMapSizes();
std::shared_ptr<CPicture> background;
std::shared_ptr<CLabel> labelHeadlineBig;
std::shared_ptr<CLabel> labelHeadlineSmall;
std::shared_ptr<CLabel> labelMapSize;
std::shared_ptr<CToggleGroup> groupMapSize;
std::shared_ptr<CToggleButton> buttonTwoLevels;
std::shared_ptr<CLabelGroup> labelGroupForOptions;
std::shared_ptr<CToggleGroup> groupMaxPlayers;
std::shared_ptr<CToggleGroup> groupMaxTeams;
std::shared_ptr<CToggleGroup> groupCompOnlyPlayers;
std::shared_ptr<CToggleGroup> groupCompOnlyTeams;
std::shared_ptr<CToggleGroup> groupWaterContent;
std::shared_ptr<CToggleGroup> groupMonsterStrength;
std::shared_ptr<CButton> buttonShowRandomMaps;
std::shared_ptr<CMapGenOptions> mapGenOptions;
std::shared_ptr<CMapInfo> mapInfo;
//options allowed - need to store as impact each other
std::set<int> playerCountAllowed, playerTeamsAllowed, compCountAllowed, compTeamsAllowed;
};
class TemplatesDropBox : public InterfaceObjectConfigurable
{
struct ListItem : public InterfaceObjectConfigurable
{
TemplatesDropBox & dropBox;
const CRmgTemplate * item = nullptr;
ListItem(const JsonNode &, TemplatesDropBox &, Point position);
void updateItem(int index, const CRmgTemplate * item = nullptr);
void hover(bool on) override;
void clickLeft(tribool down, bool previousState) override;
};
friend struct ListItem;
public:
TemplatesDropBox(RandomMapTab & randomMapTab, int3 size);
void hover(bool on) override;
void clickLeft(tribool down, bool previousState) override;
void setTemplate(const CRmgTemplate *);
private:
std::shared_ptr<CIntObject> buildListItem(const JsonNode & config);
void sliderMove(int slidPos);
void updateListItems();
RandomMapTab & randomMapTab;
std::vector<std::shared_ptr<ListItem>> listItems;
std::vector<const CRmgTemplate *> curItems;
};
class TeamAlignmentsWidget: public InterfaceObjectConfigurable
{
public:
TeamAlignmentsWidget(RandomMapTab & randomMapTab);
private:
RandomMapTab & randomMapTab;
std::shared_ptr<CFilledTexture> background;
std::shared_ptr<CLabelGroup> labels;
std::shared_ptr<CButton> buttonOk, buttonCancel;
std::vector<std::shared_ptr<CToggleGroup>> players;
std::vector<std::shared_ptr<CIntObject>> placeholders;
};

View File

@ -15,7 +15,6 @@
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
@ -142,7 +141,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
pos = background->pos;
inputName = std::make_shared<CTextInput>(inputNameRect, Point(-32, -25), "GSSTRIP.bmp", 0);
inputName->filters += CTextInput::filenameFilter;
labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
int sizes[] = {36, 72, 108, 144, 0};
const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
@ -200,7 +199,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
for(int i = 0; i < positionsToShow; i++)
listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition));
labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, EAlignment::CENTER, Colors::YELLOW, tabTitle);
labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle);
slider = std::make_shared<CSlider>(Point(372, 86), tabType != ESelectionScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, false, CSlider::BLUE);
filter(0);
}
@ -623,13 +622,13 @@ SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> ico
: CIntObject(LCLICK, position)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelName->setAutoRedraw(false);
labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelAmountOfPlayers->setAutoRedraw(false);
labelNumberOfCampaignMaps = std::make_shared<CLabel>(8, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelNumberOfCampaignMaps = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelNumberOfCampaignMaps->setAutoRedraw(false);
labelMapSizeLetter = std::make_shared<CLabel>(41, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelMapSizeLetter = std::make_shared<CLabel>(41, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelMapSizeLetter->setAutoRedraw(false);
// FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise
iconFormat = std::make_shared<CAnimImage>(iconsFormats, 0, 0, 59, -12);

View File

@ -15,7 +15,6 @@
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
@ -105,7 +104,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
addUsedEvents(LCLICK | HOVER);
graphicsImage = std::make_shared<CPicture>(config["image"].String());
hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, CENTER, Colors::YELLOW, "");
hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, "");
parent->addChild(hoverLabel.get());
}

View File

@ -37,7 +37,6 @@
#include "../CPlayerInterface.h"
#include "../../CCallback.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../Client.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CAnimation.h"
@ -370,13 +369,14 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType)
: screenType(ScreenType)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
background = std::make_shared<CPicture>("MUPOPUP.bmp");
background->convertToScreenBPP(); //so we could draw without problems
blitAt(CPicture("MUMAP.bmp"), 16, 77, *background);
pos = background->center(); //center, window has size of bg graphic
statusBar = CGStatusBar::create(std::make_shared<CPicture>(Rect(7, 465, 440, 18), 0)); //226, 472
playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), *background);
picture = std::make_shared<CPicture>("MUMAP.bmp", 16, 77);
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 465, 440, 18), 7, 465));
playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), background->getSurface());
playerName->setText(settings["general"]["playerName"].String());
playerName->cb += std::bind(&CMultiMode::onNameChange, this, _1);
@ -415,17 +415,17 @@ CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen S
std::string text = CGI->generaltexth->allTexts[446];
boost::replace_all(text, "\t", "\n");
textTitle = std::make_shared<CTextBox>(text, Rect(25, 20, 315, 50), 0, FONT_BIG, CENTER, Colors::WHITE); //HOTSEAT Please enter names
textTitle = std::make_shared<CTextBox>(text, Rect(25, 20, 315, 50), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE); //HOTSEAT Please enter names
for(int i = 0; i < inputNames.size(); i++)
{
inputNames[i] = std::make_shared<CTextInput>(Rect(60, 85 + i * 30, 280, 16), *background);
inputNames[i] = std::make_shared<CTextInput>(Rect(60, 85 + i * 30, 280, 16), background->getSurface());
inputNames[i]->cb += std::bind(&CMultiPlayers::onChange, this, _1);
}
buttonOk = std::make_shared<CButton>(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), SDLK_RETURN);
buttonCancel = std::make_shared<CButton>(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [=](){ close();}, SDLK_ESCAPE);
statusBar = CGStatusBar::create(std::make_shared<CPicture>(Rect(7, 381, 348, 18), 0)); //226, 472
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 381, 348, 18), 7, 381));
inputNames[0]->setText(firstPlayer, true);
#ifndef VCMI_IOS
@ -438,7 +438,7 @@ void CMultiPlayers::onChange(std::string newText)
size_t namesCount = 0;
for(auto & elem : inputNames)
if(!elem->text.empty())
if(!elem->getText().empty())
namesCount++;
}
@ -447,8 +447,8 @@ void CMultiPlayers::enterSelectionScreen()
std::vector<std::string> names;
for(auto name : inputNames)
{
if(name->text.length())
names.push_back(name->text);
if(name->getText().length())
names.push_back(name->getText());
}
Settings name = settings.write["general"]["playerName"];
@ -463,9 +463,9 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
background = std::make_shared<CPicture>("MUDIALOG.bmp"); // address background
pos = background->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212)
textTitle = std::make_shared<CTextBox>("", Rect(20, 20, 205, 50), 0, FONT_BIG, CENTER, Colors::WHITE);
inputAddress = std::make_shared<CTextInput>(Rect(25, 68, 175, 16), *background.get());
inputPort = std::make_shared<CTextInput>(Rect(25, 115, 175, 16), *background.get());
textTitle = std::make_shared<CTextBox>("", Rect(20, 20, 205, 50), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE);
inputAddress = std::make_shared<CTextInput>(Rect(25, 68, 175, 16), background->getSurface());
inputPort = std::make_shared<CTextInput>(Rect(25, 115, 175, 16), background->getSurface());
if(host && !settings["session"]["donotstartserver"].Bool())
{
textTitle->setText("Connecting...");
@ -485,7 +485,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
inputPort->setText(boost::lexical_cast<std::string>(CSH->getHostPort()), true);
buttonCancel = std::make_shared<CButton>(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), SDLK_ESCAPE);
statusBar = CGStatusBar::create(std::make_shared<CPicture>(Rect(7, 186, 218, 18), 0));
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
}
void CSimpleJoinScreen::connectToServer()
@ -494,7 +494,7 @@ void CSimpleJoinScreen::connectToServer()
buttonOk->block(true);
CSDL_Ext::stopTextInput();
boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->text, boost::lexical_cast<ui16>(inputPort->text));
boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
}
void CSimpleJoinScreen::leaveScreen()
@ -512,7 +512,7 @@ void CSimpleJoinScreen::leaveScreen()
void CSimpleJoinScreen::onChange(const std::string & newText)
{
buttonOk->block(inputAddress->text.empty() || inputPort->text.empty());
buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty());
}
void CSimpleJoinScreen::connectThread(const std::string addr, const ui16 port)

View File

@ -80,6 +80,7 @@ class CMultiMode : public WindowBase
public:
ESelectionScreen screenType;
std::shared_ptr<CPicture> background;
std::shared_ptr<CPicture> picture;
std::shared_ptr<CTextInput> playerName;
std::shared_ptr<CButton> buttonHotseat;
std::shared_ptr<CButton> buttonHost;

View File

@ -33,7 +33,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog
// MPTODO: Custom campaign crashing on this?
// voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));
text = std::make_shared<CMultiLineLabel>(Rect(100, 500, 600, 100), EFonts::FONT_BIG, CENTER, Colors::METALLIC_GOLD, spe.prologText);
text = std::make_shared<CMultiLineLabel>(Rect(100, 500, 600, 100), EFonts::FONT_BIG, ETextAlignment::CENTER, Colors::METALLIC_GOLD, spe.prologText);
text->scrollTextTo(-100);
}

View File

@ -30,7 +30,7 @@ CreditsScreen::CreditsScreen(Rect rect)
std::string text((char *)textFile.first.get(), textFile.second);
size_t firstQuote = text.find('\"') + 1;
text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote);
credits = std::make_shared<CMultiLineLabel>(Rect(pos.w - 350, 0, 350, 600), FONT_CREDITS, CENTER, Colors::WHITE, text);
credits = std::make_shared<CMultiLineLabel>(Rect(pos.w - 350, 0, 350, 600), FONT_CREDITS, ETextAlignment::CENTER, Colors::WHITE, text);
credits->scrollTextTo(-600); // move all text below the screen
}

View File

@ -31,8 +31,8 @@
#include "../windows/CAdvmapInterface.h"
#include "../windows/GUIClasses.h"
#include "../battle/CBattleInterfaceClasses.h"
#include "../battle/CBattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
#include "../battle/BattleInterface.h"
#include "../../CCallback.h"
#include "../../lib/StartInfo.h"
@ -87,7 +87,7 @@ void CList::CListItem::clickLeft(tribool down, bool previousState)
void CList::CListItem::hover(bool on)
{
if (on)
GH.statusbar->setText(getHoverText());
GH.statusbar->write(getHoverText());
else
GH.statusbar->clear();
}
@ -572,7 +572,7 @@ void CMinimap::clickRight(tribool down, bool previousState)
void CMinimap::hover(bool on)
{
if(on)
GH.statusbar->setText(CGI->generaltexth->zelp[291].first);
GH.statusbar->write(CGI->generaltexth->zelp[291].first);
else
GH.statusbar->clear();
}
@ -705,7 +705,7 @@ CInfoBar::VisibleDateInfo::VisibleDateInfo()
else
labelText = CGI->generaltexth->allTexts[64] + " " + boost::lexical_cast<std::string>(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK));
label = std::make_shared<CLabel>(95, 31, FONT_MEDIUM, CENTER, Colors::WHITE, labelText);
label = std::make_shared<CLabel>(95, 31, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, labelText);
forceRefresh.push_back(label);
}
@ -771,8 +771,8 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
//generate widgets
background = std::make_shared<CPicture>("ADSTATIN");
allyLabel = std::make_shared<CLabel>(10, 106, FONT_SMALL, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
enemyLabel = std::make_shared<CLabel>(10, 136, FONT_SMALL, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
allyLabel = std::make_shared<CLabel>(10, 106, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
enemyLabel = std::make_shared<CLabel>(10, 136, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
int posx = allyLabel->pos.w + allyLabel->pos.x - pos.x + 4;
for(PlayerColor & player : allies)
@ -794,7 +794,7 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
{
hallIcons.push_back(std::make_shared<CAnimImage>("itmtl", i, 0, 6 + 42 * (int)i , 11));
if(halls[i])
hallLabels.push_back(std::make_shared<CLabel>( 26 + 42 * (int)i, 64, FONT_SMALL, CENTER, Colors::WHITE, boost::lexical_cast<std::string>(halls[i])));
hallLabels.push_back(std::make_shared<CLabel>( 26 + 42 * (int)i, 64, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast<std::string>(halls[i])));
}
}
@ -807,7 +807,7 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const Component & compToDis
comp = std::make_shared<CComponent>(compToDisplay);
comp->moveTo(Point(pos.x+47, pos.y+50));
text = std::make_shared<CTextBox>(message, Rect(10, 4, 160, 50), 0, FONT_SMALL, CENTER, Colors::WHITE);
text = std::make_shared<CTextBox>(message, Rect(10, 4, 160, 50), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
}
void CInfoBar::playNewDaySound()
@ -876,7 +876,7 @@ void CInfoBar::clickRight(tribool down, bool previousState)
void CInfoBar::hover(bool on)
{
if(on)
GH.statusbar->setText(CGI->generaltexth->zelp[292].first);
GH.statusbar->write(CGI->generaltexth->zelp[292].first);
else
GH.statusbar->clear();
}
@ -1126,65 +1126,52 @@ void CInGameConsole::textEdited(const SDL_TextEditingEvent & event)
void CInGameConsole::startEnteringText()
{
if (!active)
return;
if (captureAllKeys)
return;
assert(GH.statusbar);
assert(currentStatusBar.expired());//effectively, nullptr check
currentStatusBar = GH.statusbar;
captureAllKeys = true;
CSDL_Ext::startTextInput(&GH.statusbar->pos);
enteredText = "_";
if(GH.topInt() == adventureInt)
{
GH.statusbar->alignment = TOPLEFT;
GH.statusbar->setText(enteredText);
//Prevent changes to the text from mouse interaction with the adventure map
GH.statusbar->lock(true);
}
else if(LOCPLINT->battleInt)
{
LOCPLINT->battleInt->console->ingcAlter = enteredText;
}
GH.statusbar->setEnteringMode(true);
GH.statusbar->setEnteredText(enteredText);
}
void CInGameConsole::endEnteringText(bool printEnteredText)
{
captureAllKeys = false;
CSDL_Ext::stopTextInput();
prevEntDisp = -1;
if(printEnteredText)
{
std::string txt = enteredText.substr(0, enteredText.size()-1);
LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection());
previouslyEntered.push_back(txt);
//print(txt);
}
enteredText.clear();
if(GH.topInt() == adventureInt)
{
GH.statusbar->alignment = CENTER;
GH.statusbar->lock(false);
GH.statusbar->clear();
}
else if(LOCPLINT->battleInt)
{
LOCPLINT->battleInt->console->ingcAlter = "";
}
auto statusbar = currentStatusBar.lock();
assert(statusbar);
if (statusbar)
statusbar->setEnteringMode(false);
currentStatusBar.reset();
}
void CInGameConsole::refreshEnteredText()
{
if(GH.topInt() == adventureInt)
{
GH.statusbar->lock(false);
GH.statusbar->clear();
GH.statusbar->setText(enteredText);
GH.statusbar->lock(true);
}
else if(LOCPLINT->battleInt)
{
LOCPLINT->battleInt->console->ingcAlter = enteredText;
}
auto statusbar = currentStatusBar.lock();
assert(statusbar);
if (statusbar)
statusbar->setEnteredText(enteredText);
}
CAdvMapPanel::CAdvMapPanel(SDL_Surface * bg, Point position)

View File

@ -412,6 +412,8 @@ private:
int prevEntDisp; //displayed entry from previouslyEntered - if none it's -1
int defaultTimeout; //timeout for new texts (in ms)
int maxDisplayedTexts; //hiw many texts can be displayed simultaneously
std::weak_ptr<IStatusBar> currentStatusBar;
public:
std::string enteredText;
void show(SDL_Surface * to) override;

View File

@ -16,8 +16,8 @@
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../battle/CBattleInterface.h"
#include "../battle/CBattleInterfaceClasses.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../windows/InfoWindows.h"
@ -77,15 +77,18 @@ void CButton::addCallback(std::function<void()> callback)
void CButton::addTextOverlay(const std::string & Text, EFonts font, SDL_Color color)
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
addOverlay(std::make_shared<CLabel>(pos.w/2, pos.h/2, font, CENTER, color, Text));
addOverlay(std::make_shared<CLabel>(pos.w/2, pos.h/2, font, ETextAlignment::CENTER, color, Text));
update();
}
void CButton::addOverlay(std::shared_ptr<CIntObject> newOverlay)
{
overlay = newOverlay;
addChild(newOverlay.get());
overlay->moveTo(overlay->pos.centerIn(pos).topLeft());
if(overlay)
{
addChild(newOverlay.get());
overlay->moveTo(overlay->pos.centerIn(pos).topLeft());
}
update();
}
@ -207,26 +210,10 @@ void CButton::hover (bool on)
if(!name.empty() && !isBlocked()) //if there is no name, there is nothing to display also
{
if (LOCPLINT && LOCPLINT->battleInt) //for battle buttons
{
if(on && LOCPLINT->battleInt->console->alterTxt == "")
{
LOCPLINT->battleInt->console->alterTxt = name;
LOCPLINT->battleInt->console->whoSetAlter = 1;
}
else if (LOCPLINT->battleInt->console->alterTxt == name)
{
LOCPLINT->battleInt->console->alterTxt = "";
LOCPLINT->battleInt->console->whoSetAlter = 0;
}
}
else if(GH.statusbar) //for other buttons
{
if (on)
GH.statusbar->setText(name);
else if ( GH.statusbar->getText()==(name) )
GH.statusbar->clear();
}
if (on)
GH.statusbar->write(name);
else
GH.statusbar->clearIfMatching(name);
}
}
@ -465,6 +452,11 @@ void CToggleGroup::selectionChanged(int to)
parent->redraw();
}
int CToggleGroup::getSelected() const
{
return selectedID;
}
CVolumeSlider::CVolumeSlider(const Point & position, const std::string & defName, const int value, const std::pair<std::string, std::string> * const help)
: CIntObject(LCLICK | RCLICK | WHEEL),
value(value),
@ -530,7 +522,7 @@ void CVolumeSlider::clickRight(tribool down, bool previousState)
if(!helpBox.empty())
CRClickPopup::createAndPush(helpBox);
if(GH.statusbar)
GH.statusbar->setText(helpBox);
GH.statusbar->write(helpBox);
}
}
@ -582,16 +574,21 @@ void CSlider::setScrollStep(int to)
scrollStep = to;
}
int CSlider::getAmount()
int CSlider::getAmount() const
{
return amount;
}
int CSlider::getValue()
int CSlider::getValue() const
{
return value;
}
int CSlider::getCapacity() const
{
return capacity;
}
void CSlider::moveLeft()
{
moveTo(value-1);

View File

@ -185,6 +185,7 @@ public:
/// in some cases, e.g. LoadGame difficulty selection, after refreshing the UI, the ToggleGroup should
/// reset all of it's child buttons to BLOCK state, then make selection again
void setSelectedOnly(int id);
int getSelected() const;
};
/// A typical slider for volume with an animated indicator
@ -256,8 +257,9 @@ public:
void setAmount(int to);
/// Accessors
int getAmount();
int getValue();
int getAmount() const;
int getValue() const;
int getCapacity() const;
void addCallback(std::function<void(int)> callback);

View File

@ -74,7 +74,7 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize)
for(auto & line : textLines)
{
int height = static_cast<int>(graphics->fonts[font]->getLineHeight());
auto label = std::make_shared<CLabel>(pos.w/2, pos.h + height/2, font, CENTER, Colors::WHITE, line);
auto label = std::make_shared<CLabel>(pos.w/2, pos.h + height/2, font, ETextAlignment::CENTER, Colors::WHITE, line);
pos.h += height;
if(label->pos.w > pos.w)
@ -424,7 +424,7 @@ void CComponentBox::placeComponents(bool selectable)
{
Point orPos = Point(currentX - freeSpace, currentY) + getOrTextPos(prevComp.get(), iter->get());
orLabels.push_back(std::make_shared<CLabel>(orPos.x, orPos.y, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[4]));
orLabels.push_back(std::make_shared<CLabel>(orPos.x, orPos.y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[4]));
}
currentX += getDistance(prevComp.get(), iter->get());
}

View File

@ -118,7 +118,7 @@ void CGarrisonSlot::hover (bool on)
temp = CGI->generaltexth->tcommands[11]; //Empty
}
}
GH.statusbar->setText(temp);
GH.statusbar->write(temp);
}
else
{
@ -418,7 +418,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGa
pos.h = 64;
}
stackCount = std::make_shared<CLabel>(pos.w, pos.h, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, BOTTOMRIGHT, Colors::WHITE);
stackCount = std::make_shared<CLabel>(pos.w, pos.h, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
update();
}

View File

@ -17,8 +17,8 @@
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../battle/CBattleInterface.h"
#include "../battle/CBattleInterfaceClasses.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
#include "../CBitmapHandler.h"
#include "../Graphics.h"

View File

@ -29,7 +29,8 @@ public:
bool freeSurf; //whether surface will be freed upon CPicture destruction
bool needRefresh;//Surface needs to be displayed each frame
bool visible;
operator SDL_Surface*()
SDL_Surface * getSurface()
{
return bg;
}
@ -55,7 +56,7 @@ public:
};
/// area filled with specific texture
class CFilledTexture : CIntObject
class CFilledTexture : public CIntObject
{
SDL_Surface * texture;

View File

@ -15,7 +15,6 @@
#include "../gui/CGuiHandler.h"
#include "../gui/CCursorHandler.h"
#include "../CBitmapHandler.h"
#include "../CPlayerInterface.h"
#include "../CMessage.h"
#include "../CGameInfo.h"
@ -34,9 +33,9 @@
void CHoverableArea::hover (bool on)
{
if (on)
GH.statusbar->setText(hoverText);
else if (GH.statusbar->getText()==hoverText)
GH.statusbar->clear();
GH.statusbar->write(hoverText);
else
GH.statusbar->clearIfMatching(hoverText);
}
CHoverableArea::CHoverableArea()
@ -150,7 +149,7 @@ void CHeroArea::clickRight(tribool down, bool previousState)
void CHeroArea::hover(bool on)
{
if (on && hero)
GH.statusbar->setText(hero->getObjectName());
GH.statusbar->write(hero->getObjectName());
else
GH.statusbar->clear();
}
@ -224,7 +223,7 @@ void CArmyTooltip::init(const InfoAboutArmy &army)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
title = std::make_shared<CLabel>(66, 2, FONT_SMALL, TOPLEFT, Colors::WHITE, army.name);
title = std::make_shared<CLabel>(66, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, army.name);
std::vector<Point> slotsPos;
slotsPos.push_back(Point(36, 73));
@ -257,7 +256,7 @@ void CArmyTooltip::init(const InfoAboutArmy &army)
subtitle = CGI->generaltexth->arraytxt[171 + 3*(slot.second.count)];
}
subtitles.push_back(std::make_shared<CLabel>(slotsPos[slot.first.getNum()].x + 17, slotsPos[slot.first.getNum()].y + 41, FONT_TINY, CENTER, Colors::WHITE, subtitle));
subtitles.push_back(std::make_shared<CLabel>(slotsPos[slot.first.getNum()].x + 17, slotsPos[slot.first.getNum()].y + 41, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, subtitle));
}
}
@ -282,10 +281,10 @@ void CHeroTooltip::init(const InfoAboutHero & hero)
if(hero.details)
{
for(size_t i = 0; i < hero.details->primskills.size(); i++)
labels.push_back(std::make_shared<CLabel>(75 + 28 * (int)i, 58, FONT_SMALL, CENTER, Colors::WHITE,
labels.push_back(std::make_shared<CLabel>(75 + 28 * (int)i, 58, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
boost::lexical_cast<std::string>(hero.details->primskills[i])));
labels.push_back(std::make_shared<CLabel>(158, 98, FONT_TINY, CENTER, Colors::WHITE, boost::lexical_cast<std::string>(hero.details->mana)));
labels.push_back(std::make_shared<CLabel>(158, 98, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast<std::string>(hero.details->mana)));
morale = std::make_shared<CAnimImage>("IMRL22", hero.details->morale + 3, 0, 5, 74);
luck = std::make_shared<CAnimImage>("ILCK22", hero.details->luck + 3, 0, 5, 91);
@ -325,7 +324,7 @@ void CTownTooltip::init(const InfoAboutTown & town)
if(town.details->goldIncome)
{
income = std::make_shared<CLabel>(157, 58, FONT_TINY, CENTER, Colors::WHITE,
income = std::make_shared<CLabel>(157, 58, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE,
boost::lexical_cast<std::string>(town.details->goldIncome));
}
if(town.details->garrisonedHero) //garrisoned hero icon
@ -453,7 +452,7 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A
anim->clipRect(cre->isDoubleWide()?170:150, 155, bg->pos.w, bg->pos.h);
anim->startPreview(cre->hasBonusOfType(Bonus::SIEGE_WEAPON));
amount = std::make_shared<CLabel>(bg->pos.w, bg->pos.h, FONT_MEDIUM, BOTTOMRIGHT, Colors::WHITE);
amount = std::make_shared<CLabel>(bg->pos.w, bg->pos.h, FONT_MEDIUM, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
pos.w = bg->pos.w;
pos.h = bg->pos.h;

View File

@ -14,7 +14,9 @@
#include "Images.h"
#include "../CMessage.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/AdventureMapClasses.h"
#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff
@ -36,7 +38,7 @@ void CLabel::showAll(SDL_Surface * to)
}
CLabel::CLabel(int x, int y, EFonts Font, EAlignment Align, const SDL_Color & Color, const std::string & Text)
CLabel::CLabel(int x, int y, EFonts Font, ETextAlignment Align, const SDL_Color & Color, const std::string & Text)
: CTextContainer(Align, Font, Color), text(Text)
{
type |= REDRAW_PARENT;
@ -45,7 +47,7 @@ CLabel::CLabel(int x, int y, EFonts Font, EAlignment Align, const SDL_Color & Co
pos.y += y;
pos.w = pos.h = 0;
if(alignment == TOPLEFT) // causes issues for MIDDLE
if(alignment == ETextAlignment::TOPLEFT) // causes issues for MIDDLE
{
pos.w = (int)graphics->fonts[font]->getStringWidth(visibleText().c_str());
pos.h = (int)graphics->fonts[font]->getLineHeight();
@ -96,7 +98,7 @@ size_t CLabel::getWidth()
return graphics->fonts[font]->getStringWidth(visibleText());;
}
CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color & Color, const std::string & Text) :
CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, ETextAlignment Align, const SDL_Color & Color, const std::string & Text) :
CLabel(position.x, position.y, Font, Align, Color, Text),
visibleSize(0, 0, position.w, position.h)
{
@ -137,19 +139,19 @@ void CTextContainer::blitLine(SDL_Surface * to, Rect destRect, std::string what)
// input is rect in which given text should be placed
// calculate proper position for top-left corner of the text
if(alignment == TOPLEFT)
if(alignment == ETextAlignment::TOPLEFT)
{
where.x += getBorderSize().x;
where.y += getBorderSize().y;
}
if(alignment == CENTER)
if(alignment == ETextAlignment::CENTER)
{
where.x += (int(destRect.w) - int(f->getStringWidth(what))) / 2;
where.y += (int(destRect.h) - int(f->getLineHeight())) / 2;
}
if(alignment == BOTTOMRIGHT)
if(alignment == ETextAlignment::BOTTOMRIGHT)
{
where.x += getBorderSize().x + destRect.w - (int)f->getStringWidth(what);
where.y += getBorderSize().y + destRect.h - (int)f->getLineHeight();
@ -178,7 +180,7 @@ void CTextContainer::blitLine(SDL_Surface * to, Rect destRect, std::string what)
} while(begin++ != std::string::npos);
}
CTextContainer::CTextContainer(EAlignment alignment, EFonts font, SDL_Color color) :
CTextContainer::CTextContainer(ETextAlignment alignment, EFonts font, SDL_Color color) :
alignment(alignment),
font(font),
color(color)
@ -252,15 +254,15 @@ Rect CMultiLineLabel::getTextLocation()
switch(alignment)
{
case TOPLEFT: return Rect(pos.topLeft(), textSize);
case CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize);
case BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize);
case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSize);
case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize);
case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize);
}
assert(0);
return Rect();
}
CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color & Color)
CLabelGroup::CLabelGroup(EFonts Font, ETextAlignment Align, const SDL_Color & Color)
: font(Font), align(Align), color(Color)
{
defActions = 255 - DISPOSE;
@ -277,7 +279,7 @@ size_t CLabelGroup::currentSize() const
return labels.size();
}
CTextBox::CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts Font, EAlignment Align, const SDL_Color & Color) :
CTextBox::CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts Font, ETextAlignment Align, const SDL_Color & Color) :
sliderStyle(SliderStyle),
slider(nullptr)
{
@ -339,30 +341,67 @@ void CTextBox::setText(const std::string & text)
}
}
void CGStatusBar::setText(const std::string & Text)
void CGStatusBar::setEnteringMode(bool on)
{
if(!textLock)
CLabel::setText(Text);
consoleText.clear();
if (on)
{
assert(enteringText == false);
alignment = ETextAlignment::TOPLEFT;
CSDL_Ext::startTextInput(&pos);
setText(consoleText);
}
else
{
assert(enteringText == true);
alignment = ETextAlignment::CENTER;
CSDL_Ext::stopTextInput();
setText(hoverText);
}
enteringText = on;
}
void CGStatusBar::setEnteredText(const std::string & text)
{
assert(enteringText == true);
consoleText = text;
setText(text);
}
void CGStatusBar::write(const std::string & Text)
{
hoverText = Text;
if (enteringText == false)
setText(hoverText);
}
void CGStatusBar::clearIfMatching(const std::string & Text)
{
if (hoverText == Text)
clear();
}
void CGStatusBar::clear()
{
setText("");
write({});
}
CGStatusBar::CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font, EAlignment Align, const SDL_Color & Color)
CGStatusBar::CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font, ETextAlignment Align, const SDL_Color & Color)
: CLabel(background_->pos.x, background_->pos.y, Font, Align, Color, "")
, enteringText(false)
{
background = background_;
addChild(background.get());
pos = background->pos;
getBorderSize();
textLock = false;
autoRedraw = false;
}
CGStatusBar::CGStatusBar(int x, int y, std::string name, int maxw)
: CLabel(x, y, FONT_SMALL, CENTER)
: CLabel(x, y, FONT_SMALL, ETextAlignment::CENTER)
, enteringText(false)
{
OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
background = std::make_shared<CPicture>(name);
@ -374,7 +413,6 @@ CGStatusBar::CGStatusBar(int x, int y, std::string name, int maxw)
vstd::amin(pos.w, maxw);
background->srcRect = new Rect(0, 0, maxw, pos.h);
}
textLock = false;
autoRedraw = false;
}
@ -392,14 +430,17 @@ void CGStatusBar::clickLeft(tribool down, bool previousState)
{
if(!down && onClick)
{
onClick();
if(LOCPLINT && LOCPLINT->cingconsole->active)
LOCPLINT->cingconsole->startEnteringText();
}
}
void CGStatusBar::setOnClick(std::function<void()> handler)
void CGStatusBar::deactivate()
{
onClick = handler;
addUsedEvents(LCLICK);
if (enteringText)
LOCPLINT->cingconsole->endEnteringText(false);
CIntObject::deactivate();
}
Point CGStatusBar::getBorderSize()
@ -409,21 +450,16 @@ Point CGStatusBar::getBorderSize()
switch(alignment)
{
case TOPLEFT: return Point(borderSize.x, borderSize.y);
case CENTER: return Point(pos.w / 2, pos.h / 2);
case BOTTOMRIGHT: return Point(pos.w - borderSize.x, pos.h - borderSize.y);
case ETextAlignment::TOPLEFT: return Point(borderSize.x, borderSize.y);
case ETextAlignment::CENTER: return Point(pos.w / 2, pos.h / 2);
case ETextAlignment::BOTTOMRIGHT: return Point(pos.w - borderSize.x, pos.h - borderSize.y);
}
assert(0);
return Point();
}
void CGStatusBar::lock(bool shouldLock)
{
textLock = shouldLock;
}
CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(const std::string &)> & CB)
: CLabel(Pos.x, Pos.y, font, CENTER),
: CLabel(Pos.x, Pos.y, font, ETextAlignment::CENTER),
cb(CB),
CFocusable(std::make_shared<CKeyboardFocusListener>(this))
{
@ -465,9 +501,9 @@ CTextInput::CTextInput(const Rect & Pos, SDL_Surface * srf)
background = std::make_shared<CPicture>(Pos, 0, true);
Rect hlp = Pos;
if(srf)
CSDL_Ext::blitSurface(srf, &hlp, *background.get(), nullptr);
CSDL_Ext::blitSurface(srf, &hlp, background->getSurface(), nullptr);
else
SDL_FillRect(*background.get(), nullptr, 0);
SDL_FillRect(background->getSurface(), nullptr, 0);
pos.w = background->pos.w;
pos.h = background->pos.h;
background->pos = pos;

View File

@ -25,10 +25,10 @@ protected:
/// do actual blitting of line. Text "what" will be placed at "where" and aligned according to alignment
void blitLine(SDL_Surface * to, Rect where, std::string what);
CTextContainer(EAlignment alignment, EFonts font, SDL_Color color);
CTextContainer(ETextAlignment alignment, EFonts font, SDL_Color color);
public:
EAlignment alignment;
ETextAlignment alignment;
EFonts font;
SDL_Color color; // default font color. Can be overridden by placing "{}" into the string
};
@ -41,18 +41,18 @@ protected:
virtual std::string visibleText();
std::shared_ptr<CPicture> background;
public:
std::string text;
bool autoRedraw; //whether control will redraw itself on setTxt
public:
std::string getText();
virtual void setAutoRedraw(bool option);
virtual void setText(const std::string & Txt);
virtual void setColor(const SDL_Color & Color);
size_t getWidth();
CLabel(int x = 0, int y = 0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT,
CLabel(int x = 0, int y = 0, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT,
const SDL_Color & Color = Colors::WHITE, const std::string & Text = "");
void showAll(SDL_Surface * to) override; //shows statusbar (with current text)
};
@ -62,10 +62,10 @@ class CLabelGroup : public CIntObject
{
std::vector<std::shared_ptr<CLabel>> labels;
EFonts font;
EAlignment align;
ETextAlignment align;
SDL_Color color;
public:
CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color & Color = Colors::WHITE);
CLabelGroup(EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const SDL_Color & Color = Colors::WHITE);
void add(int x = 0, int y = 0, const std::string & text = "");
size_t currentSize() const;
};
@ -86,7 +86,7 @@ public:
// total size of text, x = longest line of text, y = total height of lines
Point textSize;
CMultiLineLabel(Rect position, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color & Color = Colors::WHITE, const std::string & Text = "");
CMultiLineLabel(Rect position, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const SDL_Color & Color = Colors::WHITE, const std::string & Text = "");
void setText(const std::string & Txt) override;
void showAll(SDL_Surface * to) override;
@ -106,7 +106,7 @@ public:
std::shared_ptr<CMultiLineLabel> label;
std::shared_ptr<CSlider> slider;
CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color & Color = Colors::WHITE);
CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const SDL_Color & Color = Colors::WHITE);
void resize(Point newSize);
void setText(const std::string & Txt);
@ -114,13 +114,24 @@ public:
};
/// Status bar which is shown at the bottom of the in-game screens
class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusBar>
class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusBar>, public IStatusBar
{
bool textLock; //Used for blocking changes to the text
std::string hoverText;
std::string consoleText;
bool enteringText;
void init();
CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font = FONT_SMALL, EAlignment Align = CENTER, const SDL_Color & Color = Colors::WHITE);
CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::CENTER, const SDL_Color & Color = Colors::WHITE);
CGStatusBar(int x, int y, std::string name, int maxw = -1);
//make CLabel API private
using CLabel::getText;
using CLabel::setAutoRedraw;
using CLabel::setText;
using CLabel::setColor;
using CLabel::getWidth;
protected:
Point getBorderSize() override;
@ -137,14 +148,17 @@ public:
ret->init();
return ret;
}
void clear();//clears statusbar and refreshes
void setText(const std::string & Text) override; //prints text and refreshes statusbar
void show(SDL_Surface * to) override; //shows statusbar (with current text)
void show(SDL_Surface * to) override;
void deactivate() override;
void lock(bool shouldLock); //If true, current text cannot be changed until lock(false) is called
// IStatusBar interface
void write(const std::string & Text) override;
void clearIfMatching(const std::string & Text) override;
void clear() override;
void setEnteringMode(bool on) override;
void setEnteredText(const std::string & text) override;
void setOnClick(std::function<void()> handler);
};
class CFocusable;

View File

@ -692,19 +692,19 @@ CAdvMapInt::CAdvMapInt():
for (int i = 0; i < 5; ++i)
{
panelWorldView->addChildIcon(std::pair<int, Point>(i, Point(5, 58 + i * 20)), iconColorMultiplier);
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 263 + i * 20, EFonts::FONT_SMALL, EAlignment::TOPLEFT,
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 263 + i * 20, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
Colors::WHITE, CGI->generaltexth->allTexts[612 + i]));
}
for (int i = 0; i < 7; ++i)
{
panelWorldView->addChildIcon(std::pair<int, Point>(i + 5, Point(5, 182 + i * 20)), iconColorMultiplier);
panelWorldView->addChildIcon(std::pair<int, Point>(i + 12, Point(160, 182 + i * 20)), iconColorMultiplier);
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 387 + i * 20, EFonts::FONT_SMALL, EAlignment::TOPLEFT,
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 387 + i * 20, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
Colors::WHITE, CGI->generaltexth->allTexts[619 + i]));
}
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 5, 367, EFonts::FONT_SMALL, EAlignment::TOPLEFT,
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 5, 367, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
Colors::WHITE, CGI->generaltexth->allTexts[617]));
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 367, EFonts::FONT_SMALL, EAlignment::TOPLEFT,
panelWorldView->addChildToPanel(std::make_shared<CLabel>(wvLeft + 45, 367, EFonts::FONT_SMALL, ETextAlignment::TOPLEFT,
Colors::WHITE, CGI->generaltexth->allTexts[618]));
activeMapPanel = panelMain;
@ -716,11 +716,6 @@ CAdvMapInt::CAdvMapInt():
worldViewUnderground->block(!CGI->mh->map->twoLevel);
addUsedEvents(MOVE);
statusbar->setOnClick([&]
{
if(LOCPLINT) LOCPLINT->cingconsole->startEnteringText();
});
}
CAdvMapInt::~CAdvMapInt()
@ -1668,13 +1663,13 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
std::string text = curHero() ? objAtTile->getHoverText(curHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
boost::replace_all(text,"\n"," ");
statusbar->setText(text);
statusbar->write(text);
}
else
{
std::string hlp;
CGI->mh->getTerrainDescr(mapPos, hlp, false);
statusbar->setText(hlp);
statusbar->write(hlp);
}
if(spellBeingCasted)
@ -1811,7 +1806,7 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos)
return;
}
CRClickPopup::createAndPush(obj, GH.current->motion, CENTER);
CRClickPopup::createAndPush(obj, GH.current->motion, ETextAlignment::CENTER);
}
void CAdvMapInt::enterCastingMode(const CSpell * sp)

View File

@ -261,7 +261,7 @@ void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
|| (*parent->selectedBuilding)<(*this)) //or we are on top
{
parent->selectedBuilding = this;
GH.statusbar->setText(getSubtitle());
GH.statusbar->write(getSubtitle());
}
}
}
@ -275,19 +275,19 @@ CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstanc
const CCreature * creature = CGI->creh->objects.at(Town->creatures.at(level).second.back());
title = std::make_shared<CLabel>(80, 30, FONT_SMALL, CENTER, Colors::WHITE, creature->namePl);
title = std::make_shared<CLabel>(80, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, creature->namePl);
animation = std::make_shared<CCreaturePic>(30, 44, creature, true, true);
std::string text = boost::lexical_cast<std::string>(Town->creatures.at(level).first);
available = std::make_shared<CLabel>(80,190, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[217] + text);
costPerTroop = std::make_shared<CLabel>(80, 227, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[346]);
available = std::make_shared<CLabel>(80,190, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[217] + text);
costPerTroop = std::make_shared<CLabel>(80, 227, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[346]);
for(int i = 0; i<GameConstants::RESOURCE_QUANTITY; i++)
{
if(creature->cost[i])
{
resPicture.push_back(std::make_shared<CAnimImage>("RESOURCE", i, 0, 0, 0));
resAmount.push_back(std::make_shared<CLabel>(0,0, FONT_SMALL, CENTER, Colors::WHITE, boost::lexical_cast<std::string>(creature->cost[i])));
resAmount.push_back(std::make_shared<CLabel>(0,0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast<std::string>(creature->cost[i])));
}
}
@ -379,7 +379,7 @@ void CHeroGSlot::hover(bool on)
}
}
if(temp.size())
GH.statusbar->setText(temp);
GH.statusbar->write(temp);
}
void CHeroGSlot::clickLeft(tribool down, bool previousState)
@ -996,14 +996,14 @@ CCreaInfo::CCreaInfo(Point position, const CGTownInstance * Town, int Level, boo
if(compact)
{
label = std::make_shared<CLabel>(40, 32, FONT_TINY, BOTTOMRIGHT, Colors::WHITE, value);
label = std::make_shared<CLabel>(40, 32, FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, value);
pos.x += 8;
pos.w = 32;
pos.h = 32;
}
else
{
label = std::make_shared<CLabel>(24, 40, FONT_SMALL, CENTER, Colors::WHITE, value);
label = std::make_shared<CLabel>(24, 40, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, value);
pos.w = 48;
pos.h = 48;
}
@ -1019,7 +1019,7 @@ void CCreaInfo::update()
else
value = boost::lexical_cast<std::string>(town->creatureGrowth(level));
if(value != label->text)
if(value != label->getText())
label->setText(value);
}
}
@ -1031,11 +1031,11 @@ void CCreaInfo::hover(bool on)
if(on)
{
GH.statusbar->setText(message);
GH.statusbar->write(message);
}
else if (message == GH.statusbar->getText())
else
{
GH.statusbar->clear();
GH.statusbar->clearIfMatching(message);
}
}
@ -1105,7 +1105,7 @@ void CTownInfo::hover(bool on)
if(on)
{
if(building )
GH.statusbar->setText(building->Name());
GH.statusbar->write(building->Name());
}
else
{
@ -1143,8 +1143,8 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
garr->type |= REDRAW_PARENT;
heroes = std::make_shared<HeroSlots>(town, Point(241, 387), Point(241, 483), garr, true);
title = std::make_shared<CLabel>(85, 387, FONT_MEDIUM, TOPLEFT, Colors::WHITE, town->name);
income = std::make_shared<CLabel>(195, 443, FONT_SMALL, CENTER);
title = std::make_shared<CLabel>(85, 387, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, town->name);
income = std::make_shared<CLabel>(195, 443, FONT_SMALL, ETextAlignment::CENTER);
icon = std::make_shared<CAnimImage>("ITPT", 0, 0, 15, 387);
exit = std::make_shared<CButton>(Point(744, 544), "TSBTNS", CButton::tooltip(CGI->generaltexth->tcommands[8]), [&](){close();}, SDLK_RETURN);
@ -1159,7 +1159,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
garr->addSplitBtn(split);
Rect barRect(9, 182, 732, 18);
auto statusbarBackground = std::make_shared<CPicture>(*(panel.get()), barRect, 9, 555, false);
auto statusbarBackground = std::make_shared<CPicture>(panel->getSurface(), barRect, 9, 555, false);
statusbar = CGStatusBar::create(statusbarBackground);
resdatabar = std::make_shared<CResDataBar>("ARESBAR", 3, 575, 32, 2, 85, 85);
@ -1307,7 +1307,7 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance *
header = std::make_shared<CAnimImage>("TPTHBAR", panelIndex[state], 0, 1, 73);
if(iconIndex[state] >=0)
mark = std::make_shared<CAnimImage>("TPTHCHK", iconIndex[state], 0, 136, 56);
name = std::make_shared<CLabel>(75, 81, FONT_SMALL, CENTER, Colors::WHITE, building->Name());
name = std::make_shared<CLabel>(75, 81, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, building->Name());
//todo: add support for all possible states
if(state >= EBuildingState::BUILDING_ERROR)
@ -1326,7 +1326,7 @@ void CHallInterface::CBuildingBox::hover(bool on)
else
toPrint = CGI->generaltexth->hcommands[state];
boost::algorithm::replace_first(toPrint,"%s",building->Name());
GH.statusbar->setText(toPrint);
GH.statusbar->write(toPrint);
}
else
{
@ -1356,10 +1356,10 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
resdatabar->moveBy(pos.topLeft(), true);
Rect barRect(5, 556, 740, 18);
auto statusbarBackground = std::make_shared<CPicture>(*background, barRect, 5, 556, false);
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 5, 556, false);
statusbar = CGStatusBar::create(statusbarBackground);
title = std::make_shared<CLabel>(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
title = std::make_shared<CLabel>(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
exit = std::make_shared<CButton>(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->hcommands[8]), [&](){close();}, SDLK_RETURN);
exit->assignedKeys.insert(SDLK_ESCAPE);
@ -1403,12 +1403,12 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
icon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, building->bid, 0, 125, 50);
auto statusbarBackground = std::make_shared<CPicture>(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26);
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26);
statusbar = CGStatusBar::create(statusbarBackground);
name = std::make_shared<CLabel>(197, 30, FONT_MEDIUM, CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->hcommands[7]) % building->Name()));
description = std::make_shared<CTextBox>(building->Description(), Rect(33, 135, 329, 67), 0, FONT_MEDIUM, CENTER);
stateText = std::make_shared<CTextBox>(getTextForState(state), Rect(33, 216, 329, 67), 0, FONT_SMALL, CENTER);
name = std::make_shared<CLabel>(197, 30, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->hcommands[7]) % building->Name()));
description = std::make_shared<CTextBox>(building->Description(), Rect(33, 135, 329, 67), 0, FONT_MEDIUM, ETextAlignment::CENTER);
stateText = std::make_shared<CTextBox>(getTextForState(state), Rect(33, 216, 329, 67), 0, FONT_SMALL, ETextAlignment::CENTER);
//Create components for all required resources
std::vector<std::shared_ptr<CComponent>> components;
@ -1507,15 +1507,15 @@ void LabeledValue::init(std::string nameText, std::string descr, int min, int ma
if(min != max)
valueText += '-' + boost::lexical_cast<std::string>(max);
}
name = std::make_shared<CLabel>(3, 0, FONT_SMALL, TOPLEFT, Colors::WHITE, nameText);
value = std::make_shared<CLabel>(pos.w-3, pos.h-2, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, valueText);
name = std::make_shared<CLabel>(3, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, nameText);
value = std::make_shared<CLabel>(pos.w-3, pos.h-2, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, valueText);
}
void LabeledValue::hover(bool on)
{
if(on)
{
GH.statusbar->setText(hoverText);
GH.statusbar->write(hoverText);
}
else
{
@ -1533,7 +1533,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
fortSize--;
const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6));
title = std::make_shared<CLabel>(400, 12, FONT_BIG, CENTER, Colors::WHITE, fortBuilding->Name());
title = std::make_shared<CLabel>(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->Name());
std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->Name());
exit = std::make_shared<CButton>(Point(748, 556), "TPMAGE1", CButton::tooltip(text), [&](){ close(); }, SDLK_RETURN);
@ -1579,7 +1579,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
Rect barRect(4, 554, 740, 18);
auto statusbarBackground = std::make_shared<CPicture>(*background, barRect, 4, 554, false);
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 4, 554, false);
statusbar = CGStatusBar::create(statusbarBackground);
}
@ -1620,13 +1620,13 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
if(getMyBuilding() != nullptr)
{
buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, CENTER, Colors::WHITE, getMyBuilding()->Name());
buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->Name());
if(vstd::contains(town->builtBuildings, getMyBuilding()->bid))
{
ui32 available = town->creatures[level].first;
std::string availableText = CGI->generaltexth->allTexts[217]+ boost::lexical_cast<std::string>(available);
availableCount = std::make_shared<CLabel>(78, 119, FONT_SMALL, CENTER, Colors::WHITE, availableText);
availableCount = std::make_shared<CLabel>(78, 119, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, availableText);
}
}
@ -1634,7 +1634,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
{
hoverText = boost::str(boost::format(CGI->generaltexth->tcommands[21]) % getMyCreature()->namePl);
new CCreaturePic(159, 4, getMyCreature(), false);
new CLabel(78, 11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl);
new CLabel(78, 11, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyCreature()->namePl);
Rect sizes(287, 4, 96, 18);
values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->getAttack(false)));
@ -1683,7 +1683,7 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
void CFortScreen::RecruitArea::hover(bool on)
{
if(on)
GH.statusbar->setText(hoverText);
GH.statusbar->write(hoverText);
else
GH.statusbar->clear();
}
@ -1720,7 +1720,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner,std::string imagem)
Rect barRect(7, 556, 737, 18);
auto statusbarBackground = std::make_shared<CPicture>(*background, barRect, 7, 556, false);
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 7, 556, false);
statusbar = CGStatusBar::create(statusbarBackground);
exit = std::make_shared<CButton>(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, SDLK_RETURN);
@ -1774,7 +1774,7 @@ void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState)
void CMageGuildScreen::Scroll::hover(bool on)
{
if(on)
GH.statusbar->setText(spell->name);
GH.statusbar->write(spell->name);
else
GH.statusbar->clear();
@ -1787,7 +1787,7 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art
Rect barRect(8, pos.h - 26, pos.w - 16, 19);
auto statusbarBackground = std::make_shared<CPicture>(*background, barRect, 8, pos.h - 26, false);
auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 8, pos.h - 26, false);
statusbar = CGStatusBar::create(statusbarBackground);
animBG = std::make_shared<CPicture>("TPSMITBK", 64, 50);
@ -1797,10 +1797,10 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art
anim = std::make_shared<CCreatureAnim>(64, 50, creature->animDefName);
anim->clipRect(113,125,200,150);
title = std::make_shared<CLabel>(165, 28, FONT_BIG, CENTER, Colors::YELLOW,
title = std::make_shared<CLabel>(165, 28, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW,
boost::str(boost::format(CGI->generaltexth->allTexts[274]) % creature->nameSing));
costText = std::make_shared<CLabel>(165, 218, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->jktexts[43]);
costValue = std::make_shared<CLabel>(165, 290, FONT_MEDIUM, CENTER, Colors::WHITE,
costText = std::make_shared<CLabel>(165, 218, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->jktexts[43]);
costValue = std::make_shared<CLabel>(165, 290, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE,
boost::lexical_cast<std::string>(aid.toArtifact(CGI->artifacts())->getPrice()));
std::string text = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % creature->nameSing);

View File

@ -239,8 +239,8 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li
{
BonusInfo & bi = parent->activeBonuses[bonusIndex];
icon[leftRight] = std::make_shared<CPicture>(bi.imagePath, position.x, position.y);
name[leftRight] = std::make_shared<CLabel>(position.x + 60, position.y + 2, FONT_SMALL, TOPLEFT, Colors::WHITE, bi.name);
description[leftRight] = std::make_shared<CMultiLineLabel>(Rect(position.x + 60, position.y + 17, 137, 30), FONT_SMALL, TOPLEFT, Colors::WHITE, bi.description);
name[leftRight] = std::make_shared<CLabel>(position.x + 60, position.y + 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name);
description[leftRight] = std::make_shared<CMultiLineLabel>(Rect(position.x + 60, position.y + 17, 137, 30), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description);
}
}
}
@ -511,7 +511,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
animation->setAmount(parent->info->creatureCount);
}
name = std::make_shared<CLabel>(215, 12, FONT_SMALL, CENTER, Colors::YELLOW, parent->info->getName());
name = std::make_shared<CLabel>(215, 12, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName());
int dmgMultiply = 1;
if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON))
@ -585,7 +585,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
expArea->text = parent->generateStackExpDescription();
}
expLabel = std::make_shared<CLabel>(
pos.x + 21, pos.y + 52, FONT_SMALL, CENTER, Colors::WHITE,
pos.x + 21, pos.y + 52, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
makeNumberShort<TExpType>(stack->experience, 6));
}
@ -630,7 +630,7 @@ std::string CStackWindow::MainSection::getBackgroundName(bool showExp, bool show
void CStackWindow::MainSection::addStatLabel(EStat index, int64_t value1, int64_t value2)
{
const auto title = statNames.at(static_cast<size_t>(index));
stats.push_back(std::make_shared<CLabel>(145, 32 + (int)index*19, FONT_SMALL, TOPLEFT, Colors::WHITE, title));
stats.push_back(std::make_shared<CLabel>(145, 32 + (int)index*19, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, title));
const bool useRange = value1 != value2;
std::string formatStr = useRange ? statFormats.at(static_cast<size_t>(index)) : "%d";
@ -640,7 +640,7 @@ void CStackWindow::MainSection::addStatLabel(EStat index, int64_t value1, int64_
if(useRange)
fmt % value2;
stats.push_back(std::make_shared<CLabel>(307, 48 + (int)index*19, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, fmt.str()));
stats.push_back(std::make_shared<CLabel>(307, 48 + (int)index*19, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, fmt.str()));
}
void CStackWindow::MainSection::addStatLabel(EStat index, int64_t value)

View File

@ -15,7 +15,6 @@
#include "CKingdomInterface.h"
#include "GUIClasses.h"
#include "../CBitmapHandler.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CMT.h"
@ -109,18 +108,18 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
curHero = hero;
banner = std::make_shared<CAnimImage>("CREST58", LOCPLINT->playerID.getNum(), 0, 606, 8);
name = std::make_shared<CLabel>(190, 38, EFonts::FONT_BIG, EAlignment::CENTER, Colors::YELLOW);
title = std::make_shared<CLabel>(190, 65, EFonts::FONT_MEDIUM, EAlignment::CENTER, Colors::WHITE);
name = std::make_shared<CLabel>(190, 38, EFonts::FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW);
title = std::make_shared<CLabel>(190, 65, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
statusbar = CGStatusBar::create(7, 559, "ADROLLVR.bmp", 660);
quitButton = std::make_shared<CButton>(Point(609, 516), "hsbtns.def", CButton::tooltip(heroscrn[17]), [=](){ close(); }, SDLK_RETURN);
quitButton->assignedKeys.insert(SDLK_ESCAPE);
dismissLabel = std::make_shared<CTextBox>(CGI->generaltexth->jktexts[8], Rect(370, 430, 65, 35), 0, FONT_SMALL, TOPLEFT, Colors::WHITE);
dismissLabel = std::make_shared<CTextBox>(CGI->generaltexth->jktexts[8], Rect(370, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
dismissButton = std::make_shared<CButton>(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, SDLK_d);
questlogLabel = std::make_shared<CTextBox>(CGI->generaltexth->jktexts[9], Rect(510, 430, 65, 35), 0, FONT_SMALL, TOPLEFT, Colors::WHITE);
questlogLabel = std::make_shared<CTextBox>(CGI->generaltexth->jktexts[9], Rect(510, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
questlogButton = std::make_shared<CButton>(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, SDLK_q);
formations = std::make_shared<CToggleGroup>(0);
@ -149,7 +148,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
area->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % CGI->generaltexth->primarySkillNames[v]);
primSkillAreas.push_back(area);
auto value = std::make_shared<CLabel>(53 + 70 * v, 166, FONT_SMALL, CENTER);
auto value = std::make_shared<CLabel>(53 + 70 * v, 166, FONT_SMALL, ETextAlignment::CENTER);
primSkillValues.push_back(value);
}
@ -184,19 +183,19 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
int x = (i % 2) ? 212 : 68;
int y = 280 + 48 * (i/2);
secSkillValues.push_back(std::make_shared<CLabel>(x, y, FONT_SMALL, TOPLEFT));
secSkillNames.push_back(std::make_shared<CLabel>(x, y+20, FONT_SMALL, TOPLEFT));
secSkillValues.push_back(std::make_shared<CLabel>(x, y, FONT_SMALL, ETextAlignment::TOPLEFT));
secSkillNames.push_back(std::make_shared<CLabel>(x, y+20, FONT_SMALL, ETextAlignment::TOPLEFT));
}
// various texts
labels.push_back(std::make_shared<CLabel>(52, 99, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[1]));
labels.push_back(std::make_shared<CLabel>(123, 99, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[2]));
labels.push_back(std::make_shared<CLabel>(193, 99, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[3]));
labels.push_back(std::make_shared<CLabel>(262, 99, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[4]));
labels.push_back(std::make_shared<CLabel>(52, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[1]));
labels.push_back(std::make_shared<CLabel>(123, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[2]));
labels.push_back(std::make_shared<CLabel>(193, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[3]));
labels.push_back(std::make_shared<CLabel>(262, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[4]));
labels.push_back(std::make_shared<CLabel>(69, 183, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[5]));
labels.push_back(std::make_shared<CLabel>(69, 232, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[6]));
labels.push_back(std::make_shared<CLabel>(213, 232, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[7]));
labels.push_back(std::make_shared<CLabel>(69, 183, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[5]));
labels.push_back(std::make_shared<CLabel>(69, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[6]));
labels.push_back(std::make_shared<CLabel>(213, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[7]));
update(hero);
}

View File

@ -54,20 +54,20 @@ InfoBox::InfoBox(Point position, InfoPos Pos, InfoSize Size, std::shared_ptr<IIn
switch(infoPos)
{
case POS_CORNER:
value = std::make_shared<CLabel>(pos.w, pos.h, font, BOTTOMRIGHT, Colors::WHITE, data->getValueText());
value = std::make_shared<CLabel>(pos.w, pos.h, font, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, data->getValueText());
break;
case POS_INSIDE:
value = std::make_shared<CLabel>(pos.w/2, pos.h-6, font, CENTER, Colors::WHITE, data->getValueText());
value = std::make_shared<CLabel>(pos.w/2, pos.h-6, font, ETextAlignment::CENTER, Colors::WHITE, data->getValueText());
break;
case POS_UP_DOWN:
name = std::make_shared<CLabel>(pos.w/2, -12, font, CENTER, Colors::WHITE, data->getNameText());
name = std::make_shared<CLabel>(pos.w/2, -12, font, ETextAlignment::CENTER, Colors::WHITE, data->getNameText());
FALLTHROUGH;
case POS_DOWN:
value = std::make_shared<CLabel>(pos.w/2, pos.h+8, font, CENTER, Colors::WHITE, data->getValueText());
value = std::make_shared<CLabel>(pos.w/2, pos.h+8, font, ETextAlignment::CENTER, Colors::WHITE, data->getValueText());
break;
case POS_RIGHT:
name = std::make_shared<CLabel>(pos.w+6, 6, font, TOPLEFT, Colors::WHITE, data->getNameText());
value = std::make_shared<CLabel>(pos.w+6, pos.h-16, font, TOPLEFT, Colors::WHITE, data->getValueText());
name = std::make_shared<CLabel>(pos.w+6, 6, font, ETextAlignment::TOPLEFT, Colors::WHITE, data->getNameText());
value = std::make_shared<CLabel>(pos.w+6, pos.h-16, font, ETextAlignment::TOPLEFT, Colors::WHITE, data->getValueText());
break;
}
@ -601,7 +601,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
incomeArea = std::make_shared<CHoverableArea>();
incomeArea->pos = Rect(pos.x+580, pos.y+31+footerPos, 136, 68);
incomeArea->hoverText = CGI->generaltexth->allTexts[255];
incomeAmount = std::make_shared<CLabel>(628, footerPos + 70, FONT_SMALL, TOPLEFT, Colors::WHITE, boost::lexical_cast<std::string>(totalIncome));
incomeAmount = std::make_shared<CLabel>(628, footerPos + 70, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, boost::lexical_cast<std::string>(totalIncome));
}
void CKingdomInterface::generateButtons()
@ -682,8 +682,8 @@ CKingdHeroList::CKingdHeroList(size_t maxSize)
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
title = std::make_shared<CPicture>("OVTITLE",16,0);
title->colorize(LOCPLINT->playerID);
heroLabel = std::make_shared<CLabel>(150, 10, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->overview[0]);
skillsLabel = std::make_shared<CLabel>(500, 10, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->overview[1]);
heroLabel = std::make_shared<CLabel>(150, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[0]);
skillsLabel = std::make_shared<CLabel>(500, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[1]);
ui32 townCount = LOCPLINT->cb->howManyHeroes(false);
ui32 size = conf.go()->ac.overviewSize*116 + 19;
@ -722,9 +722,9 @@ CKingdTownList::CKingdTownList(size_t maxSize)
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
title = std::make_shared<CPicture>("OVTITLE", 16, 0);
title->colorize(LOCPLINT->playerID);
townLabel = std::make_shared<CLabel>(146, 10,FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->overview[3]);
garrHeroLabel = std::make_shared<CLabel>(375, 10, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->overview[4]);
visitHeroLabel = std::make_shared<CLabel>(608, 10, FONT_MEDIUM, CENTER, Colors::WHITE, CGI->generaltexth->overview[5]);
townLabel = std::make_shared<CLabel>(146, 10,FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[3]);
garrHeroLabel = std::make_shared<CLabel>(375, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[4]);
visitHeroLabel = std::make_shared<CLabel>(608, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[5]);
ui32 townCount = LOCPLINT->cb->howManyTowns();
ui32 size = conf.go()->ac.overviewSize*116 + 19;
@ -767,9 +767,9 @@ CTownItem::CTownItem(const CGTownInstance * Town)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
background = std::make_shared<CAnimImage>("OVSLOT", 6);
name = std::make_shared<CLabel>(74, 8, FONT_SMALL, TOPLEFT, Colors::WHITE, town->name);
name = std::make_shared<CLabel>(74, 8, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, town->name);
income = std::make_shared<CLabel>( 190, 60, FONT_SMALL, CENTER, Colors::WHITE, boost::lexical_cast<std::string>(town->dailyIncome()[Res::GOLD]));
income = std::make_shared<CLabel>( 190, 60, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast<std::string>(town->dailyIncome()[Res::GOLD]));
hall = std::make_shared<CTownInfo>( 69, 31, town, true);
fort = std::make_shared<CTownInfo>(111, 31, town, false);
@ -799,7 +799,7 @@ void CTownItem::updateGarrisons()
void CTownItem::update()
{
std::string incomeVal = boost::lexical_cast<std::string>(town->dailyIncome()[Res::GOLD]);
if (incomeVal != income->text)
if (incomeVal != income->getText())
income->setText(incomeVal);
heroes->update();
@ -863,7 +863,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
arts2->recActions = SHARE_POS;
backpack->recActions = SHARE_POS;
name = std::make_shared<CLabel>(75, 7, FONT_SMALL, TOPLEFT, Colors::WHITE, hero->name);
name = std::make_shared<CLabel>(75, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->name);
//layout is not trivial: MACH4 - catapult - excluded, MISC[x] rearranged
assert(arts1->arts.size() == 9);
@ -919,8 +919,8 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
portrait = std::make_shared<CAnimImage>("PortraitsLarge", hero->portrait, 0, 5, 6);
heroArea = std::make_shared<CHeroArea>(5, 6, hero);
name = std::make_shared<CLabel>(73, 7, FONT_SMALL, TOPLEFT, Colors::WHITE, hero->name);
artsText = std::make_shared<CLabel>(320, 55, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->overview[2]);
name = std::make_shared<CLabel>(73, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->name);
artsText = std::make_shared<CLabel>(320, 55, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[2]);
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
{

View File

@ -12,7 +12,6 @@
#include "CAdvmapInterface.h"
#include "../CBitmapHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../Graphics.h"
@ -131,11 +130,11 @@ CQuestLog::CQuestLog (const std::vector<QuestInfo> & Quests)
minimap = std::make_shared<CQuestMinimap>(Rect(12, 12, 169, 169));
// TextBox have it's own 4 pixel padding from top at least for English. To achieve 10px from both left and top only add 6px margin
description = std::make_shared<CTextBox>("", Rect(205, 18, 385, DESCRIPTION_HEIGHT_MAX), CSlider::BROWN, FONT_MEDIUM, TOPLEFT, Colors::WHITE);
description = std::make_shared<CTextBox>("", Rect(205, 18, 385, DESCRIPTION_HEIGHT_MAX), CSlider::BROWN, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE);
ok = std::make_shared<CButton>(Point(539, 398), "IOKAY.DEF", CGI->generaltexth->zelp[445], std::bind(&CQuestLog::close, this), SDLK_RETURN);
// Both button and lable are shifted to -2px by x and y to not make them actually look like they're on same line with quests list and ok button
hideCompleteButton = std::make_shared<CToggleButton>(Point(10, 396), "sysopchk.def", CButton::tooltip(texts["hideComplete"]), std::bind(&CQuestLog::toggleComplete, this, _1));
hideCompleteLabel = std::make_shared<CLabel>(46, 398, FONT_MEDIUM, TOPLEFT, Colors::WHITE, texts["hideComplete"]["label"].String());
hideCompleteLabel = std::make_shared<CLabel>(46, 398, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, texts["hideComplete"]["label"].String());
slider = std::make_shared<CSlider>(Point(166, 195), 191, std::bind(&CQuestLog::sliderMoved, this, _1), QUEST_COUNT, 0, false, CSlider::BROWN);
recreateLabelList();
@ -176,7 +175,7 @@ void CQuestLog::recreateLabelList()
else
text.addReplacement(quests[i].obj->getObjectName()); //get name of the object
}
auto label = std::make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString());
auto label = std::make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, text.toString());
label->disable();
label->callback = std::bind(&CQuestLog::selectQuest, this, i, currentLabel);

View File

@ -43,8 +43,8 @@ class CQuestLabel : public LRClickableAreaWText, public CMultiLineLabel
public:
std::function<void()> callback;
CQuestLabel(Rect position, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text = "")
: CMultiLineLabel (position, FONT_SMALL, TOPLEFT, Colors::WHITE, Text){};
CQuestLabel(Rect position, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text = "")
: CMultiLineLabel (position, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, Text){};
void clickLeft(tribool down, bool previousState) override;
void showAll(SDL_Surface * to) override;
};

View File

@ -24,7 +24,7 @@
#include "../CVideoHandler.h"
#include "../Graphics.h"
#include "../battle/CBattleInterface.h"
#include "../battle/BattleInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h"
@ -66,7 +66,7 @@ void CSpellWindow::InteractiveArea::clickRight(tribool down, bool previousState)
void CSpellWindow::InteractiveArea::hover(bool on)
{
if(on)
owner->statusBar->setText(hoverText);
owner->statusBar->write(hoverText);
else
owner->statusBar->clear();
}
@ -183,7 +183,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
for(auto item : schoolBorders)
item->preload();
mana = std::make_shared<CLabel>(435, 426, FONT_SMALL, CENTER, Colors::YELLOW, boost::lexical_cast<std::string>(myHero->mana));
mana = std::make_shared<CLabel>(435, 426, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, boost::lexical_cast<std::string>(myHero->mana));
statusBar = CGStatusBar::create(7, 569, "Spelroll.bmp");
SDL_Rect temp_rect = genRect(45, 35, 479 + pos.x, 405 + pos.y);
@ -508,12 +508,12 @@ CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
image = std::make_shared<CAnimImage>(owner->spellIcons, 0, 0);
image->visible = false;
name = std::make_shared<CLabel>(39, 70, FONT_TINY, CENTER);
level = std::make_shared<CLabel>(39, 82, FONT_TINY, CENTER);
cost = std::make_shared<CLabel>(39, 94, FONT_TINY, CENTER);
name = std::make_shared<CLabel>(39, 70, FONT_TINY, ETextAlignment::CENTER);
level = std::make_shared<CLabel>(39, 82, FONT_TINY, ETextAlignment::CENTER);
cost = std::make_shared<CLabel>(39, 94, FONT_TINY, ETextAlignment::CENTER);
for(auto l : {name, level, cost})
l->autoRedraw = false;
l->setAutoRedraw(false);
}
CSpellWindow::SpellArea::~SpellArea() = default;
@ -609,7 +609,7 @@ void CSpellWindow::SpellArea::hover(bool on)
if(mySpell)
{
if(on)
owner->statusBar->setText(boost::to_string(boost::format("%s (%s)") % mySpell->name % CGI->generaltexth->allTexts[171+mySpell->level]));
owner->statusBar->write(boost::to_string(boost::format("%s (%s)") % mySpell->name % CGI->generaltexth->allTexts[171+mySpell->level]));
else
owner->statusBar->clear();
}

View File

@ -244,13 +244,13 @@ void CTradeWindow::CTradeableItem::hover(bool on)
{
case CREATURE:
case CREATURE_PLACEHOLDER:
GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
GH.statusbar->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
break;
case ARTIFACT_PLACEHOLDER:
if(id < 0)
GH.statusbar->setText(CGI->generaltexth->zelp[582].first);
GH.statusbar->write(CGI->generaltexth->zelp[582].first);
else
GH.statusbar->setText(CGI->artifacts()->getByIndex(id)->getName());
GH.statusbar->write(CGI->artifacts()->getByIndex(id)->getName());
break;
}
}
@ -659,7 +659,7 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
madeTransaction = false;
bool sliderNeeded = true;
statusBar = CGStatusBar::create(std::make_shared<CPicture>(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
std::string title;
@ -707,7 +707,7 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
}
}
titleLabel = std::make_shared<CLabel>(300, 27, FONT_BIG, CENTER, Colors::YELLOW, title);
titleLabel = std::make_shared<CLabel>(300, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title);
initItems(false);
initItems(true);
@ -734,15 +734,15 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
case EMarketMode::RESOURCE_RESOURCE:
case EMarketMode::RESOURCE_PLAYER:
case EMarketMode::RESOURCE_ARTIFACT:
labels.push_back(std::make_shared<CLabel>(154, 148, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[270]));
labels.push_back(std::make_shared<CLabel>(154, 148, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[270]));
break;
case EMarketMode::CREATURE_RESOURCE:
//%s's Creatures
labels.push_back(std::make_shared<CLabel>(152, 102, FONT_SMALL, CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)));
labels.push_back(std::make_shared<CLabel>(152, 102, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)));
break;
case EMarketMode::ARTIFACT_RESOURCE:
//%s's Artifacts
labels.push_back(std::make_shared<CLabel>(152, 56, FONT_SMALL, CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->name)));
labels.push_back(std::make_shared<CLabel>(152, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->name)));
break;
}
@ -755,16 +755,16 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
case EMarketMode::CREATURE_RESOURCE:
case EMarketMode::RESOURCE_ARTIFACT:
case EMarketMode::ARTIFACT_RESOURCE:
labels.push_back(std::make_shared<CLabel>(445, 148, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[168]));
labels.push_back(std::make_shared<CLabel>(445, 148, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[168]));
traderTextRect = Rect(316, 48, 260, 75);
break;
case EMarketMode::RESOURCE_PLAYER:
labels.push_back(std::make_shared<CLabel>(445, 55, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[169]));
labels.push_back(std::make_shared<CLabel>(445, 55, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[169]));
traderTextRect = Rect(28, 48, 260, 75);
break;
}
traderText = std::make_shared<CTextBox>("", traderTextRect, 0, FONT_SMALL, CENTER);
traderText = std::make_shared<CTextBox>("", traderTextRect, 0, FONT_SMALL, ETextAlignment::CENTER);
int specialOffset = mode == EMarketMode::ARTIFACT_RESOURCE ? 35 : 0; //in selling artifacts mode we need to move res-res and art-res buttons down
if(printButtonFor(EMarketMode::RESOURCE_PLAYER))
@ -1098,14 +1098,14 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero,
if(Mode == EMarketMode::CREATURE_EXP)
{
//%s's Creatures
labels.push_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, CENTER, Colors::YELLOW,
labels.push_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW,
boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)));
//Altar of Sacrifice
labels.push_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));
labels.push_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));
//To sacrifice creatures, move them from your army on to the Altar and click Sacrifice
new CTextBox(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, CENTER, Colors::YELLOW);
new CTextBox(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW);
slider = std::make_shared<CSlider>(Point(231,481),137,std::bind(&CAltarWindow::sliderMoved,this,_1),0,0);
max = std::make_shared<CButton>(Point(147, 520), "IRCBTNS.DEF", CGI->generaltexth->zelp[578], std::bind(&CSlider::moveToMax, slider));
@ -1119,9 +1119,9 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero,
else
{
//Sacrifice artifacts for experience
labels.push_back(std::make_shared<CLabel>(450, 34, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]));
labels.push_back(std::make_shared<CLabel>(450, 34, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]));
//%s's Creatures
labels.push_back(std::make_shared<CLabel>(302, 423, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]));
labels.push_back(std::make_shared<CLabel>(302, 423, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]));
sacrificeAll = std::make_shared<CButton>(Point(393, 520), "ALTFILL.DEF", CGI->generaltexth->zelp[571], std::bind(&CAltarWindow::SacrificeAll,this));
sacrificeAll->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty());
@ -1135,11 +1135,11 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero,
}
//Experience needed to reach next level
texts.push_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[475], Rect(15, 415, 125, 50), 0, FONT_SMALL, CENTER, Colors::YELLOW));
texts.push_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[475], Rect(15, 415, 125, 50), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
//Total experience on the Altar
texts.push_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[476], Rect(15, 495, 125, 40), 0, FONT_SMALL, CENTER, Colors::YELLOW));
texts.push_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[476], Rect(15, 495, 125, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
statusBar = CGStatusBar::create(std::make_shared<CPicture>(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
ok = std::make_shared<CButton>(Point(516, 520), "IOK6432.DEF", CGI->generaltexth->zelp[568], [&](){ close();}, SDLK_RETURN);
ok->assignedKeys.insert(SDLK_ESCAPE);
@ -1164,8 +1164,8 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero,
expPerUnit.resize(GameConstants::ARMY_SIZE, 0);
getExpValues();
expToLevel = std::make_shared<CLabel>(73, 475, FONT_SMALL, CENTER);
expOnAltar = std::make_shared<CLabel>(73, 543, FONT_SMALL, CENTER);
expToLevel = std::make_shared<CLabel>(73, 475, FONT_SMALL, ETextAlignment::CENTER);
expOnAltar = std::make_shared<CLabel>(73, 543, FONT_SMALL, ETextAlignment::CENTER);
setExpToLevel();
calcTotalExp();

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