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:
commit
e4e5278f60
9
.github/workflows/github.yml
vendored
9
.github/workflows/github.yml
vendored
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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
|
@ -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)
|
||||
|
@ -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}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
37
Global.h
37
Global.h
@ -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)
|
||||
{
|
||||
|
10
Mods/vcmi/config/vcmi/towerCreature.json
Normal file
10
Mods/vcmi/config/vcmi/towerCreature.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"core:arrowTower" :
|
||||
{
|
||||
"graphics" :
|
||||
{
|
||||
"iconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"iconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
}
|
101
Mods/vcmi/config/vcmi/towerFactions.json
Normal file
101
Mods/vcmi/config/vcmi/towerFactions.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"core:castle" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:rampart" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:tower" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:inferno" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:necropolis" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:dungeon" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:stronghold" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:fortress" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
},
|
||||
"core:conflux" :
|
||||
{
|
||||
"town" :
|
||||
{
|
||||
"siege" :
|
||||
{
|
||||
"towerIconSmall" : "vcmi/creatureIcons/towerSmall",
|
||||
"towerIconLarge" : "vcmi/creatureIcons/towerLarge",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -30,8 +30,6 @@ template<typename T> class CApplier;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct SharedMemory;
|
||||
|
||||
class CClient;
|
||||
|
||||
class CBaseForLobbyApply;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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++;
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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"
|
||||
|
775
client/battle/BattleActionsController.cpp
Normal file
775
client/battle/BattleActionsController.cpp
Normal 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);
|
||||
}
|
96
client/battle/BattleActionsController.h
Normal file
96
client/battle/BattleActionsController.h
Normal 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;
|
||||
|
||||
};
|
1222
client/battle/BattleAnimationClasses.cpp
Normal file
1222
client/battle/BattleAnimationClasses.cpp
Normal file
File diff suppressed because it is too large
Load Diff
350
client/battle/BattleAnimationClasses.h
Normal file
350
client/battle/BattleAnimationClasses.h
Normal 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;
|
||||
};
|
328
client/battle/BattleControlPanel.cpp
Normal file
328
client/battle/BattleControlPanel.cpp
Normal 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);
|
||||
}
|
76
client/battle/BattleControlPanel.h
Normal file
76
client/battle/BattleControlPanel.h
Normal 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);
|
||||
};
|
||||
|
140
client/battle/BattleEffectsController.cpp
Normal file
140
client/battle/BattleEffectsController.cpp
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
84
client/battle/BattleEffectsController.h
Normal file
84
client/battle/BattleEffectsController.h
Normal 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;
|
||||
};
|
634
client/battle/BattleFieldController.cpp
Normal file
634
client/battle/BattleFieldController.cpp
Normal 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];
|
||||
}
|
91
client/battle/BattleFieldController.h
Normal file
91
client/battle/BattleFieldController.h
Normal 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);
|
||||
};
|
964
client/battle/BattleInterface.cpp
Normal file
964
client/battle/BattleInterface.cpp
Normal 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);
|
||||
}
|
209
client/battle/BattleInterface.h
Normal file
209
client/battle/BattleInterface.h
Normal 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;
|
||||
};
|
@ -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)
|
||||
{
|
@ -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();
|
||||
};
|
190
client/battle/BattleObstacleController.cpp
Normal file
190
client/battle/BattleObstacleController.cpp
Normal 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();
|
||||
}
|
58
client/battle/BattleObstacleController.h
Normal file
58
client/battle/BattleObstacleController.h
Normal 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);
|
||||
};
|
373
client/battle/BattleProjectileController.cpp
Normal file
373
client/battle/BattleProjectileController.cpp
Normal 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));
|
||||
}
|
||||
}
|
118
client/battle/BattleProjectileController.h
Normal file
118
client/battle/BattleProjectileController.h
Normal 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);
|
||||
};
|
71
client/battle/BattleRenderer.cpp
Normal file
71
client/battle/BattleRenderer.cpp
Normal 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);
|
||||
}
|
54
client/battle/BattleRenderer.h
Normal file
54
client/battle/BattleRenderer.h
Normal 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);
|
||||
};
|
368
client/battle/BattleSiegeController.cpp
Normal file
368
client/battle/BattleSiegeController.cpp
Normal 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;
|
||||
}
|
110
client/battle/BattleSiegeController.h
Normal file
110
client/battle/BattleSiegeController.h
Normal 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;
|
||||
};
|
589
client/battle/BattleStacksController.cpp
Normal file
589
client/battle/BattleStacksController.cpp
Normal 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();
|
||||
|
||||
}
|
121
client/battle/BattleStacksController.h
Normal file
121
client/battle/BattleStacksController.h
Normal 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
@ -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
@ -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;
|
||||
};
|
@ -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;
|
@ -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;
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
||||
|
@ -373,3 +373,6 @@ void WindowBase::close()
|
||||
logGlobal->error("Only top interface must be closed");
|
||||
GH.popInts(1);
|
||||
}
|
||||
|
||||
IStatusBar::~IStatusBar()
|
||||
{}
|
||||
|
@ -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
84
client/gui/Canvas.cpp
Normal 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
55
client/gui/Canvas.h
Normal 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 );
|
||||
};
|
@ -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
|
||||
|
389
client/gui/InterfaceObjectConfigurable.cpp
Normal file
389
client/gui/InterfaceObjectConfigurable.cpp
Normal 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;
|
||||
}
|
85
client/gui/InterfaceObjectConfigurable.h
Normal file
85
client/gui/InterfaceObjectConfigurable.h
Normal 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;
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* NotificationHandler.cpp, part of VCMI engine
|
||||
* NotificationHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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++)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user