# HG changeset patch
# User sheepluva
# Date 1564607667 -7200
# Node ID c4fd2813b127fcfe798acdcbd8684a594745d54e
# Parent 0135e64c6c6682a634c4a59db13fd8ffc078b9b1# Parent 7ab5cf40568614fa0bfc11935d1d136a59181cc6
merge 1.0.0 beta 1 into ui-scaling branch
diff -r 0135e64c6c66 -r c4fd2813b127 .gitignore
--- a/.gitignore Wed May 16 18:22:28 2018 +0200
+++ b/.gitignore Wed Jul 31 23:14:27 2019 +0200
@@ -81,3 +81,5 @@
*.mode1v3
*.mode2v3
Testing/*
+gameServer2/rls
+gameServer2/target
diff -r 0135e64c6c66 -r c4fd2813b127 .hgignore
--- a/.hgignore Wed May 16 18:22:28 2018 +0200
+++ b/.hgignore Wed Jul 31 23:14:27 2019 +0200
@@ -16,11 +16,11 @@
*.ppu
*.*~
*.core
-hedgewars.pro.user
config.inc
cmake_install.cmake
QTfrontend/hwconsts.cpp
QTfrontend/servermessages.h
+QTfrontend/creditsmessages.h
CPackConfig.cmake
CPackSourceConfig.cmake
tools/cmake_uninstall.cmake
@@ -44,6 +44,7 @@
*.rej
project_files/hwc/*.c
project_files/hwc/*.h
+project_files/hwc/*.out
project_files/Android-build/SDL-android-project/jni/**
project_files/Android-build/SDL-android-project/obj
project_files/Android-build/SDL-android-project/libs/armeabi*
@@ -82,8 +83,12 @@
xcuserdata
*.mode1v3
*.mode2v3
-gameServer2/target
-gameServer2/Cargo.lock
-gameServer2/gameServer2.iml
.idea
Testing/
+rust/*/target
+*.lock
+*.user
+*.iml
+build-qmlfrontend*
+.cabal-sandbox
+cabal.sandbox.config
diff -r 0135e64c6c66 -r c4fd2813b127 .hgtags
--- a/.hgtags Wed May 16 18:22:28 2018 +0200
+++ b/.hgtags Wed Jul 31 23:14:27 2019 +0200
@@ -87,3 +87,5 @@
0000000000000000000000000000000000000000 0.9.24.1-release
0000000000000000000000000000000000000000 0.9.24.1-release
afc089c39556bdd895892fa36ed47aaec83c3825 0.9.24.1-release
+195208deff1dd3e22d303d4a92c2ba14be3b6623 Hedgewars-iOS-2.1
+5e28098fb59379357a145b73380a1cd3839f643f 0.9.25-release
diff -r 0135e64c6c66 -r c4fd2813b127 .travis.yml
--- a/.travis.yml Wed May 16 18:22:28 2018 +0200
+++ b/.travis.yml Wed Jul 31 23:14:27 2019 +0200
@@ -23,7 +23,7 @@
- BUILD_ARGS="-DCMAKE_BUILD_TYPE=Release"
- BUILD_ARGS="-DCMAKE_BUILD_TYPE=Debug"
- BUILD_ARGS="-DNOSERVER=1 -DGL2=1 -DNOPNG=1"
- - BUILD_ARGS="-DNOSERVER=1 -DLUA_SYSTEM=0 -DPHYSFS_SYSTEM=0"
+ - BUILD_ARGS="-DNOSERVER=1 -DLUA_SYSTEM=0"
matrix:
include:
@@ -54,7 +54,7 @@
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
sudo apt-get update -qq
elif [ "$TRAVIS_OS_NAME" == "osx" ]; then
- brew update --all
+ brew update
elif [ "$TRAVIS_OS_NAME" == "ios" ]; then
hg clone http://hg.libsdl.org/SDL $SDL_LIB_PATH/SDL/
hg clone http://hg.libsdl.org/SDL_image $SDL_LIB_PATH/SDL_image/
@@ -65,11 +65,22 @@
install: |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
- sudo apt-get install -y debhelper cmake dpkg-dev qtbase5-dev qtbase5-private-dev qttools5-dev-tools qttools5-dev libphysfs-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-net-dev bzip2 ghc libghc-mtl-dev libghc-vector-dev libghc-zlib-dev libghc-random-dev libghc-network-dev libghc-sandi-dev libghc-hslogger-dev libghc-utf8-string-dev libghc-sha-dev libghc-entropy-dev libghc-regex-tdfa-dev liblua5.1-0-dev fpc fp-compiler fp-units-misc libpng-dev fp-units-gfx libavcodec-dev libavformat-dev libglew1.6-dev
+ sudo apt-get install -y debhelper cmake dpkg-dev qtbase5-dev qtbase5-private-dev qttools5-dev-tools qttools5-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-net-dev bzip2 ghc libghc-mtl-dev libghc-vector-dev libghc-zlib-dev libghc-random-dev libghc-network-dev libghc-sandi-dev libghc-hslogger-dev libghc-utf8-string-dev libghc-sha-dev libghc-entropy-dev libghc-regex-tdfa-dev libghc-aeson-dev libghc-yaml-dev libghc-text-dev liblua5.1-0-dev fpc fp-compiler fp-units-misc libpng-dev fp-units-gfx libavcodec-dev libavformat-dev libglew1.6-dev
+
+ # for xenial last availible version of libphysfs is 2.0.x, but we need >= 3.0
+ # so... building from sources!
+ wget https://icculus.org/physfs/downloads/physfs-3.0.1.tar.bz2
+ tar -xjf physfs-3.0.1.tar.bz2
+ mkdir physfs-3.0.1-build
+ pushd physfs-3.0.1-build
+ cmake ../physfs-3.0.1
+ make
+ sudo make install
+ popd
elif [ "$TRAVIS_OS_NAME" == "osx" ]; then
brew install qt5
brew install fpc glew physfs lua51 sdl2 sdl2_image sdl2_net sdl2_ttf ffmpeg ghc cabal-install
- brew install sdl2_mixer --with-libvorbis
+ brew install sdl2_mixer
# use cabal install haskell deps, pas2c ones are covered by server
if [[ "$BUILD_ARGS" != *"NOSERVER"* ]]; then
cabal update
diff -r 0135e64c6c66 -r c4fd2813b127 CMakeLists.txt
--- a/CMakeLists.txt Wed May 16 18:22:28 2018 +0200
+++ b/CMakeLists.txt Wed Jul 31 23:14:27 2019 +0200
@@ -27,10 +27,8 @@
option(BUILD_SHARED_LIBS "Build libraries as shared modules (on)" ON)
if(WIN32 OR APPLE)
- option(PHYSFS_SYSTEM "Use system physfs (off)" OFF)
option(LUA_SYSTEM "Use system lua (off)" OFF)
else()
- option(PHYSFS_SYSTEM "Use system physfs (on)" ON)
option(LUA_SYSTEM "Use system lua (on)" ON)
endif()
@@ -53,6 +51,14 @@
option(NOVERSIONINFOUPDATE "Disable update of version_info.txt. To be used if source is in a git/repo that is NOT the hedgewars repo" OFF)
+if(BUILD_ENGINE_C AND NOT NOVIDEOREC)
+ if((CMAKE_BUILD_TYPE MATCHES "RELEASE") OR (CMAKE_BUILD_TYPE MATCHES "RELWITHDEBUGINFO"))
+ message("NOTE: Video recorder support disabled. It's incompatible with BUILD_ENGINE_C")
+ set(BUILD_ENGINE_C ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
+ else()
+ message("WARNING: Video recorder support is currently incompatible with BUILD_ENGINE_C, the video recorder won't work (but demos are fine)! See .")
+ endif()
+endif()
if(BUILD_ENGINE_JS)
if(NOT CMAKE_TOOLCHAIN_FILE)
message(FATAL_ERROR "Missing emscripten toolchain file\nClean your cache and rerun cmake with -DCMAKE_TOOLCHAIN_FILE=${CMAKE_SOURCE_DIR}/cmake_modules/Platform/Emscripten.cmake")
@@ -61,7 +67,6 @@
set(BUILD_ENGINE_C ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
set(BUILD_ENGINE_LIBRARY ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
set(NOAUTOUPDATE ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
- set(PHYSFS_SYSTEM OFF CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
set(LUA_SYSTEM OFF CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
set(NOVIDEOREC ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
set(NOSERVER ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
@@ -72,17 +77,25 @@
set(target_library_install_dir "lib" CACHE PATH "install dest for libs")
endif()
+if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4" AND UNIX AND NOT APPLE)
+ set(BUILD_ENGINE_C ON CACHE STRING "PAS2C force-enabled due to a freepascal 32 bit alignment bug" FORCE)
+endif()
+
#system paths for finding required fonts (see share/hedgewars/Data/fonts)
#subdirectories will NOT be searched.
#all fonts that can't be found will be bundled with hedgewars
set(FONTS_DIRS "" CACHE STRING "Additional paths to folders where required fonts can be found ( ; is separator)")
#versioning
-set(CPACK_PACKAGE_VERSION_MAJOR 0)
-set(CPACK_PACKAGE_VERSION_MINOR 9)
-set(CPACK_PACKAGE_VERSION_PATCH "24.1")
-set(HEDGEWARS_PROTO_VER 55)
-set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
+set(CPACK_PACKAGE_VERSION_MAJOR 1)
+set(CPACK_PACKAGE_VERSION_MINOR 0)
+set(CPACK_PACKAGE_VERSION_PATCH 0)
+set(HEDGEWARS_PROTO_VER 58)
+if((CMAKE_BUILD_TYPE MATCHES "RELEASE") OR (CMAKE_BUILD_TYPE MATCHES "RELWITHDEBUGINFO"))
+ set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
+else()
+ set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}-dev")
+endif()
include(${CMAKE_MODULE_PATH}/revinfo.cmake)
message(STATUS "Building ${HEDGEWARS_VERSION}-r${HEDGEWARS_REVISION} (${HEDGEWARS_HASH})")
@@ -116,12 +129,21 @@
endif()
#set default compiler flags
-add_flag_append(CMAKE_C_FLAGS "-Wall -pipe")
-add_flag_append(CMAKE_C_FLAGS_RELEASE "-O2")
-add_flag_append(CMAKE_C_FLAGS_DEBUG "-Wextra -O0")
-add_flag_append(CMAKE_CXX_FLAGS "-Wall -pipe")
-add_flag_append(CMAKE_CXX_FLAGS_RELEASE "-O2")
-add_flag_append(CMAKE_CXX_FLAGS_DEBUG "-Wextra -O0")
+if(WIN32 AND VCPKG_TOOLCHAIN)
+ add_flag_append(CMAKE_C_FLAGS "/DWIN32_VCPKG /Wall")
+ add_flag_append(CMAKE_C_FLAGS_RELEASE "/Ox")
+ add_flag_append(CMAKE_C_FLAGS_DEBUG "/Od")
+ add_flag_append(CMAKE_CXX_FLAGS "/DWIN32_VCPKG /Wall")
+ add_flag_append(CMAKE_CXX_FLAGS_RELEASE "/Ox")
+ add_flag_append(CMAKE_CXX_FLAGS_DEBUG "/Od")
+else()
+ add_flag_append(CMAKE_C_FLAGS "-Wall -pipe")
+ add_flag_append(CMAKE_C_FLAGS_RELEASE "-O2")
+ add_flag_append(CMAKE_C_FLAGS_DEBUG "-Wextra -O0")
+ add_flag_append(CMAKE_CXX_FLAGS "-Wall -pipe")
+ add_flag_append(CMAKE_CXX_FLAGS_RELEASE "-O2")
+ add_flag_append(CMAKE_CXX_FLAGS_DEBUG "-Wextra -O0")
+endif()
#CMake adds a lot of additional configuration flags, so let's clear them up
if(MINIMAL_FLAGS)
@@ -199,40 +221,32 @@
#physfs discovery
-if(PHYSFS_SYSTEM)
- if(NOT PHYSFS_LIBRARY OR NOT PHYSFS_INCLUDE_DIR)
- find_package(PhysFS)
- endif()
-
- find_file(physfs_h physfs.h ${PHYSFS_INCLUDE_DIR})
- if(physfs_h)
- file(STRINGS ${physfs_h} physfs_majorversion REGEX "PHYSFS_VER_MAJOR[\t' ']+[0-9]+")
- file(STRINGS ${physfs_h} physfs_minorversion REGEX "PHYSFS_VER_MINOR[\t' ']+[0-9]+")
- file(STRINGS ${physfs_h} physfs_patchversion REGEX "PHYSFS_VER_PATCH[\t' ']+[0-9]+")
- string(REGEX MATCH "([0-9]+)" physfs_majorversion "${physfs_majorversion}")
- string(REGEX MATCH "([0-9]+)" physfs_minorversion "${physfs_minorversion}")
- string(REGEX MATCH "([0-9]+)" physfs_patchversion "${physfs_patchversion}")
- set(physfs_detected_ver "${physfs_majorversion}.${physfs_minorversion}.${physfs_patchversion}")
+if(NOT PHYSFS_LIBRARY OR NOT PHYSFS_INCLUDE_DIR)
+ find_package(PhysFS)
+endif()
- if(${physfs_detected_ver} VERSION_LESS 2.0.0)
- message(FATAL_ERROR "PhysFS version is too old (detected ${physfs_detected_ver}, required 2.0.0)\n"
- "Perform an update or rerun cmake with -DPHYSFS_SYSTEM=off to build the internal version")
- endif()
- endif()
+find_file(physfs_h physfs.h ${PHYSFS_INCLUDE_DIR})
+if(physfs_h)
+ file(STRINGS ${physfs_h} physfs_majorversion REGEX "PHYSFS_VER_MAJOR[\t' ']+[0-9]+")
+ file(STRINGS ${physfs_h} physfs_minorversion REGEX "PHYSFS_VER_MINOR[\t' ']+[0-9]+")
+ file(STRINGS ${physfs_h} physfs_patchversion REGEX "PHYSFS_VER_PATCH[\t' ']+[0-9]+")
+ string(REGEX MATCH "([0-9]+)" physfs_majorversion "${physfs_majorversion}")
+ string(REGEX MATCH "([0-9]+)" physfs_minorversion "${physfs_minorversion}")
+ string(REGEX MATCH "([0-9]+)" physfs_patchversion "${physfs_patchversion}")
+ set(physfs_detected_ver "${physfs_majorversion}.${physfs_minorversion}.${physfs_patchversion}")
- if(PHYSFS_LIBRARY AND PHYSFS_INCLUDE_DIR)
- #use an IMPORTED tharget so that we can just use 'physfs' to link
- add_library(physfs UNKNOWN IMPORTED)
- set_target_properties(physfs PROPERTIES IMPORTED_LOCATION ${PHYSFS_LIBRARY})
- else()
- message(FATAL_ERROR "Missing PhysFS! Rerun cmake with -DPHYSFS_SYSTEM=off to build the internal version")
+ if(${physfs_detected_ver} VERSION_LESS 3.0.0)
+ message(FATAL_ERROR "PhysFS version is too old (detected ${physfs_detected_ver}, required 3.0.0)\n"
+ "Perform an update of PhysFS to fix this.")
endif()
+endif()
+
+if(PHYSFS_LIBRARY AND PHYSFS_INCLUDE_DIR)
+ #use an IMPORTED tharget so that we can just use 'physfs' to link
+ add_library(physfs UNKNOWN IMPORTED)
+ set_target_properties(physfs PROPERTIES IMPORTED_LOCATION ${PHYSFS_LIBRARY})
else()
- if(NOT PHYSFS_LIBRARY OR NOT PHYSFS_INCLUDE_DIR)
- message(STATUS "PhysFS will be provided by the bundled sources")
- endif()
- set(physfs_output_name "hwphysfs")
- add_subdirectory(misc/libphysfs)
+ message(FATAL_ERROR "Missing PhysFS! Install PhysFS to fix this.")
endif()
find_package_or_disable_msg(LIBAV NOVIDEOREC "Video recording will not be built")
diff -r 0135e64c6c66 -r c4fd2813b127 CREDITS
--- a/CREDITS Wed May 16 18:22:28 2018 +0200
+++ b/CREDITS Wed Jul 31 23:14:27 2019 +0200
@@ -1,6 +1,7 @@
=============================
=== EXTENDED CREDITS LIST ===
=============================
+For the main credits, click on the Hedgewars logo in the main menu.
IF NOT OTHERWISE SPECIFIED, ALL OTHER CONTENT IS PROPERTY OF Andrey Korotaev .
IF NO LICENSE IS SPECIFIED, THE LICENSE IS THE SAME AS MENTIONED IN README.md.
@@ -12,26 +13,6 @@
==========
- see Fonts_LICENSE.txt
-=================
-= FRONTEND IMAGES
-=================
-(File name suffixes are omitted)
-
-- Tango Project and Wuzzy -> audio, home (CC0)
-- abustany and Wuzzy -> Videos (CC0)
-- Juliane Krug and Wuzzy -> Palette (CC0)
-- raseone and Wuzzy -> folder (CC0)
-
-==========
-= FORTS
-==========
-- Carlos Vives -> Tank (2010)
-- Dragonfly -> EvilChicken (2010)
-- Randy Broda -> SteelTower (2013)
-- Jon Dum and Wuzzy -> Snail (2017)
-- Maciej Mroziński (a.k.a. alzen) and Wuzzy -> Lonely_Island (2017)
-- Guillaume Englert and Wuzzy -> Olympic (2017)
-
==========
= HATS
==========
@@ -51,57 +32,32 @@
- Terrington_Snyde -> pirate_eyepatch (2013), jester (2013)
- Wohlstand -> policegirl [based on policecap and sm_daisy] (2014)
- TheMadCharles -> barrelhider (CC BY 3.0) (2015)
+- Trey Perry -> Other hats
==========
-= GRAVESTONES
+= GRAVES
==========
- Randy Broda -> dragonball (2012)
- CheezeMonkey -> pi (2011)
- rosenholz -> Whisky (2013)
-==========
-= MAPS
-==========
-- John Dum -> Bath (2008), Hedgelove (2008), Hedgewars (2008), Hydrant (2008), mushrooms (2008), Plane (2008)
-- Joshua Frese -> Bamboo (2008), EarthRise (2008), Freeway (2008), BambooPlinko (2008)
-- Stanko Tadić -> Castle (2008), PirateFlag (2008)
-- dctPL -> Sticks (2010)
-- wolfmarc & Dragonfly -> TrophyRace (2010), ShoppaKing (2010)
+=================
+= FRONTEND IMAGES
+=================
+(File name suffixes are omitted)
-==========
-= MUSIC
-==========
-- HSR ( http://elhombresinremedio.com ) -> (new) City theme, Rock theme and many other tracks
-- John Dum -> Nature theme
-- Jonatan Nilsson -> Pirate theme, EarthRise (former City) theme, Oriental theme, Snow theme)
-- yd - http://opengameart.org/users/yd -> "oriented", used as Olympics SD theme
-- Kevin MacLeod - http://incompetech.com/ -> "hitman", used as basis for preliminary default SD theme
-- Valentin Kraevskiy (alias alfadur) -> Jungle theme, Fruit theme
+- Tango Project and Wuzzy -> audio, home (CC0)
+- abustany and Wuzzy -> Videos (CC0)
+- Juliane Krug and Wuzzy -> Palette (CC0)
+- raseone and Wuzzy -> folder (CC0)
+
+==================
+= OTHER GRAPHICS =
+==================
+- Custom ammo images for Continental supplies: KarBoy2314PL
==========
-= THEMES
-==========
-- John Dum -> Nature (2008), Snow (2008), City
-- Joshua Frese -> Bamboo (2008), EarthRise (2008), Freeway (2008), BambooPlinko (2008)
-- Stanko Tadić -> Hell (2008)
-- Julien Koesten -> Sheep (2008)
-- KoRn666 - Jungle (2010)
-- Randy Broda -> Fruit (2013), Cake (2014)
-
-==========
-= VOICES
-==========
-- Stephen Alexander
-- mtg90pl : Default_pl, Russian_pl
-
-==========
-= MISSIONS
-==========
-- Arkhnen -> Teamwork 2 (2012)
-- Wuzzy -> Big Armory (2016)
-
-==========
-= SOUNDS
+= SOUND EFFECTS
==========
- Mine impact sound from http://www.freesound.org/people/adcbicycle/sounds/13947/
- Hammer sound from http://www.freesound.org/people/Syna-Max/sounds/43586/
@@ -124,17 +80,20 @@
https://www.freesound.org/people/Jagadamba/sounds/257057/
- Blowtorch sound originally by rombard (CC-0), remixed from
https://www.freesound.org/people/rombart/sounds/197800/
+- Flamethrower sound originally by AslakHostaker (CC-0), adapted from
+ https://freesound.org/people/AslakHostaker/sounds/395039/
+- Landspray sound originally by Benboncan (CC BY 3.0), remixed from
+ https://freesound.org/people/Benboncan/sounds/82390/
- Portable Portal Device color switching sound by Wuzzy (CC-0)
- Portable Portal Device shot sound originally by bubaproducer (CC-BY 3.0), remixed from
https://www.freesound.org/people/bubaproducer/sounds/151026/
- Portal opening sound by Wuzzy (CC-0)
- Invulnerable sound: remix based on a sound by pepingrillin (CC-0)
https://www.freesound.org/people/pepingrillin/sounds/252079/
-
-==================
-= OTHER GRAPHICS =
-==================
-- Custom ammo images for Continental supplies: KarBoy2314PL
+- Droplet1.ogg, Droplet2.ogg, Droplet3.ogg: based off a recording by saugox (CC-BY 3.0)
+ https://freesound.org/people/saugox/sounds/37548/
+- Shoryuken hit: Based off sound by CGEffex (CC-BY 3.0), tweaked by alfadur
+ http://freesound.org/people/CGEffex/sounds/98341/
======================
= LICENSE REFERENCES =
diff -r 0135e64c6c66 -r c4fd2813b127 ChangeLog.txt
--- a/ChangeLog.txt Wed May 16 18:22:28 2018 +0200
+++ b/ChangeLog.txt Wed Jul 31 23:14:27 2019 +0200
@@ -1,12 +1,433 @@
+ features
* bugfixes
-====================== 0.9.25 ======================
+======================= ??? ========================
User Interface:
+ In-Game chat size can now be adjusted. Hold Ctrl and press -, + or = while in chat input. Hold shift for finer control.
+ The intial in-game chat size can be configured in the Frontend's "advanced" settings tab.
+============== 1.0.0-dev (unreleased) ==============
+Highlights:
+ + Campaigns now respect your team identity instead of overwriting it
+ + Single missions now support team selection and track your progress
+ + Challenges track the team's highscores
+ + Hand-drawn maps can now be scaled with slider
+ + Quick games are more random
+ + Homing bee can be used as secondary ammo
+ + Can change hedgehog order in The Specialists
+ + Turn transition is less hectic
+ + Various small HUD improvements
+ * Fix wrong key names being displayed in key selection
+
+Gameplay:
+ + Quick games are more random: More map types, random team size and difficulty
+ + Hand-drawn maps can now be scaled with slider
+ + Slightly longer delays between turns to make it easier to follow the game
+ + Track high scores in singleplayer challenges
+ + Show check mark for completed scenarios, challenges and trainings
+ + Training/challenge/scenario menu now supports team selection
+ + Most target practices now highlight position of next target (must be unlocked first)
+ + Homing bee can now be used as secondary ammo
+ + If bee target was placed in the dark area in a wrap world edge map, bee will first fly across border
+ + Teach computer players how to use extra time
+ * Fix hedgehogs being pushed around (and other collision bugs) when they overlap
+ * Fix homing bee flying weird if passing wrap world edge or target was placed beyond it
+ * Fix air mine not colliding with crates initially
+ * Fix buggy behaviour of time box if hog took damage or died before it arrived
+ * Fix poison damage not working in first round
+ * Use player-chosen team identity in campaigns and singleplayer missions
+ * Fix player-chosen teams ignoring custom team controls in campaigns
+ * Fix broken behaviour of airborne attacks when placed near bounce world edge
+ * Fix crate sometimes collected twice when switching to hedgehog that touches it
+ * Deny placement of piano beyond bounce world edge
+ * Fix laser sight not working properly when it starts out of map bounds
+ * Fix parachute making hog stuck or fast when bumping into wall while looking other way
+ * Add missing winner animation in single missions
+ * Fix hog floating when switching to moving hog
+ * Fix jump key not being ignored after placing girder or target
+ * Explode hog instantly when taking damage while dying
+ * Fix buggy hog when hog took damage during "idle" phase in kamikaze attack
+
+Styles and schemes:
+ + The Specialists: Unlock game scheme
+ + The Specialists: Add script parameter support to set custom specialists order
+ + Control, CTF_Blizzard: Display scores in stats screen
+ + CTF_Blizzard: Various minor graphical and text improvements
+ + Frenzy: Change ammo slots
+ + Continental supplies: Show continent in team bars
+ * Balanced Random Weapon: Fix Lua errors after using Time Box
+ * Racer: Fix racer ghost not getting reset after a skip
+ * Space Invasion: No longer allow to set start shield above shield limit
+ * Battalion, WxW: Crates drop between turns, when appropriate
+ * Battalion: Sudden Death effects are now like in the base game
+ * King Mode: Fix team sometimes not being killed properly if king drowned
+ * King Mode: Kill resurrected minions if king is not alive
+ * King Mode: Fix whole clan being killed if a king died
+ * King Mode: Fix king placement phase not working correctly with multiple teams in a clan
+ * HedgeEditor: Fix major FPS drop when there are a lot of objects
+ * Control: Fix score failure after using extra time
+ * Frenzy: Fix incorrect ammo slot numbers in ammo menu
+ * Continental supplies: Computer teams now select random continent
+ * WxW, Racer: Computer teams no longer block setup phase
+ * Mutant: Delete excess teams when a clan has more than one team
+
+A Classic Fairytale:
+ + Backstab: Disable utilities before traitor has been dealt with
+ * Backstab: Prevent attacking the cannibals before making the choice
+ * Backstab: Fix/tweak behaviour in 3rd enemy wave
+ * First blood: Fix Lua error when hitting Attack after failing the rope challenge
+ * First blood: Fix a cut scene being played twice in row
+ * The Shadow Falls: Fix Lua error when hog dies during choice phase
+ * The Shadow Falls: Fix mission getting stuck when hog dies after accepting offer, but before returning
+ * The Shadow Falls: Fix many other Lua errors when hogs die in certain situations
+ * General: Clear hazards around cyborg when it appears in cut scenes
+ * General: Disable Sudden Death for all missions
+ * Various minor tweaks and bugfixes
+
+A Space Adventure:
+ + Show your current records at mission start when re-playing one of the challenges
+ + Spacetrip: Move flowers of desert planet above cactus
+ + Searching in the dust: Enable skip in entire mission
+ + Getting to the device: Different ending when hero chose to battle in "Bad timing" mission
+ * Searching in the dust: Fix mission ending when all smugglers are dead
+ * Searching in the dust: Fix a lot of broken/stupid smuggler behaviours
+ * Chasing the blue hog: Fix player not losing the race when timing out while still having the rope
+ * Chasing the blue hog: Fix player winning if Crazy Runner died
+ * Bad timing: Win mission in "flee" variant if all enemy hogs are dead
+ * Getting to the device: Fix clan colors
+ * Fix errors when hero and enemies die in same turn
+ * Various minor tweaks and bugfixes
+
+Controls:
+ + Add control to unselect current weapon (no key chosen by default)
+ + Add support for 4th and 5th mouse buttons
+ + Allow to leave a control unused
+ + Reset zoom resets zoom to zoom level set in options
+ + Add control to display mines time and health crate health (default: O)
+ + Precise + Reset zoom resets zoom to 100% (instead of zoom in options)
+ + Precise + zoom in/out changes zoom in smaller steps
+ + Precise + volume up/down changes volume in smaller steps
+ + Precise + cursor move keys move camera slower
+ + New chat command: “/help room” (shows room chat commands within the game)
+ + Default demo fast-forward key changed from “S” to “F”
+ * Fix broken default keyboard controls for team chat and camera movement
+
+Graphics:
+ + Animate drill rockets
+ + New idle shoryuken animation
+ + Scatter molotov cocktail pieces
+ + Improve air plane effects when used with wrap or ocean world edge
+ * Fix speech bubbles overlapping in the wrong order
+ * Fix wrong ice beam angle if it goes diagonally up out of map through world wrap
+ * Fix double water splash when flying saucer drowns
+ * Fix odd floating pixels when wielding and rotating cleaver
+ * Fix parachute and birdy sometimes being drawn behind hedgehogs and objects
+
+Game HUD:
+ + Display current hog health (and related status icons) at top right corner
+ + Display laser sight icon above wind bar when laser sight utility is active
+ + Display selected weapon above hedgehog for some weapons/tools
+ + Change cursor of piano strike
+ + Colorize switching arrows, pointing arrow and target cross in clan color
+ + Skip ammo menu animation when playing with turn time of 10s or less
+ + Don't show crate spawn message for initial crates in missions
+ + Don't show hedgehog health if “invulnerable” game modifier is active
+ + Display player name of own teams in online games
+ + Show contour of flying saucer and air mines when in highly opaque water
+ + Remove visual clutter in cut scenes
+ + Add setting to set default/initial zoom
+ * Black clan color can now be used without visual problems
+ * Fix last 2 characters in demo chat being missing
+ * Hide most HUD elements in cinematic mode
+ * Don't show "F1", "F2", etc. in ammo menu if these aren't the actual slot keys
+ * Fix wind bar animation not looping properly
+ * Fix airplane line being drawn above many HUD elements
+ * Suppress “ is gone.” message at end of game
+ * Fix game engine ignoring appropriate number formatting of user language
+ * Fix buggy behaviour when entering speech bubble command in hog placement phase
+
+Translations:
+ + Complete: German, Polish
+ + Major updates: Chinese, Scottish Gaelic
+ + Credits page is now translatable
+ * Remove Arabic translation from release
+
+Frontend:
+ + Add button in main menu at top left corner to open credits page
+ + Restructure credits page
+ + More intelligent automatic mission selection in campaign screen
+ + New data directory for video thumbnails: Data/VideoThumbnails
+ + Display a warning when the same key is used multiple times
+ + Stats screen now hides empty sections
+ + Visual notification when someone joins the room online
+ + Display recommended max. hedgehog count for Perlin maps
+ + Various minor style tweaks
+ * Fix broken handling of /watch chat command on official server
+ * Fix renaming a video leading to loss of thumbnail after restart
+ * Fix controls list failing to display correct key names with regards to keyboard layout
+ * Fix force-locked schemes getting unlocked when changing map types
+ * Fix possible to select background-only or hidden themes indirectly by changing map type
+ * Disallow slash, backslash and colon characters in team and scheme names
+
+Sounds and voicepacks:
+ + sndYoohoo has been split to sndYoohoo and sndKiss
+ + Voice files sndPoisonCough and sndPoisonMoan are now optional (fall back to Default voicepack)
+ + Add taunt: sndFlyAway / Flyaway.ogg: When hedgehog flies off the map
+ + Add underwater sound for airplane
+ + Tweak some taunts: sndFirstBlood, sndLeaveMeAlone, sndCutItOut
+ * Fix English voicepack selection of team being overwritten when playing in non-English locale
+
+Theme customization
+ + Default fallback Sudden Death music of themes (fallback-sd-music) is now sdmusic.ogg
+ + Make rope stylable by theme: Support for RopeNode.png and rope-step in theme.cfg
+
Lua API:
+ + New call: SaveMissionVar(varname, value): Save value to mission variable (variable for non-campaign mission)
+ + New call: GetMissionVar(varname): Get value of mission variable
+ + New call: SetTurnTimePaused(isPaused): Call with true to pause turn time, false to unpause
+ + New call: GetTurnTimePaused(): Returns true if turn time is paused due to Lua
+ + New call: AddMissionTeam(color): Add mission team, i.e. the team selected by player in campaign/mission page. Returns ,
+ + New call: AddMissionHog(health): Add a hedgehog for the mission team
+ + New call: SetTeamPassive(teamname, isPassive): Mark a team as passive. Passive teams do not play and are treated like frozen teams.
+ + New call: IsHogAlive(gear): Returns true if gear is a hegehog which is alive, not about to die and not hidden
+ + New call: SetAmmoSlot(ammoType, slot): Overwrite ammo slot of ammo type (use with care!)
+ + New return value: AddTeam returns ,
+ + SetClanColor: Now accepts negative color argument for user clan color, like in AddTeam
+ + AddTeam: Append “_qau” to voicepack name to enable automatic selection of voicepack language
+ + ShowMission: Add new icons: hedgehog (10), flags (11)
+ + Utils library: New calls: getReadableChallengeRecord, updateChallengeRecord, integerSqrt, integerHypotenuse
+ + New callback: onGameResult(winningClan): Called when the game ends normally. winningClan = index of winning clan or -1 on draw
+ + New callback: onCaseDrop(gear): Called at the point where a crate MIGHT be dropped between turns. Gear is the crate gear or nil
+ + New callback: onHogSwitch(oldHog): Called when hog was switched with the “switch hedgehog” utility
+ + SendStat extension: Option to use predefined modes with siPointType: statMessage = "!POINTS", "!TIME", "!TIME0" to "!TIME3", "!CRATES", or "!EMPTY"
+ + SimpleMission: Add isMissionTeam attribute for teams
+ + SpeedShoppa/TargetPractice libraries: Remove custom hog and team info settings
+ + TargetPractice library: Add faceLeft parameter
+ + Params explode, poison in the SpawnFake*Crate functions now optional and default to false
+ + New global: InitHealth: Initial hog health value from game scheme (read-only)
+ + Animate library: AnimOutOfNowhere: destX and destY are now optional (default: current position)
+ * Fix SetClanColor causing crashes and severe rendering bugs
+ * Fix SetAmmoDelay not working properly when called after onGameStart
+ * Fix DismissTeam not clearing team properly
+ * SimpleMission: Fix Lua error spam when a custom goal fails
+ * gstWinner state is preserved after the game ended
+ * If there's a mission team, IsHogLocal now only returns true for hogs in the same clan as the mission team
+
+====================== 0.9.25 ======================
+HIGHLIGHTS:
+ + Complete overhaul of Continental supplies
+ + Can adjust weapon start and crate probabilities in Balanced Random Weapon
+ + Remove rubber duck
+ + New air mine features
+ + Rework team rankings
+ + Tied teams now rank equally
+ + Help button in main menu
+ + 19 new hedgehog taunts
+ + Many new Lua API features
+ * Functionality of controllers restored
+ * Fix at least 2 crashes
+ * Fixed some awkward network bugs which caused games to come to a standstill
+ * Many bugs related to the wrap world edge fixed (but not all)
+ * Sudden Death always came exactly 1 turn later than planned
+
+Game, gameplay:
+ + Increase hedgehog limit to 64
+ + Remove rubber duck
+ + Shotgun, Desert Eagle, Sniper Rifle, Firepunch, Kamikaze, Whip and Baseball Bat can now hit air mines (and some other projectiles)
+ + Freezer can freeze air mines when they don't move too fast
+ + Air mines get stunned by getting shoved
+ + Shotgun shots can now pass through portals
+ * Fix hog being unable to walk after using sniper rifle without firing both shots
+ * Fix sine gun dealing damage to attacker if shooting up
+ * Hedgehog was able to drop more than 2 sticky mines if dropping first one from utility, then stop using utility
+ * Fix Sudden Death starting in the second turn of a round rather than the first
+ * Fix hammer and pickhammer not digging correctly at wrap world edge
+ * Fix drill rocket exploding when digging at bounce/wrap world edge
+ * Fix freezer ray not working through wrap world edge
+ * Fix freezer ray going through bounce world edge
+ * Fix freezer ray extending with low fuel usage when firing straight up/down while holding up/down key
+ * Fix cake walking through bounce world edge
+ * Fix cake walking through land when reaching wrap world edge
+ * Laser sight now works properly through wrap world edge
+ * Fix projectiles behaving incorrectly with land just behind the wrap world edge
+ * Fix bee weapon becoming unusable when hitting attack key in mid-air
+ * Fix hog sometimes getting stuck in land if roping very fast
+ * Fix seduction not stopping if hog took damage before attack was complete
+ * Limit hedgehog health to 268435455 to prevent some bugs
+ * Fix rare possibility that hog is resurrected and starts with 0 or negative health
+
+Game, controls and commands:
+ + Add new key to show mission panel (default: M)
+ + Add new key to cycle through timer values (default: N)
+ + Add default controls for controller (see README.md)
+ + Add chat command “/help”, displays help for chat commands
+ + Rename chat command “/team” to “/clan” (but “/team” still works)
+ * Functionality of controllers restored
+ * Fix crash when 2 or more controllers were connected
+ * Fix cursor teleporting to center after leaving game with a video recording
+ * Fix /hta, /hsa and /hya commands not writing message in chat
+
+Game, audiovisuals:
+ + Campaigns and missions now use the user's chosen custom clan colors
+ + New default brown clan color for better contrast
+ + Allow to change volume during pause
+ + Add sounds: flamethrower, landspray, idle freezer, shorykuen hit
+ + Add taunts: Amazing, Brilliant, Bugger, Cutitout, Drat, Excellent, Fire, Gonnagetyou, Grenade,
+ Hmm, Leavemealone, Ohdear, Ouch, Revenge, Runaway, Solong, Thisoneismine, Whatthe,
+ Watchthis
+ * Enemy/AI hogs now say “Hmm” on turn start instead of vowing for revenge (at least in most voice packs)
+ * Fix extreme amounts of droplets when shooting with minigun into ocean world edge
+ * Fix some flakes disappearing in world wrap worlds while moving camera
+ * Fix invisible projectile timer, attack bar, target on other side of wrap world edge
+ * Fix attack bar drawn over GUI elements
+ * Fix video recorder not working when game audio was disabled
+ * Fix teleport tooltip claiming it doesn't end turn in hog placing phase with inf. attack
+ * Prevent voices from being spoken directly before a victory voice
+ * Fix damage not being displayed if hog drowns in water with 100% opacity (like in Compost theme)
+ * Fix retreat timer appearing after using baseball bat or whip and immediately taking damage
+ * Fix musical effects of RC plane and piano not playing if music is enabled but sounds effects are disabled
+
+Frontend:
+ + Add help button in main menu (link to Hedgewars Wiki)
+ + Add setting to disable audio dampening when losing window focus
+ + Rework player rankings: Losing clans are now ranked in the reverse order they died
+ * Fix player rankings on round draw: Clans that died in the same turn now have the same rank
+ * Fix rare crash when aborting video encoding in progress
+ * Fix critical failure to cleanup teams list after rejoining game under certain conditions
+ * Fix displayed Sudden Death timeout being off by one
+ * Controllers are detected again
+ * Fix failure to shutdown game window properly after player got kicked
+ * No longer allow having schemes with equal names (case-insensitive)
+ * Refuse to load schemes which match the name of a default scheme
+ * No longer save default weapon schemes into file
+ * Pseudo player names in chat (like “[server]”) are no longer clickable
+ * Lobby/room: No longer allow opening context menu if no player selected
+ * Fix game window width/height setting being broken when using Arabic locale
+
+Server:
+ + Add “/help” chat command for command help
+ + Can now clear room greeting by using chat command “/greeting” without arguments
+ + Many new error and status messages for improved usability
+ * Fix many server messages being not translated
+
+Highlander:
+ * Fix all hogs receiving a free teleport after hog placement phase
+ * Fix hogs receiving air strikes in maps with border
+
+Racer/TechRacer:
+ * Fix rare bug in TechRacer causing crates and other objects to not appear on start of turn
+ * Fix ranking of teams if teams are tied
+
+Balanced Random Weapon:
+ + Can adjust weapon start and crate probabilities by using ammo scheme
+
+Random Weapon:
+ * Fix breakage when enabling per-hog ammo
+
+Mutant:
+ + Do not reduce mutant's health in Ready phase
+ + Play poison hurt sound when mutant is low on health
+ + Unlock game scheme
+
+Construction Mode:
+ * Fix girder/rubber cost not being updated correctly after selection
+
+Continental supplies:
+ + Continents are now selected before the game starts
+ + Continents give hog different start health
+ + Add Antarctica special: Upside-Down World (teleport to top of map)
+ + Major rewrite of ALL texts for better usability
+ + Add custom weapon tooltips
+ + Improve audiovisual effects
+ + Show message when hog receives new continent ammo
+ + Sabotaged hedgehogs also emit smoke when it's not their turn
+ + Can switch continent in reverse order with [Precise]+[Switch]
+ * Sabotage deals no damage in ready phase, while attacking or retreating
+ * Invulnerability now protects from sabotage damage
+ * Sabotage kills hog instantly when health reaches 0
+ * Reliably prevent using of Lonely Cries and baseball bat specials when usage not allowed
+ * Don't explode Anno 1302, Medicine and Bouncy Boomerang if drowning
+ * Don't play “Missed” and “Laugh” taunt when those don't make sense
+ * Fix retreat timer not turning red for some weapons
+
+Space Invasion:
+ + Display round score in a separate row
+ + Keep round score displayed after round ends, remove round score announcer message
+ + If team scores are tied at the end, continue playing rounds with the tied teams until there's a winner
+ * Fix rare Lua error message spam at end of game
+ * Fix round score and other info numbers messing up after screen resize
+ * Fix kamikaze bonus not being shown
+
+Missions and styles:
+ * Basic Movement Training: Back jumps are now easier
+ * The Great Escape: Infinite attack mode did not work
+ * Shotgun/Sniper Rifle Target Practice: Suppress “X remaining” message
+ * Fix resurrection animation appearing at wrong position for some missions and styles
+ * Fix Lua error when playing any mission or style in Lithuanian language
+
+A Classic Fairytale:
+ * Fix clan membership of princess in some missions
+ * Mission 5: Tribe was not in same clan as Natives, screwing up stats a bit
+
+A Space Adventure:
+ + The big bang: Terrain types are easier to distinguish
+ + Hard Flying: Display current flying time next to team bar
+ * Hard Flying: Fix incorrect recorded time, was 6 seconds more than reality
+ * Searching in the Dust: Fix display error when destroying device crate
+ * Searching in the Dust: Don't take away control right above the pit near Sandy
+ * The big bang: Don't say "Missed" or "Yes, Sir!" when inappropriate
+ * The last Encounter: Fix clan membership of PAotH
+
+Themes:
+ + New Sudden Death water texture for CrazyMission theme
+ + Add dust flakes for Cheese and CrazyMission themes
+ + New land objects for Beach theme
+ * Fix repeating sun in Hoggywood theme
+
+Content creation:
+ + HWPs can be nested inside HWPs (1 layer deep only)
+ + Add-ons now support preview images for campaign missions
+
+Translations:
+ + Translations kept up-to-date: German, Polish
+ + Major translation updates: Russian, Japanese, Scottish Gaelic, Ukrainian
+
+Lua API:
+ * Deprecation: Setting TurnTimeLeft/ReadyTimeLeft directly is deprecated and will become useless in future. Use the setter functions below
+ * Deprecation: Symbols amDuck/gtDuck are deprecated, will be removed later. For now, they alias to amCreeper/gtCreeper
+ * Changed global: lfCurrentHog becomes lfCurHogCrate
+ + New call: SetTurnTimeLeft(newTurnTimeLeft): Set remaining turn time
+ + New call: SetReadyTimeLeft(newReadyTimeLeft): Set remaining ready time
+ New call: Retreat(time [, respectGetAwayTimeFactor): Force current turn into retreating mode
+ + New call: GetAmmoTimer(gearUid, ammoType): Returns current set timer for given ammoType and hog gear in ms. Returns nil for non-timerable ammo
+ + New call: EnableSwitchHog(): Enable hog switching
+ + New call: GetAmmo(ammoType): Returns ammo configuration (corresponds to SetAmmo)
+ + New call: GetVampiric(): Returns true if vampirism is currently active
+ + New call: GetLaserSight(): Returns true if laser sight (as utility) is currenctly active (ignoring sniper rifle)
+ + New call: IsHogHidden(gear): Returns true if hog is hidden
+ + New call: PlayMusicSound(soundId): Play a sound as replacement for the main background music
+ + New call: StopMusicSound(soundId): Stop a “music sound” and resume the regular music
+ + Changed call: AddTeam: 2nd param. color: Accepts negative value to use a default clan color from player settings
+ + Changed call: HedgewarsScriptLoad: 2nd param. mustExist. If false, it's allowed for the script to not exist
+ + Changed call: HedgewarsScriptLoad: Return true on success and false on failure
+ + Change callback: onGearResurrect: 2nd parameter for visual gear spawned at resurrect position (might be nil)
+ + New parameter: SetAmmoTexts: 5th param. showExtra: Set to false to hide texts like “Not yet available”
+ + New parameter: ShowMission: 6th param. forceDisplay: Set to true to prevent this particular mission panel to be hidden manually by player
+ + Can set custom team rank: Call SendStat with 1st param siTeamRank and 2nd param to the desired rank, as string. Must be called before siPlayerKills of the team for which this applies
+ + New Lua library: Achievements (currently only for internal use)
+ + Add sprite tint globals in Utilts library: U_LAND_TINT_NORMAL, U_LAND_TINT_ICE, U_LAND_TINT_INDESTRUCTIBLE and U_LAND_TINT_BOUNCY
+ + New globals: Infinite fly time for jetpack/Birdy by setting health to JETPACK_FUEL_INFINITE or BIRDY_ENERGY_INFINITE, respectively
+ + New global game variable: MaxCaseDrops. Max. number of crats which can be in game by crate drops
+ + New global: NO_CURSOR. Value of CursorX and CursorY if cursor is inactive
+ + New global: AMMO_INFINITE. Value for infinite ammo count for AddAmmo and other functions
+ + New global: MAX_HOG_HEALTH. Maximum possible hedgehog health
+ + New global: MAX_TURN_TIME. Maximum possible turn time
+ + New global: EXPLForceDraw. Flag for Explode function, forces land removal even with gfSolidLand on
+ + New global: INTERFACE. Type of the game interface: "desktop" for desktop, "touch" for touchscreen
+ + New globals: capcolDefault, capcolSetting: Default caption colors
+ * Animate library: Remove defunct follow argument for AnimVisualGear
+ * Fixed variable: TotalRounds was -1 (instead of 0) in first real round after hog placement phase
+ * Fixed variables: LeftX, RightX, TopY, LAND_WIDTH, LAND_HEIGHT were broken if onVisualGearAdd was defined
+ * AI sometimes intentionally shot hedgehogs with aihDoesntMatter set
====================== 0.9.24.1 ====================
* Fix crash when portable portal device is fired at reduced graphics quality
@@ -91,6 +512,7 @@
* Capture the Flag: Fix many bugs caused by playing with >2 teams
* Capture the Flag: Properly place flag when first hog uses kamikaze or TimeBox
* Capture the Flag: Fix flag not being dropped when carrier uses piano strike
+ * Capture the Flag: Fix clan not winning if enemy was in time travel
* CTF_Blizzard: Don't allow more than 2 clans. Excess hogs will be removed
A Space Adventure:
diff -r 0135e64c6c66 -r c4fd2813b127 INSTALL.md
--- a/INSTALL.md Wed May 16 18:22:28 2018 +0200
+++ b/INSTALL.md Wed Jul 31 23:14:27 2019 +0200
@@ -7,6 +7,8 @@
Dependencies
------------
+### Hardware dependencies
+See README.md.
### Core dependencies
@@ -22,6 +24,9 @@
- SDL\_mixer >= 2.0
- SDL\_image >= 2.0
- SDL\_ttf >= 2.0
+- PhysFS >= 3.0.0
+
+On FreeBSD, you also need the package “fpc-rtl-extra”.
### Recommended optional dependencies
@@ -29,8 +34,6 @@
usually better to have them installed. Hedgewars has fallback mechanisms
in if these are not found on your system.
-- qtstyleplugins (for Qt 5)
-- PhysFS >= 2.0.0
- Lua = 5.1.0
### Optional dependencies
@@ -45,9 +48,6 @@
- GHC >= 6.10
- Various Haskell packages (see below)
-PhysFS will be internally built if `-DPHYSFS_SYSTEM=OFF` is passed to `cmake`
-(also allows to set `PHYSFS_LIBRARY` and `PHYSFS_INCLUDE_DIR` if needed).
-
Lua will be automatically built if not found.
### Hedgewars Server dependencies
@@ -77,9 +77,9 @@
- `utf8-string`
- `SHA`
- `entropy`
- - `zlib` >= 0.5.3 and < 0.6
+ - `zlib` >= 0.5.3 and < 0.7
- `regex-tdfa`
-
+ - `binary` >= 0.8.5.1
Building
--------
@@ -118,7 +118,6 @@
- `CMAKE_INSTALL_PREFIX`: Installation directory
- `NOSERVER`: Set to `ON` to *not* build the server
- `NOVIDEOREC`: Set to `ON` to *not* build the video recorder
-- `SYSTEM_PHYSFS`: Set to `OFF` to use Hedgewars-bundled PhysFS
### Step 2: Make
@@ -154,15 +153,6 @@
If this didn't work, make sure you have the correct Qt version
(see above).
-### Hedgewars compiles successfully, but games instantly crash the map preview fails
-
-This is likely to be a problem with PhysFS. Try to build Hedgewars
-with the Hedgewars-bundled PhysFS by setting the CMake option
-`SYSTEM_PHYSFS=OFF`, then try to run `make` again.
-
-If the _bundled_ PhysFS fails, too, this is likely to be a bug in
-Hedgewars, please report at .
-
### Broken/missing Haskell dependencies
First, try to obtain the missing Haskell packages and make sure GHC
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/CMakeLists.txt
--- a/QTfrontend/CMakeLists.txt Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/CMakeLists.txt Wed Jul 31 23:14:27 2019 +0200
@@ -45,6 +45,37 @@
list(APPEND locsout ${firstline} "\n}\\;\n")
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/servermessages.h ${locsout})
+
+# Credits localization
+file(GLOB CreditsCSV ${CMAKE_SOURCE_DIR}/QTfrontend/res/credits.csv)
+foreach(csvfile ${CreditsCSV})
+ # Load credits.csv
+ file(READ ${csvfile} csv)
+
+ # Match first line of CSV file
+ string(REGEX MATCH "(E|S|U),\"[^\n\"]+\"" loc_top ${csv})
+ string(REGEX REPLACE "(E|S|U),\"([^\n\"]+)\"" "\nQT_TRANSLATE_NOOP(\"credits\", \"\\2\")" s ${loc_top})
+ list(APPEND csvlocs ${s})
+
+ # Match remaining lines of CSV file
+ string(REGEX MATCHALL "\n(E|S|U),\"[^\n\"]+\"" locs ${csv})
+ foreach(str ${locs})
+ string(REGEX REPLACE "(E|S|U),\"([^\n\"]+)\"" "QT_TRANSLATE_NOOP(\"credits\", \"\\2\")" s ${str})
+ list(APPEND csvlocs ${s})
+ endforeach(str)
+endforeach(csvfile)
+
+list(REMOVE_DUPLICATES csvlocs)
+list(GET csvlocs 0 firstline)
+list(REMOVE_AT csvlocs 0)
+set(locsout "const char * creditsMessages[] = {")
+foreach(l ${csvlocs})
+ list(APPEND locsout ${l} ",")
+endforeach(l)
+list(APPEND locsout ${firstline} "\n}\\;\n")
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/creditsmessages.h ${locsout})
+
+
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/model)
@@ -92,8 +123,10 @@
main.cpp
team.cpp
campaign.cpp
+ mission.cpp
ui_hwform.cpp
${CMAKE_CURRENT_BINARY_DIR}/hwconsts.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/sdlkeys.cpp
)
if(MINGW)
@@ -136,7 +169,9 @@
hwconsts.h
sdlkeys.h
campaign.h
+ mission.h
${CMAKE_CURRENT_BINARY_DIR}/servermessages.h
+ ${CMAKE_CURRENT_BINARY_DIR}/creditsmessages.h
)
set(hwfr_rez hedgewars.qrc)
@@ -214,6 +249,13 @@
if(CMAKE_CXX_COMPILER MATCHES "clang*")
list(APPEND HW_LINK_LIBS stdc++ m)
+ if(NOT APPLE)
+ list(APPEND HW_LINK_LIBS atomic)
+ endif()
+endif()
+
+if(WIN32 AND VCPKG_TOOLCHAIN)
+ list(APPEND HW_LINK_LIBS Qt5::WinMain)
endif()
target_link_libraries(hedgewars ${HW_LINK_LIBS})
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/HWApplication.cpp
--- a/QTfrontend/HWApplication.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/HWApplication.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -90,6 +90,7 @@
form->NetConnectQuick(address, (quint16) port);
return true;
} else {
+ //: Here, “scheme” refers to the scheme of a Uniform Resource Identifier”
const QString errmsg = tr("Scheme '%1' not supported").arg(scheme);
MessageDialog::ShowErrorMessage(errmsg, form);
return false;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/binds.cpp
--- a/QTfrontend/binds.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/binds.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -25,9 +25,13 @@
{"+right", "right", QT_TRANSLATE_NOOP("binds", "right"), NULL, NULL},
{"+down", "down", QT_TRANSLATE_NOOP("binds", "down"), NULL, NULL},
{"+precise", "left_shift", QT_TRANSLATE_NOOP("binds", "precise aim"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "hold down precise"), QT_TRANSLATE_NOOP("binds", "stand still on slippery land"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + left/right"), QT_TRANSLATE_NOOP("binds", "change direction without moving"), NULL, NULL},
{"ljump", "return", QT_TRANSLATE_NOOP("binds", "long jump"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Traverse gaps and obstacles by jumping:")},
{"hjump", "backspace", QT_TRANSLATE_NOOP("binds", "high jump"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "high jump (twice)"), QT_TRANSLATE_NOOP("binds", "backwards jump"), NULL, NULL},
{"switch", "tab", QT_TRANSLATE_NOOP("binds", "switch"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Switch your currently active hog (if possible):")},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + switch"), QT_TRANSLATE_NOOP("binds", "switch backwards"), NULL, NULL},
{"ammomenu", "mouser", QT_TRANSLATE_NOOP("binds", "ammo menu"), QT_TRANSLATE_NOOP("binds (categories)", "Weapons"), QT_TRANSLATE_NOOP("binds (descriptions)", "Pick a weapon or utility item:")},
{"slot 1", "f1", QT_TRANSLATE_NOOP("binds", "slot 1"), NULL, NULL},
{"slot 2", "f2", QT_TRANSLATE_NOOP("binds", "slot 2"), NULL, NULL},
@@ -39,11 +43,14 @@
{"slot 8", "f8", QT_TRANSLATE_NOOP("binds", "slot 8"), NULL, NULL},
{"slot 9", "f9", QT_TRANSLATE_NOOP("binds", "slot 9"), NULL, NULL},
{"slot :", "f10", QT_TRANSLATE_NOOP("binds", "slot 10"), NULL, NULL},
+ {"setweap ~", "none", QT_TRANSLATE_NOOP("binds", "unselect weapon"), NULL, NULL},
{"timer 1", "1", QT_TRANSLATE_NOOP("binds", "timer 1 sec"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Set the timer on bombs and timed weapons:")},
{"timer 2", "2", QT_TRANSLATE_NOOP("binds", "timer 2 sec"), NULL, NULL},
{"timer 3", "3", QT_TRANSLATE_NOOP("binds", "timer 3 sec"), NULL, NULL},
{"timer 4", "4", QT_TRANSLATE_NOOP("binds", "timer 4 sec"), NULL, NULL},
{"timer 5", "5", QT_TRANSLATE_NOOP("binds", "timer 5 sec"), NULL, NULL},
+ {"timer_u", "n", QT_TRANSLATE_NOOP("binds", "change timer"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + timer"), QT_TRANSLATE_NOOP("binds", "change bounciness"), NULL, NULL},
{"+attack", "space", QT_TRANSLATE_NOOP("binds", "attack"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Fire your selected weapon or trigger an utility item:")},
{"put", "mousel", QT_TRANSLATE_NOOP("binds", "put"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Pick a weapon or a target location under the cursor:")},
{"findhh", "h", QT_TRANSLATE_NOOP("binds", "autocam / find hedgehog"),QT_TRANSLATE_NOOP("binds (categories)", "Camera"), QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle automatic camera / refocus on active hedgehog:")},
@@ -54,9 +61,10 @@
// {"+cur_m", "", QT_TRANSLATE_NOOP("binds", "movement key modifier"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Specify a modifier key to move camera and cursor using your default hog movement keys:")},
{"zoomin", "wheelup", QT_TRANSLATE_NOOP("binds", "zoom in"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Modify the camera's zoom level:")},
{"zoomout", "wheeldown", QT_TRANSLATE_NOOP("binds", "zoom out"), NULL, NULL},
- {"zoomreset", "mousem", QT_TRANSLATE_NOOP("binds", "reset zoom"), NULL, NULL},
- {"chat", "t", QT_TRANSLATE_NOOP("binds", "chat"), QT_TRANSLATE_NOOP("binds (categories)", "Miscellaneous"), QT_TRANSLATE_NOOP("binds (descriptions)", "Talk to your team or all participants:")},
- {"chat team", "u", QT_TRANSLATE_NOOP("binds", "team chat"), NULL, NULL},
+ {"zoomreset", "mousem", QT_TRANSLATE_NOOP("binds", "reset zoom to start value"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + reset zoom"), QT_TRANSLATE_NOOP("binds", "set zoom to 100%"), NULL, NULL},
+ {"chat", "t", QT_TRANSLATE_NOOP("binds", "chat"), QT_TRANSLATE_NOOP("binds (categories)", "Miscellaneous"), QT_TRANSLATE_NOOP("binds (descriptions)", "Talk to your clan or all participants:")},
+ {"chat team", "u", QT_TRANSLATE_NOOP("binds", "clan chat"), NULL, NULL},
{"history", "`", QT_TRANSLATE_NOOP("binds", "chat history"), NULL, NULL},
{"pause", "p", QT_TRANSLATE_NOOP("binds", "pause / auto skip"),NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Pause, continue or leave your game:")},
{"quit", "escape", QT_TRANSLATE_NOOP("binds", "quit"), NULL, NULL},
@@ -65,11 +73,18 @@
{"+volup", "0", QT_TRANSLATE_NOOP("binds", "volume up"), NULL, NULL},
{"mute", "8", QT_TRANSLATE_NOOP("binds", "mute audio"), NULL, NULL},
{"fullscr", "f12", QT_TRANSLATE_NOOP("binds", "change mode"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle fullscreen mode:")},
- {"capture", "c", QT_TRANSLATE_NOOP("binds", "capture"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Take a screenshot:")},
- {"+speedup", "s", QT_TRANSLATE_NOOP("binds", "speed up replay"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Demo replay:")},
+ {"capture", "c", QT_TRANSLATE_NOOP("binds", "screenshot"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Take a screenshot:")},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + screenshot"), QT_TRANSLATE_NOOP("binds", "save map as image"), NULL, NULL},
+ {"+speedup", "f", QT_TRANSLATE_NOOP("binds", "speed up replay"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Demo replay:")},
+ {"+mission", "m", QT_TRANSLATE_NOOP("binds", "show mission information"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Heads-up display:")},
+ {"gearinfo", "o", QT_TRANSLATE_NOOP("binds", "show object information"), NULL, NULL},
//: This refers to the team info bars (name/flag/health) of all teams. These are shown at the bottom center of the screen
- {"rotmask", "delete", QT_TRANSLATE_NOOP("binds", "toggle team bars"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Heads-up display:")},
+ {"rotmask", "delete", QT_TRANSLATE_NOOP("binds", "toggle team bars"), NULL, NULL},
{"rottags", "home", QT_TRANSLATE_NOOP("binds", "toggle hedgehog tags"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + toggle hedgehog tags"), QT_TRANSLATE_NOOP("binds", "change hedgehog tag types"), NULL, NULL},
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "switch + toggle hedgehog tags"), QT_TRANSLATE_NOOP("binds", "toggle hedgehog tag translucency"), NULL, NULL},
+
+ {"!MULTI", QT_TRANSLATE_NOOP("binds (combination)", "precise + switch + toggle hedgehog tags"), QT_TRANSLATE_NOOP("binds", "toggle HUD"), NULL, NULL},
#ifdef VIDEOREC
{"record", "r", QT_TRANSLATE_NOOP("binds", "record"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Record video:")}
#endif
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/binds.h
--- a/QTfrontend/binds.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/binds.h Wed Jul 31 23:14:27 2019 +0200
@@ -22,9 +22,9 @@
#include
#ifdef VIDEOREC
-#define BINDS_NUMBER 49
+#define BINDS_NUMBER 63
#else
-#define BINDS_NUMBER 48
+#define BINDS_NUMBER 62
#endif
struct BindAction
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/campaign.cpp
--- a/QTfrontend/campaign.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/campaign.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -31,7 +31,7 @@
// if then is found rename it to use _
QString spaceCampName = campaignName;
spaceCampName = spaceCampName.replace(QString("_"),QString(" "));
- if (!teamfile->childGroups().contains("Campaign " + campaignName) and
+ if (!teamfile->childGroups().contains("Campaign " + campaignName) &&
teamfile->childGroups().contains("Campaign " + spaceCampName)){
teamfile->beginGroup("Campaign " + spaceCampName);
QStringList keys = teamfile->childKeys();
@@ -52,12 +52,20 @@
missionInList: QComboBox index of the mission as selected in the mission widget
teamName: Name of the playing team
*/
-bool isMissionWon(QString & campaignName, int missionInList, QString & teamName)
+bool isCampMissionWon(QString & campaignName, int missionInList, QString & teamName)
{
QSettings* teamfile = getCampTeamFile(campaignName, teamName);
int progress = teamfile->value("Campaign " + campaignName + "/Progress", 0).toInt();
int unlockedMissions = teamfile->value("Campaign " + campaignName + "/UnlockedMissions", 0).toInt();
- if(progress>0 and unlockedMissions==0)
+ // The CowardMode cheat unlocks all campaign missions,
+ // but as "punishment", none of them will be marked as completed.
+ // Added to make it easier to test campaigns.
+ bool cheat = teamfile->value("Team/CowardMode", false).toBool();
+ if(cheat)
+ {
+ return false;
+ }
+ else if(progress>0 && unlockedMissions==0)
{
QSettings campfile("physfs://Missions/Campaign/" + campaignName + "/campaign.ini", QSettings::IniFormat, 0);
campfile.setIniCodec("UTF-8");
@@ -79,7 +87,8 @@
{
QSettings* teamfile = getCampTeamFile(campaignName, teamName);
bool won = teamfile->value("Campaign " + campaignName + "/Won", false).toBool();
- return won;
+ bool cheat = teamfile->value("Team/CowardMode", false).toBool();
+ return won && !cheat;
}
QSettings* getCampMetaInfo()
@@ -118,15 +127,20 @@
int progress = teamfile->value("Campaign " + campaignName + "/Progress", 0).toInt();
int unlockedMissions = teamfile->value("Campaign " + campaignName + "/UnlockedMissions", 0).toInt();
+ bool cheat = teamfile->value("Team/CowardMode", false).toBool();
QSettings campfile("physfs://Missions/Campaign/" + campaignName + "/campaign.ini", QSettings::IniFormat, 0);
campfile.setIniCodec("UTF-8");
QSettings* m_info = getCampMetaInfo();
- if(progress>=0 and unlockedMissions==0)
+ if(cheat)
{
- for(unsigned int i=progress+1;i>0;i--)
+ progress = campfile.value("MissionNum", 1).toInt();
+ }
+ if((progress >= 0 && unlockedMissions == 0) || cheat)
+ {
+ for(unsigned int i = progress + 1; i > 0; i--)
{
MissionInfo missionInfo;
QString script = campfile.value(QString("Mission %1/Script").arg(i)).toString();
@@ -137,8 +151,8 @@
missionInfo.realName = m_info->value(scriptPrefix+".name", missionInfo.name).toString();
missionInfo.description = m_info->value(scriptPrefix + ".desc",
QObject::tr("No description available")).toString();
- QString image = campfile.value(QString("Mission %1/Script").arg(i)).toString().replace(QString(".lua"),QString(".png"));
- missionInfo.image = ":/res/campaign/"+campaignName+"/"+image;
+ QString image = campfile.value(QString("Mission %1/Script").arg(i)).toString().replace(QString(".lua"),QString("@2x.png"));
+ missionInfo.image = "physfs://Graphics/Missions/Campaign/"+campaignName+"/"+image;
if (!QFile::exists(missionInfo.image))
missionInfo.image = ":/res/CampaignDefault.png";
missionInfoList.append(missionInfo);
@@ -159,8 +173,8 @@
missionInfo.realName = m_info->value(scriptPrefix+".name", missionInfo.name).toString();
missionInfo.description = m_info->value(scriptPrefix + ".desc",
QObject::tr("No description available")).toString();
- QString image = campfile.value(QString("Mission %1/Script").arg(missionNumber)).toString().replace(QString(".lua"),QString(".png"));
- missionInfo.image = ":/res/campaign/"+campaignName+"/"+image;
+ QString image = campfile.value(QString("Mission %1/Script").arg(missionNumber)).toString().replace(QString(".lua"),QString("@2x.png"));
+ missionInfo.image = "physfs://Graphics/Missions/Campaign/"+campaignName+"/"+image;
if (!QFile::exists(missionInfo.image))
missionInfo.image = ":/res/CampaignDefault.png";
missionInfoList.append(missionInfo);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/campaign.h
--- a/QTfrontend/campaign.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/campaign.h Wed Jul 31 23:14:27 2019 +0200
@@ -36,7 +36,7 @@
QSettings* getCampTeamFile(QString & campaignName, QString & teamName);
QSettings* getCampMetaInfo();
bool isCampWon(QString & campaignName, QString & teamName);
-bool isMissionWon(QString & campaignName, int missionInList, QString & teamName);
+bool isCampMissionWon(QString & campaignName, int missionInList, QString & teamName);
QString getRealCampName(const QString & campaignName);
QList getCampMissionList(QString & campaignName, QString & teamName);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/drawmapscene.cpp
--- a/QTfrontend/drawmapscene.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/drawmapscene.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -29,6 +29,10 @@
#define DRAWN_MAP_COLOR_CURSOR_PEN (Qt::green)
#define DRAWN_MAP_COLOR_CURSOR_ERASER (Qt::red)
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
template T sqr(const T & x)
{
return x*x;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/game.cpp
--- a/QTfrontend/game.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/game.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -29,6 +29,8 @@
#include "hwform.h"
#include "ui/page/pageoptions.h"
+#include "ui/page/pagetraining.h"
+#include "ui/page/pagecampaign.h"
#include "game.h"
#include "hwconsts.h"
#include "gameuiconfig.h"
@@ -44,15 +46,14 @@
// last game info
QList lastGameStartArgs = QList();
GameType lastGameType = gtNone;
-QString lastTrainingSubFolder = NULL;
GameCFGWidget * lastGameCfg = NULL;
QString lastGameAmmo = NULL;
TeamSelWidget * lastGameTeamSel = NULL;
-QString training, campaign, campaignScript, campaignTeam; // TODO: Cleaner solution?
+QString trainingName, trainingScript, trainingTeam, campaign, campaignScript, campaignTeam; // TODO: Cleaner solution?
HWGame::HWGame(GameUIConfig * config, GameCFGWidget * gamecfg, QString ammo, TeamSelWidget* pTeamSelWidget) :
- TCPBase(true, 0),
+ TCPBase(true, !config->language().isEmpty(), 0),
ammostr(ammo),
m_pTeamSelWidget(pTeamSelWidget)
{
@@ -75,22 +76,29 @@
void HWGame::onClientDisconnect()
{
- switch (gameType)
+ if (demoIsPresent)
{
- case gtDemo:
- // for video recording we need demo anyway
- emit HaveRecord(rtNeither, demo);
- break;
- case gtNet:
- emit HaveRecord(rtDemo, demo);
- break;
- default:
- if (gameState == gsInterrupted || gameState == gsHalted)
- emit HaveRecord(rtSave, demo);
- else if (gameState == gsFinished)
+ switch (gameType)
+ {
+ case gtDemo:
+ // for video recording we need demo anyway
+ emit HaveRecord(rtNeither, demo);
+ break;
+ case gtNet:
emit HaveRecord(rtDemo, demo);
- else
- emit HaveRecord(rtNeither, demo);
+ break;
+ default:
+ if (gameState == gsInterrupted || gameState == gsHalted)
+ emit HaveRecord(rtSave, demo);
+ else if (gameState == gsFinished)
+ emit HaveRecord(rtDemo, demo);
+ else
+ emit HaveRecord(rtNeither, demo);
+ }
+ }
+ else
+ {
+ emit HaveRecord(rtNeither, demo);
}
SetGameState(gsStopped);
}
@@ -139,37 +147,196 @@
void HWGame::SendQuickConfig()
{
+ /* Load and increase Quick Game experience level.
+ Experience increases by 1 for each started game and maxes out
+ at 20. Low experience levels will introduce a "beginner's bias" to make
+ the first quick games easier and simpler. The max. possible difficulty
+ increases progressively the longer you play.
+ If experience is maxed out, the beginner's bias is gone and quick games
+ are completely random. */
+ int exp = config->quickGameExperience();
+ if(exp < 20)
+ {
+ config->setQuickGameExperience(exp + 1);
+ }
+ qDebug("Starting quick game ...");
+ qDebug("Quick Game experience level: %d", exp);
+
+ // Init stuff
QByteArray teamscfg;
QAbstractItemModel * themeModel = DataManager::instance().themeModel()->withoutHidden();
HWProto::addStringToBuffer(teamscfg, "TL");
- HWProto::addStringToBuffer(teamscfg, QString("etheme %1")
- .arg((themeModel->rowCount() > 0) ? themeModel->index(rand() % themeModel->rowCount(), 0).data(ThemeModel::ActualNameRole).toString() : "Nature"));
+
+ // Random seed
HWProto::addStringToBuffer(teamscfg, "eseed " + QUuid::createUuid().toString());
- HWProto::addStringToBuffer(teamscfg, "e$template_filter 2");
- HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+ int r, minhogs, maxhogs;
+ // Random map type
+ r = rand() % 10000;
+ if(r < 3000) { // 30%
+ // Random
+ r = 0;
+ } else if(r < 5250) { // 22.5%
+ // Maze
+ if(exp <= 3)
+ r = 0;
+ else
+ r = 1;
+ } else if(r < 7490) { // 22.4%
+ // Perlin
+ if(exp <= 7)
+ r = 1;
+ else
+ r = 2;
+ } else if(r < 7500 && exp >= 5) { // 0.1%
+ // Floating Flowers (just for fun)
+ r = 5;
+ } else if(r < 8750) { // 12.5%
+ // Image map
+ r = 3;
+ } else { // 12.5%
+ // Forts
+ r = 4;
+ }
+ switch(r)
+ {
+ // Random map
+ default:
+ case 0: {
+ r = rand() % 3;
+ if(r == 0)
+ {
+ // small island
+ HWProto::addStringToBuffer(teamscfg, "e$template_filter 1");
+ minhogs = 3;
+ maxhogs = 4;
+ }
+ else if(r == 1 || exp <= 6)
+ {
+ // medium island
+ HWProto::addStringToBuffer(teamscfg, "e$template_filter 2");
+ minhogs = 4;
+ maxhogs = 5;
+ }
+ else
+ {
+ // cave (locked at low experience because these maps can be huge)
+ HWProto::addStringToBuffer(teamscfg, "e$template_filter 4");
+ minhogs = 4;
+ maxhogs = 6;
+ }
+ HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+ break;
+ }
+ // Maze
+ case 1: {
+ minhogs = 4;
+ maxhogs = 6;
+ HWProto::addStringToBuffer(teamscfg, "e$mapgen 1");
+ HWProto::addStringToBuffer(teamscfg, "e$template_filter "+QString::number(rand()%6));
+ HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%16+6));
+ break;
+ }
+ // Perlin
+ case 2: {
+ minhogs = 4;
+ maxhogs = 6;
+ HWProto::addStringToBuffer(teamscfg, "e$mapgen 2");
+ HWProto::addStringToBuffer(teamscfg, "e$template_filter "+QString::number(rand()%6));
+ HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+ break;
+ }
+ // Image map
+ case 3: {
+ minhogs = 4;
+ maxhogs = 6;
+ HWProto::addStringToBuffer(teamscfg, "e$mapgen 3");
+ // Select map from hardcoded list.
+ // TODO: find a more dynamic solution.
+ r = rand() % cQuickGameMaps.count();
+ HWProto::addStringToBuffer(teamscfg, "e$map " + cQuickGameMaps[r]);
+ break;
+ }
+ // Forts
+ case 4: {
+ minhogs = 4;
+ maxhogs = 6;
+ HWProto::addStringToBuffer(teamscfg, "e$mapgen 4");
+ HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%20+1));
+ break;
+ }
+ // Floating Flowers
+ // (actually empty map; this forces the engine to generate fallback structures to have
+ // something for hogs to stand on)
+ case 5: {
+ minhogs = 4;
+ maxhogs = 8;
+ HWProto::addStringToBuffer(teamscfg, "e$mapgen 3");
+ HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%4+3));
+ break;
+ }
+ }
+
+ // Theme
+ HWProto::addStringToBuffer(teamscfg, QString("etheme %1")
+ .arg((themeModel->rowCount() > 0) ? themeModel->index(rand() % themeModel->rowCount(), 0).data(ThemeModel::ActualNameRole).toString() : "Nature"));
+
+ int hogs = minhogs + rand() % (maxhogs-minhogs+1);
+ // Cap hog count at low experience
+ if((exp <= 8) && (hogs > 5))
+ hogs = 5;
+ else if((exp <= 5) && (hogs > 4))
+ hogs = 4;
+
+ // Teams
+ // Player team
HWTeam team1;
team1.setDifficulty(0);
team1.setColor(0);
- team1.setNumHedgehogs(4);
+ team1.setNumHedgehogs(hogs);
HWNamegen::teamRandomEverything(team1);
- team1.setVoicepack("Default");
- HWProto::addStringListToBuffer(teamscfg,
- team1.teamGameConfig(100));
+ team1.setVoicepack("Default_qau");
+ // Computer team
HWTeam team2;
- team2.setDifficulty(4);
+ // Random difficulty.
+ // Max. possible difficulty is capped at low experience levels.
+ if(exp >= 15) // very easy to very hard (full range)
+ r = 5 - rand() % 5;
+ else if(exp >= 9) // very easy to hard
+ r = 5 - rand() % 4;
+ else if(exp >= 6) // very easy to medium
+ r = 5 - rand() % 3;
+ else if(exp >= 2) // very easy to easy
+ r = 5 - rand() % 2;
+ else // very easy
+ r = 5;
+ team2.setDifficulty(r);
team2.setColor(1);
- team2.setNumHedgehogs(4);
+ team2.setNumHedgehogs(hogs);
+ // Make sure the team names are not equal
do
HWNamegen::teamRandomEverything(team2);
while(!team2.name().compare(team1.name()) || !team2.hedgehog(0).Hat.compare(team1.hedgehog(0).Hat));
- team2.setVoicepack("Default");
- HWProto::addStringListToBuffer(teamscfg,
- team2.teamGameConfig(100));
+ team2.setVoicepack("Default_qau");
+ // Team play order
+ r = rand() % 2;
+ if(r == 0 || exp <= 4) // player plays first
+ {
+ HWProto::addStringListToBuffer(teamscfg, team1.teamGameConfig(100));
+ HWProto::addStringListToBuffer(teamscfg, team2.teamGameConfig(100));
+ }
+ else // computer plays first
+ {
+ HWProto::addStringListToBuffer(teamscfg, team2.teamGameConfig(100));
+ HWProto::addStringListToBuffer(teamscfg, team1.teamGameConfig(100));
+ }
+
+ // Ammo scheme "Default"
+ // TODO: Random schemes
HWProto::addStringToBuffer(teamscfg, QString("eammloadt %1").arg(cDefaultAmmoStore->mid(0, cAmmoNumber)));
HWProto::addStringToBuffer(teamscfg, QString("eammprob %1").arg(cDefaultAmmoStore->mid(cAmmoNumber, cAmmoNumber)));
HWProto::addStringToBuffer(teamscfg, QString("eammdelay %1").arg(cDefaultAmmoStore->mid(2 * cAmmoNumber, cAmmoNumber)));
@@ -184,8 +351,16 @@
{
QByteArray traincfg;
HWProto::addStringToBuffer(traincfg, "TL");
+
+ HWTeam missionTeam = HWTeam();
+ missionTeam.setName(config->Form->ui.pageTraining->CBTeam->currentText());
+ missionTeam.loadFromFile();
+ missionTeam.setNumHedgehogs(HEDGEHOGS_PER_TEAM);
+ missionTeam.setMissionTeam(true);
+ HWProto::addStringListToBuffer(traincfg, missionTeam.teamGameConfig(100));
+
HWProto::addStringToBuffer(traincfg, "eseed " + QUuid::createUuid().toString());
- HWProto::addStringToBuffer(traincfg, "escript " + training);
+ HWProto::addStringToBuffer(traincfg, "escript " + trainingScript);
RawSendIPC(traincfg);
}
@@ -194,8 +369,15 @@
{
QByteArray campaigncfg;
HWProto::addStringToBuffer(campaigncfg, "TL");
+
+ HWTeam missionTeam = HWTeam();
+ missionTeam.setName(config->Form->ui.pageCampaign->CBTeam->currentText());
+ missionTeam.loadFromFile();
+ missionTeam.setNumHedgehogs(HEDGEHOGS_PER_TEAM);
+ missionTeam.setMissionTeam(true);
+ HWProto::addStringListToBuffer(campaigncfg, missionTeam.teamGameConfig(100));
+
HWProto::addStringToBuffer(campaigncfg, "eseed " + QUuid::createUuid().toString());
-
HWProto::addStringToBuffer(campaigncfg, "escript " + campaignScript);
RawSendIPC(campaigncfg);
@@ -277,6 +459,11 @@
SetGameState(gsFinished);
break;
}
+ case 'm':
+ {
+ SetDemoPresence(false);
+ break;
+ }
case 'H':
{
SetGameState(gsHalted);
@@ -307,6 +494,14 @@
writeCampaignVar(msg.right(msg.size() - 3));
break;
}
+ case 'v':
+ {
+ if (msg.at(2) == '?')
+ sendMissionVar(msg.right(msg.size() - 3));
+ else if (msg.at(2) == '!')
+ writeMissionVar(msg.right(msg.size() - 3));
+ break;
+ }
case 'W':
{
// fetch new window resolution via IPC and save it in the settings
@@ -346,6 +541,20 @@
RawSendIPC(buf);
}
+void HWGame::FromNetWarning(const QString & msg)
+{
+ QByteArray buf;
+ HWProto::addStringToBuffer(buf, "s\x00" + msg + "\x20\x20");
+ RawSendIPC(buf);
+}
+
+void HWGame::FromNetError(const QString & msg)
+{
+ QByteArray buf;
+ HWProto::addStringToBuffer(buf, "s\x05" + msg + "\x20\x20");
+ RawSendIPC(buf);
+}
+
void HWGame::onClientRead()
{
quint8 msglen;
@@ -400,6 +609,10 @@
arguments << QString::number(resolutions.second.width());
arguments << "--height";
arguments << QString::number(resolutions.second.height());
+ if (config->zoom() != 100) {
+ arguments << "--zoom";
+ arguments << QString::number(config->zoom());
+ }
arguments << "--raw-quality";
arguments << QString::number(config->translateQuality());
arguments << "--stereo";
@@ -414,6 +627,8 @@
arguments << "--nosound";
if (!config->isMusicEnabled())
arguments << "--nomusic";
+ if (!config->isAudioDampenEnabled())
+ arguments << "--nodampen";
if (!nick.isEmpty()) {
arguments << "--nick";
arguments << nick;
@@ -427,6 +642,8 @@
arguments << "--no-healthtag";
if (config->Form->ui.pageOptions->CBTagOpacity->isChecked())
arguments << "--translucent-tags";
+ if (!config->isHolidaySillinessEnabled())
+ arguments << "--no-holiday-silliness";
arguments << "--chat-size";
arguments << QString::number(config->chatSize());
@@ -453,6 +670,19 @@
SetGameState(gsStarted);
}
+void HWGame::PlayOfficialServerDemo()
+{
+ // TODO: Use gtDemo so fast-forward is available.
+ // Needs engine support first.
+ lastGameStartArgs.clear();
+ lastGameType = gtLocal;
+
+ gameType = gtLocal;
+ demo.clear();
+ Start(false);
+ SetGameState(gsStarted);
+}
+
void HWGame::StartNet()
{
lastGameStartArgs.clear();
@@ -486,16 +716,19 @@
SetGameState(gsStarted);
}
-void HWGame::StartTraining(const QString & file, const QString & subFolder)
+void HWGame::StartTraining(const QString & file, const QString & subFolder, const QString & trainTeam)
{
lastGameStartArgs.clear();
lastGameStartArgs.append(file);
+ lastGameStartArgs.append(subFolder);
+ lastGameStartArgs.append(trainTeam);
lastGameType = gtTraining;
- lastTrainingSubFolder = subFolder;
gameType = gtTraining;
- training = "Missions/" + subFolder + "/" + file + ".lua";
+ trainingScript = "Missions/" + subFolder + "/" + file + ".lua";
+ trainingName = file;
+ trainingTeam = trainTeam;
demo.clear();
Start(false);
SetGameState(gsStarted);
@@ -524,14 +757,23 @@
emit GameStateChanged(state);
if (gameType == gtCampaign)
{
- emit CampStateChanged(1);
+ emit CampStateChanged(state);
+ }
+ else if (gameType == gtTraining)
+ {
+ emit TrainingStateChanged(1);
}
}
+void HWGame::SetDemoPresence(bool hasDemo)
+{
+ emit DemoPresenceChanged(hasDemo);
+}
+
void HWGame::abort()
{
QByteArray buf;
- HWProto::addStringToBuffer(buf, QString("efinish"));
+ HWProto::addStringToBuffer(buf, QString("eforcequit"));
RawSendIPC(buf);
}
@@ -560,3 +802,28 @@
teamfile.setValue("Campaign " + campaign + "/" + varToWrite, varValue);
}
+void HWGame::sendMissionVar(const QByteArray &varToSend)
+{
+ QString varToFind = QString::fromUtf8(varToSend);
+ QSettings teamfile(QString(cfgdir->absolutePath() + "/Teams/%1.hwt").arg(trainingTeam), QSettings::IniFormat, 0);
+ teamfile.setIniCodec("UTF-8");
+ QString varValue = teamfile.value("Mission " + trainingName + "/" + varToFind, "").toString();
+ QByteArray command;
+ HWProto::addStringToBuffer(command, "v." + varValue);
+ RawSendIPC(command);
+}
+
+void HWGame::writeMissionVar(const QByteArray & varVal)
+{
+ int i = varVal.indexOf(" ");
+ if(i < 0)
+ return;
+
+ QString varToWrite = QString::fromUtf8(varVal.left(i));
+ QString varValue = QString::fromUtf8(varVal.mid(i + 1));
+
+ QSettings teamfile(QString(cfgdir->absolutePath() + "/Teams/%1.hwt").arg(trainingTeam), QSettings::IniFormat, 0);
+ teamfile.setIniCodec("UTF-8");
+ teamfile.setValue("Mission " + trainingName + "/" + varToWrite, varValue);
+}
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/game.h
--- a/QTfrontend/game.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/game.h Wed Jul 31 23:14:27 2019 +0200
@@ -64,7 +64,6 @@
// last game info
extern QList lastGameStartArgs;
extern GameType lastGameType;
-extern QString lastTrainingSubFolder;
extern GameCFGWidget * lastGameCfg;
extern QString lastGameAmmo;
extern TeamSelWidget * lastGameTeamSel;
@@ -77,10 +76,11 @@
virtual ~HWGame();
void AddTeam(const QString & team);
void PlayDemo(const QString & demofilename, bool isSave);
+ void PlayOfficialServerDemo();
void StartLocal();
void StartQuick();
void StartNet();
- void StartTraining(const QString & file, const QString & subFolder);
+ void StartTraining(const QString & file, const QString & subFolder, const QString & trainTeam);
void StartCampaign(const QString & camp, const QString & campScript, const QString & campTeam);
void abort();
GameState gameState;
@@ -96,15 +96,19 @@
void SendChat(const QString & msg);
void SendTeamMessage(const QString & msg);
void GameStateChanged(GameState gameState);
+ void DemoPresenceChanged(bool hasDemo);
void GameStats(char type, const QString & info);
void HaveRecord(RecordType type, const QByteArray & record);
void ErrorMessage(const QString &);
void CampStateChanged(int);
+ void TrainingStateChanged(int);
void SendConsoleCommand(const QString & command);
public slots:
void FromNet(const QByteArray & msg);
void FromNetChat(const QString & msg);
+ void FromNetWarning(const QString & msg);
+ void FromNetError(const QString & msg);
private:
char msgbuf[MAXMSGCHARS];
@@ -123,8 +127,11 @@
void SendCampaignConfig();
void ParseMessage(const QByteArray & msg);
void SetGameState(GameState state);
+ void SetDemoPresence(bool hasDemo);
void sendCampaignVar(const QByteArray & varToSend);
void writeCampaignVar(const QByteArray &varVal);
+ void sendMissionVar(const QByteArray & varToSend);
+ void writeMissionVar(const QByteArray &varVal);
void flushNetBuffer();
};
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/gameuiconfig.cpp
--- a/QTfrontend/gameuiconfig.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/gameuiconfig.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -105,15 +105,17 @@
Form->ui.pageOptions->CBFrontendFullscreen->setChecked(ffscr);
Form->ui.pageOptions->SLQuality->setValue(value("video/quality", 5).toUInt());
+ Form->ui.pageOptions->SLZoom->setValue(value("video/zoom", 100).toUInt());
Form->ui.pageOptions->CBStereoMode->setCurrentIndex(value("video/stereo", 0).toUInt());
Form->ui.pageOptions->CBFrontendEffects->setChecked(value("frontend/effects", true).toBool());
Form->ui.pageOptions->CBSound->setChecked(value("audio/sound", true).toBool());
Form->ui.pageOptions->CBFrontendSound->setChecked(value("frontend/sound", true).toBool());
Form->ui.pageOptions->CBMusic->setChecked(value("audio/music", true).toBool());
Form->ui.pageOptions->CBFrontendMusic->setChecked(value("frontend/music", true).toBool());
+ Form->ui.pageOptions->CBDampenAudio->setChecked(value("audio/dampen", true).toBool());
Form->ui.pageOptions->SLVolume->setValue(value("audio/volume", 100).toUInt());
- QString netNick = value("net/nick", tr("Guest")+QString("%1").arg(rand())).toString();
+ QString netNick = value("net/nick", getRandomNick()).toString();
Form->ui.pageOptions->editNetNick->setText(netNick);
bool savePwd = value("net/savepassword",true).toBool();
Form->ui.pageOptions->CBSavePassword->setChecked(savePwd);
@@ -241,11 +243,12 @@
void GameUIConfig::SaveOptions()
{
setValue("video/fullscreenResolution", Form->ui.pageOptions->CBResolution->currentText());
- setValue("video/windowedWidth", Form->ui.pageOptions->windowWidthEdit->text());
- setValue("video/windowedHeight", Form->ui.pageOptions->windowHeightEdit->text());
+ setValue("video/windowedWidth", Form->ui.pageOptions->windowWidthEdit->value());
+ setValue("video/windowedHeight", Form->ui.pageOptions->windowHeightEdit->value());
setValue("video/fullscreen", vid_Fullscreen());
setValue("video/quality", Form->ui.pageOptions->SLQuality->value());
+ setValue("video/zoom", Form->ui.pageOptions->SLZoom->value());
setValue("video/stereo", stereoMode());
setValue("frontend/effects", isFrontendEffects());
@@ -270,6 +273,7 @@
setValue("audio/music", isMusicEnabled());
setValue("frontend/music", isFrontendMusicEnabled());
setValue("audio/volume", Form->ui.pageOptions->SLVolume->value());
+ setValue("audio/dampen", isAudioDampenEnabled());
setValue("net/nick", netNick());
if (netPasswordIsValid() && Form->ui.pageOptions->CBSavePassword->isChecked()) {
@@ -365,8 +369,8 @@
full.setWidth(wh[0].toInt());
full.setHeight(wh[1].toInt());
}
- windowed.setWidth(Form->ui.pageOptions->windowWidthEdit->text().toInt());
- windowed.setHeight(Form->ui.pageOptions->windowHeightEdit->text().toInt());
+ windowed.setWidth(Form->ui.pageOptions->windowWidthEdit->value());
+ windowed.setHeight(Form->ui.pageOptions->windowHeightEdit->value());
return std::make_pair(full, windowed);
}
@@ -443,6 +447,26 @@
return Form->ui.pageOptions->CBFrontendFullscreen->isChecked();
}
+quint16 GameUIConfig::zoom()
+{
+ return Form->ui.pageOptions->SLZoom->value();
+}
+
+bool GameUIConfig::isHolidaySillinessEnabled() const
+{
+ return value("misc/holidaySilliness", true).toBool();
+}
+
+int GameUIConfig::quickGameExperience() const
+{
+ return value("misc/quickGameExperience", 0).toInt();
+}
+
+void GameUIConfig::setQuickGameExperience(int exp)
+{
+ setValue("misc/quickGameExperience", exp);
+}
+
bool GameUIConfig::isSoundEnabled()
{
return Form->ui.pageOptions->CBSound->isChecked();
@@ -460,6 +484,10 @@
{
return Form->ui.pageOptions->CBFrontendMusic->isChecked();
}
+bool GameUIConfig::isAudioDampenEnabled()
+{
+ return Form->ui.pageOptions->CBDampenAudio->isChecked();
+}
bool GameUIConfig::isShowFPSEnabled()
{
@@ -503,6 +531,16 @@
Form->ui.pageOptions->editNetNick->setText(value("net/nick", "").toString());
}
+QString GameUIConfig::getRandomNick()
+{
+ // Generate random nick name or pick old one if one was already generated.
+ QString nick;
+ if (cachedRandomNick.isNull())
+ // "Guest" + number between 1 and 99999
+ cachedRandomNick = tr("Guest") + QString("%1").arg(rand() % 99999 + 1);
+ return cachedRandomNick;
+}
+
QByteArray GameUIConfig::netPasswordHash()
{
return QCryptographicHash::hash(Form->ui.pageOptions->editNetPassword->text().toUtf8(), QCryptographicHash::Md5).toHex();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/gameuiconfig.h
--- a/QTfrontend/gameuiconfig.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/gameuiconfig.h Wed Jul 31 23:14:27 2019 +0200
@@ -47,6 +47,7 @@
QString language();
bool isMusicEnabled();
bool isFrontendMusicEnabled();
+ bool isAudioDampenEnabled();
bool isShowFPSEnabled();
bool isAltDamageEnabled();
bool appendDateTimeToRecordName();
@@ -54,6 +55,7 @@
quint8 volume();
quint8 timerInterval();
QString netNick();
+ QString getRandomNick();
QByteArray netPasswordHash();
int netPasswordLength();
void clearPasswordHash();
@@ -66,6 +68,10 @@
bool isReducedQuality() const;
bool isFrontendEffects() const;
bool isFrontendFullscreen() const;
+ quint16 zoom();
+ bool isHolidaySillinessEnabled() const;
+ int quickGameExperience() const;
+ void setQuickGameExperience(int exp);
void resizeToConfigValues();
quint32 stereoMode() const;
void setValue(const QString & key, const QVariant & value);
@@ -102,6 +108,8 @@
QList m_binds;
void applyProxySettings();
+
+ QString cachedRandomNick;
};
#endif
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/hedgewars.qrc
--- a/QTfrontend/hedgewars.qrc Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/hedgewars.qrc Wed Jul 31 23:14:27 2019 +0200
@@ -1,6 +1,7 @@
../share/hedgewars/Data/Graphics/AmmoMenu/Ammos_base.png
+ ../share/hedgewars/Data/misc/keys.csv
res/css/qt.css
res/css/chat.css
res/css/christmas.css
@@ -35,39 +36,18 @@
res/botlevels/net3.png
res/botlevels/net4.png
res/botlevels/net5.png
- res/campaign/A_Classic_Fairytale/first_blood.png
- res/campaign/A_Classic_Fairytale/shadow.png
- res/campaign/A_Classic_Fairytale/journey.png
- res/campaign/A_Classic_Fairytale/united.png
- res/campaign/A_Classic_Fairytale/backstab.png
- res/campaign/A_Classic_Fairytale/dragon.png
- res/campaign/A_Classic_Fairytale/family.png
- res/campaign/A_Classic_Fairytale/queen.png
- res/campaign/A_Classic_Fairytale/enemy.png
- res/campaign/A_Classic_Fairytale/epil.png
- res/campaign/A_Space_Adventure/cosmos.png
- res/campaign/A_Space_Adventure/moon01.png
- res/campaign/A_Space_Adventure/moon02.png
- res/campaign/A_Space_Adventure/ice01.png
- res/campaign/A_Space_Adventure/ice02.png
- res/campaign/A_Space_Adventure/desert01.png
- res/campaign/A_Space_Adventure/desert02.png
- res/campaign/A_Space_Adventure/desert03.png
- res/campaign/A_Space_Adventure/fruit01.png
- res/campaign/A_Space_Adventure/fruit02.png
- res/campaign/A_Space_Adventure/fruit03.png
- res/campaign/A_Space_Adventure/death01.png
- res/campaign/A_Space_Adventure/death02.png
- res/campaign/A_Space_Adventure/final.png
res/bonus.png
res/Hedgehog.png
res/net.png
res/About.png
+ res/AboutIcon.png
res/SimpleGame.png
res/Campaign.png
res/CampaignDefault.png
res/Multiplayer.png
res/Trainings.png
+ res/Challenges.png
+ res/Scenarios.png
res/Background.png
res/BackgroundChristmas.png
res/BackgroundEaster.png
@@ -81,9 +61,12 @@
res/audio.png
res/camera.png
res/Settings.png
+ res/Help.png
res/dropdown.png
res/dropdown_disabled.png
res/dropdown_selected.png
+ res/keyconflict.png
+ res/keyconflict_selected.png
res/new.png
res/edit.png
res/delete.png
@@ -120,6 +103,10 @@
res/panelbg.png
res/lightbulb_on.png
res/lightbulb_off.png
+ res/scroll_up.png
+ res/scroll_down.png
+ res/scroll_left.png
+ res/scroll_right.png
res/spin_up.png
res/spin_up_disabled.png
res/spin_down.png
@@ -209,6 +196,7 @@
res/StatsMostSelfDamage.png
res/StatsSelfKilled.png
res/StatsSkipped.png
+ res/StatsEverAfter.png
res/StatsCustomAchievement.png
res/Start.png
res/mapRandom.png
@@ -228,10 +216,13 @@
res/chat/lamp_off.png
res/chat/ingame.png
res/splash.png
- res/html/about.html
+ res/credits.csv
res/chat/hedgehogcontributor.png
res/chat/hedgehogcontributor_gray.png
res/chat/roomadmincontributor.png
res/chat/roomadmincontributor_gray.png
+
+ res/Trainings_en.png
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/hwconsts.cpp.in
--- a/QTfrontend/hwconsts.cpp.in Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/hwconsts.cpp.in Wed Jul 31 23:14:27 2019 +0200
@@ -16,6 +16,12 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+/*
+ * PLEASE NOTE: hwconsts.cpp is automatically generated from hwconsts.cpp.in.
+ * Do not edit hwconsts.cpp manually, it will be overwritten when building.
+ * Edit hwconsts.cpp.in to change the code.
+ */
+
#include
#include "hwconsts.h"
@@ -28,6 +34,8 @@
QString * cRevisionString = new QString("${HEDGEWARS_REVISION}");
QString * cHashString = new QString("${HEDGEWARS_HASH}");
+// For disallowing some characters that would screw up file name
+QString * cSafeFileNameRegExp = new QString("[^:/\\\\]*");
QDir * bindir = new QDir();
QDir * cfgdir = new QDir();
@@ -37,7 +45,9 @@
bool custom_data = false;
int cMaxTeams = 8;
+int cMaxHHs = HEDGEHOGS_PER_TEAM * cMaxTeams;
int cMinServerVersion = 3;
+unsigned char cInvertTextColorAt = 64;
QString * cDefaultAmmoStore = new QString( AMMOLINE_DEFAULT_QT AMMOLINE_DEFAULT_PROB
AMMOLINE_DEFAULT_DELAY AMMOLINE_DEFAULT_CRATE );
@@ -72,6 +82,9 @@
<< qMakePair(QString("Highlander"), QString(
AMMOLINE_HIGHLANDER_QT AMMOLINE_HIGHLANDER_PROB
AMMOLINE_HIGHLANDER_DELAY AMMOLINE_HIGHLANDER_CRATE ))
+ << qMakePair(QString("Balanced Random Weapon"), QString(
+ AMMOLINE_BRW_QT AMMOLINE_BRW_PROB
+ AMMOLINE_BRW_DELAY AMMOLINE_BRW_CRATE ))
<< qMakePair(QString("Construction Mode"), QString(
AMMOLINE_CONSTRUCTION_QT AMMOLINE_CONSTRUCTION_PROB
AMMOLINE_CONSTRUCTION_DELAY AMMOLINE_CONSTRUCTION_CRATE ))
@@ -83,6 +96,33 @@
AMMOLINE_HEDGEEDITOR_DELAY AMMOLINE_HEDGEEDITOR_CRATE ))
;
+QStringList cQuickGameMaps = QStringList()
+ << "Bamboo"
+ << "Bath"
+ << "Battlefield"
+ << "Blox"
+ << "Bubbleflow"
+ << "Cake"
+ << "Castle"
+ << "Cheese"
+ << "Cogs"
+ << "CrazyMission"
+ << "EarthRise"
+ << "Eyes"
+ << "Hammock"
+ << "HedgeFortress"
+ << "Hedgelove"
+ << "Hedgewars"
+ << "Hydrant"
+ << "Lonely_Island"
+ << "Mushrooms"
+ << "Octorama"
+ << "PirateFlag"
+ << "Plane"
+ << "Sheep"
+ << "Trash"
+ << "Tree";
+
unsigned int colors[] = HW_TEAMCOLOR_ARRAY;
QString * netHost = new QString();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/hwconsts.h
--- a/QTfrontend/hwconsts.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/hwconsts.h Wed Jul 31 23:14:27 2019 +0200
@@ -28,6 +28,7 @@
extern QString * cRevisionString;
extern QString * cHashString;
extern QString * cDataDir;
+extern QString * cSafeFileNameRegExp;
extern QDir * bindir;
extern QDir * cfgdir;
@@ -37,7 +38,9 @@
extern bool custom_data;
extern int cMaxTeams;
+extern int cMaxHHs;
extern int cMinServerVersion;
+extern unsigned char cInvertTextColorAt;
class QStandardItemModel;
@@ -45,6 +48,7 @@
extern QString * cEmptyAmmoStore;
extern int cAmmoNumber;
extern QList< QPair > cDefaultAmmos;
+extern QStringList cQuickGameMaps;
extern unsigned int colors[];
@@ -73,6 +77,12 @@
#define NETGAME_DEFAULT_PORT 46631
#define HEDGEHOGS_PER_TEAM 8
+//Selected engine exit codes, see hedgewars/uConsts.pas
+#define HWENGINE_EXITCODE_OK 0
+#define HWENGINE_EXITCODE_FATAL 52
+
+// Default clan colors
+// NOTE: Always keep this in sync with hedgewars/uVariables.pas (ClanColorArray)
// see https://en.wikipedia.org/wiki/List_of_colors
/*define HW_TEAMCOLOR_ARRAY {0xff007fff, /. azure ./ \
@@ -105,7 +115,7 @@
0xffe55bb0, /* pink */ \
0xff20bf00, /* green */ \
0xfffe8b0e, /* orange */ \
- 0xff5f3605, /* brown */ \
+ 0xff8f5902, /* brown */ \
0xffffff01, /* yellow */ \
/* add new colors here */ \
0 }
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/hwform.cpp
--- a/QTfrontend/hwform.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/hwform.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -57,6 +57,7 @@
#include "hwform.h"
#include "game.h"
#include "team.h"
+#include "mission.h"
#include "campaign.h"
#include "teamselect.h"
#include "selectWeapon.h"
@@ -128,6 +129,7 @@
// I started handing this down to each place it touches, but it was getting ridiculous
// and this one flag does not warrant a static class
bool frontendEffects = true;
+bool demoIsPresent = true;
QString playerHash;
QIcon finishedIcon;
@@ -167,7 +169,10 @@
config = new GameUIConfig(this, DataManager::instance().settingsFileName());
frontendEffects = config->value("frontend/effects", true).toBool();
- playerHash = QString(QCryptographicHash::hash(config->value("net/nick",tr("Guest")+QString("%1").arg(rand())).toString().toUtf8(), QCryptographicHash::Md5).toHex());
+ bool frontendSounds = config->value("frontend/sound", true).toBool();
+ onFrontendSoundsToggled(frontendSounds);
+
+ playerHash = QString(QCryptographicHash::hash(config->value("net/nick", config->getRandomNick()).toString().toUtf8(), QCryptographicHash::Md5).toHex());
// Icons for finished missions
finishedIcon.addFile(":/res/missionFinished.png", QSize(), QIcon::Normal, QIcon::On);
@@ -218,6 +223,7 @@
previousTeamName = "";
UpdateTeamsLists();
InitCampaignPage();
+ RestoreSingleplayerTeamSelection();
UpdateCampaignPage(0);
UpdateCampaignPageTeam(0);
UpdateCampaignPageMission(0);
@@ -240,12 +246,16 @@
connect(ui.pageMain->BtnFeedback, SIGNAL(clicked()), this, SLOT(showFeedbackDialog()));
+ connect(ui.pageMain->BtnTitle, SIGNAL(clicked()), pageSwitchMapper, SLOT(map()));
+ pageSwitchMapper->setMapping(ui.pageMain->BtnTitle, ID_PAGE_INFO);
+
connect(ui.pageMain->BtnInfo, SIGNAL(clicked()), pageSwitchMapper, SLOT(map()));
pageSwitchMapper->setMapping(ui.pageMain->BtnInfo, ID_PAGE_INFO);
connect(ui.pageMain->BtnDataDownload, SIGNAL(clicked()), pageSwitchMapper, SLOT(map()));
pageSwitchMapper->setMapping(ui.pageMain->BtnDataDownload, ID_PAGE_DATADOWNLOAD);
+ connect(ui.pageMain->BtnHelp, SIGNAL(clicked()), this, SLOT(GoToHelp()));
#ifdef VIDEOREC
connect(ui.pageMain->BtnVideos, SIGNAL(clicked()), pageSwitchMapper, SLOT(map()));
@@ -283,12 +293,13 @@
connect(ui.pageOptions->SchemeNew, SIGNAL(clicked()), this, SLOT(GoToNewScheme()));
connect(ui.pageOptions->SchemeDelete, SIGNAL(clicked()), this, SLOT(DeleteScheme()));
connect(ui.pageOptions->CBFrontendEffects, SIGNAL(toggled(bool)), this, SLOT(onFrontendEffects(bool)) );
+ connect(ui.pageOptions->CBFrontendSound, SIGNAL(toggled(bool)), this, SLOT(onFrontendSoundsToggled(bool)));
connect(ui.pageNet->BtnSpecifyServer, SIGNAL(clicked()), this, SLOT(NetConnect()));
connect(ui.pageNet->BtnNetSvrStart, SIGNAL(clicked()), pageSwitchMapper, SLOT(map()));
pageSwitchMapper->setMapping(ui.pageNet->BtnNetSvrStart, ID_PAGE_NETSERVER);
- connect(ui.pageNet, SIGNAL(connectClicked(const QString &, quint16)), this, SLOT(NetConnectServer(const QString &, quint16)));
+ connect(ui.pageNet, SIGNAL(connectClicked(const QString &, quint16, bool)), this, SLOT(NetConnectServer(const QString &, quint16, bool)));
connect(ui.pageNetServer->BtnStart, SIGNAL(clicked()), this, SLOT(NetStartServer()));
@@ -329,6 +340,9 @@
connect(ui.pageCampaign->CBTeam, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPageTeam(int)));
connect(ui.pageCampaign->CBCampaign, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPage(int)));
connect(ui.pageCampaign->CBMission, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPageMission(int)));
+ connect(ui.pageTraining->CBTeam, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateTrainingPageTeam(int)));
+ connect(ui.pageCampaign->CBTeam, SIGNAL(currentIndexChanged(int)), ui.pageTraining->CBTeam, SLOT(setCurrentIndex(int)));
+ connect(ui.pageTraining->CBTeam, SIGNAL(currentIndexChanged(int)), ui.pageCampaign->CBTeam, SLOT(setCurrentIndex(int)));
connect(ui.pageSelectWeapon->pWeapons, SIGNAL(weaponsDeleted(QString)),
this, SLOT(DeleteWeapons(QString)));
@@ -401,6 +415,11 @@
wBackground->stopAnimation();
}
+void HWForm::onFrontendSoundsToggled(bool value)
+{
+ ui.pageEditTeam->frontendSoundsToggled(value);
+}
+
/*
void HWForm::keyReleaseEvent(QKeyEvent *event)
{
@@ -515,7 +534,7 @@
if(teamslist.empty())
{
- QString currentNickName = config->value("net/nick",tr("Guest")+QString("%1").arg(rand())).toString();
+ QString currentNickName = config->value("net/nick", config->getRandomNick()).toString();
QString teamName;
int firstHumanTeam = 1;
int lastHumanTeam = 2;
@@ -538,6 +557,7 @@
// TODO: Remove DLC filtering when it isn't neccessary anymore
HWNamegen::teamRandomGrave(defaultTeam, false);
HWNamegen::teamRandomFort(defaultTeam, false);
+ HWNamegen::teamLocalizedDefaultVoice(defaultTeam);
defaultTeam.saveToFile();
teamslist.push_back(teamName);
@@ -551,6 +571,7 @@
HWTeam numberTeam(teamName);
HWNamegen::teamRandomGrave(numberTeam, false);
HWNamegen::teamRandomFort(numberTeam, false);
+ HWNamegen::teamLocalizedDefaultVoice(numberTeam);
numberTeam.saveToFile();
teamslist.push_back(teamName);
}
@@ -562,6 +583,7 @@
HWTeam numberTeam(teamName);
HWNamegen::teamRandomGrave(numberTeam, false);
HWNamegen::teamRandomFort(numberTeam, false);
+ HWNamegen::teamLocalizedDefaultVoice(numberTeam);
numberTeam.setDifficulty(6-i);
numberTeam.saveToFile();
teamslist.push_back(teamName);
@@ -571,7 +593,9 @@
ui.pageOptions->CBTeamName->clear();
ui.pageOptions->CBTeamName->addItems(teamslist);
ui.pageCampaign->CBTeam->clear();
- /* Only show human teams in campaign page */
+ ui.pageTraining->CBTeam->clear();
+ /* Only show human teams in campaign/training page */
+ bool playable = false;
for(int i=0; iCBTeam->addItem(teamslist[i]);
+ ui.pageTraining->CBTeam->addItem(teamslist[i]);
+ playable = true;
}
}
+ ui.pageCampaign->BtnStartCampaign->setEnabled(playable);
+ ui.pageCampaign->btnPreview->setEnabled(playable);
+ ui.pageTraining->btnStart->setEnabled(playable);
+ ui.pageTraining->btnPreview->setEnabled(playable);
+ UpdateTrainingPageTeam(0);
}
void HWForm::GoToNewWeapons()
@@ -641,6 +672,13 @@
GoToPage(ID_PAGE_SCHEME);
}
+void HWForm::GoToHelp()
+{
+ // For now just opens the Hedgewars Wiki in external browser.
+ // TODO: Replace this with an offline help someday (bug 660).
+ QDesktopServices::openUrl(QUrl("https://hedgewars.org/wiki"));
+}
+
void HWForm::GoToVideos()
{
GoToPage(ID_PAGE_VIDEOS);
@@ -778,12 +816,15 @@
case gtQLocal:
case gtTraining:
case gtCampaign:
+ case gtDemo:
+ case gtSave:
ui.pageGameStats->restartBtnVisible(true);
break;
default:
ui.pageGameStats->restartBtnVisible(false);
break;
}
+ ui.pageGameStats->saveDemoBtnEnabled(demoIsPresent);
}
if (id == ID_PAGE_MAIN)
@@ -913,6 +954,11 @@
GoBack();
}
+ if (curid == ID_PAGE_CAMPAIGN)
+ config->setValue("frontend/lastSingleplayerTeam", ui.pageCampaign->CBTeam->currentText());
+ if (curid == ID_PAGE_TRAINING)
+ config->setValue("frontend/lastSingleplayerTeam", ui.pageTraining->CBTeam->currentText());
+
if (curid == ID_PAGE_ROOMSLIST || curid == ID_PAGE_CONNECTING) NetDisconnect();
if (curid == ID_PAGE_NETGAME && hwnet && hwnet->isInRoom()) hwnet->partRoom();
// need to work on this, can cause invalid state for admin quit trying to prevent bad state message on kick
@@ -1125,18 +1171,18 @@
void HWForm::NetConnectQuick(const QString & host, quint16 port)
{
GoToPage(ID_PAGE_MAIN);
- NetConnectServer(host, port);
+ NetConnectServer(host, port, false);
}
-void HWForm::NetConnectServer(const QString & host, quint16 port)
+void HWForm::NetConnectServer(const QString & host, quint16 port, bool useTls)
{
qDebug("connecting to %s:%d", qPrintable(host), port);
- _NetConnect(host, port, ui.pageOptions->editNetNick->text().trimmed());
+ _NetConnect(host, port, useTls, ui.pageOptions->editNetNick->text().trimmed());
}
void HWForm::NetConnectOfficialServer()
{
- NetConnectServer(NETGAME_DEFAULT_SERVER, NETGAME_DEFAULT_PORT);
+ NetConnectServer(NETGAME_DEFAULT_SERVER, NETGAME_DEFAULT_PORT, false);
}
void HWForm::NetPassword(const QString & nick)
@@ -1231,7 +1277,7 @@
if (retry && hwnet) {
if (hwnet->m_private_game) {
QStringList list = hwnet->getHost().split(":");
- NetConnectServer(list.at(0), list.at(1).toShort());
+ NetConnectServer(list.at(0), list.at(1).toShort(), false);
} else
NetConnectOfficialServer();
}
@@ -1320,7 +1366,7 @@
ui.pageRoomsList->displayWarning(wrnmsg);
}
-void HWForm::_NetConnect(const QString & hostName, quint16 port, QString nick)
+void HWForm::_NetConnect(const QString & hostName, quint16 port, bool useTls, QString nick)
{
Q_UNUSED(nick);
@@ -1336,6 +1382,8 @@
GoToPage(ID_PAGE_CONNECTING);
connect(hwnet, SIGNAL(AskForRunGame()), this, SLOT(CreateNetGame()), Qt::QueuedConnection);
+ connect(hwnet, SIGNAL(AskForOfficialServerDemo()), this, SLOT(PlayOfficialServerDemo()), Qt::QueuedConnection);
+ connect(hwnet, SIGNAL(redirected(quint16)), this, SLOT(NetRedirected(quint16)), Qt::QueuedConnection);
connect(hwnet, SIGNAL(connected()), this, SLOT(NetConnected()), Qt::QueuedConnection);
connect(hwnet, SIGNAL(Error(const QString&)), this, SLOT(NetError(const QString&)), Qt::QueuedConnection);
connect(hwnet, SIGNAL(Warning(const QString&)), this, SLOT(NetWarning(const QString&)), Qt::QueuedConnection);
@@ -1496,11 +1544,11 @@
if (hwnet->m_private_game == false && AskForNickAndPwd() != 0)
return;
- QString nickname = config->value("net/nick",tr("Guest")+QString("%1").arg(rand())).toString();
+ QString nickname = config->value("net/nick", config->getRandomNick()).toString();
ui.pageRoomsList->setUser(nickname);
ui.pageNetGame->setUser(nickname);
- hwnet->Connect(hostName, port, nickname);
+ hwnet->Connect(hostName, port, useTls, nickname);
}
int HWForm::AskForNickAndPwd(void)
@@ -1515,7 +1563,7 @@
QString password;
do {
- nickname = config->value("net/nick",tr("Guest")+QString("%1").arg(rand())).toString();
+ nickname = config->value("net/nick", config->getRandomNick()).toString();
hash = config->passwordHash();
temphash = config->tempHash();
@@ -1554,7 +1602,7 @@
if (retry) {
if (hwnet->m_private_game) {
QStringList list = hwnet->getHost().split(":");
- NetConnectServer(list.at(0), list.at(1).toShort());
+ NetConnectServer(list.at(0), list.at(1).toShort(), false);
} else
NetConnectOfficialServer();
}
@@ -1600,7 +1648,7 @@
delete netHost;
netHost = new QString(hpd->leHost->text());
netPort = hpd->sbPort->value();
- NetConnectServer(*netHost, netPort);
+ NetConnectServer(*netHost, netPort, false);
}
delete hpd;
}
@@ -1628,7 +1676,7 @@
void HWForm::AsyncNetServerStart()
{
- NetConnectServer("localhost", pnetserver->getRunningPort());
+ NetConnectServer("localhost", pnetserver->getRunningPort(), false);
}
void HWForm::NetDisconnect()
@@ -1657,7 +1705,7 @@
if (retry) {
if (hwnet->m_private_game) {
QStringList list = hwnet->getHost().split(":");
- NetConnectServer(list.at(0), list.at(1).toUInt());
+ NetConnectServer(list.at(0), list.at(1).toUInt(), false);
} else
NetConnectOfficialServer();
}
@@ -1672,8 +1720,9 @@
}
if (pnetserver)
return; // we have server - let it care of all things
- if (hwnet) {
- QString errorStr = QMessageBox::tr("Connection to server is lost") + (reason.isEmpty()?"":("\n\n" + HWNewNet::tr("Quit reason: ") + '"' + reason +'"'));
+ if (hwnet && (reason != "bye"))
+ {
+ QString errorStr = QMessageBox::tr("The connection to the server is lost.") + (reason.isEmpty()?"":("\n\n" + HWNewNet::tr("Reason:") + "\n" + reason));
MessageDialog::ShowErrorMessage(errorStr, this);
}
@@ -1684,6 +1733,28 @@
}
}
+void HWForm::NetRedirected(quint16 port)
+{
+ QMessageBox questionMsg(this);
+ questionMsg.setIcon(QMessageBox::Question);
+ questionMsg.setWindowTitle(QMessageBox::tr("Server redirection"));
+ questionMsg.setText(QMessageBox::tr("This server supports secure connections on port %1.\nWould you like to reconnect securely?").arg(port));
+ questionMsg.setTextFormat(Qt::PlainText);
+ questionMsg.setWindowModality(Qt::WindowModal);
+ questionMsg.addButton(QMessageBox::Yes);
+ questionMsg.addButton(QMessageBox::No);
+
+ if (questionMsg.exec() == QMessageBox::Yes)
+ {
+ QString host = hwnet->getHost().split(":").at(0);
+ NetConnectServer(host, port, true);
+ }
+ else if (hwnet)
+ {
+ hwnet->ContinueConnection();
+ }
+}
+
void HWForm::NetConnected()
{
GoToPage(ID_PAGE_ROOMSLIST);
@@ -1707,14 +1778,6 @@
void HWForm::StartMPGame()
{
- int numHogs = ui.pageMultiplayer->teamsSelect->getNumHedgehogs();
- /* Don't allow to start game with >48 hogs.
- TODO: Remove this as soon the engine supports more hogs. */
- if(numHogs > 48)
- {
- MessageDialog::ShowErrorMessage(QMessageBox::tr("Sorry, Hedgewars can't be played with more than 48 hedgehogs. Please try again with fewer hedgehogs.\n\nCurrent number of hedgehogs: %1").arg(numHogs), this);
- return;
- }
QString ammo;
ammo = ui.pageMultiplayer->gameCFG->WeaponsName->itemData(
ui.pageMultiplayer->gameCFG->WeaponsName->currentIndex()
@@ -1732,6 +1795,7 @@
{
case gsStarted:
{
+ demoIsPresent = true;
Music(false);
if (wBackground) wBackground->stopAnimation();
if (!hwnet || (!hwnet->isRoomChief() || !hwnet->isInRoom())) GoToPage(ID_PAGE_INGAME);
@@ -1753,7 +1817,15 @@
Music(ui.pageOptions->CBFrontendMusic->isChecked());
if (wBackground) wBackground->startAnimation();
GoToPage(ID_PAGE_GAMESTATS);
- if (hwnet && (!game || !game->netSuspend)) hwnet->gameFinished(true);
+ if (hwnet)
+ {
+ if (!game || !game->netSuspend)
+ hwnet->gameFinished(true);
+ // After a game, the local player might have pseudo-teams left
+ // when rejoining a previously left game. This makes sure the
+ // teams list is in a consistent state.
+ ui.pageNetGame->cleanupFakeNetTeams();
+ }
if (game) game->netSuspend = false;
break;
}
@@ -1777,11 +1849,18 @@
}
}
+void HWForm::DemoPresenceChanged(bool hasDemo)
+{
+ demoIsPresent = hasDemo;
+}
+
void HWForm::CreateGame(GameCFGWidget * gamecfg, TeamSelWidget* pTeamSelWidget, QString ammo)
{
game = new HWGame(config, gamecfg, ammo, pTeamSelWidget);
connect(game, SIGNAL(CampStateChanged(int)), this, SLOT(UpdateCampaignPageProgress(int)));
+ connect(game, SIGNAL(TrainingStateChanged(int)), this, SLOT(UpdateTrainingPageTeam(int)));
connect(game, SIGNAL(GameStateChanged(GameState)), this, SLOT(GameStateChanged(GameState)));
+ connect(game, SIGNAL(DemoPresenceChanged(bool)), this, SLOT(DemoPresenceChanged(bool)));
connect(game, SIGNAL(GameStats(char, const QString &)), ui.pageGameStats, SLOT(GameStats(char, const QString &)));
connect(game, SIGNAL(ErrorMessage(const QString &)), this, SLOT(ShowFatalErrorMessage(const QString &)), Qt::QueuedConnection);
connect(game, SIGNAL(HaveRecord(RecordType, const QByteArray &)), this, SLOT(GetRecord(RecordType, const QByteArray &)));
@@ -1833,7 +1912,8 @@
{
CreateGame(0, 0, 0);
- game->StartTraining(scriptName, subFolder);
+ QString trainTeam = ui.pageTraining->CBTeam->currentText();
+ game->StartTraining(scriptName, subFolder, trainTeam);
}
void HWForm::StartCampaign()
@@ -1864,10 +1944,29 @@
connect(game, SIGNAL(SendConsoleCommand(const QString&)), hwnet, SLOT(consoleCommand(const QString&)));
connect(game, SIGNAL(SendTeamMessage(const QString &)), hwnet, SLOT(SendTeamMessage(const QString &)));
connect(hwnet, SIGNAL(chatStringFromNet(const QString &)), game, SLOT(FromNetChat(const QString &)), Qt::QueuedConnection);
+ connect(hwnet, SIGNAL(Warning(const QString&)), game, SLOT(FromNetWarning(const QString&)), Qt::QueuedConnection);
+ connect(hwnet, SIGNAL(Error(const QString&)), game, SLOT(FromNetError(const QString&)), Qt::QueuedConnection);
game->StartNet();
}
+void HWForm::PlayOfficialServerDemo()
+{
+ // go back in pages to prevent user from being stuck on certain pages
+ if(ui.Pages->currentIndex() == ID_PAGE_GAMESTATS ||
+ ui.Pages->currentIndex() == ID_PAGE_INGAME)
+ GoBack();
+
+ QString ammo;
+ ammo = ui.pageNetGame->pGameCFG->WeaponsName->itemData(
+ ui.pageNetGame->pGameCFG->WeaponsName->currentIndex()
+ ).toString();
+
+ CreateGame(ui.pageNetGame->pGameCFG, ui.pageNetGame->pNetTeamsWidget, ammo);
+
+ game->PlayOfficialServerDemo();
+}
+
void HWForm::closeEvent(QCloseEvent *event)
{
config->SaveOptions();
@@ -1902,6 +2001,7 @@
ui.pageNetGame->pGameCFG->GameSchemes->setModel(gameSchemeModel);
ui.pageNetGame->pGameCFG->setMaster(true);
ui.pageNetGame->pNetTeamsWidget->setInteractivity(true);
+ ui.pageNetGame->pGameCFG->resetSchemeStates();
if (hwnet)
{
@@ -1986,6 +2086,36 @@
}
}
+void HWForm::UpdateTrainingPageTeam(int index)
+{
+ Q_UNUSED(index);
+ HWTeam team(ui.pageTraining->CBTeam->currentText());
+ QString tName = team.name();
+
+ QListWidget* listWidget;
+ for(int w = 0; w < 3; w++)
+ {
+ switch(w) {
+ case 0: listWidget = ui.pageTraining->lstTrainings; break;
+ case 1: listWidget = ui.pageTraining->lstChallenges; break;
+ case 2: listWidget = ui.pageTraining->lstScenarios; break;
+ default: listWidget = ui.pageTraining->lstTrainings; break;
+ }
+ unsigned int n = listWidget->count();
+
+ for(unsigned int i = 0; i < n; i++)
+ {
+ QListWidgetItem* item = listWidget->item(i);
+ QString missionName = QString(item->data(Qt::UserRole).toString()).replace(QString(" "),QString("_"));
+ if(isMissionWon(missionName, tName))
+ item->setIcon(finishedIcon);
+ else
+ item->setIcon(notFinishedIcon);
+ }
+ }
+ ui.pageTraining->updateInfo();
+}
+
void HWForm::InitCampaignPage()
{
ui.pageCampaign->CBCampaign->clear();
@@ -2005,26 +2135,62 @@
QString tName = team.name();
ui.pageCampaign->CBCampaign->addItem(getRealCampName(campaignName), campaignName);
}
+
+}
+
+void HWForm::RestoreSingleplayerTeamSelection()
+{
+ QString lastTeam = config->value("frontend/lastSingleplayerTeam", QString()).toString();
+ if (!lastTeam.isNull() && !lastTeam.isEmpty())
+ {
+ int index = ui.pageCampaign->CBTeam->findData(lastTeam, Qt::DisplayRole);
+ if(index != -1)
+ {
+ ui.pageCampaign->CBTeam->setCurrentIndex(index);
+ UpdateCampaignPageTeam(index);
+ }
+ index = ui.pageTraining->CBTeam->findData(lastTeam, Qt::DisplayRole);
+ if(index != -1)
+ {
+ ui.pageTraining->CBTeam->setCurrentIndex(index);
+ UpdateTrainingPageTeam(index);
+ }
+ }
}
void HWForm::UpdateCampaignPage(int index)
{
Q_UNUSED(index);
HWTeam team(ui.pageCampaign->CBTeam->currentText());
- QString campaignName = ui.pageCampaign->CBCampaign->itemData(ui.pageCampaign->CBCampaign->currentIndex()).toString();
+ QString campaignName = ui.pageCampaign->CBCampaign->currentData().toString();
QString tName = team.name();
campaignMissionInfo = getCampMissionList(campaignName,tName);
ui.pageCampaign->CBMission->clear();
+ // Populate mission list
for(int i=0;iCBMission->addItem(QString(campaignMissionInfo[i].realName), QString(campaignMissionInfo[i].name));
- if(isMissionWon(campaignName, i, tName))
+ if(isCampMissionWon(campaignName, i, tName))
ui.pageCampaign->CBMission->setItemIcon(i, finishedIcon);
else
ui.pageCampaign->CBMission->setItemIcon(i, notFinishedIcon);
}
+
+ // Select first open mission
+ int missionIndex = ui.pageCampaign->CBMission->currentIndex();
+ if(isCampMissionWon(campaignName, missionIndex, tName))
+ {
+ for(int m = 0; m < ui.pageCampaign->CBMission->count(); m++)
+ {
+ if(!isCampMissionWon(campaignName, m, tName))
+ {
+ ui.pageCampaign->CBMission->setCurrentIndex(m);
+ break;
+ }
+ }
+ }
}
void HWForm::UpdateCampaignPageTeam(int index)
@@ -2041,6 +2207,7 @@
unsigned int n = entries.count();
+ // Update campaign status
for(unsigned int i = 0; i < n; i++)
{
QString campaignName = QString(entries[i]).replace(QString(" "),QString("_"));
@@ -2054,7 +2221,7 @@
void HWForm::UpdateCampaignPageMission(int index)
{
// update thumbnail and description
- QString campaignName = ui.pageCampaign->CBCampaign->itemData(ui.pageCampaign->CBCampaign->currentIndex()).toString();
+ QString campaignName = ui.pageCampaign->CBCampaign->currentData().toString();
// when campaign changes the UpdateCampaignPageMission is triggered with wrong values
// this will cause segfault. This check prevents illegal memory reads
if(index > -1 && index < campaignMissionInfo.count()) {
@@ -2066,26 +2233,74 @@
void HWForm::UpdateCampaignPageProgress(int index)
{
- Q_UNUSED(index);
-
- QString missionTitle = ui.pageCampaign->CBMission->currentText();
+ QString missionTitle = ui.pageCampaign->CBMission->currentData().toString();
UpdateCampaignPage(0);
+ int missionIndex = 0;
+ // Restore selected mission (because UpdateCampaignPage repopulated the list)
for(int i=0;iCBMission->count();i++)
{
- if (ui.pageCampaign->CBMission->itemData(i)==missionTitle)
+ if (ui.pageCampaign->CBMission->itemData(i).toString() == missionTitle)
{
- ui.pageCampaign->CBMission->setCurrentIndex(i);
+ missionIndex = i;
break;
}
}
- int i = ui.pageCampaign->CBCampaign->currentIndex();
- QString campaignName = ui.pageCampaign->CBCampaign->itemData(i).toString();
+
+ // Get metadata
+ int c = ui.pageCampaign->CBCampaign->currentIndex();
+ QString campaignName = ui.pageCampaign->CBCampaign->itemData(c).toString();
HWTeam team(ui.pageCampaign->CBTeam->currentText());
QString tName = team.name();
+
+ if(index == gsFinished)
+ {
+ // Select new mission when current mission went from
+ // unfinished to finished.
+ if(ui.pageCampaign->currentMissionWon == false &&
+ isCampMissionWon(campaignName, missionIndex, tName))
+ {
+ // Traverse all missions and pick first mission that
+ // has not been won.
+ bool selected = false;
+ // start from mission that comes after the selected one
+ for(int m = missionIndex-1; m >= 0;m--)
+ {
+ if(!isCampMissionWon(campaignName, m, tName))
+ {
+ missionIndex = m;
+ selected = true;
+ break;
+ }
+ }
+ // No mission selected? Let's try again from the end of the list
+ if(!selected)
+ {
+ for(int m = ui.pageCampaign->CBMission->count()-1; m > missionIndex-1; m--)
+ {
+ if(!isCampMissionWon(campaignName, m, tName))
+ {
+ missionIndex = m;
+ break;
+ }
+ }
+ }
+ // If no mission was selected, the old selection remains unchanged.
+ }
+ }
+ else if(index == gsStarted)
+ {
+ // Remember the "won" state of current mission before we start it.
+ // We'll need it when the game has finished.
+ ui.pageCampaign->currentMissionWon = isCampMissionWon(campaignName, missionIndex, tName);
+ }
+
+ ui.pageCampaign->CBMission->setCurrentIndex(missionIndex);
+
+ // Update campaign victory status
if(isCampWon(campaignName, tName))
- ui.pageCampaign->CBCampaign->setItemIcon(i, finishedIcon);
+ ui.pageCampaign->CBCampaign->setItemIcon(c, finishedIcon);
else
- ui.pageCampaign->CBCampaign->setItemIcon(i, notFinishedIcon);
+ ui.pageCampaign->CBCampaign->setItemIcon(c, notFinishedIcon);
}
// used for --set-everything [screen width] [screen height] [color dept] [volume] [enable music] [enable sounds] [language file] [full screen] [show FPS] [alternate damage] [timer value] [reduced quality]
@@ -2106,9 +2321,11 @@
+ " --fullscreen-height " + QString::number(resolutions.first.height())
+ " --width " + QString::number(resolutions.second.width())
+ " --height " + QString::number(resolutions.second.height())
+ + (config->zoom() == 100 ? "" : " --zoom " + QString::number(config->zoom()))
+ " --volume " + QString::number(config->volume())
+ (config->isMusicEnabled() ? "" : " --nomusic")
+ (config->isSoundEnabled() ? "" : " --nosound")
+ + (config->isAudioDampenEnabled() ? "" : " --nodampen")
+ " --locale " + config->language() + ".txt"
+ (config->vid_Fullscreen() ? " --fullscreen" : "")
+ (config->isShowFPSEnabled() ? " --showfps" : "")
@@ -2118,7 +2335,8 @@
+ (!config->Form->ui.pageOptions->CBTeamTag->isChecked() ? " --no-teamtag" : "")
+ (!config->Form->ui.pageOptions->CBHogTag->isChecked() ? " --no-hogtag" : "")
+ (!config->Form->ui.pageOptions->CBHealthTag->isChecked() ? " --no-healthtag" : "")
- + (config->Form->ui.pageOptions->CBTagOpacity->isChecked() ? " --translucent-tags" : "");
+ + (config->Form->ui.pageOptions->CBTagOpacity->isChecked() ? " --translucent-tags" : "")
+ + (!config->isHolidaySillinessEnabled() ? " --no-holiday-silliness" : "");
}
void HWForm::AssociateFiles()
@@ -2173,6 +2391,7 @@
infoMsg.setIcon(QMessageBox::Information);
infoMsg.setWindowTitle(QMessageBox::tr("Hedgewars - Success"));
infoMsg.setText(QMessageBox::tr("All file associations have been set"));
+ infoMsg.setTextFormat(Qt::PlainText);
infoMsg.setWindowModality(Qt::WindowModal);
infoMsg.exec();
}
@@ -2223,7 +2442,7 @@
switch(lastGameType) {
case gtTraining:
- game->StartTraining(lastGameStartArgs.at(0).toString(), lastTrainingSubFolder);
+ game->StartTraining(lastGameStartArgs.at(0).toString(), lastGameStartArgs.at(1).toString(), lastGameStartArgs.at(2).toString());
break;
case gtQLocal:
game->StartQuick();
@@ -2234,6 +2453,10 @@
case gtLocal:
game->StartLocal();
break;
+ case gtDemo:
+ case gtSave:
+ PlayDemo();
+ break;
default:
break;
}
@@ -2286,6 +2509,7 @@
questionMsg.setIcon(QMessageBox::Question);
questionMsg.setWindowTitle(QMessageBox::tr("Not all players are ready"));
questionMsg.setText(QMessageBox::tr("Are you sure you want to start this game?\nNot all players are ready."));
+ questionMsg.setTextFormat(Qt::PlainText);
questionMsg.setWindowModality(Qt::WindowModal);
questionMsg.addButton(QMessageBox::Yes);
questionMsg.addButton(QMessageBox::Cancel);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/hwform.h
--- a/QTfrontend/hwform.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/hwform.h Wed Jul 31 23:14:27 2019 +0200
@@ -52,6 +52,7 @@
class QSignalMapper;
extern bool frontendEffects;
+extern bool demoIsPresent;
extern QString playerHash;
class HWForm : public QMainWindow
@@ -75,6 +76,7 @@
void GoToSaves();
void GoToDemos();
void GoToNet();
+ void GoToHelp();
void GoToEditWeapons();
void GoToNewWeapons();
void GoToWeapons(int index);
@@ -99,9 +101,10 @@
void startTraining(const QString&, const QString&);
void StartCampaign();
void NetConnect();
- void NetConnectServer(const QString & host, quint16 port);
+ void NetConnectServer(const QString & host, quint16 port, bool useTls);
void NetConnectOfficialServer();
void NetStartServer();
+ void NetRedirected(quint16 port);
void NetDisconnect();
void NetConnected();
void NetError(const QString & errmsg);
@@ -119,22 +122,27 @@
void RemoveNetTeam(const HWTeam& team);
void StartMPGame();
void GameStateChanged(GameState gameState);
+ void DemoPresenceChanged(bool hasDemo);
void ForcedDisconnect(const QString & reason);
void ShowFatalErrorMessage(const QString &);
void GetRecord(RecordType type, const QByteArray & record);
void CreateNetGame();
+ void PlayOfficialServerDemo();
void UpdateWeapons();
void DeleteWeapons(QString weaponsName);
void AddWeapons(QString weaponsName, QString ammo);
void EditWeapons(QString oldWeaponsName, QString newWeaponsName, QString ammo);
void onFrontendFullscreen(bool value);
void onFrontendEffects(bool value);
+ void onFrontendSoundsToggled(bool value);
void Music(bool checked);
void UpdateCampaignPage(int index);
void UpdateCampaignPageTeam(int index);
void UpdateCampaignPageProgress(int index);
void UpdateCampaignPageMission(int index);
+ void UpdateTrainingPageTeam(int index);
void InitCampaignPage();
+ void RestoreSingleplayerTeamSelection();
void showFeedbackDialog();
void showFeedbackDialogNetChecked();
@@ -155,7 +163,7 @@
void FromNetProxySlot(const QByteArray &);
private:
- void _NetConnect(const QString & hostName, quint16 port, QString nick);
+ void _NetConnect(const QString & hostName, quint16 port, bool useTls, QString nick);
int AskForNickAndPwd(void);
void UpdateTeamsLists();
void CreateGame(GameCFGWidget * gamecfg, TeamSelWidget* pTeamSelWidget, QString ammo);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/main.cpp
--- a/QTfrontend/main.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/main.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -138,6 +138,23 @@
}
}
+// Simple Message handler that suppresses Qt debug and info messages (qDebug, qInfo).
+// Used when printing command line help (--help) or related error to keep console clean.
+void restrictedMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ Q_UNUSED(context)
+ QByteArray localMsg = msg.toLocal8Bit();
+ switch (type) {
+ case QtWarningMsg:
+ case QtCriticalMsg:
+ case QtFatalMsg:
+ fprintf(stderr, "%s\n", localMsg.constData());
+ break;
+ default:
+ break;
+ }
+}
+
QString getUsage()
{
return QString(
@@ -150,39 +167,25 @@
"\n"
"%8"
"\n"
-).arg(HWApplication::tr("Usage", "command-line"))
-.arg(HWApplication::tr("OPTION", "command-line"))
-.arg(HWApplication::tr("CONNECTSTRING", "command-line"))
-.arg(HWApplication::tr("Options", "command-line"))
-.arg(HWApplication::tr("Display this help", "command-line"))
+).arg(
+//: “Usage” as in “how the command-line syntax works”. Shown when running “hedgewars --help” in command-line
+HWApplication::tr("Usage", "command-line")
+).arg(
+//: Name of a command-line argument, shown when running “hedgewars --help” in command-line. “OPTION” as in “command-line option”
+HWApplication::tr("OPTION", "command-line")
+).arg(
+//: Name of a command-line argument, shown when running “hedgewars --help” in command-line
+HWApplication::tr("CONNECTSTRING", "command-line")
+).arg(
+//: “Options” as in “command-line options”
+HWApplication::tr("Options", "command-line")
+).arg(HWApplication::tr("Display this help", "command-line"))
.arg(HWApplication::tr("Custom path for configuration data and user data", "command-line"))
.arg(HWApplication::tr("Custom path to the game data folder", "command-line"))
.arg(HWApplication::tr("Hedgewars can use a %1 (e.g. \"%2\") to connect on start.", "command-line").arg(HWApplication::tr("CONNECTSTRING", "command-line")).arg(QString("hwplay://") + NETGAME_DEFAULT_SERVER));
}
int main(int argc, char *argv[]) {
- /* Qt5 Base removed Motif, Plastique. These are now in the Qt style plugins
- (Ubuntu: qt5-style-plugins, which was NOT backported by Debian/Ubuntu to stable/LTS).
- Windows appears to render best of the remaining options but still isn't quite right. */
-
- // Try setting Plastique if available
- QStyle* coreStyle;
- coreStyle = QStyleFactory::create("Plastique");
- if(coreStyle != 0) {
- QApplication::setStyle(coreStyle);
- qDebug("Qt style set: Plastique");
- } else {
- // Use Windows as fallback.
- // FIXME: Under Windows style, some widgets like scrollbars don't render as nicely
- coreStyle = QStyleFactory::create("Windows");
- if(coreStyle != 0) {
- QApplication::setStyle(coreStyle);
- qDebug("Qt style set: Windows");
- } else {
- // Windows style should not be missing in Qt5 Base. If it does, something went terribly wrong!
- qWarning("No Qt style could be set! Using the default one.");
- }
- }
// Since we're calling this first, closeResources() will be the last thing called after main() returns.
atexit(closeResources);
@@ -191,16 +194,27 @@
cocoaInit = new CocoaInitializer(); // Creates the autoreleasepool preventing cocoa object leaks on OS X.
#endif
- SDLInteraction::instance();
-
HWApplication app(argc, argv);
app.setAttribute(Qt::AA_DontShowIconsInMenus,false);
// file engine, to be initialized later
engine = NULL;
+ /*
+ This is for messages frelated to translatable command-line arguments.
+ If it is non-zero, will print out a message after loading locale
+ and exit.
+ */
+ enum cmdMsgStateEnum {
+ cmdMsgNone,
+ cmdMsgHelp,
+ cmdMsgMalformedArg,
+ cmdMsgUnknownArg,
+ };
+ enum cmdMsgStateEnum cmdMsgState = cmdMsgNone;
+ QString cmdMsgStateStr;
+
// parse arguments
-
QStringList arguments = app.arguments();
QMap parsedArgs;
{
@@ -221,14 +235,17 @@
if(arg.startsWith("--")) {
if(arg == "--help")
{
- printf("%s", getUsage().toUtf8().constData());
- return 0;
+ cmdMsgState = cmdMsgHelp;
+ qInstallMessageHandler(restrictedMessageHandler);
}
- // argument is something wrong
- fprintf(stderr, "%s\n\n%s",
- HWApplication::tr("Malformed option argument: %1", "command-line").arg(arg).toUtf8().constData(),
- getUsage().toUtf8().constData());
- return 1;
+ else
+ {
+ // argument is something wrong
+ cmdMsgState = cmdMsgMalformedArg;
+ cmdMsgStateStr = arg;
+ qInstallMessageHandler(restrictedMessageHandler);
+ break;
+ }
}
// if not starting with --, then always skip
@@ -238,47 +255,61 @@
}
}
- if(parsedArgs.contains("data-dir"))
+ if(cmdMsgState == cmdMsgNone)
{
- QFileInfo f(parsedArgs["data-dir"]);
- parsedArgs.remove("data-dir");
- if(!f.exists())
+ if(parsedArgs.contains("data-dir"))
+ {
+ QFileInfo f(parsedArgs["data-dir"]);
+ parsedArgs.remove("data-dir");
+ if(!f.exists())
+ {
+ qWarning() << "WARNING: Cannot open data-dir=" << f.absoluteFilePath();
+ }
+ *cDataDir = f.absoluteFilePath();
+ custom_data = true;
+ }
+
+ if(parsedArgs.contains("config-dir"))
+ {
+ QFileInfo f(parsedArgs["config-dir"]);
+ parsedArgs.remove("config-dir");
+ cfgdir->setPath(f.absoluteFilePath());
+ custom_config = true;
+ }
+ else
{
- qWarning() << "WARNING: Cannot open DATA_PATH=" << f.absoluteFilePath();
+ cfgdir->setPath(QDir::homePath());
+ custom_config = false;
+ }
+
+ if (!parsedArgs.isEmpty())
+ {
+ cmdMsgState = cmdMsgUnknownArg;
+ qInstallMessageHandler(restrictedMessageHandler);
}
- *cDataDir = f.absoluteFilePath();
- custom_data = true;
+
+ // end of parameter parsing
+
+ // Select Qt style
+ QStyle* coreStyle;
+ coreStyle = QStyleFactory::create("Windows");
+ if(coreStyle != 0) {
+ QApplication::setStyle(coreStyle);
+ qDebug("Qt style set: Windows");
+ } else {
+ // Windows style should not be missing in Qt5 Base. If it does, something went terribly wrong!
+ qWarning("No Qt style could be set! Using the default one.");
+ }
}
- if(parsedArgs.contains("config-dir"))
- {
- QFileInfo f(parsedArgs["config-dir"]);
- parsedArgs.remove("config-dir");
- cfgdir->setPath(f.absoluteFilePath());
- custom_config = true;
- }
- else
- {
- cfgdir->setPath(QDir::homePath());
- custom_config = false;
- }
-
- if (!parsedArgs.isEmpty()) {
- foreach (const QString & key, parsedArgs.keys())
- {
- fprintf(stderr, "%s\n", HWApplication::tr("Unknown option argument: %1", "command-line").arg(QString("--") + key).toUtf8().constData());
- }
- fprintf(stderr, "\n%s", getUsage().toUtf8().constData());
- return 1;
- }
-
- // end of parameter parsing
-
-
#ifdef Q_OS_WIN
+ // Splash screen for Windows
QPixmap pixmap(":/res/splash.png");
QSplashScreen splash(pixmap);
- splash.show();
+ if(cmdMsgState == cmdMsgNone)
+ {
+ splash.show();
+ }
#endif
QDateTime now = QDateTime::currentDateTime();
@@ -297,10 +328,10 @@
checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars");
cfgdir->cd("Library/Application Support/Hedgewars");
#elif defined _WIN32
- char path[1024];
- if(!SHGetFolderPathA(0, CSIDL_PERSONAL, NULL, 0, path))
+ wchar_t path[MAX_PATH];
+ if(SHGetFolderPathW(0, CSIDL_PERSONAL, NULL, 0, path) == S_OK)
{
- cfgdir->cd(path);
+ cfgdir->cd(QString::fromWCharArray(path));
checkForDir(cfgdir->absolutePath() + "/Hedgewars");
cfgdir->cd("Hedgewars");
}
@@ -329,6 +360,7 @@
checkForDir(cfgdir->absolutePath() + "/Logs");
checkForDir(cfgdir->absolutePath() + "/Videos");
checkForDir(cfgdir->absolutePath() + "/VideoTemp");
+ checkForDir(cfgdir->absolutePath() + "/VideoThumbnails");
}
datadir->cd(bindir->absolutePath());
@@ -351,27 +383,9 @@
QTranslator TranslatorHedgewars;
QTranslator TranslatorQt;
+ QSettings settings(DataManager::instance().settingsFileName(), QSettings::IniFormat);
+ settings.setIniCodec("UTF-8");
{
- QSettings settings(DataManager::instance().settingsFileName(), QSettings::IniFormat);
- settings.setIniCodec("UTF-8");
-
- // Heuristic to figure out if the user is (probably) a first-time player.
- // If nickname is not set, then probably yes.
- // The hidden setting firstLaunch is, if present, used to force HW to
- // treat iself as if it were launched the first time.
- QString nick = settings.value("net/nick", QString()).toString();
- if (settings.contains("frontend/firstLaunch"))
- {
- isProbablyNewPlayer = settings.value("frontend/firstLaunch").toBool();
- }
- else
- {
- isProbablyNewPlayer = nick.isNull();
- }
-
- // Set firstLaunch to false to make sure we remember we have been launched before.
- settings.setValue("frontend/firstLaunch", false);
-
QString cc = settings.value("misc/locale", QString()).toString();
if (cc.isEmpty())
{
@@ -401,6 +415,54 @@
app.installTranslator(&TranslatorQt);
}
app.setLayoutDirection(QLocale().textDirection());
+
+ // Handle command line messages
+ switch(cmdMsgState)
+ {
+ case cmdMsgHelp:
+ {
+ printf("%s", getUsage().toUtf8().constData());
+ return 0;
+ }
+ case cmdMsgMalformedArg:
+ {
+ fprintf(stderr, "%s\n\n%s",
+ HWApplication::tr("Malformed option argument: %1", "command-line").arg(cmdMsgStateStr).toUtf8().constData(),
+ getUsage().toUtf8().constData());
+ return 1;
+ }
+ case cmdMsgUnknownArg:
+ {
+ foreach (const QString & key, parsedArgs.keys())
+ {
+ fprintf(stderr, "%s\n",
+ HWApplication::tr("Unknown option argument: %1", "command-line").arg(QString("--") + key).toUtf8().constData());
+ }
+ fprintf(stderr, "\n%s", getUsage().toUtf8().constData());
+ return 1;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ // Heuristic to figure out if the user is (probably) a first-time player.
+ // If nickname is not set, then probably yes.
+ // The hidden setting firstLaunch is, if present, used to force HW to
+ // treat iself as if it were launched the first time.
+ QString nick = settings.value("net/nick", QString()).toString();
+ if (settings.contains("frontend/firstLaunch"))
+ {
+ isProbablyNewPlayer = settings.value("frontend/firstLaunch").toBool();
+ }
+ else
+ {
+ isProbablyNewPlayer = nick.isNull();
+ }
+
+ // Set firstLaunch to false to make sure we remember we have been launched before.
+ settings.setValue("frontend/firstLaunch", false);
}
#ifdef _WIN32
@@ -414,14 +476,21 @@
}
#endif
+ SDLInteraction::instance();
+
QString style = "";
QString fname;
- checkSeason();
- //For each season, there is an extra stylesheet
- //Todo: change background for easter and birthday
- //(simply replace res/BackgroundBirthday.png and res/BackgroundEaster.png
- //with an appropriate background
+ bool holidaySilliness = settings.value("misc/holidaySilliness", true).toBool();
+ if(holidaySilliness)
+ checkSeason();
+ else
+ season = SEASON_NONE;
+
+ // For each season, there is an extra stylesheet.
+ // TODO: change background for easter
+ // (simply replace res/BackgroundEaster.png
+ // with an appropriate background).
switch (season)
{
case SEASON_CHRISTMAS :
@@ -455,7 +524,8 @@
app.form = new HWForm(NULL, style);
#ifdef Q_OS_WIN
- splash.finish(app.form);
+ if(cmdMsgState == cmdMsgNone)
+ splash.finish(app.form);
#endif
app.form->show();
@@ -466,6 +536,7 @@
questionTutorialMsg.setIcon(QMessageBox::Question);
questionTutorialMsg.setWindowTitle(QMessageBox::tr("Welcome to Hedgewars"));
questionTutorialMsg.setText(QMessageBox::tr("Welcome to Hedgewars!\n\nYou seem to be new around here. Would you like to play some training missions first to learn the basics of Hedgewars?"));
+ questionTutorialMsg.setTextFormat(Qt::PlainText);
questionTutorialMsg.setWindowModality(Qt::WindowModal);
questionTutorialMsg.addButton(QMessageBox::Yes);
questionTutorialMsg.addButton(QMessageBox::No);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/mission.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/mission.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,79 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2018 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "mission.h"
+#include "hwconsts.h"
+#include "DataManager.h"
+#include
+#include
+#include
+
+QSettings* getMissionTeamFile(QString & missionName, QString & teamName)
+{
+ QSettings* teamfile = new QSettings(cfgdir->absolutePath() + "/Teams/" + teamName + ".hwt", QSettings::IniFormat, 0);
+ teamfile->setIniCodec("UTF-8");
+ if (!teamfile->childGroups().contains("Mission " + missionName) &&
+ teamfile->childGroups().contains("Mission " + missionName)){
+ teamfile->beginGroup("Mission " + missionName);
+ QStringList keys = teamfile->childKeys();
+ teamfile->endGroup();
+ for (int i=0;ivalue("Mission " + missionName + "/" + keys[i]);
+ teamfile->setValue("Mission " + missionName + "/" + keys[i], value);
+ }
+ teamfile->remove("Mission " + missionName);
+ }
+
+ return teamfile;
+}
+
+/**
+ Returns true if the specified mission has been completed
+ missionName: Name of the mission in question
+ teamName: Name of the playing team
+*/
+bool isMissionWon(QString & missionName, QString & teamName)
+{
+ QSettings* teamfile = getMissionTeamFile(missionName, teamName);
+ bool won = teamfile->value("Mission " + missionName + "/Won", false).toBool();
+ return won;
+}
+
+/**
+ Returns true if the mission value adressed with the provided
+ missionName: Name of the mission in question
+ teamName: Name of the playing team
+ key: name of key to check
+*/
+bool missionValueExists(QString & missionName, QString & teamName, QString key)
+{
+ QSettings* teamfile = getMissionTeamFile(missionName, teamName);
+ return teamfile->contains("Mission " + missionName + "/" + key);
+}
+/**
+ Returns a mission value.
+ NOTE: Check whether the mission value exists first, using missionValueExists.
+ missionName: Name of the mission in question
+ teamName: Name of the playing team
+ key: name of key to read its value from
+*/
+QVariant getMissionValue(QString & missionName, QString & teamName, QString key)
+{
+ QSettings* teamfile = getMissionTeamFile(missionName, teamName);
+ return teamfile->value("Mission " + missionName + "/" + key);
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/mission.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/mission.h Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,30 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2018 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef MISSION_H
+#define MISSION_H
+
+#include
+#include
+
+QSettings* getMissionTeamFile(QString & missionName, QString & teamName);
+bool isMissionWon(QString & missionName, QString & teamName);
+bool missionValueExists(QString & missionName, QString & teamName, QString key);
+QVariant getMissionValue(QString & missionName, QString & teamName, QString key);
+
+#endif
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/model/ThemeFilterProxyModel.cpp
--- a/QTfrontend/model/ThemeFilterProxyModel.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/model/ThemeFilterProxyModel.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -29,6 +29,7 @@
{
isFilteringDLC = false;
isFilteringHidden = false;
+ isFilteringBackground = false;
}
bool ThemeFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const
@@ -43,13 +44,15 @@
searchOkay = in != -1;
}
- if(isFilteringDLC || isFilteringHidden)
+ if(isFilteringDLC || isFilteringHidden || isFilteringBackground)
{
bool isDLC = index.data(ThemeModel::IsDlcRole).toBool();
bool isHidden = index.data(ThemeModel::IsHiddenRole).toBool();
+ bool isBackground = index.data(ThemeModel::IsBackgroundThemeRole).toBool();
return ( ((isFilteringDLC && !isDLC) || !isFilteringDLC) &&
- ((isFilteringHidden && !isHidden) || !isFilteringHidden) ) &&
+ ((isFilteringHidden && !isHidden) || !isFilteringHidden) &&
+ ((isFilteringBackground && !isBackground) || !isFilteringBackground) ) &&
searchOkay;
}
else
@@ -69,3 +72,9 @@
isFilteringHidden = enable;
invalidateFilter();
}
+
+void ThemeFilterProxyModel::setFilterBackground(bool enable)
+{
+ isFilteringBackground = enable;
+ invalidateFilter();
+};
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/model/ThemeFilterProxyModel.h
--- a/QTfrontend/model/ThemeFilterProxyModel.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/model/ThemeFilterProxyModel.h Wed Jul 31 23:14:27 2019 +0200
@@ -37,6 +37,7 @@
ThemeFilterProxyModel(QObject *parent = 0);
void setFilterDLC(bool enabled);
void setFilterHidden(bool enabled);
+ void setFilterBackground(bool enabled);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
@@ -44,6 +45,7 @@
private:
bool isFilteringDLC;
bool isFilteringHidden;
+ bool isFilteringBackground;
};
#endif // HEDGEWARS_THEMEFILTERPROXYMODEL_H
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/model/ThemeModel.cpp
--- a/QTfrontend/model/ThemeModel.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/model/ThemeModel.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -124,38 +124,49 @@
{
QMap dataset;
+ // Ignore directories without theme.cfg
+ QFile themeCfgFile(QString("physfs://Themes/%1/theme.cfg").arg(theme));
+ if (!themeCfgFile.open(QFile::ReadOnly))
+ {
+ continue;
+ }
+
// themes without icon are supposed to be hidden
QString iconpath = QString("physfs://Themes/%1/icon.png").arg(theme);
-
if (!QFile::exists(iconpath))
{
dataset.insert(IsHiddenRole, true);
}
else
{
- // themes with the key “hidden” in theme.cfg are hidden, too
- QFile themeCfgFile(QString("physfs://Themes/%1/theme.cfg").arg(theme));
- if (themeCfgFile.open(QFile::ReadOnly))
+ QTextStream stream(&themeCfgFile);
+ QString line = stream.readLine();
+ QString key;
+ while (!line.isNull())
{
- QTextStream stream(&themeCfgFile);
- QString line = stream.readLine();
- QString key;
- while (!line.isNull())
+ key = QString(line);
+ int equalsPos = line.indexOf('=');
+ key.truncate(equalsPos - 1);
+ key = key.simplified();
+ if (!line.startsWith(';') && key == "hidden")
{
- key = QString(line);
- int equalsPos = line.indexOf('=');
- key.truncate(equalsPos - 1);
- key = key.simplified();
- if (!line.startsWith(';') && key == "hidden")
- {
- dataset.insert(IsHiddenRole, true);
- break;
- }
- line = stream.readLine();
+ dataset.insert(IsHiddenRole, true);
+ break;
}
+ line = stream.readLine();
}
}
+ // Themes without land textures are considered "background themes"
+ // since they cannot be used for generated maps, but they can be used
+ // for image maps.
+ QString landtexpath = QString("physfs://Themes/%1/LandTex.png").arg(theme);
+ QString bordertexpath = QString("physfs://Themes/%1/Border.png").arg(theme);
+ if ((!QFile::exists(landtexpath)) || (!QFile::exists(bordertexpath)))
+ {
+ dataset.insert(IsBackgroundThemeRole, true);
+ }
+
// detect if theme is dlc
QString themeDir = PHYSFS_getRealDir(QString("Themes/%1").arg(theme).toLocal8Bit().data());
bool isDLC = !themeDir.startsWith(datadir->absolutePath());
@@ -179,5 +190,6 @@
}
m_data.append(dataset);
+ themeCfgFile.close();
}
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/model/ThemeModel.h
--- a/QTfrontend/model/ThemeModel.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/model/ThemeModel.h Wed Jul 31 23:14:27 2019 +0200
@@ -41,7 +41,7 @@
Q_OBJECT
public:
- enum Roles { ActualNameRole = Qt::UserRole, IsDlcRole, IconPathRole, IsHiddenRole };
+ enum Roles { ActualNameRole = Qt::UserRole, IsDlcRole, IconPathRole, IsHiddenRole, IsBackgroundThemeRole };
explicit ThemeModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/model/gameSchemeModel.cpp
--- a/QTfrontend/model/gameSchemeModel.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/model/gameSchemeModel.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -87,7 +87,9 @@
<< "Timeless"
<< "Thinking with Portals"
<< "King Mode"
+ << "Mutant"
<< "Construction Mode"
+ << "The Specialists"
<< "Space Invasion"
<< "HedgeEditor"
;
@@ -573,9 +575,57 @@
<< QVariant() // scriptparam 43
;
+ QList mutant;
+ mutant
+ << predefSchemesNames[10] // name 0
+ << QVariant(false) // switchhog 1
+ << QVariant(false) // team divide 2
+ << QVariant(false) // solid land 3
+ << QVariant(false) // border 4
+ << QVariant(false) // low gravity 5
+ << QVariant(false) // laser sight 6
+ << QVariant(false) // invulnerable 7
+ << QVariant(false) // reset health 8
+ << QVariant(false) // vampiric 9
+ << QVariant(false) // karma 10
+ << QVariant(false) // artillery 11
+ << QVariant(true) // random order 12
+ << QVariant(false) // king 13
+ << QVariant(false) // place hog 14
+ << QVariant(false) // shared ammo 15
+ << QVariant(false) // disable girders 16
+ << QVariant(false) // disable land objects 17
+ << QVariant(false) // AI survival 18
+ << QVariant(false) // inf. attack 19
+ << QVariant(true) // reset weps 20
+ << QVariant(false) // per hog ammo 21
+ << QVariant(false) // no wind 22
+ << QVariant(false) // more wind 23
+ << QVariant(false) // tag team 24
+ << QVariant(false) // bottom border 25
+ << QVariant(100) // damage modfier 26
+ << QVariant(20) // turn time 27
+ << QVariant(100) // init health 28
+ << QVariant(15) // sudden death 29
+ << QVariant(2) // case prob 30
+ << QVariant(1) // mines time 31
+ << QVariant(4) // mines number 32
+ << QVariant(0) // mine dud pct 33
+ << QVariant(2) // explosives 34
+ << QVariant(0) // air mines 35
+ << QVariant(0) // health case pct 36
+ << QVariant(25) // health case amt 37
+ << QVariant(0) // water rise amt 38
+ << QVariant(0) // health dec amt 39
+ << QVariant(100) // rope modfier 40
+ << QVariant(100) // get away time 41
+ << QVariant(0) // world edge 42
+ << QVariant() // scriptparam 43
+ ;
+
QList construction;
construction
- << predefSchemesNames[10] // name 0
+ << predefSchemesNames[11] // name 0
<< QVariant(false) // switchhog 1
<< QVariant(false) // team divide 2
<< QVariant(false) // solid land 3
@@ -622,9 +672,58 @@
<< QVariant("initialenergy=550, energyperround=50, maxenergy=1000, cratesperround=5") // scriptparam 43
;
+ QList specialists;
+ specialists
+ << predefSchemesNames[12] // name 0
+ << QVariant(true) // switchhog 1
+ << QVariant(false) // team divide 2
+ << QVariant(false) // solid land 3
+ << QVariant(false) // border 4
+ << QVariant(false) // low gravity 5
+ << QVariant(false) // laser sight 6
+ << QVariant(false) // invulnerable 7
+ << QVariant(false) // reset health 8
+ << QVariant(false) // vampiric 9
+ << QVariant(false) // karma 10
+ << QVariant(false) // artillery 11
+ << QVariant(false) // random order 12
+ << QVariant(false) // king 13
+ << QVariant(true) // place hog 14
+ << QVariant(false) // shared ammo 15
+ << QVariant(false) // disable girders 16
+ << QVariant(false) // disable land objects 17
+ << QVariant(false) // AI survival 18
+ << QVariant(true) // inf. attack 19
+ << QVariant(true) // reset weps 20
+ << QVariant(true) // per hog ammo 21
+ << QVariant(false) // no wind 22
+ << QVariant(false) // more wind 23
+ << QVariant(false) // tag team 24
+ << QVariant(false) // bottom border 25
+ << QVariant(100) // damage modfier 26
+ << QVariant(45) // turn time 27
+ << QVariant(100) // init health 28
+ << QVariant(15) // sudden death 29
+ << QVariant(5) // case prob 30
+ << QVariant(3) // mines time 31
+ << QVariant(0) // mines number 32
+ << QVariant(0) // mine dud pct 33
+ << QVariant(0) // explosives 34
+ << QVariant(0) // air mines 35
+ << QVariant(100) // health case pct 36
+ << QVariant(25) // health case amt 37
+ << QVariant(47) // water rise amt 38
+ << QVariant(5) // health dec amt 39
+ << QVariant(100) // rope modfier 40
+ << QVariant(100) // get away time 41
+ << QVariant(0) // world edge 42
+ // NOTE: If you change this, also change the defaults in the The Specialists script
+ << QVariant("t=SENDXHPL") // scriptparam 43
+ ;
+
QList spaceinvasion;
spaceinvasion
- << predefSchemesNames[11] // name 0
+ << predefSchemesNames[13] // name 0
<< QVariant(false) // switchhog 1
<< QVariant(false) // team divide 2
<< QVariant(false) // solid land 3
@@ -673,7 +772,7 @@
QList hedgeeditor;
hedgeeditor
- << predefSchemesNames[12] // name 0
+ << predefSchemesNames[14] // name 0
<< QVariant(false) // switchhog 1
<< QVariant(false) // team divide 2
<< QVariant(false) // solid land 3
@@ -731,13 +830,20 @@
schemes.append(timeless);
schemes.append(thinkingportals);
schemes.append(kingmode);
+ schemes.append(mutant);
schemes.append(construction);
+ schemes.append(specialists);
schemes.append(spaceinvasion);
schemes.append(hedgeeditor);
if (!QDir(cfgdir->absolutePath() + "/Schemes").exists()) {
QDir().mkdir(cfgdir->absolutePath() + "/Schemes");
}
+ QStringList predefSchemesNamesLower;
+ for (int i = 0; i < predefSchemesNames.size(); ++i)
+ {
+ predefSchemesNamesLower.append(predefSchemesNames[i].toLower());
+ }
if (!QDir(directory).exists()) {
QDir().mkdir(directory);
@@ -751,7 +857,7 @@
legacyFileConfig.setArrayIndex(i);
QString schemeName = legacyFileConfig.value(spNames[0]).toString();
- if (!schemeName.isNull() && !predefSchemesNames.contains(schemeName))
+ if (!schemeName.isNull() && !predefSchemesNamesLower.contains(schemeName.toLower()))
{
QList scheme;
QFile file(directory + "/" + schemeName + ".hwg");
@@ -794,6 +900,11 @@
if (schemeName.endsWith(".hwg", Qt::CaseInsensitive)) {
schemeName.chop(4);
}
+ // Don't load scheme if name collides with default scheme
+ if (predefSchemesNamesLower.contains(schemeName.toLower())) {
+ qWarning("Game scheme \"%s\" not loaded from file, name collides with a default scheme!", qPrintable(schemeName));
+ continue;
+ }
// Parse game scheme file
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
@@ -856,9 +967,15 @@
bool GameSchemeModel::hasScheme(QString name)
{
+ return hasScheme(name, -1);
+}
+
+bool GameSchemeModel::hasScheme(QString name, int ignoreID)
+{
+ QString nameLower = name.toLower();
for(int i=0; i > schemes;
};
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/model/netserverslist.cpp
--- a/QTfrontend/model/netserverslist.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/model/netserverslist.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -45,8 +45,10 @@
case 0:
return tr("Title");
case 1:
+ //: short for "IP address" (Internet Protocol), part of server address
return tr("IP");
case 2:
+ //: short for "port number", part of server address
return tr("Port");
default:
return QVariant();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/hwmap.cpp
--- a/QTfrontend/net/hwmap.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/hwmap.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -25,7 +25,7 @@
#include "hwmap.h"
HWMap::HWMap(QObject * parent) :
- TCPBase(false, parent)
+ TCPBase(false, false, parent)
{
templateFilter = 0;
m_mapgen = MAPGEN_REGULAR;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/hwmapoptimizer.cpp
--- a/QTfrontend/net/hwmapoptimizer.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/hwmapoptimizer.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -2,7 +2,7 @@
#include "hwconsts.h"
HWMapOptimizer::HWMapOptimizer(QObject *parent) :
- TCPBase(parent)
+ TCPBase(false, false, parent)
{
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/newnetclient.cpp
--- a/QTfrontend/net/newnetclient.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/newnetclient.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -41,6 +41,7 @@
{
m_private_game = false;
m_nick_registered = false;
+ m_demo_data_pending = false;
m_roomsListModel = new RoomsListModel(this);
@@ -74,24 +75,35 @@
{
if (m_game_connected)
{
- RawSendNet(QString("QUIT%1%2").arg(delimiter).arg("User quit"));
- emit disconnected(tr("User quit"));
+ RawSendNet(QString("QUIT%1").arg(delimiter));
+ emit disconnected("");
}
NetSocket.flush();
}
-void HWNewNet::Connect(const QString & hostName, quint16 port, const QString & nick)
+void HWNewNet::Connect(const QString & hostName, quint16 port, bool useTls, const QString & nick)
{
netClientState = Connecting;
mynick = nick;
myhost = hostName + QString(":%1").arg(port);
- NetSocket.connectToHost(hostName, port);
+ if (useTls)
+ {
+ NetSocket.connectToHostEncrypted(hostName, port);
+ if (!NetSocket.waitForEncrypted())
+ {
+ qWarning("Handshake failed");
+ }
+ }
+ else
+ {
+ NetSocket.connectToHost(hostName, port);
+ }
}
void HWNewNet::Disconnect()
{
if (m_game_connected)
- RawSendNet(QString("QUIT%1%2").arg(delimiter).arg("User quit"));
+ RawSendNet(QString("QUIT%1").arg(delimiter));
m_game_connected = false;
NetSocket.disconnectFromHost();
@@ -237,7 +249,12 @@
emit disconnected(tr("The host was not found. Please check the host name and port settings."));
break;
case QAbstractSocket::ConnectionRefusedError:
- emit disconnected(tr("Connection refused"));
+ if (getHost() == (QString("%1:%2").arg(NETGAME_DEFAULT_SERVER).arg(NETGAME_DEFAULT_PORT)))
+ // Error for official server
+ emit disconnected(tr("The connection was refused by the official server or timed out. Something seems to be wrong with the official server at the moment. This might be a temporary problem. Please try again later."));
+ else
+ // Error for every other host
+ emit disconnected(tr("The connection was refused by the host or timed out. This might have one of the following reasons:\n- The Hedgewars Server program does currently not run on the host\n- The specified port number is incorrect\n- There is a temporary network problem\n\nPlease check the host name and port settings and/or try again later."));
break;
default:
emit disconnected(NetSocket.errorString());
@@ -252,6 +269,17 @@
maybeSendPassword();
}
+void HWNewNet::ContinueConnection()
+{
+ if (netClientState == Connected)
+ {
+ RawSendNet(QString("NICK%1%2").arg(delimiter).arg(mynick));
+ RawSendNet(QString("PROTO%1%2").arg(delimiter).arg(*cProtoVer));
+ m_game_connected = true;
+ emit adminAccess(false);
+ }
+}
+
void HWNewNet::ParseCmd(const QStringList & lst)
{
qDebug() << "Server: " << lst;
@@ -291,6 +319,27 @@
return;
}
+ if (lst[0] == "REDIRECT")
+ {
+ if (lst.size() < 2 || lst[1].toInt() == 0)
+ {
+ qWarning("Net: Malformed REDIRECT message");
+ return;
+ }
+
+ quint16 port = lst[1].toInt();
+ if (port == 0)
+ {
+ qWarning() << "Invalid redirection port";
+ }
+ else
+ {
+ netClientState = Redirected;
+ emit redirected(port);
+ }
+ return;
+ }
+
if (lst[0] == "CONNECTED")
{
if(lst.size() < 3 || lst[2].toInt() < cMinServerVersion)
@@ -303,11 +352,12 @@
return;
}
- RawSendNet(QString("NICK%1%2").arg(delimiter).arg(mynick));
- RawSendNet(QString("PROTO%1%2").arg(delimiter).arg(*cProtoVer));
- netClientState = Connected;
- m_game_connected = true;
- emit adminAccess(false);
+ ClientState lastState = netClientState;
+ netClientState = Connected;
+ if (lastState != Redirected)
+ {
+ ContinueConnection();
+ }
return;
}
@@ -376,22 +426,50 @@
return;
}
- QString action = HWProto::chatStringToAction(lst[2]);
+ QString action;
+ QString message;
+ QString sender = lst[1];
+ // '[' is a special character used in fake nick names of server messages.
+ // Those are supposed to be translated
+ if(!sender.startsWith('['))
+ {
+ // Normal message
+ message = lst[2];
+ // Another kind of fake nick. '(' nicks are server messages, but they must not be translated
+ if(!sender.startsWith('('))
+ {
+ // Check for action (/me command)
+ action = HWProto::chatStringToAction(message);
+ }
+ else
+ {
+ // If parenthesis were used, replace them with square brackets
+ // for a consistent style.
+ sender.replace(0, 1, '[');
+ sender.replace(sender.length()-1, 1, ']');
+ }
+ }
+ else
+ {
+ // Server message
+ // Server messages are translated client-side
+ message = HWApplication::translate("server", lst[2].toLatin1().constData());
+ }
if (netClientState == InLobby)
{
- if (action != NULL)
- emit lobbyChatAction(lst[1], action);
+ if (!action.isNull())
+ emit lobbyChatAction(sender, action);
else
- emit lobbyChatMessage(lst[1], lst[2]);
+ emit lobbyChatMessage(sender, message);
}
else
{
- emit chatStringFromNet(HWProto::formatChatMsg(lst[1], lst[2]));
- if (action != NULL)
- emit roomChatAction(lst[1], action);
+ emit chatStringFromNet(HWProto::formatChatMsg(sender, message));
+ if (!action.isNull())
+ emit roomChatAction(sender, action);
else
- emit roomChatMessage(lst[1], lst[2]);
+ emit roomChatMessage(sender, message);
}
return;
}
@@ -721,13 +799,23 @@
return;
}
- if(netClientState == InRoom || netClientState == InGame)
+ if(netClientState == InLobby && lst[0] == "REPLAY_START")
+ {
+ netClientState = InRoom;
+ m_demo_data_pending = true;
+ emit EnteredGame();
+ emit roomMaster(false);
+ return;
+ }
+
+ if(netClientState == InRoom || netClientState == InGame || netClientState == InDemo)
{
if (lst[0] == "EM")
{
if(lst.size() < 2)
{
qWarning("Net: Bad EM message");
+ m_demo_data_pending = false;
return;
}
for(int i = 1; i < lst.size(); ++i)
@@ -735,9 +823,13 @@
QByteArray em = QByteArray::fromBase64(lst[i].toLatin1());
emit FromNet(em);
}
+ m_demo_data_pending = false;
return;
}
+ }
+ if(netClientState == InRoom || netClientState == InGame)
+ {
if (lst[0] == "ROUND_FINISHED")
{
emit FromNet(QByteArray("\x01o"));
@@ -779,8 +871,16 @@
if (lst[0] == "RUN_GAME")
{
- netClientState = InGame;
- emit AskForRunGame();
+ if(m_demo_data_pending)
+ {
+ netClientState = InDemo;
+ emit AskForOfficialServerDemo();
+ }
+ else
+ {
+ netClientState = InGame;
+ emit AskForRunGame();
+ }
return;
}
@@ -865,7 +965,10 @@
if (lst.size() < 3)
emit chatStringFromNet(tr("%1 *** %2 has left").arg('\x03').arg(lst[1]));
else
- emit chatStringFromNet(tr("%1 *** %2 has left (%3)").arg('\x03').arg(lst[1], lst[2]));
+ {
+ QString leaveMsg = QString(lst[2]);
+ emit chatStringFromNet(tr("%1 *** %2 has left (%3)").arg('\x03').arg(lst[1]).arg(HWApplication::translate("server", leaveMsg.toLatin1().constData())));
+ }
m_playersModel->playerLeftRoom(lst[1]);
return;
}
@@ -985,6 +1088,13 @@
netClientState = InRoom;
RawSendNet(QString("ROUNDFINISHED%1%2").arg(delimiter).arg(correctly ? "1" : "0"));
}
+ else if (netClientState == InDemo)
+ {
+ netClientState = InLobby;
+ askRoomsList();
+ emit LeftRoom(QString());
+ m_playersModel->resetRoomFlags();
+ }
}
void HWNewNet::banPlayer(const QString & nick)
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/newnetclient.h
--- a/QTfrontend/net/newnetclient.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/newnetclient.h Wed Jul 31 23:14:27 2019 +0200
@@ -22,7 +22,7 @@
#include
#include
-#include
+#include
#include
#include "team.h"
@@ -43,11 +43,12 @@
Q_OBJECT
public:
- enum ClientState { Disconnected, Connecting, Connected, InLobby, InRoom, InGame };
+ enum ClientState { Disconnected, Connecting, Redirected, Connected, InLobby, InRoom, InGame, InDemo };
HWNewNet();
~HWNewNet();
- void Connect(const QString & hostName, quint16 port, const QString & nick);
+ void Connect(const QString & hostName, quint16 port, bool useTls, const QString & nick);
+ void ContinueConnection();
void Disconnect();
void SendPasswordHash(const QString & hash);
void NewNick(const QString & nick);
@@ -68,10 +69,11 @@
QString mynick;
QString myroom;
QString myhost;
- QTcpSocket NetSocket;
+ QSslSocket NetSocket;
QString seed;
bool m_game_connected;
bool m_nick_registered;
+ bool m_demo_data_pending;
RoomsListModel * m_roomsListModel;
PlayersListModel * m_playersModel;
QSortFilterProxyModel * m_lobbyPlayersModel;
@@ -87,7 +89,7 @@
int ByteLength(const QString & str);
void RawSendNet(const QString & buf);
void RawSendNet(const QByteArray & buf);
- void ParseCmd(const QStringList & lst);
+ void ParseCmd(const QStringList & lst);
void handleNotice(int n);
void maybeSendPassword();
@@ -96,8 +98,10 @@
signals:
void AskForRunGame();
+ void AskForOfficialServerDemo();
void connected();
void disconnected(const QString & reason);
+ void redirected(quint16 port);
void Error(const QString & errmsg);
void Warning(const QString & wrnmsg);
void NickRegistered(const QString & nick);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/proto.cpp
--- a/QTfrontend/net/proto.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/proto.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -47,8 +47,11 @@
QString HWProto::formatChatMsg(const QString & nick, const QString & msg)
{
- if(msg.left(4) == "/me ")
+ // Messages using the /me command.
+ // Server messages (nick starts with a bracket) are never considered /me messages.
+ if(msg.left(4) == "/me " && (!nick.startsWith('[')) && (!nick.startsWith('(')))
return QString("\x02* %1 %2").arg(nick).arg(msg.mid(4));
+ // Normal chat message
else
return QString("\x01%1: %2").arg(nick).arg(msg);
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/recorder.cpp
--- a/QTfrontend/net/recorder.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/recorder.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -23,6 +23,7 @@
#include "gameuiconfig.h"
#include "hwconsts.h"
#include "game.h"
+#include "util/MessageDialog.h"
#include "LibavInteraction.h"
// Encoding is memory expensive process, so we need to limit maximum number
@@ -33,12 +34,13 @@
static QList queue;
HWRecorder::HWRecorder(GameUIConfig * config, const QString &prefix) :
- TCPBase(false)
+ TCPBase(false, !config->language().isEmpty())
{
this->config = config;
this->prefix = prefix;
item = 0;
finished = false;
+ aborted = false;
name = prefix + "." + LibavInteraction::instance().getExtension(config->AVFormat());
}
@@ -75,6 +77,16 @@
case 'v':
finished = true;
break;
+ case 'E':
+ int size = msg.size();
+ emit ErrorMessage(
+ tr("A fatal ERROR occured while processing the video recording! "
+ "The video could not be saved.\n\n"
+ "As a workaround, you could try to reset the Hedgewars video recorder settings to the defaults.\n\n"
+ "To report this error, please click the 'Feedback' button in the main menu!\n\n"
+ "Last engine message:\n%1")
+ .arg(QString::fromUtf8(msg.mid(2).left(size - 4))));
+ return;
}
}
}
@@ -140,7 +152,10 @@
// Could use a field to use quality instead. maybe quality could override bitrate - or just pass (and set) both.
// The library does support using both at once after all.
arguments << QString::number(config->rec_Bitrate()*1024);
- arguments << (config->recordAudio() ? config->audioCodec() : "no");
+ if (config->recordAudio() && (config->isSoundEnabled() || config->isMusicEnabled()))
+ arguments << config->audioCodec();
+ else
+ arguments << "no";
arguments << "--chat-size";
arguments << QString::number(config->chatSize());
@@ -151,3 +166,10 @@
{
return true;
}
+
+void HWRecorder::abort()
+{
+ queue.removeOne(this);
+ aborted = true;
+ deleteLater();
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/recorder.h
--- a/QTfrontend/net/recorder.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/recorder.h Wed Jul 31 23:14:27 2019 +0200
@@ -35,6 +35,7 @@
virtual ~HWRecorder();
void EncodeVideo(const QByteArray & record);
+ void abort();
bool simultaneousRun();
VideoItem * item; // used by pagevideos
@@ -50,9 +51,11 @@
signals:
void onProgress(float progress); // 0 < progress < 1
void encodingFinished(bool success);
+ void ErrorMessage(const QString &);
private:
bool finished;
+ bool aborted;
GameUIConfig * config;
};
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/tcpBase.cpp
--- a/QTfrontend/net/tcpBase.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/tcpBase.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -21,10 +21,12 @@
#include
#include
#include
+#include
#include "tcpBase.h"
#include "hwconsts.h"
#include "MessageDialog.h"
+#include "gameuiconfig.h"
#ifdef HWLIBRARY
extern "C" {
@@ -104,11 +106,12 @@
}
-TCPBase::TCPBase(bool demoMode, QObject *parent) :
+TCPBase::TCPBase(bool demoMode, bool usesCustomLanguage, QObject *parent) :
QObject(parent),
m_hasStarted(false),
m_isDemoMode(demoMode),
m_connected(false),
+ m_usesCustomLanguage(usesCustomLanguage),
IPCSocket(0)
{
process = 0;
@@ -183,6 +186,18 @@
process->setProcessChannelMode(QProcess::ForwardedChannels);
#endif
+ // If game config uses non-system locale, we set the environment
+ // of the engine first
+ if(m_usesCustomLanguage)
+ {
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ QString hwengineLang = QLocale().name() + ".UTF8";
+ qDebug("Setting hwengine environment: LANG=%s", qPrintable(hwengineLang));
+ // TODO: Check if this is correct and works on all systems
+ env.insert("LANG", QLocale().name() + ".UTF8");
+ process->setProcessEnvironment(env);
+ }
+ qDebug("Starting hwengine ...");
process->start(bindir->absolutePath() + "/hwengine", arguments);
#endif
m_hasStarted = true;
@@ -235,7 +250,7 @@
// show error message if there was an error that was not an engine's
// fatal error - because that one already sent a info via IPC
- if ((exitCode != 0) && (exitCode != 2))
+ if ((exitCode != HWENGINE_EXITCODE_OK) && (exitCode != HWENGINE_EXITCODE_FATAL))
{
// inform user that something bad happened
MessageDialog::ShowFatalMessage(
@@ -244,16 +259,22 @@
"We are very sorry for the inconvenience :(\n\n"
"If this keeps happening, please click the '%2' button in the main menu!")
.arg(exitCode)
- .arg("Feedback"));
+ .arg(QCoreApplication::translate("PageMain", "Feedback")));
}
}
void TCPBase::tcpServerReady()
{
- disconnect(srvsList.first(), SIGNAL(isReadyNow()), this, SLOT(tcpServerReady()));
-
- RealStart();
+ if (!srvsList.isEmpty())
+ {
+ disconnect(srvsList.first(), SIGNAL(isReadyNow()), this, SLOT(tcpServerReady()));
+ RealStart();
+ }
+ else
+ {
+ qDebug("tcpServerReady() called while srvsList was empty. Not starting TCP server");
+ }
}
void TCPBase::Start(bool couldCancelPreviousRequest)
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/net/tcpBase.h
--- a/QTfrontend/net/tcpBase.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/net/tcpBase.h Wed Jul 31 23:14:27 2019 +0200
@@ -41,7 +41,7 @@
Q_OBJECT
public:
- TCPBase(bool demoMode, QObject * parent = 0);
+ TCPBase(bool demoMode, bool usesCustomLanguage, QObject * parent = 0);
virtual ~TCPBase();
virtual bool couldBeRemoved();
@@ -80,6 +80,7 @@
#endif
bool m_isDemoMode;
bool m_connected;
+ bool m_usesCustomLanguage;
void RealStart();
QPointer IPCSocket;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/AboutIcon.png
Binary file QTfrontend/res/AboutIcon.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/AboutIcon.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/res/AboutIcon.svg Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,48 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/Challenges.png
Binary file QTfrontend/res/Challenges.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/Challenges.xcf
Binary file QTfrontend/res/Challenges.xcf has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/Help.png
Binary file QTfrontend/res/Help.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/Scenarios.png
Binary file QTfrontend/res/Scenarios.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/StatsEverAfter.png
Binary file QTfrontend/res/StatsEverAfter.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/StatsH.png
Binary file QTfrontend/res/StatsH.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/Trainings.png
Binary file QTfrontend/res/Trainings.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/Trainings_en.png
Binary file QTfrontend/res/Trainings_en.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/btnTagTeam.png
Binary file QTfrontend/res/btnTagTeam.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/btnTagTeam@2x.png
Binary file QTfrontend/res/btnTagTeam@2x.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/backstab.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/backstab.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/dragon.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/dragon.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/enemy.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/enemy.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/epil.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/epil.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/family.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/family.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/first_blood.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/first_blood.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/journey.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/journey.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/queen.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/queen.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/shadow.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/shadow.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Classic_Fairytale/united.png
Binary file QTfrontend/res/campaign/A_Classic_Fairytale/united.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/cosmos.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/cosmos.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/death01.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/death01.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/death02.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/death02.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/desert01.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/desert01.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/desert02.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/desert02.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/desert03.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/desert03.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/final.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/final.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/fruit01.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/fruit01.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/fruit02.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/fruit02.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/fruit03.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/fruit03.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/ice01.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/ice01.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/ice02.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/ice02.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/moon01.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/moon01.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/campaign/A_Space_Adventure/moon02.png
Binary file QTfrontend/res/campaign/A_Space_Adventure/moon02.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/credits.csv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/res/credits.csv Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,181 @@
+S,"Project founder",,,
+E,,"Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+S,"Programming",,,
+U,"Game engine",,,
+E,"Creator","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Many engine improvements","Derek Pomery","nemo@m8y.org","nemo"
+E,"Many engine improvements","Carlos Vives","mail@carlosvives.es",
+E,"Many engine improvements","Richard Karolyi","sheepluva@ercatec.net","sheepluva"
+E,,,"Wuzzy2@mail.ru","Wuzzy"
+E,,"Henrik Rostedt","henrik.rostedt@gmail.com",
+E,"Gamepad and Lua integration","Mario Liebisch","mario.liebisch@gmail.com",
+E,"Campaign support","Szabolcs Orbàn","szabibibi@gmail.com",
+E,"Theme customization improvements",,,"KoBeWi"
+E,"Some Pas2C and GLES2 work","Meng Xiangyun","xymengxy@gmail.com",
+E,"Video recording","Stepan Podoskin","stepik-777@mail.ru",
+E,"Other improvements","Valentin Kraevskiy",,"alfadur"
+U,"Map generation",,,
+E,"Core map generators","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Perlin maps and other improvements","Derek Pomery","nemo@m8y.org","nemo"
+E,"Maze maps","Henning Kühn","prg@cooco.de",
+U,"Weapons",,,
+E,"Most core weapons","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Air mine, rubber, others","Derek Pomery","nemo@m8y.org","nemo"
+E,"Drill rocket, ballgun, RC plane","Martin Boze","afffect@gmail.com",
+E,"Freezer","Julia Struchenko","urbertar@gmail.com",
+E,"Mine number and time game settings","David A. Cuadrado","krawek@gmail.com",
+M,,,,
+U,"Frontend / main menu",,,
+E,"Creator","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Many frontend improvements","Derek Pomery","nemo@m8y.org","nemo"
+E,"Many frontend improvements","Richard Karolyi","sheepluva@ercatec.net","sheepluva"
+E,"Many frontend improvements","Igor Ulyanov","disinbox@gmail.com",
+E,"Keybinds, feedback, maps and hats interfaces","Drew Gottlieb","gottlieb.drew@gmail.com",
+E,"Login dialogs, other improvements","Ondrej Skopek","skopekondrej@gmail.com",
+E,,,"Wuzzy2@mail.ru","Wuzzy"
+E,,"Martin Minarik","ttsmj@pokec.sk",
+E,,"Kristian Lehmann","email@thexception.net",
+E,,"Henrik Rostedt","henrik.rostedt@gmail.com",
+E,,"Mayur Pawashe","zorgiepoo@gmail.com",
+E,,"Valentin Kraevskiy",,"alfadur"
+U,"Missions and styles",,,
+E,"A Classic Fairytale","Szabolcs Orbàn","szabibibi@gmail.com",
+E,"A Space Adventure",,,"Master_ex"
+E,"Created Capture the Flag, Construction Mode, Control, HedgeEditor, Highlander, Racer, TechRacer, The Specialists, WxW",,,"mikade"
+E,"Training, time-trial and target practice challenges, Bazooka Battlefield, Tentacle Terror, Big Armory, bugfixes and maintenance",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Some styles and missions","John Lambert","redgrinner@gmail.com","redgrinner"
+E,"Battalion",,"Anachron14@gmx.de","Anachron"
+E,"Continental supplies",,,"Vatten"
+E,"Teamwork 2",,,"Arkhnen"
+E,"Climb Home","Derek Pomery","nemo@m8y.org","nemo"
+E,"Portal Mind Challenge",,,"sphrix"
+M,,,,
+U,"Game server",,,
+E,"Creator","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+M,,,,
+U,"Ports",,,
+E,"macOS/iPhone port, OpenGL-ES conversion","Vittorio Giovara","vittorio.giovara@gmail.com",
+E,"Android port","Richard Deurwaarder","xeli@xelification.com","xeli"
+E,"Android netplay, portability abstraction","Simeon Maxein","smaxein@googlemail.com",
+E,"WebGL port","Meng Xiangyun","xymengxy@gmail.com",
+E,"iPhone/iPad ports","Anton Malmygin","antonc27@mail.ru","antonc27"
+S,"Graphics",,,
+U,"General",,,
+E,,"John Dum","fizzy@gmail.com",
+E,,"Joshua Frese","joshfrese@gmail.com",
+E,,"Stanko Tadić","stanko@mfhinc.net",
+E,,"Julien Koesten","julienkoesten@aol.com",
+E,,"Joshua O'Sullivan","coheedftw@hotmail.co.uk",
+E,,"Nils Lück","nils.luck.design@gmail.com",
+E,,"Guillaume Englert","genglert@hybird.org",
+E,,,"ppicondo.cvac@gmail.com","CopherNeue"
+E,,"Valentin Kraevskiy",,"alfadur"
+E,,"Carlos Vives","mail@carlosvives.es",
+U,"Themes",,,
+E,"Nature, Snow, City, Castle, Halloween, Island","John Dum","fizzy@gmail.com",
+E,"Bamboo, EarthRise, BambooPlinko","Joshua Frese","joshfrese@gmail.com",
+E,"Golf, Hoggywood, Stage",,,"RoFra"
+E,"Hoggywood",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Cave, Olympics","Guillaume Englert","genglert@hybird.org",
+E,"Fruit, Cake","Randy Broda",,"Randy"
+E,"Art",,,"Zippy"
+E,"Beach",,"ppicondo.cvac@gmail.com","CopherNeue"
+E,"Beach",,,"Miguelac"
+E,"Brick",,,"AlexYeCu"
+E,"Hell","Stanko Tadić","stanko@mfhinc.net",
+E,"Jungle",,,"KoRn666"
+E,"Sheep","Julien Koesten","julienkoesten@aol.com",
+M,,,,
+U,"Maps",,,
+E,"Basketball, BasketballField, Bath, Bubbleflow, Hammock, Hedgelove, Hedgewars, Hydrant, Mushrooms, Plane, Ropes, Tree","John Dum","fizzy@gmail.com",
+E,"SB_Bones, SB_Crystal, SB_Grassy, SB_Grove, SB_Haunty, SB_Oaks, SB_Shrooms, SB_Tentacle","Chucklefish, Ltd",,
+E,"Bamboo, Blox, Cake, Cogs, EarthRise, Freeway","Joshua Frese","joshfrese@gmail.com",
+E,"Castle, PirateFlag","Stanko Tadić","stanko@mfhinc.net",
+E,"ShoppaKing, TrophyRace",,,"wolfmarc"
+E,"ShoppaKing, TrophyRace",,,"Dragonfly"
+E,"Battlefield",,,"nickstu"
+E,"CTF_Blizzard",,,"Palewolf"
+E,"Cheese",,"ppicondo.cvac@gmail.com","CopherNeue"
+E,"ClimbHome","Derek Pomery","nemo@m8y.org","nemo"
+E,"Lonely_Island","Maciej Mrozinski","mynick2@o2.pl","alzen"
+E,"Octorama",,,"jessor"
+E,"portal",,,"sphrix"
+E,"Ruler","Guillaume Englert","genglert@hybrid.org",
+E,"Sticks",,,"dctPL"
+M,,,,
+U,"Forts",,,
+E,"EvilChicken",,,"Dragonfly"
+E,"Lonely_Island","Maciej Mrozinski","mynick2@o2.pl","alzen"
+E,"Olympic","Guillaume Englert","genglert@hybird.org",
+E,"Olympic",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Tank","Carlos Vives","mail@carlosvives.es",
+E,"Snail","John Dum","fizzy@gmail.com",
+E,"Snail",,"Wuzzy2@mail.ru","Wuzzy"
+E,"SteelTower","Randy Broda",,"Randy"
+M,,,,
+U,"Hats, graves, other",,,
+E,"See CREDITS text file",,,
+S,"Sounds",,,
+E,"Hedgehogs voice","Stephen Alexander","ArmagonNo1@gmail.com","Armagon"
+E,"Default_pl, Russian_pl voices",,"mtg90pl@gmail.com","mtg90pl"
+E,,"John Dum","fizzy@gmail.com",
+E,,"Jonatan Nilsson","jonatanfan@gmail.com",
+E,,"Daniel Martin","elhombresinremedio@gmail.com","HSR"
+E,"Various authors from www.freesound.org (see CREDITS text file)",,
+S,"Music",,,
+E,"City, Rock, others","Daniel Martin","elhombresinremedio@gmail.com","HSR"
+E,"Compost",,,"HG"
+E,"EarthRise, oriental, Pirate, snow","Jonatan Nilsson","jonatanfan@gmail.com",
+E,"Fruit, Jungle","Valentin Kraevskiy",,"alfadur"
+E,"Nature","John Dum","fizzy@gmail.com",
+E,"olympics_sd",,,"yd <http://opengameart.org/users/yd>"
+E,"sdmusic (Hitman [sheepluva edit])","Kevin MacLeod",,
+M,,,,
+S,"Translations",,,
+E,"Brazilian Portuguese","Romulo Fernandes Machado","abra185@gmail.com",
+E,"Bulgarian","Svetoslav Stefanov",,
+E,"Czech","Petr Řezáček","rezacek@gmail.com",
+E,"Chinese","Jie Luo","lililjlj@gmail.com",
+E,"Chinese",,"yuenfu.chiu@gmail.com","yuenfu"
+E,"Finnish","Nina Kuisma","ninnnu@gmail.com",
+E,"Finnish","Janne Uusitupa",,
+E,"French","Antoine Turmel","geekshadow@gmail.com",
+E,"French","Clement Woitrain","sphrixclement@gmail.com",
+E,"French",,,"Matisumi"
+E,"French",,,"Case_Of"
+E,"German","Peter Hüwe","PeterHuewe@gmx.de",
+E,"German","Mario Liebisch","mario.liebisch@gmail.com",
+E,"German","Richard Karolyi","sheepluva@ercatec.net","sheepluva"
+E,"German",,"Wuzzy2@mail.ru","Wuzzy"
+E,"Greek",,"talos_kriti@yahoo.gr",
+E,"Italian","Luca Bonora","bonora.luca@gmail.com",
+E,"Italian","Marco Bresciani","m.bresciani@email.it",
+E,"Italian","Gianfranco Costamagna","costamagnagianfranco@yahoo.it",
+E,"Italian",,"enricobe@hotmail.com","Enrico"
+E,"Japanese","ADAM Etienne","etienne.adam@gmail.com",
+E,"Japanese","Marco Bresciani","m.bresciani@email.it",
+E,"Korean","Anthony Bellew","anthonyreflected@gmail.com",
+E,"Lithuanian","Lukas Urbonas","lukasu08@gmail.com",
+E,"Polish","Maciej Mroziński","mynick2@o2.pl","alzen"
+E,"Polish","Wojciech Latkowski","magik17l@gmail.com",
+E,"Polish","Piotr Mitana",,
+E,"Polish","Maciej Górny",,
+E,"Polish",,,"KoBeWi"
+E,"Portuguese","Fábio Canário","inufabie@gmail.com",
+E,"Russian","Andrey Korotaev","unC0Rr@gmail.com","unC0Rr"
+E,"Russian","Vitaly Novichkov","admin@wohlnet.ru",
+E,"Russian","Anton Malmygin","antonc27@mail.ru","antonc27"
+E,"Russian","Grigory Ustinov","grenka@altlinux.org","grenka"
+E,"Scottish Gaelic",,,"GunChleoc"
+E,"Slovak","Jose Riha",,
+E,"Spanish","Carlos Vives","mail@carlosvives.es",
+E,"Swedish","Niklas Grahn","raewolusjoon@yaoo.com",
+E,"Swedish","Henrik Rostedt","henrik.rostedt@gmail.com",
+E,"Ukrainian","Eugene V. Lyubimkin","jackyf.devel@gmail.com",
+E,"Ukrainian","Igor Paliychuk","mansonigor@gmail.com",
+E,"Ukrainian","Eugene Sakara","eresid@gmail.com",
+S,"Special thanks",,,
+E,,"Aleksey Andreev","blaknayabr@gmail.com",,
+E,,"Aleksander Rudalev","alexv@pomorsu.ru",,
+E,,"Natasha Korotaeva","layout@pisem.net",,
+E,,"Adam Higerd",,"ahigerd"
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/css/april1.css
--- a/QTfrontend/res/css/april1.css Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/res/css/april1.css Wed Jul 31 23:14:27 2019 +0200
@@ -1,30 +1,35 @@
/******************************************************************************
*
- * CSS-like definition of Qt frontend appearance
+ * CSS definition of Qt frontend appearance: April 1 style.
*
******************************************************************************
*
- * see http://doc.qt.nokia.com/4.5/stylesheet.html
+ * see https://doc.qt.io/qt-5/style-reference.html
*
******************************************************************************
*
* This file can be stored at different locations, but it will be read only
* once, based on first file found in this order:
*
- * /Data/css/qt.css
- * /css/qt.css
- * (:/res/css/qt.css)
+ * /Data/css/april1.css
+ * /css/april1.css
+ * (:/res/css/april1.css)
+ *
+ *****************************************************************************
+ *
+ * This file is based off qt.css with minimal changes. The altered parts are
+ * marked with “CUSTOM”.
*
*****************************************************************************/
-#infoButton
+#infoButton
{
border: transparent;
-background: transparent;
-width: 800px;
-min-width: 800px;
-qproperty-icon: url(":/res/TomatowarsTitle.png");
-qproperty-iconSize: 800px 165px;
+background: transparent;
+width: 800px; /* CUSTOM */
+min-width: 800px; /* CUSTOM */
+qproperty-icon: url(":/res/TomatowarsTitle.png"); /* CUSTOM */
+qproperty-iconSize: 800px 165px; /* CUSTOM */
}
HWForm,QDialog {
background-image: url(":/res/Background.png");
@@ -106,10 +111,6 @@
background-color: #150A61;
}
-QToolButton:pressed {
-background-color: #100744;
-}
-
QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser,
QSpinBox, QToolBox, QPlainTextEdit, QToolButton, #mapPreview, #labelLikeLineEdit {
border-radius: 10px;
@@ -175,6 +176,11 @@
QPushButton:pressed, QToolButton:pressed {
border-color: white;
+background-color: #344b1e;
+color: white;
+}
+TeamShowWidget QPushButton:pressed {
+background-color: #362c7a;
}
QPushButton:focus {
@@ -197,11 +203,12 @@
border-bottom-width: 0px;
border-color: #001d10;
border-style: solid;
-background-color: #00351d;
+background-color: #005F35;
padding: 4px;
}
QHeaderView::section:pressed {
-background-color: #00250d;
+background-color: #005A33;
+border-color: #FFFFFF;
}
QHeaderView::up-arrow {
image: url(":/res/sort_up.png");
@@ -231,6 +238,7 @@
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
QSpinBox::up-arrow {
@@ -251,8 +259,13 @@
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
+QSpinBox, QLineEdit {
+padding: 3px;
+min-height: 18px;
+}
QComboBox {
border-radius: 10px;
padding: 3px;
@@ -405,10 +418,80 @@
border-color: #F6CB1C;
}
-#hatList QScrollBar, #themeList QScrollBar {
-background-color: #130F2A;
-border-top-right-radius: 10px;
-border-bottom-right-radius: 10px;
+QScrollBar:vertical {
+border: none;
+color: #FFD902;
+background: #00321c;
+width: 15px;
+margin: 17px 0 17px 0;
+}
+QScrollBar:horizontal {
+border: none;
+color: #FFD902;
+background: #00321c;
+height: 15px;
+margin: 0 17px 0 17px;
+}
+QScrollBar::handle:vertical {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-height: 20px;
+}
+QScrollBar::handle:horizontal {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-width: 20px;
+}
+QScrollBar::handle:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+QScrollBar::add-line, QScrollBar::sub-line {
+border: 1px solid #005F35;
+border-radius: 2px;
+background: #00321c;
+subcontrol-origin: margin;
+}
+QScrollBar::add-line:vertical {
+height: 15px;
+subcontrol-position: bottom;
+}
+QScrollBar::sub-line:vertical {
+height: 15px;
+subcontrol-position: top;
+}
+QScrollBar::add-line:horizontal {
+width: 15px;
+subcontrol-position: right;
+}
+QScrollBar::sub-line:horizontal {
+width: 15px;
+subcontrol-position: left;
+}
+QScrollBar::add-line:pressed, QScrollBar::sub-line:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+
+QScrollBar::up-arrow {
+background-image: url(":/res/scroll_up.png");
+}
+QScrollBar::down-arrow {
+background-image: url(":/res/scroll_down.png");
+}
+QScrollBar::left-arrow {
+background-image: url(":/res/scroll_left.png");
+}
+QScrollBar::right-arrow {
+background-image: url(":/res/scroll_right.png");
+}
+QScrollBar::add-page, QScrollBar::sub-page {
+background: #00190F;
+}
+QScrollBar::add-page:pressed, QScrollBar::sub-page:pressed {
+background: #008751;
}
#hatList, #themeList {
@@ -437,7 +520,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
@@ -453,3 +536,36 @@
PageMultiplayer TeamSelWidget {
min-height: 500px;
}
+
+QProgressBar {
+border: 3px solid #FFCC00;
+background-color: #150A61;
+border-radius: 5px;
+text-align: center;
+color: #FFFFFF;
+}
+QProgressBar::chunk {
+background-color: #FF9B00;
+margin: 2px;
+}
+#keyBinderScrollArea {
+background: #130F2A;
+}
+#chatContainer {
+border-width: 0px;
+background-color: #ffcc00;
+border-radius: 10px;
+}
+#chatText {
+background-color: rgb(23, 11, 54);
+border-width: 0px;
+}
+#trainingList {
+border-style: none;
+padding-top: 6px;
+}
+#trainingList::item
+{
+padding-top: 2px;
+padding-bottom: 2px;
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/css/birthday.css
--- a/QTfrontend/res/css/birthday.css Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/res/css/birthday.css Wed Jul 31 23:14:27 2019 +0200
@@ -1,10 +1,10 @@
/******************************************************************************
*
- * CSS-like definition of Qt frontend appearance
+ * CSS-like definition of Qt frontend appearance: Hedgewars' Birthday.
*
******************************************************************************
*
- * see http://doc.qt.nokia.com/4.5/stylesheet.html
+ * see https://doc.qt.io/qt-5/style-reference.html
*
******************************************************************************
*
@@ -12,17 +12,35 @@
* once, based on first file found in this order:
*
* /Data/css/birthday.css
- * /css/birthday.css
+ * /css/brithday.css
* (:/res/css/birthday.css)
*
+ *****************************************************************************
+ *
+ * This file is based off qt.css with changes. The altered parts are
+ * marked with “CUSTOM”.
+ *
*****************************************************************************/
-HWForm,QDialog {
-background-image: url(":/res/BackgroundBirthday.png");
+#infoButton
+{
+border: transparent;
+background: transparent;
+}
+HWForm {
+background-image: url(":/res/BackgroundBirthday.png"); /* CUSTOM */
background-position: bottom center;
background-repeat: repeat-x;
-background-color: #100308;
+background-color: #100308; /* CUSTOM */
}
+QDialog{ /* CUSTOM */
+background-color: #100308; /* CUSTOM */
+} /* CUSTOM: This makes yellow text readable (because yellow text vs white moon) */
+#campaignInfo, #trainingInfo{ /* CUSTOM */
+background-color: rgba(20, 20, 20, 70%); /* CUSTOM */
+border-radius: 16px;
+padding: 6px;
+} /* CUSTOM */
* {
color: #ffcc00;
@@ -32,16 +50,36 @@
a { color:#c8c8ff; }
-QLineEdit, QListWidget, QListView, QTableView, QTextBrowser, QSpinBox, QComboBox,
+QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser, QSpinBox, QComboBox,
QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item, #labelLikeLineEdit {
-background-color: rgba(20, 20, 20, 70%);
+background-color: rgba(20, 20, 20, 70%); /* CUSTOM */
+}
+
+VertScrArea, QGraphicsView {
+border-style: solid; border-width: 2px; border-color: #cca300; border-radius: 3px;
+}
+#gameStatsView {
+border-color: #332816;
+}
+
+QSplitter::handle {
+background-image: url(":/res/splitter.png");
+background-clip: content;
+}
+QSplitter::handle:horizontal {
+width: 7px;
+background-repeat: repeat-y;
+}
+QSplitter::handle:vertical {
+height: 7px;
+background-repeat: repeat-x;
}
QComboBox::separator {
border: solid; border-width: 3px; border-color: #ffcc00;
}
-QPushButton, QListWidget, QListView, QTableView, QLineEdit, QHeaderView,
+QPushButton, QListWidget, QListView, QTableView, QTableWidget, QLineEdit,
QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit,
QComboBox QAbstractItemView, IconedGroupBox,
.QGroupBox, #gameStackContainer, TeamSelWidget, SelWeaponWidget,
@@ -56,33 +94,51 @@
border-color: yellow;
}
+TeamShowWidget QPushButton {
+icon-size: 48px;
+text-align: left;
+background-color: #0d0d0d; /* CUSTOM */
+color: orange;
+font: bold;
+border-width: 2px;
+margin: 6px 0px 6px 0px;
+}
+TeamShowWidget QPushButton:disabled {
+color: #a0a0a0;
+}
+
QToolButton {
-background-color: #11084A;
+background-color: #080808; /* CUSTOM */
}
QToolButton:hover {
background-color: #150A61;
}
-QToolButton:pressed {
-background-color: #100744;
-}
-
-QLineEdit, QListWidget, QListView, QTableView, QTextBrowser,
+QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser,
QSpinBox, QToolBox, QPlainTextEdit, QToolButton, #mapPreview, #labelLikeLineEdit {
border-radius: 10px;
}
#mapPreview {
-background-color: #0d0544;
+background-color: #0d0d0d; /* CUSTOM */
+}
+#mapPreview:disabled{
+border-color: #a0a0a0;
+background-color: #0d0d0d; /* CUSTOM */
+color: #ffffff;
}
QLineEdit, QLabel, QHeaderView, QListWidget, QListView, QTableView,
-QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView,
+QTableWidget, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView,
IconedGroupBox, .QGroupBox, #gameStackContainer, TeamSelWidget,
-SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit {
+SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit,
+#mapName {
font: bold 13px;
}
+.QLabel{
+background-color: transparent;
+}
SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected {
background-position: bottom center;
background-repeat: repeat-x;
@@ -94,6 +150,7 @@
border-radius: 16px;
background-color: rgba(20, 20, 20, 70%);
padding: 6px;
+margin-top: 4px;
}
/* Experimenting with PaintOnScreen and border-radius on IconedGroupBox children didn't work out well
IconedGroupBox QComboBox, IconedGroupBox QPushButton, IconedGroupBox QLineEdit,
@@ -110,10 +167,6 @@
border-top-left-radius: 0px;
}
-QLineEdit:disabled, QSpinBox:disabled {
-border-color: gray;
-}
-
GameCFGWidget {
border: none;
}
@@ -122,11 +175,16 @@
border-radius: 8px;
background-origin: margin;
background-position: top left;
-background-color: rgba(18, 42, 5, 70%);
+background-color: rgba(20, 20, 20, 70%); /* CUSTOM */
}
QPushButton:pressed, QToolButton:pressed {
border-color: white;
+background-color: rgba(40, 40, 40, 100%); /* CUSTOM */
+color: white;
+}
+TeamShowWidget QPushButton:pressed {
+background-color: #202020; /* CUSTOM */
}
QPushButton:focus {
@@ -134,12 +192,36 @@
}
QHeaderView {
-border-radius: 0;
-border-width: 0;
+background-color: #000000;
+border: solid;
border-bottom-width: 3px;
-background-color: #00351d;
+border-top-width: 0px;
+border-left-width: 0px;
+border-right-width: 0px;
+border-color: #ffcc00;
}
-QTableView {
+QHeaderView::section {
+border-left-width: 1px;
+border-right-width: 1px;
+border-top-width: 0;
+border-bottom-width: 0px;
+border-color: #5A5A5A;
+border-style: solid;
+background-color: #000000;
+padding: 4px;
+}
+QHeaderView::section:pressed {
+background-color: #333333;
+border-color: #FFFFFF;
+}
+QHeaderView::up-arrow {
+image: url(":/res/sort_up.png");
+}
+QHeaderView::down-arrow{
+image: url(":/res/sort_down.png");
+}
+
+QTableView, QTableWidget {
alternate-background-color: #2f213a;
gridline-color: transparent;
}
@@ -149,7 +231,7 @@
border-top-left-radius: 6px;
border-top-right-radius: 6px;
padding: 3px;
-background-color: #00351d;
+background-color: #000000; /* CUSTOM */
color: #ffcc00;
}
QTabBar::tab:selected {
@@ -160,22 +242,34 @@
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
QSpinBox::up-arrow {
image: url(":/res/spin_up.png");
}
+QSpinBox::up-arrow:disabled {
+image: url(":/res/spin_up_disabled.png");
+}
QSpinBox::down-arrow {
image: url(":/res/spin_down.png");
}
+QSpinBox::down-arrow:disabled {
+image: url(":/res/spin_down_disabled.png");
+}
QSpinBox::down-button {
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
+QSpinBox, QLineEdit {
+padding: 3px;
+min-height: 18px;
+}
QComboBox {
border-radius: 10px;
padding: 3px;
@@ -191,6 +285,9 @@
QComboBox::down-arrow {
image: url(":/res/dropdown.png");
}
+QComboBox::down-arrow:disabled {
+image: url(":/res/dropdown_disabled.png");
+}
VertScrArea {
background-position: bottom center;
@@ -207,15 +304,45 @@
subcontrol-position: top left;
text-align: left;
left: 15px;
-top: -4px;
}
QCheckBox::indicator:checked{
image: url(":/res/checked.png");
}
+QCheckBox::indicator:checked:hover{
+image: url(":/res/checkedHover.png");
+}
+QCheckBox::indicator:checked:pressed{
+image: url(":/res/checkedPressed.png");
+}
QCheckBox::indicator:unchecked{
image: url(":/res/unchecked.png");
}
+QCheckBox::indicator:unchecked:hover{
+image: url(":/res/uncheckedHover.png");
+}
+QCheckBox::indicator:unchecked:pressed{
+image: url(":/res/uncheckedPressed.png");
+}
+
+QRadioButton::indicator:checked{
+image: url(":/res/radioButtonChecked.png");
+}
+QRadioButton::indicator:checked:hover{
+image: url(":/res/radioButtonCheckedHover.png");
+}
+QRadioButton::indicator:checked:pressed{
+image: url(":/res/radioButtonCheckedPressed.png");
+}
+QRadioButton::indicator:unchecked{
+image: url(":/res/radioButtonUnchecked.png");
+}
+QRadioButton::indicator:unchecked:hover{
+image: url(":/res/radioButtonUncheckedHover.png");
+}
+QRadioButton::indicator:unchecked:pressed{
+image: url(":/res/radioButtonUncheckedPressed.png");
+}
.QWidget{
background: transparent;
@@ -250,12 +377,17 @@
}
QToolTip{
-background-color: #0d0544;
+background-color: #0d0d0d; /* CUSTOM */
border: 1px solid #ffcc00;
}
:disabled{
color: #a0a0a0;
+border-color: #a0a0a0;
+}
+QListWidget:item:selected:disabled, QListView:item:selected:disabled{
+color: rgba(13, 5, 68, 70%);
+background-color: #a0a0a0;
}
SquareLabel, ItemNum {
background-color: #000000;
@@ -266,6 +398,9 @@
margin: 2px 0px;
background-color: #ffcc00;
}
+QSlider::groove::horizontal:disabled {
+background-color: #a0a0a0;
+}
QSlider::handle::horizontal {
border: 0px;
@@ -275,6 +410,9 @@
height: 6px;
border-radius: 3px;
}
+QSlider::handle::horizontal:disabled {
+background-color: #a0a0a0;
+}
HatButton, ThemeButton {
text-align: left;
@@ -284,10 +422,80 @@
border-color: #F6CB1C;
}
-#hatList QScrollBar, #themeList QScrollBar {
-background-color: #130F2A;
-border-top-right-radius: 10px;
-border-bottom-right-radius: 10px;
+QScrollBar:vertical {
+border: none;
+color: #FFD902;
+background: black; /*CUSTOM */
+width: 15px;
+margin: 17px 0 17px 0;
+}
+QScrollBar:horizontal {
+border: none;
+color: #FFD902;
+background: black; /* CUSTOM */
+height: 15px;
+margin: 0 17px 0 17px;
+}
+QScrollBar::handle:vertical {
+background: #3d1b3f; /* CUSTOM */
+border: 1px solid #b465b8; /* CUSTOM */
+border-radius: 2px;
+min-height: 20px;
+}
+QScrollBar::handle:horizontal {
+background: #3d1b3f; /* CUSTOM */
+border: 1px solid #b465b8; /* CUSTOM */
+border-radius: 2px;
+min-width: 20px;
+}
+QScrollBar::handle:pressed {
+background: #622b66; /* CUSTOM */
+border-color: #FFFFFF;
+}
+QScrollBar::add-line, QScrollBar::sub-line {
+border: 1px solid #b465b8; /* CUSTOM */
+border-radius: 2px;
+background: #3d1b3f; /* CUSTOM */
+subcontrol-origin: margin;
+}
+QScrollBar::add-line:vertical {
+height: 15px;
+subcontrol-position: bottom;
+}
+QScrollBar::sub-line:vertical {
+height: 15px;
+subcontrol-position: top;
+}
+QScrollBar::add-line:horizontal {
+width: 15px;
+subcontrol-position: right;
+}
+QScrollBar::sub-line:horizontal {
+width: 15px;
+subcontrol-position: left;
+}
+QScrollBar::add-line:pressed, QScrollBar::sub-line:pressed {
+background: #622b66; /* CUSTOM */
+border-color: #FFFFFF;
+}
+
+QScrollBar::up-arrow {
+background-image: url(":/res/scroll_up.png");
+}
+QScrollBar::down-arrow {
+background-image: url(":/res/scroll_down.png");
+}
+QScrollBar::left-arrow {
+background-image: url(":/res/scroll_left.png");
+}
+QScrollBar::right-arrow {
+background-image: url(":/res/scroll_right.png");
+}
+QScrollBar::add-page, QScrollBar::sub-page {
+background: black; /* CUSTOM */
+}
+QScrollBar::add-page:pressed, QScrollBar::sub-page:pressed {
+background: #808080; /* CUSTOM */
}
#hatList, #themeList {
@@ -316,7 +524,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
@@ -332,3 +540,36 @@
PageMultiplayer TeamSelWidget {
min-height: 500px;
}
+
+QProgressBar {
+border: 3px solid #FFCC00;
+background-color: black; /* CUSTOM */
+border-radius: 5px;
+text-align: center;
+color: #FFFFFF;
+}
+QProgressBar::chunk {
+background-color: #FF9B00;
+margin: 2px;
+}
+#keyBinderScrollArea {
+background: #100308; /* CUSTOM */
+}
+#chatContainer {
+border-width: 0px;
+background-color: #ffcc00;
+border-radius: 10px;
+}
+#chatText {
+background-color: rgb(23, 11, 54);
+border-width: 0px;
+}
+#trainingList {
+border-style: none;
+padding-top: 6px;
+}
+#trainingList::item
+{
+padding-top: 2px;
+padding-bottom: 2px;
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/css/christmas.css
--- a/QTfrontend/res/css/christmas.css Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/res/css/christmas.css Wed Jul 31 23:14:27 2019 +0200
@@ -1,10 +1,10 @@
/******************************************************************************
*
- * CSS-like definition of Qt frontend appearance
+ * CSS definition of Qt frontend appearance: Christmas style.
*
******************************************************************************
*
- * see http://doc.qt.nokia.com/4.5/stylesheet.html
+ * see https://doc.qt.io/qt-5/style-reference.html
*
******************************************************************************
*
@@ -15,14 +15,27 @@
* /css/christmas.css
* (:/res/css/christmas.css)
*
+ *****************************************************************************
+ *
+ * This file is based off qt.css with minimal changes. The altered parts are
+ * marked with “CUSTOM”.
+ *
*****************************************************************************/
-HWForm,QDialog {
-background-image: url(":/res/BackgroundChristmas.png");
+#infoButton
+{
+border: transparent;
+background: transparent;
+}
+HWForm {
+background-image: url(":/res/BackgroundChristmas.png"); /* CUSTOM */
background-position: bottom center;
background-repeat: repeat-x;
-background-color: #0c0f28;
+background-color: #0c0f28; /* CUSTOM */
}
+QDialog{ /* CUSTOM */
+background-color: #0c0f28; /* CUSTOM */
+} /* CUSTOM */
* {
color: #ffcc00;
@@ -32,16 +45,36 @@
a { color:#c8c8ff; }
-QLineEdit, QListWidget, QListView, QTableView, QTextBrowser, QSpinBox, QComboBox,
+QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser, QSpinBox, QComboBox,
QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item, #labelLikeLineEdit {
background-color: rgba(13, 5, 68, 70%);
}
+VertScrArea, QGraphicsView {
+border-style: solid; border-width: 2px; border-color: #cca300; border-radius: 3px;
+}
+#gameStatsView {
+border-color: #332816;
+}
+
+QSplitter::handle {
+background-image: url(":/res/splitter.png");
+background-clip: content;
+}
+QSplitter::handle:horizontal {
+width: 7px;
+background-repeat: repeat-y;
+}
+QSplitter::handle:vertical {
+height: 7px;
+background-repeat: repeat-x;
+}
+
QComboBox::separator {
border: solid; border-width: 3px; border-color: #ffcc00;
}
-QPushButton, QListWidget, QListView, QTableView, QLineEdit, QHeaderView,
+QPushButton, QListWidget, QListView, QTableView, QTableWidget, QLineEdit,
QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit,
QComboBox QAbstractItemView, IconedGroupBox,
.QGroupBox, #gameStackContainer, TeamSelWidget, SelWeaponWidget,
@@ -56,6 +89,19 @@
border-color: yellow;
}
+TeamShowWidget QPushButton {
+icon-size: 48px;
+text-align: left;
+background-color: #0d0544;
+color: orange;
+font: bold;
+border-width: 2px;
+margin: 6px 0px 6px 0px;
+}
+TeamShowWidget QPushButton:disabled {
+color: #a0a0a0;
+}
+
QToolButton {
background-color: #11084A;
}
@@ -64,11 +110,7 @@
background-color: #150A61;
}
-QToolButton:pressed {
-background-color: #100744;
-}
-
-QLineEdit, QListWidget, QListView, QTableView, QTextBrowser,
+QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser,
QSpinBox, QToolBox, QPlainTextEdit, QToolButton, #mapPreview, #labelLikeLineEdit {
border-radius: 10px;
}
@@ -76,13 +118,22 @@
#mapPreview {
background-color: #0d0544;
}
+#mapPreview:disabled{
+border-color: #a0a0a0;
+background-color: #0d0544;
+color: #ffffff;
+}
QLineEdit, QLabel, QHeaderView, QListWidget, QListView, QTableView,
-QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView,
+QTableWidget, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView,
IconedGroupBox, .QGroupBox, #gameStackContainer, TeamSelWidget,
-SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit {
+SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit,
+#mapName {
font: bold 13px;
}
+.QLabel{
+background-color: transparent;
+}
SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected {
background-position: bottom center;
background-repeat: repeat-x;
@@ -94,6 +145,7 @@
border-radius: 16px;
background-color: rgba(13, 5, 68, 70%);
padding: 6px;
+margin-top: 4px;
}
/* Experimenting with PaintOnScreen and border-radius on IconedGroupBox children didn't work out well
IconedGroupBox QComboBox, IconedGroupBox QPushButton, IconedGroupBox QLineEdit,
@@ -110,10 +162,6 @@
border-top-left-radius: 0px;
}
-QLineEdit:disabled, QSpinBox:disabled {
-border-color: gray;
-}
-
GameCFGWidget {
border: none;
}
@@ -127,6 +175,11 @@
QPushButton:pressed, QToolButton:pressed {
border-color: white;
+background-color: #344b1e;
+color: white;
+}
+TeamShowWidget QPushButton:pressed {
+background-color: #362c7a;
}
QPushButton:focus {
@@ -134,12 +187,36 @@
}
QHeaderView {
-border-radius: 0;
-border-width: 0;
+background-color: #00351d;
+border: solid;
border-bottom-width: 3px;
-background-color: #00351d;
+border-top-width: 0px;
+border-left-width: 0px;
+border-right-width: 0px;
+border-color: #ffcc00;
}
-QTableView {
+QHeaderView::section {
+border-left-width: 1px;
+border-right-width: 1px;
+border-top-width: 0;
+border-bottom-width: 0px;
+border-color: #001d10;
+border-style: solid;
+background-color: #005F35;
+padding: 4px;
+}
+QHeaderView::section:pressed {
+background-color: #005A33;
+border-color: #FFFFFF;
+}
+QHeaderView::up-arrow {
+image: url(":/res/sort_up.png");
+}
+QHeaderView::down-arrow{
+image: url(":/res/sort_down.png");
+}
+
+QTableView, QTableWidget {
alternate-background-color: #2f213a;
gridline-color: transparent;
}
@@ -160,22 +237,34 @@
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
QSpinBox::up-arrow {
image: url(":/res/spin_up.png");
}
+QSpinBox::up-arrow:disabled {
+image: url(":/res/spin_up_disabled.png");
+}
QSpinBox::down-arrow {
image: url(":/res/spin_down.png");
}
+QSpinBox::down-arrow:disabled {
+image: url(":/res/spin_down_disabled.png");
+}
QSpinBox::down-button {
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
+QSpinBox, QLineEdit {
+padding: 3px;
+min-height: 18px;
+}
QComboBox {
border-radius: 10px;
padding: 3px;
@@ -191,6 +280,9 @@
QComboBox::down-arrow {
image: url(":/res/dropdown.png");
}
+QComboBox::down-arrow:disabled {
+image: url(":/res/dropdown_disabled.png");
+}
VertScrArea {
background-position: bottom center;
@@ -207,15 +299,45 @@
subcontrol-position: top left;
text-align: left;
left: 15px;
-top: -4px;
}
QCheckBox::indicator:checked{
image: url(":/res/checked.png");
}
+QCheckBox::indicator:checked:hover{
+image: url(":/res/checkedHover.png");
+}
+QCheckBox::indicator:checked:pressed{
+image: url(":/res/checkedPressed.png");
+}
QCheckBox::indicator:unchecked{
image: url(":/res/unchecked.png");
}
+QCheckBox::indicator:unchecked:hover{
+image: url(":/res/uncheckedHover.png");
+}
+QCheckBox::indicator:unchecked:pressed{
+image: url(":/res/uncheckedPressed.png");
+}
+
+QRadioButton::indicator:checked{
+image: url(":/res/radioButtonChecked.png");
+}
+QRadioButton::indicator:checked:hover{
+image: url(":/res/radioButtonCheckedHover.png");
+}
+QRadioButton::indicator:checked:pressed{
+image: url(":/res/radioButtonCheckedPressed.png");
+}
+QRadioButton::indicator:unchecked{
+image: url(":/res/radioButtonUnchecked.png");
+}
+QRadioButton::indicator:unchecked:hover{
+image: url(":/res/radioButtonUncheckedHover.png");
+}
+QRadioButton::indicator:unchecked:pressed{
+image: url(":/res/radioButtonUncheckedPressed.png");
+}
.QWidget{
background: transparent;
@@ -256,6 +378,11 @@
:disabled{
color: #a0a0a0;
+border-color: #a0a0a0;
+}
+QListWidget:item:selected:disabled, QListView:item:selected:disabled{
+color: rgba(13, 5, 68, 70%);
+background-color: #a0a0a0;
}
SquareLabel, ItemNum {
background-color: #000000;
@@ -266,6 +393,9 @@
margin: 2px 0px;
background-color: #ffcc00;
}
+QSlider::groove::horizontal:disabled {
+background-color: #a0a0a0;
+}
QSlider::handle::horizontal {
border: 0px;
@@ -275,6 +405,9 @@
height: 6px;
border-radius: 3px;
}
+QSlider::handle::horizontal:disabled {
+background-color: #a0a0a0;
+}
HatButton, ThemeButton {
text-align: left;
@@ -284,10 +417,80 @@
border-color: #F6CB1C;
}
-#hatList QScrollBar, #themeList QScrollBar {
-background-color: #130F2A;
-border-top-right-radius: 10px;
-border-bottom-right-radius: 10px;
+QScrollBar:vertical {
+border: none;
+color: #FFD902;
+background: #00321c;
+width: 15px;
+margin: 17px 0 17px 0;
+}
+QScrollBar:horizontal {
+border: none;
+color: #FFD902;
+background: #00321c;
+height: 15px;
+margin: 0 17px 0 17px;
+}
+QScrollBar::handle:vertical {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-height: 20px;
+}
+QScrollBar::handle:horizontal {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-width: 20px;
+}
+QScrollBar::handle:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+QScrollBar::add-line, QScrollBar::sub-line {
+border: 1px solid #005F35;
+border-radius: 2px;
+background: #00321c;
+subcontrol-origin: margin;
+}
+QScrollBar::add-line:vertical {
+height: 15px;
+subcontrol-position: bottom;
+}
+QScrollBar::sub-line:vertical {
+height: 15px;
+subcontrol-position: top;
+}
+QScrollBar::add-line:horizontal {
+width: 15px;
+subcontrol-position: right;
+}
+QScrollBar::sub-line:horizontal {
+width: 15px;
+subcontrol-position: left;
+}
+QScrollBar::add-line:pressed, QScrollBar::sub-line:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+
+QScrollBar::up-arrow {
+background-image: url(":/res/scroll_up.png");
+}
+QScrollBar::down-arrow {
+background-image: url(":/res/scroll_down.png");
+}
+QScrollBar::left-arrow {
+background-image: url(":/res/scroll_left.png");
+}
+QScrollBar::right-arrow {
+background-image: url(":/res/scroll_right.png");
+}
+QScrollBar::add-page, QScrollBar::sub-page {
+background: #00190F;
+}
+QScrollBar::add-page:pressed, QScrollBar::sub-page:pressed {
+background: #008751;
}
#hatList, #themeList {
@@ -316,7 +519,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
@@ -332,3 +535,36 @@
PageMultiplayer TeamSelWidget {
min-height: 500px;
}
+
+QProgressBar {
+border: 3px solid #FFCC00;
+background-color: #150A61;
+border-radius: 5px;
+text-align: center;
+color: #FFFFFF;
+}
+QProgressBar::chunk {
+background-color: #FF9B00;
+margin: 2px;
+}
+#keyBinderScrollArea {
+background: #130F2A;
+}
+#chatContainer {
+border-width: 0px;
+background-color: #ffcc00;
+border-radius: 10px;
+}
+#chatText {
+background-color: rgb(23, 11, 54);
+border-width: 0px;
+}
+#trainingList {
+border-style: none;
+padding-top: 6px;
+}
+#trainingList::item
+{
+padding-top: 2px;
+padding-bottom: 2px;
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/css/easter.css
--- a/QTfrontend/res/css/easter.css Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/res/css/easter.css Wed Jul 31 23:14:27 2019 +0200
@@ -1,10 +1,10 @@
/******************************************************************************
*
- * CSS-like definition of Qt frontend appearance
+ * CSS definition of Qt frontend appearance. Easter mode.
*
******************************************************************************
*
- * see http://doc.qt.nokia.com/4.5/stylesheet.html
+ * see https://doc.qt.io/qt-5/style-reference.html
*
******************************************************************************
*
@@ -15,10 +15,20 @@
* /css/easter.css
* (:/res/css/easter.css)
*
- *****************************************************************************/
+ ******************************************************************************
+ *
+ * This file is based off qt.css with minimal changes. The altered parts are
+ * marked with “CUSTOM”.
+ *
+ ******************************************************************************/
+#infoButton
+{
+border: transparent;
+background: transparent;
+}
HWForm,QDialog {
-background-image: url(":/res/BackgroundEaster.png");
+background-image: url(":/res/BackgroundEaster.png"); /* CUSTOM */
background-position: bottom center;
background-repeat: repeat-x;
background-color: #141250;
@@ -32,20 +42,40 @@
a { color:#c8c8ff; }
-QLineEdit, QListWidget, QListView, QTableView, QTextBrowser, QSpinBox, QComboBox,
-QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item {
+QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser, QSpinBox, QComboBox,
+QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item, #labelLikeLineEdit {
background-color: rgba(13, 5, 68, 70%);
}
+VertScrArea, QGraphicsView {
+border-style: solid; border-width: 2px; border-color: #cca300; border-radius: 3px;
+}
+#gameStatsView {
+border-color: #332816;
+}
+
+QSplitter::handle {
+background-image: url(":/res/splitter.png");
+background-clip: content;
+}
+QSplitter::handle:horizontal {
+width: 7px;
+background-repeat: repeat-y;
+}
+QSplitter::handle:vertical {
+height: 7px;
+background-repeat: repeat-x;
+}
+
QComboBox::separator {
border: solid; border-width: 3px; border-color: #ffcc00;
}
-QPushButton, QListWidget, QListView, QTableView, QLineEdit, QHeaderView,
+QPushButton, QListWidget, QListView, QTableView, QTableWidget, QLineEdit,
QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit,
QComboBox QAbstractItemView, IconedGroupBox,
-.QGroupBox, GameCFGWidget, TeamSelWidget, SelWeaponWidget,
-QTabWidget::pane, QTabBar::tab {
+.QGroupBox, #gameStackContainer, TeamSelWidget, SelWeaponWidget,
+QTabWidget::pane, QTabBar::tab, #mapPreview, #labelLikeLineEdit {
border: solid;
border-width: 3px;
border-color: #ffcc00;
@@ -56,28 +86,63 @@
border-color: yellow;
}
-QLineEdit, QListWidget, QListView,QTableView, QTextBrowser,
-QSpinBox, QToolBox, QPlainTextEdit {
+TeamShowWidget QPushButton {
+icon-size: 48px;
+text-align: left;
+background-color: #0d0544;
+color: orange;
+font: bold;
+border-width: 2px;
+margin: 6px 0px 6px 0px;
+}
+TeamShowWidget QPushButton:disabled {
+color: #a0a0a0;
+}
+
+QToolButton {
+background-color: #11084A;
+}
+
+QToolButton:hover {
+background-color: #150A61;
+}
+
+QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser,
+QSpinBox, QToolBox, QPlainTextEdit, QToolButton, #mapPreview, #labelLikeLineEdit {
border-radius: 10px;
}
+#mapPreview {
+background-color: #0d0544;
+}
+#mapPreview:disabled{
+border-color: #a0a0a0;
+background-color: #0d0544;
+color: #ffffff;
+}
+
QLineEdit, QLabel, QHeaderView, QListWidget, QListView, QTableView,
-QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView,
-IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget,
-SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit {
+QTableWidget, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView,
+IconedGroupBox, .QGroupBox, #gameStackContainer, TeamSelWidget,
+SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit,
+#mapName {
font: bold 13px;
}
+.QLabel{
+background-color: transparent;
+}
SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected {
background-position: bottom center;
background-repeat: repeat-x;
background-color: #000000;
}
-.QGroupBox,GameCFGWidget,TeamSelWidget,SelWeaponWidget {
+.QGroupBox, #gameStackContainer, TeamSelWidget, SelWeaponWidget {
background-position: bottom center;
background-repeat: repeat-x;
border-radius: 16px;
background-color: rgba(13, 5, 68, 70%);
padding: 6px;
+margin-top: 4px;
}
/* Experimenting with PaintOnScreen and border-radius on IconedGroupBox children didn't work out well
IconedGroupBox QComboBox, IconedGroupBox QPushButton, IconedGroupBox QLineEdit,
@@ -89,6 +154,14 @@
background-color: #130f2c;
}
+QTabWidget::pane {
+border-radius: 8px;
+border-top-left-radius: 0px;
+}
+
+GameCFGWidget {
+border: none;
+}
QPushButton {
border-radius: 8px;
@@ -97,60 +170,102 @@
background-color: rgba(18, 42, 5, 70%);
}
-QPushButton:pressed{
+QPushButton:pressed, QToolButton:pressed {
border-color: white;
+background-color: #344b1e;
+color: white;
+}
+TeamShowWidget QPushButton:pressed {
+background-color: #362c7a;
}
QPushButton:focus {
outline: none;
}
+QHeaderView {
+background-color: #00351d;
+border: solid;
+border-bottom-width: 3px;
+border-top-width: 0px;
+border-left-width: 0px;
+border-right-width: 0px;
+border-color: #ffcc00;
+}
+QHeaderView::section {
+border-left-width: 1px;
+border-right-width: 1px;
+border-top-width: 0;
+border-bottom-width: 0px;
+border-color: #001d10;
+border-style: solid;
+background-color: #005F35;
+padding: 4px;
+}
+QHeaderView::section:pressed {
+background-color: #005A33;
+border-color: #FFFFFF;
+}
+QHeaderView::up-arrow {
+image: url(":/res/sort_up.png");
+}
+QHeaderView::down-arrow{
+image: url(":/res/sort_down.png");
+}
-QHeaderView {
-border-radius: 0;
-border-width: 0;
-border-bottom-width: 3px;
-background-color: #00351d;
-}
-QTableView {
+QTableView, QTableWidget {
alternate-background-color: #2f213a;
gridline-color: transparent;
}
-
+QTabWidget::pane { top: -2px; }
QTabBar::tab {
-border-bottom-width: 0;
border-radius: 0;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
padding: 3px;
+background-color: #00351d;
+color: #ffcc00;
}
-QTabBar::tab:!selected {
-color: #0d0544;
-background-color: #ffcc00;
+QTabBar::tab:selected {
+border-bottom-color: #0d0544;
+border-bottom-width: 0;
}
QSpinBox::up-button{
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
QSpinBox::up-arrow {
image: url(":/res/spin_up.png");
}
+QSpinBox::up-arrow:disabled {
+image: url(":/res/spin_up_disabled.png");
+}
QSpinBox::down-arrow {
image: url(":/res/spin_down.png");
}
+QSpinBox::down-arrow:disabled {
+image: url(":/res/spin_down_disabled.png");
+}
QSpinBox::down-button {
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
+QSpinBox, QLineEdit {
+padding: 3px;
+min-height: 18px;
+}
QComboBox {
border-radius: 10px;
padding: 3px;
+height: 18px;
}
QComboBox:pressed{
border-color: white;
@@ -162,6 +277,9 @@
QComboBox::down-arrow {
image: url(":/res/dropdown.png");
}
+QComboBox::down-arrow:disabled {
+image: url(":/res/dropdown_disabled.png");
+}
VertScrArea {
background-position: bottom center;
@@ -178,15 +296,45 @@
subcontrol-position: top left;
text-align: left;
left: 15px;
-top: -4px;
}
QCheckBox::indicator:checked{
image: url(":/res/checked.png");
}
+QCheckBox::indicator:checked:hover{
+image: url(":/res/checkedHover.png");
+}
+QCheckBox::indicator:checked:pressed{
+image: url(":/res/checkedPressed.png");
+}
QCheckBox::indicator:unchecked{
image: url(":/res/unchecked.png");
}
+QCheckBox::indicator:unchecked:hover{
+image: url(":/res/uncheckedHover.png");
+}
+QCheckBox::indicator:unchecked:pressed{
+image: url(":/res/uncheckedPressed.png");
+}
+
+QRadioButton::indicator:checked{
+image: url(":/res/radioButtonChecked.png");
+}
+QRadioButton::indicator:checked:hover{
+image: url(":/res/radioButtonCheckedHover.png");
+}
+QRadioButton::indicator:checked:pressed{
+image: url(":/res/radioButtonCheckedPressed.png");
+}
+QRadioButton::indicator:unchecked{
+image: url(":/res/radioButtonUnchecked.png");
+}
+QRadioButton::indicator:unchecked:hover{
+image: url(":/res/radioButtonUncheckedHover.png");
+}
+QRadioButton::indicator:unchecked:pressed{
+image: url(":/res/radioButtonUncheckedPressed.png");
+}
.QWidget{
background: transparent;
@@ -227,6 +375,11 @@
:disabled{
color: #a0a0a0;
+border-color: #a0a0a0;
+}
+QListWidget:item:selected:disabled, QListView:item:selected:disabled{
+color: rgba(13, 5, 68, 70%);
+background-color: #a0a0a0;
}
SquareLabel, ItemNum {
background-color: #000000;
@@ -237,12 +390,177 @@
margin: 2px 0px;
background-color: #ffcc00;
}
+QSlider::groove::horizontal:disabled {
+background-color: #a0a0a0;
+}
QSlider::handle::horizontal {
border: 0px;
-margin: -2px 0px;
+margin: -8px 0px;
+background-color: #ffcc00;
+width: 12px;
+height: 6px;
border-radius: 3px;
-background-color: #ffcc00;
-width: 8px;
+}
+QSlider::handle::horizontal:disabled {
+background-color: #a0a0a0;
+}
+
+HatButton, ThemeButton {
+text-align: left;
+}
+
+#hatList, #hatList:hover, #themeList, #themeList:hover {
+border-color: #F6CB1C;
+}
+
+QScrollBar:vertical {
+border: none;
+color: #FFD902;
+background: #00321c;
+width: 15px;
+margin: 17px 0 17px 0;
+}
+QScrollBar:horizontal {
+border: none;
+color: #FFD902;
+background: #00321c;
+height: 15px;
+margin: 0 17px 0 17px;
+}
+QScrollBar::handle:vertical {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-height: 20px;
+}
+QScrollBar::handle:horizontal {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-width: 20px;
+}
+QScrollBar::handle:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+QScrollBar::add-line, QScrollBar::sub-line {
+border: 1px solid #005F35;
+border-radius: 2px;
+background: #00321c;
+subcontrol-origin: margin;
+}
+QScrollBar::add-line:vertical {
+height: 15px;
+subcontrol-position: bottom;
+}
+QScrollBar::sub-line:vertical {
+height: 15px;
+subcontrol-position: top;
+}
+QScrollBar::add-line:horizontal {
+width: 15px;
+subcontrol-position: right;
+}
+QScrollBar::sub-line:horizontal {
+width: 15px;
+subcontrol-position: left;
+}
+QScrollBar::add-line:pressed, QScrollBar::sub-line:pressed {
+background: #005a33;
+border-color: #FFFFFF;
}
+QScrollBar::up-arrow {
+background-image: url(":/res/scroll_up.png");
+}
+QScrollBar::down-arrow {
+background-image: url(":/res/scroll_down.png");
+}
+QScrollBar::left-arrow {
+background-image: url(":/res/scroll_left.png");
+}
+QScrollBar::right-arrow {
+background-image: url(":/res/scroll_right.png");
+}
+QScrollBar::add-page, QScrollBar::sub-page {
+background: #00190F;
+}
+QScrollBar::add-page:pressed, QScrollBar::sub-page:pressed {
+background: #008751;
+}
+#hatList, #themeList {
+border-color: #F6CB1C;
+border-width: 3px;
+border-style: solid;
+border-radius: 10px;
+border-top-left-radius: 0px;
+}
+
+#hatList::item, #themeList::item {
+background-color: #11084A;
+padding: 4px;
+border-radius: 10px;
+color: #ffcc00 !important;
+font: 8px;
+border-width: 2px;
+border-color: #11084A;
+}
+
+#hatList::item:hover, #themeList::item:hover {
+background-color: #150A61;
+}
+
+#hatList::item:selected, #themeList::item:selected {
+background-color: #150A61;
+}
+
+.QPushButton, .QPushButtonWithSound {
+padding: 3px 5px;
+}
+
+#gameCfgWidgetTabs {
+border-radius: 16px;
+border-top-left-radius: 0px;
+}
+
+TeamSelWidget, #gameStackContainer, #GBoxOptions {
+border-radius: 10px;
+}
+
+PageMultiplayer TeamSelWidget {
+min-height: 500px;
+}
+
+QProgressBar {
+border: 3px solid #FFCC00;
+background-color: #150A61;
+border-radius: 5px;
+text-align: center;
+color: #FFFFFF;
+}
+QProgressBar::chunk {
+background-color: #FF9B00;
+margin: 2px;
+}
+#keyBinderScrollArea {
+background: #130F2A;
+}
+#chatContainer {
+border-width: 0px;
+background-color: #ffcc00;
+border-radius: 10px;
+}
+#chatText {
+background-color: rgb(23, 11, 54);
+border-width: 0px;
+}
+#trainingList {
+border-style: none;
+padding-top: 6px;
+}
+#trainingList::item
+{
+padding-top: 2px;
+padding-bottom: 2px;
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/css/qt.css
--- a/QTfrontend/res/css/qt.css Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/res/css/qt.css Wed Jul 31 23:14:27 2019 +0200
@@ -1,10 +1,10 @@
/******************************************************************************
*
- * CSS-like definition of Qt frontend appearance
+ * CSS definition of Qt frontend appearance: Default style.
*
******************************************************************************
*
- * see http://doc.qt.nokia.com/4.5/stylesheet.html
+ * see https://doc.qt.io/qt-5/style-reference.html
*
******************************************************************************
*
@@ -93,6 +93,9 @@
TeamShowWidget QPushButton:disabled {
color: #a0a0a0;
}
+TeamShowWidget QPushButton:pressed {
+background-color: #362c7a;
+}
QToolButton {
background-color: #11084A;
@@ -102,10 +105,6 @@
background-color: #150A61;
}
-QToolButton:pressed {
-background-color: #100744;
-}
-
QLineEdit, QListWidget, QListView, QTableView, QTableWidget, QTextBrowser,
QSpinBox, QToolBox, QPlainTextEdit, QToolButton, #mapPreview, #labelLikeLineEdit {
border-radius: 10px;
@@ -171,6 +170,8 @@
QPushButton:pressed, QToolButton:pressed {
border-color: white;
+background-color: #344b1e;
+color: white;
}
QPushButton:focus {
@@ -191,13 +192,14 @@
border-right-width: 1px;
border-top-width: 0;
border-bottom-width: 0px;
-border-color: #001d10;
+border-color: #005F35;
border-style: solid;
background-color: #00351d;
padding: 4px;
}
QHeaderView::section:pressed {
-background-color: #00250d;
+background-color: #005A33;
+border-color: #FFFFFF;
}
QHeaderView::up-arrow {
image: url(":/res/sort_up.png");
@@ -227,6 +229,7 @@
background: transparent;
width: 16px;
height: 10px;
+padding-top: 1px;
}
QSpinBox::up-arrow {
@@ -247,8 +250,13 @@
background: transparent;
width: 16px;
height: 10px;
+padding-bottom: 1px;
}
+QSpinBox, QLineEdit {
+padding: 3px;
+min-height: 18px;
+}
QComboBox {
border-radius: 10px;
padding: 3px;
@@ -401,10 +409,80 @@
border-color: #F6CB1C;
}
-#hatList QScrollBar, #themeList QScrollBar {
-background-color: #130F2A;
-border-top-right-radius: 10px;
-border-bottom-right-radius: 10px;
+QScrollBar:vertical {
+border: none;
+color: #FFD902;
+background: #00321c;
+width: 15px;
+margin: 17px 0 17px 0;
+}
+QScrollBar:horizontal {
+border: none;
+color: #FFD902;
+background: #00321c;
+height: 15px;
+margin: 0 17px 0 17px;
+}
+QScrollBar::handle:vertical {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-height: 20px;
+}
+QScrollBar::handle:horizontal {
+background: #00321c;
+border: 1px solid #005F35;
+border-radius: 2px;
+min-width: 20px;
+}
+QScrollBar::handle:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+QScrollBar::add-line, QScrollBar::sub-line {
+border: 1px solid #005F35;
+border-radius: 2px;
+background: #00321c;
+subcontrol-origin: margin;
+}
+QScrollBar::add-line:vertical {
+height: 15px;
+subcontrol-position: bottom;
+}
+QScrollBar::sub-line:vertical {
+height: 15px;
+subcontrol-position: top;
+}
+QScrollBar::add-line:horizontal {
+width: 15px;
+subcontrol-position: right;
+}
+QScrollBar::sub-line:horizontal {
+width: 15px;
+subcontrol-position: left;
+}
+QScrollBar::add-line:pressed, QScrollBar::sub-line:pressed {
+background: #005a33;
+border-color: #FFFFFF;
+}
+
+QScrollBar::up-arrow {
+background-image: url(":/res/scroll_up.png");
+}
+QScrollBar::down-arrow {
+background-image: url(":/res/scroll_down.png");
+}
+QScrollBar::left-arrow {
+background-image: url(":/res/scroll_left.png");
+}
+QScrollBar::right-arrow {
+background-image: url(":/res/scroll_right.png");
+}
+QScrollBar::add-page, QScrollBar::sub-page {
+background: #00190F;
+}
+QScrollBar::add-page:pressed, QScrollBar::sub-page:pressed {
+background: #008751;
}
#hatList, #themeList {
@@ -433,7 +511,7 @@
background-color: #150A61;
}
-QDialogButtonBox QPushButton {
+.QPushButton, .QPushButtonWithSound {
padding: 3px 5px;
}
@@ -449,3 +527,36 @@
PageMultiplayer TeamSelWidget {
min-height: 500px;
}
+
+QProgressBar {
+border: 3px solid #FFCC00;
+background-color: #150A61;
+border-radius: 5px;
+text-align: center;
+color: #FFFFFF;
+}
+QProgressBar::chunk {
+background-color: #FF9B00;
+margin: 2px;
+}
+#keyBinderScrollArea {
+background: #130F2A;
+}
+#chatContainer {
+border-width: 0px;
+background-color: #ffcc00;
+border-radius: 10px;
+}
+#chatText {
+background-color: rgb(23, 11, 54);
+border-width: 0px;
+}
+#trainingList {
+border-style: none;
+padding-top: 6px;
+}
+#trainingList::item
+{
+padding-top: 2px;
+padding-bottom: 2px;
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/html/about.html
--- a/QTfrontend/res/html/about.html Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-
-
-
-Hedgewars - Authors
-
-
-
-
- Developers:
-
- Engine, frontend, net server: Andrey Korotaev <unC0Rr@gmail.com >
- Many frontend improvements: Igor Ulyanov <disinbox@gmail.com >
- Many engine and frontend improvements: Derek Pomery <nemo@m8y.org >
- Drill rocket, Ballgun, RC Plane weapons: Martin Boze <afffect@gmail.com >
- Mine number and time game settings: David A. Cuadrado <krawek@gmail.com >
- Frontend improvements: Martin Minarik <ttsmj@pokec.sk >
- Frontend improvements: Kristian Lehmann <email@thexception.net >
- Mac OS X/iPhone port, OpenGL-ES conversion: Vittorio Giovara <vittorio.giovara@gmail.com >
- Many engine and frontend improvements (and bugs): Richard Karolyi <sheepluva@ercatec.net >
- Gamepad and Lua integration: Mario Liebisch <mario.liebisch@gmail.com >
- Many engine improvements and graphics: Carlos Vives <mail@carlosvives.es >
- Maze maps: Henning Kühn <prg@cooco.de >
- Engine and frontend improvements: Henrik Rostedt <henrik.rostedt@gmail.com >
- Lua game modes and missions: John Lambert <redgrinner@gmail.com >
- Frontend improvements: Mayur Pawashe <zorgiepoo@gmail.com >
- Android port: Richard Deurwaarder <xeli@xelification.com >
- Android netplay, portability abstraction: Simeon Maxein <smaxein@googlemail.com >
- WebGL port, some pas2c and GLES2 work: Meng Xiangyun <xymengxy@gmail.com >
- Video recording: Stepan Podoskin <stepik-777@mail.ru >
- Campaign support, first campaign: Szabolcs Orbàn <szabibibi@gmail.com >
- Keybinds, feedback, maps and hats interfaces: Drew Gottlieb <gottlieb.drew@gmail.com >
- Login dialogs, frontend improvements: Ondrej Skopek <skopekondrej@gmail.com >
- Icegun weapon: Julia Struchenko <urbertar@gmail.com >
- iPhone/iPad ports: Anton Malmygin <antonc27@mail.ru >
- Battalion style: Anachron <Anachron14@gmx.de >
- Scripting, engine, frontend improvements, some missions: Wuzzy <Wuzzy2@mail.ru >
- Theme customization improvements: KoBeWi
- Theme music, engine and frontend improvements, graphics: Valentin Kraevskiy
-
-
- Art:
- John Dum <fizzy@gmail.com >
-
- Joshua Frese <joshfrese@gmail.com >
-
- Stanko Tadić <stanko@mfhinc.net >
-
- Julien Koesten <julienkoesten@aol.com >
-
- Joshua O'Sullivan <coheedftw@hotmail.co.uk >
-
- Nils Lück <nils.luck.design@gmail.com >
-
- Guillaume Englert <genglert@hybird.org >
-
-
CopherNeue <ppicondo.cvac@gmail.com >
-
- Hats: Trey Perry <tx.perry.j@gmail.com >
-
-
- Sounds:
-
- Hedgehogs voice: Stephen Alexander <ArmagonNo1@gmail.com >
-
- John Dum <fizzy@gmail.com >
-
- Jonatan Nilsson <jonatanfan@gmail.com >
-
- Daniel Martin <elhombresinremedio@gmail.com >
-
-
- Translations:
- Brazilian Portuguese: Romulo Fernandes Machado <abra185@gmail.com >
- Bulgarian: Svetoslav Stefanov
- Czech: Petr Řezáček <rezacek@gmail.com >
- Chinese: Jie Luo <lililjlj@gmail.com >
- English: Andrey Korotaev <unC0Rr@gmail.com >
- Finnish: Nina Kuisma <ninnnu@gmail.com >, Janne Uusitupa
- French: Antoine Turmel <geekshadow@gmail.com >, Clement Woitrain <sphrixclement@gmail.com >, Matisumi
- German: Peter Hüwe <PeterHuewe@gmx.de >, Mario Liebisch <mario.liebisch@gmail.com >, Richard Karolyi <sheepluva@ercatec.net >, Wuzzy <Wuzzy2@mail.ru >
- Greek: <talos_kriti@yahoo.gr >
- Italian: Luca Bonora <bonora.luca@gmail.com >, Marco Bresciani <m.bresciani@email.it >, Gianfranco Costamagna <costamagnagianfranco@yahoo.it >, Enrico <enricobe@hotmail.com >
- Japanese: ADAM Etienne <etienne.adam@gmail.com >, Marco Bresciani <m.bresciani@email.it >
- Korean: Anthony Bellew <anthonyreflected@gmail.com >
- Lithuanian: Lukas Urbonas <lukasu08@gmail.com >
- Polish: Maciej Mroziński <mynick2@o2.pl >, Wojciech Latkowski <magik17l@gmail.com >, Piotr Mitana, Maciej Górny, KoBeWi
- Portuguese: Fábio Canário <inufabie@gmail.com >
- Russian: Andrey Korotaev <unC0Rr@gmail.com >, Vitaly Novichkov <admin@wohlnet.ru >, Anton Malmygin <antonc27@mail.ru >, greno4ka
- Slovak: Jose Riha
- Spanish: Carlos Vives <mail@carlosvives.es >
- Swedish: Niklas Grahn <raewolusjoon@yaoo.com >, Henrik Rostedt <henrik.rostedt@gmail.com >
- Ukrainian: Eugene V. Lyubimkin <jackyf.devel@gmail.com >, Igor Paliychuk <mansonigor@gmail.com >, Eugene Sakara <eresid@gmail.com >
-
-
- Special thanks:
- Aleksey Andreev <blaknayabr@gmail.com >
- Aleksander Rudalev <alexv@pomorsu.ru >
- Natasha Korotaeva <layout@pisem.net >
- Adam Higerd (aka ahigerd at FreeNode)
-
-
-
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/keyconflict.png
Binary file QTfrontend/res/keyconflict.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/keyconflict_selected.png
Binary file QTfrontend/res/keyconflict_selected.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/scroll_down.png
Binary file QTfrontend/res/scroll_down.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/scroll_left.png
Binary file QTfrontend/res/scroll_left.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/scroll_right.png
Binary file QTfrontend/res/scroll_right.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/scroll_up.png
Binary file QTfrontend/res/scroll_up.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/res/splash.png
Binary file QTfrontend/res/splash.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/sdlkeys.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/sdlkeys.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,195 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2015 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sdlkeys.h"
+
+#include
+
+char sdlkeys[1024][2][128] =
+{
+ // Mouse
+ {"mousel", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Left button")},
+ {"mousem", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Middle button")},
+ {"mouser", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Right button")},
+ {"mousex1", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: X1 button ")},
+ {"mousex2", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: X2 button")},
+ {"wheelup", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Wheel up")},
+ {"wheeldown", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Wheel down")},
+
+ // Keyboard
+ {"backspace", QT_TRANSLATE_NOOP("binds (keys)", "Backspace")},
+ {"tab", QT_TRANSLATE_NOOP("binds (keys)", "Tab")},
+ {"clear", QT_TRANSLATE_NOOP("binds (keys)", "Clear")},
+ {"return", QT_TRANSLATE_NOOP("binds (keys)", "Return")},
+ {"pause", QT_TRANSLATE_NOOP("binds (keys)", "Pause")},
+ {"escape", QT_TRANSLATE_NOOP("binds (keys)", "Escape")},
+ {"space", QT_TRANSLATE_NOOP("binds (keys)", "Space")},
+ {"'", "'"},
+ {",", ","},
+ {"-", "-"},
+ {".", "."},
+ {"/", "/"},
+ {"0", "0"},
+ {"1", "1"},
+ {"2", "2"},
+ {"3", "3"},
+ {"4", "4"},
+ {"5", "5"},
+ {"6", "6"},
+ {"7", "7"},
+ {"8", "8"},
+ {"9", "9"},
+ {";", ";"},
+ {"[", "["},
+ {"\\", "\\"},
+ {"]", "]"},
+ {"`", "`"},
+ {"a", "A"},
+ {"b", "B"},
+ {"c", "C"},
+ {"d", "D"},
+ {"e", "E"},
+ {"f", "F"},
+ {"g", "G"},
+ {"h", "H"},
+ {"i", "I"},
+ {"j", "J"},
+ {"k", "K"},
+ {"l", "L"},
+ {"m", "M"},
+ {"n", "N"},
+ {"o", "O"},
+ {"p", "P"},
+ {"q", "Q"},
+ {"r", "R"},
+ {"s", "S"},
+ {"t", "T"},
+ {"u", "U"},
+ {"v", "V"},
+ {"w", "W"},
+ {"x", "X"},
+ {"y", "Y"},
+ {"z", "Z"},
+ {"keypad_0", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 0")},
+ {"keypad_1", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 1")},
+ {"keypad_2", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 2")},
+ {"keypad_3", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 3")},
+ {"keypad_4", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 4")},
+ {"keypad_5", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 5")},
+ {"keypad_6", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 6")},
+ {"keypad_7", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 7")},
+ {"keypad_8", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 8")},
+ {"keypad_9", QT_TRANSLATE_NOOP("binds (keys)", "Keypad 9")},
+ {"keypad_.", QT_TRANSLATE_NOOP("binds (keys)", "Keypad .")},
+ {"keypad_/", QT_TRANSLATE_NOOP("binds (keys)", "Keypad /")},
+ {"keypad_*", QT_TRANSLATE_NOOP("binds (keys)", "Keypad *")},
+ {"keypad_-", QT_TRANSLATE_NOOP("binds (keys)", "Keypad -")},
+ {"keypad_+", QT_TRANSLATE_NOOP("binds (keys)", "Keypad +")},
+ {"keypad_enter", QT_TRANSLATE_NOOP("binds (keys)", "Keypad Enter")},
+ {"up", QT_TRANSLATE_NOOP("binds (keys)", "Up")},
+ {"down", QT_TRANSLATE_NOOP("binds (keys)", "Down")},
+ {"right", QT_TRANSLATE_NOOP("binds (keys)", "Right")},
+ {"left", QT_TRANSLATE_NOOP("binds (keys)", "Left")},
+ {"insert", QT_TRANSLATE_NOOP("binds (keys)", "Insert")},
+ {"delete", QT_TRANSLATE_NOOP("binds (keys)", "Delete")},
+ {"home", QT_TRANSLATE_NOOP("binds (keys)", "Home")},
+ {"end", QT_TRANSLATE_NOOP("binds (keys)", "End")},
+ {"pageup", QT_TRANSLATE_NOOP("binds (keys)", "PageUp")},
+ {"pagedown", QT_TRANSLATE_NOOP("binds (keys)", "PageDown")},
+ {"f1", "F1"},
+ {"f2", "F2"},
+ {"f3", "F3"},
+ {"f4", "F4"},
+ {"f5", "F5"},
+ {"f6", "F6"},
+ {"f7", "F7"},
+ {"f8", "F8"},
+ {"f9", "F9"},
+ {"f10", "F10"},
+ {"f11", "F11"},
+ {"f12", "F12"},
+ {"f13", "F13"},
+ {"f14", "F14"},
+ {"f15", "F15"},
+ {"numlock", QT_TRANSLATE_NOOP("binds (keys)", "Numlock")},
+ {"capslock", QT_TRANSLATE_NOOP("binds (keys)", "CapsLock")},
+ {"scrolllock", QT_TRANSLATE_NOOP("binds (keys)", "ScrollLock")},
+ {"menu", QT_TRANSLATE_NOOP("binds (keys)", "Menu")},
+ {"right_shift", QT_TRANSLATE_NOOP("binds (keys)", "Right Shift")},
+ {"left_shift", QT_TRANSLATE_NOOP("binds (keys)", "Left Shift")},
+ {"right_ctrl", QT_TRANSLATE_NOOP("binds (keys)", "Right Ctrl")},
+ {"left_ctrl", QT_TRANSLATE_NOOP("binds (keys)", "Left Ctrl")},
+ {"right_alt", QT_TRANSLATE_NOOP("binds (keys)", "Right Alt")},
+ {"left_alt", QT_TRANSLATE_NOOP("binds (keys)", "Left Alt")},
+ //: Windows key / Command key / Meta key /Super key (right)
+ {"right_gui", QT_TRANSLATE_NOOP("binds (keys)", "Right GUI")},
+ //: Windows key / Command key / Meta key /Super key (left)
+ {"left_gui", QT_TRANSLATE_NOOP("binds (keys)", "Left GUI")}
+};
+
+// helper list for sdlkeys. true for keyboard keys, false for other
+bool sdlkeys_iskeyboard[1024];
+
+// button name definitions for Microsoft's XBox360 controller
+// don't modify button order!
+char xb360buttons[10][128] =
+{
+ QT_TRANSLATE_NOOP("binds (keys)", "A button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "B button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "X button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Y button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "LB button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "RB button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Back button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Start button"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Left stick"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Right stick")
+};
+
+// axis name definitions for Microsoft's XBox360 controller
+// don't modify axis order!
+char xbox360axes[][128] =
+{
+ QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Right)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Left)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Down)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Up)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Left trigger"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Right trigger"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Down)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Up)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Right)"),
+ QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Left)"),
+};
+char xb360dpad[128] = QT_TRANSLATE_NOOP("binds (keys)", "D-pad");
+
+// Generic controller binding names
+//: Game controller axis direction. %1 = axis number, %2 = direction
+char controlleraxis[128] = QT_TRANSLATE_NOOP("binds (keys)", "Axis %1 %2");
+//: Game controller button. %1 = button number
+char controllerbutton[128] = QT_TRANSLATE_NOOP("binds (keys)", "Button %1");
+//: Game controller D-pad button. %1 = D-pad number, %2 = direction
+char controllerhat[128] = QT_TRANSLATE_NOOP("binds (keys)", "D-pad %1 %2");
+char controllerup[128] = QT_TRANSLATE_NOOP("binds (keys)", "Up");
+char controllerdown[128] = QT_TRANSLATE_NOOP("binds (keys)", "Down");
+char controllerleft[128] = QT_TRANSLATE_NOOP("binds (keys)", "Left");
+char controllerright[128] = QT_TRANSLATE_NOOP("binds (keys)", "Right");
+
+//: Special entry in key selection when an action has no control assigned
+char unboundcontrol[128] = QT_TRANSLATE_NOOP("binds (keys)", "(Don't use)");
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/sdlkeys.h
--- a/QTfrontend/sdlkeys.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/sdlkeys.h Wed Jul 31 23:14:27 2019 +0200
@@ -16,167 +16,21 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-char sdlkeys[1024][2][128] =
-{
- {"mousel", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Left button")},
- {"mousem", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Middle button")},
- {"mouser", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Right button")},
- {"wheelup", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Wheel up")},
- {"wheeldown", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Wheel down")},
- {"backspace", QT_TRANSLATE_NOOP("binds (keys)", "Backspace")},
- {"tab", QT_TRANSLATE_NOOP("binds (keys)", "Tab")},
- {"clear", QT_TRANSLATE_NOOP("binds (keys)", "Clear")},
- {"return", QT_TRANSLATE_NOOP("binds (keys)", "Return")},
- {"pause", QT_TRANSLATE_NOOP("binds (keys)", "Pause")},
- {"escape", QT_TRANSLATE_NOOP("binds (keys)", "Escape")},
- {"space", QT_TRANSLATE_NOOP("binds (keys)", "Space")},
- {"!", "!"},
- {"\"", "\""},
- {"#", "#"},
- {"$", "$"},
- {"&", "&"},
- {"'", "'"},
- {"(", "("},
- {")", ")"},
- {"*", "*"},
- {"+", "+"},
- {",", ","},
- {"-", "-"},
- {".", "."},
- {"/", "/"},
- {"0", "0"},
- {"1", "1"},
- {"2", "2"},
- {"3", "3"},
- {"4", "4"},
- {"5", "5"},
- {"6", "6"},
- {"7", "7"},
- {"8", "8"},
- {"9", "9"},
- {":", ":"},
- {";", ";"},
- {"<", "<"},
- {"=", "="},
- {">", ">"},
- {"?", "?"},
- {"@", "@"},
- {"[", "["},
- {"\\", "\\"},
- {"]", "]"},
- {"^", "^"},
- {"_", "_"},
- {"`", "`"},
- {"a", "A"},
- {"b", "B"},
- {"c", "C"},
- {"d", "D"},
- {"e", "E"},
- {"f", "F"},
- {"g", "G"},
- {"h", "H"},
- {"i", "I"},
- {"j", "J"},
- {"k", "K"},
- {"l", "L"},
- {"m", "M"},
- {"n", "N"},
- {"o", "O"},
- {"p", "P"},
- {"q", "Q"},
- {"r", "R"},
- {"s", "S"},
- {"t", "T"},
- {"u", "U"},
- {"v", "V"},
- {"w", "W"},
- {"x", "X"},
- {"y", "Y"},
- {"z", "Z"},
- {"delete", QT_TRANSLATE_NOOP("binds (keys)", "Delete")},
- {"keypad_0", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 0")},
- {"keypad_1", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 1")},
- {"keypad_2", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 2")},
- {"keypad_3", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 3")},
- {"keypad_4", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 4")},
- {"keypad_5", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 5")},
- {"keypad_6", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 6")},
- {"keypad_7", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 7")},
- {"keypad_8", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 8")},
- {"keypad_9", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 9")},
- {"keypad_.", QT_TRANSLATE_NOOP("binds (keys)", "Numpad .")},
- {"keypad_/", QT_TRANSLATE_NOOP("binds (keys)", "Numpad /")},
- {"keypad_*", QT_TRANSLATE_NOOP("binds (keys)", "Numpad *")},
- {"keypad_-", QT_TRANSLATE_NOOP("binds (keys)", "Numpad -")},
- {"keypad_+", QT_TRANSLATE_NOOP("binds (keys)", "Numpad +")},
- {"enter", QT_TRANSLATE_NOOP("binds (keys)", "Enter")},
- {"equals", QT_TRANSLATE_NOOP("binds (keys)", "Equals")},
- {"up", QT_TRANSLATE_NOOP("binds (keys)", "Up")},
- {"down", QT_TRANSLATE_NOOP("binds (keys)", "Down")},
- {"right", QT_TRANSLATE_NOOP("binds (keys)", "Right")},
- {"left", QT_TRANSLATE_NOOP("binds (keys)", "Left")},
- {"insert", QT_TRANSLATE_NOOP("binds (keys)", "Insert")},
- {"home", QT_TRANSLATE_NOOP("binds (keys)", "Home")},
- {"end", QT_TRANSLATE_NOOP("binds (keys)", "End")},
- {"page_up", QT_TRANSLATE_NOOP("binds (keys)", "Page up")},
- {"page_down", QT_TRANSLATE_NOOP("binds (keys)", "Page down")},
- {"f1", "F1"},
- {"f2", "F2"},
- {"f3", "F3"},
- {"f4", "F4"},
- {"f5", "F5"},
- {"f6", "F6"},
- {"f7", "F7"},
- {"f8", "F8"},
- {"f9", "F9"},
- {"f10", "F10"},
- {"f11", "F11"},
- {"f12", "F12"},
- {"f13", "F13"},
- {"f14", "F14"},
- {"f15", "F15"},
- {"numlock", QT_TRANSLATE_NOOP("binds (keys)", "Num lock")},
- {"caps_lock", QT_TRANSLATE_NOOP("binds (keys)", "Caps lock")},
- {"scroll_lock", QT_TRANSLATE_NOOP("binds (keys)", "Scroll lock")},
- {"right_shift", QT_TRANSLATE_NOOP("binds (keys)", "Right shift")},
- {"left_shift", QT_TRANSLATE_NOOP("binds (keys)", "Left shift")},
- {"right_ctrl", QT_TRANSLATE_NOOP("binds (keys)", "Right ctrl")},
- {"left_ctrl", QT_TRANSLATE_NOOP("binds (keys)", "Left ctrl")},
- {"right_alt", QT_TRANSLATE_NOOP("binds (keys)", "Right alt")},
- {"left_alt", QT_TRANSLATE_NOOP("binds (keys)", "Left alt")},
- {"right_meta", QT_TRANSLATE_NOOP("binds (keys)", "Right meta")},
- {"left_meta", QT_TRANSLATE_NOOP("binds (keys)", "Left meta")}
-};
+#ifndef SDLKEYS_H
+#define SDLKEYS_H
-// button name definitions for Microsoft's XBox360 controller
-// don't modify button order!
-char xb360buttons[][128] =
-{
- QT_TRANSLATE_NOOP("binds (keys)", "A button"),
- QT_TRANSLATE_NOOP("binds (keys)", "B button"),
- QT_TRANSLATE_NOOP("binds (keys)", "X button"),
- QT_TRANSLATE_NOOP("binds (keys)", "Y button"),
- QT_TRANSLATE_NOOP("binds (keys)", "LB button"),
- QT_TRANSLATE_NOOP("binds (keys)", "RB button"),
- QT_TRANSLATE_NOOP("binds (keys)", "Back button"),
- QT_TRANSLATE_NOOP("binds (keys)", "Start button"),
- QT_TRANSLATE_NOOP("binds (keys)", "Left stick"),
- QT_TRANSLATE_NOOP("binds (keys)", "Right stick")
-};
+extern char sdlkeys[1024][2][128];
+extern bool sdlkeys_iskeyboard[1024];
+extern char xb360buttons[10][128];
+extern char xbox360axes[10][128];
+extern char xb360dpad[128];
+extern char controlleraxis[128];
+extern char controllerbutton[128];
+extern char controllerhat[128];
+extern char controllerup[128];
+extern char controllerdown[128];
+extern char controllerleft[128];
+extern char controllerright[128];
+extern char unboundcontrol[128];
-// axis name definitions for Microsoft's XBox360 controller
-// don't modify axis order!
-char xbox360axes[][128] =
-{
- QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Right)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Left)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Down)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Up)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Left trigger"),
- QT_TRANSLATE_NOOP("binds (keys)", "Right trigger"),
- QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Down)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Up)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Right)"),
- QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Left)"),
-};
-char xb360dpad[128] = QT_TRANSLATE_NOOP("binds (keys)", "DPad");
+#endif
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/team.cpp
--- a/QTfrontend/team.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/team.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -35,6 +35,7 @@
, m_difficulty(0)
, m_numHedgehogs(4)
, m_isNetTeam(false)
+ , m_isMissionTeam(false)
{
m_name = teamname;
OldTeamName = m_name;
@@ -54,9 +55,6 @@
m_binds[i].action = cbinds[i].action;
m_binds[i].strbind = QString();
}
- m_rounds = 0;
- m_wins = 0;
- m_campaignProgress = 0;
m_color = 0;
}
@@ -64,6 +62,7 @@
QObject(0)
, m_numHedgehogs(4)
, m_isNetTeam(true)
+ , m_isMissionTeam(false)
{
// net teams are configured from QStringList
if(strLst.size() != 23) throw HWTeamConstructException();
@@ -83,9 +82,6 @@
// Checking net teams is probably pointless, but can't hurt.
if (m_hedgehogs[i].Hat.isEmpty()) m_hedgehogs[i].Hat = "NoHat";
}
- m_rounds = 0;
- m_wins = 0;
- m_campaignProgress = 0;
m_color = 0;
}
@@ -94,6 +90,7 @@
, m_difficulty(0)
, m_numHedgehogs(4)
, m_isNetTeam(false)
+ , m_isMissionTeam(false)
{
m_name = QString("Team");
for (int i = 0; i < HEDGEHOGS_PER_TEAM; i++)
@@ -114,9 +111,6 @@
m_binds[i].action = cbinds[i].action;
m_binds[i].strbind = QString();
}
- m_rounds = 0;
- m_wins = 0;
- m_campaignProgress = 0;
m_color = 0;
}
@@ -134,10 +128,8 @@
, m_numHedgehogs(other.m_numHedgehogs)
, m_color(other.m_color)
, m_isNetTeam(other.m_isNetTeam)
+ , m_isMissionTeam(other.m_isMissionTeam)
, m_owner(other.m_owner)
- , m_campaignProgress(other.m_campaignProgress)
- , m_rounds(other.m_rounds)
- , m_wins(other.m_wins)
// , AchievementProgress(other.AchievementProgress)
{
@@ -160,10 +152,8 @@
m_color = other.m_color;
m_isNetTeam = other.m_isNetTeam;
m_owner = other.m_owner;
- m_campaignProgress = other.m_campaignProgress;
- m_rounds = other.m_rounds;
- m_wins = other.m_wins;
m_color = other.m_color;
+ m_isMissionTeam = other.m_isMissionTeam;
}
return *this;
@@ -179,18 +169,11 @@
m_voicepack = teamfile.value("Team/Voicepack", "Default").toString();
m_flag = teamfile.value("Team/Flag", "hedgewars").toString();
m_difficulty = teamfile.value("Team/Difficulty", 0).toInt();
- m_rounds = teamfile.value("Team/Rounds", 0).toInt();
- m_wins = teamfile.value("Team/Wins", 0).toInt();
- m_campaignProgress = teamfile.value("Team/CampaignProgress", 0).toInt();
for(int i = 0; i < HEDGEHOGS_PER_TEAM; i++)
{
QString hh = QString("Hedgehog%1/").arg(i);
m_hedgehogs[i].Name = teamfile.value(hh + "Name", QString("Hedgehog %1").arg(i+1)).toString();
m_hedgehogs[i].Hat = teamfile.value(hh + "Hat", "NoHat").toString();
- m_hedgehogs[i].Rounds = teamfile.value(hh + "Rounds", 0).toInt();
- m_hedgehogs[i].Kills = teamfile.value(hh + "Kills", 0).toInt();
- m_hedgehogs[i].Deaths = teamfile.value(hh + "Deaths", 0).toInt();
- m_hedgehogs[i].Suicides = teamfile.value(hh + "Suicides", 0).toInt();
}
for(int i = 0; i < BINDS_NUMBER; i++)
m_binds[i].strbind = teamfile.value(QString("Binds/%1").arg(m_binds[i].action), QString()).toString();
@@ -244,22 +227,18 @@
teamfile.setValue("Team/Voicepack", m_voicepack);
teamfile.setValue("Team/Flag", m_flag);
teamfile.setValue("Team/Difficulty", m_difficulty);
- teamfile.setValue("Team/Rounds", m_rounds);
- teamfile.setValue("Team/Wins", m_wins);
- teamfile.setValue("Team/CampaignProgress", m_campaignProgress);
for(int i = 0; i < HEDGEHOGS_PER_TEAM; i++)
{
QString hh = QString("Hedgehog%1/").arg(i);
teamfile.setValue(hh + "Name", m_hedgehogs[i].Name);
teamfile.setValue(hh + "Hat", m_hedgehogs[i].Hat);
- teamfile.setValue(hh + "Rounds", m_hedgehogs[i].Rounds);
- teamfile.setValue(hh + "Kills", m_hedgehogs[i].Kills);
- teamfile.setValue(hh + "Deaths", m_hedgehogs[i].Deaths);
- teamfile.setValue(hh + "Suicides", m_hedgehogs[i].Suicides);
}
for(int i = 0; i < BINDS_NUMBER; i++)
- teamfile.setValue(QString("Binds/%1").arg(m_binds[i].action), m_binds[i].strbind);
+ {
+ if(QString(m_binds[i].action) != QString("!MULTI"))
+ teamfile.setValue(QString("Binds/%1").arg(m_binds[i].action), m_binds[i].strbind);
+ }
for(int i = 0; i < MAX_ACHIEVEMENTS; i++)
if(achievements[i][0][0])
teamfile.setValue(QString("Achievements/%1").arg(achievements[i][0]), AchievementProgress[i]);
@@ -272,12 +251,25 @@
QStringList HWTeam::teamGameConfig(quint32 InitHealth) const
{
QStringList sl;
+ QString cmdAddHog = "eaddhh";
+
if (m_isNetTeam)
{
sl.push_back(QString("eaddteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(QString(QCryptographicHash::hash(m_owner.toUtf8(), QCryptographicHash::Md5).toHex())));
sl.push_back("erdriven");
}
- else sl.push_back(QString("eaddteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(playerHash));
+ else
+ {
+ if (m_isMissionTeam)
+ {
+ sl.push_back(QString("esetmissteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(playerHash));
+ cmdAddHog = "eaddmisshh";
+ }
+ else
+ {
+ sl.push_back(QString("eaddteam %3 %1 %2").arg(qcolor().rgb() & 0xffffff).arg(m_name).arg(playerHash));
+ }
+ }
sl.push_back(QString("egrave " + m_grave));
sl.push_back(QString("efort " + m_fort));
@@ -289,7 +281,7 @@
for (int t = 0; t < m_numHedgehogs; t++)
{
- sl.push_back(QString("eaddhh %1 %2 %3")
+ sl.push_back(QString(cmdAddHog + " %1 %2 %3")
.arg(QString::number(m_difficulty),
QString::number(InitHealth),
m_hedgehogs[t].Name));
@@ -310,6 +302,15 @@
return m_isNetTeam;
}
+void HWTeam::setMissionTeam(bool isMissionTeam)
+{
+ m_isMissionTeam = isMissionTeam;
+}
+
+bool HWTeam::isMissionTeam() const
+{
+ return m_isMissionTeam;
+}
bool HWTeam::operator==(const HWTeam& t1) const
{
@@ -351,6 +352,10 @@
return m_owner;
}
+void HWTeam::setOwner(const QString & owner)
+{
+ m_owner = owner;
+}
// difficulty
@@ -430,13 +435,6 @@
return m_voicepack;
}
-
-// campaignProgress - getter
-unsigned int HWTeam::campaignProgress() const
-{
- return m_campaignProgress;
-};
-
// amount of hedgehogs
unsigned char HWTeam::numHedgehogs() const
{
@@ -447,15 +445,3 @@
m_numHedgehogs = num;
}
-
-
-// rounds+wins - incrementors
-void HWTeam::incRounds()
-{
- m_rounds++;
-}
-void HWTeam::incWins()
-{
- m_wins++;
-}
-
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/team.h
--- a/QTfrontend/team.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/team.h Wed Jul 31 23:14:27 2019 +0200
@@ -38,9 +38,8 @@
{
QString Name;
QString Hat;
- int Rounds, Kills, Deaths, Suicides;
- HWHog() : Rounds(0), Kills(0), Deaths(0), Suicides(0){}
+ HWHog(){}
};
// class representing a team
@@ -65,7 +64,6 @@
bool wouldOverwriteOtherFile();
// attribute getters
- unsigned int campaignProgress() const;
int color() const;
QColor qcolor() const;
unsigned int difficulty() const;
@@ -74,10 +72,12 @@
QString grave() const;
const HWHog & hedgehog(unsigned int idx) const;
bool isNetTeam() const;
+ bool isMissionTeam() const;
QString keyBind(unsigned int idx) const;
QString name() const;
unsigned char numHedgehogs() const;
QString owner() const;
+ void setOwner(const QString & owner);
QString voicepack() const;
// attribute setters
@@ -91,10 +91,7 @@
void setNumHedgehogs(unsigned char num);
void setVoicepack(const QString & voicepack);
void setNetTeam(bool isNetTeam);
-
- // increments for statistical info
- void incRounds();
- void incWins();
+ void setMissionTeam(bool isMissionTeam);
// convert team info into strings for further computation
QStringList teamGameConfig(quint32 InitHealth) const;
@@ -125,12 +122,10 @@
quint8 m_numHedgehogs;
int m_color;
bool m_isNetTeam;
+ bool m_isMissionTeam;
QString m_owner;
// class members that contain statistics, etc.
- unsigned int m_campaignProgress;
- unsigned int m_rounds;
- unsigned int m_wins;
unsigned int AchievementProgress[MAX_ACHIEVEMENTS];
};
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageadmin.cpp
--- a/QTfrontend/ui/page/pageadmin.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageadmin.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -73,6 +73,7 @@
// 4
QLabel * lblPreview = new QLabel(this);
+ //: MOTD = Message Of The Day, the message which is shown to players joining the server
lblPreview->setText(tr("MOTD preview:"));
tab1Layout->addWidget(lblPreview, 4, 0);
@@ -94,11 +95,14 @@
twBans = new QTableWidget(this);
twBans->setColumnCount(3);
twBans->setHorizontalHeaderLabels(QStringList()
+ //: IP = short for "IP address" (Internet Protocol). Nick = short for "nick name"
<< tr("IP/Nick")
<< tr("Expiration")
<< tr("Reason")
);
twBans->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
+ twBans->horizontalHeader()->setSectionsClickable(false);
+ twBans->verticalHeader()->hide();
twBans->setEditTriggers(QAbstractItemView::NoEditTriggers);
twBans->setSelectionBehavior(QAbstractItemView::SelectRows);
twBans->setSelectionMode(QAbstractItemView::SingleSelection);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagecampaign.cpp
--- a/QTfrontend/ui/page/pagecampaign.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagecampaign.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -33,7 +33,10 @@
pageLayout->setRowStretch(0, 1);
pageLayout->setRowStretch(3, 1);
+ QWidget * infoWidget = new QWidget();
+ infoWidget->setObjectName("campaignInfo");
QGridLayout * infoLayout = new QGridLayout();
+ infoWidget->setLayout(infoLayout);
infoLayout->setColumnStretch(0, 1);
infoLayout->setColumnStretch(1, 1);
infoLayout->setColumnStretch(2, 1);
@@ -43,7 +46,7 @@
infoLayout->setRowStretch(1, 1);
// set this as default image first time page is created, this will change in hwform.cpp
- btnPreview = formattedButton(":/res/campaign/A_Classic_Fairytale/first_blood.png", true);
+ btnPreview = formattedButton("physfs://Graphics/Missions/Campaign/A_Classic_Fairytale/first_blood@2x.png", true);
btnPreview->setWhatsThis(tr("Start fighting"));
infoLayout->setAlignment(btnPreview, Qt::AlignHCenter | Qt::AlignVCenter);
@@ -69,7 +72,7 @@
infoLayout->addWidget(lbltitle,0,2,1,2);
infoLayout->addWidget(lbldescription,1,2,1,2);
- pageLayout->addLayout(infoLayout, 0, 0, 2, 4);
+ pageLayout->addWidget(infoWidget, 0, 0, 2, 4);
pageLayout->addWidget(lblteam, 2, 1);
pageLayout->addWidget(lblcampaign, 3, 1);
pageLayout->addWidget(lblmission, 4, 1);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagecampaign.h
--- a/QTfrontend/ui/page/pagecampaign.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagecampaign.h Wed Jul 31 23:14:27 2019 +0200
@@ -35,6 +35,7 @@
QComboBox *CBMission;
QComboBox *CBCampaign;
QComboBox *CBTeam;
+ bool currentMissionWon = false;
protected:
QLayout * bodyLayoutDefinition();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageeditteam.cpp
--- a/QTfrontend/ui/page/pageeditteam.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageeditteam.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -28,9 +28,12 @@
#include
#include
#include
+#include
+#include
#include "SquareLabel.h"
#include "HWApplication.h"
#include "keybinder.h"
+#include "hwconsts.h"
#include "physfs.h"
#include "DataManager.h"
@@ -129,6 +132,9 @@
TeamNameEdit->setMaxLength(64);
TeamNameEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
TeamNameEdit->setStyleSheet("QLineEdit { padding: 6px; }");
+ QRegExp rx(*cSafeFileNameRegExp);
+ QRegExpValidator * val = new QRegExpValidator(rx, TeamNameEdit);
+ TeamNameEdit->setValidator(val);
GBTLayout->addWidget(TeamNameEdit, 0, 1, 1, 2);
vbox2->addWidget(GBoxTeam);
@@ -163,12 +169,13 @@
// CPU level flag. Static image, only displayed when computer player is selected
QImage imgBotlevels = QImage("physfs://Graphics/botlevels.png");
- int botlevelOffsets[5]= { 19, 14, 10, 6, 0 };
+ int botlevelOffsetsX[5]= { 17, 13, 9, 5, 0 };
+ int botlevelOffsetsY[5]= { 11, 9, 4, 2, 0 };
for(int i=0; i<5; i++) {
QImage imgCPU = QImage("physfs://Graphics/Flags/cpu.png");
QPainter painter(&imgCPU);
- painter.drawImage(botlevelOffsets[i], 0, imgBotlevels, botlevelOffsets[i]);
+ painter.drawImage(botlevelOffsetsX[i], botlevelOffsetsY[i], imgBotlevels, botlevelOffsetsX[i], botlevelOffsetsY[i]);
pixCPU[i] = QPixmap::fromImage(imgCPU);
}
@@ -434,6 +441,11 @@
hboxCPUWidget->setHidden(index == 0);
}
+void PageEditTeam::frontendSoundsToggled(bool value)
+{
+ btnTestSound->setEnabled(value);
+}
+
void PageEditTeam::testSound()
{
DataManager & dataMgr = DataManager::instance();
@@ -462,12 +474,13 @@
lazyLoad();
OldTeamName = name;
- // Mostly create a default team, with 2 important exceptions:
+ // Mostly create a default team, with some important exceptions:
HWTeam newTeam(name);
// Randomize grave to make it less likely that default teams have equal graves (important for resurrector)
HWNamegen::teamRandomGrave(newTeam, false);
// Randomize fort for greater variety in fort mode with default teams
HWNamegen::teamRandomFort(newTeam, false);
+ HWNamegen::teamLocalizedDefaultVoice(newTeam);
// DLC forts and graves intentionally filtered out to prevent desyncs and missing grave error
// TODO: Remove DLC filter as soon it is not needed anymore
loadTeam(newTeam);
@@ -490,6 +503,7 @@
reallyDeleteMsg.setIcon(QMessageBox::Question);
reallyDeleteMsg.setWindowTitle(QMessageBox::tr("Teams - Are you sure?"));
reallyDeleteMsg.setText(QMessageBox::tr("Do you really want to delete the team '%1'?").arg(name));
+ reallyDeleteMsg.setTextFormat(Qt::PlainText);
reallyDeleteMsg.setWindowModality(Qt::WindowModal);
reallyDeleteMsg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
@@ -598,6 +612,7 @@
else
qDebug() << "Binds: cannot find" << team.keyBind(i);
}
+ binder->checkConflicts();
}
HWTeam PageEditTeam::data()
@@ -657,6 +672,7 @@
teamNameFixedMsg.setIcon(QMessageBox::Warning);
teamNameFixedMsg.setWindowTitle(QMessageBox::tr("Teams - Name already taken"));
teamNameFixedMsg.setText(QMessageBox::tr("The team name '%1' is already taken, so your team has been renamed to '%2'.").arg(origName).arg(team.name()));
+ teamNameFixedMsg.setTextFormat(Qt::PlainText);
teamNameFixedMsg.setWindowModality(Qt::WindowModal);
teamNameFixedMsg.setStandardButtons(QMessageBox::Ok);
teamNameFixedMsg.exec();
@@ -670,4 +686,5 @@
{
for (int i = 0; i < BINDS_NUMBER; i++)
binder->setBindIndex(i, 0);
+ binder->checkConflicts();
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageeditteam.h
--- a/QTfrontend/ui/page/pageeditteam.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageeditteam.h Wed Jul 31 23:14:27 2019 +0200
@@ -45,6 +45,7 @@
public slots:
void CBTeamLvl_activated(const int index);
void CBFort_activated(const int index);
+ void frontendSoundsToggled(bool value);
private:
QTabWidget * tbw;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagegamestats.cpp
--- a/QTfrontend/ui/page/pagegamestats.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagegamestats.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -42,38 +42,36 @@
{
kindOfPoints = QString("");
defaultGraphTitle = true;
- QGridLayout * pageLayout = new QGridLayout();
- pageLayout->setSpacing(20);
- pageLayout->setColumnStretch(0, 1);
- pageLayout->setColumnStretch(1, 1);
+ pageLayout = new QGridLayout();
pageLayout->setRowStretch(0, 1);
pageLayout->setRowStretch(1, 20);
- //pageLayout->setRowStretch(1, -1); this should work but there is unnecessary empty space betwin lines if used
+ pageLayout->setVerticalSpacing(20);
pageLayout->setContentsMargins(7, 7, 7, 0);
- QGroupBox * gb = new QGroupBox(this);
+ gbDetails = new QGroupBox(this);
+ gbDetails->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout * gbl = new QVBoxLayout;
// details
labelGameStats = new QLabel(this);
- QLabel * l = new QLabel(this);
- l->setTextFormat(Qt::RichText);
- l->setText(" " + PageGameStats::tr("Details") + " ");
- l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ labelDetails = new QLabel(this);
+ labelDetails->setTextFormat(Qt::RichText);
+ labelDetails->setText(" " + PageGameStats::tr("Details").toHtmlEscaped() + " ");
+ labelDetails->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
labelGameStats->setTextFormat(Qt::RichText);
labelGameStats->setAlignment(Qt::AlignTop);
labelGameStats->setWordWrap(true);
- gbl->addWidget(l);
+ gbl->addWidget(labelDetails);
gbl->addWidget(labelGameStats);
- gb->setLayout(gbl);
- pageLayout->addWidget(gb, 1, 1);
+ gbDetails->setLayout(gbl);
+ pageLayout->addWidget(gbDetails, 1, 1);
// graph
- graphic = new FitGraphicsView(gb);
+ graphic = new FitGraphicsView(gbDetails);
graphic->setObjectName("gameStatsView");
labelGraphTitle = new QLabel(this);
labelGraphTitle->setTextFormat(Qt::RichText);
- labelGraphTitle->setText(" " + PageGameStats::tr("Health graph") + " ");
+ labelGraphTitle->setText(" " + PageGameStats::tr("Health graph").toHtmlEscaped() + " ");
labelGraphTitle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
gbl->addWidget(labelGraphTitle);
gbl->addWidget(graphic);
@@ -86,20 +84,21 @@
pageLayout->addWidget(labelGameWin, 0, 0, 1, 2);
// ranking box
- gb = new QGroupBox(this);
+ gbRanks = new QGroupBox(this);
+ gbRanks->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
gbl = new QVBoxLayout;
- labelGameRank = new QLabel(gb);
- l = new QLabel(this);
+ labelGameRank = new QLabel(gbRanks);
+ QLabel* l = new QLabel(this);
l->setTextFormat(Qt::RichText);
- l->setText(" " + PageGameStats::tr("Ranking") + " ");
+ l->setText(" " + PageGameStats::tr("Ranking").toHtmlEscaped() + " ");
l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
gbl->addWidget(l);
gbl->addWidget(labelGameRank);
- gb->setLayout(gbl);
+ gbRanks->setLayout(gbl);
labelGameRank->setTextFormat(Qt::RichText);
labelGameRank->setAlignment(Qt::AlignTop);
- pageLayout->addWidget(gb, 1, 0);
+ pageLayout->addWidget(gbRanks, 1, 0);
return pageLayout;
}
@@ -143,6 +142,9 @@
void PageGameStats::AddStatText(const QString & msg)
{
labelGameStats->setText(labelGameStats->text() + msg);
+ labelDetails->show();
+ labelGameStats->show();
+ gbDetails->show();
}
void PageGameStats::clear()
@@ -152,7 +154,16 @@
labelGameRank->setText("");
labelGameWin->setText("");
playerPosition = 0;
+ scriptPlayerPosition = 0;
lastColor = 0;
+ graphic->hide();
+ labelDetails->hide();
+ labelGameStats->hide();
+ gbDetails->hide();
+ gbRanks->hide();
+ pageLayout->setColumnStretch(0, 0);
+ pageLayout->setColumnStretch(1, 0);
+ pageLayout->setHorizontalSpacing(0);
}
void PageGameStats::restartBtnVisible(bool visible)
@@ -160,10 +171,15 @@
btnRestart->setVisible(visible);
}
+void PageGameStats::saveDemoBtnEnabled(bool enabled)
+{
+ btnSave->setEnabled(enabled);
+}
+
void PageGameStats::renderStats()
{
if(defaultGraphTitle) {
- labelGraphTitle->setText(" " + PageGameStats::tr("Health graph") + " ");
+ labelGraphTitle->setText(" " + PageGameStats::tr("Health graph").toHtmlEscaped() + " ");
} else {
defaultGraphTitle = true;
}
@@ -173,6 +189,7 @@
graphic->hide();
} else {
graphic->setScene(Q_NULLPTR);
+ gbDetails->show();
m_scene.reset(new QGraphicsScene(this));
// min and max value across the entire chart
@@ -192,6 +209,7 @@
if(maxDataPoints < 2) {
labelGraphTitle->hide();
graphic->hide();
+ applySpacing();
return;
}
@@ -221,11 +239,25 @@
minValue = qMin(minValue, hps[t]);
}
- QPen pen(c);
+ // Draw clan health/score graph lines
+ QColor col = QColor(c);
+
+ // Special pen for very dark clan colors
+ if (!(col.red() >= cInvertTextColorAt || col.green() >= cInvertTextColorAt || col.blue() >= cInvertTextColorAt))
+ {
+ QPen pen_marker(QColor(255, 255, 255));
+ pen_marker.setWidth(3);
+ pen_marker.setStyle(Qt::DotLine);
+ pen_marker.setCosmetic(true);
+ m_scene->addPath(path, pen_marker);
+ }
+
+ // Regular pen
+ QPen pen(col);
pen.setWidth(2);
pen.setCosmetic(true);
+ m_scene->addPath(path, pen);
- m_scene->addPath(path, pen);
++i;
}
@@ -247,6 +279,22 @@
graphic->show();
labelGraphTitle->show();
+ gbDetails->show();
+ }
+ applySpacing();
+}
+
+void PageGameStats::applySpacing()
+{
+ if (!labelGameStats->isHidden())
+ {
+ labelGraphTitle->setText(" " + labelGraphTitle->text());
+ }
+ if ((!gbDetails->isHidden()) && (!gbRanks->isHidden()))
+ {
+ pageLayout->setColumnStretch(0, 1);
+ pageLayout->setColumnStretch(1, 1);
+ pageLayout->setHorizontalSpacing(20);
}
}
@@ -256,14 +304,14 @@
{
case 'r' :
{
- labelGameWin->setText(QString("%1 ").arg(info));
+ labelGameWin->setText(QString("%1 ").arg(info.toHtmlEscaped()));
break;
}
case 'D' :
{
int i = info.indexOf(' ');
int num = info.left(i).toInt();
- QString message = " " + PageGameStats::tr("The best shot award was won by %1 with %2 pts.", "", num).arg(info.mid(i + 1), info.left(i)) + "
";
+ QString message = " " + PageGameStats::tr("The best shot award was won by %1 with %2 pts.", "", num).arg(info.mid(i + 1).toHtmlEscaped(), info.left(i)) + "
";
AddStatText(message);
break;
}
@@ -271,7 +319,7 @@
{
int i = info.indexOf(' ');
int num = info.left(i).toInt();
- QString message = " " + PageGameStats::tr("The best killer is %1 with %2 kills in a turn.", "", num).arg(info.mid(i + 1), info.left(i)) + "
";
+ QString message = " " + PageGameStats::tr("The best killer is %1 with %2 kills in a turn.", "", num).arg(info.mid(i + 1).toHtmlEscaped(), info.left(i)) + "
";
AddStatText(message);
break;
}
@@ -294,22 +342,12 @@
{
// TODO: change default picture or add change pic capability
defaultGraphTitle = false;
- labelGraphTitle->setText(" " + info + " ");
+ labelGraphTitle->setText(" " + info.toHtmlEscaped() + " ");
break;
}
case 'T': // local team stats
{
- //AddStatText("local team: " + info + "
");
- QStringList infol = info.split(":");
- HWTeam team(infol[0]);
- if(team.fileExists()) // do some better test to avoid influence from scripted/predefined teams?
- {
- team.loadFromFile();
- team.incRounds();
- if(infol[1].toInt() > 0) // might require some better test for winning condition (or changed flag) ... WIP!
- team.incWins(); // should draws count as wins?
- //team.SaveToFile(); // don't save yet
- }
+ // unused
break;
}
case 'p' :
@@ -329,14 +367,21 @@
i = playerinfo.indexOf(' ');
- int kills = playerinfo.left(i).toInt();
+ QString killsString = playerinfo.left(i);
+ int kills = killsString.toInt();
QString playername = playerinfo.mid(i + 1);
QString image;
if (lastColor == c) playerPosition--;
lastColor = c;
- switch (playerPosition)
+ unsigned int realPlayerPosition;
+ if(scriptPlayerPosition == 0)
+ realPlayerPosition = playerPosition;
+ else
+ realPlayerPosition = scriptPlayerPosition;
+
+ switch (realPlayerPosition)
{
case 1:
image = " ";
@@ -354,25 +399,43 @@
QString message;
QString killstring;
- if(kindOfPoints.compare("") == 0) {
+ if(kindOfPoints.isEmpty()) {
//: Number of kills in stats screen, written after the team name
killstring = PageGameStats::tr("(%1 kill)", "", kills).arg(kills);
+ } else if (kindOfPoints == "!POINTS") {
+ //: Number of points in stats screen, written after the team name
+ killstring = PageGameStats::tr("(%1 point(s))", "", kills).arg(kills);
+ } else if (kindOfPoints == "!TIME") {
+ //: Time in seconds
+ killstring = PageGameStats::tr("(%L1 second(s))", "", kills).arg((double) kills/1000, 0, 'f', 3);
+ } else if (kindOfPoints.startsWith("!TIME") && kindOfPoints.length() == 6) {
+ int len = kindOfPoints.at(6).digitValue();
+ if(len != -1)
+ killstring = PageGameStats::tr("(%L1 second(s))", "", kills).arg((double) kills/1000, 0, 'f', len);
+ else
+ qWarning("SendStat: siPointType received with !TIME and invalid number length!");
+ } else if (kindOfPoints == "!CRATES") {
+ killstring = PageGameStats::tr("(%1 crate(s))", "", kills).arg(kills);
+ } else if (kindOfPoints == "!EMPTY") {
+ killstring = QString("");
} else {
//: For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points”
killstring = PageGameStats::tr("(%1 %2)", "", kills).arg(kills).arg(kindOfPoints);
- kindOfPoints = QString("");
}
+ kindOfPoints = QString("");
- message = QString("
%1 %2. %3 ").arg(image, QString::number(playerPosition), playername, clanColor.name()) + killstring + "
";
+ message = QString("
%1 %2. %3 ").arg(image, QString::number(realPlayerPosition), playername.toHtmlEscaped(), clanColor.name().toHtmlEscaped()) + killstring.toHtmlEscaped() + " ";
labelGameRank->setText(labelGameRank->text() + message);
+ scriptPlayerPosition = 0;
+ gbRanks->show();
break;
}
case 's' :
{
int i = info.indexOf(' ');
int num = info.left(i).toInt();
- QString message = " " + PageGameStats::tr("%1 thought it's good to shoot their own hedgehogs for %2 pts.", "", num).arg(info.mid(i + 1)).arg(num) + "
";
+ QString message = " " + PageGameStats::tr("%1 thought it's good to shoot their own hedgehogs for %2 pts.", "", num).arg(info.mid(i + 1).toHtmlEscaped()).arg(num) + "
";
AddStatText(message);
break;
}
@@ -380,7 +443,7 @@
{
int i = info.indexOf(' ');
int num = info.left(i).toInt();
- QString message = " " + PageGameStats::tr("%1 killed %2 of their own hedgehogs.", "", num).arg(info.mid(i + 1)).arg(num) + "
";
+ QString message = " " + PageGameStats::tr("%1 killed %2 of their own hedgehogs.", "", num).arg(info.mid(i + 1).toHtmlEscaped()).arg(num) + "
";
AddStatText(message);
break;
}
@@ -388,16 +451,26 @@
{
int i = info.indexOf(' ');
int num = info.left(i).toInt();
- QString message = " " + PageGameStats::tr("%1 was scared and skipped turn %2 times.", "", num).arg(info.mid(i + 1)).arg(num) + "
";
+ QString message = " " + PageGameStats::tr("%1 was scared and skipped turn %2 times.", "", num).arg(info.mid(i + 1).toHtmlEscaped()).arg(num) + "
";
AddStatText(message);
break;
}
case 'c' :
{
- QString message = " "+info+"
";
+ QString message = " "+info.toHtmlEscaped()+"
";
AddStatText(message);
break;
}
-
+ case 'R' :
+ {
+ scriptPlayerPosition = info.toInt();
+ break;
+ }
+ case 'h' :
+ {
+ QString message = " " + PageGameStats::tr("With everyone having the same clan color, there was no reason to fight. And so the hedgehogs happily lived in peace ever after.").toHtmlEscaped() + "
";
+ AddStatText(message);
+ break;
+ }
}
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagegamestats.h
--- a/QTfrontend/ui/page/pagegamestats.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagegamestats.h Wed Jul 31 23:14:27 2019 +0200
@@ -58,6 +58,7 @@
void clear();
void renderStats();
void restartBtnVisible(bool visible);
+ void saveDemoBtnEnabled(bool enabled);
signals:
void saveDemoRequested();
@@ -65,13 +66,20 @@
private:
void AddStatText(const QString & msg);
+ void applySpacing();
QMap > healthPoints;
unsigned int playerPosition;
+ unsigned int scriptPlayerPosition;
quint32 lastColor;
bool defaultGraphTitle;
QScopedPointer m_scene;
+ QLabel* labelDetails;
+ QGroupBox* gbDetails;
+ QGroupBox* gbRanks;
+ QGridLayout* pageLayout;
+
protected:
QLayout * bodyLayoutDefinition();
QLayout * footerLayoutDefinition();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagemain.cpp
--- a/QTfrontend/ui/page/pagemain.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagemain.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -74,10 +74,14 @@
BtnNetOfficial->setVisible(false);
// button order matters for overlapping (what's on top and what isn't)
- BtnInfo = addButton(":/res/HedgewarsTitle.png", pageLayout, 0, 0, 1, 4, true);
- BtnInfo->setObjectName("infoButton");
+ BtnTitle = addButton(":/res/HedgewarsTitle.png", pageLayout, 0, 0, 1, 4, true);
+ BtnTitle ->setObjectName("infoButton");
+ BtnTitle->setWhatsThis(tr("Read about who is behind the Hedgewars Project"));
+ pageLayout->setAlignment(BtnTitle, Qt::AlignHCenter);
+
+ BtnInfo = addButton(":/res/AboutIcon.png", pageLayout, 0, 0, 1, 1, true);
BtnInfo->setWhatsThis(tr("Read about who is behind the Hedgewars Project"));
- pageLayout->setAlignment(BtnInfo, Qt::AlignHCenter);
+ pageLayout->setAlignment(BtnInfo, Qt::AlignLeft | Qt::AlignTop);
BtnFeedback = addButton(tr("Feedback"), pageLayout, 4, 0, 1, 4, false);
BtnFeedback->setStyleSheet("padding: 5px 10px");
@@ -113,7 +117,10 @@
BtnVideos->setWhatsThis(tr("Manage videos recorded from game"));
#endif
- BtnSetup = addButton(":/res/Settings.png", bottomLayout, 2, true, Qt::AlignBottom);
+ BtnHelp = addButton(":/res/Help.png", bottomLayout, 2, true, Qt::AlignBottom);
+ BtnHelp->setWhatsThis(tr("Open the Hedgewars online game manual in your web browser"));
+
+ BtnSetup = addButton(":/res/Settings.png", bottomLayout, 3, true, Qt::AlignBottom);
BtnSetup->setWhatsThis(tr("Edit game preferences"));
return bottomLayout;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagemain.h
--- a/QTfrontend/ui/page/pagemain.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagemain.h Wed Jul 31 23:14:27 2019 +0200
@@ -37,9 +37,11 @@
QPushButton * BtnNetOfficial;
QPushButton * BtnSetup;
QPushButton * BtnFeedback;
+ QPushButton * BtnTitle;
QPushButton * BtnInfo;
QPushButton * BtnDataDownload;
QPushButton * BtnVideos;
+ QPushButton * BtnHelp;
QLabel * mainNote;
private:
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagenet.cpp
--- a/QTfrontend/ui/page/pagenet.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagenet.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -47,20 +47,28 @@
BtnNetConnect = new QPushButton(ConnGroupBox);
BtnNetConnect->setFont(*font14);
BtnNetConnect->setText(QPushButton::tr("Connect"));
+ BtnNetConnect->setWhatsThis(tr("Connect to the selected server"));
GBClayout->addWidget(BtnNetConnect, 2, 2);
tvServersList = new QTableView(ConnGroupBox);
tvServersList->setSelectionBehavior(QAbstractItemView::SelectRows);
+ tvServersList->setSelectionMode(QAbstractItemView::SingleSelection);
+ tvServersList->setShowGrid(false);
+ tvServersList->setAlternatingRowColors(true);
+ tvServersList->verticalHeader()->setVisible(false);
+
GBClayout->addWidget(tvServersList, 1, 0, 1, 3);
BtnUpdateSList = new QPushButton(ConnGroupBox);
BtnUpdateSList->setFont(*font14);
BtnUpdateSList->setText(QPushButton::tr("Update"));
+ BtnUpdateSList->setWhatsThis(tr("Update the list of servers"));
GBClayout->addWidget(BtnUpdateSList, 2, 0);
BtnSpecifyServer = new QPushButton(ConnGroupBox);
BtnSpecifyServer->setFont(*font14);
- BtnSpecifyServer->setText(QPushButton::tr("Specify"));
+ BtnSpecifyServer->setText(QPushButton::tr("Specify address"));
+ BtnSpecifyServer->setWhatsThis(tr("Specify the address and port number of a known server and connect to it directly"));
GBClayout->addWidget(BtnSpecifyServer, 2, 1);
return pageLayout;
@@ -71,6 +79,7 @@
QHBoxLayout * footerLayout = new QHBoxLayout();
BtnNetSvrStart = formattedButton(QPushButton::tr("Start server"));
+ BtnNetSvrStart->setWhatsThis(tr("Start private server"));
BtnNetSvrStart->setMinimumSize(180, 50);
QString serverPath = bindir->absolutePath() + "/hedgewars-server";
#ifdef Q_OS_WIN
@@ -100,6 +109,7 @@
tvServersList->setModel(new HWNetUdpModel(tvServersList));
tvServersList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ tvServersList->horizontalHeader()->setSectionsClickable(false);
static_cast(tvServersList->model())->updateList();
@@ -124,5 +134,5 @@
QString host = model->index(mi.row(), 1).data().toString();
quint16 port = model->index(mi.row(), 2).data().toUInt();
- emit connectClicked(host, port);
+ emit connectClicked(host, port, false);
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagenet.h
--- a/QTfrontend/ui/page/pagenet.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagenet.h Wed Jul 31 23:14:27 2019 +0200
@@ -38,7 +38,7 @@
void updateServersList();
signals:
- void connectClicked(const QString & host, quint16 port);
+ void connectClicked(const QString & host, quint16 port, bool useTls);
private:
QLayout * bodyLayoutDefinition();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagenetgame.cpp
--- a/QTfrontend/ui/page/pagenetgame.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagenetgame.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -143,13 +143,13 @@
QSize sz = lp.actualSize(QSize(65535, 65535));
BtnStart = new QPushButton();
BtnStart->setText(tr("Start"));
+ BtnStart->setStyleSheet("padding: 5px 10px");
BtnStart->setWhatsThis(tr("Start fighting (requires at least 2 teams)"));
- BtnStart->setMinimumWidth(sz.width() + 60);
BtnStart->setIcon(lp);
BtnStart->setFixedHeight(50);
BtnStart->setIconSize(sz);
BtnStart->setFlat(true);
- BtnStart->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ BtnStart->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
bottomLayout->addWidget(BtnStart, 0, Qt::AlignBottom);
return bottomLayout;
@@ -218,6 +218,10 @@
chatWidget->displayWarning(message);
}
+void PageNetGame::cleanupFakeNetTeams()
+{
+ pNetTeamsWidget->cleanupFakeNetTeams();
+}
void PageNetGame::setReadyStatus(bool isReady)
{
@@ -280,6 +284,7 @@
void PageNetGame::setUser(const QString & nickname)
{
+ pNetTeamsWidget->setUser(nickname);
chatWidget->setUser(nickname);
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagenetgame.h
--- a/QTfrontend/ui/page/pagenetgame.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagenetgame.h Wed Jul 31 23:14:27 2019 +0200
@@ -40,6 +40,7 @@
void displayError(const QString & message);
void displayNotice(const QString & message);
void displayWarning(const QString & message);
+ void cleanupFakeNetTeams();
QPushButton *BtnGo;
QPushButton *BtnMaster;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageoptions.cpp
--- a/QTfrontend/ui/page/pageoptions.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageoptions.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -142,6 +142,7 @@
BtnNewTeam->setIconSize(pmNew.size());
BtnNewTeam->setIcon(pmNew);
BtnNewTeam->setMaximumWidth(pmNew.width() + 6);
+ BtnNewTeam->setStyleSheet("padding: 0px;");
connect(BtnNewTeam, SIGNAL(clicked()), this, SIGNAL(newTeamRequested()));
groupTeams->layout()->addWidget(BtnNewTeam, 0, 1);
@@ -150,6 +151,7 @@
BtnEditTeam->setIconSize(pmEdit.size());
BtnEditTeam->setIcon(pmEdit);
BtnEditTeam->setMaximumWidth(pmEdit.width() + 6);
+ BtnEditTeam->setStyleSheet("padding: 0px;");
connect(BtnEditTeam, SIGNAL(clicked()), this, SLOT(requestEditSelectedTeam()));
groupTeams->layout()->addWidget(BtnEditTeam, 0, 2);
@@ -158,6 +160,7 @@
BtnDeleteTeam->setIconSize(pmDelete.size());
BtnDeleteTeam->setIcon(pmDelete);
BtnDeleteTeam->setMaximumWidth(pmDelete.width() + 6);
+ BtnDeleteTeam->setStyleSheet("padding: 0px;");
connect(BtnDeleteTeam, SIGNAL(clicked()), this, SLOT(requestDeleteSelectedTeam()));
groupTeams->layout()->addWidget(BtnDeleteTeam, 0, 3);
@@ -184,6 +187,7 @@
SchemeNew->setIconSize(pmNew.size());
SchemeNew->setIcon(pmNew);
SchemeNew->setMaximumWidth(pmNew.width() + 6);
+ SchemeNew->setStyleSheet("padding: 0px;");
groupSchemes->layout()->addWidget(SchemeNew, 0, 1);
SchemeEdit = new QPushButton(groupSchemes);
@@ -191,6 +195,7 @@
SchemeEdit->setIconSize(pmEdit.size());
SchemeEdit->setIcon(pmEdit);
SchemeEdit->setMaximumWidth(pmEdit.width() + 6);
+ SchemeEdit->setStyleSheet("padding: 0px;");
groupSchemes->layout()->addWidget(SchemeEdit, 0, 2);
SchemeDelete = new QPushButton(groupSchemes);
@@ -198,6 +203,7 @@
SchemeDelete->setIconSize(pmDelete.size());
SchemeDelete->setIcon(pmDelete);
SchemeDelete->setMaximumWidth(pmDelete.width() + 6);
+ SchemeDelete->setStyleSheet("padding: 0px;");
groupSchemes->layout()->addWidget(SchemeDelete, 0, 3);
}
@@ -217,6 +223,7 @@
WeaponNew->setIconSize(pmNew.size());
WeaponNew->setIcon(pmNew);
WeaponNew->setMaximumWidth(pmNew.width() + 6);
+ WeaponNew->setStyleSheet("padding: 0px;");
groupWeapons->layout()->addWidget(WeaponNew, 0, 1);
WeaponEdit = new QPushButton(groupWeapons);
@@ -224,6 +231,7 @@
WeaponEdit->setIconSize(pmEdit.size());
WeaponEdit->setIcon(pmEdit);
WeaponEdit->setMaximumWidth(pmEdit.width() + 6);
+ WeaponEdit->setStyleSheet("padding: 0px;");
groupWeapons->layout()->addWidget(WeaponEdit, 0, 2);
WeaponDelete = new QPushButton(groupWeapons);
@@ -231,6 +239,7 @@
WeaponDelete->setIconSize(pmDelete.size());
WeaponDelete->setIcon(pmDelete);
WeaponDelete->setMaximumWidth(pmDelete.width() + 6);
+ WeaponDelete->setStyleSheet("padding: 0px;");
groupWeapons->layout()->addWidget(WeaponDelete, 0, 3);
}
@@ -273,12 +282,11 @@
lblWinScreenRes->setText(QLabel::tr("Windowed Resolution"));
groupGame->layout()->addWidget(lblWinScreenRes, 2, 0);
- winResContainer = new QWidget();
- QHBoxLayout * winResLayout = new QHBoxLayout(winResContainer);
+ QHBoxLayout * winResLayout = new QHBoxLayout();
winResLayout->setSpacing(0);
- groupGame->layout()->addWidget(winResContainer, 2, 1);
+ groupGame->layout()->addLayout(winResLayout, 2, 1, 1, 3);
- QLabel *winLabelX = new QLabel(groupGame);
+ winLabelX = new QLabel(groupGame);
//: Multiplication sign, to be used between two numbers. Note the “x” is only a dummy character, we recommend to use “×” if your language permits it
winLabelX->setText(tr("x"));
winLabelX->setFixedWidth(40);
@@ -307,16 +315,29 @@
SLQuality = new QSlider(Qt::Horizontal, groupGame);
SLQuality->setTickPosition(QSlider::TicksBelow);
+ SLQuality->setPageStep(2);
SLQuality->setMaximum(5);
SLQuality->setMinimum(0);
SLQuality->setFixedWidth(150);
groupGame->layout()->addWidget(SLQuality, 3, 1, Qt::AlignLeft);
+ // Zoom
+ QLabel * lblZoom = new QLabel(groupGame);
+ lblZoom->setText(QLabel::tr("Zoom (%)"));
+ lblZoom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ groupGame->layout()->addWidget(lblZoom, 4, 0);
+
+ SLZoom = new QSpinBox(groupGame);
+ SLZoom->setSingleStep(5);
+ SLZoom->setMaximum(150);
+ SLZoom->setMinimum(50);
+ groupGame->layout()->addWidget(SLZoom, 4, 1, Qt::AlignLeft);
+
// Stereo spacing
QLabel * lblStereo = new QLabel(groupGame);
lblStereo->setText(QLabel::tr("Stereoscopy"));
- groupGame->layout()->addWidget(lblStereo, 4, 0);
+ groupGame->layout()->addWidget(lblStereo, 5, 0);
CBStereoMode = new QComboBox(groupGame);
CBStereoMode->setWhatsThis(QComboBox::tr("Stereoscopy creates an illusion of depth when you wear 3D glasses."));
@@ -337,16 +358,16 @@
CBStereoMode->addItem(QComboBox::tr("Side-by-side"));
CBStereoMode->addItem(QComboBox::tr("Top-Bottom"));
CBStereoMode->setFixedWidth(CBResolution->width());
- groupGame->layout()->addWidget(CBStereoMode, 4, 1);
+ groupGame->layout()->addWidget(CBStereoMode, 5, 1);
// Divider
- groupGame->addDivider(); // row 5
+ groupGame->addDivider(); // row 6
// FPS limit
QHBoxLayout * fpsLayout = new QHBoxLayout();
- groupGame->layout()->addLayout(fpsLayout, 6, 0, 1, 2);
+ groupGame->layout()->addLayout(fpsLayout, 7, 0, 1, 2);
QLabel * maxfps = new QLabel(groupGame);
maxfps->setText(QLabel::tr("FPS limit"));
fpsLayout->addWidget(maxfps);
@@ -363,30 +384,30 @@
// Divider
- groupGame->addDivider(); // row 7
+ groupGame->addDivider(); // row 8
// Alternative damage show
CBAltDamage = new QCheckBox(groupGame);
CBAltDamage->setText(QCheckBox::tr("Alternative damage show"));
- groupGame->layout()->addWidget(CBAltDamage, 8, 0, 1, 2);
+ groupGame->layout()->addWidget(CBAltDamage, 9, 0, 1, 2);
// Show ammo menu tooltips
WeaponTooltip = new QCheckBox(groupGame);
WeaponTooltip->setText(QCheckBox::tr("Show ammo menu tooltips"));
- groupGame->layout()->addWidget(WeaponTooltip, 9, 0, 1, 2);
+ groupGame->layout()->addWidget(WeaponTooltip, 10, 0, 1, 2);
groupGame->addDivider();
lblTags = new QLabel(groupGame);
lblTags->setText(QLabel::tr("Displayed tags above hogs and translucent tags"));
- groupGame->layout()->addWidget(lblTags, 11, 0, 1, 2);
+ groupGame->layout()->addWidget(lblTags, 12, 0, 1, 2);
tagsContainer = new QWidget();
QHBoxLayout * tagsLayout = new QHBoxLayout(tagsContainer);
tagsLayout->setSpacing(0);
- groupGame->layout()->addWidget(tagsContainer, 12, 0, 1, 2);
+ groupGame->layout()->addWidget(tagsContainer, 13, 0, 1, 2);
CBTeamTag = new QCheckBox(groupGame);
CBTeamTag->setText(QCheckBox::tr("Team"));
@@ -508,6 +529,14 @@
CBMusic->setText(QCheckBox::tr("Music"));
CBMusic->setWhatsThis(QCheckBox::tr("In-game music"));
groupGame->layout()->addWidget(CBMusic, 1, 2, 1, 2, Qt::AlignLeft);
+
+ // Dampen
+
+ CBDampenAudio = new QCheckBox(groupGame);
+ //: Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus
+ CBDampenAudio->setText(QCheckBox::tr("Dampen when losing focus"));
+ CBDampenAudio->setWhatsThis(QCheckBox::tr("Reduce the game audio volume if the game window has lost its focus"));
+ groupGame->layout()->addWidget(CBDampenAudio, 2, 1, 1, 3, Qt::AlignLeft);
}
{ // group: frontend
@@ -656,6 +685,7 @@
if(entryName.isEmpty())
{
// Show error and the locale identifier
+ //: In the case of an error, this is shown in the language selection for a language with unknown name. %1 = language code
entryName = tr("MISSING LANGUAGE NAME [%1]").arg(lname);
}
CBLanguage->addItem(entryName, lname);
@@ -961,7 +991,9 @@
lblFullScreenRes->setVisible(state);
CBResolution->setVisible(state);
lblWinScreenRes->setVisible(!state);
- winResContainer->setVisible(!state);
+ windowWidthEdit->setVisible(!state);
+ windowHeightEdit->setVisible(!state);
+ winLabelX->setVisible(!state);
int index = this->CBStereoMode->currentIndex();
if (index != 7 && index != 8 && index != 9)
@@ -1204,6 +1236,7 @@
QModelIndexList mdl = binds->match(binds->index(0, 0), Qt::UserRole + 1, value, 1, Qt::MatchExactly);
if(mdl.size() == 1) binder->setBindIndex(i, mdl[0].row());
}
+ binder->checkConflicts();
}
currentTab = index;
@@ -1226,9 +1259,12 @@
// Returns: The bind model index of the default.
int PageOptions::resetBindToDefault(int bindID)
{
+ if (QString(cbinds[bindID].action) == QString("!MULTI"))
+ return -1;
QStandardItemModel * binds = DataManager::instance().bindsModel();
QModelIndexList mdl = binds->match(binds->index(0, 0), Qt::UserRole + 1, cbinds[bindID].strbind, 1, Qt::MatchExactly);
- if(mdl.size() == 1) binder->setBindIndex(bindID, mdl[0].row());
+ if(mdl.size() == 1)
+ binder->setBindIndex(bindID, mdl[0].row());
return mdl[0].row();
}
@@ -1237,7 +1273,9 @@
{
for (int i = 0; i < BINDS_NUMBER; i++)
{
- resetBindToDefault(i);
- bindUpdated(i);
+ int ret = resetBindToDefault(i);
+ if(ret != -1)
+ bindUpdated(i);
}
+ binder->checkConflicts();
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageoptions.h
--- a/QTfrontend/ui/page/pageoptions.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageoptions.h Wed Jul 31 23:14:27 2019 +0200
@@ -79,11 +79,13 @@
QComboBox *CBResolution;
QSpinBox *windowWidthEdit;
QSpinBox *windowHeightEdit;
+ QLabel *winLabelX;
QComboBox *CBStereoMode;
QCheckBox *CBFrontendSound;
QCheckBox *CBFrontendMusic;
QCheckBox *CBSound;
QCheckBox *CBMusic;
+ QCheckBox *CBDampenAudio;
QCheckBox *CBFullscreen;
QCheckBox *CBFrontendFullscreen;
QCheckBox *CBShowFPS;
@@ -110,6 +112,7 @@
QLineEdit *editNetNick;
QLineEdit *editNetPassword;
QSlider *SLQuality;
+ QSpinBox *SLZoom;
QCheckBox *CBFrontendEffects;
QComboBox * cbProxyType;
QSpinBox * sbProxyPort;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageplayrecord.cpp
--- a/QTfrontend/ui/page/pageplayrecord.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageplayrecord.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -138,6 +138,7 @@
recordMsg.setIcon(QMessageBox::Warning);
recordMsg.setWindowTitle(QMessageBox::tr("Error"));
recordMsg.setText(QMessageBox::tr("Please select a file from the list."));
+ recordMsg.setTextFormat(Qt::PlainText);
recordMsg.setWindowModality(Qt::WindowModal);
recordMsg.exec();
return ;
@@ -165,6 +166,7 @@
renameMsg.setIcon(QMessageBox::Warning);
renameMsg.setWindowTitle(QMessageBox::tr("Error"));
renameMsg.setText(QMessageBox::tr("Cannot rename file to %1.").arg(newfullname));
+ renameMsg.setTextFormat(Qt::PlainText);
renameMsg.setWindowModality(Qt::WindowModal);
renameMsg.exec();
}
@@ -182,6 +184,7 @@
recordMsg.setIcon(QMessageBox::Warning);
recordMsg.setWindowTitle(QMessageBox::tr("Error"));
recordMsg.setText(QMessageBox::tr("Please select a file from the list."));
+ recordMsg.setTextFormat(Qt::PlainText);
recordMsg.setWindowModality(Qt::WindowModal);
recordMsg.exec();
return ;
@@ -197,6 +200,7 @@
removeMsg.setIcon(QMessageBox::Warning);
removeMsg.setWindowTitle(QMessageBox::tr("Error"));
removeMsg.setText(QMessageBox::tr("Cannot delete file %1.").arg(rfile.fileName()));
+ removeMsg.setTextFormat(Qt::PlainText);
removeMsg.setWindowModality(Qt::WindowModal);
removeMsg.exec();
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageroomslist.cpp
--- a/QTfrontend/ui/page/pageroomslist.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageroomslist.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -504,6 +504,7 @@
roomNameMsg.setIcon(QMessageBox::Warning);
roomNameMsg.setWindowTitle(QMessageBox::tr("Room Name - Error"));
roomNameMsg.setText(QMessageBox::tr("Please select room from the list"));
+ roomNameMsg.setTextFormat(Qt::PlainText);
roomNameMsg.setWindowModality(Qt::WindowModal);
roomNameMsg.exec();
return;
@@ -530,6 +531,7 @@
reallyJoinMsg.setIcon(QMessageBox::Question);
reallyJoinMsg.setWindowTitle(QMessageBox::tr("Room Name - Are you sure?"));
reallyJoinMsg.setText(QMessageBox::tr("The game you are trying to join has started.\nDo you still want to join the room?"));
+ reallyJoinMsg.setTextFormat(Qt::PlainText);
reallyJoinMsg.setWindowModality(Qt::WindowModal);
reallyJoinMsg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagescheme.cpp
--- a/QTfrontend/ui/page/pagescheme.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagescheme.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -26,10 +26,14 @@
#include
#include
#include
+#include
+#include
+#include "hwconsts.h"
#include "gameSchemeModel.h"
#include "pagescheme.h"
#include "FreqSpinBox.h"
+#include "SDTimeoutSpinBox.h"
#include "MinesTimeSpinBox.h"
@@ -89,7 +93,7 @@
glGMLayout->addWidget(TBW_switchhog,0,4,1,1);
TBW_solid = new ToggleButtonWidget(gbGameModes, ":/res/btnSolid@2x.png");
- TBW_solid->setWhatsThis(tr("Land can not be destroyed!"));
+ TBW_solid->setWhatsThis(tr("Land can not be destroyed by most weapons."));
glGMLayout->addWidget(TBW_solid,1,0,1,1);
TBW_lowGravity = new ToggleButtonWidget(gbGameModes, ":/res/btnLowGravity@2x.png");
@@ -171,6 +175,7 @@
// Right
QLabel * l;
+//: Description of the game scheme setting “Damage Modifier”. “Knockback” means how much hedgehogs and objects get pushed by explosions and other forces
QString wtDamageModifier = tr("Overall damage and knockback in percent");
QString wtTurnTime = tr("Turn time in seconds");
QString wtInitHealth = tr("Initial health of hedgehogs");
@@ -251,9 +256,21 @@
l->setWhatsThis(wtSuddenDeath);
l->setPixmap(QPixmap(":/res/iconSuddenDeathTime.png"));
glBSLayout->addWidget(l,3,1,1,1);
- SB_SuddenDeath = new QSpinBox(gbBasicSettings);
+ /* NOTE:
+ The internally stored value for Sudden Death Timeout
+ is defined as
+ "number of full rounds to play till Sudden Death, minus one"
+ i.e. value 0 means Sudden Death starts in 2nd round.
+ The lowest possible internal value is 0.
+ The user-facing value is different, it's defined as
+ "number of full rounds to play till Sudden Death"
+ i.e. the user-facing value 1 is equivalent to internal value 0.
+ We use SDTimeoutSpinBox for the magic to happen. */
+ SB_SuddenDeath = new SDTimeoutSpinBox(gbBasicSettings);
SB_SuddenDeath->setWhatsThis(wtSuddenDeath);
- SB_SuddenDeath->setRange(0, 50);
+ // Will display as 1-52
+ SB_SuddenDeath->setRange(0, 51);
+ // Will display as 16
SB_SuddenDeath->setValue(15);
SB_SuddenDeath->setSingleStep(3);
glBSLayout->addWidget(SB_SuddenDeath,3,2,1,1);
@@ -445,6 +462,7 @@
glBSLayout->addWidget(SB_AirMines,14,2,1,1);
l = new QLabel(gbBasicSettings);
+//: Label of game scheme setting for the time you get after an attack
l->setText(QLabel::tr("% Retreat Time"));
l->setWhatsThis(wtGetAwayTime);
l->setWordWrap(true);
@@ -502,6 +520,9 @@
L_name->setText(QLabel::tr("Scheme Name:"));
LE_name = new QLineEdit(this);
+ QRegExp rx(*cSafeFileNameRegExp);
+ QRegExpValidator * val = new QRegExpValidator(rx, LE_name);
+ LE_name->setValidator(val);
LE_name->setWhatsThis(tr("Name of this scheme"));
gl->addWidget(LE_name,15,1,1,5);
@@ -518,8 +539,11 @@
bottomLayout->addWidget(selectScheme, 0);
BtnCopy = addButton(tr("Copy"), bottomLayout, 1);
+ BtnCopy->setStyleSheet("padding: 5px;");
BtnNew = addButton(tr("New"), bottomLayout, 2);
+ BtnNew->setStyleSheet("padding: 5px;");
BtnDelete = addButton(tr("Delete"), bottomLayout, 3);
+ BtnDelete->setStyleSheet("padding: 5px;");
bottomLayout->setStretch(1,1);
bottomLayout->setStretch(2,1);
@@ -540,6 +564,7 @@
PageScheme::PageScheme(QWidget* parent) : AbstractPage(parent)
{
+ changingSchemes = false;
initPage();
}
@@ -594,24 +619,33 @@
mapper->addMapping(LE_ScriptParam, 43);
mapper->toFirst();
+
+ connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(dataChanged(QModelIndex, QModelIndex)));
}
void PageScheme::newRow()
{
+ changingSchemes = true;
QAbstractItemModel * model = mapper->model();
model->insertRow(-1);
selectScheme->setCurrentIndex(model->rowCount() - 1);
+ changingSchemes = false;
+ checkDupe();
}
void PageScheme::copyRow()
{
+ changingSchemes = true;
QAbstractItemModel * model = mapper->model();
model->insertRow(selectScheme->currentIndex());
selectScheme->setCurrentIndex(model->rowCount() - 1);
+ changingSchemes = false;
+ checkDupe();
}
void PageScheme::deleteRow()
{
+ changingSchemes = true;
int numberOfDefaultSchemes = ((GameSchemeModel*)mapper->model())->numberOfDefaultSchemes;
if (selectScheme->currentIndex() < numberOfDefaultSchemes)
{
@@ -619,6 +653,7 @@
deniedMsg.setIcon(QMessageBox::Warning);
deniedMsg.setWindowTitle(QMessageBox::tr("Schemes - Warning"));
deniedMsg.setText(QMessageBox::tr("Cannot delete default scheme '%1'!").arg(selectScheme->currentText()));
+ deniedMsg.setTextFormat(Qt::PlainText);
deniedMsg.setWindowModality(Qt::WindowModal);
deniedMsg.exec();
}
@@ -628,6 +663,7 @@
reallyDeleteMsg.setIcon(QMessageBox::Question);
reallyDeleteMsg.setWindowTitle(QMessageBox::tr("Schemes - Are you sure?"));
reallyDeleteMsg.setText(QMessageBox::tr("Do you really want to delete the game scheme '%1'?").arg(selectScheme->currentText()));
+ reallyDeleteMsg.setTextFormat(Qt::PlainText);
reallyDeleteMsg.setWindowModality(Qt::WindowModal);
reallyDeleteMsg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
@@ -637,6 +673,15 @@
model->removeRow(selectScheme->currentIndex());
}
}
+ changingSchemes = false;
+}
+
+void PageScheme::dataChanged(QModelIndex topLeft, QModelIndex bottomRight)
+{
+ Q_UNUSED(bottomRight)
+ if(topLeft.column() == 0) {
+ checkDupe();
+ };
}
void PageScheme::schemeSelected(int n)
@@ -646,6 +691,43 @@
gbBasicSettings->setEnabled(n >= c);
LE_name->setEnabled(n >= c);
L_name->setEnabled(n >= c);
+ checkDupe();
}
+// Check for duplicates and rename scheme if duplicate found
+void PageScheme::checkDupe()
+{
+ if (changingSchemes)
+ {
+ return;
+ }
+ int except = selectScheme->currentIndex();
+ QString name = selectScheme->currentText();
+ GameSchemeModel* model = (GameSchemeModel*)mapper->model();
+ bool dupe = model->hasScheme(name, except);
+ if (dupe)
+ {
+ QString newName;
+ //name already used -> look for an appropriate name
+ int i=2;
+ while(model->hasScheme(newName = tr("%1 (%2)").arg(name).arg(i++), except))
+ {
+ if(i > 1000)
+ {
+ return;
+ }
+ }
+ LE_name->setText(newName);
+ selectScheme->setCurrentText(newName);
+ model->renameScheme(except, newName);
+ QMessageBox dupeMsg(this);
+ dupeMsg.setIcon(QMessageBox::Warning);
+ dupeMsg.setWindowTitle(QMessageBox::tr("Schemes - Name already taken"));
+ dupeMsg.setTextFormat(Qt::PlainText);
+ dupeMsg.setText(QMessageBox::tr("A scheme with the name '%1' already exists. Your scheme has been renamed to '%2'.").arg(name).arg(newName));
+ dupeMsg.setWindowModality(Qt::WindowModal);
+ dupeMsg.exec();
+ }
+}
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagescheme.h
--- a/QTfrontend/ui/page/pagescheme.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagescheme.h Wed Jul 31 23:14:27 2019 +0200
@@ -23,6 +23,7 @@
#include "togglebutton.h"
class FreqSpinBox;
+class SDTimeoutSpinBox;
class MinesTimeSpinBox;
class PageScheme : public AbstractPage
@@ -80,7 +81,7 @@
QSpinBox * SB_DamageModifier;
QSpinBox * SB_TurnTime;
QSpinBox * SB_InitHealth;
- QSpinBox * SB_SuddenDeath;
+ SDTimeoutSpinBox * SB_SuddenDeath;
QSpinBox * SB_WaterRise;
QSpinBox * SB_HealthDecrease;
FreqSpinBox * SB_CaseProb;
@@ -101,8 +102,13 @@
QGroupBox * gbGameModes;
QGroupBox * gbBasicSettings;
+ bool changingSchemes;
+
+ void checkDupe();
+
private slots:
void schemeSelected(int);
+ void dataChanged(QModelIndex topLeft, QModelIndex bottomRight);
};
#endif
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pageselectweapon.cpp
--- a/QTfrontend/ui/page/pageselectweapon.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pageselectweapon.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -46,11 +46,15 @@
// first row
BtnNew = addButton(tr("New"), bottomLayout, 0, 1);
+ BtnNew->setStyleSheet("padding: 3px;");
BtnDefault = addButton(tr("Default"), bottomLayout, 0, 2);
+ BtnDefault->setStyleSheet("padding: 3px;");
// second row
BtnCopy = addButton(tr("Copy"), bottomLayout, 1, 1);
+ BtnCopy->setStyleSheet("padding: 3px;");
BtnDelete = addButton(tr("Delete"), bottomLayout, 1, 2);
+ BtnDelete->setStyleSheet("padding: 3px;");
bottomLayout->setColumnStretch(1,1);
bottomLayout->setColumnStretch(2,1);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagesingleplayer.cpp
--- a/QTfrontend/ui/page/pagesingleplayer.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagesingleplayer.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -48,7 +48,7 @@
BtnCampaignPage->setVisible(true);
BtnTrainPage = addButton(":/res/Trainings.png", middleLine, 1, true);
- BtnTrainPage->setWhatsThis(tr("Practice your skills in a range of training missions"));
+ BtnTrainPage->setWhatsThis(tr("Singleplayer missions: Learn how to play in the training, practice your skills in challenges or try to complete goals in scenarios."));
return vLayout;
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagetraining.cpp
--- a/QTfrontend/ui/page/pagetraining.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagetraining.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -28,6 +28,7 @@
#include
#include
+#include "mission.h"
#include "hwconsts.h"
#include "DataManager.h"
@@ -37,26 +38,32 @@
{
QGridLayout * pageLayout = new QGridLayout();
-// left column
-
// declare start button, caption and description
btnPreview = formattedButton(":/res/Trainings.png", true);
- // make both rows equal height
+ // tweak widget spacing
pageLayout->setRowStretch(0, 1);
pageLayout->setRowStretch(1, 1);
+ pageLayout->setRowStretch(2, 1);
+ pageLayout->setColumnStretch(0, 5);
+ pageLayout->setColumnStretch(1, 1);
+ pageLayout->setColumnStretch(2, 9);
+ pageLayout->setColumnStretch(3, 5);
- // add start button, caption and description to 3 different rows
- pageLayout->addWidget(btnPreview, 0, 0);
+ QWidget * infoWidget = new QWidget();
+ QHBoxLayout * infoLayout = new QHBoxLayout();
+ // add preview, caption and description
+ infoWidget->setLayout(infoLayout);
+ infoLayout->addWidget(btnPreview);
// center preview
- pageLayout->setAlignment(btnPreview, Qt::AlignRight | Qt::AlignVCenter);
-
-
-// right column
+ infoLayout->setAlignment(btnPreview, Qt::AlignRight | Qt::AlignVCenter);
// info area (caption on top, description below)
- QVBoxLayout * infoLayout = new QVBoxLayout();
+ QWidget * infoTextWidget = new QWidget();
+ QVBoxLayout * infoTextLayout = new QVBoxLayout();
+ infoTextWidget->setObjectName("trainingInfo");
+ infoTextWidget->setLayout(infoTextLayout);
lblCaption = new QLabel();
lblCaption->setMinimumWidth(360);
@@ -66,38 +73,51 @@
lblDescription->setMinimumWidth(360);
lblDescription->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
lblDescription->setWordWrap(true);
+ lblHighscores = new QLabel();
+ lblHighscores->setMinimumWidth(360);
+ lblHighscores->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
- infoLayout->addWidget(lblCaption);
- infoLayout->addWidget(lblDescription);
+ infoTextLayout->addWidget(lblCaption);
+ infoTextLayout->addWidget(lblDescription);
+ infoTextLayout->addWidget(lblHighscores);
- pageLayout->addLayout(infoLayout, 0, 1);
- pageLayout->setAlignment(infoLayout, Qt::AlignLeft);
+ infoLayout->addWidget(infoTextWidget);
+
+ pageLayout->addWidget(infoWidget, 0, 1, 1, 2); // span 2 columns
+ pageLayout->setAlignment(infoTextWidget, Qt::AlignLeft);
// tab widget containing all lists
tbw = new QTabWidget(this);
- pageLayout->addWidget(tbw, 1, 0, 1, 2); // span 2 columns
+ pageLayout->addWidget(tbw, 1, 0, 1, 4); // span 4 columns
// let's not make the tab widget use more space than needed
tbw->setFixedWidth(400);
pageLayout->setAlignment(tbw, Qt::AlignHCenter);
-
- tbw->setStyleSheet("QListWidget { border-style: none; padding-top: 6px; }");
// training/challenge/scenario lists
lstTrainings = new QListWidget(this);
lstTrainings ->setWhatsThis(tr("Pick the training to play"));
+ lstTrainings ->setObjectName("trainingList");
lstChallenges = new QListWidget(this);
lstChallenges ->setWhatsThis(tr("Pick the challenge to play"));
+ lstChallenges ->setObjectName("trainingList");
lstScenarios= new QListWidget(this);
lstScenarios->setWhatsThis(tr("Pick the scenario to play"));
+ lstScenarios->setObjectName("trainingList");
tbw->addTab(lstTrainings, tr("Trainings"));
tbw->addTab(lstChallenges, tr("Challenges"));
tbw->addTab(lstScenarios, tr("Scenarios"));
tbw->setCurrentWidget(lstTrainings);
+ QLabel* lblteam = new QLabel(tr("Team"));
+ CBTeam = new QComboBox(this);
+ CBTeam->setMaxVisibleItems(30);
+ pageLayout->addWidget(lblteam, 2, 1);
+ pageLayout->addWidget(CBTeam, 2, 2);
+
return pageLayout;
}
@@ -197,6 +217,7 @@
// first, load scripts in order specified in order.cfg (if present)
QFile orderFile(QString("physfs://Missions/%1/order.cfg").arg(subFolder));
QStringList orderedMissions;
+
if (orderFile.open(QFile::ReadOnly))
{
QString m_id;
@@ -284,7 +305,7 @@
list = (QListWidget*) tbw->currentWidget();
QListWidgetItem * curItem = list->currentItem();
- if (curItem != NULL)
+ if ((curItem != NULL) && (CBTeam->currentIndex() != -1))
emit startMission(curItem->data(Qt::UserRole).toString(), getSubFolderOfSelected());
}
@@ -299,37 +320,70 @@
list = (QListWidget*) tbw->currentWidget();
if (list->currentItem())
{
- // TODO also use .pngs in userdata folder
+ QString missionName = list->currentItem()->data(Qt::UserRole).toString();
QString thumbFile = "physfs://Graphics/Missions/" +
subFolder + "/" +
- list->currentItem()->data(Qt::UserRole).toString() +
+ missionName +
"@2x.png";
if (QFile::exists(thumbFile))
btnPreview->setIcon(QIcon(thumbFile));
+ else if (tbw->currentWidget() == lstChallenges)
+ btnPreview->setIcon(QIcon(":/res/Challenges.png"));
+ else if (tbw->currentWidget() == lstScenarios)
+ // TODO: Prettier scenario fallback image
+ btnPreview->setIcon(QIcon(":/res/Scenarios.png"));
else
btnPreview->setIcon(QIcon(":/res/Trainings.png"));
btnPreview->setWhatsThis(tr("Start fighting"));
- QString realName = list->currentItem()->data(
- Qt::UserRole).toString();
-
- QString caption = m_info->value(realName + ".name",
+ QString caption = m_info->value(missionName + ".name",
list->currentItem()->text()).toString();
- QString description = m_info->value(realName + ".desc",
+ QString description = m_info->value(missionName + ".desc",
tr("No description available")).toString();
lblCaption->setText("" + caption +" ");
lblDescription->setText(description);
+
+ // Challenge highscores
+ QString highscoreText = QString("");
+ QString teamName = CBTeam->currentText();
+ if (missionValueExists(missionName, teamName, "Highscore"))
+ highscoreText = highscoreText +
+ //: Highest score of a team
+ tr("Team highscore: %1")
+ .arg(getMissionValue(missionName, teamName, "Highscore").toString()) + "\n";
+ if (missionValueExists(missionName, teamName, "Lowscore"))
+ highscoreText = highscoreText +
+ //: Lowest score of a team
+ tr("Team lowscore: %1")
+ .arg(getMissionValue(missionName, teamName, "Lowscore").toString()) + "\n";
+ if (missionValueExists(missionName, teamName, "AccuracyRecord"))
+ highscoreText = highscoreText +
+ //: Best accuracy of a team (in a challenge)
+ tr("Team's top accuracy: %1%")
+ .arg(getMissionValue(missionName, teamName, "AccuracyRecord").toString()) + "\n";
+ if (missionValueExists(missionName, teamName, "TimeRecord"))
+ {
+ double time = ((double) getMissionValue(missionName, teamName, "TimeRecord").toInt()) / 1000.0;
+ highscoreText = highscoreText + tr("Team's best time: %L1 s").arg(time, 0, 'f', 3) + "\n";
+ }
+ if (missionValueExists(missionName, teamName, "TimeRecordHigh"))
+ {
+ double time = ((double) getMissionValue(missionName, teamName, "TimeRecordHigh").toInt()) / 1000.0;
+ highscoreText = highscoreText + tr("Team's longest time: %L1 s").arg(time, 0, 'f', 3) + "\n";
+ }
+
+ lblHighscores->setText(highscoreText);
}
else
{
btnPreview->setIcon(QIcon(":/res/Trainings.png"));
lblCaption->setText(tr("Select a mission!"));
- // TODO better text and tr()
lblDescription->setText("");
+ lblHighscores->setText("");
}
}
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagetraining.h
--- a/QTfrontend/ui/page/pagetraining.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagetraining.h Wed Jul 31 23:14:27 2019 +0200
@@ -27,7 +27,15 @@
public:
PageTraining(QWidget* parent = 0);
+ QListWidget * lstTrainings;
+ QListWidget * lstChallenges;
+ QListWidget * lstScenarios;
+ QPushButton * btnPreview;
+ QPushButton * btnStart;
+ QComboBox * CBTeam;
+ public slots:
+ void updateInfo();
signals:
void startMission(const QString & scriptName, const QString & subFolder);
@@ -40,21 +48,16 @@
private:
- QPushButton * btnPreview;
- QPushButton * btnStart;
QLabel * lblCaption;
QLabel * lblDescription;
+ QLabel * lblHighscores;
QTabWidget * tbw;
- QListWidget * lstTrainings;
- QListWidget * lstChallenges;
- QListWidget * lstScenarios;
QSettings * m_info;
QString getSubFolderOfSelected();
private slots:
void startSelected();
- void updateInfo();
};
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagevideos.cpp
--- a/QTfrontend/ui/page/pagevideos.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagevideos.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -48,6 +48,7 @@
#include "gameuiconfig.h"
#include "recorder.h"
#include "ask_quit.h"
+#include "util/MessageDialog.h"
static const QSize ThumbnailSize(350, 350*3/5);
@@ -122,14 +123,19 @@
filesTable->setSelectionBehavior(QAbstractItemView::SelectRows);
filesTable->setSelectionMode(QAbstractItemView::SingleSelection);
filesTable->setEditTriggers(QAbstractItemView::SelectedClicked);
+ filesTable->setShowGrid(false);
+ filesTable->setAlternatingRowColors(true);
filesTable->verticalHeader()->hide();
filesTable->setMinimumWidth(400);
- QHeaderView * header = filesTable->horizontalHeader();
+ QHeaderView * header = new QHeaderView(Qt::Horizontal, filesTable);
+ filesTable->setHorizontalHeader(header);
+ header = filesTable->horizontalHeader();
+ header->setStretchLastSection(true);
+ header->setSectionsClickable(false);
header->setSectionResizeMode(vcName, QHeaderView::ResizeToContents);
header->setSectionResizeMode(vcSize, QHeaderView::Fixed);
header->resizeSection(vcSize, 100);
- header->setStretchLastSection(true);
btnOpenDir = new QPushButton(QPushButton::tr("Open videos directory"), pTableGroup);
btnOpenDir->setWhatsThis(QPushButton::tr("Open the video directory in your system"));
@@ -170,9 +176,9 @@
Qt::TextSelectableByKeyboard |
Qt::LinksAccessibleByMouse |
Qt::LinksAccessibleByKeyboard);
- labelDesc->setTextFormat(Qt::RichText);
+ labelDesc->setTextFormat(Qt::PlainText);
labelDesc->setWordWrap(true);
- labelDesc->setOpenExternalLinks(true);
+ labelDesc->setOpenExternalLinks(false);
// buttons: play and delete
btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup);
@@ -215,6 +221,8 @@
{
nameChangedFromCode = false;
numRecorders = 0;
+ // Clear VideoTemp at launch in case some garbage remained in here after a crash
+ clearTemp();
initPage();
}
@@ -287,6 +295,7 @@
VideoItem * item = nameItem(row);
item->seen = true;
item->desc = "";
+ setName(item, item->name);
updateSize(row);
}
@@ -315,6 +324,7 @@
progressBar->setValue(0);
connect(pRecorder, SIGNAL(onProgress(float)), this, SLOT(updateProgress(float)));
connect(pRecorder, SIGNAL(encodingFinished(bool)), this, SLOT(encodingFinished(bool)));
+ connect(pRecorder, SIGNAL(ErrorMessage(const QString &)), this, SLOT(ShowFatalErrorMessage(const QString &)), Qt::QueuedConnection);
filesTable->setCellWidget(row, vcProgress, progressBar);
numRecorders++;
@@ -395,15 +405,40 @@
// user has edited filename, so we should rename the file
VideoItem * item = nameItem(row);
QString oldName = item->name;
+ int pointPos = oldName.lastIndexOf('.');
+ QString oldPrefix = item->name;
+ oldPrefix.truncate(pointPos);
QString newName = item->text();
if (!newName.contains('.')) // user forgot an extension
{
// restore old extension
- int pt = oldName.lastIndexOf('.');
- if (pt != -1)
+ pointPos = oldName.lastIndexOf('.');
+ if (pointPos != -1)
+ {
+ newName += oldName.right(oldName.length() - pointPos);
+ setName(item, newName);
+ }
+ }
+ QString newPrefix;
+ if (newName.contains('.'))
+ {
+ pointPos = newName.lastIndexOf('.');
+ if (pointPos != -1)
{
- newName += oldName.right(oldName.length() - pt);
- setName(item, newName);
+ newPrefix = newName;
+ newPrefix.truncate(pointPos);
+ }
+ }
+ else
+ newPrefix = newName;
+ for (int i = 0; i < filesTable->rowCount(); i++)
+ {
+ // don't allow rename if duplicate prefix
+ VideoItem * iterateItem = nameItem(i);
+ if ((i != row) && (newPrefix == iterateItem->prefix))
+ {
+ setName(item, oldName);
+ return;
}
}
#ifdef Q_OS_WIN
@@ -421,7 +456,13 @@
setName(item, oldName);
return;
}
+ if (item->ready())
+ {
+ cfgdir->rename("VideoThumbnails/" + oldPrefix + ".png", "VideoThumbnails/" + newPrefix + ".png");
+ cfgdir->rename("VideoThumbnails/" + oldPrefix + ".bmp", "VideoThumbnails/" + newPrefix + ".bmp");
+ }
item->name = newName;
+ item->prefix = newPrefix;
updateDescription();
}
@@ -431,6 +472,15 @@
item->setText(newName);
nameChangedFromCode = false;
item->name = newName;
+ // try to extract prefix
+ if (item->ready())
+ item->prefix = item->name;
+ else
+ item->prefix = item->pRecorder->name;
+ // remove extension
+ int pt = item->prefix.lastIndexOf('.');
+ if (pt != -1)
+ item->prefix.truncate(pt);
}
int PageVideos::appendRow(const QString & name)
@@ -517,7 +567,7 @@
desc += item->desc + '\n';
}
- if (item->prefix.isEmpty())
+ if (item->prefix.isNull() || item->prefix.isEmpty())
{
// try to extract prefix from file name instead
if (item->ready())
@@ -531,13 +581,11 @@
item->prefix.truncate(pt);
}
- desc.replace("\n", " ");
-
labelDesc->setText(desc);
if (!item->prefix.isEmpty())
{
- QString thumbName = cfgdir->absoluteFilePath("VideoTemp/" + item->prefix);
+ QString thumbName = cfgdir->absoluteFilePath("VideoThumbnails/" + item->prefix);
QPixmap pic;
if (pic.load(thumbName + ".png") || pic.load(thumbName + ".bmp"))
{
@@ -588,21 +636,22 @@
reallyDeleteMsg.setIcon(QMessageBox::Question);
reallyDeleteMsg.setWindowTitle(QMessageBox::tr("Videos - Are you sure?"));
reallyDeleteMsg.setText(QMessageBox::tr("Do you really want to delete the video '%1'?").arg(item->name));
+ reallyDeleteMsg.setTextFormat(Qt::PlainText);
reallyDeleteMsg.setWindowModality(Qt::WindowModal);
reallyDeleteMsg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
if (reallyDeleteMsg.exec() != QMessageBox::Ok)
return;
- // remove
+ // abort / remove
if (!item->ready())
- item->pRecorder->deleteLater();
+ item->pRecorder->abort();
else
{
cfgdir->remove("Videos/" + item->name);
// we have no idea whether screenshot is going to be bmp or png so let's delete both
- cfgdir->remove("VideoTemp/" + item->prefix + ".png");
- cfgdir->remove("VideoTemp/" + item->prefix + ".bmp");
+ cfgdir->remove("VideoThumbnails/" + item->prefix + ".png");
+ cfgdir->remove("VideoThumbnails/" + item->prefix + ".bmp");
}
// this code is for removing several files when multiple selection is enabled
@@ -661,14 +710,21 @@
QDesktopServices::openUrl(QUrl("file:///" + path));
}
-// clear VideoTemp directory (except for thumbnails)
+// clear VideoTemp directory
void PageVideos::clearTemp()
{
+ qDebug("Clearing VideoTemp directory ...");
QDir temp(cfgdir->absolutePath() + "/VideoTemp");
QStringList files = temp.entryList(QDir::Files);
foreach (const QString& file, files)
{
- if (!file.endsWith(".bmp") && !file.endsWith(".png"))
+ // Legacy support: Move thumbnails to correct dir
+ if (file.endsWith(".bmp") || file.endsWith(".png"))
+ {
+ qDebug("Moving video thumbnail '%s' to VideoThumbnails directory", qPrintable(file));
+ cfgdir->rename("VideoTemp/" + file, "VideoThumbnails/" + file);
+ }
+ else
temp.remove(file);
}
}
@@ -744,3 +800,8 @@
}
}
+void PageVideos::ShowFatalErrorMessage(const QString & msg)
+{
+ MessageDialog::ShowFatalMessage(msg, this);
+}
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/page/pagevideos.h
--- a/QTfrontend/ui/page/pagevideos.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/page/pagevideos.h Wed Jul 31 23:14:27 2019 +0200
@@ -86,6 +86,7 @@
void deleteSelectedFiles();
void openVideosDirectory();
void updateFileList(const QString & path);
+ void ShowFatalErrorMessage(const QString & msg);
};
#endif // PAGE_VIDEOS_H
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/SDTimeoutSpinBox.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/ui/widget/SDTimeoutSpinBox.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,49 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2015 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @brief SDTimeoutSpinBox class implementation
+ */
+
+#include "SDTimeoutSpinBox.h"
+
+SDTimeoutSpinBox::SDTimeoutSpinBox(QWidget* parent) : QSpinBox(parent)
+{
+ // do nothing
+};
+
+
+QString SDTimeoutSpinBox::textFromValue(int internalValue) const
+{
+ // user-facing value = internal value + 1
+ return QString::number(internalValue + 1);
+}
+
+int SDTimeoutSpinBox::valueFromText(const QString & userFacingString) const
+{
+ // internal value = user-facing value - 1
+ bool ok;
+ int value = userFacingString.toInt(&ok);
+
+ if (ok)
+ return value - 1;
+ // Fallback
+ else
+ return 15;
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/SDTimeoutSpinBox.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/ui/widget/SDTimeoutSpinBox.h Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,65 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2015 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @brief SDTimeoutSpinBox class definition
+ */
+
+#ifndef HEDGEWARS_SDTIMEOUTSPINBOX_H
+#define HEDGEWARS_SDTIMEOUTSPINBOX_H
+
+#include
+#include
+
+/**
+ * SpinBox
for Sudden Death timeout.
+ * The internally stored Sudden Death timeout is different
+ * from the actual number of rounds it takes until SD starts.
+ * e.g. value 0 means SD starts in 2nd round
+ * @author Wuzzy
+ * @since 0.9.25
+ */
+class SDTimeoutSpinBox : public QSpinBox
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * @brief Class constructor.
+ * @param parent parent widget.
+ */
+ SDTimeoutSpinBox(QWidget * parent);
+
+ protected:
+ /**
+ * Returns its value in real number of rounds.
+ * @param internal value integer value to be represented as string.
+ * @return the real number of rounds
+ */
+ QString textFromValue(int value) const;
+ /**
+ * Returns the internally-used value for SD timeout.
+ * @param user-facing string, i.e. real number of rounds
+ * @return internally-stored SD timeout value
+ */
+ int valueFromText(const QString & text) const;
+};
+
+
+#endif // HEDGEWARS_SDTIMEOUTSPINBOX_H
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/about.cpp
--- a/QTfrontend/ui/widget/about.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/about.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -32,6 +32,8 @@
#include "SDL.h"
#include "SDL_version.h"
#include "physfs.h"
+#include "creditsmessages.h"
+#include "HWApplication.h"
#ifdef VIDEOREC
extern "C"
@@ -53,6 +55,193 @@
#include "about.h"
+QString About::getCreditsHtml()
+{
+ // Open the credits file
+
+ /* *** FILE FORMAT OF CREDITS FILE ***
+ The credits file is an RFC-4180-compliant CSV file with 5 columns.
+ The first column (column 1) is always 1 letter long and is the row type.
+ The row type determines the meaning of the other columns.
+
+ The following row types are supported:
+
+ * E: Credits entry
+ * Column 2: Task/contribution
+ * Column 3: Contributor name
+ * Column 4: Contributor e-mail
+ * Column 5: Contributor nickname
+ * M: Alternative credits entry that is a placeholder for other or unknown authors
+ * Columns 2-5: Unused
+ * S: Section
+ * Column 2: Section name
+ * Columns 3-5: Unused
+ * U: Subsection
+ * Column 2: Subsection name
+ * Columns 3-5: Unused
+
+ Columns 2, 3 and 5 MUST be in US-ASCII.
+ */
+ QFile creditsFile(":/res/credits.csv");
+ if (!creditsFile.open(QIODevice::ReadOnly))
+ {
+ qWarning("ERROR: Credits file could not be opened!");
+ return "ERROR: Credits file could not be opened!
";
+ }
+ QString creditsString = creditsFile.readAll();
+ QString out = QString("" + tr("Credits") + " \n");
+ QStringList cells = QStringList() << QString("") << QString("") << QString("") << QString("") << QString("");
+ bool firstSection = true;
+ unsigned long int column = 0;
+ unsigned long int charInCell = 0;
+ bool isInQuote = false;
+ bool ignoreChar = false;
+ bool lineComplete = false;
+ QChar currChar;
+ QChar prevChar;
+ for(long long int i = 0; i 0 && prevChar == '"' && (currChar == '\r' || currChar == ','))
+ {
+ isInQuote = false;
+ ignoreChar = true;
+ }
+
+ charInCell++;
+ if(!isInQuote && currChar == ',')
+ {
+ column++;
+ charInCell = 0;
+ }
+ else if(!isInQuote && currChar == '\n' && prevChar == '\r')
+ {
+ lineComplete = true;
+ }
+ if(!isInQuote && (currChar == '\r' || currChar == '\n' || currChar == ','))
+ {
+ ignoreChar = true;
+ }
+
+
+ if(!ignoreChar)
+ {
+ cells[column].append(currChar);
+ }
+ ignoreChar = false;
+
+ if(lineComplete)
+ {
+ type = cells[0];
+ task = cells[1];
+ name = cells[2];
+ mail = cells[3];
+ nick = cells[4];
+
+ if(type == "S")
+ {
+ // section
+ if (!firstSection)
+ out = out + "\n";
+ out = out + "" + HWApplication::translate("credits", task.toLatin1().constData()) + " \n\n";
+ firstSection = false;
+ }
+ else if(type == "U")
+ {
+ // subsection
+ out = out + " \n";
+ out = out + "" + HWApplication::translate("credits", task.toLatin1().constData()) + " \n\n";
+ }
+ else if(type == "M")
+ {
+ // other people
+ out = out + "" + tr("Other people") + " " + "\n";
+ }
+ else if(type == "E")
+ {
+ QString showName = QString("");
+ if(!name.isEmpty())
+ name = ""+name+" ";
+ if(!nick.isEmpty())
+ nick= ""+nick+" ";
+ if(!name.isEmpty() && !nick.isEmpty())
+ showName = tr("%1 (alias %2)").arg(name).arg(nick);
+ else if(name.isEmpty() && !nick.isEmpty())
+ showName = nick;
+ else if(!name.isEmpty() && nick.isEmpty())
+ showName = name;
+ // credits list entry
+ QString mailLink = QString("%1 ").arg(mail);
+ if(task.isEmpty() && mail.isEmpty() && !showName.isEmpty())
+ {
+ // Name only
+ out = out + "" + showName + " \n";
+ }
+ else if(showName.isEmpty() && mail.isEmpty() && !task.isEmpty())
+ {
+ // Task only
+ out = out + "" + HWApplication::translate("credits", task.toLatin1().constData()) + " \n";
+ }
+ else if(task.isEmpty())
+ {
+ // Name and e-mail
+ //: Part of credits. %1: Contributor name. %2: E-mail address
+ out = out + "" + tr("%1 <%2>").arg(showName).arg(mailLink) + " \n";
+ }
+ else if(mail.isEmpty())
+ {
+ // Contribution and name
+ //: Part of credits. %1: Description of contribution. %2: Contributor name
+ out = out + "" + tr("%1: %2")
+ .arg(HWApplication::translate("credits", task.toLatin1().constData()))
+ .arg(showName)
+ + " \n";
+ }
+ else
+ {
+ // Contribution, name and e-mail
+ //: Part of credits. %1: Description of contribution. %2: Contributor name. %3: E-mail address
+ out = out + "" + tr("%1: %2 <%3>")
+ .arg(HWApplication::translate("credits", task.toLatin1().constData()))
+ .arg(showName)
+ .arg(mailLink)
+ + " \n";
+ }
+ }
+ else
+ {
+ qWarning("Invalid row type in credits.csv: %s", qPrintable(type));
+ }
+ lineComplete = false;
+ column = 0;
+ cells[0] = "";
+ cells[1] = "";
+ cells[2] = "";
+ cells[3] = "";
+ cells[4] = "";
+ charInCell = 0;
+ }
+
+ prevChar = currChar;
+ }
+ creditsFile.close();
+ out = out + " ";
+ return out;
+}
+
About::About(QWidget * parent) :
QWidget(parent)
{
@@ -89,23 +278,51 @@
lbl1->setWordWrap(true);
mainLayout->addWidget(lbl1, 0, 1);
- lbl2 = new QTextBrowser(this);
- lbl2->setOpenExternalLinks(true);
- QUrl localpage = QUrl::fromLocalFile(":/res/html/about.html");
- lbl2->setSource(localpage); //sets the source of the label from the file above
- mainLayout->addWidget(lbl2, 1, 1);
+ /* Credits */
+ creditsBrowser = new QTextBrowser(this);
+ creditsBrowser->setOpenExternalLinks(true);
+ QString credits = getCreditsHtml();
+
+ QString header =
+ ""
+ ""
+ ""
+ "Hedgewars Credits "
+ " "
+ ""
+ ""
+ ""
+ "";
+ QString footer =
+ ""
+ "" + tr("Extended Credits") + " "
+ "" + tr("An extended credits list can be found in the CREDITS text file.") + "
"
+ "";
+
+ creditsBrowser->setHtml(header + credits + footer);
+ mainLayout->addWidget(creditsBrowser, 1, 1);
/* Library information */
//: For the version numbers of Hedgewars' software dependencies
QString libinfo = QString(tr("Dependency versions:") + QString(" "));
-#ifdef __GNUC__
+#if defined(__GNUC__)
libinfo.append(QString(tr("GCC : %1")).arg(__VERSION__));
+#elif defined(WIN32_VCPKG)
+ libinfo.append(QString(tr("VC++ : %1")).arg(_MSC_FULL_VER));
+#elif defined(__VERSION__)
+ libinfo.append(QString(tr("Unknown Compiler: %1")).arg(__VERSION__));
+#else
+ libinfo.append(QString(tr("Unknown Compiler")));
+#endif
libinfo.append(QString(" "));
-#else
- libinfo.append(QString(tr("Unknown Compiler")).arg(__VERSION__) + QString(" "));
-#endif
const SDL_version *sdl_ver;
SDL_version sdl_version;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/about.h
--- a/QTfrontend/ui/widget/about.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/about.h Wed Jul 31 23:14:27 2019 +0200
@@ -35,7 +35,8 @@
virtual void dropEvent(QDropEvent * event);
private:
- QTextBrowser * lbl2;
+ QString getCreditsHtml();
+ QTextBrowser * creditsBrowser;
};
#endif // _ABOUT_H
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/chatwidget.cpp
--- a/QTfrontend/ui/widget/chatwidget.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/chatwidget.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -201,8 +201,7 @@
mainLayout.setMargin(0);
QWidget * leftSideContainer = new QWidget();
- leftSideContainer->setObjectName("leftSideContainer");
- leftSideContainer->setStyleSheet("#leftSideContainer { border-width: 0px; background-color: #ffcc00; border-radius: 10px;} QTextBrowser, SmartLineEdit { background-color: rgb(13, 5, 68); }");
+ leftSideContainer->setObjectName("chatContainer");
QVBoxLayout * leftSide = new QVBoxLayout(leftSideContainer);
leftSide->setSpacing(3);
leftSide->setMargin(3);
@@ -217,7 +216,7 @@
chatText->setMinimumWidth(10);
chatText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
chatText->setOpenLinks(false);
- chatText->setStyleSheet("QTextBrowser { background-color: rgb(23, 11, 54); border-width: 0px; }");
+ chatText->setObjectName("chatText");
connect(chatText, SIGNAL(anchorClicked(const QUrl&)),
this, SLOT(linkClicked(const QUrl&)));
leftSide->addWidget(chatText, 1);
@@ -308,7 +307,7 @@
void HWChatWidget::linkClicked(const QUrl & link)
{
- if ((link.scheme() == "http") or (link.scheme() == "https"))
+ if ((link.scheme() == "http") || (link.scheme() == "https"))
QDesktopServices::openUrl(link);
else if (link.scheme() == "hwnick")
{
@@ -373,15 +372,20 @@
// it as host would convert it to it's lower case variant
QString HWChatWidget::linkedNick(const QString & nickname)
{
- if (nickname != m_userNick)
+ // '[' and '(' are reserved characters used for fake player names in special server messages
+ if ((nickname != m_userNick) && (!nickname.startsWith('[')) && (!nickname.startsWith('(')))
+ // linked nick
return QString("%2 ").arg(
QString(nickname.toUtf8().toBase64())).arg(nickname.toHtmlEscaped());
- // unlinked nick (if own one)
+ // unlinked nick (if own one or fake player name)
return QString("%1 ").arg(nickname.toHtmlEscaped());
}
-const QRegExp HWChatWidget::URLREGEXP = QRegExp("(http(s)?://)?(www\\.)?((([^/:?]+\\.)?hedgewars\\.org|code\\.google\\.com|googlecode\\.com|hh\\.unit22\\.org)(/[^ ]*)?)");
+// Regex to make some URLs clickable for selected domains:
+// - hedgewars.org (official website)
+// - hh.unit22.org (community addon server)
+const QRegExp HWChatWidget::URLREGEXP = QRegExp("(http(s)?://)?(www\\.)?((([^/:?]+\\.)?hedgewars\\.org|hh\\.unit22\\.org)(/[^ ]*)?)");
bool HWChatWidget::containsHighlight(const QString & sender, const QString & message)
{
@@ -401,7 +405,7 @@
QString HWChatWidget::messageToHTML(const QString & message)
{
QString formattedStr = message.toHtmlEscaped();
- // link some urls
+ // link some URLs
formattedStr = formattedStr.replace(URLREGEXP, "\\4 ");
return formattedStr;
}
@@ -516,10 +520,18 @@
if (!isIgnored)
printChatString(nick, QString("*** ") + tr("%1 has joined").arg(linkedNick(nick)), "Join", false);
- if (notifyNick && notify && (m_helloSounds.size() > 0))
+ if (notifyNick && notify)
{
- SDLInteraction::instance().playSoundFile(
+ if (m_helloSounds.size() > 0)
+ {
+ SDLInteraction::instance().playSoundFile(
m_helloSounds.at(rand() % m_helloSounds.size()));
+ }
+
+ if (!isInGame())
+ {
+ HWApplication::alert(this, 2000);
+ }
}
}
@@ -534,10 +546,16 @@
emit nickCountUpdate(chatNicks->model()->rowCount());
- if (message.isEmpty())
+ // Normal quit
+ if (message.isEmpty() || message == "bye")
+ {
printChatString(nick, QString("*** ") + tr("%1 has left").arg(linkedNick(nick)), "Leave", false);
+ }
+ // Quit with additional server message (i.e. ping timeout)
else
- printChatString(nick, QString("*** ") + tr("%1 has left (%2)").arg(linkedNick(nick)).arg(messageToHTML(message)), "Leave", false);
+ {
+ printChatString(nick, QString("*** ") + tr("%1 has left (%2)").arg(linkedNick(nick)).arg(HWApplication::translate("server", message.toLatin1().constData()).toHtmlEscaped()), "Leave", false);
+ }
}
void HWChatWidget::clear()
@@ -547,8 +565,7 @@
// add default commands
QStringList cmds;
// /saveStyleSheet is(/was?) broken because of Physfs or something
- // cmds << "/me" << "/discardStyleSheet" << "/saveStyleSheet";
- cmds << "/me" << "/info" << "/quit" << "/clear" << "/discardStyleSheet";
+ cmds << "/clear" << "/help" << "/info" << "/me" << "/quit" << "/rnd";
chatEditLine->addCommands(cmds);
chatText->clear();
@@ -846,6 +863,12 @@
if (line[0] == '/')
{
QString tline = line.trimmed();
+ if (tline.length() <= 1)
+ {
+ // Empty chat command
+ displayWarning(QCoreApplication::translate("server", "Unknown command or invalid parameters. Say '/help' in chat for a list of commands."));
+ return true;
+ }
if (tline.startsWith("/me"))
return false; // not a real command
else if (tline == "/clear") {
@@ -894,7 +917,9 @@
QString nick;
- if(mil.size())
+ if(mil.size() == 0)
+ return;
+ else if(mil.size() == 1)
nick = mil[0].data().toString();
else
nick = m_clickedNick;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/colorwidget.cpp
--- a/QTfrontend/ui/widget/colorwidget.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/colorwidget.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -30,7 +30,7 @@
QStandardItem * item = m_colorsModel->item(m_color);
- setStyleSheet(QString("* { border: 2px solid #ffcc00; border-radius: 8px; background: %1 } :disabled { border-color: #a0a0a0; }").arg(item->data().value().name()));
+ setStyleSheet(QString("* { border: 2px solid #ffcc00; border-radius: 8px; background: %1 } :disabled { border-color: #a0a0a0; } :hover { border-color: #ffff00; }").arg(item->data().value().name()));
/*
QPalette p = palette();
p.setColor(QPalette::Window, item->data().value());
@@ -67,6 +67,7 @@
void ColorWidget::wheelEvent(QWheelEvent *event)
{
+ event->accept();
if(event->delta() > 0)
previousColor();
else
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/drawmapwidget.cpp
--- a/QTfrontend/ui/widget/drawmapwidget.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/drawmapwidget.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -154,6 +154,7 @@
errorMsg.setIcon(QMessageBox::Warning);
errorMsg.setWindowTitle(QMessageBox::tr("File error"));
errorMsg.setText(QMessageBox::tr("Cannot open '%1' for writing").arg(fileName));
+ errorMsg.setTextFormat(Qt::PlainText);
errorMsg.setWindowModality(Qt::WindowModal);
errorMsg.exec();
}
@@ -174,6 +175,7 @@
errorMsg.setIcon(QMessageBox::Warning);
errorMsg.setWindowTitle(QMessageBox::tr("File error"));
errorMsg.setText(QMessageBox::tr("Cannot open '%1' for reading").arg(fileName));
+ errorMsg.setTextFormat(Qt::PlainText);
errorMsg.setWindowModality(Qt::WindowModal);
errorMsg.exec();
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/feedbackdialog.cpp
--- a/QTfrontend/ui/widget/feedbackdialog.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/feedbackdialog.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -109,7 +109,8 @@
CheckSendSpecs = new QCheckBox();
CheckSendSpecs->setText(QLabel::tr("Send system information"));
- CheckSendSpecs->setChecked(true);
+ CheckSendSpecs->setChecked(false);
+ CheckSendSpecs->setToolTip(tr("This is optional, but this information might help us to resolve bugs and other technical problems."));
BtnViewInfo = new QPushButton(tr("View"));
BtnViewInfo->setFixedHeight(40);
feedbackLayout->addWidget(CheckSendSpecs, 0, 2, 2, 1);
@@ -329,6 +330,7 @@
msgMsg.setIcon(QMessageBox::Warning);
msgMsg.setWindowTitle(QMessageBox::tr("Hedgewars - Error"));
msgMsg.setText(msg);
+ msgMsg.setTextFormat(Qt::PlainText);
msgMsg.setWindowModality(Qt::WindowModal);
msgMsg.exec();
}
@@ -416,6 +418,7 @@
infoMsg.setIcon(QMessageBox::Information);
infoMsg.setWindowTitle(QMessageBox::tr("Hedgewars - Success"));
infoMsg.setText(reply->readAll());
+ infoMsg.setTextFormat(Qt::PlainText);
infoMsg.setWindowModality(Qt::WindowModal);
infoMsg.exec();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/frameTeam.cpp
--- a/QTfrontend/ui/widget/frameTeam.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/frameTeam.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -126,7 +126,7 @@
bool FrameTeams::isFullTeams() const
{
- return teamToWidget.size() >= 8;
+ return teamToWidget.size() >= cMaxTeams;
}
void FrameTeams::emitTeamColorChanged(const HWTeam& team)
@@ -151,9 +151,11 @@
{
setStyleSheet(
"FrameTeams{"
- "border: solid;"
+ "border-top: transparent;"
+ "border-left: transparent;"
+ "border-right: transparent;"
+ "border-bottom: solid;"
"border-width: 1px;"
- "border-radius: 16px;"
"border-color: #ffcc00;"
"}"
);
@@ -163,3 +165,11 @@
setStyleSheet("FrameTeams{ border: transparent }");
}
}
+
+void FrameTeams::resizeEvent(QResizeEvent * event)
+{
+ Q_UNUSED(event);
+
+ QResizeEvent* pevent=new QResizeEvent(parentWidget()->size(), parentWidget()->size());
+ QCoreApplication::postEvent(parentWidget(), pevent);
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/frameTeam.h
--- a/QTfrontend/ui/widget/frameTeam.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/frameTeam.h Wed Jul 31 23:14:27 2019 +0200
@@ -53,6 +53,9 @@
void addTeam(HWTeam team, bool willPlay);
void removeTeam(HWTeam team);
+ protected:
+ virtual void resizeEvent(QResizeEvent * event);
+
private:
int currentColor;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/gamecfgwidget.cpp
--- a/QTfrontend/ui/widget/gamecfgwidget.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/gamecfgwidget.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -150,6 +150,7 @@
goToSchemePage->setIconSize(pmEdit.size());
goToSchemePage->setIcon(iconEdit);
goToSchemePage->setMaximumWidth(pmEdit.width() + 6);
+ goToSchemePage->setStyleSheet("padding: 0px;");
SchemeWidgetLayout->addWidget(goToSchemePage, 0, 3);
connect(goToSchemePage, SIGNAL(clicked()), this, SLOT(jumpToSchemes()));
@@ -172,6 +173,7 @@
goToWeaponPage->setIconSize(pmEdit.size());
goToWeaponPage->setIcon(pmEdit);
goToWeaponPage->setMaximumWidth(pmEdit.width() + 6);
+ goToWeaponPage->setStyleSheet("padding: 0px;");
SchemeWidgetLayout->addWidget(goToWeaponPage, 1, 3);
connect(goToWeaponPage, SIGNAL(clicked()), this, SLOT(jumpToWeapons()));
@@ -336,6 +338,7 @@
bcfg << QString("e$gmflags %1").arg(getGameFlags()).toUtf8();
bcfg << QString("e$damagepct %1").arg(schemeData(26).toInt()).toUtf8();
bcfg << QString("e$turntime %1").arg(schemeData(27).toInt() * 1000).toUtf8();
+ bcfg << QString("e$inithealth %1").arg(schemeData(28).toInt()).toUtf8();
bcfg << QString("e$sd_turns %1").arg(schemeData(29).toInt()).toUtf8();
bcfg << QString("e$casefreq %1").arg(schemeData(30).toInt()).toUtf8();
bcfg << QString("e$minestime %1").arg(schemeData(31).toInt() * 1000).toUtf8();
@@ -400,6 +403,7 @@
illegalMsg.setIcon(QMessageBox::Warning);
illegalMsg.setWindowTitle(QMessageBox::tr("Error"));
illegalMsg.setText(QMessageBox::tr("Cannot use the weapon scheme '%1'!").arg(name));
+ illegalMsg.setTextFormat(Qt::PlainText);
illegalMsg.setWindowModality(Qt::WindowModal);
illegalMsg.exec();
}
@@ -545,6 +549,65 @@
}
}
+void GameCFGWidget::resetSchemeStates()
+{
+ updateSchemeEnabledStates(Scripts->currentIndex());
+}
+
+void GameCFGWidget::updateSchemeEnabledStates(int scriptIndex)
+{
+ QString scheme;
+ QString weapons;
+ if(scriptIndex > 0)
+ {
+ scheme = Scripts->itemData(scriptIndex, GameStyleModel::SchemeRole).toString();
+ weapons = Scripts->itemData(scriptIndex, GameStyleModel::WeaponsRole).toString();
+ }
+ else
+ {
+ scheme = pMapContainer->getCurrentScheme();
+ weapons = pMapContainer->getCurrentWeapons();
+ }
+ if (scheme == "locked")
+ {
+ GameSchemes->setEnabled(false);
+ goToSchemePage->setEnabled(false);
+ lblScheme->setEnabled(false);
+ GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
+ }
+ else if(m_master)
+ {
+ GameSchemes->setEnabled(true);
+ goToSchemePage->setEnabled(true);
+ lblScheme->setEnabled(true);
+ int num = GameSchemes->findText(scheme);
+ if (num != -1)
+ GameSchemes->setCurrentIndex(num);
+ }
+
+ if (weapons == "locked")
+ {
+ WeaponsName->setEnabled(false);
+ goToWeaponPage->setEnabled(false);
+ lblWeapons->setEnabled(false);
+ WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
+ }
+ else if(m_master)
+ {
+ WeaponsName->setEnabled(true);
+ goToWeaponPage->setEnabled(true);
+ lblWeapons->setEnabled(true);
+ int num = WeaponsName->findText(weapons);
+ if (num != -1)
+ WeaponsName->setCurrentIndex(num);
+ }
+
+ if (scheme != "locked" && weapons != "locked")
+ bindEntries->setEnabled(true);
+ else
+ bindEntries->setEnabled(false);
+}
+
void GameCFGWidget::mapChanged(const QString & value)
{
if(isEnabled() && pMapContainer->getCurrentIsMission())
@@ -552,49 +615,7 @@
Scripts->setEnabled(false);
lblScript->setEnabled(false);
Scripts->setCurrentIndex(0);
-
- if (pMapContainer->getCurrentScheme() == "locked")
- {
- GameSchemes->setEnabled(false);
- goToSchemePage->setEnabled(false);
- lblScheme->setEnabled(false);
- GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
- else
- {
- GameSchemes->setEnabled(true);
- goToSchemePage->setEnabled(true);
- lblScheme->setEnabled(true);
- int num = GameSchemes->findText(pMapContainer->getCurrentScheme());
- if (num != -1)
- GameSchemes->setCurrentIndex(num);
- //else
- // GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
-
- if (pMapContainer->getCurrentWeapons() == "locked")
- {
- WeaponsName->setEnabled(false);
- goToWeaponPage->setEnabled(false);
- lblWeapons->setEnabled(false);
- WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
- else
- {
- WeaponsName->setEnabled(true);
- goToWeaponPage->setEnabled(true);
- lblWeapons->setEnabled(true);
- int num = WeaponsName->findText(pMapContainer->getCurrentWeapons());
- if (num != -1)
- WeaponsName->setCurrentIndex(num);
- //else
- // WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
-
- if (pMapContainer->getCurrentScheme() != "locked" && pMapContainer->getCurrentWeapons() != "locked")
- bindEntries->setEnabled(true);
- else
- bindEntries->setEnabled(false);
+ updateSchemeEnabledStates(0);
}
else
{
@@ -671,51 +692,7 @@
if(isEnabled() && index > 0)
{
- QString scheme = Scripts->itemData(index, GameStyleModel::SchemeRole).toString();
- QString weapons = Scripts->itemData(index, GameStyleModel::WeaponsRole).toString();
-
- if (scheme == "locked")
- {
- GameSchemes->setEnabled(false);
- goToSchemePage->setEnabled(false);
- lblScheme->setEnabled(false);
- GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
- else if (m_master)
- {
- GameSchemes->setEnabled(true);
- goToSchemePage->setEnabled(true);
- lblScheme->setEnabled(true);
- int num = GameSchemes->findText(scheme);
- if (num != -1)
- GameSchemes->setCurrentIndex(num);
- //else
- // GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
- }
-
- if (weapons == "locked")
- {
- WeaponsName->setEnabled(false);
- goToWeaponPage->setEnabled(false);
- lblWeapons->setEnabled(false);
- WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
- else if (m_master)
- {
- WeaponsName->setEnabled(true);
- goToWeaponPage->setEnabled(true);
- lblWeapons->setEnabled(true);
- int num = WeaponsName->findText(weapons);
- if (num != -1)
- WeaponsName->setCurrentIndex(num);
- //else
- // WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
- }
-
- if (scheme != "locked" && weapons != "locked")
- bindEntries->setEnabled(true);
- else
- bindEntries->setEnabled(false);
+ updateSchemeEnabledStates(index);
}
else
{
@@ -744,6 +721,8 @@
void GameCFGWidget::mapgenChanged(MapGenerator m)
{
+ int scriptIndex = Scripts->currentIndex();
+ updateSchemeEnabledStates(scriptIndex);
emit paramChanged("MAPGEN", QStringList(QString::number(m)));
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/gamecfgwidget.h
--- a/QTfrontend/ui/widget/gamecfgwidget.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/gamecfgwidget.h Wed Jul 31 23:14:27 2019 +0200
@@ -61,6 +61,7 @@
void fullNetConfig();
void resendSchemeData();
void resendAmmoData();
+ void resetSchemeStates();
void setMaster(bool master);
void setTabbed(bool tabbed);
@@ -77,6 +78,7 @@
void seedChanged(const QString &);
void themeChanged(const QString &);
void schemeChanged(int);
+ void updateSchemeEnabledStates(int scriptIndex);
void scriptChanged(int);
void jumpToSchemes();
void jumpToWeapons();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/keybinder.cpp
--- a/QTfrontend/ui/widget/keybinder.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/keybinder.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -38,6 +38,7 @@
{
this->defaultText = defaultText;
enableSignal = false;
+ p_hasConflicts = false;
// Two-column tab layout
QHBoxLayout * pageKeysLayout = new QHBoxLayout(this);
@@ -48,7 +49,7 @@
QVBoxLayout * catListContainer = new QVBoxLayout();
catListContainer->setContentsMargins(10, 10, 10, 10);
catList = new QListWidget();
- catList->setFixedWidth(180);
+ catList->setMinimumWidth(180);
catList->setStyleSheet("QListWidget::item { font-size: 14px; } QListWidget:hover { border-color: #F6CB1C; } QListWidget::item:selected { background: #150A61; color: yellow; }");
catList->setFocusPolicy(Qt::NoFocus);
connect(catList, SIGNAL(currentRowChanged(int)), this, SLOT(changeBindingsPage(int)));
@@ -78,7 +79,7 @@
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
scrollArea->setWidgetResizable(true);
scrollArea->setFrameShape(QFrame::NoFrame);
- scrollArea->setStyleSheet("background: #130F2A;");
+ scrollArea->setObjectName("keyBinderScrollArea");
// Add key binding pages to bindings tab
pageKeysLayout->addWidget(scrollArea);
@@ -90,6 +91,12 @@
helpLabel->setStyleSheet("color: #130F2A; background: #F6CB1C; border: solid 4px #F6CB1C; border-radius: 10px; padding: auto 20px;");
helpLabel->setFixedHeight(24);
rightLayout->addWidget(helpLabel, 0, Qt::AlignCenter);
+ conflictLabel = new QLabel();
+ conflictLabel->setText(tr("Warning: The same key is assigned multiple times!"));
+ conflictLabel->setStyleSheet("color: white; background: #E31A1A; border: solid 4px #E31A1A; border-radius: 10px; padding: auto 20px;");
+ conflictLabel->setFixedHeight(24);
+ conflictLabel->setHidden(true);
+ rightLayout->addWidget(conflictLabel, 0, Qt::AlignCenter);
// Category list and bind table row heights
const int rowHeight = 20;
@@ -117,6 +124,21 @@
selectedBindTable = NULL;
bindComboBoxCellMappings = new QHash();
bindCellComboBoxMappings = new QHash();
+
+ dropDownIcon = new QIcon();
+ QPixmap dd1 = QPixmap(":/res/dropdown.png");
+ QPixmap dd2 = QPixmap(":/res/dropdown_selected.png");
+ dropDownIcon->addPixmap(dd1, QIcon::Normal);
+ dropDownIcon->addPixmap(dd2, QIcon::Selected);
+ conflictIcon = new QIcon();
+ QPixmap kc1 = QPixmap(":/res/keyconflict.png");
+ QPixmap kc2 = QPixmap(":/res/keyconflict_selected.png");
+ conflictIcon->addPixmap(kc1, QIcon::Normal);
+ conflictIcon->addPixmap(kc2, QIcon::Selected);
+ QPixmap emptySpace = QPixmap(16, 16);
+ emptySpace.fill(QColor(0, 0, 0, 0));
+ QIcon emptyIcon = QIcon(emptySpace);
+
for (int i = 0; i < BINDS_NUMBER; i++)
{
if (cbinds[i].category != NULL)
@@ -157,7 +179,7 @@
curTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
curTable->verticalHeader()->setDefaultSectionSize(rowHeight);
curTable->setShowGrid(false);
- curTable->setStyleSheet("QTableWidget { border: none; } ");
+ curTable->setStyleSheet("QTableWidget { border: none; background-color: transparent; } ");
curTable->setSelectionBehavior(QAbstractItemView::SelectRows);
curTable->setSelectionMode(QAbstractItemView::SingleSelection);
curTable->setFocusPolicy(Qt::NoFocus);
@@ -167,37 +189,55 @@
}
// Hidden combo box
- QComboBox * comboBox = CBBind[i] = new QComboBox(curTable);
- comboBox->setModel((QAbstractItemModel*)DataManager::instance().bindsModel());
- comboBox->setVisible(false);
- comboBox->setFixedWidth(200);
- comboBox->setMaxVisibleItems(50);
+ QComboBox * comboBox;
+ if (cbinds[i].action != "!MULTI")
+ {
+ comboBox = CBBind[i] = new QComboBox(curTable);
+ comboBox->setModel((QAbstractItemModel*)DataManager::instance().bindsModel());
+ comboBox->setVisible(false);
+ comboBox->setMinimumWidth(400);
+ comboBox->setMaxVisibleItems(50);
+ }
+ else
+ {
+ comboBox = CBBind[i] = NULL;
+ }
// Table row
int row = curTable->rowCount();
QTableWidgetItem * nameCell = new QTableWidgetItem(HWApplication::translate("binds", cbinds[i].name));
- nameCell->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
curTable->insertRow(row);
curTable->setItem(row, 0, nameCell);
- QTableWidgetItem * bindCell = new QTableWidgetItem(comboBox->currentText());
- QIcon dropDownIcon = QIcon();
- QPixmap dd1 = QPixmap(":/res/dropdown.png");
- QPixmap dd2 = QPixmap(":/res/dropdown_selected.png");
- dropDownIcon.addPixmap(dd1, QIcon::Normal);
- dropDownIcon.addPixmap(dd2, QIcon::Selected);
- bindCell->setIcon(dropDownIcon);
- bindCell->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+ QTableWidgetItem * bindCell;
+ if (cbinds[i].action != "!MULTI")
+ {
+ bindCell = new QTableWidgetItem(comboBox->currentText());
+ nameCell->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+ bindCell->setIcon(*dropDownIcon);
+ bindCell->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+ }
+ else
+ {
+ bindCell = new QTableWidgetItem(HWApplication::translate("binds (combination)", cbinds[i].strbind.toUtf8().constData()));
+ nameCell->setFlags(Qt::NoItemFlags);
+ bindCell->setFlags(Qt::NoItemFlags);
+ bindCell->setIcon(emptyIcon);
+ }
curTable->setItem(row, 1, bindCell);
curTable->resizeColumnsToContents();
curTable->setFixedHeight(curTable->verticalHeader()->length() + 10);
- // Updates the text in the table cell
- connect(comboBox, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(bindChanged(const QString &)));
+ if (cbinds[i].action != "!MULTI")
+ {
+ // Updates the text in the table cell
+ connect(comboBox, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(bindChanged(const QString &)));
- // Map combo box and that row's cells to each other
- bindComboBoxCellMappings->insert(comboBox, bindCell);
- bindCellComboBoxMappings->insert(nameCell, comboBox);
- bindCellComboBoxMappings->insert(bindCell, comboBox);
+ // Map combo box and that row's cells to each other
+ bindComboBoxCellMappings->insert(comboBox, bindCell);
+ bindCellComboBoxMappings->insert(nameCell, comboBox);
+ bindCellComboBoxMappings->insert(bindCell, comboBox);
+ }
+
}
// Add stretch at end of last layout
@@ -237,6 +277,7 @@
if (CBBind[i] == sender())
{
emit bindUpdate(i);
+ checkConflicts();
break;
}
}
@@ -247,6 +288,8 @@
void KeyBinder::bindCellClicked(QTableWidgetItem * item)
{
QComboBox * box = bindCellComboBoxMappings->value(item);
+ if(box == NULL)
+ return;
QTableWidget * table = item->tableWidget();
box->move(
@@ -268,18 +311,95 @@
}
}
+// check if the given key is bound multiple times
+bool KeyBinder::checkConflictsWith(int compareTo, bool updateState)
+{
+ for(int i=0; icurrentData(Qt::UserRole + 1).toString();
+ QString bind2 = CBBind[compareTo]->currentData(Qt::UserRole + 1).toString();
+ // TODO: For team key binds, also check collisions with global key binds
+ if((!(bind1 == "none" || bind2 == "none" || bind1 == "default" || bind2 == "default")) && (bind1 == bind2))
+ {
+ if(updateState)
+ {
+ p_hasConflicts = true;
+ conflictLabel->setHidden(false);
+ }
+ QTableWidgetItem* conflictItem = bindComboBoxCellMappings->value(CBBind[i]);
+ conflictItem->setIcon(*conflictIcon);
+ conflictItem->setBackground(QBrush(QColor(0xE3, 0x1A, 0x1A)));
+ conflictItem->setForeground(QBrush(Qt::white));
+ conflictItems.append(conflictItem);
+ conflictItem = bindComboBoxCellMappings->value(CBBind[compareTo]);
+ conflictItem->setIcon(*conflictIcon);
+ conflictItem->setBackground(QBrush(QColor(0xE3, 0x1A, 0x1A)));
+ conflictItem->setForeground(QBrush(Qt::white));
+ conflictItems.append(conflictItem);
+ return true;
+ }
+ }
+ if(updateState)
+ {
+ p_hasConflicts = false;
+ conflictLabel->setHidden(true);
+ }
+ for (int c=0; c < conflictItems.size(); c++)
+ {
+ QTableWidgetItem* conflictItem = conflictItems[c];
+ conflictItem->setIcon(*dropDownIcon);
+ conflictItem->setBackground(QBrush(Qt::transparent));
+ conflictItem->setForeground(QBrush(QColor("#F6CB1C")));
+ conflictItem = NULL;
+ }
+ conflictItems.clear();
+ return false;
+}
+
+// check if any key is bound multiple times and causing a conflict
+bool KeyBinder::checkConflicts()
+{
+ bool conflict = false;
+ for(int i=0; isetHidden(false);
+ return true;
+ }
+ }
+ p_hasConflicts = false;
+ conflictLabel->setHidden(true);
+ return false;
+}
+
+bool KeyBinder::hasConflicts()
+{
+ return p_hasConflicts;
+}
+
// Set a combobox's index
void KeyBinder::setBindIndex(int keyIndex, int bindIndex)
{
enableSignal = false;
- CBBind[keyIndex]->setCurrentIndex(bindIndex);
+ if(CBBind[keyIndex] != NULL)
+ CBBind[keyIndex]->setCurrentIndex(bindIndex);
enableSignal = true;
}
// Return a combobox's selected index
int KeyBinder::bindIndex(int keyIndex)
{
- return CBBind[keyIndex]->currentIndex();
+ if(CBBind[keyIndex] != NULL)
+ return CBBind[keyIndex]->currentIndex();
+ else
+ return 0;
}
// Clears selection and goes to first category
@@ -299,9 +419,12 @@
DataManager::instance().bindsModel()->item(0)->setData(defaultText, Qt::DisplayRole);
for (int i = 0; i < BINDS_NUMBER; i++)
{
- CBBind[i]->setModel(DataManager::instance().bindsModel());
- CBBind[i]->setCurrentIndex(0);
- bindComboBoxCellMappings->value(CBBind[i])->setText(defaultText);
+ if (CBBind[i] != NULL)
+ {
+ CBBind[i]->setModel(DataManager::instance().bindsModel());
+ CBBind[i]->setCurrentIndex(0);
+ bindComboBoxCellMappings->value(CBBind[i])->setText(defaultText);
+ }
}
enableSignal = true;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/keybinder.h
--- a/QTfrontend/ui/widget/keybinder.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/keybinder.h Wed Jul 31 23:14:27 2019 +0200
@@ -29,6 +29,7 @@
class QTableWidget;
class QBoxLayout;
class QComboBox;
+class QLabel;
// USAGE NOTE: Every time the widget comes into view, you must call resetInterface()
@@ -43,6 +44,9 @@
void setBindIndex(int keyIndex, int bindIndex);
int bindIndex(int keyIndex);
void resetInterface();
+ bool hasConflicts();
+ bool checkConflicts();
+ bool checkConflictsWith(int bind, bool updateState);
private:
QHash * bindComboBoxCellMappings;
@@ -51,8 +55,13 @@
QListWidget * catList;
QBoxLayout *bindingsPages;
QComboBox * CBBind[BINDS_NUMBER];
+ QLabel * conflictLabel;
+ QIcon * dropDownIcon;
+ QIcon * conflictIcon;
QString defaultText;
bool enableSignal;
+ QList conflictItems;
+ bool p_hasConflicts;
signals:
void bindUpdate(int bindID);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/mapContainer.cpp
--- a/QTfrontend/ui/widget/mapContainer.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/mapContainer.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -154,6 +154,7 @@
/* Seed button */
btnSeed = new QPushButton(parentWidget()->parentWidget());
+ //: Refers to the "random seed"; the source of randomness in the game
btnSeed->setText(tr("Seed"));
btnSeed->setWhatsThis(tr("View and edit the seed, the source of randomness in the game"));
btnSeed->setStyleSheet("padding: 5px;");
@@ -228,14 +229,34 @@
drawnControls->addStretch(1);
- btnLoadMap = new QPushButton(tr("Load map drawing"));
- btnLoadMap->setStyleSheet("padding: 20px;");
+ QPixmap pmLoad(":/res/Load.png");
+ QIcon iconLoad = QIcon(pmLoad);
+ sz = iconLoad.actualSize(QSize(48, 48));
+
+ btnLoadMap = new QPushButton(tr("Load"));
+ btnLoadMap->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ btnLoadMap->setWhatsThis(tr("Load map drawing"));
+ btnLoadMap->setStyleSheet("padding: 5px;");
+ btnLoadMap->setFixedHeight(50);
+ btnLoadMap->setIcon(iconLoad);
+ btnLoadMap->setIconSize(sz);
+ btnLoadMap->setFlat(true);
drawnControls->addWidget(btnLoadMap, 0);
m_childWidgets << btnLoadMap;
connect(btnLoadMap, SIGNAL(clicked()), this, SLOT(loadDrawing()));
- btnEditMap = new QPushButton(tr("Edit map drawing"));
- btnEditMap->setStyleSheet("padding: 20px;");
+ QPixmap pmEdit(":/res/edit.png");
+ QIcon iconEdit = QIcon(pmEdit);
+ sz = iconEdit.actualSize(QSize(48, 48));
+
+ btnEditMap = new QPushButton(tr("Edit"));
+ btnEditMap->setWhatsThis(tr("Edit map drawing"));
+ btnEditMap->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ btnEditMap->setStyleSheet("padding: 5px;");
+ btnEditMap->setFixedHeight(50);
+ btnEditMap->setIcon(iconEdit);
+ btnEditMap->setIconSize(sz);
+ btnEditMap->setFlat(true);
drawnControls->addWidget(btnEditMap, 0);
m_childWidgets << btnEditMap;
connect(btnEditMap, SIGNAL(clicked()), this, SIGNAL(drawMapRequested()));
@@ -276,6 +297,7 @@
mapFeatureSize->setMaximum(25);
mapFeatureSize->setMinimum(1);
//mapFeatureSize->setFixedWidth(259);
+ mapFeatureSize->setPageStep(5);
mapFeatureSize->setValue(m_mapFeatureSize);
mapFeatureSize->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
bottomLeftLayout->addWidget(mapFeatureSize, 0);
@@ -425,7 +447,7 @@
getDrawnMapData(),
m_script,
m_scriptparam,
- m_mapFeatureSize
+ m_mapFeatureSize
);
setHHLimit(0);
@@ -920,6 +942,7 @@
QString randomNoMapPrev = tr("Click to randomize the theme and seed");
QString mfsComplex = QString(tr("Adjust the complexity of the generated map"));
QString mfsFortsDistance = QString(tr("Adjust the distance between forts"));
+ QString mfsDrawnMap = QString(tr("Scale size of the drawn map"));
switch (type)
{
case MapModel::GeneratedMap:
@@ -937,6 +960,7 @@
case MapModel::HandDrawnMap:
mapPreview->setWhatsThis(tr("Click to edit"));
btnRandomize->setWhatsThis(randomSeed);
+ mapFeatureSize->setWhatsThis(mfsDrawnMap);
break;
case MapModel::FortsMap:
mapPreview->setWhatsThis(randomNoMapPrev);
@@ -989,7 +1013,7 @@
mapgen = MAPGEN_DRAWN;
setMapInfo(MapModel::MapInfoDrawn);
btnLoadMap->show();
- mapFeatureSize->hide();
+ //mapFeatureSize->hide();
btnEditMap->show();
break;
case MapModel::MissionMap:
@@ -1070,18 +1094,14 @@
{
m_mapFeatureSize = val;
intSetFeatureSize(val);
- //m_mapFeatureSize = val>>2<<2;
- //if (qAbs(m_prevMapFeatureSize-m_mapFeatureSize) > 4)
- {
- m_prevMapFeatureSize = m_mapFeatureSize;
- updatePreview();
- }
+ m_prevMapFeatureSize = m_mapFeatureSize;
+ updatePreview();
}
// unused because I needed the space for the slider
void HWMapContainer::updateThemeButtonSize()
{
- if (m_mapInfo.type != MapModel::StaticMap && m_mapInfo.type != MapModel::HandDrawnMap)
+ if (m_mapInfo.type != MapModel::StaticMap)
{
btnTheme->setIconSize(QSize(30, 30));
btnTheme->setFixedHeight(30);
@@ -1191,6 +1211,15 @@
{
if (!selectedTheme.isNull() && !selectedTheme.isEmpty())
{
+ // Fall back to a default theme if current theme is a background theme or hidden
+ QModelIndexList mdl = m_themeModel->match(m_themeModel->index(0), ThemeModel::ActualNameRole, m_theme);
+ if (mdl.size() > 0)
+ {
+ if ((mdl.at(0).data(ThemeModel::IsBackgroundThemeRole).toBool() == true) || (mdl.at(0).data(ThemeModel::IsHiddenRole).toBool() == true))
+ {
+ selectedTheme = "Nature";
+ }
+ }
setTheme(selectedTheme);
emit themeChanged(selectedTheme);
}
@@ -1222,6 +1251,7 @@
errorMsg.setIcon(QMessageBox::Warning);
errorMsg.setWindowTitle(QMessageBox::tr("File error"));
errorMsg.setText(QMessageBox::tr("Cannot open '%1' for reading").arg(fileName));
+ errorMsg.setTextFormat(Qt::PlainText);
errorMsg.setWindowModality(Qt::WindowModal);
errorMsg.exec();
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/seedprompt.cpp
--- a/QTfrontend/ui/widget/seedprompt.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/seedprompt.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -31,6 +31,7 @@
setModal(true);
setWindowFlags(Qt::Sheet);
setWindowModality(Qt::WindowModal);
+ //: Refers to the "random seed"; the source of randomness in the game
setWindowTitle(tr("Seed"));
setMinimumSize(360, 160);
resize(360, 160);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/selectWeapon.cpp
--- a/QTfrontend/ui/widget/selectWeapon.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/selectWeapon.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -31,6 +31,9 @@
#include
#include
#include
+#include
+#include
+
#include
QImage getAmmoImage(int num)
@@ -94,23 +97,22 @@
if (!QDir(cfgdir->absolutePath() + "/Schemes").exists()) {
QDir().mkdir(cfgdir->absolutePath() + "/Schemes");
}
+ QStringList defaultAmmos;
+ for(int i = 0; i < cDefaultAmmos.size(); ++i)
+ {
+ defaultAmmos.append(cDefaultAmmos[i].first.toLower());
+ }
if (!QDir(cfgdir->absolutePath() + "/Schemes/Ammo").exists()) {
qDebug("No /Schemes/Ammo directory found. Trying to import weapon schemes from weapons.ini.");
QDir().mkdir(cfgdir->absolutePath() + "/Schemes/Ammo");
QSettings old_wconf(cfgdir->absolutePath() + "/weapons.ini", QSettings::IniFormat);
- QStringList defaultAmmos;
- for(int i = 0; i < cDefaultAmmos.size(); ++i)
- {
- defaultAmmos.append(cDefaultAmmos[i].first);
- }
-
QStringList keys = old_wconf.allKeys();
int imported = 0;
for(int i = 0; i < keys.size(); i++)
{
- if (!defaultAmmos.contains(keys[i])) {
+ if (!defaultAmmos.contains(keys[i].toLower())) {
wconf->insert(keys[i], fixWeaponSet(old_wconf.value(keys[i]).toString()));
QFile file(cfgdir->absolutePath() + "/Schemes/Ammo/" + keys[i] + ".hwa");
if (file.open(QIODevice::WriteOnly)) {
@@ -140,7 +142,11 @@
if (schemeName.endsWith(".hwa", Qt::CaseInsensitive)) {
schemeName.chop(4);
}
- wconf->insert(schemeName, fixWeaponSet(config));
+ // Don't load weapon scheme if name collides with any default scheme
+ if (!defaultAmmos.contains(schemeName.toLower()))
+ wconf->insert(schemeName, fixWeaponSet(config));
+ else
+ qWarning("Weapon scheme \"%s\" not loaded from file, name collides with a default scheme!", qPrintable(schemeName));
}
}
@@ -177,7 +183,9 @@
int i = 0, k = 0;
for(; i < m_numItems; ++i)
{
- if (i == 6) continue;
+ // Hide amSkip (6) and amCreeper (57)
+ // TODO: Unhide amCreeper when this weapon is done
+ if (i == 6 || i == 57) continue;
if (k % 4 == 0) ++j;
SelWeaponItem * swi = new SelWeaponItem(true, i, currentState[i].digitValue(), QImage(":/res/ammopic.png"), QImage(":/res/ammopicgrey.png"), this);
weaponItems[i].append(swi);
@@ -200,6 +208,9 @@
//pLayout->setRowStretch(5, 100);
m_name = new QLineEdit(this);
+ QRegExp rx(*cSafeFileNameRegExp);
+ QRegExpValidator* val = new QRegExpValidator(rx, m_name);
+ m_name->setValidator(val);
pageLayout->addWidget(m_name, i, 0, 1, 5);
}
@@ -251,11 +262,16 @@
//prevent this.
if (isDeleting)
return;
- // TODO make this return if success or not, so that the page can react
- // properly and not goBack if saving failed
if (m_name->text() == "")
return;
+ // Don't save an default ammo scheme
+ for(int i = 0; i < cDefaultAmmos.size(); ++i)
+ {
+ if(curWeaponsName == cDefaultAmmos[i].first)
+ return;
+ }
+
QString state1;
QString state2;
QString state3;
@@ -277,21 +293,25 @@
stateFull = state1 + state2 + state3 + state4;
- for(int i = 0; i < cDefaultAmmos.size(); i++)
+ // Check for duplicates
+ QString inputNameLower = m_name->text().toLower();
+ QString curWeaponsNameLower = curWeaponsName.toLower();
+ QStringList keys = wconf->keys();
+ for(int i = 0; i < keys.size(); i++)
{
- // Don't allow same name as default weapon set, even case-insensitively.
+ QString compName = keys[i];
+ QString compNameLower = compName.toLower();
+ // Don't allow same name as other weapon set, even case-insensitively.
// This prevents some problems with saving/loading.
- if (cDefaultAmmos[i].first.toLower().compare(m_name->text().toLower()) == 0)
+ if ((compNameLower == inputNameLower) && (compNameLower != curWeaponsNameLower))
{
- // don't show warning if no change
- if (cDefaultAmmos[i].second.compare(stateFull) == 0)
- return;
-
+ // Discard changed made to current weapon scheme if there's a duplicate
m_name->setText(curWeaponsName);
QMessageBox deniedMsg(this);
deniedMsg.setIcon(QMessageBox::Warning);
deniedMsg.setWindowTitle(QMessageBox::tr("Weapons - Warning"));
- deniedMsg.setText(QMessageBox::tr("Cannot overwrite default weapon set '%1'!").arg(cDefaultAmmos[i].first));
+ deniedMsg.setText(QMessageBox::tr("A weapon scheme with the name '%1' already exists. Changes made to the weapon scheme have been discarded.").arg(compName));
+ deniedMsg.setTextFormat(Qt::PlainText);
deniedMsg.setWindowModality(Qt::WindowModal);
deniedMsg.exec();
return;
@@ -337,6 +357,7 @@
deniedMsg.setIcon(QMessageBox::Warning);
deniedMsg.setWindowTitle(QMessageBox::tr("Weapons - Warning"));
deniedMsg.setText(QMessageBox::tr("Cannot delete default weapon set '%1'!").arg(cDefaultAmmos[i].first));
+ deniedMsg.setTextFormat(Qt::PlainText);
deniedMsg.setWindowModality(Qt::WindowModal);
deniedMsg.exec();
return;
@@ -347,6 +368,7 @@
reallyDeleteMsg.setIcon(QMessageBox::Question);
reallyDeleteMsg.setWindowTitle(QMessageBox::tr("Weapons - Are you sure?"));
reallyDeleteMsg.setText(QMessageBox::tr("Do you really want to delete the weapon set '%1'?").arg(delWeaponsName));
+ reallyDeleteMsg.setTextFormat(Qt::PlainText);
reallyDeleteMsg.setWindowModality(Qt::WindowModal);
reallyDeleteMsg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/teamselect.cpp
--- a/QTfrontend/ui/widget/teamselect.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/teamselect.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -74,6 +74,11 @@
framePlaying->setInteractivity(interactive);
}
+void TeamSelWidget::setUser(const QString& nickname)
+{
+ m_curUser = nickname;
+}
+
void TeamSelWidget::hhNumChanged(const HWTeam& team)
{
QList::iterator itPlay=std::find(curPlayingTeams.begin(), curPlayingTeams.end(), team);
@@ -152,6 +157,37 @@
emit setEnabledGameStart(curPlayingTeams.size()>1);
}
+// Removes teams classified as net teams but which are owned by the local user.
+// Those teams don't make sense and might be leftovers from a finished game
+// after rejoining. See also: Bugzilla bug 597.
+void TeamSelWidget::cleanupFakeNetTeams()
+{
+ // m_curUser is not set for offline games. No cleanup is needed when offline.
+ if(m_curUser.isNull())
+ return;
+
+ QList::iterator itPlay = curPlayingTeams.begin();
+ while(itPlay != curPlayingTeams.end())
+ {
+ if(itPlay->isNetTeam() && itPlay->owner() == m_curUser)
+ {
+ qDebug() << QString("cleanupFakeNetTeams: team '%1' removed").arg(itPlay->name());
+ QObject::disconnect(framePlaying->getTeamWidget(*itPlay), SIGNAL(teamStatusChanged(HWTeam)));
+ framePlaying->removeTeam(*itPlay);
+ itPlay = curPlayingTeams.erase(itPlay);
+ }
+ else
+ itPlay++;
+ }
+
+ // Show team notice if less than two teams.
+ if (curPlayingTeams.size() < 2)
+ {
+ numTeamNotice->show();
+ }
+ emit setEnabledGameStart(curPlayingTeams.size()>1);
+}
+
void TeamSelWidget::changeTeamStatus(HWTeam team)
{
QList::iterator itDontPlay=std::find(m_curNotPlayingTeams.begin(), m_curNotPlayingTeams.end(), team);
@@ -179,8 +215,9 @@
// dont playing team => playing
itDontPlay->setColor(framePlaying->getNextColor());
team=*itDontPlay; // for net team info saving in framePlaying (we have only name with netID from network)
- curPlayingTeams.push_back(*itDontPlay);
- if(!m_acceptOuter) emit teamWillPlay(*itDontPlay);
+ team.setOwner(m_curUser);
+ curPlayingTeams.push_back(team);
+ if(!m_acceptOuter) emit teamWillPlay(team);
m_curNotPlayingTeams.erase(itDontPlay);
// Hide team notice if at least two teams.
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/ui/widget/teamselect.h
--- a/QTfrontend/ui/widget/teamselect.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/ui/widget/teamselect.h Wed Jul 31 23:14:27 2019 +0200
@@ -49,6 +49,8 @@
QList getNotPlayingTeams() const;
unsigned short getNumHedgehogs() const;
void setInteractivity(bool interactive);
+ void setUser(const QString& nickname);
+ void cleanupFakeNetTeams();
public slots:
void addTeam(HWTeam team);
@@ -78,6 +80,7 @@
QLabel *numTeamNotice;
bool m_acceptOuter;
void repaint();
+ QString m_curUser;
QList curPlayingTeams;
QList m_curNotPlayingTeams;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/DataManager.cpp
--- a/QTfrontend/util/DataManager.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/DataManager.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -28,9 +28,12 @@
#include
#include
+#include
+
#include "hwconsts.h"
#include "HWApplication.h"
#include "sdlkeys.h"
+#include "KeyMap.h"
#include "physfs.h"
#include "DataManager.h"
@@ -147,6 +150,7 @@
QStandardItemModel * DataManager::bindsModel()
{
+ KeyMap km = KeyMap::instance();
if(m_bindsModel == NULL)
{
m_bindsModel = new QStandardItemModel();
@@ -159,7 +163,33 @@
for(int j = 0; sdlkeys[j][1][0] != '\0'; j++)
{
QStandardItem * item = new QStandardItem();
- item->setData(HWApplication::translate("binds (keys)", sdlkeys[j][1]).contains(": ") ? HWApplication::translate("binds (keys)", sdlkeys[j][1]) : HWApplication::translate("binds (keys)", "Keyboard") + QString(": ") + HWApplication::translate("binds (keys)", sdlkeys[j][1]), Qt::DisplayRole);
+ QString keyId = QString(sdlkeys[j][0]);
+ QString keyDisplay;
+ bool isKeyboard = sdlkeys_iskeyboard[j] == true;
+ if (keyId == "none" || (!isKeyboard))
+ keyDisplay = HWApplication::translate("binds (keys)", sdlkeys[j][1]);
+ else
+ // Get key name with respect to keyboard layout
+ keyDisplay = QString(SDL_GetKeyName(SDL_GetKeyFromScancode(km.getScancodeFromKeyname(sdlkeys[j][0]))));
+
+ bool kbFallback = keyDisplay.trimmed().isEmpty();
+ if (kbFallback)
+ {
+ keyDisplay = QString(sdlkeys[j][1]);
+ if ((QString(sdlkeys[j][0]) != "f13") && (QString(sdlkeys[j][0]) != "f14") && (QString(sdlkeys[j][0]) != "f15"))
+ {
+ // If SDL doesn't know a name, show fallback name and a warning
+ //: Name of QWERTY US keyboard layout
+ keyDisplay = keyDisplay + QString(" ") + HWApplication::translate("binds (keys)", "(QWERTY)");
+ }
+ }
+ if (isKeyboard)
+ {
+ if (!kbFallback)
+ keyDisplay = HWApplication::translate("binds (keys)", keyDisplay.toUtf8().constData());
+ keyDisplay = HWApplication::translate("binds (keys)", "Keyboard") + QString(": ") + keyDisplay;
+ }
+ item->setData(keyDisplay, Qt::DisplayRole);
item->setData(sdlkeys[j][0], Qt::UserRole + 1);
m_bindsModel->appendRow(item);
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/FileEngine.cpp
--- a/QTfrontend/util/FileEngine.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/FileEngine.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -313,8 +313,10 @@
FileEngineHandler::FileEngineHandler(char *argv0)
{
- PHYSFS_init(argv0);
-
+ if (!PHYSFS_init(argv0))
+ {
+ qCritical("PHYSFS initialization failed");
+ }
qDebug("%s", QString("[PHYSFS] Init: %1").arg(errorStr()).toLocal8Bit().constData());
}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/KeyMap.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/util/KeyMap.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,103 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2019 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include
+#include "KeyMap.h"
+#include "SDL.h"
+
+KeyMap & KeyMap::instance()
+{
+ static KeyMap instance;
+ instance.getKeyMap();
+ return instance;
+}
+
+bool KeyMap::getKeyMap()
+{
+ if (keyMapGenerated)
+ return true;
+ QFile keyFile(":keys.csv");
+ if (!keyFile.open(QIODevice::ReadOnly))
+ {
+ qWarning("ERROR: keys.csv could not be opened!");
+ return false;
+ }
+ QString keyString = keyFile.readAll();
+ QStringList cells = QStringList() << QString("") << QString("");
+ QChar currChar;
+ bool isInQuote = false;
+ int cell = 0;
+ int charInCell = 0;
+ QString scancode = "";
+ QString keyname = "";
+ for(long long int i = 0; i < keyString.length(); i++)
+ {
+ currChar = keyString.at(i);
+ if (currChar == '\"') {
+ isInQuote = !isInQuote;
+ }
+ if (currChar == ',' && !isInQuote) {
+ cell++;
+ continue;
+ }
+ if (currChar == '\n') {
+ mapOfKeynames[(SDL_Scancode) scancode.toInt()] = keyname;
+ mapOfScancodes[keyname] = (SDL_Scancode) scancode.toInt();
+ if ((SDL_Scancode) scancode.toInt() == SDL_SCANCODE_UNKNOWN)
+ continue;
+ cell = 0;
+ scancode = "";
+ keyname = "";
+ continue;
+ }
+ if (cell == 0 && currChar != '\"') {
+ scancode += currChar;
+ } else if (cell == 1 && currChar != '\"') {
+ keyname += currChar;
+ }
+ charInCell++;
+ }
+ keyMapGenerated = true;
+ keyFile.close();
+ return true;
+}
+
+SDL_Scancode KeyMap::getScancodeFromKeyname(QString keyname)
+{
+ if (mapOfScancodes.contains(keyname))
+ return mapOfScancodes[keyname];
+ else
+ return SDL_SCANCODE_UNKNOWN;
+}
+
+QString KeyMap::getKeynameFromScancode(int scancode)
+{
+ if (mapOfKeynames.contains((SDL_Scancode) scancode))
+ if ((SDL_Scancode) scancode == SDL_SCANCODE_UNKNOWN)
+ return QString("none");
+ else
+ return mapOfKeynames[(SDL_Scancode) scancode];
+ else
+ return QString("");
+}
+
+QString KeyMap::getKeynameFromScancodeConverted(int scancode)
+{
+ SDL_Keycode keycode = SDL_GetKeyFromScancode((SDL_Scancode) scancode);
+ return SDL_GetKeyName(keycode);
+}
+
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/KeyMap.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/util/KeyMap.h Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,56 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2019 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @brief KeyMap class definition
+ */
+
+#ifndef HEDGEWARS_KEYMAP_H
+#define HEDGEWARS_KEYMAP_H
+
+#include
+#include
+#include "SDL.h"
+
+class KeyMap
+{
+ public:
+ /**
+ * @brief Returns reference to the singleton instance of this class.
+ *
+ * @see singleton pattern
+ *
+ * @return reference to the instance.
+ */
+ static KeyMap & instance();
+ SDL_Scancode getScancodeFromKeyname(QString keyname);
+ QString getKeynameFromScancode(int scancode);
+ QString getKeynameFromScancodeConverted(int scancode);
+ QString getKeynameFromKeycode(int keycode);
+
+ private:
+ // TODO: Optimize data structures
+ QHash mapOfKeynames;
+ QHash mapOfScancodes;
+ bool getKeyMap();
+ bool keyMapGenerated = false;
+
+};
+
+#endif // HEDGEWARS_KEYMAP_H
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/LibavInteraction.cpp
--- a/QTfrontend/util/LibavInteraction.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/LibavInteraction.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -57,14 +57,14 @@
bool isAudio;
QString shortName; // used for identification
QString longName; // used for displaying to user
- bool isRecomended;
+ bool isRecommended;
};
struct Format
{
QString shortName;
QString longName;
- bool isRecomended;
+ bool isRecommended;
QString extension;
QVector codecs;
};
@@ -103,6 +103,10 @@
if (strcmp(pCodec->name, "rv10") == 0 || strcmp(pCodec->name, "rv20") == 0)
continue;
+ // this encoder is experimental (as of Jan 17, 2019)
+ if (strcmp(pCodec->name, "libaom-av1") == 0)
+ continue;
+
// doesn't support stereo sound
if (strcmp(pCodec->name, "real_144") == 0)
continue;
@@ -151,30 +155,27 @@
codec.shortName = pCodec->name;
codec.longName = pCodec->long_name;
- codec.isRecomended = false;
+ codec.isRecommended = false;
if (strcmp(pCodec->name, "libx264") == 0)
{
codec.longName = "H.264/MPEG-4 Part 10 AVC (x264)";
- codec.isRecomended = true;
+ codec.isRecommended = true;
}
else if (strcmp(pCodec->name, "libxvid") == 0)
{
codec.longName = "MPEG-4 Part 2 (Xvid)";
- codec.isRecomended = true;
+ codec.isRecommended = true;
}
else if (strcmp(pCodec->name, "libmp3lame") == 0)
{
codec.longName = "MP3 (MPEG audio layer 3) (LAME)";
- codec.isRecomended = true;
+ codec.isRecommended = true;
}
else
codec.longName = pCodec->long_name;
if (strcmp(pCodec->name, "mpeg4") == 0 || strcmp(pCodec->name, "ac3_fixed") == 0)
- codec.isRecomended = true;
-
- // FIXME: remove next line
- //codec.longName += QString(" (%1)").arg(codec.shortName);
+ codec.isRecommended = true;
}
// get list of all formats
@@ -207,10 +208,7 @@
format.shortName = pFormat->name;
format.longName = QString("%1 (*.%2)").arg(pFormat->long_name).arg(ext);
- // FIXME: remove next line
- //format.longName += QString(" (%1)").arg(format.shortName);
-
- format.isRecomended = strcmp(pFormat->name, "mp4") == 0 || strcmp(pFormat->name, "avi") == 0;
+ format.isRecommended = strcmp(pFormat->name, "mp4") == 0 || strcmp(pFormat->name, "avi") == 0;
formats[pFormat->name] = format;
}
@@ -220,7 +218,7 @@
{
// first insert recomended formats
foreach(const Format & format, formats)
- if (format.isRecomended)
+ if (format.isRecommended)
pFormats->addItem(format.longName, format.shortName);
// remember where to place separator between recomended and other formats
@@ -228,7 +226,7 @@
// insert remaining formats
foreach(const Format & format, formats)
- if (!format.isRecomended)
+ if (!format.isRecommended)
pFormats->addItem(format.longName, format.shortName);
// insert separator if necessary
@@ -243,7 +241,7 @@
// first insert recomended codecs
foreach(Codec * codec, format.codecs)
{
- if (codec->isRecomended)
+ if (codec->isRecommended)
{
if (codec->isAudio)
pACodecs->addItem(codec->longName, codec->shortName);
@@ -259,7 +257,7 @@
// insert remaining codecs
foreach(Codec * codec, format.codecs)
{
- if (!codec->isRecomended)
+ if (!codec->isRecommended)
{
if (codec->isAudio)
pACodecs->addItem(codec->longName, codec->shortName);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/MessageDialog.cpp
--- a/QTfrontend/util/MessageDialog.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/MessageDialog.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -66,6 +66,7 @@
msgMsg.setWindowTitle(title != NULL ? title : "Hedgewars");
msgMsg.setText(msg);
msgMsg.setIcon(icon);
+ msgMsg.setTextFormat(Qt::PlainText);
msgMsg.setWindowModality(Qt::WindowModal);
return msgMsg.exec();
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/SDLInteraction.cpp
--- a/QTfrontend/util/SDLInteraction.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/SDLInteraction.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -25,6 +25,7 @@
#include "SDL_mixer.h"
#include "HWApplication.h"
+#include "sdlkeys.h"
#include "hwform.h" /* you know, we could just put a config singleton lookup function in gameuiconfig or something... */
#include "gameuiconfig.h"
@@ -32,12 +33,6 @@
#include "physfsrwops.h"
-extern char sdlkeys[1024][2][128];
-extern char xb360buttons[][128];
-extern char xb360dpad[128];
-extern char xbox360axes[][128];
-
-
SDLInteraction & SDLInteraction::instance()
{
static SDLInteraction instance;
@@ -55,8 +50,26 @@
m_musicTrack = "";
m_isPlayingMusic = false;
lastchannel = 0;
+ int i;
+ // Initialize sdlkeys_iskeyboard
+ for (i=0; i<1024; i++) {
+ // First 7 entries are mouse buttons (see sdlkeys.cpp)
+ if ((i > 6) && (sdlkeys[i][0][0] != '\0'))
+ sdlkeys_iskeyboard[i] = true;
+ else
+ sdlkeys_iskeyboard[i] = false;
+ }
+
if(SDL_NumJoysticks())
addGameControllerKeys();
+
+ // Add special "none" key at the end of list
+ i = 0;
+ while(i < 1024 && sdlkeys[i][1][0] != '\0')
+ i++;
+ sprintf(sdlkeys[i][0], "none");
+ sprintf(sdlkeys[i][1], "%s", HWApplication::translate("binds (keys)", unboundcontrol).toUtf8().constData());
+
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
m_soundMap = new QMap();
@@ -105,19 +118,23 @@
QStringList result;
#if SDL_VERSION_ATLEAST(2, 0, 0)
-// TODO or not TODO?
-#else
int i = 0;
while(i < 1024 && sdlkeys[i][1][0] != '\0')
i++;
// Iterate through all game controllers
+ qDebug("Detecting controllers ...");
for(int jid = 0; jid < SDL_NumJoysticks(); jid++)
{
SDL_Joystick* joy = SDL_JoystickOpen(jid);
- // Retrieve the game controller's name and strip "Controller (...)" that's added by some drivers (English only)
- QString joyname = QString(SDL_JoystickName(jid)).replace(QRegExp("^Controller \\((.*)\\)$"), "\\1");
+ // Retrieve the game controller's name
+ QString joyname = QString(SDL_JoystickNameForIndex(jid));
+
+ // Strip "Controller (...)" that's added by some drivers (English only)
+ joyname.replace(QRegExp("^Controller \\((.*)\\)$"), "\\1");
+
+ qDebug("- Controller no. %d: %s", jid, qPrintable(joyname));
// Connected Xbox 360 controller? Use specific button names then
// Might be interesting to add 'named' buttons for the most often used gamepads
@@ -129,39 +146,38 @@
// Register entries for missing axes not assigned to sticks of this joystick/gamepad
for(int aid = 0; aid < SDL_JoystickNumAxes(joy) && i < 1021; aid++)
{
- // Again store the part of the string not changing for multiple uses
- QString axis = prefix + HWApplication::translate("binds (keys)", "Axis") + QString(" %1 ").arg(aid + 1);
+ QString axis = prefix + HWApplication::translate("binds (keys)", controlleraxis).arg(aid + 1);
// Entry for "Axis Up"
sprintf(sdlkeys[i][0], "j%da%du", jid, aid);
- sprintf(sdlkeys[i++][1], "%s", ((isxb && aid < 5) ? (prefix + HWApplication::translate("binds (keys)", xbox360axes[aid * 2])) : axis + HWApplication::translate("binds (keys)", "(Up)")).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", ((isxb && aid < 5) ? (prefix + HWApplication::translate("binds (keys)", xbox360axes[aid * 2])) : (axis.arg(HWApplication::translate("binds (keys)", controllerup)))).toUtf8().constData());
// Entry for "Axis Down"
sprintf(sdlkeys[i][0], "j%da%dd", jid, aid);
- sprintf(sdlkeys[i++][1], "%s", ((isxb && aid < 5) ? (prefix + HWApplication::translate("binds (keys)", xbox360axes[aid * 2 + 1])) : axis + HWApplication::translate("binds (keys)", "(Down)")).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", ((isxb && aid < 5) ? (prefix + HWApplication::translate("binds (keys)", xbox360axes[aid * 2 + 1])) : (axis.arg(HWApplication::translate("binds (keys)", controllerdown)))).toUtf8().constData());
}
// Register entries for all coolie hats of this joystick/gamepad
for(int hid = 0; hid < SDL_JoystickNumHats(joy) && i < 1019; hid++)
{
// Again store the part of the string not changing for multiple uses
- QString hat = prefix + (isxb ? (HWApplication::translate("binds (keys)", xb360dpad) + QString(" ")) : HWApplication::translate("binds (keys)", "Hat") + QString(" %1 ").arg(hid + 1));
+ QString hat = prefix + (isxb ? (HWApplication::translate("binds (keys)", xb360dpad) + QString(" ")) : HWApplication::translate("binds (keys)", controllerhat).arg(hid + 1));
// Entry for "Hat Up"
sprintf(sdlkeys[i][0], "j%dh%du", jid, hid);
- sprintf(sdlkeys[i++][1], "%s", (hat + HWApplication::translate("binds (keys)", "(Up)")).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", hat.arg(HWApplication::translate("binds (keys)", controllerup)).toUtf8().constData());
// Entry for "Hat Down"
sprintf(sdlkeys[i][0], "j%dh%dd", jid, hid);
- sprintf(sdlkeys[i++][1], "%s", (hat + HWApplication::translate("binds (keys)", "(Down)")).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", hat.arg(HWApplication::translate("binds (keys)", controllerdown)).toUtf8().constData());
// Entry for "Hat Left"
sprintf(sdlkeys[i][0], "j%dh%dl", jid, hid);
- sprintf(sdlkeys[i++][1], "%s", (hat + HWApplication::translate("binds (keys)", "(Left)")).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", hat.arg(HWApplication::translate("binds (keys)", controllerleft)).toUtf8().constData());
// Entry for "Hat Right"
sprintf(sdlkeys[i][0], "j%dh%dr", jid, hid);
- sprintf(sdlkeys[i++][1], "%s", (hat + HWApplication::translate("binds (keys)", "(Right)")).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", hat.arg(HWApplication::translate("binds (keys)", controllerright)).toUtf8().constData());
}
// Register entries for all buttons of this joystick/gamepad
@@ -169,7 +185,7 @@
{
// Buttons
sprintf(sdlkeys[i][0], "j%db%d", jid, bid);
- sprintf(sdlkeys[i++][1], "%s", (prefix + ((isxb && bid < 10) ? (HWApplication::translate("binds (keys)", xb360buttons[bid]) + QString(" ")) : HWApplication::translate("binds (keys)", "Button") + QString(" %1").arg(bid + 1))).toUtf8().constData());
+ sprintf(sdlkeys[i++][1], "%s", (prefix + ((isxb && bid < 10) ? (HWApplication::translate("binds (keys)", xb360buttons[bid]) + QString(" ")) : HWApplication::translate("binds (keys)", controllerbutton).arg(bid + 1))).toUtf8().constData());
}
// Close the game controller as we no longer need it
SDL_JoystickClose(joy);
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/namegen.cpp
--- a/QTfrontend/util/namegen.cpp Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/namegen.cpp Wed Jul 31 23:14:27 2019 +0200
@@ -155,7 +155,7 @@
for(int i = 0; i < HEDGEHOGS_PER_TEAM; i++)
{
HWHog hh = team.hedgehog(i);
- if (sameHogs and i > 0)
+ if (sameHogs && i > 0)
hh.Hat = team.hedgehog(i-1).Hat;
else
hh.Hat = getRandomHat(withDLC);
@@ -228,6 +228,11 @@
team.setHedgehog(HedgehogNumber, hh);
}
+void HWNamegen::teamLocalizedDefaultVoice(HWTeam & team, bool withDLC)
+{
+ team.setVoicepack(getLocalizedDefaultVoice(withDLC));
+}
+
QStringList HWNamegen::dictContents(const QString filename)
{
QStringList list;
@@ -488,3 +493,26 @@
//pick a random voice
return Voices[rand()%(Voices.size())];
}
+
+QString HWNamegen::getLocalizedDefaultVoice(bool withDLC)
+{
+ QStringList entries = DataManager::instance().entryList(
+ "Sounds/voices",
+ QDir::Dirs | QDir::NoDotAndDotDot,
+ QStringList("*"),
+ withDLC);
+
+ QString loc = QLocale().name();
+ if(entries.contains("Default_" + loc))
+ {
+ return QString("Default_" + loc);
+ }
+ else if(entries.contains("Default_" + loc.left(2)))
+ {
+ return QString("Default_" + loc.left(2));
+ }
+ else
+ {
+ return QString("Default");
+ }
+}
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/util/namegen.h
--- a/QTfrontend/util/namegen.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/util/namegen.h Wed Jul 31 23:14:27 2019 +0200
@@ -38,6 +38,7 @@
static void teamRandomHogNames(HWTeam & team);
static void teamRandomHogName(HWTeam & team, const int HedgehogNumber);
static void teamRandomEverything(HWTeam & team);
+ static void teamLocalizedDefaultVoice(HWTeam & team, bool withDLC = true);
private:
HWNamegen();
@@ -48,6 +49,7 @@
static QString getRandomFort(bool withDLC = true);
static QString getRandomFlag(bool withDLC = true);
static QString getRandomVoice(bool withDLC = true);
+ static QString getLocalizedDefaultVoice(bool withDLC = true);
static QList TypesTeamnames;
static QList TypesHatnames;
diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/weapons.h
--- a/QTfrontend/weapons.h Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/weapons.h Wed Jul 31 23:14:27 2019 +0200
@@ -81,7 +81,7 @@
amKnife-------------------------------------------------------------------------------|
amRubber-------------------------------------------------------------------------------|
amAirMine-------------------------------------------------------------------------------|
- amDuck-----------------------------------------------------------------------------------|
+ amCreeper--------------------------------------------------------------------------------|
amMinigun---------------------------------------------------------------------------------|
*/
#define AMMOLINE_DEFAULT_QT "93919294221991210322351110012000000002111001010111110001000"
@@ -89,8 +89,8 @@
#define AMMOLINE_DEFAULT_DELAY "00000000000002055000000400070040000000002200000006000200000"
#define AMMOLINE_DEFAULT_CRATE "13111103121111111231141111111111111112111111111111111111111"
-#define AMMOLINE_CRAZY_QT "99999999999999999929999999999999992999999999999999929991999"
-#define AMMOLINE_CRAZY_PROB "11111101111111111111111111111111111111111111111111111111111"
+#define AMMOLINE_CRAZY_QT "99999999999999999929999999999999992999999999999999929991909"
+#define AMMOLINE_CRAZY_PROB "11111101111111111111111111111111111111111111111111111111101"
#define AMMOLINE_CRAZY_DELAY "00000000000000000000000000000000000000000000000000000000000"
#define AMMOLINE_CRAZY_CRATE "13111103121111111231141111111111111112111111111111111111111"
@@ -119,15 +119,20 @@
#define AMMOLINE_PORTALS_DELAY "00000000000002055000000400070040000000002000000006000200000"
#define AMMOLINE_PORTALS_CRATE "13111103121111111231141111111111111112111111111111111111111"
-#define AMMOLINE_ONEEVERY_QT "11111191111111111111111111111111111111111111111111111111111"
-#define AMMOLINE_ONEEVERY_PROB "11111101111111111111111111111111111111111111111111111111111"
+#define AMMOLINE_ONEEVERY_QT "11111191111111111111111111111111111111111111111111111111101"
+#define AMMOLINE_ONEEVERY_PROB "11111101111111111111111111111111111111111111111111111111101"
#define AMMOLINE_ONEEVERY_DELAY "00000000000000000000000000000000000000000000000000000000000"
#define AMMOLINE_ONEEVERY_CRATE "11111101111111111111111111111111111111111111111111111111111"
+#define AMMOLINE_BRW_QT "33323392332332322323233131122113000003232203022022200020301"
+#define AMMOLINE_BRW_PROB "00000000000000000000000000000000111110000000000000000000000"
+#define AMMOLINE_BRW_DELAY "00000000000000000000000000000000000000000000000000000000000"
+#define AMMOLINE_BRW_CRATE "11111101111111111111111111111111111111111111111111111111111"
+
#define AMMOLINE_HIGHLANDER_QT "11111191111111111111019111111111100101111101111001001011101"
#define AMMOLINE_HIGHLANDER_PROB "00000000000000000000000000000000000000000000000000000000000"
#define AMMOLINE_HIGHLANDER_DELAY "00000000000000000000000000000000000000000000000000000000000"
-#define AMMOLINE_HIGHLANDER_CRATE "00000000000000000090000000000000000000000000000000000000000"
+#define AMMOLINE_HIGHLANDER_CRATE "00000000000000000000000000000000000000000000000000000000000"
#define AMMOLINE_CONSTRUCTION_QT "11000190000000100100900000000000000000000000000000000000000"
#define AMMOLINE_CONSTRUCTION_PROB "11111101111111100100011111101111111111111101111100101110101"
diff -r 0135e64c6c66 -r c4fd2813b127 README.md
--- a/README.md Wed May 16 18:22:28 2018 +0200
+++ b/README.md Wed Jul 31 23:14:27 2019 +0200
@@ -1,5 +1,6 @@
Hedgewars - a turn-based strategy game
======================================
+[![Build Status](https://travis-ci.org/hedgewars/hw.svg)](https://travis-ci.org/hedgewars/hw)
Description
-----------
@@ -63,10 +64,11 @@
* Tab: Switch hedgehog (after activating the utility)
* 1-5: Set weapon timer
* F1-F10: Weapon shortcuts
+* M: Mission panel / game mode information. Hold pressed to display, release to hide
* P: Pause, when playing offline, toggle automatic turn skipping when online
-* Esc: Quit with prompt (also shows mission panel)
+* Esc: Quit with prompt
* T: Chat
-* U: Team chat
+* U: Clan chat
For the full list, go to the Hedgewars settings. Also read the weapon tooltips
for weapon-specific controls.
@@ -77,9 +79,30 @@
configured controls:
* Precise + Toggle hedgehog tags: Change visible hedgehog tags (team name/hog name/health)
-* Switch + Toggle hedgehog tags: Toggle hedgehog tag translucency
+* Switch + Toggle hedgehog tags: Toggle hedgehog tag translucency
* Precise + Toggle team bars + Switch: Toggle HUD
* Precise + Capture (screenshot key): Save current map + mask into Screenshot directory
+* Precise + zoom in/out: Change zoom in smaller steps
+* Precise + volume up/down: Change volume in smaller steps
+* Precise + Reset zoom: Set zoom to 100% (instead of the zoom level in the settings)
+
+System requirements
+-------------------
+For PC or Mac:
+
+* Mouse and keyboard
+* Monitor, minimal resolution 1024×768
+* 200 MiB storage space
+* Processor: 1 GHz (1 core is enough), 64bit recommended
+* Video card: 250 MHz or so
+ (any decent card from the year 2004 or later should do fine)
+* 1 GiB RAM minimum
+* Operating system: Windows Vista/7/8/10, GNU/Linux, macOS, FreeBSD, others
+
+Hedgewars has been ported to other operating systems in the past.
+Check out for the latest information.
+
+Gamepads are supported partially (only in-game, not in the main menu).
Installation instructions
-------------------------
@@ -93,8 +116,6 @@
Mercurial as DVCS. A Git repository is also available (mirrored daily)
at .
-[![Build Status](https://travis-ci.org/hedgewars/hw.svg)](https://travis-ci.org/hedgewars/hw)
-
Contribute
----------
If you see a bug or have any suggestion please use the official bug tracker at
@@ -116,14 +137,13 @@
images and sounds are distributed under the terms of the GNU Free Documentation
Licence version 1.2. See the `COPYING` file for the full text of the licenses.
-Copyright 2004-2015 Andrey Korotaev and others.
-See `QTfrontend/res/html/about.html` and `CREDITS` for a more complete list of
-authors.
+Copyright 2004-2018 Andrey Korotaev and others.
+Click on the Hedgewars logo in the main menu and read the `CREDITS` text file
+for a more complete list of authors.
Contact
-------
* Homepage - https://hedgewars.org/
* IRC channel - irc://irc.freenode.net/hedgewars
* Community forum - https://hedgewars.org/forum
-* Mailing list - https://mail.gna.org/listinfo/hedgewars-dev
diff -r 0135e64c6c66 -r c4fd2813b127 bin/CMakeLists.txt
--- a/bin/CMakeLists.txt Wed May 16 18:22:28 2018 +0200
+++ b/bin/CMakeLists.txt Wed Jul 31 23:14:27 2019 +0200
@@ -1,9 +1,6 @@
if(WIN32 AND NOT UNIX)
- file(GLOB_RECURSE DLLs *.dll)
- file(GLOB ICOs *.ico)
-
- install(FILES
- ${DLLs}
- ${ICOs}
- DESTINATION ${target_library_install_dir})
+ install(DIRECTORY .
+ DESTINATION ${target_library_install_dir}
+ FILES_MATCHING PATTERN "*.dll" PATTERN "*.ico"
+ )
endif(WIN32 AND NOT UNIX)
diff -r 0135e64c6c66 -r c4fd2813b127 cmake_modules/CMakePascalInformation.cmake
--- a/cmake_modules/CMakePascalInformation.cmake Wed May 16 18:22:28 2018 +0200
+++ b/cmake_modules/CMakePascalInformation.cmake Wed Jul 31 23:14:27 2019 +0200
@@ -149,7 +149,7 @@
# create a Pascal shared library
if(NOT CMAKE_Pascal_CREATE_SHARED_LIBRARY)
if(WIN32)
- set(CMAKE_Pascal_CREATE_SHARED_LIBRARY "${EXECUTABLE_OUTPUT_PATH}/ppas.bat")
+ file(TO_NATIVE_PATH "${EXECUTABLE_OUTPUT_PATH}/ppas.bat" CMAKE_Pascal_CREATE_SHARED_LIBRARY)
else(WIN32)
set(CMAKE_Pascal_CREATE_SHARED_LIBRARY "${EXECUTABLE_OUTPUT_PATH}/ppas.sh")
endif(WIN32)
@@ -198,7 +198,7 @@
# link Pascal objects in a single executable
if(NOT CMAKE_Pascal_LINK_EXECUTABLE)
if(WIN32)
- set(CMAKE_Pascal_LINK_EXECUTABLE "${EXECUTABLE_OUTPUT_PATH}/ppas.bat")
+ file(TO_NATIVE_PATH "${EXECUTABLE_OUTPUT_PATH}/ppas.bat" CMAKE_Pascal_LINK_EXECUTABLE)
else(WIN32)
set(CMAKE_Pascal_LINK_EXECUTABLE "${EXECUTABLE_OUTPUT_PATH}/ppas.sh")
endif(WIN32)
diff -r 0135e64c6c66 -r c4fd2813b127 cmake_modules/cpackvars.cmake
--- a/cmake_modules/cpackvars.cmake Wed May 16 18:22:28 2018 +0200
+++ b/cmake_modules/cpackvars.cmake Wed Jul 31 23:14:27 2019 +0200
@@ -19,8 +19,8 @@
if(WIN32 AND NOT UNIX)
set(CPACK_NSIS_DISPLAY_NAME "Hedgewars")
- set(CPACK_NSIS_HELP_LINK "http://www.hedgewars.org/")
- set(CPACK_NSIS_URL_INFO_ABOUT "http://www.hedgewars.org/")
+ set(CPACK_NSIS_HELP_LINK "https://www.hedgewars.org/")
+ set(CPACK_NSIS_URL_INFO_ABOUT "https://www.hedgewars.org/")
set(CPACK_NSIS_CONTACT "unC0Rr@gmail.com")
set(CPACK_NSIS_MODIFY_PATH OFF)
set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
@@ -82,7 +82,6 @@
"[rR]elease$"
"CPack"
"CTestTestfile.cmake"
- "gameServer2"
"cmake_install\\\\.cmake$"
"cmake_uninstall\\\\.cmake$"
"CMakeCache\\\\.txt$"
@@ -104,6 +103,9 @@
"^${CMAKE_CURRENT_SOURCE_DIR}/install_manifest.txt"
"^${CMAKE_CURRENT_SOURCE_DIR}/CMakeCache.txt"
"^${CMAKE_CURRENT_SOURCE_DIR}/hedgewars\\\\."
+ "^${CMAKE_CURRENT_SOURCE_DIR}/gameServer2"
+ "^${CMAKE_CURRENT_SOURCE_DIR}/rust"
+ "^${CMAKE_CURRENT_SOURCE_DIR}/qmlfrontend"
)
include(CPack)
diff -r 0135e64c6c66 -r c4fd2813b127 cmake_modules/paths.cmake
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/Actions.hs
--- a/gameServer/Actions.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/Actions.hs Wed Jul 31 23:14:27 2019 +0200
@@ -46,6 +46,7 @@
-----------------------------
#if defined(OFFICIAL_SERVER)
import OfficialServer.GameReplayStore
+import qualified Data.Yaml as YAML
#endif
import CoreTypes
import Utils
@@ -116,7 +117,7 @@
loggedIn <- client's isVisible
when (ri /= lobbyId) $ do
- processAction $ MoveToLobby ("quit: " `B.append` msg)
+ processAction $ (MoveToLobby msg)
return ()
clientsChans <- liftM (Prelude.map sendChan . Prelude.filter isVisible) $! allClientsS
@@ -265,6 +266,8 @@
ModifyClient2 (fromJust newMasterId) (\c -> c{isMaster = True})
, AnswerClients [sendChan $ fromJust newMaster] ["ROOM_CONTROL_ACCESS", "1"]
, AnswerClients thisRoomChans ["CLIENT_FLAGS", "+h", nick $ fromJust newMaster]
+ -- TODO: Send message to other clients, too (requires proper localization, however)
+ , AnswerClients [sendChan $ fromJust newMaster] ["CHAT", nickServer, loc "You're the new room master!"]
]
processAction $
@@ -476,7 +479,7 @@
c <- client's isChecker
when (not b) $ (if c then checkerLogin else playerLogin) passwd isAdmin isContr
Guest | isRegisteredUsersOnly si -> do
- processAction $ ByeClient "Registered users only"
+ processAction $ ByeClient $ loc "This server only allows registered users to join."
| otherwise -> do
b <- isBanned
c <- client's isChecker
@@ -556,7 +559,7 @@
mapM_ processAction [
AddIP2Bans clHost (loc "60 seconds cooldown after kick") (addUTCTime 60 currentTime)
, ModifyClient (\c -> c{isKickedFromServer = True})
- , ByeClient "Kicked"
+ , ByeClient $ loc "Kicked"
]
@@ -633,7 +636,7 @@
mapM_ processAction
[
CheckBanned True
- , AnswerClients [sendChan cl] ["CONNECTED", "Hedgewars server http://www.hedgewars.org/", serverVersion]
+ , AnswerClients [sendChan cl] ["CONNECTED", "Hedgewars server https://www.hedgewars.org/", serverVersion]
]
else
processAction $ ByeClient $ loc "Reconnected too fast"
@@ -730,9 +733,9 @@
processAction (Random chans items) = do
- let i = if null items then ["heads", "tails"] else items
+ let i = if null items then [loc "heads", loc "tails"] else items
n <- io $ randomRIO (0, length i - 1)
- processAction $ AnswerClients chans ["CHAT", "[random]", i !! n]
+ processAction $ AnswerClients chans ["CHAT", if null items then nickRandomCoin else nickRandomCustom, i !! n]
processAction (LoadGhost location) = do
@@ -817,14 +820,6 @@
uid <- client's clUID
io $ writeChan (dbQueries si) $ GetReplayName ci (hashUnique uid) rname
-#else
-processAction SaveReplay = return ()
-processAction CheckRecord = return ()
-processAction (CheckFailed _) = return ()
-processAction (CheckSuccess _) = return ()
-processAction (QueryReplay _) = return ()
-#endif
-
processAction (ShowReplay rname) = do
c <- client's sendChan
cl <- client's id
@@ -839,25 +834,46 @@
let (teams', params1, params2, roundMsgs') = fromJust cInfo
when (isJust cInfo) $ do
- mapM_ processAction $ concat [
- [AnswerClients [c] ["JOINED", nick cl]]
- , answerFullConfigParams cl params1 params2
- , answerAllTeams cl teams'
- , [AnswerClients [c] ["RUN_GAME"]]
- , [AnswerClients [c] $ "EM" : roundMsgs']
- , [AnswerClients [c] ["KICKED"]]
- ]
+ mapM_ processAction $
+ if clientProto cl < 58 then
+ concat [
+ [AnswerClients [c] ["JOINED", nick cl]]
+ , answerFullConfigParams cl params1 params2
+ , answerAllTeams cl teams'
+ , [AnswerClients [c] ["RUN_GAME"]]
+ , [AnswerClients [c] $ "EM" : roundMsgs']
+ , [AnswerClients [c] ["KICKED"]]
+ ]
+ else
+ concat [
+ [AnswerClients [c] ["REPLAY_START"]]
+ , answerFullConfigParams cl params1 params2
+ , answerAllTeams cl teams'
+ , [AnswerClients [c] ["RUN_GAME"]]
+ , [AnswerClients [c] $ "EM" : roundMsgs']
+ ]
processAction (SaveRoom rname) = do
rnc <- gets roomsClients
ri <- clientRoomA
rm <- io $ room'sM rnc id ri
- liftIO $ writeFile (B.unpack rname) $ show (greeting rm, roomSaves rm)
+ liftIO $ YAML.encodeFile (B.unpack rname) (greeting rm, roomSaves rm)
processAction (LoadRoom rname) = do
- (g, rs) <- liftIO $ liftM read $ readFile (B.unpack rname)
+ Right (g, rs) <- io $ YAML.decodeFileEither (B.unpack rname)
processAction $ ModifyRoom $ \r -> r{greeting = g, roomSaves = rs}
+#else
+processAction SaveReplay = return ()
+processAction CheckRecord = return ()
+processAction (CheckFailed _) = return ()
+processAction (CheckSuccess _) = return ()
+processAction (QueryReplay _) = processAction $ Warning $ loc "This server does not support replays!"
+processAction (ShowReplay rname) = return ()
+processAction (SaveRoom rname) = return ()
+processAction (LoadRoom rname) = return ()
+#endif
+
processAction Cleanup = do
jm <- gets joinsMonitor
@@ -879,3 +895,13 @@
processAction CheckVotes =
checkVotes >>= mapM_ processAction
+
+processAction (ShowRegisteredOnlyState chans) = do
+ si <- gets serverInfo
+ processAction $ AnswerClients chans
+ ["CHAT", nickServer,
+ if isRegisteredUsersOnly si then
+ loc "This server no longer allows unregistered players to join."
+ else
+ loc "This server now allows unregistered players to join."
+ ]
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/ClientIO.hs
--- a/gameServer/ClientIO.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/ClientIO.hs Wed Jul 31 23:14:27 2019 +0200
@@ -96,6 +96,7 @@
where
killReciever = Exception.throwTo tId . ShutdownThreadException
+ -- intentionally not localized
quitMessage ["BYE"] = "bye"
quitMessage ("BYE":msg:_) = msg
quitMessage _ = error "quitMessage"
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/CommandHelp.hs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gameServer/CommandHelp.hs Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,107 @@
+{-
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2015 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ \-}
+
+{-# LANGUAGE CPP, OverloadedStrings #-}
+module CommandHelp where
+
+import qualified Data.ByteString.Char8 as B
+
+import CoreTypes
+import Utils
+import Consts
+
+-- List and documentation of chat commands
+
+cmdHelpSharedPlayer :: [B.ByteString]
+cmdHelpSharedPlayer = [
+ loc "/info : Show info about player",
+ loc "/me : Chat action, e.g. '/me eats pizza' becomes '* Player eats pizza'",
+ loc "/rnd: Flip a virtual coin and reply with 'heads' or 'tails'",
+ loc "/rnd [A] [B] [C] [...]: Reply with a random word from the given list",
+#if defined(OFFICIAL_SERVER)
+ loc "/watch : Watch a demo stored on the server with the given ID",
+#endif
+ loc "/quit: Quit the server",
+ loc "/help: Show chat command help"
+ ]
+
+cmdHelpRoomOnlyPlayer :: [B.ByteString]
+cmdHelpRoomOnlyPlayer = [
+ -- For everyone
+ loc "/callvote [arguments]: Start a vote",
+ loc "/vote : Vote 'yes' or 'no' for active vote",
+ -- For room master only
+ loc "/greeting [message]: Set or clear greeting message to be shown to players who join the room",
+ loc "/delegate : Surrender room control to player",
+ loc "/maxteams : Limit maximum number of teams to N"
+ ]
+
+cmdHelpSharedAdmin :: [B.ByteString]
+cmdHelpSharedAdmin = [
+ loc "/global : Send global chat message which can be seen by everyone on the server",
+ loc "/registered_only: Toggle 'registered only' state. If enabled, only registered players can join server",
+ loc "/super_power: Activate your super power. With it you can enter any room and are protected from kicking. Expires when you leave server"
+ -- TODO: Add /restart_server? This command seems broken at the moment
+ ]
+
+cmdHelpLobbyOnlyAdmin :: [B.ByteString]
+cmdHelpLobbyOnlyAdmin = [
+ loc "/stats: Query server stats"
+ ]
+
+cmdHelpRoomOnlyAdmin :: [B.ByteString]
+cmdHelpRoomOnlyAdmin = [
+ loc "/force : Force vote result for active vote",
+ loc "/fix: Force this room to stay open when it is empty",
+ loc "/unfix: Undo the /fix command",
+ loc "/save : Add current room configuration as votable choice for /callvote map",
+ loc "/delete : Delete a votable room configuration",
+ loc "/saveroom : Save all votable room configurations (and the greeting) of this room into a file",
+ loc "/loadroom : Load votable room configurations (and greeting) from a file"
+ ]
+
+cmdHelpHeaderLobby :: [B.ByteString]
+cmdHelpHeaderLobby = [ loc "List of lobby chat commands:" ]
+
+cmdHelpHeaderRoom :: [B.ByteString]
+cmdHelpHeaderRoom = [ loc "List of room chat commands:" ]
+
+cmdHelpHeaderAdmin :: [B.ByteString]
+cmdHelpHeaderAdmin = [ loc "Commands for server admins only:" ]
+
+-- Put it all together
+-- Lobby commands
+cmdHelpLobbyPlayer :: [B.ByteString]
+cmdHelpLobbyPlayer = cmdHelpHeaderLobby ++ cmdHelpSharedPlayer
+
+cmdHelpLobbyAdmin :: [B.ByteString]
+cmdHelpLobbyAdmin = cmdHelpLobbyPlayer ++ cmdHelpHeaderAdmin ++ cmdHelpLobbyOnlyAdmin ++ cmdHelpSharedAdmin
+
+-- Room commands
+cmdHelpRoomPlayer :: [B.ByteString]
+cmdHelpRoomPlayer = cmdHelpHeaderRoom ++ cmdHelpRoomOnlyPlayer ++ cmdHelpSharedPlayer
+
+cmdHelpRoomAdmin :: [B.ByteString]
+cmdHelpRoomAdmin = cmdHelpRoomPlayer ++ cmdHelpHeaderAdmin ++ cmdHelpRoomOnlyAdmin ++ cmdHelpSharedAdmin
+
+-- Helper functions for chat command handler
+cmdHelpActionEntry :: [ClientChan] -> B.ByteString -> Action
+cmdHelpActionEntry chan msg = AnswerClients chan [ "CHAT", nickServer, msg ]
+
+cmdHelpActionList :: [ClientChan] -> [B.ByteString] -> [Action]
+cmdHelpActionList chan list = map (cmdHelpActionEntry chan) list
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/Consts.hs
--- a/gameServer/Consts.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/Consts.hs Wed Jul 31 23:14:27 2019 +0200
@@ -23,3 +23,40 @@
serverVersion :: B.ByteString
serverVersion = "3"
+
+-- Maximum hedgehogs per team
+cHogsPerTeam :: Int
+cHogsPerTeam = 8
+
+-- Maximum teams count
+cMaxTeams :: Int
+cMaxTeams = 8
+
+-- Maximum total number of hedgehogs
+cMaxHHs :: Int
+cMaxHHs = cHogsPerTeam * cMaxTeams
+
+{- "Fake" nick names used for special server messages in chat.
+They are enclosed in brackets; these characters not allowed in real nick names.
+The brackets are required as they are parsed by the frontend.
+Names enclosed in square brackets send messages that are supposed to be translated by the frontend.
+Names enclosed in parenthesis send messages that are not supposed to be translated. -}
+
+-- For most server messages, usually response to a command
+nickServer :: B.ByteString
+nickServer = "[server]"
+
+-- For /rnd command
+nickRandomCoin :: B.ByteString
+nickRandomCoin = "[random]"
+
+nickRandomCustom :: B.ByteString
+nickRandomCustom = "(random)"
+
+-- For /global command
+nickGlobal :: B.ByteString
+nickGlobal = "(global notice)"
+
+-- For greeting message added with /greeting command
+nickGreeting :: B.ByteString
+nickGreeting = "(greeting)"
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/CoreTypes.hs
--- a/gameServer/CoreTypes.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/CoreTypes.hs Wed Jul 31 23:14:27 2019 +0200
@@ -103,11 +103,13 @@
| ReactCmd [B.ByteString]
| CheckVotes
| SetRandomSeed
+ | ShowRegisteredOnlyState [ClientChan]
data Event = LobbyChatMessage
| EngineMessage
| RoomJoin
+ | RoomNameUpdate
type EventsInfo = [(Int, UTCTime)]
@@ -310,9 +312,9 @@
ServerInfo
True
False
- "http://www.hedgewars.org/
"
- "Hedgewars 0.9.24 is out! Please update. Download page here
"
- 55 -- latestReleaseVersion
+ "https://www.hedgewars.org/
"
+ "Hedgewars 0.9.25 is out! Please update. Download page here
"
+ 57 -- latestReleaseVersion
41 -- earliestCompatibleVersion
46631
""
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/FloodDetection.hs
--- a/gameServer/FloodDetection.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/FloodDetection.hs Wed Jul 31 23:14:27 2019 +0200
@@ -36,15 +36,19 @@
einfo LobbyChatMessage = eiLobbyChat
einfo EngineMessage = eiEM
einfo RoomJoin = eiJoin
+ einfo RoomNameUpdate = eiLobbyChat
transformField LobbyChatMessage f = \c -> c{eiLobbyChat = f $ eiLobbyChat c}
transformField EngineMessage f = \c -> c{eiEM = f $ eiEM c}
transformField RoomJoin f = \c -> c{eiJoin = f $ eiJoin c}
+ transformField RoomNameUpdate f = transformField LobbyChatMessage f
+
boundaries :: Event -> (Int, (NominalDiffTime, Int), (NominalDiffTime, Int), ([Action], [Action]))
boundaries LobbyChatMessage = (3, (10, 2), (30, 3), (chat1, chat2))
boundaries EngineMessage = (8, (10, 4), (25, 5), (em1, em2))
boundaries RoomJoin = (2, (10, 2), (35, 3), (join1, join2))
+ boundaries RoomNameUpdate = (\(a, b, c, _) -> (a, b, c, (roomName1, roomName2))) $ boundaries LobbyChatMessage
chat1 = [Warning $ loc "Warning! Chat flood protection activated"]
chat2 = [ByeClient $ loc "Excess flood"]
@@ -52,6 +56,8 @@
em2 = [ByeClient $ loc "Excess flood"]
join1 = [Warning $ loc "Warning! Joins flood protection activated"]
join2 = [ByeClient $ loc "Excess flood"]
+ roomName1 = [Warning $ loc "Warning! Room name change flood protection activated"]
+ roomName2 = [ByeClient $ loc "Excess flood"]
doCheck ei = do
curTime <- io getCurrentTime
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/HWProtoCore.hs
--- a/gameServer/HWProtoCore.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/HWProtoCore.hs Wed Jul 31 23:14:27 2019 +0200
@@ -31,8 +31,9 @@
import HandlerUtils
import RoomsAndClients
import Utils
+import Consts
-handleCmd, handleCmd_loggedin :: CmdHandler
+handleCmd, handleCmd_loggedin, handleCmd_lobbyOnly, handleCmd_roomOnly :: CmdHandler
handleCmd ["PING"] = answerClient ["PONG"]
@@ -40,7 +41,9 @@
handleCmd ("QUIT" : xs) = return [ByeClient msg]
where
- msg = if not $ null xs then "User quit: " `B.append` (head xs) else loc "bye"
+ -- "bye" is a special string (do not translate!) when the user quits manually,
+ -- otherwise there will be an additional server message
+ msg = if not $ null xs then (head xs) else "bye"
handleCmd ["PONG"] = do
@@ -61,44 +64,76 @@
else
handleCmd_NotEntered cmd
+handleCmd_lobbyOnly cmd = do
+ (ci, rnc) <- ask
+ if (clientRoom rnc ci) == lobbyId then
+ handleCmd cmd
+ else
+ return [Warning $ loc "This command is only available in the lobby."]
+
+handleCmd_roomOnly cmd = do
+ (ci, rnc) <- ask
+ if (clientRoom rnc ci) == lobbyId then
+ return [Warning $ loc "This command is only available in rooms."]
+ else
+ handleCmd cmd
+
+-- Chat command handling
+unknownCmdWarningText :: B.ByteString
+unknownCmdWarningText = loc "Unknown command or invalid parameters. Say '/help' in chat for a list of commands."
+
+handleCmd_loggedin ["CMD"] = return [Warning unknownCmdWarningText]
handleCmd_loggedin ["CMD", parameters] = uncurry h $ extractParameters parameters
where
- h "DELEGATE" n | not $ B.null n = handleCmd ["DELEGATE", n]
- h "SAVEROOM" n | not $ B.null n = handleCmd ["SAVEROOM", n]
- h "LOADROOM" n | not $ B.null n = handleCmd ["LOADROOM", n]
- h "SAVE" n | not $ B.null n = let (sn, ln) = B.break (== ' ') n in if B.null ln then return [] else handleCmd ["SAVE", sn, B.tail ln]
- h "DELETE" n | not $ B.null n = handleCmd ["DELETE", n]
- h "STATS" _ = handleCmd ["STATS"]
- h "PART" m | not $ B.null m = handleCmd ["PART", m]
- | otherwise = handleCmd ["PART"]
- h "QUIT" m | not $ B.null m = handleCmd ["QUIT", m]
- | otherwise = handleCmd ["QUIT"]
+ -- room-only commands
+ h "DELEGATE" n | not $ B.null n = handleCmd_roomOnly ["DELEGATE", n]
+ h "SAVEROOM" n | not $ B.null n = handleCmd_roomOnly ["SAVEROOM", n]
+ h "LOADROOM" n | not $ B.null n = handleCmd_roomOnly ["LOADROOM", n]
+ h "SAVE" n | not $ B.null n = let (sn, ln) = B.break (== ' ') n in if B.null ln then return [Warning unknownCmdWarningText] else handleCmd_roomOnly ["SAVE", sn, B.tail ln]
+ h "DELETE" n | not $ B.null n = handleCmd_roomOnly ["DELETE", n]
+ h "FIX" _ = handleCmd_roomOnly ["FIX"]
+ h "UNFIX" _ = handleCmd_roomOnly ["UNFIX"]
+ h "GREETING" msg = handleCmd_roomOnly ["GREETING", msg]
+ h "CALLVOTE" msg | B.null msg = handleCmd_roomOnly ["CALLVOTE"]
+ | otherwise = let (c, p) = extractParameters msg in
+ if B.null p then handleCmd_roomOnly ["CALLVOTE", c] else handleCmd_roomOnly ["CALLVOTE", c, p]
+ h "VOTE" msg | not $ B.null msg = handleCmd_roomOnly ["VOTE", upperCase msg]
+ | otherwise = handleCmd_roomOnly ["VOTE", ""]
+ h "FORCE" msg | not $ B.null msg = handleCmd_roomOnly ["VOTE", upperCase msg, "FORCE"]
+ | otherwise = handleCmd_roomOnly ["VOTE", "", "FORCE"]
+ h "MAXTEAMS" n | not $ B.null n = handleCmd_roomOnly ["MAXTEAMS", n]
+ | otherwise = handleCmd_roomOnly ["MAXTEAMS"]
+
+ -- lobby-only commands
+ h "STATS" _ = handleCmd_lobbyOnly ["STATS"]
+ h "RESTART_SERVER" p = handleCmd_lobbyOnly ["RESTART_SERVER", upperCase p]
+
+ -- room and lobby commands
+ h "QUIT" _ = handleCmd ["QUIT"]
h "RND" p = handleCmd ("RND" : B.words p)
h "GLOBAL" p = serverAdminOnly $ do
rnc <- liftM snd ask
let chans = map (sendChan . client rnc) $ allClients rnc
- return [AnswerClients chans ["CHAT", "[global notice]", p]]
+ return [AnswerClients chans ["CHAT", nickGlobal, p]]
h "WATCH" f = return [QueryReplay f]
- h "FIX" _ = handleCmd ["FIX"]
- h "UNFIX" _ = handleCmd ["UNFIX"]
- h "GREETING" msg | not $ B.null msg = handleCmd ["GREETING", msg]
- h "CALLVOTE" msg | B.null msg = handleCmd ["CALLVOTE"]
- | otherwise = let (c, p) = extractParameters msg in
- if B.null p then handleCmd ["CALLVOTE", c] else handleCmd ["CALLVOTE", c, p]
- h "VOTE" msg | not $ B.null msg = handleCmd ["VOTE", upperCase msg]
- h "FORCE" msg | not $ B.null msg = handleCmd ["VOTE", upperCase msg, "FORCE"]
- h "MAXTEAMS" n | not $ B.null n = handleCmd ["MAXTEAMS", n]
h "INFO" n | not $ B.null n = handleCmd ["INFO", n]
- h "RESTART_SERVER" "YES" = handleCmd ["RESTART_SERVER"]
+ h "HELP" _ = handleCmd ["HELP"]
h "REGISTERED_ONLY" _ = serverAdminOnly $ do
+ rnc <- liftM snd ask
+ let chans = map (sendChan . client rnc) $ allClients rnc
+ return
+ [ModifyServerInfo(\s -> s{isRegisteredUsersOnly = not $ isRegisteredUsersOnly s})
+ , ShowRegisteredOnlyState chans
+ ]
+ h "SUPER_POWER" _ = serverAdminOnly $ do
cl <- thisClient
return
- [ModifyServerInfo(\s -> s{isRegisteredUsersOnly = not $ isRegisteredUsersOnly s})
- , AnswerClients [sendChan cl] ["CHAT", "[server]", "'Registered only' state toggled"]
+ [ModifyClient (\c -> c{hasSuperPower = True})
+ , AnswerClients [sendChan cl] ["CHAT", nickServer, loc "Super power activated."]
]
- h "SUPER_POWER" _ = serverAdminOnly $ return [ModifyClient (\c -> c{hasSuperPower = True})]
- h c p = return [Warning $ B.concat ["Unknown cmd: /", c, " ", p]]
+ h _ _ = return [Warning unknownCmdWarningText]
+
extractParameters p = let (a, b) = B.break (== ' ') p in (upperCase a, B.dropWhile (== ' ') b)
@@ -113,14 +148,14 @@
let clRoom = room rnc roomId
let roomMasterSign = if isMaster cl then "+" else ""
let adminSign = if isAdministrator cl then "@" else ""
- let rInfo = if roomId /= lobbyId then B.concat [adminSign, roomMasterSign, "room ", name clRoom] else adminSign `B.append` "lobby"
+ let rInfo = if roomId /= lobbyId then B.concat [adminSign, roomMasterSign, loc "room", " ", name clRoom] else adminSign `B.append` (loc "lobby")
let roomStatus = if isJust $ gameInfo clRoom then
- if teamsInGame cl > 0 then "(playing)" else "(spectating)"
+ if teamsInGame cl > 0 then (loc "(playing)") else (loc "(spectating)")
else
""
let hostStr = if isAdminAsking then host cl else B.empty
if noSuchClient then
- return []
+ answerClient [ "CHAT", nickServer, loc "Player is not online." ]
else
answerClient [
"INFO",
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/HWProtoInRoomState.hs
--- a/gameServer/HWProtoInRoomState.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/HWProtoInRoomState.hs Wed Jul 31 23:14:27 2019 +0200
@@ -27,11 +27,13 @@
import Control.Monad.Reader
--------------------------------------
import CoreTypes
+import Consts
import Utils
import HandlerUtils
import RoomsAndClients
import EngineInteraction
import Votes
+import CommandHelp
startGame :: Reader (ClientIndex, IRnC) [Action]
startGame = do
@@ -72,8 +74,9 @@
s <- roomOthersChans
return [AnswerClients s ["CHAT", n, msg]]
-handleCmd_inRoom ["PART"] = return [MoveToLobby "part"]
-handleCmd_inRoom ["PART", msg] = return [MoveToLobby $ "part: " `B.append` msg]
+-- Leave room normally
+handleCmd_inRoom ["PART"] = return [MoveToLobby ""]
+handleCmd_inRoom ["PART", _] = return [MoveToLobby ""]
handleCmd_inRoom ("CFG" : paramName : paramStrs)
@@ -144,7 +147,7 @@
AnswerClients roomChans ["HH_NUM", tName, showB $ hhnum newTeam]
]
where
- canAddNumber rt = (48::Int) - (sum $ map hhnum rt)
+ canAddNumber rt = (cMaxHHs) - (sum $ map hhnum rt)
findTeam = find (\t -> tName == teamname t) . teams
dif = readInt_ difStr
hhsList [] = []
@@ -153,7 +156,7 @@
newTeamHHNum rt p = min p (canAddNumber rt)
maxTeams r
| roomProto r < 38 = 6
- | otherwise = 8
+ | otherwise = cMaxTeams
handleCmd_inRoom ["REMOVE_TEAM", tName] = do
@@ -196,7 +199,7 @@
[ProtocolError $ loc "You're not the room master!"]
else if isNothing maybeTeam then
[]
- else if hhNumber < 1 || hhNumber > 8 || hhNumber > canAddNumber r + hhnum team then
+ else if hhNumber < 1 || hhNumber > cHogsPerTeam || hhNumber > canAddNumber r + hhnum team then
[AnswerClients clChan ["HH_NUM", teamName, showB $ hhnum team]]
else
[ModifyRoom $ modifyTeam team{hhnum = hhNumber},
@@ -204,7 +207,7 @@
where
hhNumber = readInt_ numberStr
findTeam = find (\t -> teamName == teamname t) . teams
- canAddNumber = (-) 48 . sum . map hhnum . teams
+ canAddNumber = (-) cMaxHHs . sum . map hhnum . teams
@@ -323,28 +326,35 @@
[Warning $ loc "A room with the same name already exists."]
else
[ModifyRoom roomUpdate,
- AnswerClients chans ("ROOM" : "UPD" : name rm : roomInfo (clientProto cl) (nick cl) (roomUpdate rm))]
+ AnswerClients chans ("ROOM" : "UPD" : name rm : roomInfo (clientProto cl) (nick cl) (roomUpdate rm)),
+ RegisterEvent RoomNameUpdate]
where
roomUpdate r = r{name = newName}
handleCmd_inRoom ["KICK", kickNick] = roomAdminOnly $ do
(thisClientId, rnc) <- ask
- maybeClientId <- clientByNick kickNick
+ maybeKickId <- clientByNick kickNick
rm <- thisRoom
- let kickId = fromJust maybeClientId
+ let kickId = fromJust maybeKickId
let kickCl = rnc `client` kickId
let sameRoom = clientRoom rnc thisClientId == clientRoom rnc kickId
let notOnly2Players = (length . group . sort . map teamowner . teams $ rm) > 2
- return
- [KickRoomClient kickId |
- isJust maybeClientId
- && (kickId /= thisClientId)
- && sameRoom
- && (not $ hasSuperPower kickCl)
- && ((isNothing $ gameInfo rm) || notOnly2Players || teamsInGame kickCl == 0)
- ]
-
+ return $
+ -- Catch some error conditions
+ if (isNothing maybeKickId) then
+ [Warning $ loc "Player is not online."]
+ else if (kickId == thisClientId) then
+ [Warning $ loc "You can't kick yourself!"]
+ else if (not ((isNothing $ gameInfo rm) || notOnly2Players || teamsInGame kickCl == 0)) then
+ [Warning $ loc "You can't kick the only other player!"]
+ else if (not sameRoom) then
+ [Warning $ loc "The player is not in your room."]
+ else if (hasSuperPower kickCl) then
+ [Warning $ loc "This player is protected from being kicked."]
+ else
+ -- Kick!
+ [KickRoomClient kickId]
handleCmd_inRoom ["DELEGATE", newAdmin] = do
(thisClientId, rnc) <- ask
@@ -354,20 +364,26 @@
thisRoomMasterId <- liftM masterID thisRoom
let newAdminId = fromJust maybeClientId
let sameRoom = clientRoom rnc thisClientId == clientRoom rnc newAdminId
- return
- [ChangeMaster (Just newAdminId) |
- (master || serverAdmin)
- && isJust maybeClientId
- && (Just newAdminId /= thisRoomMasterId)
- && sameRoom]
-
+ return $
+ if (not (master || serverAdmin)) then
+ [Warning $ loc "You're not the room master or a server admin!"]
+ else if (isNothing maybeClientId) then
+ [Warning $ loc "Player is not online."]
+ else if (Just newAdminId == thisRoomMasterId) then
+ [Warning $ loc "You're already the room master."]
+ else if (not sameRoom) then
+ [Warning $ loc "The player is not in your room."]
+ else
+ [ChangeMaster (Just newAdminId)]
handleCmd_inRoom ["TEAMCHAT", msg] = do
cl <- thisClient
chans <- roomSameClanChans
return [AnswerClients chans ["EM", engineMsg cl]]
where
- engineMsg cl = toEngineMsg $ B.concat ["b", nick cl, " (team): ", msg, "\x20\x20"]
+ -- This is formatted in a way so it can parsed by engine to make it translatable
+ -- Format: b]
+ engineMsg cl = toEngineMsg $ B.concat ["b", nick cl, "]", msg, "\x20\x20"]
handleCmd_inRoom ["BAN", banNick] = do
@@ -390,35 +406,66 @@
return [AnswerClients s ["CHAT", n, B.unwords $ "/rnd" : rs], Random s rs]
-handleCmd_inRoom ["MAXTEAMS", n] = roomAdminOnly $ do
+handleCmd_inRoom ["MAXTEAMS", n] = do
cl <- thisClient
let m = readInt_ n
- if m < 2 || m > 8 then
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/maxteams: specify number from 2 to 8"]]
+ if not $ isMaster cl then
+ return [Warning $ loc "You're not the room master!"]
+ else if m < 2 || m > cMaxTeams then
+ return [Warning $ loc "/maxteams: specify number from 2 to 8"]
else
return [ModifyRoom (\r -> r{teamsNumberLimit = m})]
+handleCmd_inRoom ["MAXTEAMS"] = handleCmd_inRoom ["MAXTEAMS", ""]
+
handleCmd_inRoom ["FIX"] = serverAdminOnly $
return [ModifyRoom (\r -> r{isSpecial = True})]
-handleCmd_inRoom ["UNFIX"] = serverAdminOnly $
- return [ModifyRoom (\r -> r{isSpecial = False})]
+handleCmd_inRoom ["UNFIX"] = serverAdminOnly $ do
+ cl <- thisClient
+ return $ if not $ isMaster cl then
+ [Warning $ loc "You're not the room master!"]
+ else
+ [ModifyRoom (\r -> r{isSpecial = False})]
+
+handleCmd_inRoom ["HELP"] = do
+ cl <- thisClient
+ if isAdministrator cl then
+ return (cmdHelpActionList [sendChan cl] cmdHelpRoomAdmin)
+ else
+ return (cmdHelpActionList [sendChan cl] cmdHelpRoomPlayer)
handleCmd_inRoom ["GREETING", msg] = do
cl <- thisClient
rm <- thisRoom
- return [ModifyRoom (\r -> r{greeting = msg}) | isAdministrator cl || (isMaster cl && (not $ isSpecial rm))]
-
+ return $ if (not (isAdministrator cl || (isMaster cl && (not $ isSpecial rm)))) then
+ [Warning $ loc "You're not the room master or a server admin!"]
+ else
+ [ModifyRoom (\r -> r{greeting = msg}),
+ AnswerClients [sendChan cl]
+ ["CHAT", nickServer,
+ if B.null msg then
+ loc "Greeting message cleared."
+ else
+ loc "Greeting message set."
+ ]]
handleCmd_inRoom ["CALLVOTE"] = do
cl <- thisClient
return [AnswerClients [sendChan cl]
- ["CHAT", "[server]", loc "Available callvote commands: kick , map , pause, newseed, hedgehogs"]
+ ["CHAT", nickServer, loc "Available callvote commands: hedgehogs , pause, newseed, map , kick "]
]
handleCmd_inRoom ["CALLVOTE", "KICK"] = do
cl <- thisClient
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/callvote kick: You need to specify a nickname."]]
+ rm <- thisRoom
+ return
+ [Warning $
+ if isJust $ masterID rm then
+ loc "/callvote kick: This is only allowed in rooms without a room master."
+ else
+ loc "/callvote kick: You need to specify a nickname."
+ ]
handleCmd_inRoom ["CALLVOTE", "KICK", nickname] = do
(thisClientId, rnc) <- ask
@@ -429,18 +476,25 @@
let sameRoom = clientRoom rnc thisClientId == clientRoom rnc kickId
if isJust $ masterID rm then
- return []
+ return [Warning $ loc "/callvote kick: This is only allowed in rooms without a room master."]
else
if isJust maybeClientId && sameRoom then
startVote $ VoteKick nickname
else
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/callvote kick: No such user!"]]
+ return [Warning $ loc "/callvote kick: No such user!"]
handleCmd_inRoom ["CALLVOTE", "MAP"] = do
+ -- Display list of available maps for voting
cl <- thisClient
s <- liftM (Map.keys . roomSaves) thisRoom
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", B.concat ["callvote map: ", B.intercalate ", " s]]]
+ return [AnswerClients [sendChan cl]
+ ["CHAT", nickServer,
+ if (not $ null s) then
+ (B.concat ["/callvote map: ", B.intercalate ", " s])
+ else
+ loc "/callvote map: No maps available."
+ ]]
handleCmd_inRoom ["CALLVOTE", "MAP", roomSave] = do
@@ -450,7 +504,7 @@
if Map.member roomSave $ roomSaves rm then
startVote $ VoteMap roomSave
else
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/callvote map: No such map!"]]
+ return [Warning $ loc "/callvote map: No such map!"]
handleCmd_inRoom ["CALLVOTE", "PAUSE"] = do
@@ -460,35 +514,44 @@
if isJust $ gameInfo rm then
startVote VotePause
else
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/callvote pause: No game in progress!"]]
+ return [Warning $ loc "/callvote pause: No game in progress!"]
+handleCmd_inRoom ["CALLVOTE", "PAUSE", _] = handleCmd_inRoom ["CALLVOTE", "PAUSE"]
handleCmd_inRoom ["CALLVOTE", "NEWSEED"] = do
startVote VoteNewSeed
+handleCmd_inRoom ["CALLVOTE", "NEWSEED", _] = handleCmd_inRoom ["CALLVOTE", "NEWSEED"]
handleCmd_inRoom ["CALLVOTE", "HEDGEHOGS"] = do
cl <- thisClient
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/callvote hedgehogs: Specify number from 1 to 8."]]
+ return [Warning $ loc "/callvote hedgehogs: Specify number from 1 to 8."]
handleCmd_inRoom ["CALLVOTE", "HEDGEHOGS", hhs] = do
cl <- thisClient
let h = readInt_ hhs
- if h > 0 && h <= 8 then
+ if h > 0 && h <= cHogsPerTeam then
startVote $ VoteHedgehogsPerTeam h
else
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/callvote hedgehogs: Specify number from 1 to 8."]]
+ return [Warning $ loc "/callvote hedgehogs: Specify number from 1 to 8."]
+handleCmd_inRoom ["CALLVOTE", _] = handleCmd_inRoom ["CALLVOTE"]
+handleCmd_inRoom ["CALLVOTE", _, _] = handleCmd_inRoom ["CALLVOTE"]
handleCmd_inRoom ("VOTE" : m : p) = do
cl <- thisClient
let b = if m == "YES" then Just True else if m == "NO" then Just False else Nothing
if isJust b then
voted (p == ["FORCE"]) (fromJust b)
- else
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", "/vote: Please use 'yes' or 'no'."]]
+ else
+ return [Warning $
+ if (p == ["FORCE"]) then
+ loc "/force: Please use 'yes' or 'no'."
+ else
+ loc "/vote: Please use 'yes' or 'no'."
+ ]
handleCmd_inRoom ["SAVE", stateName, location] = serverAdminOnly $ do
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/HWProtoLobbyState.hs
--- a/gameServer/HWProtoLobbyState.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/HWProtoLobbyState.hs Wed Jul 31 23:14:27 2019 +0200
@@ -26,9 +26,11 @@
--------------------------------------
import CoreTypes
import Utils
+import Consts
import HandlerUtils
import RoomsAndClients
import EngineInteraction
+import CommandHelp
handleCmd_lobby :: CmdHandler
@@ -116,7 +118,7 @@
++ answerFullConfig cl jRoom
++ answerTeams cl jRoom
++ watchRound cl jRoom chans
- ++ [AnswerClients [sendChan cl] ["CHAT", "[greeting]", greeting jRoom] | greeting jRoom /= ""]
+ ++ [AnswerClients [sendChan cl] ["CHAT", nickGreeting, greeting jRoom] | greeting jRoom /= ""]
++ map (\t -> AnswerClients chans ["EM", toEngineMsg $ 'G' `B.cons` t]) clTeamsNames
++ [AnswerClients [sendChan cl] ["EM", toEngineMsg "I"] | isPaused `fmap` gameInfo jRoom == Just True]
@@ -166,6 +168,13 @@
c <- liftM sendChan thisClient
return [Random [c] rs]
+handleCmd_lobby ["HELP"] = do
+ cl <- thisClient
+ if isAdministrator cl then
+ return (cmdHelpActionList [sendChan cl] cmdHelpLobbyAdmin)
+ else
+ return (cmdHelpActionList [sendChan cl] cmdHelpLobbyPlayer)
+
---------------------------
-- Administrator's stuff --
@@ -211,10 +220,18 @@
handleCmd_lobby ["CLEAR_ACCOUNTS_CACHE"] = serverAdminOnly $
return [ClearAccountsCache]
+handleCmd_lobby ["RESTART_SERVER", "YES"] = serverAdminOnly $
+ return [RestartServer]
+
handleCmd_lobby ["RESTART_SERVER"] = serverAdminOnly $
- return [RestartServer]
+ return [Warning $ loc "Please confirm server restart with '/restart_server yes'."]
+
+handleCmd_lobby ["RESTART_SERVER", _] = handleCmd_lobby ["RESTART_SERVER"]
+
handleCmd_lobby ["STATS"] = serverAdminOnly $
return [Stats]
-handleCmd_lobby _ = return [ProtocolError "Incorrect command (state: in lobby)"]
+handleCmd_lobby (s:_) = return [ProtocolError $ "Incorrect command '" `B.append` s `B.append` "' (state: in lobby)"]
+
+handleCmd_lobby [] = return [ProtocolError "Empty command (state: in lobby)"]
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/HWProtoNEState.hs
--- a/gameServer/HWProtoNEState.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/HWProtoNEState.hs Wed Jul 31 23:14:27 2019 +0200
@@ -65,6 +65,7 @@
if clientProto cl < 48 && passwd == webPassword cl then
return $ JoinLobby : [AnswerClients [sendChan cl] ["ADMIN_ACCESS"] | isAdministrator cl]
else
+ -- String is parsed by frontend, do not localize!
return [ByeClient "Authentication failed"]
@@ -81,6 +82,7 @@
, JoinLobby
]
else
+ -- String is parsed by frontend, do not localize!
return [ByeClient "Authentication failed"]
where
h = B.pack . showDigest . sha1 . BL.fromChunks
@@ -99,4 +101,6 @@
parsedProto = readInt_ protoNum
#endif
-handleCmd_NotEntered _ = return [ProtocolError "Incorrect command (state: not entered)"]
+handleCmd_NotEntered (s:_) = return [ProtocolError $ "Incorrect command '" `B.append` s `B.append` "' (state: not entered)"]
+
+handleCmd_NotEntered [] = return [ProtocolError "Empty command (state: not entered)"]
\ No newline at end of file
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/OfficialServer/checker.hs
--- a/gameServer/OfficialServer/checker.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/OfficialServer/checker.hs Wed Jul 31 23:14:27 2019 +0200
@@ -55,7 +55,7 @@
deriving Show
serverAddress = "netserver.hedgewars.org"
-protocolNumber = "53"
+protocolNumber = "55"
getLines :: Handle -> IO [B.ByteString]
getLines h = g
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/Utils.hs
--- a/gameServer/Utils.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/Utils.hs Wed Jul 31 23:14:27 2019 +0200
@@ -16,7 +16,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE OverloadedStrings,CPP #-}
module Utils where
import Data.Char
@@ -32,6 +32,10 @@
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.UTF8 as UTF8
import Data.Maybe
+#if defined(OFFICIAL_SERVER)
+import qualified Data.Aeson.Types as Aeson
+import qualified Data.Text as Text
+#endif
-------------------------------------------------
import CoreTypes
@@ -77,7 +81,7 @@
illegalName b = B.null b || length s > 40 || all isSpace s || isSpace (head s) || isSpace (last s) || any isIllegalChar s
where
s = UTF8.toString b
- isIllegalChar c = c `List.elem` ("$()*+?[]^{|}\x7F" ++ ['\x00'..'\x1F'])
+ isIllegalChar c = c `List.elem` ("$()*+?[]^{|}\x7F" ++ ['\x00'..'\x1F'] ++ ['\xFFF0'..'\xFFFF'])
protoNumber2ver :: Word16 -> B.ByteString
protoNumber2ver v = Map.findWithDefault "Unknown" v vermap
@@ -121,6 +125,8 @@
, (54, "0.9.24-dev")
, (55, "0.9.24")
, (56, "0.9.25-dev")
+ , (57, "0.9.25")
+ , (58, "1.0.0-dev")
]
askFromConsole :: B.ByteString -> IO B.ByteString
@@ -231,6 +237,9 @@
AnswerClients [clChan] ["HH_NUM", teamname team, showB $ hhnum team]]
+-- Locale function to localize strings.
+-- loc is just the identity functions, but it will be collected by scripts
+-- for localization. Use loc to mark a string for translation.
loc :: B.ByteString -> B.ByteString
loc = id
@@ -253,3 +262,17 @@
isRegistered :: ClientInfo -> Bool
isRegistered = (<) 0 . B.length . webPassword
+
+#if defined(OFFICIAL_SERVER)
+instance Aeson.ToJSON B.ByteString where
+ toJSON = Aeson.toJSON . B.unpack
+
+instance Aeson.FromJSON B.ByteString where
+ parseJSON = Aeson.withText "ByteString" $ pure . B.pack . Text.unpack
+
+instance Aeson.ToJSONKey B.ByteString where
+ toJSONKey = Aeson.toJSONKeyText (Text.pack . B.unpack)
+
+instance Aeson.FromJSONKey B.ByteString where
+ fromJSONKey = Aeson.FromJSONKeyTextParser (return . B.pack . Text.unpack)
+#endif
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/Votes.hs
--- a/gameServer/Votes.hs Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/Votes.hs Wed Jul 31 23:14:27 2019 +0200
@@ -28,6 +28,7 @@
import Data.Maybe
import Control.Applicative
-------------------
+import Consts
import Utils
import CoreTypes
import HandlerUtils
@@ -42,16 +43,16 @@
case voting rm of
Nothing ->
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "There's no voting going on."]]
+ return [Warning $ loc "There's no voting going on."]
Just voting ->
if (not forced) && (uid `L.notElem` entitledToVote voting) then
return []
else if (not forced) && (uid `L.elem` map fst (votes voting)) then
- return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "You already have voted."]]
+ return [Warning $ loc "You already have voted."]
else if forced && (not $ isAdministrator cl) then
return []
else
- ((:) (AnswerClients [sendChan cl] ["CHAT", "[server]", loc "Your vote has been counted."]))
+ ((:) (AnswerClients [sendChan cl] ["CHAT", nickServer, loc "Your vote has been counted."]))
<$> (actOnVoting $ voting{votes = (uid, vote):votes voting})
where
@@ -73,7 +74,7 @@
closeVoting = do
chans <- roomClientsChans
return [
- AnswerClients chans ["CHAT", "[server]", loc "Voting closed."]
+ AnswerClients chans ["CHAT", nickServer, loc "Voting closed."]
, ModifyRoom (\r -> r{voting = Nothing})
]
@@ -100,7 +101,7 @@
chans <- roomClientsChans
return $
[ModifyRoom $ \r -> r{params = p, mapParams = mp}
- , AnswerClients chans ["CHAT", "[server]", location]
+ , AnswerClients chans ["CHAT", nickServer, location]
, SendUpdateOnThisRoom
, LoadGhost location]
act (VotePause) = do
@@ -108,7 +109,7 @@
chans <- roomClientsChans
let modifyGameInfo f room = room{gameInfo = fmap f $ gameInfo room}
return [ModifyRoom (modifyGameInfo $ \g -> g{isPaused = not $ isPaused g}),
- AnswerClients chans ["CHAT", "[server]", loc "Pause toggled."],
+ AnswerClients chans ["CHAT", nickServer, loc "Pause toggled."],
AnswerClients chans ["EM", toEngineMsg "I"]]
act (VoteNewSeed) =
return [SetRandomSeed]
@@ -118,7 +119,7 @@
let answers = concatMap (\t ->
[ModifyRoom $ modifyTeam t{hhnum = h}
, AnswerClients chans ["HH_NUM", teamname t, showB h]]
- ) $ if length curteams * h > 48 then [] else curteams
+ ) $ if length curteams * h > cMaxHHs then [] else curteams
;
curteams =
if isJust $ gameInfo rm then
@@ -143,7 +144,7 @@
else
return [
ModifyRoom (\r -> r{voting = Just (newVoting vt){entitledToVote = uids}})
- , AnswerClients chans ["CHAT", "[server]", B.concat [loc "New voting started", ": ", voteInfo vt]]
+ , AnswerClients chans ["CHAT", nickServer, B.concat [loc "New voting started", ": ", voteInfo vt]]
, ReactCmd ["VOTE", "YES"]
]
@@ -162,7 +163,7 @@
modifyRoom rnc (\r -> r{voting = if voteTTL rv == 0 then Nothing else Just rv{voteTTL = voteTTL rv - 1}}) ri
if voteTTL rv == 0 then do
chans <- liftM (map sendChan) $ roomClientsM rnc ri
- return [AnswerClients chans ["CHAT", "[server]", loc "Voting expired."]]
+ return [AnswerClients chans ["CHAT", nickServer, loc "Voting expired."]]
else
return []
Nothing -> return []
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer/hedgewars-server.cabal
--- a/gameServer/hedgewars-server.cabal Wed May 16 18:22:28 2018 +0200
+++ b/gameServer/hedgewars-server.cabal Wed Jul 31 23:14:27 2019 +0200
@@ -2,7 +2,7 @@
Version: 0.1
Synopsis: hedgewars server
Description: hedgewars server
-Homepage: http://www.hedgewars.org/
+Homepage: https://www.hedgewars.org/
License: GPL-2
Author: unC0Rr
Maintainer: a.korotaev@hedgewars.org
@@ -16,13 +16,13 @@
default-language: Haskell2010
--- Don't forget to update INSTALL.md when you change these dependencies!
+-- Don't forget to update INSTALL.md and .travis.yml when you change these dependencies!
Build-depends:
base >= 4.8,
containers,
vector,
bytestring,
- network >= 2.3,
+ network >= 2.3 && < 3.0,
random,
time,
mtl >= 2,
@@ -33,11 +33,42 @@
utf8-string,
SHA,
entropy,
- zlib >= 0.5.3 && < 0.6,
+ zlib >= 0.5.3 && < 0.7,
regex-tdfa,
- binary >= 0.8.5.1
+ binary >= 0.8.5.1,
+
+-- These dependencies are for OFFICIAL_SERVER only and do not need to be mentioned in docs
+ yaml >= 0.8.30,
+ aeson,
+ text >= 1.2
if !os(windows)
build-depends: unix
ghc-options: -O2
+
+Executable checker
+ main-is: OfficialServer/checker.hs
+
+ default-language: Haskell2010
+
+ Build-depends:
+ base >= 4.8,
+ containers,
+ vector,
+ bytestring,
+ network >= 2.3 && < 3.0,
+ mtl >= 2,
+ sandi,
+ hslogger,
+ process,
+ ConfigFile,
+ directory
+
+ if !os(windows)
+ build-depends: unix
+
+ ghc-options: -O2
+
+
+
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/Cargo.toml
--- a/gameServer2/Cargo.toml Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-[package]
-name = "hedgewars-server"
-version = "0.0.1"
-authors = [ "Andrey Korotaev " ]
-
-[dependencies]
-rand = "0.3"
-mio = "0.6"
-slab = "0.4"
-netbuf = "0.4.0"
-nom = "3.2"
-env_logger = "0.4"
-log = "0.3.8"
-proptest = "0.5.1"
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/main.rs
--- a/gameServer2/src/main.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-#![allow(unused_imports)]
-
-extern crate rand;
-extern crate mio;
-extern crate slab;
-extern crate netbuf;
-#[macro_use]
-extern crate nom;
-#[macro_use]
-extern crate log;
-extern crate env_logger;
-#[macro_use] extern crate proptest;
-
-//use std::io::*;
-//use rand::Rng;
-//use std::cmp::Ordering;
-use mio::net::*;
-use mio::*;
-
-mod utils;
-mod server;
-mod protocol;
-
-use server::network::NetworkLayer;
-
-fn main() {
- env_logger::init().unwrap();
-
- info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION);
-
- let address = "0.0.0.0:46631".parse().unwrap();
- let listener = TcpListener::bind(&address).unwrap();
-
- let poll = Poll::new().unwrap();
- let mut hw_network = NetworkLayer::new(listener, 1024, 512);
- hw_network.register_server(&poll).unwrap();
-
- let mut events = Events::with_capacity(1024);
-
- loop {
- poll.poll(&mut events, None).unwrap();
-
- for event in events.iter() {
- if event.readiness() & Ready::readable() == Ready::readable() {
- match event.token() {
- utils::SERVER => hw_network.accept_client(&poll).unwrap(),
- Token(tok) => hw_network.client_readable(&poll, tok).unwrap(),
- }
- }
- if event.readiness() & Ready::writable() == Ready::writable() {
- match event.token() {
- utils::SERVER => unreachable!(),
- Token(tok) => hw_network.client_writable(&poll, tok).unwrap(),
- }
- }
-// if event.kind().is_hup() || event.kind().is_error() {
-// match event.token() {
-// utils::SERVER => unreachable!(),
-// Token(tok) => server.client_error(&poll, tok).unwrap(),
-// }
-// }
- }
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/protocol/messages.rs
--- a/gameServer2/src/protocol/messages.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-use server::coretypes::{ServerVar, GameCfg, TeamInfo, HedgehogInfo};
-use std;
-use std::ops;
-use std::convert::From;
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum HWProtocolMessage {
- // core
- Ping,
- Pong,
- Quit(Option),
- //Cmd(String, Vec),
- Global(String),
- Watch(String),
- ToggleServerRegisteredOnly,
- SuperPower,
- Info(String),
- // not entered state
- Nick(String),
- Proto(u32),
- Password(String, String),
- Checker(u32, String, String),
- // lobby
- List,
- Chat(String),
- CreateRoom(String, Option),
- Join(String, Option),
- Follow(String),
- Rnd(Vec),
- Kick(String),
- Ban(String, String, u32),
- BanIP(String, String, u32),
- BanNick(String, String, u32),
- BanList,
- Unban(String),
- SetServerVar(ServerVar),
- GetServerVar,
- RestartServer,
- Stats,
- // in room
- Part(Option),
- Cfg(GameCfg),
- AddTeam(TeamInfo),
- RemoveTeam(String),
- SetHedgehogsNumber(String, u8),
- SetTeamColor(String, u8),
- ToggleReady,
- StartGame,
- EngineMessage(String),
- RoundFinished,
- ToggleRestrictJoin,
- ToggleRestrictTeams,
- ToggleRegisteredOnly,
- RoomName(String),
- Delegate(String),
- TeamChat(String),
- MaxTeams(u8),
- Fix,
- Unfix,
- Greeting(String),
- CallVote(Option<(String, Option)>),
- Vote(String),
- ForceVote(String),
- Save(String, String),
- Delete(String),
- SaveRoom(String),
- LoadRoom(String),
- Malformed,
- Empty,
-}
-
-pub enum HWServerMessage {
- Ping,
- Pong,
- Bye(String),
- Nick(String),
- LobbyLeft(String),
- LobbyJoined(Vec),
- ChatMsg(String, String),
- ClientFlags(String, Vec),
-
- Warning(String),
- Connected(u32),
- Unreachable,
-}
-
-fn construct_message(msg: & [&str]) -> String {
- let mut m = String::with_capacity(64);
-
- for s in msg {
- m.push_str(s);
- m.push('\n');
- }
- m.push('\n');
-
- m
-}
-
-impl<'a> HWProtocolMessage {
- pub fn to_raw_protocol(&self) -> String {
- use self::HWProtocolMessage::*;
- match *self {
- Ping => "PING\n\n".to_string(),
- Pong => "PONG\n\n".to_string(),
- Quit(None) => format!("QUIT\n\n"),
- Quit(Some(ref msg)) => format!("QUIT\n{}\n\n", msg),
- Global(ref msg) => format!("CMD\nGLOBAL\n{}\n\n", msg),
- Watch(ref name) => format!("CMD\nWATCH\n{}\n\n", name),
- ToggleServerRegisteredOnly => "CMD\nREGISTERED_ONLY\n\n".to_string(),
- SuperPower => "CMD\nSUPER_POWER\n\n".to_string(),
- Info(ref info) => format!("CMD\nINFO\n{}\n\n", info),
- Nick(ref nick) => format!("NICK\n{}\n\n", nick),
- Proto(version) => format!("PROTO\n{}\n\n", version),
- Password(ref p, ref s) => format!("PASSWORD\n{}\n{}\n\n", p, s), //?
- Checker(i, ref n, ref p) =>
- format!("CHECKER\n{}\n{}\n{}\n\n", i, n, p), //?,
- List => "LIST\n\n".to_string(),
- Chat(ref msg) => format!("CHAT\n{}\n\n", msg),
- CreateRoom(ref name, None) =>
- format!("CREATE_ROOM\n{}\n\n", name),
- CreateRoom(ref name, Some(ref password)) =>
- format!("CREATE_ROOM\n{}\n{}\n\n", name, password),
- Join(ref name, None) =>
- format!("JOIN\n{}\n\n", name),
- Join(ref name, Some(ref arg)) =>
- format!("JOIN\n{}\n{}\n\n", name, arg),
- Follow(ref name) =>
- format!("FOLLOW\n{}\n\n", name),
- //Rnd(Vec), ???
- Kick(ref name) => format!("KICK\n{}\n\n", name),
- Ban(ref name, ref reason, time) =>
- format!("BAN\n{}\n{}\n{}\n\n", name, reason, time),
- BanIP(ref ip, ref reason, time) =>
- format!("BAN_IP\n{}\n{}\n{}\n\n", ip, reason, time),
- BanNick(ref nick, ref reason, time) =>
- format!("BAN_NICK\n{}\n{}\n{}\n\n", nick, reason, time),
- BanList => "BANLIST\n\n".to_string(),
- Unban(ref name) => format!("UNBAN\n{}\n\n", name),
- //SetServerVar(ServerVar), ???
- GetServerVar => "GET_SERVER_VAR\n\n".to_string(),
- RestartServer => "CMD\nRESTART_SERVER\nYES\n\n".to_string(),
- Stats => "CMD\nSTATS\n\n".to_string(),
- Part(None) => "CMD\nPART\n\n".to_string(),
- Part(Some(ref msg)) => format!("CMD\nPART\n{}\n\n", msg),
- //Cfg(GameCfg) ??
- //AddTeam(TeamInfo) ??,
- RemoveTeam(ref name) => format!("REMOVE_TEAM\n{}\n\n", name),
- //SetHedgehogsNumber(String, u8), ??
- //SetTeamColor(String, u8), ??
- ToggleReady => "TOGGLE_READY\n\n".to_string(),
- StartGame => "START_GAME\n\n".to_string(),
- EngineMessage(ref msg) => format!("EM\n{}\n\n", msg),
- RoundFinished => "ROUNDFINISHED\n\n".to_string(),
- ToggleRestrictJoin => "TOGGLE_RESTRICT_JOINS\n\n".to_string(),
- ToggleRestrictTeams => "TOGGLE_RESTRICT_TEAMS\n\n".to_string(),
- ToggleRegisteredOnly => "TOGGLE_REGISTERED_ONLY\n\n".to_string(),
- RoomName(ref name) => format!("ROOM_NAME\n{}\n\n", name),
- Delegate(ref name) => format!("CMD\nDELEGATE\n{}\n\n", name),
- TeamChat(ref msg) => format!("TEAMCHAT\n{}\n\n", msg),
- MaxTeams(count) => format!("CMD\nMAXTEAMS\n{}\n\n", count) ,
- Fix => "CMD\nFIX\n\n".to_string(),
- Unfix => "CMD\nUNFIX\n\n".to_string(),
- Greeting(ref msg) => format!("CMD\nGREETING\n{}\n\n", msg),
- //CallVote(Option<(String, Option)>) =>, ??
- Vote(ref msg) => format!("CMD\nVOTE\n{}\n\n", msg),
- ForceVote(ref msg) => format!("CMD\nFORCE\n{}\n\n", msg),
- //Save(String, String), ??
- Delete(ref room) => format!("CMD\nDELETE\n{}\n\n", room),
- SaveRoom(ref room) => format!("CMD\nSAVEROOM\n{}\n\n", room),
- LoadRoom(ref room) => format!("CMD\nLOADROOM\n{}\n\n", room),
- Malformed => "A\nQUICK\nBROWN\nHOG\nJUMPS\nOVER\nTHE\nLAZY\nDOG\n\n".to_string(),
- Empty => "\n\n".to_string(),
- _ => panic!("Protocol message not yet implemented")
- }
- }
-}
-
-impl HWServerMessage {
- pub fn to_raw_protocol(&self) -> String {
- use self::HWServerMessage::*;
- match self {
- &Ping => "PING\n\n".to_string(),
- &Pong => "PONG\n\n".to_string(),
- &Connected(protocol_version)
- => construct_message(&[
- "CONNECTED",
- "Hedgewars server http://www.hedgewars.org/",
- &protocol_version.to_string()
- ]),
- &Bye(ref msg) => construct_message(&["BYE", &msg]),
- &Nick(ref nick) => construct_message(&["NICK", &nick]),
- &LobbyLeft(ref nick)
- => construct_message(&["LOBBY_LEFT", &nick]),
- &LobbyJoined(ref nicks)
- => {
- let mut v = vec!["LOBBY:JOINED"];
- v.extend(nicks.iter().map(|n| { &n[..] }));
- construct_message(&v)
- },
- &ClientFlags(ref flags, ref nicks)
- => {
- let mut v = vec!["CLIENT_FLAGS"];
- v.push(&flags[..]);
- v.extend(nicks.iter().map(|n| { &n[..] }));
- construct_message(&v)
- },
- &ChatMsg(ref nick, ref msg)
- => construct_message(&["CHAT", &nick, &msg]),
- &Warning(ref msg)
- => construct_message(&["WARNING", &msg]),
- _ => construct_message(&["ERROR", "UNIMPLEMENTED"]),
- }
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/protocol/mod.rs
--- a/gameServer2/src/protocol/mod.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-use netbuf;
-use std::io::Read;
-use std::io::Result;
-use nom::IResult;
-
-pub mod messages;
-mod parser;
-
-pub struct ProtocolDecoder {
- buf: netbuf::Buf,
- consumed: usize,
-}
-
-impl ProtocolDecoder {
- pub fn new() -> ProtocolDecoder {
- ProtocolDecoder {
- buf: netbuf::Buf::new(),
- consumed: 0,
- }
- }
-
- pub fn read_from(&mut self, stream: &mut R) -> Result {
- self.buf.read_from(stream)
- }
-
- pub fn extract_messages(&mut self) -> Vec {
- let parse_result = parser::extract_messages(&self.buf[..]);
- match parse_result {
- IResult::Done(tail, msgs) => {
- self.consumed = self.buf.len() - self.consumed - tail.len();
- msgs
- },
- IResult::Incomplete(_) => unreachable!(),
- IResult::Error(_) => unreachable!(),
- }
- }
-
- pub fn sweep(&mut self) {
- self.buf.consume(self.consumed);
- self.consumed = 0;
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/protocol/parser.rs
--- a/gameServer2/src/protocol/parser.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,282 +0,0 @@
-use nom::*;
-
-use std::str;
-use std::str::FromStr;
-use super::messages::HWProtocolMessage;
-use super::messages::HWProtocolMessage::*;
-
-use proptest::test_runner::{TestRunner, Reason};
-use proptest::arbitrary::{any, any_with, Arbitrary, StrategyFor};
-use proptest::strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree};
-use proptest::string::RegexGeneratorValueTree;
-use std::ops::Range;
-
-named!(end_of_message, tag!("\n\n"));
-named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8));
-named!( a_line<&[u8], String>, map!(str_line, String::from));
-named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str));
-named!(u32_line<&[u8], u32>, map_res!(str_line, FromStr::from_str));
-named!(opt_param<&[u8], Option >, opt!(map!(flat_map!(preceded!(eol, str_line), non_empty), String::from)));
-
-named!(basic_message<&[u8], HWProtocolMessage>, alt!(
- do_parse!(tag!("PING") >> (Ping))
- | do_parse!(tag!("PONG") >> (Pong))
- | do_parse!(tag!("LIST") >> (List))
- | do_parse!(tag!("BANLIST") >> (BanList))
- | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
- | do_parse!(tag!("TOGGLE_READY") >> (ToggleReady))
- | do_parse!(tag!("START_GAME") >> (StartGame))
- | do_parse!(tag!("ROUNDFINISHED") >> (RoundFinished))
- | do_parse!(tag!("TOGGLE_RESTRICT_JOINS") >> (ToggleRestrictJoin))
- | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS") >> (ToggleRestrictTeams))
- | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
-));
-
-named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
- do_parse!(tag!("NICK") >> eol >> n: a_line >> (Nick(n)))
- | do_parse!(tag!("INFO") >> eol >> n: a_line >> (Info(n)))
- | do_parse!(tag!("CHAT") >> eol >> m: a_line >> (Chat(m)))
- | do_parse!(tag!("FOLLOW") >> eol >> n: a_line >> (Follow(n)))
- | do_parse!(tag!("KICK") >> eol >> n: a_line >> (Kick(n)))
- | do_parse!(tag!("UNBAN") >> eol >> n: a_line >> (Unban(n)))
- | do_parse!(tag!("EM") >> eol >> m: a_line >> (EngineMessage(m)))
- | do_parse!(tag!("TEAMCHAT") >> eol >> m: a_line >> (TeamChat(m)))
- | do_parse!(tag!("ROOM_NAME") >> eol >> n: a_line >> (RoomName(n)))
- | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
-
- | do_parse!(tag!("PROTO") >> eol >> d: u32_line >> (Proto(d)))
-
- | do_parse!(tag!("QUIT") >> msg: opt_param >> (Quit(msg)))
-));
-
-named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
- do_parse!(tag_no_case!("STATS") >> (Stats))
- | do_parse!(tag_no_case!("FIX") >> (Fix))
- | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
- | do_parse!(tag_no_case!("RESTART_SERVER") >> eol >> tag!("YES") >> (RestartServer))
- | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
- | do_parse!(tag_no_case!("SUPER_POWER") >> (SuperPower))
- | do_parse!(tag_no_case!("PART") >> m: opt_param >> (Part(m)))
- | do_parse!(tag_no_case!("QUIT") >> m: opt_param >> (Quit(m)))
- | do_parse!(tag_no_case!("DELEGATE") >> eol >> n: a_line >> (Delegate(n)))
- | do_parse!(tag_no_case!("SAVEROOM") >> eol >> r: a_line >> (SaveRoom(r)))
- | do_parse!(tag_no_case!("LOADROOM") >> eol >> r: a_line >> (LoadRoom(r)))
- | do_parse!(tag_no_case!("DELETE") >> eol >> r: a_line >> (Delete(r)))
- | do_parse!(tag_no_case!("GLOBAL") >> eol >> m: a_line >> (Global(m)))
- | do_parse!(tag_no_case!("WATCH") >> eol >> i: a_line >> (Watch(i)))
- | do_parse!(tag_no_case!("GREETING") >> eol >> m: a_line >> (Greeting(m)))
- | do_parse!(tag_no_case!("VOTE") >> eol >> m: a_line >> (Vote(m)))
- | do_parse!(tag_no_case!("FORCE") >> eol >> m: a_line >> (ForceVote(m)))
- | do_parse!(tag_no_case!("INFO") >> eol >> n: a_line >> (Info(n)))
- | do_parse!(tag_no_case!("MAXTEAMS") >> eol >> n: u8_line >> (MaxTeams(n)))
-)));
-
-named!(complex_message<&[u8], HWProtocolMessage>, alt!(
- do_parse!(tag!("PASSWORD") >> eol >>
- p: a_line >> eol >>
- s: a_line >>
- (Password(p, s)))
- | do_parse!(tag!("CHECKER") >> eol >>
- i: u32_line >> eol >>
- n: a_line >> eol >>
- p: a_line >>
- (Checker(i, n, p)))
- | do_parse!(tag!("CREATE_ROOM") >> eol >>
- n: a_line >>
- p: opt_param >>
- (CreateRoom(n, p)))
- | do_parse!(tag!("JOIN") >> eol >>
- n: a_line >>
- p: opt_param >>
- (Join(n, p)))
- | do_parse!(tag!("BAN") >> eol >>
- n: a_line >> eol >>
- r: a_line >> eol >>
- t: u32_line >>
- (Ban(n, r, t)))
- | do_parse!(tag!("BAN_IP") >> eol >>
- n: a_line >> eol >>
- r: a_line >> eol >>
- t: u32_line >>
- (BanIP(n, r, t)))
- | do_parse!(tag!("BAN_NICK") >> eol >>
- n: a_line >> eol >>
- r: a_line >> eol >>
- t: u32_line >>
- (BanNick(n, r, t)))
-));
-
-named!(malformed_message<&[u8], HWProtocolMessage>,
- do_parse!(separated_list!(eol, a_line) >> (Malformed)));
-
-named!(empty_message<&[u8], HWProtocolMessage>,
- do_parse!(alt!(end_of_message | eol) >> (Empty)));
-
-named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
- alt!(
- basic_message
- | one_param_message
- | cmd_message
- | complex_message
- ), end_of_message
- )
- | terminated!(malformed_message, end_of_message)
- | empty_message
- )
-);
-
-named!(pub extract_messages<&[u8], Vec >, many0!(complete!(message)));
-
-// Due to inability to define From between Options
-trait Into2: Sized { fn into2(self) -> T; }
-impl Into2 for T { fn into2(self) -> T { self } }
-impl Into2 for Ascii { fn into2(self) -> String { self.0 } }
-impl Into2> for Option{
- fn into2(self) -> Option { self.map(|x| {x.0}) }
-}
-
-macro_rules! proto_msg_case {
- ($val: ident()) =>
- (Just($val));
- ($val: ident($arg: ty)) =>
- (any::<$arg>().prop_map(|v| {$val(v.into2())}));
- ($val: ident($arg1: ty, $arg2: ty)) =>
- (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())}));
- ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) =>
- (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())}));
-}
-
-macro_rules! proto_msg_match {
- ($var: expr, def = $default: ident, $($num: expr => $constr: ident $res: tt),*) => (
- match $var {
- $($num => (proto_msg_case!($constr $res)).boxed()),*,
- _ => Just($default).boxed()
- }
- )
-}
-
-#[derive(Debug)]
-struct Ascii(String);
-
-struct AsciiValueTree(RegexGeneratorValueTree);
-
-impl ValueTree for AsciiValueTree {
- type Value = Ascii;
-
- fn current(&self) -> Self::Value { Ascii(self.0.current()) }
- fn simplify(&mut self) -> bool { self.0.simplify() }
- fn complicate(&mut self) -> bool { self.0.complicate() }
-}
-
-impl Arbitrary for Ascii {
- type Parameters = ::Parameters;
-
- fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
- any_with::(args)
- .prop_filter("not ascii", |s| {
- s.len() > 0 && s.is_ascii() &&
- s.find(|c| {
- ['\0', '\n', '\x20'].contains(&c)
- }).is_none()})
- .prop_map(Ascii)
- .boxed()
- }
-
- type Strategy = BoxedStrategy;
- type ValueTree = Box>;
-}
-
-fn gen_proto_msg() -> BoxedStrategy where {
- let res = (0..58).no_shrink().prop_flat_map(|i| {
- proto_msg_match!(i, def = Malformed,
- 0 => Ping(),
- 1 => Pong(),
- 2 => Quit(Option),
- //3 => Cmd
- 4 => Global(Ascii),
- 5 => Watch(Ascii),
- 6 => ToggleServerRegisteredOnly(),
- 7 => SuperPower(),
- 8 => Info(Ascii),
- 9 => Nick(Ascii),
- 10 => Proto(u32),
- 11 => Password(Ascii, Ascii),
- 12 => Checker(u32, Ascii, Ascii),
- 13 => List(),
- 14 => Chat(Ascii),
- 15 => CreateRoom(Ascii, Option),
- 16 => Join(Ascii, Option),
- 17 => Follow(Ascii),
- //18 => Rnd(Vec),
- 19 => Kick(Ascii),
- 20 => Ban(Ascii, Ascii, u32),
- 21 => BanIP(Ascii, Ascii, u32),
- 22 => BanNick(Ascii, Ascii, u32),
- 23 => BanList(),
- 24 => Unban(Ascii),
- //25 => SetServerVar(ServerVar),
- 26 => GetServerVar(),
- 27 => RestartServer(),
- 28 => Stats(),
- 29 => Part(Option),
- //30 => Cfg(GameCfg),
- //31 => AddTeam(TeamInfo),
- 32 => RemoveTeam(Ascii),
- //33 => SetHedgehogsNumber(String, u8),
- //34 => SetTeamColor(String, u8),
- 35 => ToggleReady(),
- 36 => StartGame(),
- 37 => EngineMessage(Ascii),
- 38 => RoundFinished(),
- 39 => ToggleRestrictJoin(),
- 40 => ToggleRestrictTeams(),
- 41 => ToggleRegisteredOnly(),
- 42 => RoomName(Ascii),
- 43 => Delegate(Ascii),
- 44 => TeamChat(Ascii),
- 45 => MaxTeams(u8),
- 46 => Fix(),
- 47 => Unfix(),
- 48 => Greeting(Ascii),
- //49 => CallVote(Option<(String, Option)>),
- 50 => Vote(String),
- 51 => ForceVote(Ascii),
- //52 => Save(String, String),
- 53 => Delete(Ascii),
- 54 => SaveRoom(Ascii),
- 55 => LoadRoom(Ascii),
- 56 => Malformed(),
- 57 => Empty()
- )});
- res.boxed()
-}
-
-proptest! {
- #[test]
- fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
- println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes());
- assert_eq!(message(msg.to_raw_protocol().as_bytes()), IResult::Done(&b""[..], msg.clone()))
- }
-}
-
-#[test]
-fn parse_test() {
- assert_eq!(message(b"PING\n\n"), IResult::Done(&b""[..], Ping));
- assert_eq!(message(b"START_GAME\n\n"), IResult::Done(&b""[..], StartGame));
- assert_eq!(message(b"NICK\nit's me\n\n"), IResult::Done(&b""[..], Nick("it's me".to_string())));
- assert_eq!(message(b"PROTO\n51\n\n"), IResult::Done(&b""[..], Proto(51)));
- assert_eq!(message(b"QUIT\nbye-bye\n\n"), IResult::Done(&b""[..], Quit(Some("bye-bye".to_string()))));
- assert_eq!(message(b"QUIT\n\n"), IResult::Done(&b""[..], Quit(None)));
- assert_eq!(message(b"CMD\nwatch\ndemo\n\n"), IResult::Done(&b""[..], Watch("demo".to_string())));
- assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), IResult::Done(&b""[..], Ban("me".to_string(), "bad".to_string(), 77)));
-
- assert_eq!(message(b"CMD\nPART\n\n"), IResult::Done(&b""[..], Part(None)));
- assert_eq!(message(b"CMD\nPART\n_msg_\n\n"), IResult::Done(&b""[..], Part(Some("_msg_".to_string()))));
-
- assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), IResult::Done(&b""[..], vec![Malformed]));
-
- assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), IResult::Done(&b"P"[..], vec![Ping, Ping]));
- assert_eq!(extract_messages(b"SING\n\nPING\n\n"), IResult::Done(&b""[..], vec![Malformed, Ping]));
- assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), IResult::Done(&b""[..], vec![Empty, Empty, Ping]));
- assert_eq!(extract_messages(b"\n\n\nPING\n\n"), IResult::Done(&b""[..], vec![Empty, Empty, Ping]));
-}
\ No newline at end of file
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/actions.rs
--- a/gameServer2/src/server/actions.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-use mio;
-use std::io::Write;
-use std::io;
-
-use super::server::HWServer;
-use super::room::HWRoom;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage;
-use protocol::messages::HWServerMessage::*;
-use super::handlers;
-
-pub enum Action {
- SendMe(HWServerMessage),
- SendAllButMe(HWServerMessage),
- RemoveClient,
- ByeClient(String),
- ReactProtocolMessage(HWProtocolMessage),
- CheckRegistered,
- JoinLobby,
- AddRoom(String, Option),
- Warn(String),
-}
-
-use self::Action::*;
-
-pub fn run_action(server: &mut HWServer, token: usize, action: Action) {
- match action {
- SendMe(msg) =>
- server.send_self(token, msg),
- SendAllButMe(msg) => {
- server.send_others(token, msg)
- },
- ByeClient(msg) => {
- server.react(token, vec![
- SendMe(Bye(msg)),
- RemoveClient,
- ]);
- },
- RemoveClient => {
- server.removed_clients.push(token);
- if server.clients.contains(token) {
- server.clients.remove(token);
- }
- },
- ReactProtocolMessage(msg) =>
- handlers::handle(server, token, msg),
- CheckRegistered =>
- if server.clients[token].protocol_number > 0 && server.clients[token].nick != "" {
- server.react(token, vec![
- JoinLobby,
- ]);
- },
- JoinLobby => {
- server.clients[token].room_id = Some(server.lobby_id);
-
- let joined_msg;
- {
- let mut lobby_nicks = Vec::new();
- for (_, c) in server.clients.iter() {
- if c.room_id.is_some() {
- lobby_nicks.push(c.nick.clone());
- }
- }
- joined_msg = LobbyJoined(lobby_nicks);
- }
- let everyone_msg = LobbyJoined(vec![server.clients[token].nick.clone()]);
- server.react(token, vec![
- SendAllButMe(everyone_msg),
- SendMe(joined_msg),
- ]);
- },
- AddRoom(name, password) => {
- let room_id = server.add_room();;
- {
- let r = &mut server.rooms[room_id];
- let c = &mut server.clients[token];
- r.name = name;
- r.password = password;
- r.ready_players_number = 1;
- r.protocol_number = c.protocol_number;
- c.room_id = Some(room_id);
- }
- },
- Warn(msg) => {
- run_action(server, token,SendMe(Warning(msg)));
- }
- //_ => unimplemented!(),
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/client.rs
--- a/gameServer2/src/server/client.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-pub type ClientId = usize;
-
-pub struct HWClient {
- pub id: ClientId,
- pub room_id: Option,
- pub nick: String,
- pub protocol_number: u32,
- pub is_master: bool,
- pub is_ready: bool,
- pub is_joined_mid_game: bool,
-}
-
-impl HWClient {
- pub fn new(id: ClientId) -> HWClient {
- HWClient {
- id,
- room_id: None,
- nick: String::new(),
- protocol_number: 0,
- is_master: false,
- is_ready: false,
- is_joined_mid_game: false,
- }
- }
-}
\ No newline at end of file
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/coretypes.rs
--- a/gameServer2/src/server/coretypes.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum ServerVar {
- MOTDNew(String),
- MOTDOld(String),
- LatestProto(u32),
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum GameCfg {
-
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct TeamInfo {
- name: String,
- color: u8,
- grave: String,
- fort: String,
- voice_pack: String,
- flag: String,
- difficulty: u8,
- hedgehogs_number: u8,
- hedgehogs: [HedgehogInfo; 8],
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct HedgehogInfo {
- name: String,
- hat: String,
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/handlers/inroom.rs
--- a/gameServer2/src/server/handlers/inroom.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-use mio;
-
-use server::server::HWServer;
-use server::actions::Action;
-use server::actions::Action::*;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage::*;
-
-pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
- match message {
- _ => warn!("Unimplemented!"),
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/handlers/lobby.rs
--- a/gameServer2/src/server/handlers/lobby.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-use mio;
-
-use server::server::HWServer;
-use server::actions::Action;
-use server::actions::Action::*;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage::*;
-
-pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
- use protocol::messages::HWProtocolMessage::*;
- match message {
- Chat(msg) => {
- let chat_msg = ChatMsg(server.clients[token].nick.clone(), msg);
- server.react(token, vec![SendAllButMe(chat_msg)]);
- },
- CreateRoom(name, password) => {
- let room_exists = server.rooms.iter().find(|&(_, r)| r.name == name).is_some();
- if room_exists {
- server.react(token, vec![Warn("Room exists".to_string())]);
- } else {
- let flags_msg = ClientFlags("+hr".to_string(), vec![server.clients[token].nick.clone()]);
- {
- let c = &mut server.clients[token];
- c.is_master = true;
- c.is_ready = true;
- c.is_joined_mid_game = false;
- }
- server.react(token, vec![
- AddRoom(name, password)
- , SendMe(flags_msg)
- ]);
- }
- },
- Join(name, password) => {
-
- },
- List => warn!("Deprecated LIST message received"),
- _ => warn!("Incorrect command in lobby state"),
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/handlers/loggingin.rs
--- a/gameServer2/src/server/handlers/loggingin.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-use mio;
-
-use server::server::HWServer;
-use server::actions::Action;
-use server::actions::Action::*;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage::*;
-
-pub fn handle(server: & mut HWServer, token: usize, message: HWProtocolMessage) {
- match message {
- HWProtocolMessage::Nick(nick) =>
- if server.clients[token].room_id == None {
- server.react(token, vec![SendMe(Nick(nick.clone()))]);
- server.clients[token].nick = nick;
- server.react(token, vec![CheckRegistered]);
- },
- HWProtocolMessage::Proto(proto) => {
- server.clients[token].protocol_number = proto;
- server.react(token, vec![CheckRegistered]);
- },
- _ => warn!("Incorrect command in logging-in state"),
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/handlers/mod.rs
--- a/gameServer2/src/server/handlers/mod.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-use mio;
-use std::io::Write;
-use std::io;
-
-use super::server::HWServer;
-use super::actions::Action;
-use super::actions::Action::*;
-use protocol::messages::HWProtocolMessage;
-use protocol::messages::HWServerMessage::*;
-
-mod loggingin;
-mod lobby;
-mod inroom;
-
-pub fn handle(server: &mut HWServer, token: usize, message: HWProtocolMessage) {
- match message {
- HWProtocolMessage::Ping =>
- server.react(token, vec![SendMe(Pong)]),
- HWProtocolMessage::Quit(Some(msg)) =>
- server.react(token, vec![ByeClient("User quit: ".to_string() + &msg)]),
- HWProtocolMessage::Quit(None) =>
- server.react(token, vec![ByeClient("User quit".to_string())]),
- HWProtocolMessage::Malformed => warn!("Malformed/unknown message"),
- HWProtocolMessage::Empty => warn!("Empty message"),
- _ => {
- match server.clients[token].room_id {
- None =>
- loggingin::handle(server, token, message),
- Some(id) if id == server.lobby_id =>
- lobby::handle(server, token, message),
- _ =>
- inroom::handle(server, token, message)
- }
- },
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/mod.rs
--- a/gameServer2/src/server/mod.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-pub mod server;
-pub mod client;
-pub mod room;
-pub mod network;
-pub mod coretypes;
-mod actions;
-mod handlers;
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/network.rs
--- a/gameServer2/src/server/network.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-extern crate slab;
-
-use std::io::ErrorKind;
-use mio::net::*;
-use super::server::{HWServer, PendingMessage, Destination};
-use super::client::ClientId;
-use slab::Slab;
-
-use mio::net::TcpStream;
-use mio::*;
-use std::io::Write;
-use std::io;
-use netbuf;
-
-use utils;
-use protocol::ProtocolDecoder;
-use protocol::messages::*;
-use std::net::SocketAddr;
-
-pub struct NetworkClient {
- id: ClientId,
- socket: TcpStream,
- peer_addr: SocketAddr,
- decoder: ProtocolDecoder,
- buf_out: netbuf::Buf,
- closed: bool
-}
-
-impl NetworkClient {
- pub fn new(id: ClientId, socket: TcpStream, peer_addr: SocketAddr) -> NetworkClient {
- NetworkClient {
- id, socket, peer_addr,
- decoder: ProtocolDecoder::new(),
- buf_out: netbuf::Buf::new(),
- closed: false
- }
- }
-
- pub fn send_raw_msg(&mut self, msg: &[u8]) {
- self.buf_out.write(msg).unwrap();
- self.flush();
- }
-
- pub fn send_string(&mut self, msg: &String) {
- self.send_raw_msg(&msg.as_bytes());
- }
-
- pub fn send_msg(&mut self, msg: HWServerMessage) {
- self.send_string(&msg.to_raw_protocol());
- }
-
- fn flush(&mut self) {
- self.buf_out.write_to(&mut self.socket).unwrap();
- self.socket.flush().unwrap();
- }
-
- pub fn read_messages(&mut self) -> io::Result> {
- let bytes_read = self.decoder.read_from(&mut self.socket)?;
- debug!("Read {} bytes", bytes_read);
-
- if bytes_read == 0 {
- self.closed = true;
- info!("EOF for client {} ({})", self.id, self.peer_addr);
- }
-
- Ok(self.decoder.extract_messages())
- }
-
- pub fn write_messages(&mut self) -> io::Result<()> {
- self.buf_out.write_to(&mut self.socket)?;
- Ok(())
- }
-}
-
-pub struct NetworkLayer {
- listener: TcpListener,
- server: HWServer,
-
- clients: Slab
-}
-
-impl NetworkLayer {
- pub fn new(listener: TcpListener, clients_limit: usize, rooms_limit: usize) -> NetworkLayer {
- let server = HWServer::new(clients_limit, rooms_limit);
- let clients = Slab::with_capacity(clients_limit);
- NetworkLayer {listener, server, clients}
- }
-
- pub fn register_server(&self, poll: &Poll) -> io::Result<()> {
- poll.register(&self.listener, utils::SERVER, Ready::readable(),
- PollOpt::edge())
- }
-
- fn deregister_client(&mut self, poll: &Poll, id: ClientId) {
- let mut client_exists = false;
- if let Some(ref client) = self.clients.get_mut(id) {
- poll.deregister(&client.socket)
- .ok().expect("could not deregister socket");
- info!("client {} ({}) removed", client.id, client.peer_addr);
- client_exists = true;
- }
- if client_exists {
- self.clients.remove(id);
- }
- }
-
- fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: TcpStream, addr: SocketAddr) {
- poll.register(&client_socket, Token(id),
- Ready::readable() | Ready::writable(),
- PollOpt::edge())
- .ok().expect("could not register socket with event loop");
-
- let entry = self.clients.vacant_entry();
- let client = NetworkClient::new(id, client_socket, addr);
- info!("client {} ({}) added", client.id, client.peer_addr);
- entry.insert(client);
- }
-
- pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> {
- let (client_socket, addr) = self.listener.accept()?;
- info!("Connected: {}", addr);
-
- let client_id = self.server.add_client();
- self.register_client(poll, client_id, client_socket, addr);
- self.flush_server_messages();
-
- Ok(())
- }
-
- fn flush_server_messages(&mut self) {
- for PendingMessage(destination, msg) in self.server.output.drain(..) {
- match destination {
- Destination::ToSelf(id) => {
- if let Some(ref mut client) = self.clients.get_mut(id) {
- client.send_msg(msg)
- }
- }
- Destination::ToOthers(id) => {
- let msg_string = msg.to_raw_protocol();
- for item in self.clients.iter_mut() {
- if item.0 != id {
- item.1.send_string(&msg_string)
- }
- }
- }
- }
- }
- }
-
- pub fn client_readable(&mut self, poll: &Poll,
- client_id: ClientId) -> io::Result<()> {
- let mut client_lost = false;
- let messages;
- if let Some(ref mut client) = self.clients.get_mut(client_id) {
- messages = match client.read_messages() {
- Ok(messages) => Some(messages),
- Err(ref error) if error.kind() == ErrorKind::WouldBlock => None,
- Err(error) => return Err(error)
- };
- if client.closed {
- client_lost = true;
- }
- } else {
- warn!("invalid readable client: {}", client_id);
- messages = None;
- };
-
- if client_lost {
- self.client_error(&poll, client_id)?;
- } else if let Some(msg) = messages {
- for message in msg {
- self.server.handle_msg(client_id, message);
- }
- self.flush_server_messages();
- }
-
- if !self.server.removed_clients.is_empty() {
- let ids = self.server.removed_clients.to_vec();
- self.server.removed_clients.clear();
- for client_id in ids {
- self.deregister_client(poll, client_id);
- }
- }
-
- Ok(())
- }
-
- pub fn client_writable(&mut self, poll: &Poll,
- client_id: ClientId) -> io::Result<()> {
- if let Some(ref mut client) = self.clients.get_mut(client_id) {
- match client.write_messages() {
- Ok(_) => (),
- Err(ref error) if error.kind() == ErrorKind::WouldBlock => (),
- Err(error) => return Err(error)
- }
- } else {
- warn!("invalid writable client: {}", client_id);
- }
-
- Ok(())
- }
-
- pub fn client_error(&mut self, poll: &Poll,
- client_id: ClientId) -> io::Result<()> {
- self.deregister_client(poll, client_id);
- self.server.client_lost(client_id);
-
- Ok(())
- }
-}
-
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/room.rs
--- a/gameServer2/src/server/room.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-pub type RoomId = usize;
-
-pub struct HWRoom {
- pub id: RoomId,
- pub name: String,
- pub password: Option,
- pub protocol_number: u32,
- pub ready_players_number: u8,
-}
-
-impl HWRoom {
- pub fn new(id: RoomId) -> HWRoom {
- HWRoom {
- id,
- name: String::new(),
- password: None,
- protocol_number: 0,
- ready_players_number: 0,
- }
- }
-}
\ No newline at end of file
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/server/server.rs
--- a/gameServer2/src/server/server.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-use slab;
-use mio::net::*;
-use mio::*;
-use std::io;
-
-use utils;
-use super::client::*;
-use super::room::*;
-use super::actions;
-use protocol::messages::*;
-use super::handlers;
-
-type Slab = slab::Slab;
-
-pub enum Destination {
- ToSelf(ClientId),
- ToOthers(ClientId)
-}
-
-pub struct PendingMessage(pub Destination, pub HWServerMessage);
-
-pub struct HWServer {
- pub clients: Slab,
- pub rooms: Slab,
- pub lobby_id: RoomId,
- pub output: Vec,
- pub removed_clients: Vec,
-}
-
-impl HWServer {
- pub fn new(clients_limit: usize, rooms_limit: usize) -> HWServer {
- let rooms = Slab::with_capacity(rooms_limit);
- let clients = Slab::with_capacity(clients_limit);
- let mut server = HWServer {
- clients, rooms,
- lobby_id: 0,
- output: vec![],
- removed_clients: vec![]
- };
- server.lobby_id = server.add_room();
- server
- }
-
- pub fn add_client(&mut self) -> ClientId {
- let key: ClientId;
- {
- let entry = self.clients.vacant_entry();
- key = entry.key();
- let client = HWClient::new(entry.key());
- entry.insert(client);
- }
- self.send_self(key, HWServerMessage::Connected(utils::PROTOCOL_VERSION));
- key
- }
-
- pub fn client_lost(&mut self, client_id: ClientId) {
- actions::run_action(self, client_id,
- actions::Action::ByeClient("Connection reset".to_string()));
- }
-
- pub fn add_room(&mut self) -> RoomId {
- let entry = self.rooms.vacant_entry();
- let key = entry.key();
- let room = HWRoom::new(entry.key());
- entry.insert(room);
- key
- }
-
- pub fn handle_msg(&mut self, client_id: ClientId, msg: HWProtocolMessage) {
- handlers::handle(self, client_id, msg);
- }
-
- pub fn send_self(&mut self, client_id: ClientId, msg: HWServerMessage) {
- self.output.push(PendingMessage(
- Destination::ToSelf(client_id), msg));
- }
-
- pub fn send_others(&mut self, client_id: ClientId, msg: HWServerMessage) {
- self.output.push(PendingMessage(
- Destination::ToOthers(client_id), msg));
- }
-
- pub fn react(&mut self, client_id: ClientId, actions: Vec) {
- for action in actions {
- actions::run_action(self, client_id, action);
- }
- }
-}
diff -r 0135e64c6c66 -r c4fd2813b127 gameServer2/src/utils.rs
--- a/gameServer2/src/utils.rs Wed May 16 18:22:28 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-use mio;
-
-pub const PROTOCOL_VERSION : u32 = 3;
-pub const SERVER: mio::Token = mio::Token(1000000000 + 0);
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/ArgParsers.pas
--- a/hedgewars/ArgParsers.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/ArgParsers.pas Wed Jul 31 23:14:27 2019 +0200
@@ -34,6 +34,7 @@
implementation
uses uVariables, uTypes, uUtils, uSound, uConsts;
var isInternal: Boolean;
+ helpCommandUsed: Boolean;
{$IFDEF HWLIBRARY}
@@ -71,7 +72,10 @@
procedure DisplayUsage;
begin
- WriteLn(stdout, 'Usage: hwengine [options]');
+ WriteLn(stdout, 'This is the Hedgewars Engine (hwengine), used to play Hedgewars games and demos.');
+ WriteLn(stdout, 'Use the command-line arguments to play a demo.');
+ WriteLn(stdout, '');
+ WriteLn(stdout, 'Usage: hwengine [options]');
WriteLn(stdout, '');
WriteLn(stdout, 'where [options] can be any of the following:');
WriteLn(stdout, ' --prefix [path to folder]');
@@ -96,19 +100,45 @@
WriteLn(stdout, ' --no-hogtag');
WriteLn(stdout, ' --no-healthtag');
WriteLn(stdout, ' --translucent-tags');
+ WriteLn(stdout, ' --stats-only');
WriteLn(stdout, ' --chat-size [default chat size in percent]');
- WriteLn(stdout, ' --stats-only');
WriteLn(stdout, ' --help');
WriteLn(stdout, '');
- WriteLn(stdout, 'For more detailed help and examples go to:');
- WriteLn(stdout, 'http://hedgewars.org/kb/CommandLineOptions');
- GameType:= gmtSyntax;
+ WriteLn(stdout, 'HUD:');
+ WriteLn(stdout, ' --altdmg: Show alternative damage');
+ WriteLn(stdout, ' --no-teamtag: Disable team name tags');
+ WriteLn(stdout, ' --no-hogtag: Disable hedgehog name tags');
+ WriteLn(stdout, ' --no-healthtag: Disable hedgehog health tags');
+ WriteLn(stdout, ' --translucent-tags: Enable translucent name and health tags');
+ WriteLn(stdout, ' --chat-size [default chat size in percent]');
+ WriteLn(stdout, ' --showfps: Show frames per second');
+ WriteLn(stdout, '');
+ WriteLn(stdout, 'Miscellaneous:');
+ WriteLn(stdout, ' --nick : Set user nickname');
+ WriteLn(stdout, ' --help: Show a list of command-line options and exit');
+ WriteLn(stdout, ' --protocol: Display protocol number and exit');
+ WriteLn(stdout, '');
+ Writeln(stdout, 'Advanced options:');
+ Writeln(stdout, ' --stereo : Set stereoscopic rendering (1 to 14)');
+ WriteLn(stdout, ' --frame-interval : Set minimum interval (in ms) between each frame. Eg, 40 would make the game run at most 25 fps');
+ WriteLn(stdout, ' --raw-quality : Manually specify the reduced quality flags');
+ WriteLn(stdout, ' --stats-only: Write the round information to console without launching the game, useful for statistics only');
+ WriteLn(stdout, ' --lua-test : Run a Lua test script');
+ GameType:= gmtSyntaxHelp;
+ helpCommandUsed:= true;
+end;
+
+procedure DisplayProtocol;
+begin
+ WriteLn(stdout, IntToStr(cNetProtoVersion));
+ GameType:= gmtSyntaxHelp;
+ helpCommandUsed:= true;
end;
procedure setDepth(var paramIndex: LongInt);
begin
WriteLn(stdout, 'WARNING: --depth is a deprecated command, which could be removed in a future version!');
- WriteLn(stdout, ' This option no longer does anything, please consider removing it');
+ WriteLn(stdout, ' This option no longer does anything, please consider removing it.');
WriteLn(stdout, '');
inc(ParamIndex);
end;
@@ -128,7 +158,7 @@
ipcPort := port
else
begin
- WriteLn(stderr, 'ERROR: use of --port is not allowed');
+ WriteLn(stderr, 'ERROR: use of --port is not allowed!');
wrongParameter := true;
end
end;
@@ -192,7 +222,7 @@
val(str, tmpInt, c);
wrongParameter:= c <> 0;
if wrongParameter then
- WriteLn(stderr, 'ERROR: '+ParamStr(paramIndex-1)+' expects a number, you passed "'+str+'"');
+ WriteLn(stderr, 'ERROR: '+ParamStr(paramIndex-1)+' expects a number, you passed "'+str+'"!');
{$ENDIF}
getLongIntParameter:= tmpInt;
end;
@@ -203,11 +233,28 @@
wrongParameter:= (str='') or (Copy(str,1,2) = '--');
{$IFNDEF HWLIBRARY}
if wrongParameter then
- WriteLn(stderr, 'ERROR: '+ParamStr(paramIndex-1)+' expects a string, you passed "'+str+'"');
+ WriteLn(stderr, 'ERROR: '+ParamStr(paramIndex-1)+' expects a string, you passed "'+str+'"!');
{$ENDIF}
getstringParameter:= str;
end;
+procedure setZoom(str:shortstring; var paramIndex:LongInt; var wrongParameter:Boolean);
+var param: LongInt;
+begin
+ param:= getLongIntParameter(str, paramIndex, wrongParameter);
+
+ if param = 100 then
+ exit;
+ UserZoom:= (param/100.0) * cDefaultZoomLevel;
+
+ if UserZoom < cMaxZoomLevel then
+ UserZoom:= cMaxZoomLevel;
+ if UserZoom > cMinZoomLevel then
+ UserZoom:= cMinZoomLevel;
+ zoom:= UserZoom;
+ ZoomValue:= UserZoom;
+end;
+
procedure parseClassicParameter(cmdarray: array of string; size:LongInt; var paramIndex:LongInt); forward;
function parseParameter(cmd:string; arg:string; var paramIndex:LongInt): Boolean;
@@ -215,14 +262,15 @@
audioarray: array [0..2] of string = ('--volume','--nomusic','--nosound');
otherarray: array [0..2] of string = ('--locale','--fullscreen','--showfps');
mediaarray: array [0..9] of string = ('--fullscreen-width', '--fullscreen-height', '--width', '--height', '--depth', '--volume','--nomusic','--nosound','--locale','--fullscreen');
- allarray: array [0..17] of string = ('--fullscreen-width','--fullscreen-height', '--width', '--height', '--depth','--volume','--nomusic','--nosound','--locale','--fullscreen','--showfps','--altdmg','--frame-interval','--low-quality','--no-teamtag','--no-hogtag','--no-healthtag','--translucent-tags');
- reallyAll: array[0..36] of shortstring = (
+ allarray: array [0..19] of string = ('--fullscreen-width','--fullscreen-height', '--width', '--height', '--depth','--volume','--nomusic','--nosound','--nodampen','--locale','--fullscreen','--showfps','--altdmg','--frame-interval','--low-quality','--no-teamtag','--no-hogtag','--no-healthtag','--translucent-tags', '--chat-size');
+ reallyAll: array[0..40] of shortstring = (
'--prefix', '--user-prefix', '--locale', '--fullscreen-width', '--fullscreen-height', '--width',
- '--height', '--frame-interval', '--volume','--nomusic', '--nosound',
+ '--height', '--frame-interval', '--volume','--nomusic', '--nosound', '--nodampen',
'--fullscreen', '--showfps', '--altdmg', '--low-quality', '--raw-quality', '--stereo', '--nick',
+ '--zoom',
{deprecated} '--depth', '--set-video', '--set-audio', '--set-other', '--set-multimedia', '--set-everything',
{internal} '--internal', '--port', '--recorder', '--landpreview',
- {misc} '--stats-only', '--gci', '--help','--no-teamtag','--no-hogtag','--no-healthtag','--translucent-tags','--lua-test','--chat-size');
+ {misc} '--stats-only', '--gci', '--help','--protocol', '--no-teamtag','--no-hogtag','--no-healthtag','--translucent-tags','--lua-test','--no-holiday-silliness','--chat-size');
var cmdIndex: byte;
begin
parseParameter:= false;
@@ -235,7 +283,7 @@
case cmdIndex of
{--prefix} 0 : PathPrefix := getstringParameter (arg, paramIndex, parseParameter);
{--user-prefix} 1 : UserPathPrefix := getstringParameter (arg, paramIndex, parseParameter);
- {--locale} 2 : cLocaleFName := getstringParameter (arg, paramIndex, parseParameter);
+ {--locale} 2 : cLanguageFName := getstringParameter (arg, paramIndex, parseParameter);
{--fullscreen-width} 3 : cFullscreenWidth := max(getLongIntParameter(arg, paramIndex, parseParameter), cMinScreenWidth);
{--fullscreen-height} 4 : cFullscreenHeight := max(getLongIntParameter(arg, paramIndex, parseParameter), cMinScreenHeight);
{--width} 5 : cWindowedWidth := max(2 * (getLongIntParameter(arg, paramIndex, parseParameter) div 2), cMinScreenWidth);
@@ -244,43 +292,47 @@
{--volume} 8 : SetVolume ( max(getLongIntParameter(arg, paramIndex, parseParameter), 0) );
{--nomusic} 9 : SetMusic ( false );
{--nosound} 10 : SetSound ( false );
- {--fullscreen} 11 : cFullScreen := true;
- {--showfps} 12 : cShowFPS := true;
- {--altdmg} 13 : cAltDamage := true;
- {--low-quality} 14 : cReducedQuality := $FFFFFFFF xor rqLowRes;
- {--raw-quality} 15 : cReducedQuality := getLongIntParameter(arg, paramIndex, parseParameter);
- {--stereo} 16 : setStereoMode ( getLongIntParameter(arg, paramIndex, parseParameter) );
- {--nick} 17 : UserNick := parseNick( getstringParameter(arg, paramIndex, parseParameter) );
+ {--nodampen} 11 : SetAudioDampen ( false );
+ {--fullscreen} 12 : cFullScreen := true;
+ {--showfps} 13 : cShowFPS := true;
+ {--altdmg} 14 : cAltDamage := true;
+ {--low-quality} 15 : cReducedQuality := $FFFFFFFF xor rqLowRes;
+ {--raw-quality} 16 : cReducedQuality := getLongIntParameter(arg, paramIndex, parseParameter);
+ {--stereo} 17 : setStereoMode ( getLongIntParameter(arg, paramIndex, parseParameter) );
+ {--nick} 18 : UserNick := parseNick( getstringParameter(arg, paramIndex, parseParameter) );
+ {--zoom} 19 : setZoom(arg, paramIndex, parseParameter);
{deprecated options}
- {--depth} 18 : setDepth(paramIndex);
- {--set-video} 19 : parseClassicParameter(videoarray,5,paramIndex);
- {--set-audio} 20 : parseClassicParameter(audioarray,3,paramIndex);
- {--set-other} 21 : parseClassicParameter(otherarray,3,paramIndex);
- {--set-multimedia} 22 : parseClassicParameter(mediaarray,10,paramIndex);
- {--set-everything} 23 : parseClassicParameter(allarray,14,paramIndex);
+ {--depth} 20 : setDepth(paramIndex);
+ {--set-video} 21 : parseClassicParameter(videoarray,5,paramIndex);
+ {--set-audio} 22 : parseClassicParameter(audioarray,3,paramIndex);
+ {--set-other} 23 : parseClassicParameter(otherarray,3,paramIndex);
+ {--set-multimedia} 24 : parseClassicParameter(mediaarray,10,paramIndex);
+ {--set-everything} 25 : parseClassicParameter(allarray,14,paramIndex);
{"internal" options}
- {--internal} 24 : {$IFDEF HWLIBRARY}isInternal:= true{$ENDIF};
- {--port} 25 : setIpcPort( getLongIntParameter(arg, paramIndex, parseParameter), parseParameter );
- {--recorder} 26 : startVideoRecording(paramIndex);
- {--landpreview} 27 : GameType := gmtLandPreview;
+ {--internal} 26 : {$IFDEF HWLIBRARY}isInternal:= true{$ENDIF};
+ {--port} 27 : setIpcPort( getLongIntParameter(arg, paramIndex, parseParameter), parseParameter );
+ {--recorder} 28 : startVideoRecording(paramIndex);
+ {--landpreview} 29 : GameType := gmtLandPreview;
{anything else}
- {--stats-only} 28 : statsOnlyGame();
- {--gci} 29 : GciEasterEgg();
- {--help} 30 : DisplayUsage();
- {--no-teamtag} 31 : cTagsMask := cTagsMask and (not htTeamName);
- {--no-hogtag} 32 : cTagsMask := cTagsMask and (not htName);
- {--no-healthtag} 33 : cTagsMask := cTagsMask and (not htHealth);
- {--translucent-tags} 34 : cTagsMask := cTagsMask or htTransparent;
- {--lua-test} 35 : begin cTestLua := true; SetSound(false); cScriptName := getstringParameter(arg, paramIndex, parseParameter); WriteLn(stdout, 'Lua test file specified: ' + cScriptName);end;
- {--chat-size} 36 : cDefaultChatScale := 1.0 * getLongIntParameter(arg, paramIndex, parseParameter) / 100;
+ {--stats-only} 30 : statsOnlyGame();
+ {--gci} 31 : GciEasterEgg();
+ {--help} 32 : DisplayUsage();
+ {--protocol} 33 : DisplayProtocol();
+ {--no-teamtag} 34 : cTagsMask := cTagsMask and (not htTeamName);
+ {--no-hogtag} 35 : cTagsMask := cTagsMask and (not htName);
+ {--no-healthtag} 36 : cTagsMask := cTagsMask and (not htHealth);
+ {--translucent-tags} 37 : cTagsMask := cTagsMask or htTransparent;
+ {--lua-test} 38 : begin cTestLua := true; SetSound(false); cScriptName := getstringParameter(arg, paramIndex, parseParameter); WriteLn(stdout, 'Lua test file specified: ' + cScriptName);end;
+ {--no-holiday-silliness} 39 : cHolidaySilliness:= false;
+ {--chat-size} 40 : cDefaultChatScale := 1.0 * getLongIntParameter(arg, paramIndex, parseParameter) / 100;
else
begin
- //Assume the first "non parameter" is the replay file, anything else is invalid
+ //Assume the first "non parameter" is the demo file, anything else is invalid
if (recordFileName = '') and (Copy(cmd,1,2) <> '--') then
recordFileName := cmd
else
begin
- WriteLn(stderr, '"'+cmd+'" is not a valid option');
+ WriteLn(stderr, '"'+cmd+'" is not a valid option.');
parseParameter:= true;
end;
end;
@@ -292,9 +344,9 @@
isBool, isValid: Boolean;
cmd, arg, newSyntax: string;
begin
- WriteLn(stdout, 'WARNING: you are using a deprecated command, which could be removed in a future version!');
+ WriteLn(stdout, 'WARNING: You are using a deprecated command, which could be removed in a future version!');
WriteLn(stdout, ' Consider updating to the latest syntax, which is much more flexible!');
- WriteLn(stdout, ' Run `hwegine --help` to learn it!');
+ WriteLn(stdout, ' Run "hwegine --help" to learn it!');
WriteLn(stdout, '');
index:= 0;
@@ -308,10 +360,10 @@
isValid:= (cmd<>'--depth');
// check if the parameter is a boolean one
- isBool:= (cmd = '--nomusic') or (cmd = '--nosound') or (cmd = '--fullscreen') or (cmd = '--showfps') or (cmd = '--altdmg') or (cmd = '--no-teamtag') or (cmd = '--no-hogtag') or (cmd = '--no-healthtag') or (cmd = '--translucent-tags');
+ isBool:= (cmd = '--nomusic') or (cmd = '--nosound') or (cmd = '--nodampen') or (cmd = '--fullscreen') or (cmd = '--showfps') or (cmd = '--altdmg') or (cmd = '--no-teamtag') or (cmd = '--no-hogtag') or (cmd = '--no-healthtag') or (cmd = '--translucent-tags');
if isBool and (arg='0') then
isValid:= false;
- if (cmd='--nomusic') or (cmd='--nosound') then
+ if (cmd='--nomusic') or (cmd='--nosound') or (cmd='--nodampen') then
isValid:= not isValid;
if isValid then
@@ -359,12 +411,13 @@
inc(paramIndex);
end;
if wrongParameter = true then
- GameType:= gmtSyntax;
+ GameType:= gmtBadSyntax;
end;
procedure GetParams;
begin
isInternal:= (ParamStr(1) = '--internal');
+ helpCommandUsed:= false;
UserPathPrefix := _S'.';
PathPrefix := cDefaultPathPrefix;
@@ -373,20 +426,21 @@
if (isInternal) and (ParamCount<=1) then
begin
- WriteLn(stderr, '--internal should not be manually used');
- GameType := gmtSyntax;
+ WriteLn(stderr, 'The "--internal" option should not be manually used!');
+ GameType := gmtBadSyntax;
end;
- if (not cTestLua) and (not isInternal) and (recordFileName = '') then
- begin
- WriteLn(stderr, 'You must specify a replay file');
- GameType := gmtSyntax;
- end
- else if (recordFileName <> '') then
- WriteLn(stdout, 'Attempting to play demo file "' + recordFilename + '"');
+ if (not helpCommandUsed) then
+ if (not cTestLua) and (not isInternal) and (recordFileName = '') then
+ begin
+ WriteLn(stderr, 'You must specify a demo file.');
+ GameType := gmtBadSyntax;
+ end
+ else if (recordFileName <> '') then
+ WriteLn(stdout, 'Attempting to play demo file "' + recordFilename + '".');
- if (GameType = gmtSyntax) then
- WriteLn(stderr, 'Please use --help to see possible arguments and their usage');
+ if (GameType = gmtBadSyntax) then
+ WriteLn(stderr, 'Please use --help to see possible arguments and their usage.');
(*
WriteLn(stdout,'PathPrefix: ' + PathPrefix);
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/CMakeLists.txt
--- a/hedgewars/CMakeLists.txt Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/CMakeLists.txt Wed Jul 31 23:14:27 2019 +0200
@@ -52,6 +52,7 @@
uConsole.pas
uCommands.pas
uDebug.pas
+ uKeyNames.pas
uInputHandler.pas
uTextures.pas
uRenderUtils.pas
@@ -164,17 +165,10 @@
add_flag_append(CMAKE_Pascal_FLAGS "-XLAlua=${lua_output_name}")
endif()
-if(PHYSFS_SYSTEM)
- get_filename_component(PHYSFS_LIBRARY_DIR ${PHYSFS_LIBRARY} PATH)
- add_flag_append(CMAKE_Pascal_FLAGS "-Fl${PHYSFS_LIBRARY}")
-else()
- add_definitions(-dPHYSFS_INTERNAL)
- list(APPEND HW_LINK_LIBS physfs)
- #-XLA is a beta fpc flag that renames libraries before passing them to the linker
- #we also have to pass PHYSFS_INTERNAL to satisfy windows runtime requirements
- #(should be harmless on other platforms)
- add_flag_append(CMAKE_Pascal_FLAGS "-XLAphysfs=${physfs_output_name}")
-endif()
+# PhysFS
+get_filename_component(PHYSFS_LIBRARY_DIR ${PHYSFS_LIBRARY} PATH)
+add_flag_append(CMAKE_Pascal_FLAGS "-Fl${PHYSFS_LIBRARY}")
+
list(APPEND HW_LINK_LIBS physlayer)
@@ -193,6 +187,10 @@
list(APPEND sourcefiles_sofar "${CMAKE_CURRENT_SOURCE_DIR}/${loop_var}")
endforeach(loop_var)
+if (WIN32 AND VCPKG_TOOLCHAIN)
+ add_definitions(-dWIN32_VCPKG)
+endif()
+
#SOURCE AND PROGRAMS SECTION
if(BUILD_ENGINE_LIBRARY)
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/LuaPas.pas
--- a/hedgewars/LuaPas.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/LuaPas.pas Wed Jul 31 23:14:27 2019 +0200
@@ -14,9 +14,14 @@
uses uConsts;
{.$DEFINE LUA_GETHOOK}
-const LuaLibName = {$IFDEF LUA_INTERNAL}'libhwlua'{$ELSE}'liblua'{$ENDIF};
+const LuaLibName =
+{$IFDEF LUA_INTERNAL}
+ {$IFDEF WIN32_VCPKG}'hwlua'{$ELSE}'libhwlua'{$ENDIF}
+{$ELSE}
+ {$IFDEF WIN32_VCPKG}'lua'{$ELSE}'liblua'{$ENDIF}
+{$ENDIF};
-{$IFNDEF WIN32}
+{$IFNDEF WINDOWS}
{$linklib lua}
{$ENDIF}
@@ -114,7 +119,7 @@
(*
** $Id: lua.h,v 1.216 2006/01/10 12:50:13 roberto Exp $
** Lua - An Extensible Extension Language
-** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
+** Lua.org, PUC-Rio, Brazil (https://www.lua.org)
** See Copyright Notice at the end of this file
*)
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/SDLh.pas
--- a/hedgewars/SDLh.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/SDLh.pas Wed Jul 31 23:14:27 2019 +0200
@@ -59,7 +59,7 @@
(* SDL *)
const
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
SDLLibName = 'SDL2.dll';
SDL_TTFLibName = 'SDL2_ttf.dll';
SDL_MixerLibName = 'SDL2_mixer.dll';
@@ -109,6 +109,10 @@
SDL_BUTTON_X1 = 4;
SDL_BUTTON_X2 = 5;
+ // SDL_ShowCursor consts
+ SDL_QUERY = -1;
+ SDL_DISABLE = 0;
+ SDL_ENABLE = 1;
SDL_TEXTEDITINGEVENT_TEXT_SIZE = 32;
SDL_TEXTINPUTEVENT_TEXT_SIZE = 32;
@@ -247,6 +251,7 @@
MIX_INIT_MP3 = $00000008;
MIX_INIT_OGG = $00000010;
MIX_INIT_FLUIDSYNTH = $00000020;
+ MIX_INIT_OPUS = $00000040;
{* SDL_TTF *}
TTF_STYLE_NORMAL = 0;
@@ -541,8 +546,8 @@
/////////////////////////////////////////////////////////////////
// two important reference points for the wanderers of this area
-// http://www.freepascal.org/docs-html/ref/refsu5.html
-// http://www.freepascal.org/docs-html/prog/progsu144.html
+// https://www.freepascal.org/docs-html/ref/refsu5.html
+// https://www.freepascal.org/docs-html/prog/progsu144.html
type
PSDL_Window = Pointer;
@@ -553,6 +558,8 @@
TSDL_FingerId = Int64;
TSDL_Keycode = LongInt;
TSDL_Scancode = LongInt;
+ TSDL_JoystickID = LongInt;
+ TSDL_bool = LongInt;
TSDL_eventaction = (SDL_ADDEVENT, SDL_PEEPEVENT, SDL_GETEVENT);
@@ -656,7 +663,7 @@
fd: LongInt;
end;
{$ELSE}
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
TWinbuffer = record
data: Pointer;
size, left: LongInt;
@@ -680,7 +687,7 @@
{$IFDEF ANDROID}
0: (androidio: TAndroidio);
{$ELSE}
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
0: (windowsio: TWindowsio);
{$ENDIF}
{$ENDIF}
@@ -765,7 +772,7 @@
TSDL_ControllerAxisEvent = record
type_: LongWord;
timestamp: LongWord;
- which: LongInt;
+ which: TSDL_JoystickID;
axis, padding1, padding2, padding3: Byte;
value: SmallInt;
padding4: Word;
@@ -774,14 +781,14 @@
TSDL_ControllerButtonEvent = record
type_: LongWord;
timestamp: LongWord;
- which: LongInt;
+ which: TSDL_JoystickID;
button, states, padding1, padding2: Byte;
end;
TSDL_ControllerDeviceEvent = record
type_: LongWord;
timestamp: LongWord;
- which: SmallInt;
+ which: LongInt;
end;
TSDL_JoyDeviceEvent = TSDL_ControllerDeviceEvent;
@@ -829,17 +836,17 @@
TSDL_JoyAxisEvent = record
type_: LongWord;
timestamp: LongWord;
- which: LongWord;
+ which: TSDL_JoystickID;
axis: Byte;
padding1, padding2, padding3: Byte;
- value: LongInt;
+ value: SmallInt;
padding4: Word;
end;
TSDL_JoyBallEvent = record
type_: LongWord;
timestamp: LongWord;
- which: LongWord;
+ which: TSDL_JoystickID;
ball: Byte;
padding1, padding2, padding3: Byte;
xrel, yrel: SmallInt;
@@ -848,7 +855,7 @@
TSDL_JoyHatEvent = record
type_: LongWord;
timestamp: LongWord;
- which: LongWord;
+ which: TSDL_JoystickID;
hat: Byte;
value: Byte;
padding1, padding2: Byte;
@@ -857,10 +864,11 @@
TSDL_JoyButtonEvent = record
type_: LongWord;
timestamp: LongWord;
- which: Byte;
+ which: TSDL_JoystickID;
button: Byte;
state: Byte;
padding1: Byte;
+ padding2: Byte;
end;
TSDL_QuitEvent = record
@@ -1021,7 +1029,7 @@
sockets: PTCPSocket;
end;
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
TThreadFunction = function (p: pointer): Longword; stdcall;
pfnSDL_CurrentBeginThread = function (
_Security: pointer;
@@ -1105,6 +1113,7 @@
function SDL_RenderReadPixels(renderer: PSDL_Renderer; rect: PSDL_Rect; format: LongInt; pixels: Pointer; pitch: LongInt): LongInt; cdecl; external SDLLibName;
function SDL_RenderSetViewport(window: PSDL_Window; rect: PSDL_Rect): LongInt; cdecl; external SDLLibName;
+function SDL_SetRelativeMouseMode(enabled: TSDL_bool): LongInt; cdecl; external SDLLibName;
function SDL_GetRelativeMouseState(x, y: PLongInt): Byte; cdecl; external SDLLibName;
function SDL_PixelFormatEnumToMasks(format: TSDL_ArrayByteOrder; bpp: PLongInt; Rmask, Gmask, Bmask, Amask: PLongInt): Boolean; cdecl; external SDLLibName;
@@ -1143,7 +1152,7 @@
(* remember to mark the threaded functions as 'cdecl; export;'
(or have fun debugging nil arguments) *)
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
// SDL uses wrapper in windows
function SDL_CreateThread(fn: Pointer; name: PChar; data: Pointer; bt: pfnSDL_CurrentBeginThread; et: pfnSDL_CurrentEndThread): PSDL_Thread; cdecl; external SDLLibName;
function SDL_CreateThread(fn: Pointer; name: PChar; data: Pointer): PSDL_Thread; cdecl; overload;
@@ -1165,7 +1174,7 @@
procedure SDL_UnlockAudio; cdecl; external SDLLibName;
function SDL_NumJoysticks: LongInt; cdecl; external SDLLibName;
-function SDL_JoystickName(idx: LongInt): PChar; cdecl; external SDLLibName;
+function SDL_JoystickNameForIndex(idx: LongInt): PChar; cdecl; external SDLLibName;
function SDL_JoystickOpen(idx: LongInt): PSDL_Joystick; cdecl; external SDLLibName;
function SDL_JoystickOpened(idx: LongInt): LongInt; cdecl; external SDLLibName;
function SDL_JoystickIndex(joy: PSDL_Joystick): LongInt; cdecl; external SDLLibName;
@@ -1181,7 +1190,7 @@
function SDL_JoystickGetButton(joy: PSDL_Joystick; button: LongInt): Byte; cdecl; external SDLLibName;
procedure SDL_JoystickClose(joy: PSDL_Joystick); cdecl; external SDLLibName;
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
function SDL_putenv(const text: PChar): LongInt; cdecl; external SDLLibName;
function SDL_getenv(const text: PChar): PChar; cdecl; external SDLLibName;
{$ENDIF}
@@ -1319,7 +1328,7 @@
(PByteArray(buf)^[0] shl 24)
end;
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
function SDL_CreateThread(fn: Pointer; name: PChar; data: Pointer): PSDL_Thread; cdecl;
begin
SDL_CreateThread:= SDL_CreateThread(fn, name, data, nil, nil)
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/avwrapper/avwrapper.c
--- a/hedgewars/avwrapper/avwrapper.c Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/avwrapper/avwrapper.c Wed Jul 31 23:14:27 2019 +0200
@@ -35,6 +35,8 @@
#define AVWRAP_DECL
#endif
+#define UNUSED(x) (void)(x)
+
static AVFormatContext* g_pContainer;
static AVOutputFormat* g_pFormat;
static AVStream* g_pAStream;
@@ -138,6 +140,9 @@
// (there is mutex in AddFileLogRaw).
static void LogCallback(void* p, int Level, const char* pFmt, va_list VaArgs)
{
+ UNUSED(p);
+ UNUSED(Level);
+
char Buffer[1024];
vsnprintf(Buffer, 1024, pFmt, VaArgs);
@@ -362,6 +367,8 @@
VideoTime = (double)g_pVFrame->pts * g_pVStream->time_base.num/g_pVStream->time_base.den;
do
{
+ if (!g_pAFrame)
+ return FatalError("Error while writing video frame: g_pAFrame does not exist");
AudioTime = (double)g_pAFrame->pts * g_pAStream->time_base.num/g_pAStream->time_base.den;
ret = WriteAudioFrame();
}
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/hwLibrary.pas
--- a/hedgewars/hwLibrary.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/hwLibrary.pas Wed Jul 31 23:14:27 2019 +0200
@@ -24,7 +24,7 @@
* and language of choice.
*
* See also: C declarations on Wikipedia
- * http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl
+ * https://en.wikipedia.org/wiki/X86_calling_conventions#cdecl
*)
Library hwLibrary;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/hwengine.pas
--- a/hedgewars/hwengine.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/hwengine.pas Wed Jul 31 23:14:27 2019 +0200
@@ -18,7 +18,7 @@
{$INCLUDE "options.inc"}
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
{$R res/hwengine.rc}
{$ENDIF}
@@ -36,7 +36,8 @@
{$IFDEF USE_VIDEO_RECORDING}, uVideoRec {$ENDIF}
{$IFDEF USE_TOUCH_INTERFACE}, uTouch {$ENDIF}
{$IFDEF ANDROID}, GLUnit{$ENDIF}
- {$IFDEF WIN32}, dynlibs{$ENDIF}
+ {$IFDEF UNIX}, clocale{$ENDIF}
+ {$IFDEF WINDOWS}, dynlibs{$ENDIF}
;
{$IFDEF HWLIBRARY}
@@ -45,15 +46,21 @@
procedure preInitEverything();
procedure initEverything(complete:boolean);
procedure freeEverything(complete:boolean);
+{$IFNDEF PAS2C}
+procedure catchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
+{$ENDIF}
implementation
{$ELSE}
procedure preInitEverything(); forward;
procedure initEverything(complete:boolean); forward;
procedure freeEverything(complete:boolean); forward;
+{$IFNDEF PAS2C}
+procedure catchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer); forward;
+{$ENDIF}
{$ENDIF}
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
type TSetProcessDpiAwareness = function(value: Integer): Integer; stdcall;
var SetProcessDpiAwareness: TSetProcessDpiAwareness;
var ShcoreLibHandle: TLibHandle;
@@ -99,6 +106,7 @@
PlayMusic;
InitZoom(zoom);
ScriptCall('onGameStart');
+ RandomizeHHAnim;
for t:= 0 to Pred(TeamsCount) do
with TeamsArray[t]^ do
MaxTeamHealth:= TeamHealth;
@@ -246,19 +254,21 @@
SDL_FINGERUP:
onTouchUp(event.tfinger.x, event.tfinger.y, event.tfinger.fingerId);
{$ELSE}
+ SDL_MOUSEMOTION:
+ ProcessMouseMotion(event.motion.xrel, event.motion.yrel);
+
SDL_MOUSEBUTTONDOWN:
if GameState = gsConfirm then
ParseCommand('quit', true)
else
- if (GameState >= gsGame) then ProcessMouse(event.button, true);
+ if (GameState >= gsGame) then ProcessMouseButton(event.button, true);
SDL_MOUSEBUTTONUP:
- if (GameState >= gsGame) then ProcessMouse(event.button, false);
+ if (GameState >= gsGame) then ProcessMouseButton(event.button, false);
SDL_MOUSEWHEEL:
begin
wheelEvent:= true;
- //ProcessMouseWheel(event.wheel.x, event.wheel.y);
ProcessMouseWheel(event.wheel.y);
end;
{$ENDIF}
@@ -280,7 +290,7 @@
ResetMouseWheel();
if (CursorMovementX <> 0) or (CursorMovementY <> 0) then
- handlePositionUpdate(CursorMovementX * cameraKeyboardSpeed, CursorMovementY * cameraKeyboardSpeed);
+ handlePositionUpdate(CursorMovementX, CursorMovementY);
if (cScreenResizeDelay <> 0) and (cScreenResizeDelay < RealTicks) and
((cNewScreenWidth <> cScreenWidth) or (cNewScreenHeight <> cScreenHeight)) then
@@ -321,6 +331,9 @@
DoTimer(0); // gsLandGen -> gsStart
DoTimer(0); // gsStart -> gsGame
+ newGameTicks:= 0;
+ newRealTicks:= 0;
+
if not LoadNextCameraPosition(newRealTicks, newGameTicks) then
exit;
fastScrolling:= true;
@@ -348,10 +361,12 @@
///////////////////////////////////////////////////////////////////////////////
procedure GameRoutine;
-//var p: TPathType;
var s: shortstring;
i: LongInt;
begin
+{$IFDEF PAS2C}
+ AddFileLog('Generated using pas2c');
+{$ENDIF}
WriteLnToConsole('Hedgewars engine ' + cVersionString + '-r' + cRevisionString +
' (' + cHashString + ') with protocol #' + inttostr(cNetProtoVersion));
AddFileLog('Prefix: "' + shortstring(PathPrefix) +'"');
@@ -371,9 +386,8 @@
end;
if not allOK then exit;
- //SDL_StartTextInput();
- SDL_ShowCursor(0);
+ SDL_ShowCursor(SDL_DISABLE);
{$IFDEF USE_VIDEO_RECORDING}
if GameType = gmtRecord then
@@ -393,22 +407,23 @@
if not allOK then exit;
LoadLocale(cPathz[ptLocale] + '/en.txt'); // Do an initial load with english
- if cLocaleFName <> 'en.txt' then
+ if cLanguageFName <> 'en.txt' then
begin
// Try two letter locale first before trying specific locale overrides
- if (Length(cLocale) > 3) and (Copy(cLocale, 1, 2) <> 'en') then
+ if (Length(cLanguage) > 3) and (Copy(cLanguage, 1, 2) <> 'en') then
begin
- LoadLocale(cPathz[ptLocale] + '/' + Copy(cLocale, 1, 2) + '.txt')
+ LoadLocale(cPathz[ptLocale] + '/' + Copy(cLanguage, 1, 2) + '.txt')
end;
- LoadLocale(cPathz[ptLocale] + '/' + cLocaleFName)
+ LoadLocale(cPathz[ptLocale] + '/' + cLanguageFName)
end
- else cLocale := 'en';
+ else cLanguage := 'en';
if not allOK then exit;
WriteLnToConsole(msgGettingConfig);
LoadFonts();
AddProgress();
+ LoadDefaultClanColors(cPathz[ptConfig] + '/settings.ini');
if cTestLua then
begin
@@ -440,7 +455,6 @@
isDeveloperMode:= false;
if checkFails(InitStepsFlags = cifAllInited, 'Some parameters not set (flags = ' + inttostr(InitStepsFlags) + ')', true) then exit;
- //ParseCommand('rotmask', true);
if not allOK then exit;
{$IFDEF USE_VIDEO_RECORDING}
@@ -597,6 +611,30 @@
freeEverything(false);
end;
+{$IFNDEF PAS2C}
+// Write backtrace to console and log when an unhandled exception occurred
+procedure catchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
+var
+ Message: string;
+ i: LongInt;
+begin
+ WriteLnToConsole('An unhandled exception occurred at $' + HexStr(Addr) + ':');
+ if Obj is exception then
+ begin
+ Message := Exception(Obj).ClassName + ': ' + Exception(Obj).Message;
+ WriteLnToConsole(Message);
+ end
+ else
+ WriteLnToConsole('Exception object ' + Obj.ClassName + ' is not of class Exception.');
+ WriteLnToConsole(BackTraceStrFunc(Addr));
+ if (FrameCount > 0) then
+ begin
+ for i := 0 to FrameCount - 1 do
+ WriteLnToConsole(BackTraceStrFunc(Frames[i]));
+ end;
+end;
+{$ENDIF}
+
{$IFDEF HWLIBRARY}
function RunEngine(argc: LongInt; argv: PPChar): LongInt; cdecl; export;
begin
@@ -606,7 +644,7 @@
begin
{$ENDIF}
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
ShcoreLibHandle := LoadLibrary('Shcore.dll');
if (ShcoreLibHandle <> 0) then
begin
@@ -624,17 +662,22 @@
// workaround for pascal's ParamStr and ParamCount
init(argc, argv);
{$ENDIF}
+{$IFNDEF PAS2C}
+ // Custom procedure for unhandled exceptions; ExceptProc is used by SysUtils module
+ ExceptProc:= @catchUnhandledException;
+{$ENDIF}
+
preInitEverything();
GetParams();
if GameType = gmtLandPreview then
GenLandPreview()
- else if GameType <> gmtSyntax then
+ else if (GameType <> gmtBadSyntax) and (GameType <> gmtSyntaxHelp) then
Game();
- // return 1 when engine is not called correctly
- if GameType = gmtSyntax then
+ // return error when engine is not called correctly
+ if GameType = gmtBadSyntax then
{$IFDEF PAS2C}
exit(HaltUsageError);
{$ELSE}
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/options.inc
--- a/hedgewars/options.inc Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/options.inc Wed Jul 31 23:14:27 2019 +0200
@@ -52,7 +52,7 @@
{$ENDIF}
-{$IFDEF WIN32}
+{$IFDEF WINDOWS}
{$DEFINE USE_CONTEXT_RESTORE}
{$ENDIF}
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/pas2cRedo.pas
--- a/hedgewars/pas2cRedo.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/pas2cRedo.pas Wed Jul 31 23:14:27 2019 +0200
@@ -48,6 +48,8 @@
Handle = integer;
+ TDateTime = double;
+
var
write, writeLn, read, readLn, flush, CreateDir: procedure;
@@ -84,7 +86,8 @@
min, max:function:integer;
assign, rewrite, rewrite_2, reset, reset_2, flush, BlockWrite, BlockRead, close : procedure;
- FileExists, DirectoryExists, eof : function : boolean;
+ FileSize : function: Int64;
+ FileExists, DirectoryExists, eof, DeleteFile : function : boolean;
ParamCount : function : integer;
ParamStr : function : string;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/pas2cSystem.pas
--- a/hedgewars/pas2cSystem.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/pas2cSystem.pas Wed Jul 31 23:14:27 2019 +0200
@@ -97,7 +97,8 @@
glGetError, glDeleteProgram, glDeleteBuffers,
glGenBuffers, glBufferData, glBindBuffer, glewInit,
glUniform4f, glDisableVertexAttribArray, glTexEnvi,
- glLoadMatrixf, glMultMatrixf, glGetFloatv: procedure;
+ glLoadMatrixf, glMultMatrixf, glGetFloatv,
+ glDrawBuffer, glReadBuffer: procedure;
GL_BGRA, GL_BLEND, GL_CLAMP_TO_EDGE, GL_COLOR_ARRAY,
GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_DEPTH_COMPONENT,
@@ -115,7 +116,7 @@
GL_INFO_LOG_LENGTH, GL_LINK_STATUS, GL_VERTEX_SHADER, GL_FRAGMENT_SHADER,
GL_NO_ERROR, GL_ARRAY_BUFFER, GL_STATIC_DRAW, GLEW_OK,
GL_AUX_BUFFERS, GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE, GL_ADD,
- GL_MODELVIEW_MATRIX: integer;
+ GL_MODELVIEW_MATRIX, GL_AUX0, GL_BACK: integer;
TThreadId : function : integer;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uAI.pas
--- a/hedgewars/uAI.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uAI.pas Wed Jul 31 23:14:27 2019 +0200
@@ -66,7 +66,6 @@
const cBranchStackSize = 12;
type TStackEntry = record
- WastedTicks: Longword;
MadeActions: TActions;
Hedgehog: TGear;
end;
@@ -76,14 +75,13 @@
States: array[0..Pred(cBranchStackSize)] of TStackEntry;
end;
-function Push(Ticks: Longword; const Actions: TActions; const Me: TGear; Dir: integer): boolean;
+function Push(const Actions: TActions; const Me: TGear; Dir: integer): boolean;
var bRes: boolean;
begin
bRes:= (Stack.Count < cBranchStackSize) and (Actions.Count < MAXACTIONS - 5);
if bRes then
with Stack.States[Stack.Count] do
begin
- WastedTicks:= Ticks;
MadeActions:= Actions;
Hedgehog:= Me;
Hedgehog.Message:= Dir;
@@ -92,12 +90,11 @@
Push:= bRes
end;
-procedure Pop(var Ticks: Longword; var Actions: TActions; var Me: TGear);
+procedure Pop(var Actions: TActions; var Me: TGear);
begin
dec(Stack.Count);
with Stack.States[Stack.Count] do
begin
- Ticks:= WastedTicks;
Actions:= MadeActions;
Me:= Hedgehog
end
@@ -253,15 +250,16 @@
procedure Walk(Me: PGear; var Actions: TActions);
const FallPixForBranching = cHHRadius;
var
- ticks, maxticks, oldticks, steps, tmp: Longword;
- BaseRate, BestRate, Rate: integer;
+ maxticks, oldticks, steps, tmp: Longword;
+ BaseRate, BestRate, Rate: LongInt;
GoInfo: TGoInfo;
CanGo: boolean;
AltMe: TGear;
BotLevel: Byte;
a: TAmmoType;
+ isAfterAttack: boolean;
begin
-ticks:= 0;
+Actions.ticks:= 0;
oldticks:= 0; // avoid compiler hint
Stack.Count:= 0;
@@ -272,12 +270,13 @@
BotLevel:= Me^.Hedgehog^.BotLevel;
-if (Me^.State and gstAttacked) = 0 then
- maxticks:= Max(0, TurnTimeLeft - 5000 - LongWord(4000 * BotLevel))
+isAfterAttack:= ((Me^.State and gstAttacked) <> 0) and ((GameFlags and gfInfAttack) = 0);
+if isAfterAttack then
+ maxticks:= Max(0, TurnTimeLeft - 500)
else
- maxticks:= TurnTimeLeft;
+ maxticks:= Max(0, TurnTimeLeft - 5000 - LongWord(4000 * BotLevel));
-if (Me^.State and gstAttacked) = 0 then
+if not isAfterAttack then
TestAmmos(Actions, Me, false);
BestRate:= RatePlace(Me);
@@ -291,12 +290,12 @@
and (CurrentHedgehog^.Effects[heArtillery] = 0) and (cGravityf <> 0) then
begin
tmp:= random(2) + 1;
- Push(0, Actions, Me^, tmp);
- Push(0, Actions, Me^, tmp xor 3);
+ Push(Actions, Me^, tmp);
+ Push(Actions, Me^, tmp xor 3);
while (Stack.Count > 0) and (not StopThinking) do
begin
- Pop(ticks, Actions, Me^);
+ Pop(Actions, Me^);
AddAction(Actions, Me^.Message, aim_push, 250, 0, 0);
if (Me^.Message and gmLeft) <> 0 then
@@ -311,10 +310,25 @@
{$HINTS OFF}
CanGo:= HHGo(Me, @AltMe, GoInfo);
{$HINTS ON}
- oldticks:= ticks;
- inc(ticks, GoInfo.Ticks);
- if ticks > maxticks then
+ oldticks:= Actions.ticks;
+ inc(Actions.ticks, GoInfo.Ticks);
+ if (Actions.ticks > maxticks) or (TurnTimeLeft < BestActions.ticks + 5000) then
+ begin
+ if (BotLevel < 5)
+ and (not isAfterAttack)
+ and (BestActions.Score > 0) // we have a good move
+ and (TurnTimeLeft < BestActions.ticks + 5000) // we won't have a lot of time after attack
+ and (HHHasAmmo(Me^.Hedgehog^, amExtraTime) > 0) // but can use extra time
+ then
+ begin
+ BestActions.Count:= 0;
+ AddAction(BestActions, aia_Weapon, Longword(amExtraTime), 80, 0, 0);
+ AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
+ AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
+ end;
+
break;
+ end;
if (BotLevel < 5)
and (GoInfo.JumpType = jmpHJump)
@@ -323,7 +337,7 @@
begin
// check if we could go backwards and maybe ljump over a gap after this hjump
addMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped);
- if Push(ticks, Actions, AltMe, Me^.Message xor 3) then
+ if Push(Actions, AltMe, Me^.Message xor 3) then
begin
with Stack.States[Pred(Stack.Count)] do
begin
@@ -336,7 +350,7 @@
AddAction(MadeActions, aia_HJump, 0, 350, 0, 0);
end;
// but first check walking forward
- Push(ticks, Stack.States[Pred(Stack.Count)].MadeActions, AltMe, Me^.Message)
+ Push(Stack.States[Pred(Stack.Count)].MadeActions, AltMe, Me^.Message)
end;
end;
if (BotLevel < 3)
@@ -346,7 +360,7 @@
begin
addMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped);
// at final check where we go after jump walking backward
- if Push(ticks, Actions, AltMe, Me^.Message xor 3) then
+ if Push(Actions, AltMe, Me^.Message xor 3) then
with Stack.States[Pred(Stack.Count)] do
begin
if (Me^.Message and gmLeft) <> 0 then
@@ -358,12 +372,13 @@
end;
// push current position so we proceed from it after checking jump+forward walk opportunities
- if CanGo then Push(ticks, Actions, Me^, Me^.Message);
+ if CanGo then Push(Actions, Me^, Me^.Message);
// first check where we go after jump walking forward
- if Push(ticks, Actions, AltMe, Me^.Message) then
+ if Push(Actions, AltMe, Me^.Message) then
with Stack.States[Pred(Stack.Count)] do
AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
+
break
end;
@@ -379,27 +394,22 @@
BestActions:= Actions;
BestActions.isWalkingToABetterPlace:= true;
BestRate:= Rate;
- Me^.State:= Me^.State or gstAttacked // we have better place, go there and do not use ammo
+ isAfterAttack:= true // we have better place, go there and do not use ammo
end
else if Rate < BestRate then
break;
- if ((Me^.State and gstAttacked) = 0) and ((steps mod 4) = 0) then
+ if (not isAfterAttack) and ((steps mod 4) = 0) then
begin
if (steps > 4) and checkMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere) then
break;
addMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere);
- TestAmmos(Actions, Me, ticks shr 12 = oldticks shr 12);
-
+ TestAmmos(Actions, Me, Actions.ticks shr 12 = oldticks shr 12);
end;
if GoInfo.FallPix >= FallPixForBranching then
- Push(ticks, Actions, Me^, Me^.Message xor 3); // aia_Left xor 3 = aia_Right
-
- if (StartTicks > GameTicks - 1500) and (not StopThinking) then
- SDL_Delay(1000);
-
+ Push(Actions, Me^, Me^.Message xor 3); // aia_Left xor 3 = aia_Right
end {while};
if BestRate > BaseRate then
@@ -463,8 +473,8 @@
or (itHedgehog = currHedgehogIndex)
or BestActions.isWalkingToABetterPlace;
- if (StartTicks > GameTicks - 1500) and (not StopThinking) then
- SDL_Delay(700);
+ if (StartTicks > GameTicks - 1500) and (not StopThinking) then
+ SDL_Delay(700);
if (BestActions.Score < -1023) and (not BestActions.isWalkingToABetterPlace) then
begin
@@ -474,7 +484,7 @@
// Hog has no idea what to do. Use tardis or skip
if not bonuses.activity then
- if (((GameFlags and gfInfAttack) <> 0) or (not isInMultiShoot)) and ((HHHasAmmo(Me^.Hedgehog^, amTardis) > 0)) and (CanUseTardis(Me^.Hedgehog^.Gear)) and (random(4) < 3) then
+ if (((GameFlags and gfInfAttack) <> 0) or (CurrentHedgehog^.MultiShootAttacks = 0)) and (HHHasAmmo(Me^.Hedgehog^, amTardis) > 0) and (CanUseTardis(Me^.Hedgehog^.Gear)) and (random(4) < 3) then
// Tardis brings hog to a random place. Perfect for clueless AI
begin
AddAction(BestActions, aia_Weapon, Longword(amTardis), 80, 0, 0);
@@ -544,7 +554,7 @@
exit
end;
-FillBonuses(((Me^.State and gstAttacked) <> 0) and (not isInMultiShoot));
+FillBonuses(((Me^.State and gstAttacked) <> 0) and (not isInMultiShoot) and ((GameFlags and gfInfAttack) = 0));
SDL_LockMutex(ThreadLock);
ThinkThread:= SDL_CreateThread(@Think, PChar('think'), Me);
@@ -561,7 +571,7 @@
with CurrentHedgehog^ do
if (Gear <> nil)
and ((Gear^.State and gstHHDriven) <> 0)
- and (TurnTimeLeft < cHedgehogTurnTime - 50) then
+ and ((TurnTimeLeft < cHedgehogTurnTime - 50) or (TurnTimeLeft > cHedgehogTurnTime)) then
if ((Gear^.State and gstHHThinking) = 0) then
if (BestActions.Pos >= BestActions.Count)
and (TurnTimeLeft > cStopThinkTime) then
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uAIActions.pas
--- a/hedgewars/uAIActions.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uAIActions.pas Wed Jul 31 23:14:27 2019 +0200
@@ -58,6 +58,7 @@
TActions = record
Count, Pos: Longword;
+ ticks: Longword;
actions: array[0..Pred(MAXACTIONS)] of TAction;
Score: LongInt;
isWalkingToABetterPlace: boolean;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uAIAmmoTests.pas
--- a/hedgewars/uAIAmmoTests.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uAIAmmoTests.pas Wed Jul 31 23:14:27 2019 +0200
@@ -119,13 +119,12 @@
(proc: nil; flags: 0), // amDrillStrike
(proc: nil; flags: 0), // amSnowball
(proc: nil; flags: 0), // amTardis
- //(proc: nil; flags: 0), // amStructure
(proc: nil; flags: 0), // amLandGun
(proc: nil; flags: 0), // amIceGun
(proc: nil; flags: 0), // amKnife
(proc: nil; flags: 0), // amRubber
(proc: nil; flags: 0), // amAirMine
- (proc: nil; flags: 0), // amDuck
+ (proc: nil; flags: 0), // amCreeper
(proc: @TestShotgun; flags: 0) // amMinigun
);
@@ -138,11 +137,12 @@
end;
function TestBazooka(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt;
+const cExtraTime = 300;
var Vx, Vy, r, mX, mY: real;
rTime: LongInt;
EX, EY: LongInt;
valueResult: LongInt;
- x, y, dX, dY: real;
+ targXWrap, x, y, dX, dY: real;
t: LongInt;
value: LongInt;
begin
@@ -151,10 +151,16 @@
ap.Time:= 0;
rTime:= 350;
ap.ExplR:= 0;
+if (WorldEdge = weWrap) then
+ if (Targ.Point.X < mX) then
+ targXWrap:= Targ.Point.X + (RightX-LeftX)
+ else targXWrap:= Targ.Point.X - (RightX-LeftX);
valueResult:= BadTurn;
repeat
rTime:= rTime + 300 + Level * 50 + random(300);
- Vx:= - windSpeed * rTime * 0.5 + (Targ.Point.X + AIrndSign(2) - mX) / rTime;
+ if (WorldEdge = weWrap) and (random(2)=0) then
+ Vx:= - windSpeed * rTime * 0.5 + (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - mX) / rTime
+ else Vx:= - windSpeed * rTime * 0.5 + (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - mX) / rTime;
Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - mY) / rTime;
r:= sqr(Vx) + sqr(Vy);
if not (r > 1) then
@@ -165,24 +171,34 @@
dY:= -Vy;
t:= rTime;
repeat
+ x:= CheckWrap(x);
x:= x + dX;
+
y:= y + dY;
dX:= dX + windSpeed;
+ //dX:= CheckBounce(x,dX);
dY:= dY + cGravityf;
dec(t)
until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 5)) or
- ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 5))) or (t <= 0);
+ ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 5))) or (t < -cExtraTime);
EX:= trunc(x);
EY:= trunc(y);
- if Level = 1 then
- value:= RateExplosion(Me, EX, EY, 101, afTrackFall or afErasesLand)
- else value:= RateExplosion(Me, EX, EY, 101);
+ if t >= -cExtraTime then
+ begin
+ if Level = 1 then
+ value:= RateExplosion(Me, EX, EY, 101, afTrackFall or afErasesLand)
+ else
+ value:= RateExplosion(Me, EX, EY, 101);
+ end else
+ value:= BadTurn;
+
if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then
if GameFlags and gfSolidLand = 0 then
value := 1024 - Metric(Targ.Point.X, Targ.Point.Y, EX, EY) div 64
else value := BadTurn;
- if valueResult <= value then
+
+ if (valueResult < value) or ((valueResult = value) and (Level < 3)) then
begin
ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9));
ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1);
@@ -192,7 +208,6 @@
valueResult:= value
end;
end
-//until (value > 204800) or (rTime > 4250); not so useful since adding score to the drowning
until rTime > 5050 - Level * 800;
TestBazooka:= valueResult
end;
@@ -310,7 +325,7 @@
rTime: LongInt;
EX, EY: LongInt;
valueResult: LongInt;
- x, y, dX, dY: real;
+ targXWrap, x, y, dX, dY: real;
t: LongInt;
value: LongInt;
t2: real;
@@ -324,10 +339,16 @@
rTime:= 350;
ap.ExplR:= 0;
valueResult:= BadTurn;
+ if (WorldEdge = weWrap) then
+ if (Targ.Point.X < mX) then
+ targXWrap:= Targ.Point.X + (RightX-LeftX)
+ else targXWrap:= Targ.Point.X - (RightX-LeftX);
timer:= 0;
repeat
rTime:= rTime + 300 + Level * 50 + random(300);
- Vx:= - windSpeed * rTime * 0.5 + (Targ.Point.X + AIrndSign(2) - mX) / rTime;
+ if (WorldEdge = weWrap) and (random(2)=0) then
+ Vx:= - windSpeed * rTime * 0.5 + (targXWrap + AIrndSign(2) - mX) / rTime
+ else Vx:= - windSpeed * rTime * 0.5 + (Targ.Point.X + AIrndSign(2) - mX) / rTime;
Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y - 35 - mY) / rTime;
r:= sqr(Vx) + sqr(Vy);
if not (r > 1) then
@@ -338,6 +359,7 @@
dY:= -Vy;
t:= rTime;
repeat
+ x:= CheckWrap(x);
x:= x + dX;
y:= y + dY;
dX:= dX + windSpeed;
@@ -391,7 +413,7 @@
rTime: LongInt;
EX, EY: LongInt;
valueResult: LongInt;
- x, y, dX, dY, meX, meY: real;
+ targXWrap, x, y, dX, dY, meX, meY: real;
t: LongInt;
value: LongInt;
@@ -402,9 +424,15 @@
rTime:= 350;
ap.ExplR:= 0;
valueResult:= BadTurn;
+if (WorldEdge = weWrap) then
+ if (Targ.Point.X < meX) then
+ targXWrap:= Targ.Point.X + (RightX-LeftX)
+ else targXWrap:= Targ.Point.X - (RightX-LeftX);
repeat
rTime:= rTime + 300 + Level * 50 + random(1000);
- Vx:= - windSpeed * rTime * 0.5 + ((Targ.Point.X + AIrndSign(2)) - meX) / rTime;
+ if (WorldEdge = weWrap) and (random(2)=0) then
+ Vx:= - windSpeed * rTime * 0.5 + ((targXWrap + AIrndSign(2)) - meX) / rTime
+ else Vx:= - windSpeed * rTime * 0.5 + ((Targ.Point.X + AIrndSign(2)) - meX) / rTime;
Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y - meY) / rTime;
r:= sqr(Vx) + sqr(Vy);
if not (r > 1) then
@@ -415,6 +443,7 @@
dY:= -Vy;
t:= rTime;
repeat
+ x:= CheckWrap(x);
x:= x + dX;
y:= y + dY;
dX:= dX + windSpeed;
@@ -448,7 +477,7 @@
var Vx, Vy, r: real;
Score, EX, EY, valueResult: LongInt;
TestTime: LongInt;
- x, y, dY, meX, meY: real;
+ targXWrap, x, y, dY, meX, meY: real;
t: LongInt;
begin
meX:= hwFloat2Float(Me^.X);
@@ -456,9 +485,15 @@
valueResult:= BadTurn;
TestTime:= 0;
ap.ExplR:= 0;
+if (WorldEdge = weWrap) then
+ if (Targ.Point.X < meX) then
+ targXWrap:= Targ.Point.X + (RightX-LeftX)
+ else targXWrap:= Targ.Point.X - (RightX-LeftX);
repeat
inc(TestTime, 300);
- Vx:= (Targ.Point.X - meX) / TestTime;
+ if (WorldEdge = weWrap) and (random(2)=0) then
+ Vx:= (targXWrap - meX) / TestTime
+ else Vx:= (Targ.Point.X - meX) / TestTime;
Vy:= cGravityf * (TestTime div 2) - Targ.Point.Y - meY / TestTime;
r:= sqr(Vx) + sqr(Vy);
if not (r > 1) then
@@ -468,6 +503,7 @@
dY:= -Vy;
t:= TestTime;
repeat
+ x:= CheckWrap(x);
x:= x + Vx;
y:= y + dY;
dY:= dY + cGravityf;
@@ -500,7 +536,7 @@
var Vx, Vy, r: real;
Score, EX, EY, valueResult: LongInt;
TestTime: LongInt;
- x, y, meX, meY, dY: real;
+ targXWrap, x, y, meX, meY, dY: real;
t: LongInt;
begin
valueResult:= BadTurn;
@@ -508,9 +544,15 @@
ap.ExplR:= 0;
meX:= hwFloat2Float(Me^.X);
meY:= hwFloat2Float(Me^.Y);
+if (WorldEdge = weWrap) then
+ if (Targ.Point.X < meX) then
+ targXWrap:= Targ.Point.X + (RightX-LeftX)
+ else targXWrap:= Targ.Point.X - (RightX-LeftX);
repeat
inc(TestTime, 1000);
- Vx:= (Targ.Point.X - meX) / (TestTime + tDelta);
+ if (WorldEdge = weWrap) and (random(2)=0) then
+ Vx:= (targXWrap + AIrndOffset(Targ, Level) - meX) / (TestTime + tDelta)
+ else Vx:= (Targ.Point.X + AIrndOffset(Targ, Level) - meX) / (TestTime + tDelta);
Vy:= cGravityf * ((TestTime + tDelta) div 2) - (Targ.Point.Y - meY) / (TestTime + tDelta);
r:= sqr(Vx) + sqr(Vy);
if not (r > 1) then
@@ -520,6 +562,7 @@
dY:= -Vy;
t:= TestTime;
repeat
+ x:= CheckWrap(x);
x:= x + Vx;
y:= y + dY;
dY:= dY + cGravityf;
@@ -613,7 +656,7 @@
var Vx, Vy, r: real;
Score, EX, EY, valueResult: LongInt;
TestTime: Longword;
- x, y, dY, meX, meY: real;
+ targXWrap, x, y, dY, meX, meY: real;
t: LongInt;
begin
valueResult:= BadTurn;
@@ -621,9 +664,15 @@
ap.ExplR:= 0;
meX:= hwFloat2Float(Me^.X);
meY:= hwFloat2Float(Me^.Y);
+if (WorldEdge = weWrap) then
+ if (Targ.Point.X < meX) then
+ targXWrap:= Targ.Point.X + (RightX-LeftX)
+ else targXWrap:= Targ.Point.X - (RightX-LeftX);
repeat
inc(TestTime, 900);
- Vx:= (Targ.Point.X - meX) / (TestTime + tDelta);
+ if (WorldEdge = weWrap) and (random(2)=0) then
+ Vx:= (targXWrap - meX) / (TestTime + tDelta)
+ else Vx:= (Targ.Point.X - meX) / (TestTime + tDelta);
Vy:= cGravityf * ((TestTime + tDelta) div 2) - ((Targ.Point.Y-50) - meY) / (TestTime + tDelta);
r:= sqr(Vx)+sqr(Vy);
if not (r > 1) then
@@ -633,6 +682,7 @@
dY:= -Vy;
t:= TestTime;
repeat
+ x:= CheckWrap(x);
x:= x + Vx;
y:= y + dY;
dY:= dY + cGravityf;
@@ -1085,7 +1135,7 @@
if d < 10 then
begin
dx:= 0;
- dy:= 8;
+ dy:= step;
ap.Angle:= 2048
end
else
@@ -1099,17 +1149,18 @@
if dx >= 0 then cx:= 0.45 else cx:= -0.45;
- for i:= 0 to 512 div step - 2 do
+ for i:= 1 to 512 div step - 2 do
begin
+ x:= x + dx;
+ y:= y + dy;
+
valueResult:= valueResult +
RateShove(Me, trunc(x), trunc(y)
, 30, 30, 25
, cx, -0.9, trackFall or afSetSkip);
+ end;
- x:= x + dx;
- y:= y + dy;
- end;
- if dx = 0 then
+ if (d < 10) and (dx = 0) then
begin
x:= hwFloat2Float(Me^.X);
y:= hwFloat2Float(Me^.Y);
@@ -1126,8 +1177,10 @@
, -cx, -0.9, trackFall or afSetSkip);
end
end;
+
if v > valueResult then
- begin
+ begin
+ cx:= -cx;
ap.Angle:= -2048;
valueResult:= v
end;
@@ -1209,20 +1262,23 @@
end;
until fexit or (Y > cWaterLine);
-for i:= 0 to 5 do inc(valueResult, dmg[i]);
+for i:= 0 to 5 do
+ if dmg[i] <> BadTurn then
+ inc(valueResult, dmg[i]);
t:= valueResult;
ap.AttackPutX:= Targ.Point.X - 60;
for i:= 0 to 3 do
- begin
- dec(t, dmg[i]);
- inc(t, dmg[i + 6]);
- if t > valueResult then
+ if dmg[i] <> BadTurn then
begin
- valueResult:= t;
- ap.AttackPutX:= Targ.Point.X - 30 - cShift + i * 30
- end
- end;
+ dec(t, dmg[i]);
+ inc(t, dmg[i + 6]);
+ if t > valueResult then
+ begin
+ valueResult:= t;
+ ap.AttackPutX:= Targ.Point.X - 30 - cShift + i * 30
+ end
+ end;
if valueResult <= 0 then
valueResult:= BadTurn;
@@ -1309,7 +1365,7 @@
//FillChar(cake, sizeof(cake), 0);
cake.Radius:= 7;
- cake.CollisionMask:= lfNotCurrentMask;
+ cake.CollisionMask:= lfNotCurHogCrate;
cake.Hedgehog:= Me^.Hedgehog;
// check left direction
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uAIMisc.pas
--- a/hedgewars/uAIMisc.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uAIMisc.pas Wed Jul 31 23:14:27 2019 +0200
@@ -76,6 +76,7 @@
procedure AwareOfExplosion(x, y, r: LongInt); inline;
function RatePlace(Gear: PGear): LongInt;
+function CheckWrap(x: real): real; inline;
function TestColl(x, y, r: LongInt): boolean; inline;
function TestCollExcludingObjects(x, y, r: LongInt): boolean; inline;
function TestCollExcludingMe(Me: PGear; x, y, r: LongInt): boolean; inline;
@@ -88,7 +89,8 @@
function RateHammer(Me: PGear): LongInt;
function HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean;
-function AIrndSign(num: LongInt): LongInt;
+function AIrndSign(num: LongInt): LongInt; inline;
+function AIrndOffset(targ: TTarget; Level: LongWord): LongInt; inline;
var ThinkingHH: PGear;
Targets: TTargets;
@@ -227,9 +229,11 @@
, gtAirBomb
, gtCluster
, gtMelonPiece
+ , gtBee
, gtMolotov: bonuses.activity:= true;
gtCase:
- AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y) + 3, 37, 25);
+ if (Gear^.AIHints and aihDoesntMatter) = 0 then
+ AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y) + 3, 37, 25);
gtFlame:
if (Gear^.State and gsttmpFlag) <> 0 then
AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 20, -50);
@@ -237,12 +241,13 @@
gtMine: begin
if (Gear^.State and gstMoving) <> 0 then bonuses.activity:= true;
- if ((Gear^.State and gstAttacking) = 0) and (((cMineDudPercent < 90) and (Gear^.Health <> 0))
+ if ((Gear^.State and gstAttacking) = 0) and (((cMineDudPercent < 90) or ((Gear^.State and gstWait) <> 0) and (Gear^.Health <> 0))
or (isAfterAttack and (Gear^.Health = 0) and (Gear^.Damage > 30))) then
AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 50, -50)
else if (Gear^.State and gstAttacking) <> 0 then
AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 100, -50); // mine is on
end;
+ gtAirMine: if ((Gear^.State and gstFrozen) = 0) then AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), gear^.Angle+5, -30);
gtExplosives:
begin
@@ -330,6 +335,16 @@
RatePlace:= rate;
end;
+function CheckWrap(x: real): real; inline;
+begin
+ if WorldEdge = weWrap then
+ if (x < leftX) then
+ x:= x + (rightX - leftX)
+ else if x > rightX then
+ x:= x - (rightX - leftX);
+ CheckWrap:= x;
+end;
+
function CheckBounds(x, y, r: Longint): boolean; inline;
begin
CheckBounds := (((x-r) and LAND_WIDTH_MASK) = 0) and
@@ -372,10 +387,10 @@
if not CheckBounds(x, y, r) then
exit(false);
- if (Land[y-r, x-r] and lfNotCurrentMask <> 0) or
- (Land[y+r, x-r] and lfNotCurrentMask <> 0) or
- (Land[y+r, x-r] and lfNotCurrentMask <> 0) or
- (Land[y+r, x+r] and lfNotCurrentMask <> 0) then
+ if (Land[y-r, x-r] and lfNotCurHogCrate <> 0) or
+ (Land[y+r, x-r] and lfNotCurHogCrate <> 0) or
+ (Land[y+r, x-r] and lfNotCurHogCrate <> 0) or
+ (Land[y+r, x+r] and lfNotCurHogCrate <> 0) then
exit(true);
TestColl:= false;
@@ -412,6 +427,7 @@
rCorner:= r * 0.75;
while true do
begin
+ x:= CheckWrap(x);
x:= x + dX;
y:= y + dY;
dY:= dY + cGravityf;
@@ -458,6 +474,7 @@
//v:= random($FFFFFFFF);
while true do
begin
+ x:= CheckWrap(x);
x:= x + dX;
y:= y + dY;
dY:= dY + cGravityf;
@@ -519,6 +536,7 @@
pX, pY, dX, dY: real;
hadSkips: boolean;
begin
+x:= round(CheckWrap(real(x)));
fallDmg:= 0;
rate:= 0;
// add our virtual position
@@ -615,20 +633,22 @@
end
end;
-if hadSkips and (rate = 0) then
+if hadSkips and (rate <= 0) then
RealRateExplosion:= BadTurn
- else
+else
RealRateExplosion:= rate;
end;
function RateShove(Me: PGear; x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt;
var i, fallDmg, dmg, rate, subrate: LongInt;
dX, dY, pX, pY: real;
+ hadSkips: boolean;
begin
fallDmg:= 0;
dX:= gdX * 0.01 * kick;
dY:= gdY * 0.01 * kick;
rate:= 0;
+hadSkips:= false;
for i:= 0 to Pred(Targets.Count) do
with Targets.ar[i] do
if skip then
@@ -695,8 +715,14 @@
if abs(subrate) > 2000 then inc(Rate,subrate div 1024);
end
end
- end;
-RateShove:= rate * 1024;
+ end
+ else
+ hadSkips:= true;
+
+if hadSkips and (rate <= 0) then
+ RateShove:= BadTurn
+else
+ RateShove:= rate * 1024;
ResetTargets
end;
@@ -799,25 +825,28 @@
end
end;
-if hadSkips and (rate = 0) then
+if hadSkips and (rate <= 0) then
RateShotgun:= BadTurn
- else
+else
RateShotgun:= rate * 1024;
- ResetTargets;
+ResetTargets;
end;
function RateHammer(Me: PGear): LongInt;
var x, y, i, r, rate: LongInt;
+ hadSkips: boolean;
begin
// hammer hit shift against attecker hog is 10
x:= hwRound(Me^.X) + hwSign(Me^.dX) * 10;
y:= hwRound(Me^.Y);
rate:= 0;
-
+hadSkips:= false;
for i:= 0 to Pred(Targets.Count) do
with Targets.ar[i] do
// hammer hit radius is 8, shift is 10
- if matters and (Kind = gtHedgehog) and (abs(Point.x - x) + abs(Point.y - y) < 18) then
+ if (not matters) then
+ hadSkips:= true
+ else if matters and (Kind = gtHedgehog) and (abs(Point.x - x) + abs(Point.y - y) < 18) then
begin
r:= trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y)));
@@ -827,7 +856,11 @@
else
inc(rate, Score div 3 * friendlyfactor div 100)
end;
-RateHammer:= rate * 1024;
+
+if hadSkips and (rate <= 0) then
+ RateHammer:= BadTurn
+else
+ RateHammer:= rate * 1024;
end;
function HHJump(Gear: PGear; JumpType: TJumpType; var GoInfo: TGoInfo): boolean;
@@ -927,7 +960,7 @@
var pX, pY, tY: LongInt;
begin
HHGo:= false;
-Gear^.CollisionMask:= lfNotCurrentMask;
+Gear^.CollisionMask:= lfNotCurHogCrate;
AltGear^:= Gear^;
GoInfo.Ticks:= 0;
@@ -999,7 +1032,7 @@
HHJump(AltGear, jmpHJump, GoInfo);
end;
-function AIrndSign(num: LongInt): LongInt;
+function AIrndSign(num: LongInt): LongInt; inline;
begin
if random(2) = 0 then
AIrndSign:= num
@@ -1007,6 +1040,14 @@
AIrndSign:= - num
end;
+function AIrndOffset(targ: TTarget; Level: LongWord): LongInt; inline;
+begin
+if Level <> 1 then exit(0);
+// at present level 2 doesn't track falls on most things
+//if Level = 2 then exit(round(targ.Radius*(random(5)-2)/2));
+AIrndOffset := targ.Radius*(random(7)-3)*2
+end;
+
procedure initModule;
begin
friendlyfactor:= 300;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uAmmos.pas
--- a/hedgewars/uAmmos.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uAmmos.pas Wed Jul 31 23:14:27 2019 +0200
@@ -52,11 +52,10 @@
implementation
uses uVariables, uCommands, uUtils, uCaptions, uDebug, uScript;
-type TAmmoCounts = array[TAmmoType] of Longword;
- TAmmoArray = array[TAmmoType] of TAmmo;
+type TAmmoArray = array[TAmmoType] of TAmmo;
var StoresList: array[0..Pred(cMaxHHs)] of PHHAmmo;
ammoLoadout, ammoProbability, ammoDelay, ammoReinforcement: shortstring;
- InitialCounts: array[0..Pred(cMaxHHs)] of TAmmoCounts;
+ InitialCountsLocal: array[0..Pred(cMaxHHs)] of TAmmoCounts;
procedure FillAmmoStore(Ammo: PHHAmmo; var newAmmo: TAmmoArray);
var mi: array[0..cMaxSlotIndex] of byte;
@@ -79,7 +78,6 @@
end;
procedure AddAmmoStore;
-const probability: array [0..8] of LongWord = (0,20,30,60,100,200,400,600,800);
var cnt: Longword;
a: TAmmoType;
ammos: TAmmoCounts;
@@ -99,7 +97,7 @@
begin
if a <> amNothing then
begin
- Ammoz[a].Probability:= probability[byte(ammoProbability[ord(a)]) - byte('0')];
+ Ammoz[a].Probability:= probabilityLevels[byte(ammoProbability[ord(a)]) - byte('0')];
Ammoz[a].SkipTurns:= (byte(ammoDelay[ord(a)]) - byte('0'));
Ammoz[a].NumberInCase:= (byte(ammoReinforcement[ord(a)]) - byte('0'));
cnt:= byte(ammoLoadout[ord(a)]) - byte('0');
@@ -118,31 +116,42 @@
or ((a = amInvulnerable) and ((GameFlags and gfInvulnerable) <> 0))
or ((a = amLaserSight) and ((GameFlags and gfLaserSight) <> 0))
or ((a = amVampiric) and ((GameFlags and gfVampiric) <> 0))
- or ((a = amExtraTime) and (cHedgehogTurnTime >= 1000000)) then
+ or ((a = amExtraTime) and (cHedgehogTurnTime >= 1000000))
+ // Always remove creeper because it's unfinished.
+ // TODO: Re-enable creeper when creeper is done
+ or (a = amCreeper) then
begin
cnt:= 0;
Ammoz[a].Probability:= 0
end;
ammos[a]:= cnt;
- if ((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)
- and (Ammoz[a].SkipTurns = 0) and (a <> amTeleport) and (a <> amSkip) then
- Ammoz[a].SkipTurns:= 1;
-
- if ((GameFlags and gfPlaceHog) <> 0)
+ if (((GameFlags and gfPlaceHog) <> 0)
+ or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
and (a <> amTeleport) and (a <> amSkip)
and (Ammoz[a].SkipTurns < 10000) then
inc(Ammoz[a].SkipTurns,10000);
- if ((GameFlags and gfPlaceHog) <> 0) and (a = amTeleport) then
+ if (((GameFlags and gfPlaceHog) <> 0)
+ or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
+ and (a = amTeleport) then
ammos[a]:= AMMO_INFINITE
end
else
ammos[a]:= AMMO_INFINITE;
- if ((GameFlags and gfPlaceHog) <> 0) and (a = amTeleport) then
- InitialCounts[Pred(StoreCnt)][a]:= cnt
+
+ if (((GameFlags and gfPlaceHog) <> 0)
+ or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
+ and (a = amTeleport) then
+ begin
+ InitialCountsLocal[Pred(StoreCnt)][a]:= cnt;
+ InitialAmmoCounts[a]:= cnt;
+ end
else
- InitialCounts[Pred(StoreCnt)][a]:= ammos[a];
+ begin
+ InitialCountsLocal[Pred(StoreCnt)][a]:= ammos[a];
+ InitialAmmoCounts[a]:= ammos[a];
+ end
end;
for a:= Low(TAmmoType) to High(TAmmoType) do
@@ -156,7 +165,7 @@
function GetAmmoByNum(num: LongInt): PHHAmmo;
begin
- if checkFails(num < StoreCnt, 'Invalid store number', true) then
+ if checkFails(num < StoreCnt, 'Invalid ammo store number', true) then
GetAmmoByNum:= nil
else
GetAmmoByNum:= StoresList[num]
@@ -395,7 +404,9 @@
CurWeapon:= GetCurAmmoEntry(Hedgehog);
OldWeapon:= GetCurAmmoEntry(Hedgehog);
- if (CurWeapon^.Count = 0) then
+ if (Hedgehog.Gear^.State and gstHHDriven) = 0 then
+ Hedgehog.CurAmmoType:= amNothing
+ else if (CurWeapon^.Count = 0) then
SwitchToFirstLegalAmmo(Hedgehog)
else if CurWeapon^.AmmoType = amNothing then
Hedgehog.CurAmmoType:= amNothing;
@@ -418,7 +429,8 @@
s:= s + ansistring(' (' + IntToStr(Count) + ')');
if (Propz and ammoprop_Timerable) <> 0 then
s:= s + ansistring(', ' + IntToStr(Timer div 1000) + ' ') + trammo[sidSeconds];
- AddCaption(s, Team^.Clan^.Color, capgrpAmmoinfo);
+ if (Hedgehog.Gear^.State and gstHHDriven) <> 0 then
+ AddCaption(s, Team^.Clan^.Color, capgrpAmmoinfo);
if (Propz and ammoprop_NeedTarget) <> 0 then
begin
if Gear <> nil then Gear^.State:= Gear^.State or gstChooseTarget;
@@ -458,7 +470,7 @@
if (Propz and ammoprop_NotBorder) <> 0 then
begin
Count:= 0;
- InitialCounts[i][AmmoType]:= 0
+ InitialCountsLocal[i][AmmoType]:= 0
end;
PackAmmo(StoresList[i], slot)
@@ -489,7 +501,7 @@
ammoReinforcement:= s;
end;
-// Restore indefinitely disabled weapons and initial weapon counts. Only used for hog placement right now
+// Restore indefinitely disabled weapons and initial weapon counts.
procedure ResetWeapons;
var i, t: Longword;
a: TAmmoType;
@@ -506,7 +518,7 @@
for i:= 0 to Pred(StoreCnt) do
begin
for a:= Low(TAmmoType) to High(TAmmoType) do
- newAmmos[a].Count:= InitialCounts[i][a];
+ newAmmos[a].Count:= InitialCountsLocal[i][a];
FillAmmoStore(StoresList[i], newAmmos);
end;
@@ -546,7 +558,7 @@
ammoDelay:= ammoDelay + '0';
ammoReinforcement:= ammoReinforcement + '0'
end;
- FillChar(InitialCounts, sizeof(InitialCounts), 0)
+ FillChar(InitialCountsLocal, sizeof(InitialCountsLocal), 0)
end;
procedure freeModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uChat.pas
--- a/hedgewars/uChat.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uChat.pas Wed Jul 31 23:14:27 2019 +0200
@@ -35,7 +35,8 @@
procedure TextInput(var event: TSDL_TextInputEvent);
implementation
-uses uConsts, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO, uScript, uRenderUtils, uStore;
+uses uConsts, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO, uScript, uRenderUtils, uStore, uLocale
+ {$IFDEF USE_VIDEO_RECORDING}, uVideoRec{$ENDIF};
const MaxStrIndex = 27;
MaxInputStrLen = 200;
@@ -71,7 +72,7 @@
const
colors: array[#0..#9] of TSDL_Color = (
- (r:$FF; g:$FF; b:$FF; a:$FF), // #0 unused, feel free to take it for anything
+ (r:$FF; g:$FF; b:$00; a:$FF), // #0 warning message [Yellow]
(r:$FF; g:$FF; b:$FF; a:$FF), // #1 chat message [White]
(r:$FF; g:$00; b:$FF; a:$FF), // #2 action message [Purple]
(r:$90; g:$FF; b:$90; a:$FF), // #3 join/leave message [Lime]
@@ -387,8 +388,13 @@
visibleCount:= 0;
// draw chat lines with some distance from screen border
+{$IFDEF USE_TOUCH_INTERFACE}
+left:= 4 - cScreenWidth div 2;
+top := 55 + visibleCount * ClHeight; // we start with input line (if any)
+{$ELSE}
left:= 4 - cScreenWidth div 2;
top := 10 + visibleCount * ClHeight; // we start with input line (if any)
+{$ENDIF}
// draw chat input line first and under all other lines
if isInChatMode and (InputStr.Tex <> nil) then
@@ -449,7 +455,7 @@
// default to current hedgehog (if own) or first hedgehog
if SpeechHogNumber = 0 then
begin
- if not CurrentTeam^.ExtDriven then
+ if (not CurrentTeam^.ExtDriven) and (not CurrentHedgehog^.Unplaced) then
SpeechHogNumber:= CurrentTeam^.CurrHedgehog + 1
else
SpeechHogNumber:= 1;
@@ -525,12 +531,15 @@
t:= LocalTeam;
x:= 0;
+// speech bubble
if (s[1] = '"') and (s[Length(s)] = '"')
then x:= 1
+// thinking bubble
else if (s[1] = '''') and (s[Length(s)] = '''') then
x:= 2
+// yelling bubble
else if (s[1] = '-') and (s[Length(s)] = '-') then
x:= 3;
@@ -550,7 +559,22 @@
if (s[1] = '/') then
begin
- // These 3 are same as above, only are to make the hedgehog say it on next attack
+ if (Length(s) <= 1) then
+ begin
+ // empty chat command
+ AddChatString(#0 + shortstring(trcmd[sidCmdUnknown]));
+ exit;
+ end;
+
+ // Ignore message-type commands with empty argument list
+ if (copy(s, 2, 2) = 'me') and (Length(s) = 3) then
+ exit;
+ if ((copy(s, 2, 3) = 'hsa') or (copy(s, 2, 3) = 'hta') or (copy(s, 2, 3) = 'hya')) and (Length(s) = 4) then
+ exit;
+ if ((copy(s, 2, 4) = 'team') or (copy(s, 2, 4) = 'clan')) and (Length(s) = 5) then
+ exit;
+
+ // Speech bubble, but on next attack
if (copy(s, 2, 4) = 'hsa ') then
begin
if CurrentTeam^.ExtDriven then
@@ -560,6 +584,7 @@
exit
end;
+ // Thinking bubble, but on next attack
if (copy(s, 2, 4) = 'hta ') then
begin
if CurrentTeam^.ExtDriven then
@@ -569,6 +594,7 @@
exit
end;
+ // Yelling bubble, but on next attack
if (copy(s, 2, 4) = 'hya ') then
begin
if CurrentTeam^.ExtDriven then
@@ -578,9 +604,11 @@
exit
end;
- if (copy(s, 2, 5) = 'team ') and (length(s) > 6) then
+ // "/clan" or "/team" ("/team" is an alias for "/clan")
+ if ((copy(s, 2, 5) = 'clan ') or (copy(s, 2, 5) = 'team ')) then
begin
- ParseCommand(s, true);
+ if (Length(s) > 6) then
+ ParseCommand('team ' + copy(s, 7, Length(s) - 6), true);
exit
end;
@@ -600,6 +628,7 @@
// debugging commands
if (copy(s, 2, 7) = 'debugvl') then
+ // This command intentionally not documented in /help
begin
cViewLimitsDebug:= (not cViewLimitsDebug);
UpdateViewLimits();
@@ -608,22 +637,85 @@
if (copy(s, 2, 3) = 'lua') then
begin
+ LuaCmdUsed:= true;
AddFileLog('/lua issued');
+{$IFDEF USE_VIDEO_RECORDING}
+ if flagPrerecording then
+ begin
+ AddFileLog('Force-stopping prerecording! Lua commands can not be recorded');
+ StopPreRecording;
+ end;
+{$ENDIF}
if gameType <> gmtNet then
begin
liveLua:= (not liveLua);
if liveLua then
begin
AddFileLog('[Lua] chat input string parsing enabled');
- AddChatString(#3 + 'Lua parsing: ON');
+ AddChatString(#3 + shortstring(trmsg[sidLuaParsingOn]));
end
else
begin
AddFileLog('[Lua] chat input string parsing disabled');
- AddChatString(#3 + 'Lua parsing: OFF');
+ AddChatString(#3 + shortstring(trmsg[sidLuaParsingOff]));
end;
UpdateInputLinePrefix();
- end;
+ end
+ else
+ AddChatString(#5 + shortstring(trmsg[sidLuaParsingDenied]));
+ exit
+ end;
+
+ // Help commands
+ if (copy(s, 2, 11) = 'help taunts') then
+ begin
+ AddChatString(#3 + shortstring(trcmd[sidCmdHeaderTaunts]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdSpeech]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdThink]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdYell]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdSpeechNumberHint]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHsa]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHta]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHya]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHurrah]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdIlovelotsoflemonade]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdJuggle]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdRollup]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdShrug]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdWave]));
+ exit
+ end;
+
+ if (copy(s, 2, 9) = 'help room') then
+ begin
+ if (gameType = gmtNet) then
+ SendConsoleCommand('/help')
+ else
+ AddChatString(#0 + shortstring(trcmd[sidCmdHelpRoomFail]));
+ exit;
+ end;
+
+ if (copy(s, 2, 4) = 'help') then
+ begin
+ AddChatString(#3 + shortstring(trcmd[sidCmdHeaderBasic]));
+ if gameType = gmtNet then
+ AddChatString(#3 + shortstring(trcmd[sidCmdPauseNet]))
+ else
+ AddChatString(#3 + shortstring(trcmd[sidCmdPause]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdFullscreen]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdQuit]));
+ if gameType <> gmtNet then
+ AddChatString(#3 + shortstring(trcmd[sidLua]));
+ // history and help commands needs to be close to the end because they are always visible
+ // with a short chat history length.
+ AddChatString(#3 + shortstring(trcmd[sidCmdTeam]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdMe]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdTogglechat]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHistory]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHelp]));
+ AddChatString(#3 + shortstring(trcmd[sidCmdHelpTaunts]));
+ if gameType = gmtNet then
+ AddChatString(#3 + shortstring(trcmd[sidCmdHelpRoom]));
exit
end;
@@ -647,6 +739,8 @@
if (gameType = gmtNet) then
SendConsoleCommand(s)
+ else
+ AddChatString(#0 + shortstring(trcmd[sidCmdUnknown]));
end
else
begin
@@ -1192,7 +1286,7 @@
if copy(s, 1, 4) = '/me ' then
s:= #2 + '* ' + UserNick + ' ' + copy(s, 5, Length(s) - 4)
else
- s:= #1 + UserNick + ': ' + s;
+ s:= #1 + Format(shortstring(trmsg[sidChat]), UserNick, s);
AddChatString(s)
end;
@@ -1201,7 +1295,7 @@
begin
SendIPC('b' + s);
- s:= #4 + '[Team] ' + UserNick + ': ' + s;
+ s:= #4 + Format(shortstring(trmsg[sidChatTeam]), UserNick, s);
AddChatString(s)
end;
@@ -1239,7 +1333,7 @@
SetLine(InputStr, '', true)
else
begin
- SetLine(InputStr, '/team ', true);
+ SetLine(InputStr, '/clan ', true);
cursorPos:= 6;
UpdateCursorCoords();
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uCollisions.pas
--- a/hedgewars/uCollisions.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uCollisions.pas Wed Jul 31 23:14:27 2019 +0200
@@ -24,6 +24,7 @@
const cMaxGearArrayInd = 1023;
const cMaxGearHitOrderInd = 1023;
+const cMaxGearProximityCacheInd = 1023;
type PGearArray = ^TGearArray;
TGearArray = record
@@ -40,6 +41,12 @@
Count: Longword
end;
+type PGearProximityCache = ^TGearProximityCache;
+ TGearProximityCache = record
+ ar: array[0..cMaxGearProximityCacheInd] of PGear;
+ Count: Longword
+ end;
+
type TLineCollision = record
hasCollision: Boolean;
cX, cY: LongInt; //for visual effects only
@@ -53,6 +60,7 @@
function CheckGearsCollision(Gear: PGear): PGearArray;
function CheckAllGearsCollision(SourceGear: PGear): PGearArray;
+function CheckCacheCollision(SourceGear: PGear): PGearArray;
function CheckGearsLineCollision(Gear: PGear; oX, oY, tX, tY: hwFloat): PGearArray;
function CheckAllGearsLineCollision(SourceGear: PGear; oX, oY, tX, tY: hwFloat): PGearArray;
@@ -61,6 +69,10 @@
procedure ClearHitOrderLeq(MinOrder: LongInt);
procedure ClearHitOrder();
+procedure RefillProximityCache(SourceGear: PGear; radius: LongInt);
+procedure RemoveFromProximityCache(Gear: PGear);
+procedure ClearProximityCache();
+
function TestCollisionXwithGear(Gear: PGear; Dir: LongInt): Word;
function TestCollisionYwithGear(Gear: PGear; Dir: LongInt): Word;
@@ -97,6 +109,7 @@
cinfos: array[0..MAXRECTSINDEX] of TCollisionEntry;
ga: TGearArray;
ordera: TGearHitOrder;
+ proximitya: TGearProximityCache;
procedure AddCI(Gear: PGear);
begin
@@ -132,7 +145,7 @@
function CheckCoordInWater(X, Y: LongInt): boolean; inline;
begin
CheckCoordInWater:= (Y > cWaterLine)
- or ((WorldEdge = weSea) and ((X < LongInt(leftX)) or (X > LongInt(rightX))));
+ or ((WorldEdge = weSea) and ((X < leftX) or (X > rightX)));
end;
function CheckGearsCollision(Gear: PGear): PGearArray;
@@ -180,8 +193,8 @@
(sqr(mx - hwRound(Gear^.x)) + sqr(my - hwRound(Gear^.y)) <= sqr(Gear^.Radius + tr))then
begin
ga.ar[ga.Count]:= Gear;
- ga.cX[ga.Count]:= hwround(SourceGear^.X);
- ga.cY[ga.Count]:= hwround(SourceGear^.Y);
+ ga.cX[ga.Count]:= mx;
+ ga.cY[ga.Count]:= my;
inc(ga.Count)
end;
@@ -287,6 +300,33 @@
end;
end;
+function CheckCacheCollision(SourceGear: PGear): PGearArray;
+var mx, my, tr, i: LongInt;
+ Gear: PGear;
+begin
+ CheckCacheCollision:= @ga;
+ ga.Count:= 0;
+
+ mx:= hwRound(SourceGear^.X);
+ my:= hwRound(SourceGear^.Y);
+
+ tr:= SourceGear^.Radius + 2;
+
+ for i:= 0 to proximitya.Count - 1 do
+ begin
+ Gear:= proximitya.ar[i];
+ // Assuming the cache has been filled correctly, it will not contain SourceGear
+ // and other gears won't be far enough for sqr overflow
+ if (sqr(mx - hwRound(Gear^.X)) + sqr(my - hwRound(Gear^.Y)) <= sqr(Gear^.Radius + tr)) then
+ begin
+ ga.ar[ga.Count]:= Gear;
+ ga.cX[ga.Count]:= mx;
+ ga.cY[ga.Count]:= my;
+ inc(ga.Count)
+ end;
+ end;
+end;
+
function UpdateHitOrder(Gear: PGear; Order: LongInt): boolean;
var i: LongInt;
begin
@@ -337,14 +377,58 @@
ordera.Count:= 0;
end;
+procedure RefillProximityCache(SourceGear: PGear; radius: LongInt);
+var cx, cy, dx, dy, r: LongInt;
+ Gear: PGear;
+begin
+ proximitya.Count:= 0;
+ cx:= hwRound(SourceGear^.X);
+ cy:= hwRound(SourceGear^.Y);
+ Gear:= GearsList;
+
+ while (Gear <> nil) and (proximitya.Count <= cMaxGearProximityCacheInd) do
+ begin
+ dx:= abs(hwRound(Gear^.X) - cx);
+ dy:= abs(hwRound(Gear^.Y) - cy);
+ r:= radius + Gear^.radius + 2;
+ if (Gear <> SourceGear) and (max(dx, dy) <= r) and (sqr(dx) + sqr(dy) <= sqr(r)) then
+ begin
+ proximitya.ar[proximitya.Count]:= Gear;
+ inc(proximitya.Count)
+ end;
+ Gear := Gear^.NextGear
+ end;
+end;
+
+procedure RemoveFromProximityCache(Gear: PGear);
+var i: LongInt;
+begin
+ i := 0;
+ while i < proximitya.Count do
+ begin
+ if proximitya.ar[i] = Gear then
+ begin
+ proximitya.ar[i]:= proximitya.ar[proximitya.Count - 1];
+ dec(proximitya.Count);
+ end
+ else
+ inc(i);
+ end;
+end;
+
+procedure ClearProximityCache();
+begin
+ proximitya.Count:= 0;
+end;
+
function TestCollisionXwithGear(Gear: PGear; Dir: LongInt): Word;
var x, y, i: LongInt;
begin
// Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap
-if (Gear^.CollisionMask = lfNotCurrentMask) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and
+if (Gear^.CollisionMask = lfNotCurHogCrate) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and
((hwRound(Gear^.Hedgehog^.Gear^.X) + Gear^.Hedgehog^.Gear^.Radius + 16 < hwRound(Gear^.X) - Gear^.Radius) or
(hwRound(Gear^.Hedgehog^.Gear^.X) - Gear^.Hedgehog^.Gear^.Radius - 16 > hwRound(Gear^.X) + Gear^.Radius)) then
- Gear^.CollisionMask:= $FFFF;
+ Gear^.CollisionMask:= lfAll;
x:= hwRound(Gear^.X);
if Dir < 0 then
@@ -370,10 +454,10 @@
var x, y, i: LongInt;
begin
// Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap
-if (Gear^.CollisionMask = lfNotCurrentMask) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and
+if (Gear^.CollisionMask = lfNotCurHogCrate) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and
((hwRound(Gear^.Hedgehog^.Gear^.Y) + Gear^.Hedgehog^.Gear^.Radius + 16 < hwRound(Gear^.Y) - Gear^.Radius) or
(hwRound(Gear^.Hedgehog^.Gear^.Y) - Gear^.Hedgehog^.Gear^.Radius - 16 > hwRound(Gear^.Y) + Gear^.Radius)) then
- Gear^.CollisionMask:= $FFFF;
+ Gear^.CollisionMask:= lfAll;
y:= hwRound(Gear^.Y);
if Dir < 0 then
@@ -417,7 +501,7 @@
begin
if Land[y, x] and Gear^.CollisionMask <> 0 then
begin
- if Land[y, x] and Gear^.CollisionMask > 255 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask)
else
pixel:= Land[y, x] and Gear^.CollisionMask;
@@ -483,7 +567,7 @@
if (x and LAND_WIDTH_MASK) = 0 then
if Land[y, x] > 0 then
begin
- if Land[y, x] and Gear^.CollisionMask > 255 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask)
else // if Land[y, x] <> 0 then
pixel:= Land[y, x] and Gear^.CollisionMask;
@@ -558,7 +642,7 @@
i:= y + Gear^.Radius * 2 - 2;
repeat
if (y and LAND_HEIGHT_MASK) = 0 then
- if Land[y, x] and Gear^.CollisionMask > 255 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask);
inc(y)
until (y > i);
@@ -581,7 +665,7 @@
i:= x + Gear^.Radius * 2 - 2;
repeat
if (x and LAND_WIDTH_MASK) = 0 then
- if Land[y, x] and Gear^.CollisionMask > 255 then
+ if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then
exit(Land[y, x] and Gear^.CollisionMask);
inc(x)
until (x > i);
@@ -897,7 +981,7 @@
i:= x + Gear^.Radius * 2 - 2;
repeat
if (x and LAND_WIDTH_MASK) = 0 then
- if Land[y, x] > 255 then
+ if (Land[y, x] and lfLandMask) <> 0 then
if (not isColl) or (abs(x-gx) < abs(collX-gx)) then
begin
isColl:= true;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uCommandHandlers.pas
--- a/hedgewars/uCommandHandlers.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uCommandHandlers.pas Wed Jul 31 23:14:27 2019 +0200
@@ -73,6 +73,8 @@
s:= s; // avoid compiler hint
if GameState = gsConfirm then
begin
+ if (luaCmdUsed) then
+ SendIPC(_S'm');
SendIPC(_S'Q');
GameState:= gsExit
end
@@ -81,6 +83,8 @@
procedure chHalt (var s: shortstring);
begin
s:= s; // avoid compiler hint
+ if (luaCmdUsed) then
+ SendIPC(_S'm');
SendIPC(_S'H');
GameState:= gsExit
end;
@@ -113,7 +117,7 @@
if s[byte(s[0])]='"' then
Delete(s, byte(s[0]), 1);
cScriptName:= s;
-ScriptLoad(s)
+ScriptLoad(s, true)
end;
procedure chScriptParam(var s: shortstring);
@@ -125,49 +129,53 @@
procedure chCurU_p(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementY:= -1;
+updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, -1, CursorMovementY);
end;
procedure chCurU_m(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementY:= 0;
+if CursorMovementY < 0 then
+ updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, 0, CursorMovementY);
end;
procedure chCurD_p(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementY:= 1;
+updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, 1, CursorMovementY);
end;
procedure chCurD_m(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementY:= 0;
+if CursorMovementY > 0 then
+ updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, 0, CursorMovementY);
end;
procedure chCurL_p(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementX:= -1;
+updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, -1, CursorMovementX);
end;
procedure chCurL_m(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementX:= 0;
+if CursorMovementX < 0 then
+ updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, 0, CursorMovementX);
end;
procedure chCurR_p(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementX:= 1;
+updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, 1, CursorMovementX);
end;
procedure chCurR_m(var s: shortstring);
begin
s:= s; // avoid compiler hint
-CursorMovementX:= 0;
+if CursorMovementX > 0 then
+ updateCursorMovementDelta((LocalMessage and gmPrecise) <> 0, 0, CursorMovementX);
end;
procedure chLeft_p(var s: shortstring);
@@ -419,6 +427,32 @@
end
end;
+// Increment timer or bounciness
+procedure chTimerU(var s: shortstring);
+var t: LongWord;
+ tb: Byte;
+begin
+s:= s; // avoid compiler hint
+if CheckNoTeamOrHH then
+ exit;
+// We grab the current timer first so we can increment it
+if (CurrentHedgehog^.Gear^.Message and gmPrecise) = 0 then
+ t:= HHGetTimerMsg(CurrentHedgehog^.Gear)
+else
+ // Use bounciness if Precise is pressed
+ t:= HHGetBouncinessMsg(CurrentHedgehog^.Gear);
+if t <> MSGPARAM_INVALID then
+ begin
+ // Calculate new timer
+ Inc(t);
+ if t > 5 then
+ t:= 1;
+ tb:= t mod 255;
+ // Delegate the actual change to /timer
+ ParseCommand('timer ' + Char(tb + Ord('0')), true);
+ end;
+end;
+
procedure chSlot(var s: shortstring);
var slot: LongWord;
ss: shortstring;
@@ -448,6 +482,11 @@
if CheckNoTeamOrHH then
exit;
+ (* Use "~" (ASCII character 126) as synonym for NUL byte (=amNothing).
+ This is done to allow to add "setweap ~" in QTfrontend/binds.cpp because
+ the NUL byte would terminate the strings in C++ otherwise. *)
+ if (s[1] = '~') then
+ s[1]:= #0;
if checkFails((s[0] = #1) and (s[1] <= char(High(TAmmoType))), 'Malformed /setweap', true) then exit;
if not isExternalSource then
@@ -510,7 +549,7 @@
InitStepsFlags:= InitStepsFlags or cifMap
end;
cMapName:= s;
-ScriptLoad('Maps/' + s + '/map.lua')
+ScriptLoad('Maps/' + s + '/map.lua', false)
end;
procedure chSetTheme(var s: shortstring);
@@ -557,16 +596,32 @@
end
end;
-procedure chVol_p(var s: shortstring);
+procedure chVolUp_p(var s: shortstring);
+begin
+s:= s; // avoid compiler hint
+cVolumeUpKey:= true;
+updateVolumeDelta((LocalMessage and gmPrecise) <> 0);
+end;
+
+procedure chVolUp_m(var s: shortstring);
begin
s:= s; // avoid compiler hint
-inc(cVolumeDelta, 3)
+cVolumeUpKey:= false;
+updateVolumeDelta((LocalMessage and gmPrecise) <> 0);
end;
-procedure chVol_m(var s: shortstring);
+procedure chVolDown_p(var s: shortstring);
begin
s:= s; // avoid compiler hint
-dec(cVolumeDelta, 3)
+cVolumeDownKey:= true;
+updateVolumeDelta((LocalMessage and gmPrecise) <> 0);
+end;
+
+procedure chVolDown_m(var s: shortstring);
+begin
+s:= s; // avoid compiler hint
+cVolumeDownKey:= false;
+updateVolumeDelta((LocalMessage and gmPrecise) <> 0);
end;
procedure chMute(var s: shortstring);
@@ -584,12 +639,12 @@
if autoCameraOn then
begin
FollowGear:= nil;
- AddCaption(trmsg[sidAutoCameraOff], $CCCCCC, capgrpVolume);
+ AddCaption(trmsg[sidAutoCameraOff], capcolSetting, capgrpVolume);
autoCameraOn:= false
end
else
begin
- AddCaption(trmsg[sidAutoCameraOn], $CCCCCC, capgrpVolume);
+ AddCaption(trmsg[sidAutoCameraOn], capcolSetting, capgrpVolume);
bShowFinger:= true;
if not CurrentHedgehog^.Unplaced then
FollowGear:= CurrentHedgehog^.Gear;
@@ -683,21 +738,32 @@
procedure chZoomIn(var s: shortstring);
begin
s:= s; // avoid compiler hint
- if ZoomValue < cMinZoomLevel then
+ if (LocalMessage and gmPrecise <> 0) then
+ ZoomValue:= ZoomValue + cZoomDeltaSmall
+ else
ZoomValue:= ZoomValue + cZoomDelta;
+ if ZoomValue > cMinZoomLevel then
+ ZoomValue:= cMinZoomLevel;
end;
procedure chZoomOut(var s: shortstring);
begin
s:= s; // avoid compiler hint
- if ZoomValue > cMaxZoomLevel then
+ if (LocalMessage and gmPrecise <> 0) then
+ ZoomValue:= ZoomValue - cZoomDeltaSmall
+ else
ZoomValue:= ZoomValue - cZoomDelta;
+ if ZoomValue < cMaxZoomLevel then
+ ZoomValue:= cMaxZoomLevel;
end;
procedure chZoomReset(var s: shortstring);
begin
s:= s; // avoid compiler hint
- ZoomValue:= cDefaultZoomLevel;
+ if (LocalMessage and gmPrecise <> 0) then
+ ZoomValue:= cDefaultZoomLevel
+ else
+ ZoomValue:= UserZoom;
end;
procedure chMapGen(var s: shortstring);
@@ -755,6 +821,11 @@
cHealthDecrease:= StrToInt(s)
end;
+procedure chInitHealth(var s: shortstring);
+begin
+cInitHealth:= StrToInt(s)
+end;
+
procedure chDamagePercent(var s: shortstring);
begin
cDamagePercent:= StrToInt(s)
@@ -823,6 +894,11 @@
CampaignVariable := s;
end;
+procedure chMissVar(var s:shortstring);
+begin
+ MissionVariable := s;
+end;
+
procedure chWorldEdge(var s: shortstring);
begin
WorldEdge:= TWorldEdge(StrToInt(s))
@@ -834,6 +910,26 @@
cAdvancedMapGenMode:= true;
end;
+procedure chShowMission_p(var s: shortstring);
+begin
+ s:= s; // avoid compiler hint
+ isShowMission:= true;
+end;
+
+procedure chShowMission_m(var s: shortstring);
+begin
+ s:= s; // avoid compiler hint
+ isShowMission:= false;
+ if (not isForceMission) then
+ HideMission();
+end;
+
+procedure chGearInfo(var s: shortstring);
+begin
+ s:= s; // avoid compiler hint
+ isShowGearInfo:= not isShowGearInfo;
+end;
+
procedure initModule;
begin
//////// Begin top sorted by freq analysis not including chatmsg
@@ -877,6 +973,7 @@
RegisterVariable('sd_turns', @chSuddenDTurns , false);
RegisterVariable('waterrise', @chWaterRise , false);
RegisterVariable('healthdec', @chHealthDecrease, false);
+ RegisterVariable('inithealth',@chInitHealth, false);
RegisterVariable('damagepct',@chDamagePercent , false);
RegisterVariable('ropepct' , @chRopePercent , false);
RegisterVariable('getawaytime' , @chGetAwayTime , false);
@@ -903,10 +1000,10 @@
RegisterVariable('timer' , @chTimer , false, true);
RegisterVariable('taunt' , @chTaunt , false);
RegisterVariable('put' , @chPut , false);
- RegisterVariable('+volup' , @chVol_p , true );
- RegisterVariable('-volup' , @chVol_m , true );
- RegisterVariable('+voldown', @chVol_m , true );
- RegisterVariable('-voldown', @chVol_p , true );
+ RegisterVariable('+volup' , @chVolUp_p , true );
+ RegisterVariable('-volup' , @chVolUp_m , true );
+ RegisterVariable('+voldown', @chVolDown_p , true );
+ RegisterVariable('-voldown', @chVolDown_m , true );
RegisterVariable('mute' , @chMute , true );
RegisterVariable('findhh' , @chFindhh , true );
RegisterVariable('pause' , @chPause , true );
@@ -919,9 +1016,14 @@
RegisterVariable('+cur_r' , @chCurR_p , true );
RegisterVariable('-cur_r' , @chCurR_m , true );
RegisterVariable('campvar' , @chCampVar , true );
+ RegisterVariable('missvar' , @chMissVar , true );
RegisterVariable('record' , @chRecord , true );
RegisterVariable('worldedge',@chWorldEdge , false);
RegisterVariable('advmapgen',@chAdvancedMapGenMode, false);
+ RegisterVariable('+mission', @chShowMission_p, true);
+ RegisterVariable('-mission', @chShowMission_m, true);
+ RegisterVariable('gearinfo', @chGearInfo , true );
+ RegisterVariable('timer_u' , @chTimerU , true );
end;
procedure freeModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uConsts.pas
--- a/hedgewars/uConsts.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uConsts.pas Wed Jul 31 23:14:27 2019 +0200
@@ -29,25 +29,26 @@
HDPIScaleFactor = 1;
// application return codes
- HaltNoError = 0;
+ HaltNoError = 0; // Hedgewars quits normally
// error codes are placed in range 50-99 because that way then don't overlap with run-time errors of pascal
// see https://www.freepascal.org/docs-html/user/userap4.html
- HaltUsageError = 51;
- HaltFatalError = 52;
- HaltStartupError = 53;
- HaltFatalErrorNoIPC = 54;
+ HaltUsageError = 51; // Hedgewars was invoked incorrectly (e.g. bad command-line parameter)
+ HaltFatalError = 52; // Fatal internal error. See logs for more. Also reports error to frontend
+ HaltStartupError = 53; // Failure loading critical resources
+ HaltFatalErrorNoIPC = 54; // Fatal internal error, IPC socket is not available
// for automatic tests
- HaltTestSuccess = 0;
- HaltTestFailed = 60;
- HaltTestLuaError = 61;
- HaltTestUnexpected = 62;
+ HaltTestSuccess = 0; // Test result: success
+ HaltTestFailed = 60; // Test result: failed
+ HaltTestLuaError = 61; // Lua runtime error
+ HaltTestUnexpected = 62; // Unexpected error
+ // maximum ScreenFadeValue
sfMax = 1000;
- // message constants
+ // log message constants
errmsgCreateSurface = 'Error creating SDL surface';
errmsgTransparentSet = 'Error setting transparent color';
errmsgUnknownCommand = 'Unknown command';
@@ -64,15 +65,23 @@
msgGettingConfig = 'Getting game config...';
// camera movement multipliers
- cameraKeyboardSpeed : ShortInt = 10;
+ cameraKeyboardSpeed : LongInt = 10;
+ cameraKeyboardSpeedSlow : LongInt = 3;
// color constants
cWhiteColorChannels : TSDL_Color = (r:$FF; g:$FF; b:$FF; a:$FF);
cNearBlackColorChannels : TSDL_Color = (r:$00; g:$00; b:$10; a:$FF);
+ cInvertTextColorAt : Byte = 64;
- cWhiteColor : Longword = $FFFFFFFF;
- cYellowColor : Longword = $FFFFFF00;
- cNearBlackColor : Longword = $FF000010;
+ cWhiteColor : Longword = $FFFFFFFF; // white
+ cNearBlackColor : Longword = $FF000010; // nearly black
+
+ capcolDefault : Longword = $FFFFFFFF; // default caption color
+ capcolSetting : Longword = $FFCCCCCC; // caption color for changing client setting like volume or auto camera
+ capcolDefaultLua : Longword = $FFFFFFFF; // capcolDefault for Lua
+ capcolSettingLua : Longword = $CCCCCCFF; // capcolSetting for Lua
+
+ cCentralMessageColor : Longword = $FFFFFF00; // color of message in center of screen like quit or pause
{$WARNINGS OFF}
cAirPlaneSpeed: hwFloat = (isNegative: false; QWordValue: 3006477107); // 1.4
@@ -112,16 +121,16 @@
// To allow these to layer, going to treat them as masks. The bottom byte is reserved for objects
// TODO - set lfBasic for all solid land, ensure all uses of the flags can handle multiple flag bits
// lfObject and lfBasic are only to be different *graphically* in all other ways they should be treated the same
- lfBasic = $8000; // black
- lfIndestructible = $4000; // red
- lfObject = $2000; // white
+ lfBasic = $8000; // normal destructible terrain (mask.png: black)
+ lfIndestructible = $4000; // indestructible terrain (mask.png: red)
+ lfObject = $2000; // destructible terrain, land object (mask.png: white)
lfDamaged = $1000; //
- lfIce = $0800; // blue
- lfBouncy = $0400; // green
+ lfIce = $0800; // icy terrain (mask.png: blue)
+ lfBouncy = $0400; // bouncy terrain (mask.png: green)
lfLandMask = $FF00; // upper byte is used for terrain, not objects.
- lfCurrentHog = $0080; // CurrentHog. It is also used to flag crates, for convenience of AI. Since an active hog would instantly collect the crate, this does not impact play
- lfNotCurrentMask = $FF7F; // inverse of above. frequently used
+ lfCurHogCrate = $0080; // CurrentHedgehog, and crates, for convenience of AI. Since an active hog would instantly collect the crate, this does not impact playj
+ lfNotCurHogCrate = $FF7F; // inverse of above. frequently used
lfObjMask = $007F; // lower 7 bits used for hogs and explosives and mines
lfNotObjMask = $FF80; // inverse of above.
@@ -136,16 +145,19 @@
// lower byte is for objects.
// consists of 0-127 counted for object checkins and $80 as a bit flag for current hog.
- lfAllObjMask = $00FF; // lfCurrentHog or lfObjMask
+ lfAllObjMask = $00FF; // lfCurHogCrate or lfObjMask
+
+ lfAll = $FFFF; // everything
- cMaxPower = 1500;
- cMaxAngle = 2048;
+ cMaxPower = 1500; // maximum power value for ammo that powers up
+ cMaxAngle = 2048; // maximum positive value for Gear angle
cPowerDivisor = 1500;
MAXNAMELEN = 192;
MAXROPEPOINTS = 3840;
+ MAXROPELAYERS = 16;
{$IFNDEF PAS2C}
// some opengl headers do not have these macros
@@ -157,6 +169,7 @@
cVisibleWater : LongInt = 128;
cTeamHealthWidth : LongInt = 128;
+ cGearContourThreshold : LongInt = 179; // if water opacity is higher than this, draw contour for some gears when in water
cifRandomize = $00000001;
cifTheme = $00000002;
@@ -167,33 +180,53 @@
RGB_LUMINANCE_GREEN = 0.715160;
RGB_LUMINANCE_BLUE = 0.072169;
- cMaxTeams = 8;
- cMaxHHIndex = 7;
- cMaxHHs = 48;
+ // hedgehog info
+ cMaxTeams = 8; // maximum number of teams
+ cMaxHHIndex = 7; // maximum hedgehog index (counting starts at 0)
+ // NOTE: If you change cMaxHHIndex, also change cMaxHogHealth!
+ cMaxHHs = cMaxTeams * (cMaxHHIndex+1); // maximum number of hogs
+
+ cClanColors = 9; // number of possible clan colors
cMaxEdgePoints = 32768;
- cHHRadius = 9;
+ cBorderWidth = 6; // width of indestructible border
+ // width of 3 allowed hogs to be knocked through with grenade
+
+ cHHRadius = 9; // hedgehog radius
cHHStepTicks = 29;
+ cMaxHogHealth = 268435455; // maximum hedgehog health
+ // cMaxHogHealth was calculated by: High(LongInt) div (cMaxHHIndex+1);
+
+ ouchDmg = 55; // least amount of damage a hog must take in one blow for sndOuch to play
+
+ // Z levels
cHHZ = 1000;
cCurrHHZ = Succ(cHHZ);
- cBarrelHealth = 60;
- cShotgunRadius = 22;
- cBlowTorchC = 6;
- cakeDmg = 75;
+ // some gear constants
+ cBarrelHealth = 60; // initial barrel health
+ cShotgunRadius = 22; // radius of land a shotgun shot destroys
+ cBlowTorchC = 6; // blow torch gear radius component (added to cHHRadius to get the full radius)
+ cakeDmg = 75; // default cake damage
+ // key stuff
cKeyMaxIndex = 1600;
cKbdMaxIndex = 65536;//need more room for the modifier keys
+ // font stuff
cFontBorder = 2 * HDPIScaleFactor;
cFontPadding = 2 * HDPIScaleFactor;
- cDefaultBuildMaxDist = 256;
+ cDefaultBuildMaxDist = 256; // default max. building distance with girder/rubber
+ cResurrectorDist = 100; // effect distance of resurrector
+ cSeductionDist = 250; // effect distance of seduction
+
+ ExtraTime = 30000; // amount of time (ms) given for using Extra Time
// do not change this value
- cDefaultZoomLevel = 2.0;
+ cDefaultZoomLevel = 2.0; // 100% zoom
cBaseChatFontHeight = 12;
cChatScaleRelDelta = 0.1;
@@ -203,141 +236,165 @@
cDefaultUIScaleLevel = 1.0;
// game flags
- gfAny = $FFFFFFFF;
- gfOneClanMode = $00000001; // used in trainings
- gfMultiWeapon = $00000002; // used in trainings
- gfSolidLand = $00000004;
- gfBorder = $00000008;
- gfDivideTeams = $00000010;
- gfLowGravity = $00000020;
- gfLaserSight = $00000040;
- gfInvulnerable = $00000080;
- gfResetHealth = $00000100;
- gfVampiric = $00000200;
- gfKarma = $00000400;
- gfArtillery = $00000800;
- gfSwitchHog = $00001000;
- gfRandomOrder = $00002000;
- gfKing = $00004000;
- gfPlaceHog = $00008000;
- gfSharedAmmo = $00010000;
- gfDisableGirders = $00020000;
- gfDisableLandObjects = $00040000;
- gfAISurvival = $00080000;
- gfInfAttack = $00100000;
- gfResetWeps = $00200000;
- gfPerHogAmmo = $00400000;
- gfDisableWind = $00800000;
- gfMoreWind = $01000000;
- gfTagTeam = $02000000;
- gfBottomBorder = $04000000;
- gfShoppaBorder = $08000000;
+ gfAny = $FFFFFFFF; // mask for all possible gameflags
+ gfOneClanMode = $00000001; // Game does not end if there's only one clan in play. For missions
+ gfMultiWeapon = $00000002; // Enter multishoot mode after attack with infinite shots. For target practice
+ gfSolidLand = $00000004; // (almost) indestrutible land
+ gfBorder = $00000008; // border at top, left and right
+ gfDivideTeams = $00000010; // each clan spawns their hogs on own side of terrain
+ gfLowGravity = $00000020; // low gravity
+ gfLaserSight = $00000040; // laser sight for all
+ gfInvulnerable = $00000080; // invulerable for all
+ gfResetHealth = $00000100; // heal hogs health up to InitialHealth each turn
+ gfVampiric = $00000200; // vampirism for all
+ gfKarma = $00000400; // receive damage you deal
+ gfArtillery = $00000800; // hogs can't walk
+ gfSwitchHog = $00001000; // free switch hog at turn start
+ gfRandomOrder = $00002000; // hogs play in random order
+ gfKing = $00004000; // King Mode
+ gfPlaceHog = $00008000; // place all hogs at game start
+ gfSharedAmmo = $00010000; // ammo is shared per-clan
+ gfDisableGirders = $00020000; // disable land girders
+ gfDisableLandObjects = $00040000; // disable land objects
+ gfAISurvival = $00080000; // AI is revived
+ gfInfAttack = $00100000; // infinite attack
+ gfResetWeps = $00200000; // reset weapons each turn
+ gfPerHogAmmo = $00400000; // each hog has its own ammo
+ gfDisableWind = $00800000; // don't automatically change wind
+ gfMoreWind = $01000000; // wind influences most gears
+ gfTagTeam = $02000000; // hogs of same clan share their turn time
+ gfBottomBorder = $04000000; // border at bottom
+ gfShoppaBorder = $08000000; // Surround terrain with fancy "security border". Pure eye candy
// NOTE: When adding new game flags, ask yourself
// if a "game start notice" would be useful. If so,
// add one in uWorld.pas - look for "AddGoal".
// gear states
- gstDrowning = $00000001;
- gstHHDriven = $00000002;
- gstMoving = $00000004;
- gstAttacked = $00000008;
- gstAttacking = $00000010;
- gstCollision = $00000020;
- gstChooseTarget = $00000040;
- gstHHJumping = $00000100;
- gsttmpFlag = $00000200;
- gstHHThinking = $00000800;
- gstNoDamage = $00001000;
- gstHHHJump = $00002000;
- gstAnimation = $00004000;
- gstHHDeath = $00008000;
- gstWinner = $00010000; // this, along with gstLoser, is good for indicating hedgies know they screwed up
+ gstDrowning = $00000001; // drowning
+ gstHHDriven = $00000002; // hog is controlled by current player
+ gstMoving = $00000004; // moving
+ gstAttacked = $00000008; // after attack
+ gstAttacking = $00000010; // while attacking
+ gstCollision = $00000020; // it has *just* collided
+ gstChooseTarget = $00000040; // choosing target
+ gstHHJumping = $00000100; // hog is doing long jump
+ gsttmpFlag = $00000200; // temporary wildcard flag, use it for anything you want
+ gstHHThinking = $00000800; // AI hog is thinking
+ gstNoDamage = $00001000; // gear is immune to damage
+ gstHHHJump = $00002000; // hog is doing high jump
+ gstAnimation = $00004000; // hog is playing an animation
+ gstHHDeath = $00008000; // hog is dying
+ gstWinner = $00010000; // indicates if hog did well
gstWait = $00020000;
- gstNotKickable = $00040000;
- gstLoser = $00080000;
- gstHHGone = $00100000;
- gstInvisible = $00200000;
- gstSubmersible = $00400000;
- gstFrozen = $00800000;
- gstNoGravity = $01000000;
+ gstNotKickable = $00040000; // gear cannot be pushed by forces
+ gstLoser = $00080000; // indicates if hog screwed up
+ gstHHGone = $00100000; // hog is gone (teamgone event)
+ gstInvisible = $00200000; // invisible
+ gstSubmersible = $00400000; // can survive in water
+ gstFrozen = $00800000; // frozen
+ gstNoGravity = $01000000; // ignores gravity
+ gstInBounceEdge = $02000000; // spawned in bounce edge
// gear messages
- gmLeft = $00000001;
- gmRight = $00000002;
- gmUp = $00000004;
- gmDown = $00000008;
- gmSwitch = $00000010;
- gmAttack = $00000020;
- gmLJump = $00000040;
- gmHJump = $00000080;
- gmDestroy = $00000100;
- gmSlot = $00000200; // with param
- gmWeapon = $00000400; // with param
- gmTimer = $00000800; // with param
- gmAnimate = $00001000; // with param
- gmPrecise = $00002000;
+ gmLeft = $00000001; // left
+ gmRight = $00000002; // right
+ gmUp = $00000004; // up
+ gmDown = $00000008; // down
+ gmSwitch = $00000010; // switch hedgehog
+ gmAttack = $00000020; // attack
+ gmLJump = $00000040; // long jump
+ gmHJump = $00000080; // high jump
+ gmDestroy = $00000100; // request to self-destruct
+ gmSlot = $00000200; // slot key; with param
+ gmWeapon = $00000400; // direct weapon selection (SetWeapon); with param
+ gmTimer = $00000800; // set timer; with param
+ gmAnimate = $00001000; // start animation; with param
+ gmPrecise = $00002000; // precise aim
- gmRemoveFromList = $00004000;
- gmAddToList = $00008000;
- gmDelete = $00010000;
+ // gmAddToList and gmRemoveFromList are used when changing order of gears in the gear list. They are used together when changing a gear's Z (drawing order)
+ gmRemoveFromList = $00004000; // remove gear from gear list
+ gmAddToList = $00008000; // add gear to gear list
+
+ gmDelete = $00010000; // delete gear
gmAllStoppable = gmLeft or gmRight or gmUp or gmDown or gmAttack or gmPrecise;
- cMaxSlotIndex = 10;
+ // ammo slots
+ cMaxSlotIndex = 10; // maximum slot index (including hidden slot) (row in ammo menu)
cHiddenSlotIndex = cMaxSlotIndex; // slot for hidden ammo types, not visible and has no key
- cMaxSlotAmmoIndex = 6;
+ cMaxSlotAmmoIndex = 5; // maximum index for ammos per slot (column in ammo menu)
- // ai hints
- aihUsualProcessing = $00000000;
- aihDoesntMatter = $00000001;
+ // AI hints to be set for any gear
+ aihUsualProcessing = $00000000; // treat gear as usual
+ aihDoesntMatter = $00000001; // ignore gear in attack calculations and don't intentionally attack it
// ammo properties
- ammoprop_Timerable = $00000001;
- ammoprop_Power = $00000002;
- ammoprop_NeedTarget = $00000004;
- ammoprop_ForwMsgs = $00000008;
- ammoprop_AttackInMove = $00000010;
+ ammoprop_Timerable = $00000001; // can set timer
+ ammoprop_Power = $00000002; // can power up fire strength
+ ammoprop_NeedTarget = $00000004; // must select target
+ ammoprop_ForwMsgs = $00000008; // received gear messages are forwarded to the spawned gear
+ ammoprop_AttackInMove = $00000010; // can attack while moving / in mid-air
ammoprop_DoesntStopTimerWhileAttacking
- = $00000020;
- ammoprop_NoCrosshair = $00000040;
- ammoprop_AttackingPut = $00000080;
- ammoprop_DontHold = $00000100;
- ammoprop_AltAttack = $00000200;
- ammoprop_AltUse = $00000400;
- ammoprop_NotBorder = $00000800;
- ammoprop_Utility = $00001000;
- ammoprop_Effect = $00002000;
- ammoprop_SetBounce = $00004000;
- ammoprop_NeedUpDown = $00008000;//Used by TouchInterface to show or hide up/down widgets
- ammoprop_OscAim = $00010000;
- ammoprop_NoMoveAfter = $00020000;
- ammoprop_Track = $00040000;
+ = $00000020; // doesn't stop timer while attacker is attacking
+ ammoprop_NoCrosshair = $00000040; // no crosshair rendered
+ ammoprop_AttackingPut = $00000080; // selecting a target is considered an attack
+ ammoprop_DontHold = $00000100; // don't keep ammo selected in next turn
+ ammoprop_AltAttack = $00000200; // ammo can equip alternate ammo (ammoprop_AltUse)
+ ammoprop_AltUse = $00000400; // ammo can be equipped by an ammo with ammoprop_AltAttack
+ ammoprop_NotBorder = $00000800; // ammo is not available if map has border
+ ammoprop_Utility = $00001000; // ammo is considered an utility instead of a weapon
+ ammoprop_Effect = $00002000; // ammo is considered an effect like extra time or vampirism
+ ammoprop_SetBounce = $00004000; // can set bounciness
+ ammoprop_NeedUpDown = $00008000; // used by TouchInterface to show or hide up/down widgets
+ ammoprop_OscAim = $00010000; // oscillating aim
+ ammoprop_NoMoveAfter = $00020000; // can't move after attacking
+ ammoprop_Track = $00040000; // ammo follows the landscape, used by AI
ammoprop_DoesntStopTimerInMultiShoot
- = $00080000;
+ = $00080000; // doesn't stop timer after entering multi-shoot mode
ammoprop_DoesntStopTimerWhileAttackingInInfAttackMode
- = $00100000;
- ammoprop_ForceTurnEnd = $00200000;
- ammoprop_NoTargetAfter= $00400000;
- ammoprop_NoRoundEnd = $10000000;
+ = $00100000; // doesn't stop timer while Attacking gear msg is set and inf. attack mode is on
+ ammoprop_ForceTurnEnd = $00200000; // always ends turn after usage, ignoring inf. attack
+ ammoprop_NoTargetAfter= $00400000; // disable target selection after attack
+ ammoprop_NoWrapTarget = $00800000; // allow to select target beyond wrap world edge limits
+ ammoprop_ShowSelIcon = $01000000; // show icon when selected
+ ammoprop_NoRoundEnd = $10000000; // ammo doesn't end turn
+
+ AMMO_INFINITE = 100; // internal representation of infinite ammo count
+ AMMO_FINITE_MAX = 99; // maximum possible finite ammo count
- AMMO_INFINITE = 100;
- AMMO_FINITE_MAX = 99;
+ JETPACK_FUEL_INFINITE : LongInt = Low(LongInt); // internal representation of infinite jetpack fuel
+ BIRDY_ENERGY_INFINITE : LongInt = Low(LongInt); // as above, but for Birdy
+
+ // Special msgParam value used internally for invalid/non-existing value
+ // Must not be sent over the network!
+ MSGPARAM_INVALID = High(LongWord);
+
+ // raw probability values for crate drops
+ probabilityLevels: array [0..8] of LongWord = (0,20,30,60,100,200,400,600,800);
+
+ // raw bounciness values for each of the player-selectable bounciness levels
+ defaultBounciness = 1000;
+ bouncinessLevels: array [0..4] of LongWord = (350, 700, defaultBounciness, 2000, 4000);
// explosion flags
+ // By default, an explosion removes land, damages and pushes gears,
+ // spawns an explosion animation and plays no sound.
//EXPLAllDamageInRadius = $00000001; Completely unused for ages
- EXPLAutoSound = $00000002;
- EXPLNoDamage = $00000004;
- EXPLDoNotTouchHH = $00000008;
- EXPLDontDraw = $00000010;
- EXPLNoGfx = $00000020;
- EXPLPoisoned = $00000040;
- EXPLDoNotTouchAny = $00000080;
+ EXPLAutoSound = $00000002; // enable sound (if appropriate)
+ EXPLNoDamage = $00000004; // don't damage gears
+ EXPLDoNotTouchHH = $00000008; // don't push hogs
+ EXPLDontDraw = $00000010; // don't remove land
+ EXPLNoGfx = $00000020; // don't spawn visual effects
+ EXPLPoisoned = $00000040; // poison hogs in effect radius
+ EXPLDoNotTouchAny = $00000080; // don't push anything
+ EXPLForceDraw = $00000100; // remove land even with gfSolidLand
- posCaseAmmo = $00000001;
- posCaseHealth = $00000002;
- posCaseUtility = $00000004;
- posCaseDummy = $00000008;
- posCaseExplode = $00000010;
- posCasePoison = $00000020;
+ // Pos flags for gtCase
+ posCaseAmmo = $00000001; // ammo crate
+ posCaseHealth = $00000002; // health crate
+ posCaseUtility = $00000004; // utility crate
+ posCaseDummy = $00000008; // dummy crate
+ posCaseExplode = $00000010; // crate explodes when touched
+ posCasePoison = $00000020; // crate poisons hog when touched
cCaseHealthRadius = 14;
@@ -348,7 +405,7 @@
htHealth = $04;
htTransparent = $08;
- NoPointX = Low(LongInt);
+ NoPointX = Low(LongInt); // special value for CursorX/CursorY if cursor's disabled
cTargetPointRef : TPoint = (x: NoPointX; y: 0);
kSystemSoundID_Vibrate = $00000FFF;
@@ -356,6 +413,10 @@
cMinPlayWidth = 200;
cWorldEdgeDist = 200;
+ cMaxLaserSightWraps = 1; // maximum number of world wraps of laser sight
+
+ cMaxTurnTime = Pred(High(LongInt)); // maximum possible turn time
+
implementation
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uCursor.pas
--- a/hedgewars/uCursor.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uCursor.pas Wed Jul 31 23:14:27 2019 +0200
@@ -1,45 +1,53 @@
+{$INCLUDE "options.inc"}
+
unit uCursor;
interface
procedure init;
procedure resetPosition;
-procedure updatePosition;
+procedure resetPositionDelta();
+procedure updatePositionDelta(xrel, yrel: LongInt);
+procedure updatePosition();
procedure handlePositionUpdate(x, y: LongInt);
implementation
-uses SDLh, uVariables;
+uses SDLh, uVariables, uTypes;
procedure init;
begin
+ SDL_ShowCursor(SDL_DISABLE);
resetPosition();
+{$IFNDEF USE_TOUCH_INTERFACE}
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+{$ENDIF}
end;
procedure resetPosition;
begin
- // Move curser by 1px in case it's already centered.
- // The game camera in the Alpha for 0.9.23 screwed up if
- // the game started with the mouse already being centered.
- // This fixes it, but we might have overlooked a related
- // bug somewhere else.
- // No big deal since this function is (so far) only called once.
- SDL_WarpMouse((cScreenWidth div 2) + 1, cScreenHeight div 2);
+ if GameType = gmtRecord then
+ exit;
SDL_WarpMouse(cScreenWidth div 2, cScreenHeight div 2);
+ resetPositionDelta();
end;
-procedure updatePosition;
-var x, y: LongInt;
+procedure resetPositionDelta();
begin
- SDL_GetMouseState(@x, @y);
+ CursorPointDelta.X:= 0;
+ CursorPointDelta.Y:= 0;
+end;
- if(x <> cScreenWidth div 2) or (y <> cScreenHeight div 2) then
- begin
- handlePositionUpdate(x - cScreenWidth div 2, y - cScreenHeight div 2);
+procedure updatePositionDelta(xrel, yrel: LongInt);
+begin
+ CursorPointDelta.X:= CursorPointDelta.X + xrel;
+ CursorPointDelta.Y:= CursorPointDelta.Y + yrel;
+end;
- if cHasFocus then
- SDL_WarpMouse(cScreenWidth div 2, cScreenHeight div 2);
- end
+procedure updatePosition();
+begin
+ handlePositionUpdate(CursorPointDelta.X, CursorPointDelta.Y);
+ resetPositionDelta();
end;
procedure handlePositionUpdate(x, y: LongInt);
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uDebug.pas
--- a/hedgewars/uDebug.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uDebug.pas Wed Jul 31 23:14:27 2019 +0200
@@ -56,7 +56,7 @@
if not Assert then
begin
lastConsoleLine:= Msg;
- OutError(Msg, false);
+ OutError(Msg, isFatal);
end;
allOK:= allOK and (Assert or (not isFatal));
@@ -69,7 +69,7 @@
if not Assert then
begin
s:= SDL_GetError();
- OutError(Msg + ': ' + s, false)
+ OutError(Msg + ': ' + s, isFatal)
end;
allOK:= allOK and (Assert or (not isFatal));
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGame.pas
--- a/hedgewars/uGame.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGame.pas Wed Jul 31 23:14:27 2019 +0200
@@ -35,6 +35,37 @@
var i,j : LongInt;
s: ansistring;
begin
+
+inc(SoundTimerTicks, Lag);
+if SoundTimerTicks >= 50 then
+ begin
+ SoundTimerTicks:= 0;
+ if cVolumeDelta <> 0 then
+ begin
+ j:= Volume;
+ i:= ChangeVolume(cVolumeDelta);
+ if (not cIsSoundEnabled) or (isAudioMuted and (j<>i)) then
+ AddCaption(trmsg[sidMute], capcolSetting, capgrpVolume)
+ else if not isAudioMuted then
+ begin
+ s:= ansistring(inttostr(i));
+ AddCaption(FormatA(trmsg[sidVolume], s), capcolSetting, capgrpVolume)
+ end
+ end
+ else if cMuteToggle then
+ begin
+ MuteAudio;
+ if isAudioMuted then
+ AddCaption(trmsg[sidMute], capcolSetting, capgrpVolume)
+ else
+ begin
+ s:= ansistring(inttostr(GetVolumePercent()));
+ AddCaption(FormatA(trmsg[sidVolume], s), capcolSetting, capgrpVolume);
+ end;
+ cMuteToggle:= false;
+ end;
+ end;
+
if isPaused then
exit;
@@ -78,35 +109,6 @@
if cTestLua then
Lag:= High(LongInt);
-inc(SoundTimerTicks, Lag);
-if SoundTimerTicks >= 50 then
- begin
- SoundTimerTicks:= 0;
- if cVolumeDelta <> 0 then
- begin
- j:= Volume;
- i:= ChangeVolume(cVolumeDelta);
- if isAudioMuted and (j<>i) then
- AddCaption(trmsg[sidMute], cWhiteColor, capgrpVolume)
- else if not isAudioMuted then
- begin
- s:= ansistring(inttostr(i));
- AddCaption(FormatA(trmsg[sidVolume], s), cWhiteColor, capgrpVolume)
- end
- end
- else if cMuteToggle then
- begin
- MuteAudio;
- if isAudioMuted then
- AddCaption(trmsg[sidMute], cWhiteColor, capgrpVolume)
- else
- begin
- s:= ansistring(inttostr(GetVolumePercent()));
- AddCaption(FormatA(trmsg[sidVolume], s), cWhiteColor, capgrpVolume);
- end;
- cMuteToggle:= false;
- end;
- end;
PlayNextVoice;
i:= 1;
while (GameState <> gsExit) and (i <= Lag) and allOK do
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGears.pas
--- a/hedgewars/uGears.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGears.pas Wed Jul 31 23:14:27 2019 +0200
@@ -42,10 +42,11 @@
procedure ProcessGears;
procedure EndTurnCleanup;
procedure DrawGears;
-procedure DrawGearsTimers;
+procedure DrawGearsGui;
procedure FreeGearsList;
procedure AddMiscGears;
procedure AssignHHCoords;
+procedure RandomizeHHAnim;
procedure StartSuddenDeath;
function GearByUID(uid : Longword) : PGear;
function IsClockRunning() : boolean;
@@ -62,11 +63,19 @@
var delay: LongWord;
delay2: LongWord;
- step: (stInit, stDelay, stChDmg, stSweep, stTurnReact,
- stAfterDelay, stChWin, stWater, stChWin2, stHealth,
- stSpawn, stNTurn);
+ step: (stInit, stDelay1, stChDmg, stSweep, stTurnStats, stChWin1,
+ stTurnReact, stDelay2, stChWin2, stWater, stChWin3,
+ stChKing, stSuddenDeath, stDelay3, stHealth, stSpawn, stDelay4,
+ stNTurn);
NewTurnTick: LongWord;
- //SDMusic: shortstring;
+
+const delayInit = 50;
+ delaySDStart = 1600;
+ delaySDWarning = 1000;
+ delayDamageTagFull = 1500;
+ delayDamageTagShort = 500;
+ delayTurnReact = 800;
+ delayFinal = 100;
function CheckNoDamage: boolean; // returns TRUE in case of no damaged hhs
var Gear: PGear;
@@ -86,7 +95,7 @@
CheckNoDamage:= false;
dmg:= Gear^.Damage;
- if Gear^.Health < dmg then
+ if (Gear^.Health < dmg) then
begin
Gear^.Active:= true;
Gear^.Health:= 0
@@ -105,7 +114,15 @@
RenderHealth(Gear^.Hedgehog^);
RecountTeamHealth(Gear^.Hedgehog^.Team);
+ end
+ else if ((GameFlags and gfKing) <> 0) and (not Gear^.Hedgehog^.Team^.hasKing) then
+ begin
+ Gear^.Active:= true;
+ Gear^.Health:= 0;
+ RenderHealth(Gear^.Hedgehog^);
+ RecountTeamHealth(Gear^.Hedgehog^.Team);
end;
+
if (not isInMultiShoot) then
Gear^.Karma:= 0;
Gear^.Damage:= 0
@@ -114,6 +131,34 @@
end;
end;
+function DoDelay: boolean;
+begin
+if delay <= 0 then
+ delay:= 1
+else
+ dec(delay);
+DoDelay:= delay = 0;
+end;
+
+function CheckMinionsDie: boolean;
+var Gear: PGear;
+begin
+ CheckMinionsDie:= false;
+ if (GameFlags and gfKing) = 0 then
+ exit;
+
+ Gear:= GearsList;
+ while Gear <> nil do
+ begin
+ if (Gear^.Kind = gtHedgehog) and (not Gear^.Hedgehog^.King) and (not Gear^.Hedgehog^.Team^.hasKing) then
+ begin
+ CheckMinionsDie:= true;
+ exit;
+ end;
+ Gear:= Gear^.NextGear;
+ end;
+end;
+
procedure HealthMachine;
var Gear: PGear;
team: PTeam;
@@ -128,19 +173,21 @@
if Gear^.Kind = gtHedgehog then
begin
tmp:= 0;
+ // Deal poison damage (when not frozen)
if (Gear^.Hedgehog^.Effects[hePoisoned] <> 0) and (Gear^.Hedgehog^.Effects[heFrozen] = 0) then
begin
inc(tmp, ModifyDamage(Gear^.Hedgehog^.Effects[hePoisoned], Gear));
if (GameFlags and gfResetHealth) <> 0 then
- dec(Gear^.Hedgehog^.InitialHealth) // does not need a minimum check since <= 1 basically disables it
+ dec(Gear^.Hedgehog^.InitialHealth);
end;
// Apply SD health decrease as soon as SD starts
- if (TotalRounds > cSuddenDTurns - 1) then
+ if (TotalRoundsPre > cSuddenDTurns - 1) then
begin
inc(tmp, cHealthDecrease);
if (GameFlags and gfResetHealth) <> 0 then
dec(Gear^.Hedgehog^.InitialHealth, cHealthDecrease)
end;
+ // Reduce king health when he is alone in team
if Gear^.Hedgehog^.King then
begin
flag:= false;
@@ -156,6 +203,10 @@
dec(Gear^.Hedgehog^.InitialHealth, 5)
end
end;
+ // Initial health must never be below 1 because hog might be resurrected
+ if Gear^.Hedgehog^.InitialHealth < 1 then
+ Gear^.Hedgehog^.InitialHealth:= 1;
+ // Set real damage
if tmp > 0 then
begin
// SD damage never reduces health below 1
@@ -163,7 +214,7 @@
inc(Gear^.Damage, tmp);
if tmp > 0 then
// Make hedgehog moan on damage
- HHHurt(Gear^.Hedgehog, dsPoison);
+ HHHurt(Gear^.Hedgehog, dsPoison, tmp);
end
end;
@@ -172,8 +223,8 @@
end;
procedure ProcessGears;
-var t: PGear;
- i, AliveCount: LongInt;
+var t, tmpGear: PGear;
+ i, j, AliveCount: LongInt;
s: ansistring;
prevtime: LongWord;
stirFallers: boolean;
@@ -225,14 +276,35 @@
end;
if curHandledGear^.Active then
begin
- if curHandledGear^.RenderTimer and (curHandledGear^.Timer > 500) and ((curHandledGear^.Timer mod 1000) = 0) then
+ if curHandledGear^.RenderTimer then
begin
- FreeAndNilTexture(curHandledGear^.Tex);
- curHandledGear^.Tex:= RenderStringTex(ansistring(inttostr(curHandledGear^.Timer div 1000)), cWhiteColor, fntSmall);
+ // Mine timer
+ if (curHandledGear^.Kind in [gtMine, gtSMine, gtAirMine]) then
+ begin
+ if curHandledGear^.Tex = nil then
+ if (curHandledGear^.Karma = 1) and (not (GameType in [gmtDemo, gmtRecord])) then
+ // Secret mine timer
+ curHandledGear^.Tex:= RenderStringTex(trmsg[sidUnknownGearValue], $ff808080, fntSmall)
+ else
+ begin
+ // Display mine timer with up to 1 decimal point of precision (rounded down)
+ i:= curHandledGear^.Timer div 1000;
+ j:= (curHandledGear^.Timer mod 1000) div 100;
+ if j = 0 then
+ curHandledGear^.Tex:= RenderStringTex(ansistring(inttostr(i)), $ff808080, fntSmall)
+ else
+ curHandledGear^.Tex:= RenderStringTex(ansistring(inttostr(i) + lDecimalSeparator + inttostr(j)), $ff808080, fntSmall);
+ end
+ end
+ // Timer of other gears
+ else if ((curHandledGear^.Timer > 500) and ((curHandledGear^.Timer mod 1000) = 0)) then
+ begin
+ // Display time in seconds as whole number, rounded up
+ FreeAndNilTexture(curHandledGear^.Tex);
+ curHandledGear^.Tex:= RenderStringTex(ansistring(inttostr(curHandledGear^.Timer div 1000)), cWhiteColor, fntSmall);
+ end;
end;
curHandledGear^.doStep(curHandledGear);
- // might be useful later
- //ScriptCall('onGearStep', Gear^.uid);
end
end
end;
@@ -261,24 +333,25 @@
begin
if (not bBetweenTurns) and (not isInMultiShoot) then
ScriptCall('onEndTurn');
+ delay:= delayInit;
inc(step)
end;
- stDelay:
+ stDelay1:
begin
- if delay = 0 then
- delay:= cInactDelay
- else
- dec(delay);
-
- if delay = 0 then
- inc(step)
+ if DoDelay() then
+ inc(step);
end;
-
stChDmg:
if CheckNoDamage then
inc(step)
else
- step:= stDelay;
+ begin
+ if (not bBetweenTurns) and (not isInMultiShoot) then
+ delay:= delayDamageTagShort
+ else
+ delay:= delayDamageTagFull;
+ step:= stDelay1;
+ end;
stSweep:
if SweepDirty then
@@ -289,28 +362,36 @@
else
inc(step);
+ stTurnStats:
+ begin
+ if (not bBetweenTurns) and (not isInMultiShoot) then
+ uStats.TurnStats;
+ inc(step)
+ end;
+
+ stChWin1:
+ begin
+ CheckForWin();
+ inc(step)
+ end;
+
stTurnReact:
begin
if (not bBetweenTurns) and (not isInMultiShoot) then
begin
uStats.TurnReaction;
+ uStats.TurnStatsReset;
+ delay:= delayTurnReact;
inc(step)
end
else
inc(step, 2);
end;
- stAfterDelay:
- begin
- if delay = 0 then
- delay:= cInactDelay
- else
- dec(delay);
-
- if delay = 0 then
- inc(step)
- end;
- stChWin:
+ stDelay2:
+ if DoDelay() then
+ inc(step);
+ stChWin2:
begin
CheckForWin();
inc(step)
@@ -319,53 +400,101 @@
if (not bBetweenTurns) and (not isInMultiShoot) then
begin
// Start Sudden Death water rise in the 2nd round of Sudden Death
- if TotalRounds = cSuddenDTurns + 1 then
+ if TotalRoundsPre = cSuddenDTurns + 1 then
bWaterRising:= true;
if bWaterRising and (cWaterRise > 0) then
+ begin
+ bDuringWaterRise:= true;
AddGear(0, 0, gtWaterUp, 0, _0, _0, 0)^.Tag:= cWaterRise;
+ end;
inc(step)
end
- else // since we are not raising the water, a second win-check isn't needed
+ else // since we are not raising the water, another win-check isn't needed
inc(step,2);
- stChWin2:
+ stChWin3:
begin
CheckForWin;
+ bDuringWaterRise:= false;
inc(step)
end;
+ stChKing:
+ begin
+ if (not isInMultiShoot) and (CheckMinionsDie) then
+ step:= stChDmg
+ else
+ inc(step);
+ end;
+ stSuddenDeath:
+ begin
+ if ((cWaterRise <> 0) or (cHealthDecrease <> 0)) and (not (isInMultiShoot or bBetweenTurns)) then
+ begin
+ // Start Sudden Death
+ if (TotalRoundsPre = cSuddenDTurns) and (not SuddenDeath) then
+ begin
+ StartSuddenDeath();
+ delay:= delaySDStart;
+ inc(step);
+ end
+ // Show Sudden Death warning message
+ else if (TotalRoundsPre < cSuddenDTurns) and ((LastSuddenDWarn = -2) or (LastSuddenDWarn <> TotalRoundsPre)) then
+ begin
+ i:= cSuddenDTurns - TotalRoundsPre;
+ s:= ansistring(inttostr(i));
+ // X rounds before SD. X = 1, 2, 3, 5, 7, 10, 15, 20, 25, 50, 100, ...
+ if (i > 0) and ((i <= 3) or (i = 7) or ((i mod 50 = 0) or ((i <= 25) and (i mod 5 = 0)))) then
+ begin
+ if i = 1 then
+ AddCaption(trmsg[sidRoundSD], capcolDefault, capgrpGameState)
+ else
+ AddCaption(FormatA(trmsg[sidRoundsSD], s), capcolDefault, capgrpGameState);
+ delay:= delaySDWarning;
+ inc(step);
+ LastSuddenDWarn:= TotalRoundsPre;
+ end
+ else
+ inc(step, 2);
+ end
+ else
+ inc(step, 2);
+ end
+ else
+ inc(step, 2);
+ end;
+ stDelay3:
+ if DoDelay() then
+ inc(step);
stHealth:
begin
- if (cWaterRise <> 0) or (cHealthDecrease <> 0) then
- begin
- if (TotalRounds = cSuddenDTurns) and (not SuddenDeath) and (not isInMultiShoot) then
- StartSuddenDeath()
- else if (TotalRounds < cSuddenDTurns) and (not isInMultiShoot) then
- begin
- i:= cSuddenDTurns - TotalRounds;
- s:= ansistring(inttostr(i));
- if i = 1 then
- AddCaption(trmsg[sidRoundSD], cWhiteColor, capgrpGameState)
- else if (i = 2) or ((i > 0) and ((i mod 50 = 0) or ((i <= 25) and (i mod 5 = 0)))) then
- AddCaption(FormatA(trmsg[sidRoundsSD], s), cWhiteColor, capgrpGameState);
- end;
+ if bBetweenTurns
+ or isInMultiShoot
+ or (TotalRoundsReal = -1) then
+ inc(step)
+ else
+ begin
+ bBetweenTurns:= true;
+ HealthMachine;
+ step:= stChDmg
end;
- if bBetweenTurns
- or isInMultiShoot
- or (TotalRounds = -1) then
- inc(step)
- else
- begin
- bBetweenTurns:= true;
- HealthMachine;
- step:= stChDmg
- end
- end;
+ end;
stSpawn:
begin
- if not isInMultiShoot then
- SpawnBoxOfSmth;
- inc(step)
+ if (not isInMultiShoot) then
+ begin
+ tmpGear:= SpawnBoxOfSmth;
+ if tmpGear <> nil then
+ ScriptCall('onCaseDrop', tmpGear^.uid)
+ else
+ ScriptCall('onCaseDrop');
+ delay:= delayFinal;
+ inc(step);
+ end
+ else
+ inc(step, 2)
end;
+ stDelay4:
+ if DoDelay() then
+ inc(step);
stNTurn:
begin
if isInMultiShoot then
@@ -404,7 +533,8 @@
dec(delay2);
if ((delay2 mod cInactDelay) = 0) and (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil)
- and (not CurrentHedgehog^.Unplaced) then
+ and (not CurrentHedgehog^.Unplaced)
+ and (not PlacingHogs) then
begin
if (CurrentHedgehog^.Gear^.State and gstAttacked <> 0)
and (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0) then
@@ -439,13 +569,12 @@
if TurnTimeLeft > 0 then
if IsClockRunning() then
- //(CurrentHedgehog^.CurAmmoType in [amShotgun, amDEagle, amSniperRifle])
begin
- if (cHedgehogTurnTime >= 10000)
+ if (cHedgehogTurnTime > TurnTimeLeft)
and (CurrentHedgehog^.Gear <> nil)
and ((CurrentHedgehog^.Gear^.State and gstAttacked) = 0)
- and (not isGetAwayTime) then
- if TurnTimeLeft = 5000 then
+ and (not isGetAwayTime) and (ReadyTimeLeft = 0) then
+ if (TurnTimeLeft = 5000) and (cHedgehogTurnTime >= 10000) then
PlaySoundV(sndHurry, CurrentTeam^.voicepack)
else if TurnTimeLeft = 4000 then
PlaySound(sndCountdown4)
@@ -457,7 +586,7 @@
PlaySound(sndCountdown1);
if ReadyTimeLeft > 0 then
begin
- if (ReadyTimeLeft = 2000) and (LastVoice.snd = sndNone) and (not PlacingHogs) then
+ if (ReadyTimeLeft = 2000) and (LastVoice.snd = sndNone) and (not PlacingHogs) and (not CinematicScript) then
AddVoice(sndComeonthen, CurrentTeam^.voicepack);
dec(ReadyTimeLeft)
end
@@ -490,7 +619,9 @@
end;
AddRandomness(CheckSum);
TurnClockActive:= prevtime <> TurnTimeLeft;
-inc(GameTicks)
+inc(GameTicks);
+if (OuchTauntTimer > 0) then
+ dec(OuchTauntTimer);
end;
//Purpose, to reset all transient attributes toggled by a utility and clean up various gears and effects at end of turn
@@ -525,18 +656,13 @@
for i:= 0 to cMaxHHIndex do
with Hedgehogs[i] do
begin
-(*
- if (SpeechGear <> nil) then
- begin
- DeleteVisualGear(SpeechGear); // remove to restore persisting beyond end of turn. Tiy says was too much of a gameplay issue
- SpeechGear:= nil
- end;
-*)
if (Gear <> nil) then
begin
if (GameFlags and gfInvulnerable) = 0 then
Gear^.Hedgehog^.Effects[heInvulnerable]:= 0;
+ if (Gear^.Hedgehog^.Effects[heArtillery] = 2) then
+ Gear^.Hedgehog^.Effects[heArtillery]:= 0;
end;
end;
t:= GearsList;
@@ -554,7 +680,7 @@
t:= t^.NextGear
end;
- if ((GameFlags and gfResetWeps) <> 0) and (not PlacingHogs) then
+ if ((GameFlags and gfResetWeps) <> 0) and (not PlacingHogs) and (not PlacingKings) then
ResetWeapons;
if (GameFlags and gfResetHealth) <> 0 then
@@ -582,7 +708,8 @@
DrawHHOrder();
end;
-procedure DrawGearsTimers;
+// Draw gear timers and other GUI overlays
+procedure DrawGearsGui;
var Gear: PGear;
x, y: LongInt;
begin
@@ -591,7 +718,12 @@
begin
x:= hwRound(Gear^.X) + WorldDx;
y:= hwRound(Gear^.Y) + WorldDy;
+ if Gear^.Kind = gtAirMine then
+ RenderAirMineGuiExtras(Gear, x, y);
+ RenderGearHealth(Gear, x, y);
RenderGearTimer(Gear, x, y);
+ if Gear^.Kind = gtHedgehog then
+ RenderHHGuiExtras(Gear, x, y);
Gear:= Gear^.NextGear
end;
end;
@@ -603,6 +735,7 @@
GearsList:= nil;
while tt <> nil do
begin
+ FreeAndNilTexture(tt^.Tex);
t:= tt;
tt:= tt^.NextGear;
Dispose(t)
@@ -652,66 +785,63 @@
i:= 0;
j:= 0;
-p:= 0; // 0 searching, 1 bad position, 2 added.
+p:= 0; // 0: good position, 1: bad position.
unplaced:= 0;
if cAirMines > 0 then
Gear:= AddGear(0, 0, gtAirMine, 0, _0, _0, 0);
-while (i < cAirMines) and (j < 1000*cAirMines) do
- begin
- p:= 0;
- if hasBorder then
- begin
- rx:= leftX+GetRandom(rightX-leftX-16)+8;
- ry:= topY+GetRandom(LAND_HEIGHT-topY-16)+8
- end
- else
+ while (i < cAirMines) and (j < 1000*cAirMines) do
begin
- rx:= leftX+GetRandom(rightX-leftX+400)-200;
- ry:= topY+GetRandom(LAND_HEIGHT-topY+400)-200
- end;
- Gear^.X:= int2hwFloat(rx);
- Gear^.Y:= int2hwFloat(ry);
- if CheckLandValue(rx, ry, $FFFF) and
- (TestCollisionYwithGear(Gear,-1) = 0) and
- (TestCollisionXwithGear(Gear, 1) = 0) and
- (TestCollisionXwithGear(Gear,-1) = 0) and
- (TestCollisionYwithGear(Gear, 1) = 0) then
- begin
- t:= 0;
- while (t < TeamsCount) and (p = 0) do
+ p:= 0;
+ if (hasBorder) or (WorldEdge = weBounce) then
+ rx:= leftX+GetRandom(rightX-leftX-16)+8
+ else
+ rx:= leftX+GetRandom(rightX-leftX+400)-200;
+ if hasBorder then
+ ry:= topY+GetRandom(LAND_HEIGHT-topY-16)+8
+ else
+ ry:= topY+GetRandom(LAND_HEIGHT-topY+400)-200;
+ Gear^.X:= int2hwFloat(CalcWorldWrap(rx,Gear^.Radius));
+ Gear^.Y:= int2hwFloat(ry);
+ if CheckLandValue(rx, ry, $FFFF) and
+ (TestCollisionYwithGear(Gear,-1) = 0) and
+ (TestCollisionXwithGear(Gear, 1) = 0) and
+ (TestCollisionXwithGear(Gear,-1) = 0) and
+ (TestCollisionYwithGear(Gear, 1) = 0) then
begin
- h:= 0;
- with TeamsArray[t]^ do
- while (h < cMaxHHIndex) and (p = 0) do
- begin
- if (Hedgehogs[h].Gear <> nil) then
+ t:= 0;
+ while (t < TeamsCount) and (p = 0) do
+ begin
+ h:= 0;
+ with TeamsArray[t]^ do
+ while (h <= cMaxHHIndex) and (p = 0) do
begin
- rdx:=Gear^.X-Hedgehogs[h].Gear^.X;
- rdy:=Gear^.Y-Hedgehogs[h].Gear^.Y;
- if (Gear^.Angle < $FFFFFFFF) and
- ((rdx.Round+rdy.Round < Gear^.Angle) and
- (hwRound(hwSqr(rdx) + hwSqr(rdy)) < sqr(Gear^.Angle))) then
+ if (Hedgehogs[h].Gear <> nil) then
begin
-// Debug line. Remove later
-// AddFileLog('Too Close to Hog @ (' + inttostr(rx) + ',' + inttostr(ry) + ')');
-
- p:= 1
- end
+ rdx:=Gear^.X-Hedgehogs[h].Gear^.X;
+ rdy:=Gear^.Y-Hedgehogs[h].Gear^.Y;
+ if (Gear^.Angle < $FFFFFFFF) and
+ ((rdx.Round+rdy.Round < Gear^.Angle) and
+ (hwRound(hwSqr(rdx) + hwSqr(rdy)) < sqr(Gear^.Angle))) then
+ begin
+ p:= 1
+ end
+ end;
+ inc(h)
end;
- inc(h)
- end;
- inc(t)
- end;
- if p = 0 then
- begin
- inc(i);
- AddFileLog('Placed Air Mine @ (' + inttostr(rx) + ',' + inttostr(ry) + ')');
- if i < cAirMines then
- Gear:= AddGear(0, 0, gtAirMine, 0, _0, _0, 0)
+ inc(t)
+ end;
+ if p = 0 then
+ begin
+ inc(i);
+ AddFileLog('Placed Air Mine @ (' + inttostr(rx) + ',' + inttostr(ry) + ')');
+ if i < cAirMines then
+ Gear:= AddGear(0, 0, gtAirMine, 0, _0, _0, 0)
+ end
end
+ else
+ p:= 1;
+ inc(j)
end;
- inc(j)
- end;
if p <> 0 then DeleteGear(Gear);
if (GameFlags and gfLowGravity) <> 0 then
@@ -744,7 +874,7 @@
rdx:= _90-(GetRandomf*_360);
rdy:= _90-(GetRandomf*_360);
Gear:= AddGear(rx, ry, gtGenericFaller, gstInvisible, rdx, rdy, $FFFFFFFF);
- // Tag=1: This allows this generic faller to be displaced randomly by events
+ // This allows this generic faller to be displaced randomly by events
Gear^.Tag:= 1;
end;
@@ -832,13 +962,15 @@
end;
procedure AssignHHCoords;
-var i, t, p, j: LongInt;
+var i, t, p, j, x, y: LongInt;
ar: array[0..Pred(cMaxHHs)] of PHedgehog;
Count: Longword;
divide, sectionDivide: boolean;
begin
if (GameFlags and gfPlaceHog) <> 0 then
- PlacingHogs:= true;
+ PlacingHogs:= true
+else if (GameFlags and gfKing) <> 0 then
+ PlacingKings:= true;
divide:= ((GameFlags and gfDivideTeams) <> 0);
@@ -875,9 +1007,10 @@
Unplaced:= true
else
FindPlace(Gear, false, t, t + playWidth div ClansCount, true);// could make Gear == nil;
+ if PlacingKings and King then
+ UnplacedKing:= true;
if Gear <> nil then
begin
- //AddCI(Gear); uncomment if new hogs should be able to spawn on top of old ones.
Gear^.Pos:= GetRandom(49);
// unless the world is wrapping, make outter teams face to map center
if (WorldEdge <> weWrap) and ((p = 0) or (p = ClansCount - 1)) then
@@ -910,11 +1043,11 @@
ar[i]^.Unplaced:= true
else
FindPlace(ar[i]^.Gear, false, leftX, rightX, true);
+ if PlacingKings and ar[i]^.King then
+ ar[i]^.UnplacedKing:= true;
if ar[i]^.Gear <> nil then
begin
- //AddCI(ar[i]^.Gear); uncomment if new hogs should be able to spawn on top of old ones
ar[i]^.Gear^.dX.isNegative:= hwRound(ar[i]^.Gear^.X) > leftX + playWidth div 2;
- ar[i]^.Gear^.Pos:= GetRandom(19)
end;
ar[i]:= ar[Count - 1];
dec(Count)
@@ -936,13 +1069,25 @@
with Hedgehogs[i] do
if (Gear <> nil) and (Gear^.State and gsttmpFlag <> 0) then
begin
- ForcePlaceOnLand(hwRound(Gear^.X) - SpritesData[sprTargetBee].Width div 2,
- hwRound(Gear^.Y) - SpritesData[sprTargetBee].Height div 2,
- sprTargetBee, 0, lfBasic, $FFFFFFFF, false, false, false);
- Gear^.Y:= int2hwFloat(hwRound(Gear^.Y) - 16 - Gear^.Radius);
+ // Get flower position
+ x:= hwRound(Gear^.X) - SpritesData[sprTargetBee].Width div 2;
+ y:= hwRound(Gear^.Y) - SpritesData[sprTargetBee].Height div 2;
+ // Calculate offset from map boundaries and border
+ if hasBorder then
+ x:= max(min(x, RightX - SpritesData[sprTargetBee].Width - cBorderWidth), LeftX + cBorderWidth)
+ else
+ x:= max(min(x, RightX - SpritesData[sprTargetBee].Width), LeftX);
+ y:= max(y, TopY);
+
+ // Place flower
+ ForcePlaceOnLand(x, y, sprTargetBee, 0, lfBasic, $FFFFFFFF, false, false, false);
+
+ // Place hog
+ Gear^.Y:= int2hwFloat(hwRound(Gear^.Y) - (SpritesData[sprTargetBee].Height div 2) - Gear^.Radius);
AddCI(Gear);
Gear^.State:= Gear^.State and (not gsttmpFlag);
- AddFileLog('Placed flower for hog at coordinates (' + inttostr(hwRound(Gear^.X)) + ',' + inttostr(hwRound(Gear^.Y)) + ')')
+
+ AddFileLog('Placed flower for hog at coordinates (' + inttostr(x) + ',' + inttostr(y) + ')')
end;
@@ -951,6 +1096,18 @@
SortHHsByClan();
end;
+// Set random pos for all hogs so their animations have different starting points
+procedure RandomizeHHAnim;
+var i, j, p: LongInt;
+begin
+ for p:= 0 to (ClansCount - 1) do
+ with SpawnClansArray[p]^ do
+ for j:= 0 to Pred(TeamsNumber) do
+ with Teams[j]^ do
+ for i:= 0 to cMaxHHIndex do
+ if (Hedgehogs[i].Gear <> nil) then
+ Hedgehogs[i].Gear^.Pos:= GetRandom(19);
+end;
{procedure AmmoFlameWork(Ammo: PGear);
var t: PGear;
@@ -974,79 +1131,94 @@
function SpawnCustomCrateAt(x, y: LongInt; crate: TCrateType; content, cnt: Longword): PGear;
+var gear: PGear;
begin
- FollowGear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ gear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ if(FinishedTurnsTotal > -1) then
+ FollowGear:= gear;
cCaseFactor := 0;
if (crate <> HealthCrate) and (content > ord(High(TAmmoType))) then
content := ord(High(TAmmoType));
- FollowGear^.Power:= cnt;
+ gear^.Power:= cnt;
case crate of
HealthCrate:
begin
- FollowGear^.Pos := posCaseHealth;
+ gear^.Pos := posCaseHealth;
+ gear^.RenderHealth:= true;
// health crate is smaller than the other crates
- FollowGear^.Radius := cCaseHealthRadius;
- FollowGear^.Health := content;
- AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo);
+ gear^.Radius := cCaseHealthRadius;
+ gear^.Health := content;
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
end;
AmmoCrate:
begin
- FollowGear^.Pos := posCaseAmmo;
- FollowGear^.AmmoType := TAmmoType(content);
- AddCaption(GetEventString(eidNewAmmoPack), cWhiteColor, capgrpAmmoInfo);
+ gear^.Pos := posCaseAmmo;
+ gear^.AmmoType := TAmmoType(content);
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
end;
UtilityCrate:
begin
- FollowGear^.Pos := posCaseUtility;
- FollowGear^.AmmoType := TAmmoType(content);
- AddCaption(GetEventString(eidNewUtilityPack), cWhiteColor, capgrpAmmoInfo);
+ gear^.Pos := posCaseUtility;
+ gear^.AmmoType := TAmmoType(content);
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewUtilityPack), capColDefault, capgrpAmmoInfo);
end;
end;
if ( (x = 0) and (y = 0) ) then
- FindPlace(FollowGear, true, 0, LAND_WIDTH);
+ FindPlace(gear, true, 0, LAND_WIDTH);
- SpawnCustomCrateAt := FollowGear;
+ SpawnCustomCrateAt := gear;
end;
function SpawnFakeCrateAt(x, y: LongInt; crate: TCrateType; explode: boolean; poison: boolean): PGear;
+var gear: PGear;
begin
- FollowGear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ gear := AddGear(x, y, gtCase, 0, _0, _0, 0);
+ if(FinishedTurnsTotal > -1) then
+ FollowGear:= gear;
cCaseFactor := 0;
- FollowGear^.Pos := posCaseDummy;
+ gear^.Pos := posCaseDummy;
if explode then
- FollowGear^.Pos := FollowGear^.Pos + posCaseExplode;
+ gear^.Pos := gear^.Pos + posCaseExplode;
if poison then
- FollowGear^.Pos := FollowGear^.Pos + posCasePoison;
+ gear^.Pos := gear^.Pos + posCasePoison;
case crate of
HealthCrate:
begin
- FollowGear^.Pos := FollowGear^.Pos + posCaseHealth;
+ gear^.Pos := gear^.Pos + posCaseHealth;
+ gear^.RenderHealth:= true;
+ gear^.Karma:= 2;
// health crate is smaller than the other crates
- FollowGear^.Radius := cCaseHealthRadius;
- AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo);
+ gear^.Radius := cCaseHealthRadius;
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
end;
AmmoCrate:
begin
- FollowGear^.Pos := FollowGear^.Pos + posCaseAmmo;
- AddCaption(GetEventString(eidNewAmmoPack), cWhiteColor, capgrpAmmoInfo);
+ gear^.Pos := gear^.Pos + posCaseAmmo;
+ if(FinishedTurnstotal > -1) then
+ AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
end;
UtilityCrate:
begin
- FollowGear^.Pos := FollowGear^.Pos + posCaseUtility;
- AddCaption(GetEventString(eidNewUtilityPack), cWhiteColor, capgrpAmmoInfo);
+ gear^.Pos := gear^.Pos + posCaseUtility;
+ if(FinishedTurnsTotal > -1) then
+ AddCaption(GetEventString(eidNewUtilityPack), capcolDefault, capgrpAmmoInfo);
end;
end;
if ( (x = 0) and (y = 0) ) then
- FindPlace(FollowGear, true, 0, LAND_WIDTH);
+ FindPlace(gear, true, 0, LAND_WIDTH);
- SpawnFakeCrateAt := FollowGear;
+ SpawnFakeCrateAt := gear;
end;
procedure StartSuddenDeath();
@@ -1076,7 +1248,7 @@
Ammoz[amTardis].SkipTurns:= 9999;
Ammoz[amTardis].Probability:= 0;
- AddCaption(trmsg[sidSuddenDeath], cWhiteColor, capgrpGameState);
+ AddCaption(trmsg[sidSuddenDeath], capcolDefault, capgrpGameState);
ScriptCall('onSuddenDeath');
playSound(sndSuddenDeath);
StopMusic;
@@ -1115,7 +1287,8 @@
or (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerWhileAttacking <> 0)
or ((GameFlags and gfInfAttack) <> 0) and (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerWhileAttackingInInfAttackMode <> 0)
or (CurrentHedgehog^.CurAmmoType = amSniperRifle))
- and (not(isInMultiShoot and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerInMultiShoot) <> 0)));
+ and (not(isInMultiShoot and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_DoesntStopTimerInMultiShoot) <> 0)))
+ and (not LuaClockPaused);
end;
@@ -1159,18 +1332,10 @@
if text = '' then text:= '...';
- (*
- if CheckNoTeamOrHH then
- begin
- ParseCommand('say ' + text, true);
- exit
- end;
- *)
-
if (x < 4) and (TeamsArray[t] <> nil) then
begin
// if team matches current hedgehog team, default to current hedgehog
- if (i = 0) and (CurrentHedgehog <> nil) and (CurrentHedgehog^.Team = TeamsArray[t]) then
+ if (i = 0) and (CurrentHedgehog <> nil) and (CurrentHedgehog^.Team = TeamsArray[t]) and (not CurrentHedgehog^.Unplaced) then
hh:= CurrentHedgehog
else
begin
@@ -1179,7 +1344,7 @@
c:= 0;
while (j <= cMaxHHIndex) and (hh = nil) do
begin
- if (TeamsArray[t]^.Hedgehogs[j].Gear <> nil) then
+ if (TeamsArray[t]^.Hedgehogs[j].Gear <> nil) and (not TeamsArray[t]^.Hedgehogs[j].Unplaced) then
begin
inc(c);
if (i=0) or (i=c) then
@@ -1197,8 +1362,7 @@
Gear^.Text:= text;
Gear^.FrameTicks:= x
end;
- //ParseCommand('/say [' + hh^.Name + '] '+text, true)
- AddChatString(#9+'[' + HH^.Name + '] '+text);
+ AddChatString(#9+Format(shortstring(trmsg[sidChatHog]), HH^.Name, text));
end
end
else if (x >= 4) then
@@ -1270,14 +1434,13 @@
@doStepNapalmBomb,
@doStepSnowball,
@doStepSnowflake,
- //@doStepStructure,
@doStepLandGun,
@doStepTardis,
@doStepIceGun,
@doStepAddAmmo,
@doStepGenericFaller,
@doStepKnife,
- @doStepDuck,
+ @doStepCreeper,
@doStepMinigun,
@doStepMinigunBullet);
begin
@@ -1302,10 +1465,9 @@
//typed const
delay:= 0;
delay2:= 0;
- step:= stDelay;
+ step:= stDelay1;
upd:= 0;
- //SDMusic:= 'hell.ogg';
NewTurnTick:= $FFFFFFFF;
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsHandlers.pas
--- a/hedgewars/uGearsHandlers.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsHandlers.pas Wed Jul 31 23:14:27 2019 +0200
@@ -27,7 +27,7 @@
implementation
-uses SDLh, uFloat, uCollisions;
+uses SDLh, uFloat, uCollisions, uVariables, uGearsUtils;
@@ -95,6 +95,28 @@
end
end;
+ // Handle world wrap and bounce edge manually
+ if (WorldEdge = weWrap) and
+ ((hwRound(Gear^.X) < leftX) or (hwRound(Gear^.X) > rightX)) then
+ begin
+ LeftImpactTimer:= 150;
+ RightImpactTimer:= 150;
+ Gear^.WDTimer:= 0;
+ Gear^.Karma:= 1;
+ end
+ else if (WorldEdge = weBounce) and
+ (((hwRound(Gear^.X) - Gear^.Radius) < leftX) or ((hwRound(Gear^.X) + Gear^.Radius) > rightX)) then
+ begin
+ if (hwRound(Gear^.X) - Gear^.Radius < leftX) then
+ LeftImpactTimer:= 333
+ else
+ RightImpactTimer:= 333;
+ Gear^.Karma:= 2;
+ Gear^.WDTimer:= 0;
+ if (Gear^.Radius > 2) and (Gear^.dX.QWordValue > _0_001.QWordValue) then
+ AddBounceEffectForGear(Gear);
+ end;
+
cakeStep:= Gear^.WDTimer < 4
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsHandlersMess.pas
--- a/hedgewars/uGearsHandlersMess.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsHandlersMess.pas Wed Jul 31 23:14:27 2019 +0200
@@ -32,7 +32,7 @@
uses uTypes, uFloat;
procedure doStepPerPixel(Gear: PGear; step: TGearStepProcedure; onlyCheckIfChanged: boolean);
-procedure makeHogsWorry(x, y: hwFloat; r: LongInt);
+procedure makeHogsWorry(x, y: hwFloat; r: LongInt; gearType: TGearType);
procedure HideHog(HH: PHedgehog);
procedure doStepDrowningGear(Gear: PGear);
procedure doStepFallingGear(Gear: PGear);
@@ -115,6 +115,7 @@
procedure doStepMovingPortal(Gear: PGear);
procedure doStepPortalShot(newPortal: PGear);
procedure doStepPiano(Gear: PGear);
+procedure doStepPianoWork(Gear: PGear);
procedure doStepSineGunShotWork(Gear: PGear);
procedure doStepSineGunShot(Gear: PGear);
procedure doStepFlamethrowerWork(Gear: PGear);
@@ -128,7 +129,6 @@
procedure doStepResurrectorWork(Gear: PGear);
procedure doStepResurrector(Gear: PGear);
procedure doStepNapalmBomb(Gear: PGear);
-//procedure doStepStructure(Gear: PGear);
procedure doStepTardisWarp(Gear: PGear);
procedure doStepTardis(Gear: PGear);
procedure updateFuel(Gear: PGear);
@@ -136,9 +136,8 @@
procedure doStepIceGun(Gear: PGear);
procedure doStepAddAmmo(Gear: PGear);
procedure doStepGenericFaller(Gear: PGear);
-//procedure doStepCreeper(Gear: PGear);
+procedure doStepCreeper(Gear: PGear);
procedure doStepKnife(Gear: PGear);
-procedure doStepDuck(Gear: PGear);
procedure doStepMinigunWork(Gear: PGear);
procedure doStepMinigun(Gear: PGear);
procedure doStepMinigunBullet(Gear: PGear);
@@ -202,10 +201,11 @@
end;
end;
-procedure makeHogsWorry(x, y: hwFloat; r: LongInt);
+procedure makeHogsWorry(x, y: hwFloat; r: LongInt; gearType: TGearType);
var
gi: PGear;
d: LongInt;
+ grenadeTaunt: boolean;
begin
gi := GearsList;
while gi <> nil do
@@ -216,7 +216,18 @@
if (d > 1) and (gi^.Hedgehog^.Effects[heInvulnerable] = 0) and (GetRandom(2) = 0) then
begin
if (CurrentHedgehog^.Gear = gi) then
- PlaySoundV(sndOops, gi^.Hedgehog^.Team^.voicepack)
+ if (CurrentHedgehog^.Gear^.FlightTime = 0) then
+ case random(4) of
+ 0: PlaySoundV(sndWhatThe, gi^.Hedgehog^.Team^.voicepack);
+ 1: PlaySoundV(sndOops, gi^.Hedgehog^.Team^.voicepack);
+ 2: PlaySoundV(sndRunAway, gi^.Hedgehog^.Team^.voicepack);
+ 3: PlaySoundV(sndRunAway, gi^.Hedgehog^.Team^.voicepack);
+ end
+ else
+ if random(4) = 0 then
+ PlaySoundV(sndWhatThe, gi^.Hedgehog^.Team^.voicepack)
+ else
+ PlaySoundV(sndOops, gi^.Hedgehog^.Team^.voicepack)
else
begin
@@ -226,10 +237,21 @@
gi^.State := gi^.State or gstLoser;
end;
- if d > r div 2 then
- PlaySoundV(sndNooo, gi^.Hedgehog^.Team^.voicepack)
+ grenadeTaunt:= false;
+ if (gearType = gtGrenade) then
+ grenadeTaunt:= random(2) = 0;
+
+ if grenadeTaunt then
+ PlaySoundV(sndGrenade, gi^.Hedgehog^.Team^.voicepack)
else
- PlaySoundV(sndUhOh, gi^.Hedgehog^.Team^.voicepack);
+ if d > r div 2 then
+ if random(3) = 0 then
+ PlaySoundV(sndWhatThe, gi^.Hedgehog^.Team^.voicepack)
+ else
+ PlaySoundV(sndNooo, gi^.Hedgehog^.Team^.voicepack)
+ else
+ PlaySoundV(sndUhOh, gi^.Hedgehog^.Team^.voicepack);
+
end;
end;
end;
@@ -287,7 +309,7 @@
if cWaterLine > hwRound(Gear^.Y) + Gear^.Radius then
begin
- if LongInt(leftX) + Gear^.Radius > hwRound(Gear^.X) then
+ if leftX + Gear^.Radius > hwRound(Gear^.X) then
Gear^.X := Gear^.X - cDrownSpeed
else
Gear^.X := Gear^.X + cDrownSpeed;
@@ -307,9 +329,41 @@
AddVisualGear(bubbleX, bubbleY, vgtBubble)
else if Random(12) = 0 then
AddVisualGear(bubbleX, bubbleY, vgtBubble);
+// Insta-delete gear and skip drowning animation if water is 100% opaque
if (not SuddenDeathDmg and (WaterOpacity > $FE))
-or (SuddenDeathDmg and (SDWaterOpacity > $FE))
-or (hwRound(Gear^.Y) > Gear^.Radius + cWaterLine + cVisibleWater) then
+or (SuddenDeathDmg and (SDWaterOpacity > $FE)) then
+ begin
+ // Teleport gear to a suitable position for the damage tag in the water
+ if (WorldEdge = weSea) and (hwRound(Gear^.X) - Gear^.Radius < leftX) then
+ begin
+ if (hwRound(Gear^.X) - Gear^.Radius > leftX - 90) then
+ Gear^.X := Gear^.X - _90
+ end
+ else if (WorldEdge = weSea) and (hwRound(Gear^.X) + Gear^.Radius > rightX) then
+ begin
+ if (hwRound(Gear^.X) - Gear^.Radius < rightX + 90) then
+ Gear^.X := Gear^.X + _90
+ end
+ else
+ Gear^.Y := int2hwFloat(Gear^.Radius + cWaterLine + cVisibleWater);
+ DeleteGear(Gear);
+ exit;
+ end;
+// Delete gear normally if it is outside of visible range.
+// But first determine size tolerance for big gears to make sure the gear is REALLY out of range.
+if Gear^.Kind = gtPiano then
+ d:= SpritesData[sprPiano].height
+else if Gear^.Kind = gtRCPlane then
+ d:= SpritesData[sprPlane].width
+else if Gear^.Kind = gtKnife then
+ d:= SpritesData[sprKnife].height
+else if Gear^.Kind = gtDynamite then
+ d:= SpritesData[sprDynamite].height
+else if Gear^.Kind = gtSnowball then
+ d:= SpritesData[sprSnowball].height
+else
+ d:= Gear^.Radius * 2;
+if (hwRound(Gear^.Y) > d + cWaterLine + cVisibleWater) then
DeleteGear(Gear);
end;
@@ -317,7 +371,6 @@
procedure doStepFallingGear(Gear: PGear);
var
isFalling: boolean;
- //tmp: QWord;
tX, tdX, tdY: hwFloat;
collV, collH, gX, gY: LongInt;
land, xland: word;
@@ -325,11 +378,20 @@
tX:= Gear^.X;
gX:= hwRound(Gear^.X);
gY:= hwRound(Gear^.Y);
- if (Gear^.Kind <> gtGenericFaller) and WorldWrap(Gear) and (WorldEdge = weWrap) and (Gear^.AdvBounce <> 0) and
+ Gear^.State := Gear^.State and (not gstCollision);
+
+ // World wrap
+ if (Gear^.Kind <> gtGenericFaller) and WorldWrap(Gear) and (WorldEdge = weWrap) and
((TestCollisionXwithGear(Gear, 1) <> 0) or (TestCollisionXwithGear(Gear, -1) <> 0)) then
begin
- Gear^.X:= tX;
- Gear^.dX.isNegative:= (gX > LongInt(leftX) + Gear^.Radius*2)
+ // Collision with land that *just* behind the other side of the world wrap edge
+ if (not Gear^.Sticky) then
+ begin
+ Gear^.X:= tX;
+ Gear^.dX.isNegative:= (gX > leftX + Gear^.Radius*2);
+ Gear^.dX := Gear^.dX * Gear^.Friction;
+ end;
+ Gear^.State := Gear^.State or gstCollision;
end;
// clip velocity at 2 - over 1 per pixel, but really shouldn't cause many actual problems.
@@ -344,7 +406,6 @@
Gear^.dY:= Gear^.dY * _0_999
end;
- Gear^.State := Gear^.State and (not gstCollision);
collV := 0;
collH := 0;
tdX := Gear^.dX;
@@ -430,10 +491,8 @@
xland:= TestCollisionXwithGear(Gear, -hwSign(Gear^.dX));
if xland <> 0 then collH := -hwSign(Gear^.dX)
end;
- //if Gear^.AdvBounce and (collV <>0) and (collH <> 0) and (hwSqr(tdX) + hwSqr(tdY) > _0_08) then
if (collV <> 0) and (collH <> 0) and
(((Gear^.AdvBounce=1) and ((collV=-1) or ((tdX.QWordValue + tdY.QWordValue) > _0_2.QWordValue)))) then
- //or ((xland or land) and lfBouncy <> 0)) then
begin
if (xland or land) and lfBouncy = 0 then
begin
@@ -486,7 +545,6 @@
Gear^.X := Gear^.X + Gear^.dX;
Gear^.Y := Gear^.Y + Gear^.dY;
CheckGearDrowning(Gear);
- //if (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) < _0_0002) and
if (not isFalling) and ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) < _0_02.QWordValue) then
Gear^.State := Gear^.State and (not gstMoving)
else
@@ -495,7 +553,7 @@
if ((xland or land) and lfBouncy <> 0) and (Gear^.dX.QWordValue < _0_15.QWordValue) and (Gear^.dY.QWordValue < _0_15.QWordValue) then
Gear^.State := Gear^.State or gstCollision;
- if ((xland or land) and lfBouncy <> 0) and (Gear^.Radius >= 3) and
+ if ((xland or land) and lfBouncy <> 0) and
((Gear^.dX.QWordValue > _0_15.QWordValue) or (Gear^.dY.QWordValue > _0_15.QWordValue)) then
begin
AddBounceEffectForGear(Gear);
@@ -526,8 +584,8 @@
gtGrenade,
gtClusterBomb,
gtWatermelon,
- gtHellishBomb: makeHogsWorry(Gear^.X, Gear^.Y, Gear^.Boom);
- gtGasBomb: makeHogsWorry(Gear^.X, Gear^.Y, 50);
+ gtHellishBomb: makeHogsWorry(Gear^.X, Gear^.Y, Gear^.Boom, Gear^.Kind);
+ gtGasBomb: makeHogsWorry(Gear^.X, Gear^.Y, 50, Gear^.Kind);
end;
if (Gear^.Kind = gtBall) and ((Gear^.State and gstTmpFlag) <> 0) then
@@ -664,25 +722,14 @@
gY := hwRound(Gear^.Y);
for i:= 0 to 4 do
begin
- (*glass:= AddVisualGear(gx+random(7)-3, gy+random(5)-2, vgtEgg);
- if glass <> nil then
- begin
- glass^.Frame:= 2;
- glass^.Tint:= $41B83ED0 - i * $10081000;
- glass^.dX:= 1/(10*(random(11)-5));
- glass^.dY:= -1/(random(4)+5);
- end;*)
- glass:= AddVisualGear(gx+random(7)-3, gy+random(7)-3, vgtStraightShot);
+ glass:= AddVisualGear(gx+random(7)-3, gy+random(7)-3, vgtEgg);
if glass <> nil then
with glass^ do
begin
Frame:= 2;
Tint:= $41B83ED0 - i * $10081000;
+ dX:= dX + hwFloat2Float(Gear^.dX) / 2;
Angle:= random(360);
- dx:= 0.0000001;
- dy:= 0;
- if random(2) = 0 then
- dx := -dx;
FrameTicks:= 750;
State:= ord(sprEgg)
end;
@@ -794,9 +841,9 @@
draw:= true;
xx:= hwRound(Gear^.X);
yy:= hwRound(Gear^.Y);
- if draw and (WorldEdge = weWrap) and ((xx < LongInt(leftX) + 3) or (xx > LongInt(rightX) - 3)) then
- begin
- if xx < LongInt(leftX) + 3 then
+ if draw and (WorldEdge = weWrap) and ((xx < leftX + 3) or (xx > rightX - 3)) then
+ begin
+ if xx < leftX + 3 then
xx:= rightX-3
else xx:= leftX+3;
Gear^.X:= int2hwFloat(xx)
@@ -819,17 +866,6 @@
else if 360 < DirAngle then
DirAngle := DirAngle - 360;
end;
-(*
-We aren't using frametick right now, so just a waste of cycles.
- inc(Health, 8);
- if longword(Health) > vobFrameTicks then
- begin
- dec(Health, vobFrameTicks);
- inc(Timer);
- if Timer = vobFramesCount then
- Timer:= 0
- end;
-*)
// move back to cloud layer
if CheckCoordInWater(xx, yy) then
move:= true
@@ -998,11 +1034,14 @@
var
t: hwFloat;
gX,gY,i: LongInt;
- uw, nuw: boolean;
+ uw, nuw, wrapped: boolean;
flower: PVisualGear;
begin
- WorldWrap(Gear);
+ wrapped:= WorldWrap(Gear);
+ if wrapped then
+ HomingWrap(Gear);
+
AllInactive := false;
gX := hwRound(Gear^.X);
gY := hwRound(Gear^.Y);
@@ -1030,7 +1069,7 @@
if Gear^.Timer = 0 then
begin
- // no "fuel"? just fall
+ // no energy? just fall
doStepFallingGear(Gear);
// if drowning, stop bee sound
if (Gear^.State and gstDrowning) <> 0 then
@@ -1097,11 +1136,14 @@
end;
procedure doStepBee(Gear: PGear);
+var wrapped: boolean;
begin
AllInactive := false;
Gear^.X := Gear^.X + Gear^.dX;
Gear^.Y := Gear^.Y + Gear^.dY;
- WorldWrap(Gear);
+ wrapped:= WorldWrap(Gear);
+ if wrapped then
+ HomingWrap(Gear);
Gear^.dY := Gear^.dY + cGravity;
CheckGearDrowning(Gear);
CheckCollision(Gear);
@@ -1140,9 +1182,7 @@
end
end
else
- begin
DeleteGear(Gear);
- end
end;
procedure CreateShellForGear(Gear: PGear; startFrame: Longword);
@@ -1160,9 +1200,33 @@
end;
end;
+function ShotgunLineHitHelp(Gear: PGear; oX, oY, tX, tY: hwFloat): Boolean;
+var i: LongInt;
+ Collisions: PGearArray;
+begin
+ ShotgunLineHitHelp := false;
+ Collisions := CheckAllGearsLineCollision(Gear, oX, oY, tX, tY);
+ i := Collisions^.Count;
+ while i > 0 do
+ begin
+ dec(i);
+ if Collisions^.ar[i]^.Kind in
+ [gtMine, gtSMine, gtAirMine, gtKnife, gtCase, gtTarget, gtExplosives] then
+ begin
+ Gear^.X := Collisions^.ar[i]^.X;
+ Gear^.Y := Collisions^.ar[i]^.Y;
+ ShotgunShot(Gear);
+ Gear^.doStep := @doStepShotIdle;
+ ShotgunLineHitHelp := true;
+ exit;
+ end;
+ end;
+end;
+
procedure doStepShotgunShot(Gear: PGear);
var
i: LongWord;
+ oX, oY, tmpX, tmpY: hwFloat;
begin
AllInactive := false;
@@ -1186,29 +1250,60 @@
else
inc(Gear^.Timer);
- i := 200;
+ i := 100;
+ oX := Gear^.X;
+ oY := Gear^.Y;
repeat
- Gear^.X := Gear^.X + Gear^.dX;
- Gear^.Y := Gear^.Y + Gear^.dY;
- WorldWrap(Gear);
+ if Gear^.Tag = 0 then
+ begin
+ Gear^.X := Gear^.X + Gear^.dX;
+ Gear^.Y := Gear^.Y + Gear^.dY;
+ end;
+
+ tmpX := Gear^.X;
+ tmpY := Gear^.Y;
+ if (Gear^.PortalCounter < 30) and WorldWrap(Gear) then
+ begin
+ inc(Gear^.PortalCounter);
+ if ShotgunLineHitHelp(Gear, oX, oY, tmpX, tmpY) then
+ exit;
+ oX := Gear^.X;
+ oY := Gear^.Y;
+ end;
CheckCollision(Gear);
- if (Gear^.State and gstCollision) <> 0 then
- begin
- Gear^.X := Gear^.X + Gear^.dX * 8;
- Gear^.Y := Gear^.Y + Gear^.dY * 8;
- ShotgunShot(Gear);
- Gear^.doStep := @doStepShotIdle;
- exit
- end;
+
+ if ((Gear^.State and gstCollision) <> 0) then
+ begin
+ if Gear^.Tag = 0 then
+ begin
+ //Try to align the shot with the land to give portals a chance to catch it
+ Gear^.X := Gear^.X + Gear^.dX * 2;
+ Gear^.Y := Gear^.Y + Gear^.dY * 2;
+ Gear^.Tag := 1
+ end
+ else
+ begin
+ Gear^.X := Gear^.X + Gear^.dX * 6;
+ Gear^.Y := Gear^.Y + Gear^.dY * 6;
+ ShotgunShot(Gear);
+ Gear^.doStep := @doStepShotIdle;
+ end;
+ exit
+ end
+ else
+ Gear^.Tag := 0;
CheckGearDrowning(Gear);
if (Gear^.State and gstDrowning) <> 0 then
begin
Gear^.doStep := @doStepShotIdle;
- exit
+ break;
end;
dec(i)
until i = 0;
+
+ ShotgunLineHitHelp(Gear, oX, oY, Gear^.X, Gear^.Y);
+
if (hwRound(Gear^.X) and LAND_WIDTH_MASK <> 0) or (hwRound(Gear^.Y) and LAND_HEIGHT_MASK <> 0) then
Gear^.doStep := @doStepShotIdle
end;
@@ -1301,8 +1396,7 @@
procedure LineShoveHelp(Gear: PGear; oX, oY, tX, tY, dX, dY: hwFloat; count: LongWord);
var dmg,power: LongInt;
begin
- if ((Gear^.Kind = gtMinigunBullet) or (Gear^.Damage > 0))
- and (hwSqr(tX - oX) + hwSqr(tY - oY) > _0_25) then
+ if hwSqr(tX - oX) + hwSqr(tY - oY) > _0_25 then
begin
if (Gear^.AmmoType = amDEagle) or (Gear^.AmmoType = amMinigun) then
dmg:= Gear^.Boom
@@ -1322,6 +1416,48 @@
end;
end;
+procedure CheckBulletDrowningHelp(Bullet: PGear);
+var dX, dY: hwFloat;
+begin
+ dX := Bullet^.dX;
+ dY := Bullet^.dY;
+ CheckGearDrowning(Bullet);
+ if (dX <> Bullet^.dX) or (dY <> Bullet^.dY) then
+ begin
+ SpawnBulletTrail(Bullet, Bullet^.X, Bullet^.Y, Bullet^.FlightTime = 0);
+ Bullet^.Elasticity := Bullet^.X;
+ Bullet^.Friction := Bullet^.Y;
+ Inc(Bullet^.PortalCounter);
+ Bullet^.FlightTime:= 1;
+ end;
+end;
+
+procedure CreateBubblesForBullet(Gear: PGear);
+var i, iInit: LongWord;
+begin
+iInit:= 0;
+if ((Gear^.State and gstDrowning) <> 0) and (Gear^.Health > 0) then
+ begin
+ // draw bubbles
+ if (not SuddenDeathDmg and (WaterOpacity < $FF)) or (SuddenDeathDmg and (SDWaterOpacity < $FF)) then
+ begin
+ case Gear^.Kind of
+ gtMinigunBullet: iInit:= Gear^.Health * 100;
+ gtDEagleShot, gtSniperRifleShot: iInit:= Gear^.Health * 4
+ end;
+ for i:=iInit downto 0 do
+ begin
+ if Random(6) = 0 then
+ AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBubble);
+ Gear^.X := Gear^.X + Gear^.dX;
+ Gear^.Y := Gear^.Y + Gear^.dY;
+ end;
+ end;
+ // bullet dies underwater
+ Gear^.Health:= 0;
+ end;
+end;
+
procedure doStepBulletWork(Gear: PGear);
var
i, x, y, iInit: LongWord;
@@ -1378,7 +1514,7 @@
dec(Gear^.Damage);
Gear^.X := Gear^.X - Gear^.dX;
Gear^.Y := Gear^.Y - Gear^.dY;
- CheckGearDrowning(Gear);
+ CheckBulletDrowningHelp(Gear);
break;
end
else if (not isDigging) then
@@ -1395,67 +1531,72 @@
oY:= Gear^.Y;
end;
- CheckGearDrowning(Gear);
+ CheckBulletDrowningHelp(Gear);
case Gear^.Kind of
- gtMinigunBullet: isDead:= isDigging;
- gtDEagleShot, gtSniperRifleShot: isDead:= Gear^.Damage >= Gear^.Health;
+ gtMinigunBullet: isDead:= isDigging or ((Gear^.State and gstDrowning) <> 0);
+ gtDEagleShot, gtSniperRifleShot: isDead:= (Gear^.Damage >= Gear^.Health) or ((Gear^.State and gstDrowning) <> 0)
end;
dec(i)
- until (i = 0) or (isDead) or ((Gear^.State and gstDrowning) <> 0);
+ until (i = 0) or (isDead);
LineShoveHelp(Gear, oX, oY, Gear^.X, Gear^.Y,
Gear^.dX, Gear^.dY, iInit + 2 - i);
- if ((Gear^.State and gstDrowning) <> 0) and (Gear^.Health > 0) then
- begin
- // draw bubbles
- if (not SuddenDeathDmg and (WaterOpacity < $FF)) or (SuddenDeathDmg and (SDWaterOpacity < $FF)) then
- begin
- for i:=(Gear^.Health * 4) downto 0 do
+ CreateBubblesForBullet(Gear);
+
+ x := hwRound(Gear^.X);
+ y := hwRound(Gear^.Y);
+ if (isDead) or (x and LAND_WIDTH_MASK <> 0) or (y and LAND_HEIGHT_MASK <> 0) then
+ begin
+ if (Gear^.Kind = gtSniperRifleShot) then
+ cLaserSightingSniper := false;
+ if (Ammoz[Gear^.AmmoType].Ammo.NumPerTurn <= CurrentHedgehog^.MultiShootAttacks) and (CurrentHedgehog^.Effects[heArtillery] = 2) then
+ CurrentHedgehog^.Effects[heArtillery]:= 0;
+
+ // Bullet Hit
+ if ((Gear^.State and gstDrowning) = 0) and (x and LAND_WIDTH_MASK = 0) and (y and LAND_HEIGHT_MASK = 0) then
+ begin
+ if Gear^.Kind = gtMinigunBullet then
begin
- if Random(6) = 0 then
- AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBubble);
- Gear^.X := Gear^.X + Gear^.dX;
- Gear^.Y := Gear^.Y + Gear^.dY;
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 5,
+ Gear^.Hedgehog, (EXPLNoDamage or EXPLDoNotTouchHH){ or EXPLDontDraw or EXPLNoGfx});
+ VGear := AddVisualGear(hwRound(Gear^.X + Gear^.dX * 5), hwRound(Gear^.Y + Gear^.dY * 5), vgtBulletHit);
+ end
+ else
+ VGear := AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBulletHit);
+
+ if VGear <> nil then
+ begin
+ VGear^.Angle := DxDy2Angle(-Gear^.dX, Gear^.dY);
end;
end;
- // bullet dies underwater
- Gear^.Health:= 0;
- end;
-
- if (isDead)
- or (hwRound(Gear^.X) and LAND_WIDTH_MASK <> 0)
- or (hwRound(Gear^.Y) and LAND_HEIGHT_MASK <> 0) then
- begin
- if (Gear^.Kind = gtSniperRifleShot) then
- cLaserSightingSniper := false;
- if (Ammoz[Gear^.AmmoType].Ammo.NumPerTurn <= CurrentHedgehog^.MultiShootAttacks) and (CurrentHedgehog^.Effects[heArtillery] = 2) then
- CurrentHedgehog^.Effects[heArtillery]:= 0;
-
- // Bullet Hit
- if ((Gear^.State and gstDrowning) = 0) and (hwRound(Gear^.X) and LAND_WIDTH_MASK = 0) and (hwRound(Gear^.Y) and LAND_HEIGHT_MASK = 0) then
+
+ spawnBulletTrail(Gear, Gear^.X, Gear^.Y, Gear^.FlightTime = 0);
+ Gear^.FlightTime:= 1;
+ if Gear^.Kind = gtMinigunBullet then
+ ClearHitOrderLeq(Gear^.Tag);
+
+ if (worldEdge = weSea) and (Gear^.Kind = gtMinigunBullet)
+ and Gear^.Y.isNegative and Gear^.dY.isNegative
+ and (Gear^.Health > 0) and (not isZero(Gear^.dX)) then
+ begin
+ if Gear^.dX.isNegative then
begin
- if Gear^.Kind = gtMinigunBullet then
- begin
- doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 5,
- Gear^.Hedgehog, EXPLNoDamage{ or EXPLDontDraw or EXPLNoGfx});
- VGear := AddVisualGear(hwRound(Gear^.X + Gear^.dX * 5), hwRound(Gear^.Y + Gear^.dY * 5), vgtBulletHit);
- end
- else
- VGear := AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBulletHit);
-
- if VGear <> nil then
- begin
- VGear^.Angle := DxDy2Angle(-Gear^.dX, Gear^.dY);
- end;
+
+ Gear^.X:= int2hwFloat(-1);
+ iInit:= x - leftX;
+ end
+ else
+ begin
+ Gear^.X:= int2hwFloat(LAND_WIDTH);
+ iInit:= rightX - x - 1;
end;
-
- spawnBulletTrail(Gear, Gear^.X, Gear^.Y, Gear^.FlightTime = 0);
- Gear^.FlightTime:= 1;
- if Gear^.Kind = gtMinigunBullet then
- ClearHitOrderLeq(Gear^.Tag);
- Gear^.doStep := @doStepShotIdle
- end;
+ Gear^.Y:= Gear^.Y + Gear^.dY * hwAbs(int2hwFloat(iInit) / Gear^.dX);
+ CheckGearDrowning(Gear);
+ CreateBubblesForBullet(Gear);
+ end;
+ Gear^.doStep := @doStepShotIdle
+ end;
end;
procedure doStepDEagleShot(Gear: PGear);
@@ -1466,6 +1607,7 @@
Gear^.Data:= Pointer(Gear^.Hedgehog^.Gear);
PlaySound(sndGun);
+ ClearHitOrder();
// add 2 initial steps to avoid problem with ammoshove related to calculation of radius + 1 radius as gear widths, and also just plain old weird angles
Gear^.X := Gear^.X + Gear^.dX * 2;
Gear^.Y := Gear^.Y + Gear^.dY * 2;
@@ -1508,6 +1650,7 @@
Gear^.dX := SignAs(AngleSin(HHGear^.Angle), HHGear^.dX) * _0_5;
Gear^.dY := -AngleCos(HHGear^.Angle) * _0_5;
PlaySound(sndGun);
+ ClearHitOrder();
// add 2 initial steps to avoid problem with ammoshove related to calculation of radius + 1 radius as gear widths, and also just weird angles
Gear^.X := Gear^.X + Gear^.dX * 2;
Gear^.Y := Gear^.Y + Gear^.dY * 2;
@@ -1543,7 +1686,7 @@
AllInactive := false;
if Gear^.Timer = 0 then
begin
- AddCaption(GetEventString(eidRoundStart), cWhiteColor, capgrpGameState);
+ AddCaption(GetEventString(eidRoundStart), capcolDefault, capgrpGameState);
end
end;
gtATFinishGame:
@@ -1558,6 +1701,8 @@
if Gear^.Timer = 0 then
begin
SendIPC(_S'N');
+ if (luaCmdUsed) then
+ SendIPC(_S'm');
SendIPC(_S'q');
GameState := gsExit
end
@@ -1608,7 +1753,7 @@
ei := x + Gear^.Radius + LongInt(GetRandom(2));
while i <= ei do
begin
- DrawExplosion(i, y + 3, 3);
+ doMakeExplosion(i, y + 3, 3, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx or EXPLForceDraw);
inc(i, 1)
end;
@@ -1821,6 +1966,7 @@
dmg: LongWord;
begin
if Gear^.Health = 0 then dxdy:= hwAbs(Gear^.dX)+hwAbs(Gear^.dY);
+ Gear^.RenderTimer:= ((Gear^.State and gstFrozen) = 0) and ((Gear^.State and gstAttacking) = 0) and (Gear^.Health <> 0);
if (Gear^.State and gstMoving) <> 0 then
begin
DeleteCI(Gear);
@@ -1887,7 +2033,8 @@
or (getRandom(100) > cMineDudPercent) then
begin
doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
- DeleteGear(Gear)
+ DeleteGear(Gear);
+ exit // redundant but we've had too many delete gear bugs
end
else
begin
@@ -1915,7 +2062,29 @@
targ, tmpG: PGear;
trackSpeed, airFriction, tX, tY: hwFloat;
isUnderwater: Boolean;
+ sparkle: PVisualGear;
begin
+ targ:= nil;
+ Gear^.RenderTimer:= ((Gear^.State and gstFrozen) = 0) and ((Gear^.State and gstAttacking) = 0);
+ if (Gear^.State and gstFrozen) <> 0 then
+ begin
+ if Gear^.Damage > 0 then
+ begin
+ // Normal, damaging explosion
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
+ if ((Gear^.State and gstNoGravity) <> 0) then
+ // Remove land created by frozen air mine sprite pixel-perfectly
+ EraseLand(
+ hwRound(Gear^.X) - SpritesData[sprFrozenAirMine].Width div 2,
+ hwRound(Gear^.Y) - SpritesData[sprFrozenAirMine].Height div 2,
+ sprFrozenAirMine, 0, 0, false, false, false, false);
+ DeleteGear(Gear);
+ exit
+ end;
+ if ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) or ((GameTicks and $3F) = 15) then
+ doStepFallingGear(Gear);
+ exit
+ end;
isUnderwater:= CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y) + Gear^.Radius);
if Gear^.Pos > 0 then
begin
@@ -1927,10 +2096,55 @@
Gear^.dX:= Gear^.dX*airFriction;
Gear^.dY:= Gear^.dY*airFriction
end;
- doStepFallingGear(Gear);
+ if ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) or ((GameTicks and $3F) = 15) then
+ doStepFallingGear(Gear);
if (TurnTimeLeft = 0) and ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) then
AllInactive := false;
+ // Disable targeting if airmine is not active yet
+ if ((Gear^.State and gsttmpFlag) = 0) then
+ begin
+ if (TurnTimeLeft = 0)
+ or ((GameFlags and gfInfAttack <> 0) and (GameTicks > Gear^.FlightTime))
+ or (CurrentHedgehog^.Gear = nil) then
+ begin
+ Gear^.FlightTime:= GameTicks;
+ Gear^.State := Gear^.State or gsttmpFlag;
+ Gear^.Hedgehog := nil;
+ end;
+ exit;
+ end;
+
+ //Disable targeting while the airmine is stunned
+ if Gear^.Tag <> 0 then
+ begin
+ if ((Gear^.FlightTime and $FF) = 0) then
+ // spawn lots of particles when stunned (sparkles or bubbles)
+ if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) = false then
+ begin
+ sparkle:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtDust, 1);
+ if sparkle <> nil then
+ begin
+ sparkle^.dX:= 0.004 * (random(100) - 50);
+ sparkle^.dY:= -0.05 + 0.004 * (random(100) - 50);
+ sparkle^.Tint:= $D5CD8CFF;
+ sparkle^.Angle:= random(360);
+ end;
+ end
+ else
+ AddVisualGear(hwRound(Gear^.X) - 8 + random(16), hwRound(Gear^.Y) + 16 + random(8), vgtBubble);
+
+ dec(Gear^.FlightTime);
+ if Gear^.FlightTime = 0 then
+ begin
+ Gear^.Tag:= 0;
+ Gear^.Hedgehog:= nil;
+ Gear^.State:= Gear^.State and (not gstAttacking);
+ Gear^.Timer:= Gear^.WDTimer
+ end;
+ exit
+ end;
+
if (TurnTimeLeft = 0) or (Gear^.Angle = 0) or (Gear^.Hedgehog = nil) or (Gear^.Hedgehog^.Gear = nil) then
begin
Gear^.Hedgehog:= nil;
@@ -1943,18 +2157,17 @@
tX:=Gear^.X-targ^.X;
tY:=Gear^.Y-targ^.Y;
// allow escaping - should maybe flag this too
- if (GameTicks > Gear^.FlightTime+10000) or
- ((tX.Round+tY.Round > Gear^.Angle*6) and
- (hwRound(hwSqr(tX) + hwSqr(tY)) > sqr(Gear^.Angle*6))) then
+ if (GameTicks > Gear^.FlightTime + 10000) or
+ (not ((tX.Round + tY.Round < Gear^.Angle * 9) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Angle * 6))))
+ then
targ:= nil
end;
// If in ready timer, or after turn, or in first 5 seconds of turn (really a window due to extra time utility)
- // or mine is inactive due to lack of gsttmpflag or hunting is disabled due to seek radius of 0
- // then we aren't hunting
+ // or hunting is disabled due to seek radius of 0 then we aren't hunting
if (ReadyTimeLeft > 0) or (TurnTimeLeft = 0) or
((TurnTimeLeft < cHedgehogTurnTime) and (cHedgehogTurnTime-TurnTimeLeft < 5000)) or
- (Gear^.State and gsttmpFlag = 0) or
(Gear^.Angle = 0) then
gear^.State:= gear^.State and (not gstChooseTarget)
else if
@@ -1970,7 +2183,7 @@
for t:= 0 to Pred(TeamsCount) do
with TeamsArray[t]^ do
for i:= 0 to cMaxHHIndex do
- if Hedgehogs[i].Gear <> nil then
+ if (Hedgehogs[i].Gear <> nil) and (Hedgehogs[i].Effects[heFrozen] = 0) then
begin
tmpG:= Hedgehogs[i].Gear;
tX:=Gear^.X-tmpG^.X;
@@ -2015,68 +2228,57 @@
end
else Gear^.Hedgehog:= nil;
- if ((Gear^.State and gsttmpFlag) <> 0) and (Gear^.Health <> 0) then
- begin
- if ((Gear^.State and gstAttacking) = 0) then
- begin
- if ((GameTicks and $1F) = 0) then
+ if ((Gear^.State and gstAttacking) = 0) then
+ begin
+ if (((GameTicks+Gear^.Uid) and $1F) = 0) then
+ begin
+ if targ <> nil then
begin
- if targ <> nil then
- begin
- tX:=Gear^.X-targ^.X;
- tY:=Gear^.Y-targ^.Y;
- if (tX.Round+tY.Round < Gear^.Karma) and
- (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Karma)) then
- Gear^.State := Gear^.State or gstAttacking
- end
- else if (Gear^.Angle > 0) and (CheckGearNear(Gear, gtHedgehog, Gear^.Karma, Gear^.Karma) <> nil) then
- Gear^.State := Gear^.State or gstAttacking
+ tX:=Gear^.X-targ^.X;
+ tY:=Gear^.Y-targ^.Y;
+ if (tX.Round+tY.Round < Gear^.Boom) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Boom)) then
+ Gear^.State := Gear^.State or gstAttacking
end
+ else if (Gear^.Angle > 0) and (CheckGearNear(Gear, gtHedgehog, Gear^.Boom, Gear^.Boom) <> nil) then
+ Gear^.State := Gear^.State or gstAttacking
end
- else // gstAttacking <> 0
- begin
- AllInactive := false;
- if (Gear^.Timer and $FF) = 0 then
- PlaySound(sndMineTick);
- if Gear^.Timer = 0 then
+ end
+ else // gstAttacking <> 0
+ begin
+ AllInactive := false;
+ if (Gear^.Timer and $FF) = 0 then
+ PlaySound(sndMineTick);
+ if Gear^.Timer = 0 then
+ begin
+ // recheck
+ if targ <> nil then
begin
- // recheck
- if targ <> nil then
- begin
- tX:=Gear^.X-targ^.X;
- tY:=Gear^.Y-targ^.Y;
- if (tX.Round+tY.Round < Gear^.Karma) and
- (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Karma)) then
- begin
- Gear^.Hedgehog:= CurrentHedgehog;
- tmpG:= FollowGear;
- doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Karma, Gear^.Hedgehog, EXPLAutoSound);
- FollowGear:= tmpG;
- DeleteGear(Gear);
- exit
- end
- end
- else if (Gear^.Angle > 0) and (CheckGearNear(Gear, gtHedgehog, Gear^.Karma, Gear^.Karma) <> nil) then
+ tX:=Gear^.X-targ^.X;
+ tY:=Gear^.Y-targ^.Y;
+ if (tX.Round+tY.Round < Gear^.Boom) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Boom)) then
begin
Gear^.Hedgehog:= CurrentHedgehog;
- doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Karma, Gear^.Hedgehog, EXPLAutoSound);
+ tmpG:= FollowGear;
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
+ FollowGear:= tmpG;
DeleteGear(Gear);
exit
- end;
- Gear^.State:= Gear^.State and (not gstAttacking);
- Gear^.Timer:= Gear^.WDTimer
+ end
+ end
+ else if (Gear^.Angle > 0) and (CheckGearNear(Gear, gtHedgehog, Gear^.Boom, Gear^.Boom) <> nil) then
+ begin
+ Gear^.Hedgehog:= CurrentHedgehog;
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
+ DeleteGear(Gear);
+ exit
end;
- if Gear^.Timer > 0 then
- dec(Gear^.Timer);
- end
- end
- else // gsttmpFlag = 0
- if (TurnTimeLeft = 0)
- or ((GameFlags and gfInfAttack <> 0) and (GameTicks > Gear^.FlightTime))
- or (CurrentHedgehog^.Gear = nil) then
- begin
- Gear^.FlightTime:= GameTicks;
- Gear^.State := Gear^.State or gsttmpFlag
+ Gear^.State:= Gear^.State and (not gstAttacking);
+ Gear^.Timer:= Gear^.WDTimer
+ end;
+ if Gear^.Timer > 0 then
+ dec(Gear^.Timer);
end
end;
@@ -2109,6 +2311,7 @@
CalcRotationDirAngle(Gear);
end;
+ Gear^.RenderTimer:= (Gear^.State and (gstFrozen or gstAttacking or gstDrowning) = 0);
if ((Gear^.State and gsttmpFlag) <> 0) and (Gear^.Health <> 0) then
begin
if ((Gear^.State and gstAttacking) = 0) and ((Gear^.State and gstFrozen) = 0) then
@@ -2148,7 +2351,7 @@
if Gear^.Timer mod 166 = 0 then
inc(Gear^.Tag);
if Gear^.Timer = 1000 then // might need better timing
- makeHogsWorry(Gear^.X, Gear^.Y, 75);
+ makeHogsWorry(Gear^.X, Gear^.Y, 75, Gear^.Kind);
if Gear^.Timer = 0 then
begin
doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
@@ -2195,7 +2398,6 @@
ScriptCall('onGearDamage', Gear^.UID, dmg)
end;
CalcRotationDirAngle(Gear);
- //CheckGearDrowning(Gear)
end
else
begin
@@ -2203,22 +2405,6 @@
AddCI(Gear)
end;
-(*
-Attempt to make a barrel knock itself over an edge. Would need more checks to avoid issues like burn damage
- begin
- x:= hwRound(Gear^.X);
- y:= hwRound(Gear^.Y);
- if (((y+1) and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then
- if (Land[y+1, x] = 0) then
- begin
- if (((y+1) and LAND_HEIGHT_MASK) = 0) and (((x+Gear^.Radius-2) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x+Gear^.Radius-2] = 0) then
- Gear^.dX:= -_0_08
- else if (((y+1 and LAND_HEIGHT_MASK)) = 0) and (((x-(Gear^.Radius-2)) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x-(Gear^.Radius-2)] = 0) then
- Gear^.dX:= _0_08;
- end;
- if Gear^.dX.QWordValue = 0 then AddCI(Gear)
- end; *)
-
if not Gear^.dY.isNegative and (Gear^.dY < _0_001) and (TestCollisionYwithGear(Gear, 1) <> 0) then
Gear^.dY := _0;
if hwAbs(Gear^.dX) < _0_001 then
@@ -2229,10 +2415,19 @@
AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke)
else
AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite);
+
dec(Gear^.Health, Gear^.Damage);
Gear^.Damage := 0;
if Gear^.Health <= 0 then
- doStepCase(Gear);
+ doStepCase(Gear)
+ else
+ // health texture (FlightTime = health when the last texture was generated)
+ if (Gear^.Health <> Gear^.FlightTime) or (Gear^.Tex = nil) then
+ begin
+ Gear^.FlightTime:= Gear^.Health;
+ FreeAndNilTexture(Gear^.Tex);
+ Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff808080, fnt16);
+ end;
end;
procedure doStepCase(Gear: PGear);
@@ -2264,9 +2459,6 @@
y := hwRound(Gear^.Y);
hog:= Gear^.Hedgehog;
- DeleteGear(Gear);
- // <-- delete gear!
-
if k = gtCase then
begin
doMakeExplosion(x, y, Gear^.Boom, hog, EXPLAutoSound);
@@ -2276,22 +2468,22 @@
else if k = gtTarget then
uStats.TargetHit()
else if k = gtExplosives then
+ begin
+ doMakeExplosion(x, y, Gear^.Boom, hog, EXPLAutoSound);
+ for i:= 0 to 31 do
begin
- doMakeExplosion(x, y, Gear^.Boom, hog, EXPLAutoSound);
- for i:= 0 to 31 do
- begin
- dX := AngleCos(i * 64) * _0_5 * (getrandomf + _1);
- dY := AngleSin(i * 64) * _0_5 * (getrandomf + _1);
- AddGear(x, y, gtFlame, 0, dX, dY, 0);
- AddGear(x, y, gtFlame, gstTmpFlag, -dX, -dY, 0);
- end
- end;
- exit
+ dX := AngleCos(i * 64) * _0_5 * (getrandomf + _1);
+ dY := AngleSin(i * 64) * _0_5 * (getrandomf + _1);
+ AddGear(x, y, gtFlame, 0, dX, dY, 0);
+ AddGear(x, y, gtFlame, gstTmpFlag, -dX, -dY, 0);
+ end
+ end;
+ DeleteGear(Gear);
+ exit
end;
if k = gtExplosives then
begin
- //if V > _0_03 then Gear^.State:= Gear^.State or gstAnimation;
if (hwAbs(Gear^.dX) > _0_15) or ((hwAbs(Gear^.dY) > _0_15) and (hwAbs(Gear^.dX) > _0_02)) then
begin
Gear^.doStep := @doStepRollingBarrel;
@@ -2304,11 +2496,56 @@
AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke)
else
AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite);
+
dec(Gear^.Health, Gear^.Damage);
Gear^.Damage := 0;
+ // health texture (FlightTime = health when the last texture was generated)
+ if (Gear^.Health <> Gear^.FlightTime) or (Gear^.Tex = nil) then
+ begin
+ Gear^.FlightTime:= Gear^.Health;
+ FreeAndNilTexture(Gear^.Tex);
+ Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff808080, fnt16);
+ end;
end
else
begin
+ // health texture for health crate
+ if (k = gtCase) and ((Gear^.Pos and posCaseHealth) <> 0) then
+ begin
+ if ((Gear^.State and gstFrozen) = 0) then
+ begin
+ // Karma=2: Always hide health
+ if (Gear^.Karma = 2) then
+ i:= 0
+ // Karma=1: Hide health in game, but show in demo
+ else if (Gear^.Karma = 1) then
+ if (GameType in [gmtDemo, gmtRecord]) then
+ i:= 1
+ else
+ i:= 0
+ // Always show health (default)
+ else
+ i:= 1;
+ if i = 1 then
+ begin
+ if (Gear^.Health <> Gear^.FlightTime) or (Gear^.Tex = nil) then
+ begin
+ Gear^.FlightTime:= Gear^.Health;
+ FreeAndNilTexture(Gear^.Tex);
+ Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff80ff80, fnt16)
+ end
+ end
+ else
+ begin
+ if (Gear^.FlightTime <> $ffffffff) or (Gear^.Tex = nil) then
+ begin
+ Gear^.FlightTime:= $ffffffff;
+ FreeAndNilTexture(Gear^.Tex);
+ Gear^.Tex := RenderStringTex(trmsg[sidUnknownGearValue], $ff80ff80, fnt16)
+ end
+ end
+ end;
+ end;
if Gear^.Timer = 500 then
begin
(* Can't make sparkles team coloured without working out what the next team is going to be. This should be solved, really, since it also screws up
@@ -2371,8 +2608,9 @@
else if Gear^.dY < - _0_03 then
PlaySound(Gear^.ImpactSound);
end;
- //if Gear^.dY > - _0_001 then Gear^.dY:= _0
CheckGearDrowning(Gear);
+ if ((Gear^.State and gstDrowning) <> 0) then
+ Gear^.RenderHealth:= false;
end;
if (Gear^.dY.QWordValue = 0) then
@@ -2429,7 +2667,12 @@
if Gear^.Timer = 0 then
begin
inc(Gear^.Tag);
- Gear^.Timer := 50
+ Gear^.Timer := 50;
+ if Gear^.Tag = 3 then
+ begin
+ ClearHitOrder();
+ RefillProximityCache(Gear, 100);
+ end;
end;
if Gear^.Tag = 3 then
@@ -2438,12 +2681,14 @@
HHGear^.State := HHGear^.State or gstNoDamage;
DeleteCI(HHGear);
- AmmoShove(Gear, Gear^.Boom, 115);
+ AmmoShoveCache(Gear, Gear^.Boom, 115);
HHGear^.State := (HHGear^.State and (not gstNoDamage)) or gstMoving;
end
else if Gear^.Tag = 4 then
begin
+ ClearHitOrder();
+ ClearProximityCache();
Gear^.Timer := 250;
Gear^.doStep := @doStepIdle
end
@@ -2459,13 +2704,17 @@
HHGear^.State := HHGear^.State or gstNoDamage;
DeleteCI(HHGear);
+ ClearHitOrder();
+ RefillProximityCache(Gear, 100);
for i:= 0 to 3 do
begin
AddVisualGear(hwRound(Gear^.X) + hwSign(Gear^.dX) * (10 + 6 * i), hwRound(Gear^.Y) + 12 + Random(6), vgtDust);
- AmmoShove(Gear, Gear^.Boom, 25);
+ AmmoShoveCache(Gear, Gear^.Boom, 25);
Gear^.X := Gear^.X + Gear^.dX * 5
end;
+ ClearHitOrder();
+ ClearProximityCache();
HHGear^.State := (HHGear^.State and (not gstNoDamage)) or gstMoving;
Gear^.Timer := 250;
@@ -2483,14 +2732,19 @@
begin
WorldWrap(Gear);
if Gear^.FlightTime > 0 then dec(Gear^.FlightTime);
+ // There are 2 flame types: normal and sticky
sticky:= (Gear^.State and gsttmpFlag) <> 0;
if not sticky then AllInactive := false;
landPixel:= TestCollisionYwithGear(Gear, 1);
+ // Flame is in mid-air
if landPixel = 0 then
begin
AllInactive := false;
+ // Deals damage in mid-air if FlightTime = 0.
+ // Otherwise, flame is harmless in mid-air.
+ // Intended for use with scripts.
if (GameTicks and $F = 0) and (Gear^.FlightTime = 0) then
begin
Gear^.Radius := 7;
@@ -2504,7 +2758,7 @@
Gear^.dX:= tdX;
Gear^.dY:= tdY;
Gear^.Radius := 1
- end;
+ end;
if ((GameTicks mod 100) = 0) then
begin
@@ -2527,23 +2781,34 @@
Gear^.dY:= Gear^.dY * f;
end
else begin
+ // Gravity and wind
if Gear^.dX.QWordValue > _0_01.QWordValue then
Gear^.dX := Gear^.dX * _0_995;
Gear^.dY := Gear^.dY + cGravity;
- // if sticky then Gear^.dY := Gear^.dY + cGravity;
if Gear^.dY.QWordValue > _0_2.QWordValue then
Gear^.dY := Gear^.dY * _0_995;
- //if sticky then Gear^.X := Gear^.X + Gear^.dX else
- Gear^.X := Gear^.X + Gear^.dX + cWindSpeed * 640;
+ // Apply speed changes
+
+ tdX:= Gear^.dX + cWindSpeed * 640;
+ // Don't apply wind speed if moving against bounce world edge
+ if (WorldEdge = weBounce) and
+ (((hwRound(Gear^.X + tdX) - Gear^.Radius < leftX) and (hwSign(tdX) = -1)) or
+ ((hwRound(Gear^.X + tdX) + Gear^.Radius > rightX) and (hwSign(tdX) = 1))) then
+ Gear^.X := Gear^.X + Gear^.dX
+ else
+ // Apply dX and wind speed
+ Gear^.X := Gear^.X + tdX;
+
Gear^.Y := Gear^.Y + Gear^.dY;
end;
gX := hwRound(Gear^.X);
gY := hwRound(Gear^.Y);
+ // Extinguish in water
if CheckCoordInWater(gX, gY) then
begin
for i:= 0 to 3 do
@@ -2553,16 +2818,18 @@
exit
end
end
+ // Flame is on terrain
else
begin
if (Gear^.Timer = 1) and (GameTicks and $3 = 0) then
begin
Gear^.Y:= Gear^.Y+_6;
+ // Extinguish on ice
if (landPixel and lfIce <> 0) or (TestCollisionYwithGear(Gear, 1) and lfIce <> 0) then
begin
gX := hwRound(Gear^.X);
gY := hwRound(Gear^.Y) - 6;
- DrawExplosion(gX, gY, 4);
+ doMakeExplosion(gX, gY, 4, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx);
PlaySound(sndVaporize);
AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSteam);
DeleteGear(Gear);
@@ -2570,6 +2837,7 @@
end;
Gear^.Y:= Gear^.Y-_6
end;
+ // Sticky flame damage
if sticky and (GameTicks and $F = 0) then
begin
Gear^.Radius := 7;
@@ -2590,12 +2858,14 @@
inc(Gear^.Damage)
end
else
+ // Flame burn-down handling
begin
gX := hwRound(Gear^.X);
gY := hwRound(Gear^.Y);
- // Standard fire
+ // Normal flame: Burns down quickly and must be destroyed before the turn ends
if not sticky then
begin
+ // Deal damage
if ((GameTicks and $1) = 0) then
begin
Gear^.Radius := 7;
@@ -2611,35 +2881,41 @@
Gear^.Radius := 1;
end
else if ((GameTicks and $3) = 3) then
- doMakeExplosion(gX, gY, Gear^.Boom * 4, Gear^.Hedgehog, 0);//, EXPLNoDamage);
- //DrawExplosion(gX, gY, 4);
+ doMakeExplosion(gX, gY, Gear^.Boom * 4, Gear^.Hedgehog, 0);
if ((GameTicks and $7) = 0) and (Random(2) = 0) then
for i:= Random(2) downto 0 do
AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
+ // Flame burn-out due to time
if Gear^.Health > 0 then
dec(Gear^.Health);
+
+ // Calculate time for next flame update with a bit of random jitter
Gear^.Timer := 450 - Gear^.Tag * 8 + LongInt(GetRandom(2))
end
+ // Sticky flame: Burns down slowly and persists between turns
else
begin
- // Modified fire
+ // Destroy land very slowly (low chance this gets called)
if ((GameTicks and $7FF) = 0) and ((GameFlags and gfSolidLand) = 0) then
begin
- DrawExplosion(gX, gY, 4);
+ doMakeExplosion(gX, gY, 4, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx);
for i:= Random(3) downto 0 do
AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
end;
-// This one is interesting. I think I understand the purpose, but I wonder if a bit more fuzzy of kicking could be done with getrandom.
+ // Calculate time for next flame update with a bit of random jitter
Gear^.Timer := 100 - Gear^.Tag * 3 + LongInt(GetRandom(2));
+
+ // Flame burn-out due to time
if (Gear^.Damage > 3000+Gear^.Tag*1500) then
Gear^.Health := 0
end
end
end;
+ // This kills the flame
if Gear^.Health = 0 then
begin
gX := hwRound(Gear^.X);
@@ -2655,7 +2931,7 @@
AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
DeleteGear(Gear)
- end;
+ end
end;
////////////////////////////////////////////////////////////////////////////////
@@ -2678,7 +2954,7 @@
DrawTunnel(HHGear^.X - int2hwFloat(cHHRadius), HHGear^.Y - _1, _0_5, _0, cHHRadius * 4+2, 2);
HHGear^.State := HHGear^.State or gstNoDamage;
Gear^.Y := HHGear^.Y;
- AmmoShove(Gear, Gear^.Boom, 40);
+ AmmoShoveCache(Gear, Gear^.Boom, 40);
HHGear^.State := HHGear^.State and (not gstNoDamage)
end;
@@ -2687,6 +2963,8 @@
if not (HHGear^.dY.isNegative) or (Gear^.Timer = 0) then
begin
HHGear^.State := HHGear^.State or gstMoving;
+ ClearHitOrder();
+ ClearProximityCache();
DeleteGear(Gear);
AfterAttack;
exit
@@ -2694,7 +2972,10 @@
if CheckLandValue(hwRound(HHGear^.X), hwRound(HHGear^.Y + HHGear^.dY + SignAs(_6,Gear^.dY)),
lfIndestructible) then
- HHGear^.Y := HHGear^.Y + HHGear^.dY
+ HHGear^.Y := HHGear^.Y + HHGear^.dY;
+
+ if (Gear^.Timer mod 200) = 0 then
+ RefillProximityCache(Gear, 300);
end;
procedure doStepFirePunch(Gear: PGear);
@@ -2704,11 +2985,13 @@
AllInactive := false;
HHGear := Gear^.Hedgehog^.Gear;
DeleteCI(HHGear);
- //HHGear^.X := int2hwFloat(hwRound(HHGear^.X)) - _0_5; WTF?
HHGear^.dX := SignAs(cLittle, Gear^.dX);
HHGear^.dY := - _0_3;
+ ClearHitOrder();
+ RefillProximityCache(Gear, 300);
+
Gear^.X := HHGear^.X;
Gear^.dX := SignAs(_0_45, Gear^.dX);
Gear^.dY := - _0_9;
@@ -2723,6 +3006,7 @@
procedure doStepParachuteWork(Gear: PGear);
var
HHGear: PGear;
+ deltaX, deltaY: hwFloat;
begin
HHGear := Gear^.Hedgehog^.Gear;
@@ -2736,36 +3020,51 @@
with HHGear^ do
begin
Message := 0;
+ if Gear^.Tag = 1 then
+ dX := _1
+ else
+ dX := - _1;
SetLittle(dX);
dY := _0;
State := State or gstMoving;
end;
- DeleteGear(Gear);
- if (GetAmmoEntry(HHGear^.Hedgehog^, amParachute)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then
+ if (GetAmmoEntry(HHGear^.Hedgehog^, amParachute)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
HHGear^.Hedgehog^.CurAmmoType:= amParachute;
isCursorVisible := false;
ApplyAmmoChanges(HHGear^.Hedgehog^);
+ DeleteGear(Gear);
exit
end;
- HHGear^.X := HHGear^.X + cWindSpeed * 200;
+ deltaX:= _0;
+ deltaX:= deltaX + cWindSpeed * 200;
+ deltaY:= _0;
if (Gear^.Message and gmLeft) <> 0 then
- HHGear^.X := HHGear^.X - cMaxWindSpeed * 80
+ deltaX := deltaX - cMaxWindSpeed * 80
else if (Gear^.Message and gmRight) <> 0 then
- HHGear^.X := HHGear^.X + cMaxWindSpeed * 80;
+ deltaX := deltaX + cMaxWindSpeed * 80;
if (Gear^.Message and gmUp) <> 0 then
- HHGear^.Y := HHGear^.Y - cGravity * 40
+ deltaY := deltaY - cGravity * 40
else if (Gear^.Message and gmDown) <> 0 then
- HHGear^.Y := HHGear^.Y + cGravity * 40;
-
+ deltaY := deltaY + cGravity * 40;
+
+ HHGear^.X := HHGear^.X + deltaX;
// don't drift into obstacles
- if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) <> 0 then
- HHGear^.X := HHGear^.X - int2hwFloat(hwSign(HHGear^.dX));
- HHGear^.Y := HHGear^.Y + cGravity * 100;
+ if TestCollisionXwithGear(HHGear, hwSign(deltaX)) <> 0 then
+ begin
+ HHGear^.X := HHGear^.X - int2hwFloat(hwSign(deltaX));
+ deltaX:= _0;
+ end;
+ deltaY := deltaY + cGravity * 100;
+ HHGear^.Y := HHGear^.Y + deltaY;
+
+ HHGear^.dX := deltaX;
+ HHGear^.dY := deltaY;
+
Gear^.X := HHGear^.X;
Gear^.Y := HHGear^.Y
end;
@@ -2795,54 +3094,141 @@
////////////////////////////////////////////////////////////////////////////////
procedure doStepAirAttackWork(Gear: PGear);
+var uw, nuw: boolean;
+ tmpFloat: hwFloat;
+ i: LongInt;
begin
AllInactive := false;
+ if (WorldEdge = weWrap) then
+ if (WorldWrap(Gear)) then
+ inc(Gear^.Power);
Gear^.X := Gear^.X + cAirPlaneSpeed * Gear^.Tag;
-
- if (Gear^.Health > 0) and (not (Gear^.X < Gear^.dX)) and (Gear^.X < Gear^.dX + cAirPlaneSpeed) then
+ if (Gear^.Health > 0) and (Gear^.Power >= Gear^.WDTimer) and (((Gear^.Tag = 1) and (not (Gear^.X < Gear^.dX))) or ((Gear^.Tag = -1) and (not (Gear^.X > Gear^.dX)))) then
begin
dec(Gear^.Health);
- case Gear^.State of
- 0: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
- 1: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtMine, 0, cBombsSpeed * Gear^.Tag, _0, 0);
- 2: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtNapalmBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
- 3: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtDrill, gsttmpFlag, cBombsSpeed * Gear^.Tag, _0, Gear^.Timer + 1);
- //4: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtWaterMelon, 0, cBombsSpeed *
- // Gear^.Tag, _0, 5000);
+ Gear^.FlightTime:= 0;
+ // Spawn missile
+ case Gear^.State of
+ 0: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
+ 1: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtMine, 0, cBombsSpeed * Gear^.Tag, _0, 0);
+ 2: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtNapalmBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
+ 3: FollowGear := AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtDrill, gsttmpFlag, cBombsSpeed * Gear^.Tag, _0, Gear^.Timer + 1);
+ end;
+ Gear^.dX := Gear^.X + int2hwFloat(Gear^.Damage * Gear^.Tag);
+ if (WorldEdge = weWrap) then
+ begin
+ Gear^.dX := int2hwFloat(CalcWorldWrap(hwRound(Gear^.dX), 0));
+ if (((Gear^.Tag = 1) and (not (Gear^.X < Gear^.dX))) or ((Gear^.Tag = -1) and (not (Gear^.X > Gear^.dX)))) then
+ inc(Gear^.WDTimer);
end;
- Gear^.dX := Gear^.dX + int2hwFloat(Gear^.Damage * Gear^.Tag);
+
if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
FollowGear^.State:= FollowGear^.State or gstSubmersible;
+ end;
+
+ if (Gear^.Health = 0) then
+ inc(Gear^.FlightTime);
+
+ if (Gear^.SoundChannel <> -1) and (WorldEdge <> weSea) and (Gear^.FlightTime > 20) then
+ begin
StopSoundChan(Gear^.SoundChannel, 4000);
- end;
-
+ Gear^.SoundChannel := -1;
+ end;
+
+ // Particles
if (GameTicks and $3F) = 0 then
- AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmokeTrace);
-
- if (hwRound(Gear^.X) > (max(LAND_WIDTH,4096)+2048)) or (hwRound(Gear^.X) < -2048) then
- begin
- // avoid to play forever (is this necessary?)
+ if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
+ begin
+ // air plane bubbles
+ for i:=1 to 3 do
+ AddVisualGear(hwRound(Gear^.X) - 8 + Random(16), hwRound(Gear^.Y) - 8 + Random(16), vgtBubble);
+ // pilot's snorkel bubbles
+ if random(2) = 0 then
+ AddVisualGear(hwRound(Gear^.X) + 10, hwRound(Gear^.Y) - 50, vgtBubble);
+ end
+ else
+ // smoke
+ AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmokeTrace);
+
+ // Get rid of gear and cleanup
+ if ((WorldEdge = weWrap) and (Gear^.FlightTime >= 4000)) or
+ ((WorldEdge <> weWrap) and (((hwRound(Gear^.X) - Gear^.Radius > (max(LAND_WIDTH,4096)+2048)) or (hwRound(Gear^.X) + Gear^.Radius < -2048) or ((Gear^.Message and gmDestroy) > 0)))) then
+ begin
+ // fail-safe: instanly stop sound if it wasn't disabled before
+ if (Gear^.SoundChannel <> -1) then
+ begin
+ StopSoundChan(Gear^.SoundChannel);
+ Gear^.SoundChannel := -1;
+ end;
+ if (WorldEdge = weWrap) then
+ AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBigExplosion);
+ DeleteGear(Gear);
+ exit;
+ end;
+
+ uw := (Gear^.Karma <> 0); // Was plane underwater last tick?
+ nuw := CheckCoordInWater(hwRound(Gear^.X) + Gear^.Radius * Gear^.Tag, hwRound(Gear^.Y)); // Is plane underwater now?
+
+ // if water entered or left
+ if nuw <> uw then
+ begin
+ tmpFloat:= Gear^.dX;
+ Gear^.dX := cAirPlaneSpeed * Gear^.Tag;
+ AddSplashForGear(Gear, false);
+ Gear^.dX := tmpFloat;
StopSoundChan(Gear^.SoundChannel);
- DeleteGear(Gear)
- end;
+ if nuw then
+ begin
+ Gear^.SoundChannel := LoopSound(sndPlaneWater);
+ StopSoundChan(Gear^.SoundChannel, 4000);
+ Gear^.SoundChannel := -1;
+ Gear^.Karma := 1;
+ end
+ else
+ begin
+ Gear^.SoundChannel := LoopSound(sndPlane);
+ Gear^.Karma := 0;
+ end;
+ end;
+
end;
procedure doStepAirAttack(Gear: PGear);
+var HHGear: PGear;
begin
AllInactive := false;
+ if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ HHGear:= Gear^.Hedgehog^.Gear;
+
+ if (HHGear <> nil) then
+ PlaySoundV(sndIncoming, Gear^.Hedgehog^.Team^.voicepack);
+ AfterAttack;
+ CurAmmoGear := nil;
+
if Gear^.X.QWordValue = 0 then
begin
Gear^.Tag := 1;
- Gear^.X := -_2048;
+ if (WorldEdge = weWrap) then
+ Gear^.X := int2hwFloat(CalcWorldWrap(Gear^.Target.X + max(384, LAND_WIDTH shr 2), 0))
+ else
+ Gear^.X := -_2048;
end
else
begin
Gear^.Tag := -1;
- Gear^.X := int2hwFloat(max(LAND_WIDTH,4096) + 2048);
- end;
-
- Gear^.Y := int2hwFloat(topY-300);
+ if (WorldEdge = weWrap) then
+ Gear^.X := int2hwFloat(CalcWorldWrap(Gear^.Target.X - max(384, LAND_WIDTH shr 2), 0))
+ else
+ Gear^.X := int2hwFloat(max(LAND_WIDTH,4096) + 2048);
+ end;
+
+ Gear^.Y := int2hwFloat(topY - 300);
+
+ // Appear out of nowhere in wrap
+ if (WorldEdge = weWrap) then
+ AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBigExplosion);
+
Gear^.dX := int2hwFloat(Gear^.Target.X) - int2hwFloat(Gear^.Tag * (Gear^.Health-1) * Gear^.Damage) / 2;
// calcs for Napalm Strike, so that it will hit the target (without wind at least :P)
@@ -2853,8 +3239,30 @@
Gear^.dX := Gear^.dX - cBombsSpeed * hwSqrt((int2hwFloat(Gear^.Target.Y) - Gear^.Y) * 2 /
cGravity) * Gear^.Tag;
+ if (WorldEdge = weWrap) then
+ begin
+ Gear^.dX := int2hwFloat(CalcWorldWrap(hwRound(Gear^.dX), 0));
+ if (((Gear^.Tag = 1) and (not (Gear^.X < Gear^.dX))) or ((Gear^.Tag = -1) and (not (Gear^.X > Gear^.dX)))) then
+ Gear^.WDTimer:= 1;
+ end;
+
Gear^.doStep := @doStepAirAttackWork;
- Gear^.SoundChannel := LoopSound(sndPlane, 4000);
+
+ if (WorldEdge = weSea) then
+ begin
+ Gear^.SoundChannel := LoopSound(sndPlaneWater, 4000);
+ Gear^.Karma := 1;
+ end
+ else if (WorldEdge = weWrap) then
+ begin
+ Gear^.SoundChannel := LoopSound(sndPlane, 500);
+ Gear^.Karma := 0;
+ end
+ else
+ begin
+ Gear^.SoundChannel := LoopSound(sndPlane, 4000);
+ Gear^.Karma := 0;
+ end;
end;
@@ -2867,12 +3275,12 @@
if (Gear^.State and gstCollision) <> 0 then
begin
doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
- DeleteGear(Gear);
{$IFNDEF PAS2C}
with mobileRecord do
if (performRumble <> nil) and (not fastUntilLag) then
performRumble(kSystemSoundID_Vibrate);
{$ENDIF}
+ DeleteGear(Gear);
exit
end;
if (GameTicks and $3F) = 0 then
@@ -2928,7 +3336,7 @@
begin
PlaySound(sndPlaced);
DeleteGear(Gear);
- AfterAttack;
+ AfterAttack
end;
HHGear^.State := HHGear^.State and (not (gstAttacking or gstAttacked));
@@ -2947,7 +3355,7 @@
or (HHGear = nil)
or ((HHGear^.State and gstMoving) = 0)
or (HHGear^.Damage > 0)
- or ((HHGear^.State and gstDrowning) = 1) then
+ or ((HHGear^.State and gstDrowning) <> 0) then
begin
DeleteGear(Gear);
AfterAttack
@@ -2960,6 +3368,7 @@
begin
DeleteGear(Gear);
AfterAttack;
+ exit
end;
inc(Gear^.Timer);
if Gear^.Timer = 65 then
@@ -3056,25 +3465,25 @@
hedgehog: PHedgehog;
State: Longword;
switchDir: Longword;
+ oldUid: Longword;
begin
AllInactive := false;
if ((Gear^.Message and (not (gmSwitch or gmPrecise))) <> 0) or (TurnTimeLeft = 0) then
begin
hedgehog := Gear^.Hedgehog;
- //Msg := Gear^.Message and (not gmSwitch);
- DeleteGear(Gear);
ApplyAmmoChanges(hedgehog^);
HHGear := CurrentHedgehog^.Gear;
ApplyAmmoChanges(HHGear^.Hedgehog^);
- //HHGear^.Message := Msg;
+ DeleteGear(Gear);
exit
end;
+ HHGear := CurrentHedgehog^.Gear;
if (Gear^.Message and gmSwitch) <> 0 then
begin
- HHGear := CurrentHedgehog^.Gear;
+ oldUid:= HHGear^.uid;
HHGear^.Message := HHGear^.Message and (not gmSwitch);
Gear^.Message := Gear^.Message and (not gmSwitch);
@@ -3088,9 +3497,10 @@
switchDir:= 1;
State := HHGear^.State;
- HHGear^.State := 0;
+ if (HHGear^.State and gstMoving) = 0 then
+ HHGear^.Active := false;
+ HHGear^.State := HHGear^.State and gstMoving;
HHGear^.Z := cHHZ;
- HHGear^.Active := false;
HHGear^.Message:= HHGear^.Message or gmRemoveFromList or gmAddToList;
PlaySound(sndSwitchHog);
@@ -3107,6 +3517,7 @@
AmmoMenuInvalidated:= true;
HHGear := CurrentHedgehog^.Gear;
+ ScriptCall('onHogSwitch', oldUid);
HHGear^.State := State;
HHGear^.Active := true;
FollowGear := HHGear;
@@ -3115,9 +3526,10 @@
if (switchDir <> 1) then
HHGear^.Message:= HHGear^.Message or gmPrecise;
HHGear^.Message:= HHGear^.Message or gmRemoveFromList or gmAddToList;
- Gear^.X := HHGear^.X;
- Gear^.Y := HHGear^.Y
- end;
+ end;
+ doStepHedgehogMoving(HHGear);
+ Gear^.X := HHGear^.X;
+ Gear^.Y := HHGear^.Y
end;
procedure doStepSwitcher(Gear: PGear);
@@ -3131,7 +3543,7 @@
with HHGear^ do
begin
State := State and (not gstAttacking);
- Message := Message and (not gmAttack)
+ Message := Message and (not (gmAttack or gmSwitch))
end
end;
@@ -3165,6 +3577,7 @@
exit
end;
+ // particles
if (GameTicks and $3F) = 0 then
AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmokeTrace);
end;
@@ -3186,6 +3599,8 @@
HHGear := Gear^.Hedgehog^.Gear;
if HHGear = nil then
begin
+ ClearHitOrder();
+ ClearProximityCache();
DeleteGear(Gear);
exit
end;
@@ -3217,15 +3632,14 @@
if CheckGearDrowning(HHGear) then
begin
AfterAttack;
+ ClearHitOrder();
+ ClearProximityCache();
DeleteGear(Gear);
exit;
end;
inc(Gear^.Damage, 2);
- // if TestCollisionXwithGear(HHGear, hwSign(Gear^.dX))
- // or TestCollisionYwithGear(HHGear, hwSign(Gear^.dY)) then inc(Gear^.Damage, 3);
-
dec(i)
until (i = 0)
or (Gear^.Damage > Gear^.Health);
@@ -3241,7 +3655,7 @@
Gear^.Pos := 2;
end;
- AmmoShove(Gear, Gear^.Boom, 40);
+ AmmoShoveCache(Gear, Gear^.Boom, 40);
DrawTunnel(HHGear^.X - HHGear^.dX * 10,
HHGear^.Y - _2 - HHGear^.dY * 10 + hwAbs(HHGear^.dY) * 2,
@@ -3253,6 +3667,10 @@
upd := 0
end;
+ inc(Gear^.Timer);
+ if (Gear^.Timer mod 100) = 0 then
+ RefillProximityCache(Gear, 300);
+
if Gear^.Health < Gear^.Damage then
begin
doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
@@ -3275,10 +3693,12 @@
end
end;
s:= ansistring(Gear^.Hedgehog^.Name);
- AddCaption(FormatA(GetEventString(eidKamikaze), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidKamikaze), s), capcolDefault, capgrpMessage);
uStats.HedgehogSacrificed(Gear^.Hedgehog);
AfterAttack;
HHGear^.Message:= HHGear^.Message or gmDestroy;
+ ClearHitOrder();
+ ClearProximityCache();
DeleteGear(Gear);
end
else
@@ -3289,13 +3709,26 @@
end;
procedure doStepKamikazeIdle(Gear: PGear);
+var HHGear: PGear;
begin
AllInactive := false;
dec(Gear^.Timer);
+ HHGear := Gear^.Hedgehog^.Gear;
+ if (HHGear = nil) or (HHGear^.Damage <> 0) then
+ begin
+ if (HHGear <> nil) then
+ AfterAttack;
+ ClearHitOrder();
+ ClearProximityCache();
+ DeleteGear(Gear);
+ exit;
+ end;
if Gear^.Timer = 0 then
begin
Gear^.Pos := 1;
PlaySoundV(sndKamikaze, Gear^.Hedgehog^.Team^.voicepack);
+ ClearHitOrder();
+ RefillProximityCache(Gear, 300);
Gear^.doStep := @doStepKamikazeWork
end
end;
@@ -3361,10 +3794,6 @@
if Gear^.Pos = 0 then
begin
///////////// adapted from doMakeExplosion ///////////////////////////
- //fX:= Gear^.X;
- //fY:= Gear^.Y;
- //fX.QWordValue:= fX.QWordValue and $FFFFFFFF00000000;
- //fY.QWordValue:= fY.QWordValue and $FFFFFFFF00000000;
fX:= int2hwFloat(hwRound(Gear^.X));
fY:= int2hwFloat(hwRound(Gear^.Y));
dmgBase:= Gear^.Boom shl 1 + cHHRadius div 2;
@@ -3425,6 +3854,7 @@
var
tdx, tdy: hwFloat;
cakeData: PCakeData;
+ i: Longword;
begin
AllInactive := false;
@@ -3449,6 +3879,72 @@
if not cakeStep(Gear) then Gear^.doStep:= @doStepCakeFall;
+ // Cake passed world edge.
+ if (Gear^.Karma = 1) then
+ (* This code is not ideal, but at least not horribly broken.
+ The cake tries to reach the other side and continue to walk,
+ but there are some exceptions.
+ This code is called *after* the X coordinate have been wrapped.
+ Depending on terrain on the other side, the cake does this:
+ * Cake collides horizontally (even by 1 pixel): Turn around
+ * Cake does not see walkable ground above or below: Fall
+ * Otherwise: Walk normally
+ *)
+ begin
+ // Update coordinates
+ tdx:=Gear^.X;
+ if (hwRound(Gear^.X) < leftX) then
+ Gear^.X:= Gear^.X + int2hwfloat(rightX - leftX)
+ else Gear^.X:= Gear^.X - int2hwfloat(rightX - leftX);
+
+ Gear^.Tag:= 0;
+ if ((TestCollisionXwithGear(Gear, 1) <> 0) or (TestCollisionXwithGear(Gear, -1) <> 0)) then
+ // Cake collided horizontally, turn around. Prevents cake from being stuck in infinite loop.
+ // This can also happen if the terrain is just a slight slope. :-(
+ begin
+ Gear^.X := tdx;
+ Gear^.Karma := 3;
+ end
+ else
+ begin
+ // Check if cake has something to walk on the other side. If not, make it drop.
+ // There is nothing for the cake to stand on.
+ if (TestCollisionYwithGear(Gear, 1) = 0) and (TestCollisionYwithGear(Gear, -1) = 0) then
+ Gear^.doStep:= @doStepCakeFall;
+ Gear^.Karma := 4;
+ end;
+ end;
+ // Cake bounced!
+ if (Gear^.Karma = 2) or (Gear^.Karma = 3) then
+ begin
+ // Turn cake around
+ Gear^.dX.isNegative := (not Gear^.dX.isNegative);
+ Gear^.WDTimer := 0;
+ Gear^.Angle := (LongInt(Gear^.Angle) + 2) and 3;
+
+ // Bounce effect
+ if (Gear^.Karma = 2) then
+ AddBounceEffectForGear(Gear, 0.55);
+
+ Gear^.Tag:= 0;
+ Gear^.Karma := 4;
+ end;
+ if (Gear^.Karma = 4) then
+ begin
+ // Reset CakePoints to fix cake angle
+ cakeData:= PCakeData(Gear^.Data);
+ with cakeData^ do
+ begin
+ for i:= 0 to Pred(cakeh) do
+ begin
+ CakePoints[i].x := Gear^.X;
+ CakePoints[i].y := Gear^.Y;
+ end;
+ CakeI:= 0;
+ end;
+ Gear^.Karma := 0;
+ end;
+
if Gear^.Tag = 0 then
begin
cakeData:= PCakeData(Gear^.Data);
@@ -3524,7 +4020,7 @@
begin
AllInactive := false;
- Gear^.CollisionMask:= lfNotCurrentMask;
+ Gear^.CollisionMask:= lfNotCurHogCrate;
Gear^.dY:= cMaxWindSpeed * 100;
@@ -3535,8 +4031,19 @@
procedure doStepSeductionWork(Gear: PGear);
var i: LongInt;
hogs: PGearArrayS;
+ HHGear: PGear;
begin
AllInactive := false;
+
+ HHGear := Gear^.Hedgehog^.Gear;
+ if (HHGear <> nil) and ((HHGear^.State and gstHHDriven) = 0) then
+ begin
+ StopSound(sndYoohoo);
+ AfterAttack;
+ DeleteGear(Gear);
+ exit;
+ end;
+
hogs := GearsNear(Gear^.X, Gear^.Y, gtHedgehog, Gear^.Radius);
if hogs.size > 0 then
begin
@@ -3556,37 +4063,25 @@
else if Hedgehog^.Effects[heFrozen] > 255 then
Hedgehog^.Effects[heFrozen]:= 255
end ;
- AfterAttack;
- DeleteGear(Gear);
-(*
- Gear^.X := Gear^.X + Gear^.dX;
- Gear^.Y := Gear^.Y + Gear^.dY;
- x := hwRound(Gear^.X);
- y := hwRound(Gear^.Y);
-
- if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then
- if (Land[y, x] <> 0) then
- begin
- Gear^.dX.isNegative := not Gear^.dX.isNegative;
- Gear^.dY.isNegative := not Gear^.dY.isNegative;
- Gear^.dX := Gear^.dX * _1_5;
- Gear^.dY := Gear^.dY * _1_5 - _0_3;
- AmmoShove(Gear, 0, 40);
- AfterAttack;
- DeleteGear(Gear)
- end
- else
- else
- begin
- AfterAttack;
- DeleteGear(Gear)
- end*)
+ AfterAttack;
+ DeleteGear(Gear);
end;
procedure doStepSeductionWear(Gear: PGear);
var heart: PVisualGear;
+ HHGear: PGear;
begin
AllInactive := false;
+
+ HHGear := Gear^.Hedgehog^.Gear;
+ if (HHGear <> nil) and ((HHGear^.State and gstHHDriven) = 0) then
+ begin
+ StopSound(sndYoohoo);
+ AfterAttack;
+ DeleteGear(Gear);
+ exit;
+ end;
+
inc(Gear^.Timer);
if Gear^.Timer > 250 then
begin
@@ -3594,6 +4089,8 @@
inc(Gear^.Pos);
if Gear^.Pos = 5 then
PlaySound(sndYoohoo);
+ if Gear^.Pos = 14 then
+ PlaySound(sndKiss);
end;
@@ -3604,14 +4101,6 @@
if heart <> nil then
with heart^ do
begin
- { // old calcs
- dx:= 0.001 * (random(200));
- dy:= 0.001 * (random(200));
- if random(2) = 0 then
- dx := -dx;
- if random(2) = 0 then
- dy := -dy;
- }
// randomize speed in both directions
dx:= 0.001 * (random(201));
@@ -3649,7 +4138,6 @@
procedure doStepSeduction(Gear: PGear);
begin
AllInactive := false;
- //DeleteCI(Gear^.Hedgehog^.Gear);
Gear^.doStep := @doStepSeductionWear
end;
@@ -3702,6 +4190,7 @@
t: PGearArray;
tempColl: Word;
begin
+ WorldWrap(Gear);
AllInactive := false;
if (Gear^.Timer > 0) and (Gear^.Timer mod 10 <> 0) then
begin
@@ -3721,7 +4210,7 @@
end;
tempColl:= Gear^.CollisionMask;
- Gear^.CollisionMask:= $007F;
+ Gear^.CollisionMask:= lfObjMask;
if (TestCollisionYWithGear(Gear, hwSign(Gear^.dY)) <> 0) or (TestCollisionXWithGear(Gear, hwSign(Gear^.dX)) <> 0) or (GameTicks > Gear^.FlightTime) then
t := CheckGearsCollision(Gear)
else t := nil;
@@ -3803,6 +4292,7 @@
Gear^.X:= Gear^.X+Gear^.dX*4;
Gear^.Y:= Gear^.Y+Gear^.dY*4;
Gear^.SoundChannel := LoopSound(sndDrillRocket);
+ Gear^.Pos:= 1;
Gear^.doStep := @doStepDrillDrilling;
if (Gear^.State and gsttmpFlag) <> 0 then
@@ -3845,7 +4335,7 @@
ry := rndSign(getRandomf * _0_1);
ball:= AddGear(gx, gy, gtBall, 0, SignAs(AngleSin(HHGear^.Angle) * _0_8, HHGear^.dX) + rx, AngleCos(HHGear^.Angle) * ( - _0_8) + ry, 0);
- ball^.CollisionMask:= lfNotCurrentMask;
+ ball^.CollisionMask:= lfNotCurHogCrate;
PlaySound(sndGun);
end;
@@ -3964,14 +4454,13 @@
dec(Gear^.Health)
end;
s:= ansistring(inttostr(Gear^.Health));
- AddCaption(formatA(trmsg[sidRemaining], s), cWhiteColor, capgrpAmmostate);
+ AddCaption(formatA(trmsg[sidRemaining], s), capcolDefault, capgrpAmmostate);
end;
if (HHGear <> nil) and ((HHGear^.Message and gmLJump) <> 0) and ((Gear^.State and gsttmpFlag) = 0) then
begin
Gear^.State := Gear^.State or gsttmpFlag;
- PauseMusic;
- playSound(sndRideOfTheValkyries);
+ PlayMusicSound(sndRideOfTheValkyries);
inCinematicMode:= true;
end;
@@ -3986,8 +4475,7 @@
begin
inCinematicMode:= false;
StopSoundChan(Gear^.SoundChannel);
- StopSound(sndRideOfTheValkyries);
- ResumeMusic;
+ StopMusicSound(sndRideOfTheValkyries);
if ((Gear^.State and gstCollision) <> 0) then
begin
@@ -4050,17 +4538,11 @@
dec(Gear^.Pos);
AllInactive := false;
HHGear := Gear^.Hedgehog^.Gear;
- //dec(Gear^.Timer);
move := _0_2;
fuel := 50;
-(*if (HHGear^.Message and gmPrecise) <> 0 then
- begin
- move:= _0_02;
- fuel:= 5;
- end;*)
if HHGear^.Message and gmPrecise <> 0 then
HedgehogChAngle(HHGear)
- else if Gear^.Health > 0 then
+ else if (Gear^.Health > 0) or (Gear^.Health = JETPACK_FUEL_INFINITE) then
begin
if HHGear^.Message and gmUp <> 0 then
begin
@@ -4082,7 +4564,8 @@
HHGear^.dY := HHGear^.dY - move;
end
end;
- dec(Gear^.Health, fuel);
+ if Gear^.Health <> JETPACK_FUEL_INFINITE then
+ dec(Gear^.Health, fuel);
Gear^.MsgParam := Gear^.MsgParam or gmUp;
Gear^.Timer := GameTicks
end;
@@ -4108,7 +4591,8 @@
end
end
else PlaySound(sndJetpackBoost);
- dec(Gear^.Health, fuel div 5);
+ if Gear^.Health <> JETPACK_FUEL_INFINITE then
+ dec(Gear^.Health, fuel div 5);
Gear^.MsgParam := Gear^.MsgParam or (HHGear^.Message and (gmLeft or gmRight));
Gear^.Timer := GameTicks
end
@@ -4121,7 +4605,7 @@
Gear^.MsgParam := 0
end;
- if Gear^.Health < 0 then
+ if (Gear^.Health < 0) and (Gear^.Health <> JETPACK_FUEL_INFINITE) then
Gear^.Health := 0;
i:= Gear^.Health div 20;
@@ -4129,9 +4613,9 @@
if (i <> Gear^.Damage) and ((GameTicks and $3F) = 0) then
begin
Gear^.Damage:= i;
- //AddCaption('Fuel: '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate);
FreeAndNilTexture(Gear^.Tex);
- Gear^.Tex := RenderStringTex(trmsg[sidFuel] + ansistring(': ' + inttostr(i) + '%'), cWhiteColor, fntSmall)
+ if Gear^.Health <> JETPACK_FUEL_INFINITE then
+ Gear^.Tex := RenderStringTex(FormatA(trmsg[sidFuel], ansistring(inttostr(i))), cWhiteColor, fntSmall)
end;
if (HHGear^.Message and (gmAttack or gmUp or gmLeft or gmRight) <> 0) and
@@ -4153,9 +4637,8 @@
or (HHGear^.dY < _0) then
doStepHedgehogMoving(HHGear);
- if // (Gear^.Health = 0)
+ if
(HHGear^.Damage <> 0)
- //or CheckGearDrowning(HHGear)
// drown if too deep under water
or (cWaterLine + cVisibleWater * 4 < hwRound(HHGear^.Y))
or (TurnTimeLeft = 0)
@@ -4169,16 +4652,11 @@
Active := true;
State := State or gstMoving
end;
- DeleteGear(Gear);
- if (GetAmmoEntry(HHGear^.Hedgehog^, amJetpack)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then
+ if (GetAmmoEntry(HHGear^.Hedgehog^, amJetpack)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
HHGear^.Hedgehog^.CurAmmoType:= amJetpack;
isCursorVisible := false;
ApplyAmmoChanges(HHGear^.Hedgehog^);
- // if Gear^.Tex <> nil then FreeTexture(Gear^.Tex);
-
-// Gear^.Tex:= RenderStringTex(trmsg[sidFuel] + ': ' + inttostr(round(Gear^.Health / 20)) + '%', cWhiteColor, fntSmall)
-
-//AddCaption(trmsg[sidFuel]+': '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate);
+ DeleteGear(Gear);
end
end;
@@ -4216,15 +4694,13 @@
if Gear^.Timer < 2000 then
inc(Gear^.Timer, 1)
else
- begin
- DeleteGear(Gear);
- end;
+ DeleteGear(Gear)
end;
procedure doStepBirdyFly(Gear: PGear);
var
HHGear: PGear;
- fuel, i: LongInt;
+ energy, i: LongInt;
move: hwFloat;
s: ansistring;
begin
@@ -4242,7 +4718,7 @@
end;
move := _0_2;
- fuel := 50;
+ energy:= 50;
if Gear^.Pos > 0 then
dec(Gear^.Pos, 1)
@@ -4260,7 +4736,8 @@
or (HHGear^.Y > -_256) then
HHGear^.dY := HHGear^.dY - move;
- dec(Gear^.Health, fuel);
+ if (Gear^.Health <> BIRDY_ENERGY_INFINITE) then
+ dec(Gear^.Health, energy);
Gear^.MsgParam := Gear^.MsgParam or gmUp;
end;
@@ -4268,14 +4745,15 @@
if (HHGear^.Message and (gmLeft or gmRight)) <> 0 then
begin
HHGear^.dX := HHGear^.dX + (move * _0_1);
- dec(Gear^.Health, fuel div 5);
+ if (Gear^.Health <> BIRDY_ENERGY_INFINITE) then
+ dec(Gear^.Health, energy div 5);
Gear^.MsgParam := Gear^.MsgParam or (HHGear^.Message and (gmLeft or gmRight));
end;
- if Gear^.Health < 0 then
+ if (Gear^.Health < 0) and (Gear^.Health <> BIRDY_ENERGY_INFINITE) then
Gear^.Health := 0;
- if ((GameTicks and $FF) = 0) and (Gear^.Health < 500) then
+ if ((GameTicks and $FF) = 0) and (Gear^.Health < 500) and (Gear^.Health <> BIRDY_ENERGY_INFINITE) then
for i:= ((500-Gear^.Health) div 250) downto 0 do
AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtFeather);
@@ -4289,7 +4767,7 @@
dec(Gear^.FlightTime)
end;
s:= ansistring(inttostr(Gear^.FlightTime));
- AddCaption(formatA(trmsg[sidRemaining], s), cWhiteColor, capgrpAmmostate);
+ AddCaption(formatA(trmsg[sidRemaining], s), capcolDefault, capgrpAmmostate);
end;
if HHGear^.Message and (gmUp or gmPrecise or gmLeft or gmRight) <> 0 then
@@ -4349,7 +4827,6 @@
Gear^.Hedgehog := nil;
Gear^.Timer := 0;
Gear^.State := Gear^.State or gstAnimation or gstTmpFlag;
- Gear^.Timer := 0;
Gear^.doStep := @doStepBirdyDisappear;
CurAmmoGear := nil;
isCursorVisible := false;
@@ -4423,7 +4900,6 @@
AllInactive := false;
Gear^.dX := Gear^.dX;
doStepFallingGear(Gear);
- // CheckGearDrowning(Gear); // already checked for in doStepFallingGear
CalcRotationDirAngle(Gear);
if (Gear^.State and gstCollision) <> 0 then
@@ -4452,7 +4928,7 @@
procedure doPortalColorSwitch();
var CurWeapon: PAmmo;
begin
- if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and ((CurrentHedgehog^.Gear^.Message and gmSwitch) <> 0) then
+ if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and ((CurrentHedgehog^.Gear^.State and gstHHDriven) <> 0) and ((CurrentHedgehog^.Gear^.Message and gmSwitch) <> 0) then
with CurrentHedgehog^ do
if (CurAmmoType = amPortalGun) then
begin
@@ -4571,10 +5047,6 @@
if not ((Gear^.dX*ox + Gear^.dY*oy).isNegative) then
continue;
- if iterator^.Kind = gtDuck then
- // Make duck go into “falling” mode again
- iterator^.Pos:= 0;
-
isbullet:= (iterator^.Kind in [gtShotgunShot, gtDEagleShot, gtSniperRifleShot, gtSineGunShot, gtMinigunBullet]);
r:= int2hwFloat(iterator^.Radius);
@@ -4758,6 +5230,9 @@
end;
end;
+ if iterator^.Kind = gtKamikaze then
+ RefillProximityCache(iterator, 300);
+
//
// You're now officially portaled!
//
@@ -4979,33 +5454,78 @@
newPortal^.doStep := @doStepMovingPortal;
end;
+procedure doStepPiano(Gear: PGear);
+var valid: boolean;
+ HHGear: PGear;
+begin
+ AllInactive := false;
+ valid := true;
+
+ if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ HHGear := Gear^.Hedgehog^.Gear;
+
+ if (WorldEdge = weBounce) then
+ if (hwRound(Gear^.X) - Gear^.Radius < leftX) then
+ valid := false
+ else if (hwRound(Gear^.X) + Gear^.Radius > rightX) then
+ valid := false;
+
+ if (not valid) then
+ begin
+ if (HHGear <> nil) then
+ begin
+ HHGear^.Message := HHGear^.Message and (not gmAttack);
+ HHGear^.State := HHGear^.State and (not gstAttacking);
+ HHGear^.State := HHGear^.State or gstChooseTarget;
+ isCursorVisible := true;
+ end;
+ DeleteGear(Gear);
+ PlaySound(sndDenied);
+ exit;
+ end;
+
+ isCursorVisible := false;
+ if (HHGear <> nil) then
+ begin
+ PlaySoundV(sndIncoming, Gear^.Hedgehog^.Team^.voicepack);
+ // Tuck the hedgehog away until the piano attack is completed
+ Gear^.Hedgehog^.Unplaced:= true;
+ HHGear^.X:= _0;
+ HHGear^.Y:= _0;
+ end;
+
+ PauseMusic;
+ Gear^.doStep:= @doStepPianoWork;
+end;
////////////////////////////////////////////////////////////////////////////////
-procedure doStepPiano(Gear: PGear);
+procedure doStepPianoWork(Gear: PGear);
var
r0, r1: LongInt;
odY: hwFloat;
begin
AllInactive := false;
+ // Play piano notes with slot keys
if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and
((CurrentHedgehog^.Gear^.Message and gmSlot) <> 0) then
begin
+ // Piano notes are played if sound OR music (or both) is enabled
case CurrentHedgehog^.Gear^.MsgParam of
- 0: PlaySound(sndPiano0);
- 1: PlaySound(sndPiano1);
- 2: PlaySound(sndPiano2);
- 3: PlaySound(sndPiano3);
- 4: PlaySound(sndPiano4);
- 5: PlaySound(sndPiano5);
- 6: PlaySound(sndPiano6);
- 7: PlaySound(sndPiano7);
- else PlaySound(sndPiano8);
+ 0: PlaySound(sndPiano0, false, false, true);
+ 1: PlaySound(sndPiano1, false, false, true);
+ 2: PlaySound(sndPiano2, false, false, true);
+ 3: PlaySound(sndPiano3, false, false, true);
+ 4: PlaySound(sndPiano4, false, false, true);
+ 5: PlaySound(sndPiano5, false, false, true);
+ 6: PlaySound(sndPiano6, false, false, true);
+ 7: PlaySound(sndPiano7, false, false, true);
+ 8: PlaySound(sndPiano8, false, false, true);
end;
AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtNote);
CurrentHedgehog^.Gear^.MsgParam := 0;
CurrentHedgehog^.Gear^.Message := CurrentHedgehog^.Gear^.Message and (not gmSlot);
end;
- if (*((Gear^.Pos = 3) and ((GameFlags and gfSolidLand) <> 0)) or*) (Gear^.Pos = 5) then
+ if (Gear^.Pos = 5) then
begin
Gear^.dY := Gear^.dY + cGravity * 2;
Gear^.Y := Gear^.Y + Gear^.dY;
@@ -5121,20 +5641,20 @@
if WorldEdge = weWrap then
begin
- if x > LongInt(rightX) then
+ if x > rightX then
repeat
dec(x, playWidth);
dec(rx, playWidth);
- until x <= LongInt(rightX)
- else if x < LongInt(leftX) then
+ until x <= rightX
+ else if x < leftX then
repeat
inc(x, playWidth);
inc(rx, playWidth);
- until x >= LongInt(leftX);
+ until x >= leftX;
end
else if (WorldEdge = weBounce) then
begin
- if (not justBounced) and ((x > LongInt(rightX)) or (x < LongInt(leftX))) then
+ if (not justBounced) and ((x > rightX) or (x < leftX)) then
begin
// reflect
lX:= lX - ldX + ((oX - lX) * 2);
@@ -5187,7 +5707,7 @@
begin
if ((GameFlags and gfSolidLand) = 0) then
begin
- DrawExplosion(rX,rY,Gear^.Radius);
+ doMakeExplosion(rX,rY,Gear^.Radius, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx);
end;
// kick nearby hogs
@@ -5269,7 +5789,7 @@
Gear^.dY.isNegative := not Gear^.dY.isNegative;
HHGear^.dX := Gear^.dX;
HHGear^.dY := Gear^.dY;
- AmmoShove(Gear, 0, 80);
+ AmmoShove(Gear, 0, 79);
Gear^.dX.isNegative := not Gear^.dX.isNegative;
Gear^.dY.isNegative := not Gear^.dY.isNegative;
end;
@@ -5293,6 +5813,7 @@
HHGear := Gear^.Hedgehog^.Gear;
if HHGear = nil then
begin
+ StopSoundChan(Gear^.SoundChannel, 300);
DeleteGear(gear);
exit
end;
@@ -5331,16 +5852,14 @@
flame:= AddGear(gx, gy, gtFlame, gstTmpFlag,
SignAs(AngleSin(HHGear^.Angle) * speed, HHGear^.dX) + rx,
AngleCos(HHGear^.Angle) * ( - speed) + ry, 0);
- flame^.CollisionMask:= lfNotCurrentMask;
- //flame^.FlightTime:= 500; use the default huge value to avoid sticky flame suddenly being damaging as opposed to other flames
+ flame^.CollisionMask:= lfNotCurHogCrate;
if (Gear^.Health mod 30) = 0 then
begin
flame:= AddGear(gx, gy, gtFlame, 0,
SignAs(AngleSin(HHGear^.Angle) * speed, HHGear^.dX) + rx,
AngleCos(HHGear^.Angle) * ( - speed) + ry, 0);
- flame^.CollisionMask:= lfNotCurrentMask;
- //flame^.FlightTime:= 500;
+ flame^.CollisionMask:= lfNotCurHogCrate;
end
end;
Gear^.Timer:= Gear^.Tag
@@ -5348,7 +5867,9 @@
if (Gear^.Health = 0) or ((HHGear^.State and gstHHDriven) = 0) then
begin
+ HHGear^.Message:= HHGear^.Message and (not (gmAttack or gmLeft or gmRight));
HHGear^.State := HHGear^.State and (not gstNotKickable);
+ StopSoundChan(Gear^.SoundChannel, 300);
DeleteGear(Gear);
AfterAttack
end
@@ -5359,8 +5880,8 @@
begin
Gear^.Damage:= i;
FreeAndNilTexture(Gear^.Tex);
- Gear^.Tex := RenderStringTex(trmsg[sidFuel] + ansistring(': ' + inttostr(i) +
- '%'), cWhiteColor, fntSmall)
+ Gear^.Tex := RenderStringTex(FormatA(trmsg[sidFuel], ansistring(inttostr(i))),
+ cWhiteColor, fntSmall)
end
end
end;
@@ -5372,6 +5893,12 @@
HHGear := Gear^.Hedgehog^.Gear;
HHGear^.Message := HHGear^.Message and (not (gmUp or gmDown or gmLeft or gmRight));
HHGear^.State := HHGear^.State or gstNotKickable;
+ (* NOTE: Flamethrower sound is supposed to start instantly (no fade in),
+ but this would cause the game volume to screw up because of a bug in SDL_mixer:
+ https://bugzilla.libsdl.org/show_bug.cgi?id=4205
+ As workaround, a tiny fade-in delay was added.
+ FIXME: Remove the fade-in delay argument when the SDL bug has been fixed. *)
+ Gear^.SoundChannel := LoopSound(sndFlamethrower, 20);
Gear^.doStep := @doStepFlamethrowerWork
end;
@@ -5386,6 +5913,7 @@
HHGear := Gear^.Hedgehog^.Gear;
if HHGear = nil then
begin
+ StopSoundChan(gear^.SoundChannel);
DeleteGear(gear);
exit
end;
@@ -5423,15 +5951,16 @@
land:= AddGear(gx, gy, gtFlake, gstTmpFlag,
SignAs(AngleSin(HHGear^.Angle) * speed, HHGear^.dX) + rx,
AngleCos(HHGear^.Angle) * ( - speed) + ry, 0);
- land^.CollisionMask:= lfNotCurrentMask;
+ land^.CollisionMask:= lfNotCurHogCrate;
Gear^.Timer:= Gear^.Tag
end;
if (Gear^.Health = 0) or ((HHGear^.State and gstHHDriven) = 0) or ((HHGear^.Message and gmAttack) <> 0) then
begin
- HHGear^.Message:= HHGear^.Message and (not gmAttack);
+ HHGear^.Message:= HHGear^.Message and (not (gmAttack or gmLeft or gmRight));
HHGear^.State := HHGear^.State and (not gstNotKickable);
+ StopSoundChan(gear^.SoundChannel);
DeleteGear(Gear);
AfterAttack
end
@@ -5442,8 +5971,8 @@
begin
Gear^.Damage:= i;
FreeAndNilTexture(Gear^.Tex);
- Gear^.Tex := RenderStringTex(trmsg[sidFuel] + ansistring(': ' + inttostr(i) +
- '%'), cWhiteColor, fntSmall)
+ Gear^.Tex := RenderStringTex(FormatA(trmsg[sidFuel], ansistring(inttostr(i))),
+ cWhiteColor, fntSmall)
end
end
end;
@@ -5455,6 +5984,7 @@
HHGear := Gear^.Hedgehog^.Gear;
HHGear^.Message := HHGear^.Message and (not (gmUp or gmDown or gmLeft or gmRight or gmAttack));
HHGear^.State := HHGear^.State or gstNotKickable;
+ Gear^.SoundChannel := LoopSound(sndLandGun);
Gear^.doStep := @doStepLandGunWork
end;
@@ -5507,7 +6037,6 @@
if (tmp^.Kind = gtHedgehog) or (tmp^.Kind = gtMine) or (tmp^.Kind = gtExplosives) then
begin
dmg:= 0;
- //tmp^.State:= tmp^.State or gstFlatened;
if (tmp^.Kind <> gtHedgehog) or (tmp^.Hedgehog^.Effects[heInvulnerable] = 0) then
begin
// base damage on remaining health
@@ -5518,14 +6047,13 @@
dmg:= dmg div Gear^.Boom;
if dmg > 0 then
- ApplyDamage(tmp, CurrentHedgehog, dmg, dsUnknown);
+ ApplyDamage(tmp, CurrentHedgehog, dmg, dsHammer);
end;
- tmp^.dY:= _0_03 * Gear^.Boom
+ tmp^.dY:= _0_03 * Gear^.Boom
end;
if (tmp^.Kind <> gtHedgehog) or (dmg > 0) or (tmp^.Health > tmp^.Damage) then
begin
- //DrawTunnel(tmp^.X, tmp^.Y - _1, _0, _0_5, cHHRadius * 6, cHHRadius * 3);
tmp2:= AddGear(hwRound(tmp^.X), hwRound(tmp^.Y), gtHammerHit, 0, _0, _0, 0);
tmp2^.LinkedGear:= tmp;
SetAllToActive
@@ -5558,18 +6086,17 @@
i := hwRound(Gear^.X) - HitGear^.Radius + 2;
ei := hwRound(Gear^.X) + HitGear^.Radius - 2;
- for j := 1 to 4 do DrawExplosion(i - GetRandom(5), hwRound(Gear^.Y) + 6*j, 3);
- for j := 1 to 4 do DrawExplosion(ei + LongInt(GetRandom(5)), hwRound(Gear^.Y) + 6*j, 3);
+ for j := 1 to 4 do doMakeExplosion(i - GetRandom(5), hwRound(Gear^.Y) + 6*j, 3, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx or EXPLForceDraw);
+ for j := 1 to 4 do doMakeExplosion(ei + LongInt(GetRandom(5)), hwRound(Gear^.Y) + 6*j, 3, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx or EXPLForceDraw);
while i <= ei do
begin
- for j := 1 to 11 do DrawExplosion(i, hwRound(Gear^.Y) + 3*j, 3);
+ for j := 1 to 11 do doMakeExplosion(i, hwRound(Gear^.Y) + 3*j, 3, Gear^.Hedgehog, EXPLNoDamage or EXPLDoNotTouchAny or EXPLNoGfx or EXPLForceDraw);
inc(i, 1)
end;
if CheckLandValue(hwRound(Gear^.X + Gear^.dX + SignAs(_6,Gear^.dX)), hwRound(Gear^.Y + _1_9)
, lfIndestructible) then
begin
- //Gear^.X := Gear^.X + Gear^.dX;
Gear^.Y := Gear^.Y + _1_9
end;
end;
@@ -5581,13 +6108,10 @@
end
else
begin
- //Gear^.dY := Gear^.dY + cGravity;
- //Gear^.Y := Gear^.Y + Gear^.dY;
if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
Gear^.Timer := 1
end;
- //Gear^.X := Gear^.X + HitGear^.dX;
HitGear^.X := Gear^.X;
HitGear^.Y := Gear^.Y;
SetLittle(HitGear^.dY);
@@ -5629,19 +6153,9 @@
i: LongInt;
s: ansistring;
begin
- if (TurnTimeLeft > 0) then
- dec(TurnTimeLeft);
-
AllInactive := false;
hh := Gear^.Hedgehog;
- // no, you can't do that here
- {DrawCentered(hwRound(hh^.Gear^.X) + WorldDx, hwRound(hh^.Gear^.Y) + WorldDy -
- cHHRadius - 14 - hh^.HealthTagTex^.h, hh^.HealthTagTex);
- }
- (*DrawCircle(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Radius, 1.5, 0, 0, $FF,
- $FF);*)
-
if ((Gear^.Message and gmUp) <> 0) then
begin
if (GameTicks and $F) <> 0 then
@@ -5677,12 +6191,6 @@
RecountTeamHealth(hh^.Team);
inc(graves.ar^[Gear^.Tag]^.Health);
inc(Gear^.Tag)
-{-for i:= 0 to High(graves) do begin
- if hh^.Gear^.Health > 0 then begin
- dec(hh^.Gear^.Health);
- inc(graves[i]^.Health);
- end;
- end; -}
end
else
begin
@@ -5699,8 +6207,14 @@
RenderHealth(resgear^.Hedgehog^);
RecountTeamHealth(resgear^.Hedgehog^.Team);
resgear^.Hedgehog^.Effects[heResurrected]:= 1;
+ if resgear^.Hedgehog^.King then
+ resgear^.Hedgehog^.Team^.hasKing:= true;
+ { Reviving a hog implies its clan is now alive, too. }
+ resgear^.Hedgehog^.Team^.Clan^.DeathLogged:= false;
+ if (not resgear^.Hedgehog^.Team^.Passive) then
+ resgear^.Hedgehog^.Team^.Clan^.Passive:= false;
s:= ansistring(resgear^.Hedgehog^.Name);
- AddCaption(FormatA(GetEventString(eidResurrected), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidResurrected), s), capcolDefault, capgrpMessage);
// only make hat-less hedgehogs look like zombies, preserve existing hats
if resgear^.Hedgehog^.Hat = 'NoHat' then
@@ -5714,7 +6228,6 @@
Gear^.Timer := 250;
Gear^.doStep := @doStepIdle;
end
- //if hh^.Gear^.Health = 0 then doStepHedgehogFree(hh^.Gear);
end;
procedure doStepResurrector(Gear: PGear);
@@ -5793,100 +6306,6 @@
dec(Gear^.Timer)
end;
-(*
-////////////////////////////////////////////////////////////////////////////////
-procedure doStepStructure(Gear: PGear);
-var
- x, y: LongInt;
- HH: PHedgehog;
- t: PGear;
-begin
- HH:= Gear^.Hedgehog;
-
- if (Gear^.State and gstMoving) <> 0 then
- begin
- AddCI(Gear);
- Gear^.dX:= _0;
- Gear^.dY:= _0;
- Gear^.State:= Gear^.State and (not gstMoving);
- end;
-
- dec(Gear^.Health, Gear^.Damage);
- Gear^.Damage:= 0;
-
- if Gear^.Pos = 1 then
- begin
- AddCI(Gear);
- AfterAttack;
- if Gear = CurAmmoGear then
- CurAmmoGear:= nil;
- if HH^.Gear <> nil then
- HideHog(HH);
- Gear^.Pos:= 2
- end;
-
- if Gear^.Pos = 2 then
- begin
- if ((GameTicks mod 100) = 0) and (Gear^.Timer < 1000) then
- begin
- if (Gear^.Timer mod 10) = 0 then
- begin
- DeleteCI(Gear);
- Gear^.Y:= Gear^.Y - _0_5;
- AddCI(Gear);
- end;
- inc(Gear^.Timer);
- end;
- if Gear^.Tag <= TotalRounds then
- Gear^.Pos:= 3;
- end;
-
- if Gear^.Pos = 3 then
- if Gear^.Timer < 1000 then
- begin
- if (Gear^.Timer mod 10) = 0 then
- begin
- DeleteCI(Gear);
- Gear^.Y:= Gear^.Y - _0_5;
- AddCI(Gear);
- end;
- inc(Gear^.Timer);
- end
- else
- begin
- if HH^.GearHidden <> nil then
- RestoreHog(HH);
- Gear^.Pos:= 4;
- end;
-
- if Gear^.Pos = 4 then
- if ((GameTicks mod 1000) = 0) and ((GameFlags and gfInvulnerable) = 0) then
- begin
- t:= GearsList;
- while t <> nil do
- begin
- if (t^.Kind = gtHedgehog) and (t^.Hedgehog^.Team^.Clan = HH^.Team^.Clan) then
- t^.Hedgehog^.Effects[heInvulnerable]:= 1;
- t:= t^.NextGear;
- end;
- end;
-
- if Gear^.Health <= 0 then
- begin
- if HH^.GearHidden <> nil then
- RestoreHog(HH);
-
- x := hwRound(Gear^.X);
- y := hwRound(Gear^.Y);
-
- DeleteCI(Gear);
- DeleteGear(Gear);
-
- doMakeExplosion(x, y, 50, CurrentHedgehog, EXPLAutoSound);
- end;
-end;
-*)
-
////////////////////////////////////////////////////////////////////////////////
(*
TARDIS needs
@@ -5907,6 +6326,25 @@
s: ansistring;
begin
HH:= Gear^.Hedgehog;
+if Gear^.Tag = 0 then
+ begin
+ if HH^.Gear <> nil then
+ begin
+ if (HH^.Gear^.Damage <> 0) or (HH^.Gear^.Health = 0) or
+ ((HH^.Gear^.State and (gstMoving or gstHHDeath or gstHHGone or gstDrowning)) <> 0) then
+ begin
+ Gear^.Tag:= 1;
+ HH^.Gear^.State:= HH^.Gear^.State and (not gstAttacking);
+ HH^.Gear^.Message:= HH^.Gear^.Message and (not gmAttack);
+ AfterAttack;
+ end;
+ end
+ else if HH^.GearHidden = nil then
+ Gear^.Tag:= 1;
+ if (Gear^.Tag = 1) and (Gear = CurAmmoGear) then
+ CurAmmoGear:= nil;
+ end;
+
if Gear^.Pos = 2 then
begin
StopSoundChan(Gear^.SoundChannel);
@@ -5915,18 +6353,18 @@
begin
if (HH^.Gear <> nil) and (HH^.Gear^.State and gstInvisible = 0) then
begin
- AfterAttack;
- if Gear = CurAmmoGear then CurAmmoGear := nil;
- if (HH^.Gear^.Damage = 0) and (HH^.Gear^.Health > 0) and
- ((Gear^.State and (gstMoving or gstHHDeath or gstHHGone)) = 0) then
- HideHog(HH)
+ if Gear^.Tag = 0 then
+ AfterAttack;
+ if Gear = CurAmmoGear then
+ CurAmmoGear:= nil;
+ if Gear^.Tag = 0 then
+ HideHog(HH);
end
- //else if (HH^.Gear <> nil) and (HH^.Gear^.State and gstInvisible <> 0) then
- else if (HH^.GearHidden <> nil) then// and (HH^.Gear^.State and gstInvisible <> 0) then
+ else if (HH^.GearHidden <> nil) then
begin
RestoreHog(HH);
s:= ansistring(HH^.Name);
- AddCaption(FormatA(GetEventString(eidTimeTravelEnd), s), cWhiteColor, capgrpMessage)
+ AddCaption(FormatA(GetEventString(eidTimeTravelEnd), s), capcolDefault, capgrpMessage)
end
end;
@@ -5942,8 +6380,7 @@
begin
inc(Gear^.Power);
if (Gear^.Power = 172) and (HH^.Gear <> nil) and
- (HH^.Gear^.Damage = 0) and (HH^.Gear^.Health > 0) and
- ((HH^.Gear^.State and (gstMoving or gstHHDeath or gstHHGone)) = 0) then
+ (Gear^.Tag = 0) then
with HH^.Gear^ do
begin
State:= State or gstAnimation;
@@ -6028,9 +6465,8 @@
////////////////////////////////////////////////////////////////////////////////
(*
-WIP. The ice gun will have the following effects. It has been proposed by sheepluva that it take the appearance of a large freezer
-spewing ice cubes. The cubes will be visual gears only. The scatter from them and the impact snow dust should help hide imprecisions in things like the GearsNear effect.
-For now we assume a "ray" like a deagle projected out from the gun.
+The ice gun has the following effects:
+A "ray" like a deagle is projected out from the gun.
All these effects assume the ray's angle is not changed and that the target type was unchanged over a number of ticks. This is a simplifying assumption for "gun was applying freezing effect to the same target".
* When fired at water a layer of ice textured land is added above the water.
* When fired at non-ice land (land and lfLandMask and not lfIce) the land is overlaid with a thin layer of ice textured land around that point (say, 1 or 2px into land, 1px above). For attractiveness, a slope would probably be needed.
@@ -6038,10 +6474,8 @@
As long as the gun is on the hog, a frozen hog sprite creeps up from the feet to the head.
If the effect is interrupted before reaching the top, the freezing state is cleared.
A frozen hog will animate differently.
- To be decided, but possibly in a similar fashion to a grave when it comes to explosions.
- The hog might (possibly) not be damaged by explosions.
+ Frozen hogs take less damage and are harder to push.
This might make freezing potentially useful for friendlies in a bad position.
- It might be better to allow damage though.
A frozen hog stays frozen for a certain number of turns.
Each turn the frozen overlay becomes fainter, until it fades and the hog animates normally again.
*)
@@ -6056,27 +6490,33 @@
begin
Gear^.Damage:= t;
FreeAndNilTexture(Gear^.Tex);
- Gear^.Tex := RenderStringTex(trmsg[sidFuel] + ansistring(': ' + inttostr(t) +
- '%'), cWhiteColor, fntSmall)
+ Gear^.Tex := RenderStringTex(FormatA(trmsg[sidFuel], ansistring(inttostr(t))),
+ cWhiteColor, fntSmall)
end;
if Gear^.Message and (gmUp or gmDown) <> 0 then
begin
- StopSoundChan(Gear^.SoundChannel);
- Gear^.SoundChannel:= -1;
+ if (Gear^.Tag <> 2) then
+ begin
+ StopSoundChan(Gear^.SoundChannel);
+ Gear^.SoundChannel:= LoopSound(sndIceBeamIdle);
+ Gear^.Tag:= 2;
+ end;
if GameTicks mod 40 = 0 then dec(Gear^.Health)
end
else
begin
- if Gear^.SoundChannel = -1 then
- Gear^.SoundChannel := LoopSound(sndIceBeam);
+ if (Gear^.Tag <> 1) then
+ begin
+ StopSoundChan(Gear^.SoundChannel);
+ Gear^.SoundChannel:= LoopSound(sndIceBeam);
+ Gear^.Tag:= 1;
+ end;
if GameTicks mod 10 = 0 then dec(Gear^.Health)
end
end;
procedure updateTarget(Gear:PGear; newX, newY:HWFloat);
-// var
-// iter:PGear;
begin
with Gear^ do
begin
@@ -6093,10 +6533,7 @@
procedure doStepIceGun(Gear: PGear);
const iceWaitCollision = 0;
const iceCollideWithGround = 1;
-//const iceWaitNextTarget:Longint = 2;
-//const iceCollideWithHog:Longint = 4;
const iceCollideWithWater = 5;
-//const waterFreezingTime:Longint = 500;
const groundFreezingTime = 1000;
const iceRadius = 32;
const iceHeight = 40;
@@ -6117,21 +6554,31 @@
exit
end;
updateFuel(Gear);
+ if (WorldEdge <> weBounce) then
+ if WorldWrap(Gear) and (WorldEdge = weWrap) and (Gear^.Target.X = NoPointX) then
+ // Use FlightTime to count number of times the gear has world-wrapped
+ inc(Gear^.FlightTime);
with Gear^ do
begin
HedgehogChAngle(HHGear);
ndX:= SignAs(AngleSin(HHGear^.Angle), HHGear^.dX) * _4;
ndY:= -AngleCos(HHGear^.Angle) * _4;
- if (ndX <> dX) or (ndY <> dY) or
- ((Target.X <> NoPointX) and (Target.X and LAND_WIDTH_MASK = 0) and
+ if (ndX <> dX) or (ndY <> dY) or (Gear^.Message and (gmUp or gmDown) <> 0) or
+ (((Target.X <> NoPointX) and (Target.X and LAND_WIDTH_MASK = 0) and
(Target.Y and LAND_HEIGHT_MASK = 0) and ((Land[Target.Y, Target.X] = 0)) and
- (not CheckCoordInWater(Target.X, Target.Y))) then
+ (not CheckCoordInWater(Target.X, Target.Y))) and (CheckGearNear(gtAirMine, int2hwFloat(Target.X),int2hwFloat(Target.Y), Gear^.Radius*3, Gear^.Radius*3) = nil) and
+ (not ((WorldEdge = weBounce) and ((Target.X > rightX) or (Target.X < leftX))))) then
begin
updateTarget(Gear, ndX, ndY);
Timer := iceWaitCollision;
+ FlightTime := 0;
end
- else
+ // Extend ice beam, unless it is far outside he map boundaries
+ else if (not ((hwRound(X + dX) > max(LAND_WIDTH,4096)*2) or
+ (hwRound(X + dX) < -max(LAND_WIDTH,4096)*2) or
+ (hwRound(Y + dY) < -max(LAND_HEIGHT,4096)*2) or
+ (hwRound(Y + dY) > max(LAND_HEIGHT,4096)+512))) then
begin
X:= X + dX;
Y:= Y + dY;
@@ -6142,7 +6589,7 @@
if Target.X <> NoPointX then
begin
CheckCollision(Gear);
- if (State and gstCollision) <> 0 then
+ if ((State and gstCollision) <> 0) or (CheckGearNear(gtAirMine, int2hwFloat(Target.X),int2hwFloat(Target.Y), Gear^.Radius*4, Gear^.Radius*4) <> nil) then
begin
if Timer = iceWaitCollision then
begin
@@ -6156,8 +6603,8 @@
(Land[Target.Y, Target.X] = lfIce) and
((Target.Y+iceHeight+5 > cWaterLine) or
((WorldEdge = weSea) and
- ((Target.X+iceHeight+5 > LongInt(rightX)) or
- (Target.X-iceHeight-5 < LongInt(leftX)))))
+ ((Target.X+iceHeight+5 > rightX) or
+ (Target.X-iceHeight-5 < leftX))))
) then
begin
if Timer = iceWaitCollision then
@@ -6187,8 +6634,8 @@
while iter <> nil do
begin
if (iter^.State and gstFrozen = 0) and
- ((iter^.Kind = gtExplosives) or (iter^.Kind = gtCase) or (iter^.Kind = gtMine) or (iter^.Kind = gtSMine)) and
- (abs(LongInt(iter^.X.Round) - target.x) + abs(LongInt(iter^.Y.Round) - target.y) + 2 < 2 * iceRadius)
+ ((iter^.Kind = gtExplosives) or (iter^.Kind = gtAirMine) or (iter^.Kind = gtCase) or (iter^.Kind = gtMine) or (iter^.Kind = gtSMine)) and
+ (abs(hwRound(iter^.X) - target.x) + abs(hwRound(iter^.Y) - target.y) + 2 < 2 * iceRadius)
and (Distance(iter^.X - int2hwFloat(target.x), iter^.Y - int2hwFloat(target.y)) < int2hwFloat(iceRadius * 2)) then
begin
for t:= 0 to 5 do
@@ -6231,6 +6678,28 @@
iter^.State:= iter^.State or gstFrozen;
AddCI(iter)
end
+ else if iter^.Kind = gtAirMine then
+ begin
+ iter^.Damage:= 0;
+ iter^.State:= iter^.State or gstFrozen;
+ if (hwRound(iter^.X) < RightX-16) and (hwRound(iter^.X) > LeftX+16) and
+ (hwRound(iter^.Y) > topY+16) and (hwRound(iter^.Y) < LAND_HEIGHT-16) then
+ begin
+ AddCI(iter);
+ iter^.X:= int2hwFloat(min(RightX-16,max(hwRound(iter^.X), LeftX+16)));
+ iter^.Y:= int2hwFloat(min(LAND_HEIGHT-16,max(hwRound(iter^.Y),TopY+16)));
+ ForcePlaceOnLand(hwRound(iter^.X)-16, hwRound(iter^.Y)-16, sprFrozenAirMine, 0, lfIce, $FFFFFFFF, false, false, false);
+ iter^.State:= iter^.State or gstInvisible
+ end
+ else
+ begin
+ updateTarget(Gear, ndX, ndY);
+ FlightTime := 0;
+ Timer := iceWaitCollision;
+ Power := GameTicks;
+ iter^.State:= iter^.State and (not gstNoGravity)
+ end
+ end
else // gtExplosives
begin
iter^.State:= iter^.State or gstFrozen;
@@ -6240,9 +6709,9 @@
iter:= iter^.NextGear
end;
- // FillRoundInLandWithIce(Target.X, Target.Y, iceRadius);
SetAllHHToActive;
Timer := iceWaitCollision;
+ Power:= GameTicks
end;
if (Timer = iceCollideWithWater) and ((GameTicks - Power) > groundFreezingTime div 2) then
@@ -6252,29 +6721,13 @@
DrawIceBreak(Target.X, Target.Y, iceRadius, iceHeight)
else if Target.Y+iceHeight+5 > cWaterLine then
DrawIceBreak(Target.X, Target.Y+iceHeight+5, iceRadius, iceHeight)
- else if Target.X+iceHeight+5 > LongInt(rightX) then
+ else if Target.X+iceHeight+5 > rightX then
DrawIceBreak(Target.X+iceHeight+5, Target.Y, iceRadius, iceHeight)
else
DrawIceBreak(Target.X-iceHeight-5, Target.Y, iceRadius, iceHeight);
SetAllHHToActive;
Timer := iceWaitCollision;
end;
-(*
- Any ideas for something that would look good here?
- if (Target.X <> NoPointX) and ((Timer = iceCollideWithGround) or (Timer = iceCollideWithWater)) and (GameTicks mod max((groundFreezingTime-((GameTicks - Power)*2)),2) = 0) then //and CheckLandValue(Target.X, Target.Y, lfIce) then
- begin
- vg:= AddVisualGear(Target.X+random(20)-10, Target.Y+random(40)-10, vgtDust, 1);
- if vg <> nil then
- begin
- i:= random(100) + 155;
- vg^.Tint:= IceColor or $FF;
- vg^.Angle:= random(360);
- vg^.dx:= 0.001 * random(80);
- vg^.dy:= 0.001 * random(80)
- end
- end;
-*)
-
// freeze nearby hogs
hogs := GearsNear(int2hwFloat(Target.X), int2hwFloat(Target.Y), gtHedgehog, Gear^.Radius*2);
if hogs.size > 0 then
@@ -6301,17 +6754,25 @@
Target.Y:= gY;
X:= HHGear^.X;
Y:= HHGear^.Y
- end;
- if (gX > max(LAND_WIDTH,4096)*2) or
- (gX < -max(LAND_WIDTH,4096)) or
- (gY < -max(LAND_HEIGHT,4096)) or
- (gY > max(LAND_HEIGHT,4096)+512) then
+ end
+ else if (WorldEdge = weBounce) and ((gX > rightX) or (gX < leftX)) then
begin
- //X:= HHGear^.X;
- //Y:= HHGear^.Y
Target.X:= gX;
Target.Y:= gY;
+ X:= HHGear^.X;
+ Y:= HHGear^.Y
end
+ else
+ begin
+ iter:= CheckGearNear(Gear, gtAirMine, Gear^.Radius*2, Gear^.Radius*2);
+ if (iter <> nil) and (iter^.State <> gstFrozen) then
+ begin
+ Target.X:= gX;
+ Target.Y:= gY;
+ X:= HHGear^.X;
+ Y:= HHGear^.Y
+ end
+ end;
end
end;
end;
@@ -6354,7 +6815,7 @@
if (Gear^.State and gstTmpFlag <> 0) or (GameTicks and $7 = 0) then
begin
doStepFallingGear(Gear);
- if (Gear^.Tag = 1) and (GameTicks and $FF = 0) and (hwRound(Gear^.X) < LongInt(leftX)) or (hwRound(Gear^.X) > LongInt(rightX)) or (hwRound(Gear^.Y) < LongInt(topY)) then
+ if (Gear^.Tag = 1) and (GameTicks and $FF = 0) and (hwRound(Gear^.X) < leftX) or (hwRound(Gear^.X) > rightX) or (hwRound(Gear^.Y) < topY) then
begin
Gear^.X:= int2hwFloat(GetRandom(rightX-leftX)+leftX);
Gear^.Y:= int2hwFloat(GetRandom(LAND_HEIGHT-topY)+topY);
@@ -6363,96 +6824,181 @@
end;
end
end;
-(*
+
+// TODO: Finish creeper implementation
procedure doStepCreeper(Gear: PGear);
-var hogs: PGearArrayS;
- HHGear: PGear;
- tdX: hwFloat;
- dir: LongInt;
+var i,t,targDist,tmpDist: LongWord;
+ targ, tmpG: PGear;
+ tX, tY: hwFloat;
+ vg: PVisualGear;
begin
-doStepFallingGear(Gear);
-if Gear^.Timer > 0 then dec(Gear^.Timer);
-// creeper sleep phase
-if (Gear^.Hedgehog = nil) and (Gear^.Timer > 0) then exit;
-
-if Gear^.Hedgehog <> nil then HHGear:= Gear^.Hedgehog^.Gear
-else HHGear:= nil;
-
-// creeper boom phase
-if (Gear^.State and gstTmpFlag <> 0) then
- begin
- if (Gear^.Timer = 0) then
- begin
- doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 300, CurrentHedgehog, EXPLAutoSound);
- DeleteGear(Gear)
- end;
- // ssssss he essssscaped
- if (Gear^.Timer > 250) and ((HHGear = nil) or
- (((abs(HHGear^.X.Round-Gear^.X.Round) + abs(HHGear^.Y.Round-Gear^.Y.Round) + 2) > 180) and
- (Distance(HHGear^.X-Gear^.X,HHGear^.Y-Gear^.Y) > _180))) then
- begin
- Gear^.State:= Gear^.State and (not gstTmpFlag);
- Gear^.Timer:= 0
- end;
- exit
- end;
-
-// Search out a new target, as target seek time has expired, target is dead, target is out of range, or we did not have a target
-if (HHGear = nil) or (Gear^.Timer = 0) or
- (((abs(HHGear^.X.Round-Gear^.X.Round) + abs(HHGear^.Y.Round-Gear^.Y.Round) + 2) > Gear^.Angle) and
- (Distance(HHGear^.X-Gear^.X,HHGear^.Y-Gear^.Y) > int2hwFloat(Gear^.Angle)))
- then
- begin
- hogs := GearsNear(Gear^.X, Gear^.Y, gtHedgehog, Gear^.Angle);
- if hogs.size > 1 then
- Gear^.Hedgehog:= hogs.ar^[GetRandom(hogs.size)]^.Hedgehog
- else if hogs.size = 1 then Gear^.Hedgehog:= hogs.ar^[0]^.Hedgehog
- else Gear^.Hedgehog:= nil;
- if Gear^.Hedgehog <> nil then Gear^.Timer:= 5000;
- exit
- end;
-
-// we have a target. move the creeper.
-if HHGear <> nil then
- begin
- // GOTCHA
- if ((abs(HHGear^.X.Round-Gear^.X.Round) + abs(HHGear^.Y.Round-Gear^.Y.Round) + 2) < 50) and
- (Distance(HHGear^.X-Gear^.X,HHGear^.Y-Gear^.Y) < _50) then
- begin
- // hisssssssssss
- Gear^.State:= Gear^.State or gstTmpFlag;
- Gear^.Timer:= 1500;
+ targ:= nil;
+ doStepFallingGear(Gear);
+ if (Gear^.State and gstFrozen) <> 0 then
+ begin
+ if Gear^.Damage > 0 then
+ begin
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
+ DeleteGear(Gear)
+ end;
exit
end;
- if (Gear^.State and gstMoving <> 0) then
- begin
- Gear^.dY:= _0;
- Gear^.dX:= _0;
+ if (TurnTimeLeft = 0) or (Gear^.Angle = 0) or (Gear^.Hedgehog = nil) or (Gear^.Hedgehog^.Gear = nil) then
+ begin
+ Gear^.Hedgehog:= nil;
+ targ:= nil;
end
- else if (GameTicks and $FF = 0) then
- begin
- tdX:= HHGear^.X-Gear^.X;
- dir:= hwSign(tdX);
- if TestCollisionX(Gear, dir) = 0 then
- Gear^.X:= Gear^.X + signAs(_1,tdX);
- if TestCollisionXwithXYShift(Gear, signAs(_10,tdX), 0, dir) <> 0 then
- begin
- Gear^.dX:= SignAs(_0_15, tdX);
- Gear^.dY:= -_0_3;
- Gear^.State:= Gear^.State or gstMoving
+ else if Gear^.Hedgehog <> nil then
+ targ:= Gear^.Hedgehog^.Gear;
+ if (targ <> nil) and ((GameTicks and $3F) = 0) and (TestCollisionYKick(Gear, 1) <> 0) then
+ begin
+ vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmokeWhite);
+ if vg <> nil then vg^.Tint:= $FF0000FF;
+ if (Gear^.X < targ^.X) then // need to add collision checks to avoid walking off edges or getting too close to obstacles where jumping is needed
+ if (WorldEdge = weWrap) and ((targ^.X - Gear^.X) > ((Gear^.X - int2hwFloat(LeftX)) + (int2hwFloat(RightX) - targ^.X))) then
+ Gear^.dX:= -cLittle
+ else
+ Gear^.dX:= cLittle
+ else if (Gear^.X > targ^.X) then
+ if (WorldEdge = weWrap) and ((Gear^.X - targ^.X) > ((targ^.X - int2hwFloat(LeftX)) + (int2hwFloat(RightX) - Gear^.X))) then
+ Gear^.dX:= cLittle
+ else
+ Gear^.dX:= -cLittle;
+ if (GetRandom(30) = 0) then
+ begin
+ Gear^.dY := -_0_15;
+ Gear^.dX:= SignAs(_0_15, Gear^.dX);
+ end;
+ MakeHedgehogsStep(Gear);
+ end;
+ if (TurnTimeLeft = 0) and ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) then
+ AllInactive := false;
+
+ if targ <> nil then
+ begin
+ tX:=Gear^.X-targ^.X;
+ tY:=Gear^.Y-targ^.Y;
+ // allow escaping - should maybe flag this too
+ if (GameTicks > Gear^.FlightTime+10000) or
+ ((tX.Round+tY.Round > Gear^.Angle*6) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) > sqr(Gear^.Angle*6))) then
+ targ:= nil
+ end;
+
+ // If in ready timer, or after turn, or in first 5 seconds of turn (really a window due to extra time utility)
+ // or mine is inactive due to lack of gsttmpflag or hunting is disabled due to seek radius of 0
+ // then we aren't hunting
+ if (ReadyTimeLeft > 0) or (TurnTimeLeft = 0) or
+ ((TurnTimeLeft < cHedgehogTurnTime) and (cHedgehogTurnTime-TurnTimeLeft < 5000)) or
+ (Gear^.State and gsttmpFlag = 0) or
+ (Gear^.Angle = 0) then
+ gear^.State:= gear^.State and (not gstChooseTarget)
+ else if
+ // todo, allow not finding new target, set timeout on target retention
+ (Gear^.State and gstAttacking = 0) and
+ ((GameTicks and $FF) = 17) and
+ (GameTicks > Gear^.FlightTime) then // recheck hunted hog
+ begin
+ gear^.State:= gear^.State or gstChooseTarget;
+ if targ <> nil then
+ targDist:= Distance(Gear^.X-targ^.X,Gear^.Y-targ^.Y).Round
+ else targDist:= 0;
+ for t:= 0 to Pred(TeamsCount) do
+ with TeamsArray[t]^ do
+ for i:= 0 to cMaxHHIndex do
+ if Hedgehogs[i].Gear <> nil then
+ begin
+ tmpG:= Hedgehogs[i].Gear;
+ tX:=Gear^.X-tmpG^.X;
+ tY:=Gear^.Y-tmpG^.Y;
+ if (Gear^.Angle = $FFFFFFFF) or
+ ((tX.Round+tY.Round < Gear^.Angle) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Angle))) then
+ begin
+ if targ <> nil then tmpDist:= Distance(tX,tY).Round;
+ if (targ = nil) or (tmpDist < targDist) then
+ begin
+ if targ = nil then targDist:= Distance(tX,tY).Round
+ else targDist:= tmpDist;
+ Gear^.Hedgehog:= @Hedgehogs[i];
+ targ:= tmpG;
+ end
+ end
+ end;
+ if targ <> nil then Gear^.FlightTime:= GameTicks + 5000
+ end;
+
+ if ((Gear^.State and gsttmpFlag) <> 0) and (Gear^.Health <> 0) then
+ begin
+ if ((Gear^.State and gstAttacking) = 0) then
+ begin
+ if ((GameTicks and $1F) = 0) then
+ begin
+ if targ <> nil then
+ begin
+ tX:=Gear^.X-targ^.X;
+ tY:=Gear^.Y-targ^.Y;
+ if (tX.Round+tY.Round < Gear^.Boom) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Boom)) then
+ Gear^.State := Gear^.State or gstAttacking
+ end
+ else if (Gear^.Angle > 0) and (CheckGearNear(Gear, gtHedgehog, Gear^.Boom, Gear^.Boom) <> nil) then
+ Gear^.State := Gear^.State or gstAttacking
+ end
end
- end;
- end;
+ else // gstAttacking <> 0
+ begin
+ AllInactive := false;
+ if (Gear^.Timer and $1FF) = 0 then
+ PlaySound(sndVaporize);
+ if Gear^.Timer = 0 then
+ begin
+ // recheck
+ if targ <> nil then
+ begin
+ tX:=Gear^.X-targ^.X;
+ tY:=Gear^.Y-targ^.Y;
+ if (tX.Round+tY.Round < Gear^.Boom) and
+ (hwRound(hwSqr(tX) + hwSqr(tY)) < sqr(Gear^.Boom)) then
+ begin
+ Gear^.Hedgehog:= CurrentHedgehog;
+ tmpG:= FollowGear;
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
+ FollowGear:= tmpG;
+ DeleteGear(Gear);
+ exit
+ end
+ end
+ else if (Gear^.Angle > 0) and (CheckGearNear(Gear, gtHedgehog, Gear^.Boom, Gear^.Boom) <> nil) then
+ begin
+ Gear^.Hedgehog:= CurrentHedgehog;
+ doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
+ DeleteGear(Gear);
+ exit
+ end;
+ Gear^.State:= Gear^.State and (not gstAttacking);
+ Gear^.Timer:= Gear^.WDTimer
+ end;
+ if Gear^.Timer > 0 then
+ dec(Gear^.Timer);
+ end
+ end
+ else // gsttmpFlag = 0
+ if (TurnTimeLeft = 0)
+ or ((GameFlags and gfInfAttack <> 0) and (GameTicks > Gear^.FlightTime))
+ or (CurrentHedgehog^.Gear = nil) then
+ begin
+ Gear^.FlightTime:= GameTicks;
+ Gear^.State := Gear^.State or gsttmpFlag
+ end
end;
-*)
+
////////////////////////////////////////////////////////////////////////////////
procedure doStepKnife(Gear: PGear);
-//var ox, oy: LongInt;
-// la: hwFloat;
var a: real;
begin
// Gear is shrunk so it can actually escape the hog without carving into the terrain
- if (Gear^.Radius = 4) and (Gear^.CollisionMask = $FFFF) then Gear^.Radius:= 7;
+ if (Gear^.Radius = 4) and (Gear^.CollisionMask = lfAll) then Gear^.Radius:= 7;
if Gear^.Damage > 100 then Gear^.CollisionMask:= 0
else if Gear^.Damage > 30 then
if GetRandom(max(4,18-Gear^.Damage div 10)) < 3 then Gear^.CollisionMask:= 0;
@@ -6472,29 +7018,12 @@
end
else if (Gear^.CollisionIndex = -1) and (Gear^.Timer = 0) then
begin
- (*ox:= 0; oy:= 0;
- if TestCollisionYwithGear(Gear, -1) <> 0 then oy:= -1;
- if TestCollisionXwithGear(Gear, 1) <> 0 then ox:= 1;
- if TestCollisionXwithGear(Gear, -1) <> 0 then ox:= -1;
- if TestCollisionYwithGear(Gear, 1) <> 0 then oy:= 1;
-
- la:= _10000;
- if (ox <> 0) or (oy <> 0) then
- la:= CalcSlopeNearGear(Gear, ox, oy);
- if la = _10000 then
- begin
- // debug for when we couldn't get an angle
- //AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtSmokeWhite);
-*)
if Gear^.Health > 0 then
PlaySound(Gear^.ImpactSound);
Gear^.DirAngle:= DxDy2Angle(Gear^.dX, Gear^.dY) + (random(30)-15);
if (Gear^.dX.isNegative and Gear^.dY.isNegative) or
((not Gear^.dX.isNegative) and (not Gear^.dY.isNegative)) then Gear^.DirAngle:= Gear^.DirAngle-90;
- // end
- // else Gear^.DirAngle:= hwFloat2Float(la)*90; // sheepluva's comment claims 45deg = 0.5 - yet orientation doesn't seem consistent?
- // AddFileLog('la: '+floattostr(la)+' DirAngle: '+inttostr(round(Gear^.DirAngle)));
Gear^.dX:= _0;
Gear^.dY:= _0;
Gear^.State:= Gear^.State and (not gstMoving) or gstCollision;
@@ -6514,179 +7043,6 @@
end;
////////////////////////////////////////////////////////////////////////////////
-procedure doStepDuck(Gear: PGear);
-begin
- // Mirror duck on bounce world edge, even turn around later
- if WorldWrap(Gear) and (WorldEdge = weBounce) then
- begin
- Gear^.Tag:= Gear^.Tag * -1;
- if Gear^.Pos = 2 then
- Gear^.Pos:= 1
- else if Gear^.Pos = 1 then
- Gear^.Pos:= 2
- else if Gear^.Pos = 5 then
- Gear^.Pos:= 6
- else if Gear^.Pos = 5 then
- Gear^.Pos:= 5;
- end;
-
- AllInactive := false;
-
- // Duck falls (Pos = 0)
- if Gear^.Pos = 0 then
- doStepFallingGear(Gear);
-
- (* Check if duck is near water surface
- (Karma is distance from water) *)
- if (Gear^.Pos <> 1) and (Gear^.Pos <> 2) and (cWaterLine <= hwRound(Gear^.Y) + Gear^.Karma) then
- begin
- if cWaterLine = hwRound(Gear^.Y) + Gear^.Karma then
- begin
- // Let's make that duck swim!
- // Does the duck come FROM the Sea edge? (left or right)
- if (((Gear^.Pos = 3) or (Gear^.Pos = 7)) and (cWindSpeed > _0)) or (((Gear^.Pos = 4) or (Gear^.Pos = 8)) and (cWindSpeed < _0)) then
- begin
- PlaySound(sndDuckWater);
- Gear^.DirAngle:= 0;
- Gear^.Pos:= 1;
- Gear^.dY:= _0;
- end;
-
- // Duck comes either falling (usual case) or was rising from below
- if (Gear^.Pos = 0) or (Gear^.Pos = 5) or (Gear^.Pos = 6) then
- begin
- PlaySound(sndDroplet2);
- if Gear^.dY > _0_4 then
- PlaySound(sndDuckWater);
- Gear^.Pos:= 1;
- Gear^.dY:= _0;
- end;
- end
- else if Gear^.Pos = 0 then
- Gear^.Pos:= 5;
- end;
-
- // Manual speed handling when duck is on water
- if Gear^.Pos <> 0 then
- begin
- Gear^.X:= Gear^.X + Gear^.dX;
- Gear^.Y:= Gear^.Y + Gear^.dY;
- end;
-
- // Handle speed
- // 1-4: On water: Let's swim!
- if Gear^.Pos = 1 then
- // On water (normal)
- Gear^.dX:= cWindSpeed * Gear^.Damage
- else if Gear^.Pos = 2 then
- // On water, mirrored (after bounce edge bounce)
- Gear^.dX:= -cWindSpeed * Gear^.Damage
- else if Gear^.Pos = 3 then
- // On left Sea edge
- Gear^.dY:= cWindSpeed * Gear^.Damage
- else if Gear^.Pos = 4 then
- // On right Sea edge
- Gear^.dY:= -cWindSpeed * Gear^.Damage
- // 5-8: Underwater: Slowly rise to the surface and slightly follow wind
- else if Gear^.Pos = 5 then
- // Underwater (normal)
- begin
- Gear^.dX:= (cWindSpeed / 4) * Gear^.Damage;
- Gear^.dY:= -_0_07;
- end
- else if Gear^.Pos = 6 then
- // Underwater, mirrored duck (after bounce edge bounce)
- begin
- Gear^.dX:= -(cWindSpeed / 4) * Gear^.Damage;
- Gear^.dY:= -_0_07;
- end
- else if Gear^.Pos = 7 then
- // Inside left Sea edge
- begin
- Gear^.dX:= _0_07;
- Gear^.dY:= (cWindSpeed / 4) * Gear^.Damage;
- end
- else if Gear^.Pos = 8 then
- // Inside right Sea edge
- begin
- Gear^.dX:= -_0_07;
- Gear^.dY:= -(cWindSpeed / 4) * Gear^.Damage;
- end;
-
-
- // Rotate duck and change direction when reaching Sea world edge (Pos 3 or 4)
- if (WorldEdge = weSea) and (Gear^.Pos <> 3) and (Gear^.Pos <> 4) then
- // Swimming TOWARDS left edge
- if (LeftX >= hwRound(Gear^.X) - Gear^.Karma) and ((cWindSpeed < _0) or ((Gear^.Pos = 0) or (Gear^.Pos = 7))) then
- begin
- // Turn duck when reaching edge the first time
- if (Gear^.Pos <> 3) and (Gear^.Pos <> 7) then
- begin
- if Gear^.Tag = 1 then
- Gear^.DirAngle:= 90
- else
- Gear^.DirAngle:= 270;
- end;
-
- // Reaching the edge surface
- if (LeftX = hwRound(Gear^.X) - Gear^.Karma) and (Gear^.Pos <> 3) then
- // We are coming from the horizontal side
- begin
- PlaySound(sndDuckWater);
- Gear^.dX:= _0;
- Gear^.Pos:= 3;
- end
- else
- // We are coming from inside the Sea, go into “surfacing” mode
- Gear^.Pos:= 7;
-
- end
-
- // Swimming TOWARDS right edge (similar to left edge)
- else if (RightX <= hwRound(Gear^.X) + Gear^.Karma) and ((cWindSpeed > _0) or ((Gear^.Pos = 0) or (Gear^.Pos = 8))) then
- begin
- if (Gear^.Pos <> 4) and (Gear^.Pos <> 8) then
- begin
- if Gear^.Tag = 1 then
- Gear^.DirAngle:= 270
- else
- Gear^.DirAngle:= 90;
- end;
-
- if (RightX = hwRound(Gear^.X) + Gear^.Karma) and (Gear^.Pos <> 4) then
- begin
- PlaySound(sndDuckWater);
- Gear^.dX:= _0;
- Gear^.Pos:= 4;
- end
- else
- Gear^.Pos:= 8;
-
- end;
-
-
- if Gear^.Pos <> 0 then
- // Manual collision check required because we don't use onStepFallingGear in this case
- CheckCollision(Gear);
- if (Gear^.Timer = 0) or ((Gear^.State and gstCollision) <> 0) then
- // Explode duck
- begin
- doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
- PlaySound(sndDuckDie);
- DeleteGear(Gear);
- exit;
- end;
-
- // Update timer stuff
- if Gear^.Timer < 6000 then
- Gear^.RenderTimer:= true
- else
- Gear^.RenderTimer:= false;
-
- dec(Gear^.Timer);
-end;
-
-////////////////////////////////////////////////////////////////////////////////
procedure doStepMinigunWork(Gear: PGear);
var HHGear: PGear;
bullet: PGear;
@@ -6715,7 +7071,7 @@
ry := rndSign(getRandomf * _0_2);
bullet:= AddGear(gx, gy, gtMinigunBullet, 0, SignAs(AngleSin(HHGear^.Angle) * _0_8, HHGear^.dX) + rx, AngleCos(HHGear^.Angle) * ( - _0_8) + ry, 0);
- bullet^.CollisionMask:= lfNotCurrentMask;
+ bullet^.CollisionMask:= lfNotCurHogCrate;
bullet^.WDTimer := Gear^.WDTimer;
Inc(Gear^.WDTimer);
@@ -6769,137 +7125,4 @@
Gear^.doStep := @doStepBulletWork
end;
-(*
- This didn't end up getting used, but, who knows, might be reasonable for javellin or something
-// Make the knife initial angle based on the hog attack angle, or is that too hard?
-procedure doStepKnife(Gear: PGear);
-var t,
- gx, gy, ga, // gear x,y,angle
- lx, ly, la, // land x,y,angle
- ox, oy, // x,y offset
- w, h, // wXh of clip area
- tx, ty // tip position in sprite
- : LongInt;
- surf: PSDL_Surface;
- s: hwFloat;
-
-begin
- Gear^.dY := Gear^.dY + cGravity;
- if (GameFlags and gfMoreWind) <> 0 then
- Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density;
- Gear^.X := Gear^.X + Gear^.dX;
- Gear^.Y := Gear^.Y + Gear^.dY;
- CheckGearDrowning(Gear);
- gx:= hwRound(Gear^.X);
- gy:= hwRound(Gear^.Y);
- if Gear^.State and gstDrowning <> 0 then exit;
- with Gear^ do
- begin
- if CheckLandValue(gx, gy, lfLandMask) then
- begin
- t:= Angle + hwRound((hwAbs(dX)+hwAbs(dY)) * _10);
-
- if t < 0 then inc(t, 4096)
- else if 4095 < t then dec(t, 4096);
- Angle:= t;
-
- DirAngle:= Angle / 4096 * 360
- end
- else
- begin
-//This is the set of postions for the knife.
-//Using FlipSurface and copyToXY the knife can be written to the LandPixels at 32 positions, and an appropriate line drawn in Land.
- t:= Angle mod 1024;
- case t div 128 of
- 0: begin
- ox:= 2; oy:= 5;
- w := 25; h:= 5;
- tx:= 0; ty:= 2
- end;
- 1: begin
- ox:= 2; oy:= 15;
- w:= 24; h:= 8;
- tx:= 0; ty:= 7
- end;
- 2: begin
- ox:= 2; oy:= 27;
- w:= 23; h:= 12;
- tx:= -12; ty:= -5
- end;
- 3: begin
- ox:= 2; oy:= 43;
- w:= 21; h:= 15;
- tx:= 0; ty:= 14
- end;
- 4: begin
- ox:= 29; oy:= 8;
- w:= 19; h:= 19;
- tx:= 0; ty:= 17
- end;
- 5: begin
- ox:= 29; oy:= 32;
- w:= 15; h:= 21;
- tx:= 0; ty:= 20
- end;
- 6: begin
- ox:= 51; oy:= 3;
- w:= 11; h:= 23;
- tx:= 0; ty:= 22
- end;
- 7: begin
- ox:= 51; oy:= 34;
- w:= 7; h:= 24;
- tx:= 0; ty:= 23
- end
- end;
-
- surf:= SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, RMask, GMask, BMask, AMask);
- copyToXYFromRect(SpritesData[sprKnife].Surface, surf, ox, oy, w, h, 0, 0);
- // try to make the knife hit point first
- lx := 0;
- ly := 0;
- if CalcSlopeTangent(Gear, gx, gy, lx, ly, 255) then
- begin
- la:= vector2Angle(int2hwFloat(lx), int2hwFloat(ly));
- ga:= vector2Angle(dX, dY);
- AddFileLog('la: '+inttostr(la)+' ga: '+inttostr(ga)+' Angle: '+inttostr(Angle));
- // change to 0 to 4096 forced by LongWord in Gear
- if la < 0 then la:= 4096+la;
- if ga < 0 then ga:= 4096+ga;
- if ((Angle > ga) and (Angle < la)) or ((Angle < ga) and (Angle > la)) then
- begin
- if Angle >= 2048 then dec(Angle, 2048)
- else if Angle < 2048 then inc(Angle, 2048)
- end;
- AddFileLog('la: '+inttostr(la)+' ga: '+inttostr(ga)+' Angle: '+inttostr(Angle))
- end;
- case Angle div 1024 of
- 0: begin
- flipSurface(surf, true);
- flipSurface(surf, true);
- BlitImageAndGenerateCollisionInfo(gx-(w-tx), gy-(h-ty), w, surf)
- end;
- 1: begin
- flipSurface(surf, false);
- BlitImageAndGenerateCollisionInfo(gx-(w-tx), gy-ty, w, surf)
- end;
- 2: begin // knife was actually drawn facing this way...
- BlitImageAndGenerateCollisionInfo(gx-tx, gy-ty, w, surf)
- end;
- 3: begin
- flipSurface(surf, true);
- BlitImageAndGenerateCollisionInfo(gx-tx, gy-(h-ty), w, surf)
- end
- end;
- SDL_FreeSurface(surf);
- // this needs to calculate actual width/height + land clipping since update texture doesn't.
- // i.e. this will crash if you fire near sides of map, but until I get the blit right, not going to put real values
- UpdateLandTexture(hwRound(X)-32, 64, hwRound(Y)-32, 64, true);
- DeleteGear(Gear);
- exit
- end
- end;
-end;
-*)
-
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsHandlersRope.pas
--- a/hedgewars/uGearsHandlersRope.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsHandlersRope.pas Wed Jul 31 23:14:27 2019 +0200
@@ -50,7 +50,7 @@
((TestCollisionXwithGear(HHGear, 1) <> 0) or (TestCollisionXwithGear(HHGear, -1) <> 0)) then
begin
HHGear^.X:= tX;
- HHGear^.dX.isNegative:= hwRound(tX) > LongInt(leftX) + HHGear^.Radius * 2
+ HHGear^.dX.isNegative:= hwRound(tX) > leftX + HHGear^.Radius * 2
end;
if (HHGear^.Hedgehog^.CurAmmoType = amParachute) and (HHGear^.dY > _0_39) then
@@ -66,7 +66,7 @@
or (TestCollisionYwithGear(HHGear, 1) <> 0) then
begin
DeleteGear(Gear);
- if (TestCollisionYwithGear(HHGear, 1) <> 0) and (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then
+ if (TestCollisionYwithGear(HHGear, 1) <> 0) and (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
HHGear^.Hedgehog^.CurAmmoType:= amRope;
isCursorVisible := false;
ApplyAmmoChanges(HHGear^.Hedgehog^);
@@ -160,7 +160,7 @@
PlaySound(sndRopeRelease);
RopeDeleteMe(Gear, HHGear);
HHGear^.X:= tX;
- HHGear^.dX.isNegative:= hwRound(tX) > LongInt(leftX) + HHGear^.Radius * 2;
+ HHGear^.dX.isNegative:= hwRound(tX) > leftX + HHGear^.Radius * 2;
exit
end;
@@ -214,12 +214,12 @@
if ((Gear^.Message and gmDown) <> 0) and (Gear^.Elasticity < Gear^.Friction) then
if not ((TestCollisionXwithXYShift(HHGear, _2*hwSign(ropeDx), 0, hwSign(ropeDx), true) <> 0)
- or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, 1*hwSign(ropeDy), hwSign(ropeDy)) <> 0))) then
+ or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, hwSign(ropeDy), hwSign(ropeDy)) <> 0))) then
Gear^.Elasticity := Gear^.Elasticity + _1_2;
if ((Gear^.Message and gmUp) <> 0) and (Gear^.Elasticity > _30) then
if not ((TestCollisionXwithXYShift(HHGear, -_2*hwSign(ropeDx), 0, -hwSign(ropeDx), true) <> 0)
- or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, 1*-hwSign(ropeDy), -hwSign(ropeDy)) <> 0))) then
+ or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, -hwSign(ropeDy), -hwSign(ropeDy)) <> 0))) then
Gear^.Elasticity := Gear^.Elasticity - _1_2;
HHGear^.X := Gear^.X + mdX * Gear^.Elasticity;
@@ -227,8 +227,6 @@
HHGear^.dX := HHGear^.X - tx;
HHGear^.dY := HHGear^.Y - ty;
- ////
-
haveDivided := false;
// check whether rope needs dividing
@@ -419,9 +417,9 @@
if (Gear^.State and gstAttacked) = 0 then
begin
OnUsedAmmo(HHGear^.Hedgehog^);
- Gear^.State := Gear^.State or gstAttacked
+ Gear^.State := Gear^.State or gstAttacked;
+ ApplyAmmoChanges(HHGear^.Hedgehog^);
end;
- ApplyAmmoChanges(HHGear^.Hedgehog^)
end;
procedure doStepRopeAttach(Gear: PGear);
@@ -429,7 +427,6 @@
HHGear: PGear;
tx, ty, tt: hwFloat;
begin
-
Gear^.X := Gear^.X - Gear^.dX;
Gear^.Y := Gear^.Y - Gear^.dY;
Gear^.Elasticity := Gear^.Elasticity + _1;
@@ -451,7 +448,7 @@
HHGear^.State := HHGear^.State and (not (gstAttacking or gstHHJumping or gstHHHJump));
HHGear^.Message := HHGear^.Message and (not gmAttack);
DeleteGear(Gear);
- if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then
+ if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
HHGear^.Hedgehog^.CurAmmoType:= amRope;
isCursorVisible := false;
ApplyAmmoChanges(HHGear^.Hedgehog^);
@@ -466,6 +463,11 @@
Gear^.X := Gear^.X + HHGear^.dX;
Gear^.Y := Gear^.Y + HHGear^.dY;
+ // hedgehog can teleport up to 5 pixels upwards when sliding,
+ // so we have to give up the maintained rope length
+ // after doStepHedgehogMoving() call and recalculate
+ // it based on the gear and current hedgehog positions
+ Gear^.Elasticity:= int2hwFloat(hwRound(Distance(Gear^.X - HHGear^.X, Gear^.Y - HHGear^.Y) + _0_001));
tt := Gear^.Elasticity;
tx := _0;
@@ -478,6 +480,7 @@
Gear^.Y := Gear^.Y + ty;
Gear^.Elasticity := tt;
Gear^.doStep := @doStepRopeWork;
+
PlaySound(sndRopeAttach);
with HHGear^ do
begin
@@ -486,8 +489,6 @@
end;
RopeRemoveFromAmmo(Gear, HHGear);
-
- tt := _0;
exit
end;
tx := tx + Gear^.dX + Gear^.dX;
@@ -497,7 +498,7 @@
end;
if Gear^.Elasticity < _20 then Gear^.CollisionMask:= lfLandMask
- else Gear^.CollisionMask:= lfNotCurrentMask; //lfNotObjMask or lfNotHHObjMask;
+ else Gear^.CollisionMask:= lfNotCurHogCrate; //lfNotObjMask or lfNotHHObjMask;
CheckCollision(Gear);
if (Gear^.State and gstCollision) <> 0 then
@@ -529,7 +530,7 @@
Message := Message and (not gmAttack)
end;
DeleteGear(Gear);
- if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then
+ if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
HHGear^.Hedgehog^.CurAmmoType:= amRope;
isCursorVisible := false;
ApplyAmmoChanges(HHGear^.Hedgehog^);
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsHedgehog.pas
--- a/hedgewars/uGearsHedgehog.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsHedgehog.pas Wed Jul 31 23:14:27 2019 +0200
@@ -31,6 +31,10 @@
procedure AddPickup(HH: THedgehog; ammo: TAmmoType; cnt, X, Y: LongWord);
procedure CheckIce(Gear: PGear); inline;
procedure PlayTaunt(taunt: Longword);
+function HHGetTimer(Gear: PGear): LongWord;
+function HHGetTimerMsg(Gear: PGear): LongWord;
+function HHGetBounciness(Gear: PGear): LongWord;
+function HHGetBouncinessMsg(Gear: PGear): LongWord;
implementation
uses uConsts, uVariables, uFloat, uAmmos, uSound, uCaptions,
@@ -47,7 +51,7 @@
t:= 0;
while (TeamsArray[t] <> CurrentTeam) do inc(t);
- AddChatString(#2 + FormatA(trmsg[sidAutoSkip], CurrentTeam^.TeamName));
+ AddChatString(#2 + Format(shortstring(trmsg[sidAutoSkip]), CurrentTeam^.TeamName));
ParseCommand('/skip', true)
end;
@@ -143,14 +147,6 @@
HHGear^.Message:= HHGear^.Message and (not gmWeapon);
-// Special case: amNothing unselects weapon
-if weap = amNothing then
- begin
- HHGear^.Hedgehog^.CurAmmoType:= amNothing;
- ApplyAmmoChanges(HHGear^.Hedgehog^);
- exit
- end;
-
if Hedgehog^.Team^.Clan^.TurnNumber <= Ammoz[weap].SkipTurns then
exit; // weapon is not activated yet
@@ -182,31 +178,31 @@
Gear^.Message:= Gear^.Message and (not gmTimer);
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
with Gear^.Hedgehog^ do
+ if (((Gear^.State and gstAttacked) <> 0) and (GameFlags and gfInfAttack = 0))
+ or ((Gear^.State and gstHHDriven) = 0) then
+ exit;
if ((Gear^.Message and gmPrecise) <> 0) and ((CurWeapon^.Propz and ammoprop_SetBounce) <> 0) then
begin
color:= Gear^.Hedgehog^.Team^.Clan^.Color;
+
case Gear^.MsgParam of
1: begin
AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce1]), color, capgrpAmmostate);
- CurWeapon^.Bounciness:= 350;
end;
2: begin
AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce2]), color, capgrpAmmostate);
- CurWeapon^.Bounciness:= 700;
end;
3: begin
AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce3]), color, capgrpAmmostate);
- CurWeapon^.Bounciness:= 1000;
end;
4: begin
AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce4]), color, capgrpAmmostate);
- CurWeapon^.Bounciness:= 2000;
end;
5: begin
AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce5]), color, capgrpAmmostate);
- CurWeapon^.Bounciness:= 4000;
end
- end
+ end;
+ CurWeapon^.Bounciness:= bouncinessLevels[Gear^.MsgParam - 1];
end
else if (CurWeapon^.Propz and ammoprop_Timerable) <> 0 then
begin
@@ -216,6 +212,57 @@
end;
end;
+// Return timer (in ticks) of hogs current ammo or MSGPARAM_INVALID
+// if not timerable
+function HHGetTimer(Gear: PGear): LongWord;
+var CurWeapon: PAmmo;
+begin
+CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
+with Gear^.Hedgehog^ do
+ if ((CurWeapon^.Propz and ammoprop_Timerable) <> 0) then
+ HHGetTimer:= CurWeapon^.Timer
+ else
+ HHGetTimer:= MSGPARAM_INVALID;
+end;
+
+// Returns timer as a corresponding msgParam for /timer command
+function HHGetTimerMsg(Gear: PGear): LongWord;
+var timer: LongInt;
+begin
+timer:= HHGetTimer(Gear);
+if timer > -1 then
+ HHGetTimerMsg:= timer div 1000
+else
+ HHGetTimerMsg:= MSGPARAM_INVALID
+end;
+
+// Returns the selected bounciness value for the hog gear's current ammo
+// or MSGPARAM_INVALID if current ammo has no settable bounciness
+function HHGetBounciness(Gear: PGear): LongWord;
+var CurWeapon: PAmmo;
+begin
+CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
+with Gear^.Hedgehog^ do
+ if ((CurWeapon^.Propz and ammoprop_SetBounce) <> 0) then
+ HHGetBounciness:= CurWeapon^.Bounciness
+ else
+ HHGetBounciness:= MSGPARAM_INVALID
+end;
+
+// Returns bounciness as a corresponding msgParam for /timer command
+function HHGetBouncinessMsg(Gear: PGear): LongWord;
+var bounciness, i: LongInt;
+begin
+ bounciness:= HHGetBounciness(Gear);
+ if bounciness > -1 then
+ for i:=0 to High(bouncinessLevels) do
+ if bounciness = bouncinessLevels[i] then
+ begin
+ HHGetBouncinessMsg:= i+1;
+ exit
+ end;
+ HHGetBouncinessMsg:= MSGPARAM_INVALID
+end;
procedure Attack(Gear: PGear);
var xx, yy, newDx, newDy, lx, ly: hwFloat;
@@ -223,7 +270,7 @@
newGear: PGear;
CurWeapon: PAmmo;
usedAmmoType: TAmmoType;
- altUse: boolean;
+ altUse, faceLeft: boolean;
elastic: hwFloat;
begin
newGear:= nil;
@@ -338,14 +385,16 @@
newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtSniperRifleShot, 0, xx * _0_5, yy * _0_5, 0);
end;
amDynamite: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtDynamite, 0, SignAs(_0_03, dX), _0, 5000);
- amDuck: begin
- // Does duck spawn inside water?
+ amCreeper: begin
+ // TODO: Implement proper creeper spawning code. This is still the old rubber duck code.
+
+ // Does it spawn inside water?
if (LeftX > hwRound(Gear^.X) - Gear^.Karma) or (RightX < hwRound(Gear^.X) + Gear^.Karma) or (cWaterLine < hwRound(Gear^.Y) + Gear^.Karma) then
PlaySound(sndDroplet2)
else
- // Duck spawned in air, normal drop sound
- PlaySound(sndDuckDrop);
- newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtDuck, 0, SignAs(_0_03, dX), _0, 0);
+ // spawned in air, normal drop sound
+ PlaySound(sndCreeperDrop);
+ newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtCreeper, 0, SignAs(_0_03, dX), _0, 0);
if not ((not dX.isNegative) xor ((State and gstHHHJump) <> 0)) then
newGear^.Tag:= -1
else
@@ -365,7 +414,10 @@
PlaySound(sndBaseballBat) // TODO: Only play if something is hit?
end;
amParachute: begin
+ faceLeft:= IsHogFacingLeft(Gear);
newGear:= AddGear(hwRound(lx), hwRound(ly), gtParachute, 0, _0, _0, 0);
+ if faceLeft then
+ newGear^.Tag:= -1;
PlaySound(sndParachute)
end;
// we save CurWeapon^.Pos (in this case: cursor direction) by using it as (otherwise irrelevant) X value of the new gear.
@@ -408,7 +460,7 @@
cLowGravity := true
end;
amExtraDamage: begin
- PlaySound(sndHellishImpact4);
+ PlaySound(sndExtraDamage);
cDamageModifier:= _1_5
end;
amInvulnerable: begin
@@ -417,7 +469,10 @@
end;
amExtraTime: begin
PlaySound(sndExtraTime);
- TurnTimeLeft:= TurnTimeLeft + 30000
+ if TurnTimeLeft <= (cMaxTurnTime - ExtraTime) then
+ TurnTimeLeft:= TurnTimeLeft + ExtraTime
+ else
+ TurnTimeLeft:= cMaxTurnTime;
end;
amLaserSight: begin
PlaySound(sndLaserSight);
@@ -427,21 +482,13 @@
PlaySoundV(sndOw1, Team^.voicepack);
cVampiric:= true;
end;
- amPiano: begin
- // Tuck the hedgehog away until the piano attack is completed
- Unplaced:= true;
- X:= _0;
- Y:= _0;
- newGear:= AddGear(TargetPoint.X, -1024, gtPiano, 0, _0, _0, 0);
- PauseMusic
- end;
+ amPiano: newGear:= AddGear(TargetPoint.X, -1024, gtPiano, 0, _0, _0, 0);
amFlamethrower: newGear:= AddGear(hwRound(X), hwRound(Y), gtFlamethrower, 0, xx * _0_5, yy * _0_5, 0);
amLandGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtLandGun, 0, xx * _0_5, yy * _0_5, 0);
amResurrector: begin
newGear:= AddGear(hwRound(lx), hwRound(ly), gtResurrector, 0, _0, _0, 0);
newGear^.SoundChannel := LoopSound(sndResurrector);
end;
- //amStructure: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtStructure, gstWait, SignAs(_0_02, dX), _0, 3000);
amTardis: newGear:= AddGear(hwRound(X), hwRound(Y), gtTardis, 0, _0, _0, 0);
amIceGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtIceGun, 0, _0, _0, 0);
end;
@@ -476,13 +523,13 @@
amSeduction, amBallgun,
amJetpack, amBirdy,
amFlamethrower, amLandGun,
- amResurrector, //amStructure,
- amTardis, amPiano,
- amIceGun, amRubber,
- amMinigun: CurAmmoGear:= newGear;
+ amResurrector, amTardis,
+ amPiano, amIceGun,
+ amAirAttack, amNapalm,
+ amMineStrike, amDrillStrike,
+ amRubber, amMinigun: CurAmmoGear:= newGear;
end;
if CurAmmoType = amCake then FollowGear:= newGear;
- if CurAmmoType = amAirMine then newGear^.Hedgehog:= nil;
if ((CurAmmoType = amMine) or (CurAmmoType = amSMine) or (CurAmmoType = amAirMine)) and (GameFlags and gfInfAttack <> 0) then
newGear^.FlightTime:= GameTicks + min(TurnTimeLeft,1000)
@@ -493,7 +540,7 @@
newGear^.Target.X:= TargetPoint.X;
newGear^.Target.Y:= TargetPoint.Y
end;
- if (newGear <> nil) and (newGear^.CollisionMask and lfCurrentHog <> 0) then newGear^.CollisionMask:= newGear^.CollisionMask and (not lfCurrentHog);
+ if (newGear <> nil) and (newGear^.CollisionMask and lfCurHogCrate <> 0) then newGear^.CollisionMask:= newGear^.CollisionMask and (not lfCurHogCrate);
// Clear FollowGear if using on a rope/parachute/saucer etc so focus stays with the hog's movement
if altUse then
@@ -524,6 +571,7 @@
speech^.Text:= SpeechText;
speech^.Hedgehog:= Gear^.Hedgehog;
speech^.FrameTicks:= SpeechType;
+ AddChatString(#9+Format(shortstring(trmsg[sidChatHog]), Gear^.Hedgehog^.Name, SpeechText));
end;
SpeechText:= ''
end;
@@ -540,15 +588,14 @@
if (not CurrentTeam^.ExtDriven) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) <> 0) then
SendIPC(_S'a');
AfterAttack;
- end
+ end;
+ TargetPoint.X := NoPointX;
end
else
Message:= Message and (not gmAttack);
ScriptCall('onHogAttack', ord(usedAmmoType));
end; // of with Gear^, Gear^.Hedgehog^ do
-
- TargetPoint.X := NoPointX;
end;
procedure AfterAttack;
@@ -565,10 +612,10 @@
begin
Inc(MultiShootAttacks);
- if (Ammoz[a].Ammo.NumPerTurn >= MultiShootAttacks) then
+ if (Ammoz[a].Ammo.NumPerTurn > 0) and ((GameFlags and gfMultiWeapon) = 0) then
begin
s:= ansistring(inttostr(Ammoz[a].Ammo.NumPerTurn - MultiShootAttacks + 1));
- AddCaption(formatA(trmsg[sidRemaining], s), cWhiteColor, capgrpAmmostate);
+ AddCaption(formatA(trmsg[sidRemaining], s), capcolDefault, capgrpAmmostate);
end;
if (Ammoz[a].Ammo.NumPerTurn >= MultiShootAttacks)
@@ -583,10 +630,13 @@
begin
if TagTurnTimeLeft = 0 then
TagTurnTimeLeft:= TurnTimeLeft;
- if (CurAmmoGear <> nil) and (CurAmmoGear^.State and gstSubmersible <> 0) and CheckCoordInWater(hwRound(CurAmmoGear^.X), hwRound(CurAmmoGear^.Y)) then
- TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 25
- else TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 100;
- IsGetAwayTime := true;
+ if (HHGear <> nil) and ((HHGear^.State and gstHHDriven) <> 0) then
+ begin
+ if (CurAmmoGear <> nil) and (CurAmmoGear^.State and gstSubmersible <> 0) and CheckCoordInWater(hwRound(CurAmmoGear^.X), hwRound(CurAmmoGear^.Y)) then
+ TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 25
+ else TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 100;
+ IsGetAwayTime := true;
+ end;
end;
if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) and (HHGear <> nil) then
HHGear^.State:= HHGear^.State or gstAttacked;
@@ -637,7 +687,11 @@
Gear^.Z:= cCurrHHZ;
RemoveGearFromList(Gear);
InsertGearToList(Gear);
- PlaySoundV(sndByeBye, Gear^.Hedgehog^.Team^.voicepack);
+ case random(3) of
+ 0: PlaySoundV(sndByeBye, Gear^.Hedgehog^.Team^.voicepack);
+ 1: PlaySoundV(sndSoLong, Gear^.Hedgehog^.Team^.voicepack);
+ 2: PlaySoundV(sndOhDear, Gear^.Hedgehog^.Team^.voicepack);
+ end;
Gear^.Pos:= 0;
Gear^.Timer:= timertime
end
@@ -710,7 +764,8 @@
if (ammo = amNothing) or (cnt = 0) then
s:= trmsg[sidEmptyCrate]
else if cnt >= AMMO_INFINITE then
- s:= name + ansistring(' (+∞)')
+ // infinity symbol
+ s:= name + ansistring(' (+' + char($E2) + char($88) + char($9E) +')')
else
s:= name + ansistring(' (+' + IntToStr(cnt) + ')');
@@ -730,8 +785,10 @@
////////////////////////////////////////////////////////////////////////////////
procedure PickUp(HH, Gear: PGear);
var ag, gi: PGear;
+ healthBoost: LongInt;
begin
if Gear^.State and gstFrozen <> 0 then exit;
+if Gear^.Message and gmDestroy <> 0 then exit;
Gear^.Message:= gmDestroy;
if (Gear^.Pos and posCaseExplode) <> 0 then
@@ -776,11 +833,11 @@
end;
posCaseHealth: begin
PlaySound(sndShotgunReload);
- inc(HH^.Health, Gear^.Health);
+ healthBoost:= IncHogHealth(HH^.Hedgehog, Gear^.Health);
HH^.Hedgehog^.Effects[hePoisoned] := 0;
RenderHealth(HH^.Hedgehog^);
RecountTeamHealth(HH^.Hedgehog^.Team);
- HHHeal(HH^.Hedgehog, Gear^.Health, true);
+ HHHeal(HH^.Hedgehog, healthBoost, true);
end;
end
end;
@@ -788,7 +845,6 @@
procedure HedgehogStep(Gear: PGear);
var PrevdX: LongInt;
CurWeapon: PAmmo;
- portals: PGearArrayS;
begin
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
if ((Gear^.State and (gstAttacking or gstMoving)) = 0) then
@@ -797,16 +853,20 @@
with Gear^.Hedgehog^ do
with CurWeapon^ do
begin
- if (Gear^.Message and gmLeft ) <> 0 then
+ if Ammoz[AmmoType].PosCount < 2 then
+ exit
+ else if (Gear^.Message and gmLeft ) <> 0 then
Pos:= (Pos - 1 + Ammoz[AmmoType].PosCount) mod Ammoz[AmmoType].PosCount
+ else if (Gear^.Message and gmRight ) <> 0 then
+ Pos:= (Pos + 1) mod Ammoz[AmmoType].PosCount
else
- if (Gear^.Message and gmRight ) <> 0 then
- Pos:= (Pos + 1) mod Ammoz[AmmoType].PosCount
- else
+ exit;
+ GHStepTicks:= 200;
+ exit
+ end;
+
+ if (Gear^.Hedgehog^.Unplaced) then
exit;
- GHStepTicks:= 200;
- exit
- end;
if ((Gear^.Message and gmAnimate) <> 0) then
begin
@@ -851,12 +911,9 @@
exit
end;
- if (Gear^.Message and (gmLeft or gmRight) <> 0) and (Gear^.State and gstMoving = 0) then
- begin
- // slightly inefficient since it doesn't halt after one portal, maybe could add a param to GearsNear for number desired.
- portals:= GearsNear(Gear^.X, Gear^.Y, gtPortal, 26);
- if portals.size = 0 then Gear^.PortalCounter:= 0
- end;
+ if (Gear^.Message and (gmLeft or gmRight) <> 0) and (Gear^.State and gstMoving = 0) and
+ (CheckGearNear(Gear, gtPortal, 26, 26) = nil) then
+ Gear^.PortalCounter:= 0;
PrevdX:= hwSign(Gear^.dX);
if (Gear^.Message and gmLeft )<>0 then
Gear^.dX:= -cLittle else
@@ -1159,7 +1216,8 @@
if (not isFalling)
and (hwAbs(Gear^.dX) + hwAbs(Gear^.dY) < _0_03) then
begin
- Gear^.State:= Gear^.State and (not gstWinner);
+ if (not GameOver) then
+ Gear^.State:= Gear^.State and (not gstWinner);
Gear^.State:= Gear^.State and (not gstMoving);
cnt:= 0;
while (cnt < 6) and (not CheckGearDrowning(Gear)) and (Gear <> nil) and (TestCollisionYWithGear(Gear,1) = 0) do
@@ -1208,11 +1266,11 @@
if (not isZero(Gear^.dY)) and (Gear^.FlightTime > 0) and ((GameFlags and gfLowGravity) = 0) then
begin
inc(Gear^.FlightTime);
- if (Gear^.FlightTime > 1500) and ((hwRound(Gear^.X) < LongInt(leftX)-250) or (hwRound(Gear^.X) > LongInt(rightX)+250)) then
+ if (Gear^.FlightTime > 1500) and ((hwRound(Gear^.X) < leftX-250) or (hwRound(Gear^.X) > rightX+250)) then
begin
Gear^.FlightTime:= 0;
s:= ansistring(CurrentHedgehog^.Name);
- AddCaption(FormatA(GetEventString(eidHomerun), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidHomerun), s), capcolDefault, capgrpMessage2);
PlaySound(sndHomerun)
end;
end
@@ -1221,6 +1279,11 @@
uStats.hedgehogFlight(Gear, Gear^.FlightTime);
Gear^.FlightTime:= 0;
end;
+if (WorldEdge = weNone) and (not Gear^.Hedgehog^.FlownOffMap) and (not isZero(Gear^.dX)) and (not isUnderwater) and ((Gear^.State and gstHHDriven) = 0) and (hwRound(Gear^.Y) < cWaterLine-300) and ((hwRound(Gear^.X) < leftX-2048) or (hwRound(Gear^.X) > rightX+2048)) then
+ begin
+ PlaySoundV(sndFlyAway, Gear^.Hedgehog^.Team^.voicepack);
+ Gear^.Hedgehog^.FlownOffMap:= true;
+ end;
end;
@@ -1241,20 +1304,20 @@
else if not isInMultiShoot then
AllInactive:= false;
-if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (LuaEndTurnRequested = true) then
+if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (HHGear^.Health = 0) or (((GameFlags and gfKing) <> 0) and (not Hedgehog^.Team^.hasKing)) or (LuaEndTurnRequested = true) then
begin
if (Hedgehog^.CurAmmoType = amKnife) then
LoadHedgehogHat(Hedgehog^, Hedgehog^.Hat);
if TagTurnTimeLeft = 0 then
TagTurnTimeLeft:= TurnTimeLeft;
TurnTimeLeft:= 0;
- if (GameOver = false) and ((GameFlags and gfInfAttack) = 0) and ((HHGear^.State and gstAttacked) = 0) and (HHGear^.Damage = 0) and (LuaNoEndTurnTaunts = false) then
+ if (GameOver = false) and ((GameFlags and gfInfAttack) = 0) and ((HHGear^.State and gstAttacked) = 0) and (HHGear^.Damage = 0) and (HHGear^.Health > 0) and (LuaNoEndTurnTaunts = false) and (uStats.getIsTurnSkipped() = false) then
begin
AddVoice(sndBoring, Hedgehog^.Team^.voicepack);
if (GameFlags and gfInfAttack = 0) then
begin
s:= Hedgehog^.Name;
- AddCaption(FormatA(GetEventString(eidTimeout), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidTimeout), s), capcolDefault, capgrpMessage);
end;
end;
isCursorVisible:= false;
@@ -1263,7 +1326,7 @@
StopSound(sndThrowPowerUp);
LuaEndTurnRequested:= false;
LuaNoEndTurnTaunts:= false;
- if HHGear^.Damage > 0 then
+ if (HHGear^.Damage > 0) or (HHGear^.Health = 0) then
HHGear^.State:= HHGear^.State and (not (gstHHJumping or gstHHHJump));
exit
end;
@@ -1423,9 +1486,9 @@
// Death message
s:= ansistring(Gear^.Hedgehog^.Name);
if Gear^.Hedgehog^.King then
- AddCaption(FormatA(GetEventString(eidKingDied), s), cWhiteColor, capgrpMessage)
+ AddCaption(FormatA(GetEventString(eidKingDied), s), capcolDefault, capgrpMessage)
else
- AddCaption(FormatA(GetEventString(eidDied), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidDied), s), capcolDefault, capgrpMessage);
end;
end
else
@@ -1435,7 +1498,7 @@
// Gone message
s:= ansistring(Gear^.Hedgehog^.Name);
- AddCaption(FormatA(GetEventString(eidGone), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidGone), s), capcolDefault, capgrpMessage);
end
end;
exit
@@ -1451,7 +1514,9 @@
begin
if Gear^.Timer = 0 then
begin
- Gear^.State:= Gear^.State and (not (gstWait or gstLoser or gstWinner or gstAttacked or gstNotKickable or gstChooseTarget));
+ Gear^.State:= Gear^.State and (not (gstWait or gstLoser or gstAttacked or gstNotKickable or gstChooseTarget));
+ if (not GameOver) then
+ Gear^.State:= Gear^.State and (not gstWinner);
if Gear^.Hedgehog^.Effects[heFrozen] = 0 then Gear^.Active:= false;
AddCI(Gear);
exit
@@ -1512,10 +1577,16 @@
(CurAmmoGear <> nil) and (CurAmmoGear^.Kind =gtRope) and (CurAmmoGear^.Elasticity <> _0) then
CurAmmoGear^.PortalCounter:= 1;
if (WorldEdge = weWrap) and ((TestCollisionXwithGear(Gear, 1) <> 0) or (TestCollisionXwithGear(Gear, -1) <> 0)) then
- begin
- Gear^.X:= tX;
- Gear^.dX.isNegative:= (hwRound(tX) > LongInt(leftX) + Gear^.Radius * 2)
- end
+ // Stop hedgehog if it collides with land *just* behind other side of world wrap edge
+ if ((Gear^.State and gstNotKickable) = 0) then
+ begin
+ if (hwRound(tX) > leftX + Gear^.Radius * 2) then
+ Gear^.X:= int2HwFloat(RightX)
+ else
+ Gear^.X:= int2HwFloat(LeftX);
+ Gear^.dX.QWordValue:= 0;
+ Gear^.State := Gear^.State or gstCollision;
+ end;
end;
CheckSum:= CheckSum xor Gear^.Hedgehog^.BotLevel;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsList.pas
--- a/hedgewars/uGearsList.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsList.pas Wed Jul 31 23:14:27 2019 +0200
@@ -98,14 +98,13 @@
(* gtPoisonCloud *) , amNothing
(* gtSnowball *) , amSnowball
(* gtFlake *) , amNothing
-//(* gtStructure *) , amStructure // TODO - This will undoubtedly change once there is more than one structure
(* gtLandGun *) , amLandGun
(* gtTardis *) , amTardis
(* gtIceGun *) , amIceGun
(* gtAddAmmo *) , amNothing
(* gtGenericFaller *) , amNothing
(* gtKnife *) , amKnife
-(* gtDuck *) , amDuck
+(* gtCreeper *) , amCreeper
(* gtMinigun *) , amMinigun
(* gtMinigunBullet *) , amMinigun
);
@@ -204,27 +203,76 @@
gear^.Density:= _1;
// Define ammo association, if any.
gear^.AmmoType:= GearKindAmmoTypeMap[Kind];
-gear^.CollisionMask:= $FFFF;
+gear^.CollisionMask:= lfAll;
gear^.Tint:= $FFFFFFFF;
gear^.Data:= nil;
+gear^.Sticky:= false;
if CurrentHedgehog <> nil then
begin
gear^.Hedgehog:= CurrentHedgehog;
if (CurrentHedgehog^.Gear <> nil) and (hwRound(CurrentHedgehog^.Gear^.X) = X) and (hwRound(CurrentHedgehog^.Gear^.Y) = Y) then
- gear^.CollisionMask:= lfNotCurrentMask
+ gear^.CollisionMask:= lfNotCurHogCrate
end;
if (Ammoz[Gear^.AmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0) then
gear^.Z:= cHHZ+1
else gear^.Z:= cUsualZ;
+// set gstInBounceEdge if gear spawned inside the bounce world edge
+if WorldEdge = weBounce then
+ if (hwRound(gear^.X) - Gear^.Radius < leftX) or (hwRound(gear^.X) + Gear^.Radius > rightX) then
+ case gear^.Kind of
+ // list all gears here that could collide with the bounce world edge
+ gtHedgehog,
+ gtFlame,
+ gtMine,
+ gtAirBomb,
+ gtDrill,
+ gtNapalmBomb,
+ gtCase,
+ gtAirMine,
+ gtExplosives,
+ gtGrenade,
+ gtShell,
+ gtBee,
+ gtDynamite,
+ gtClusterBomb,
+ gtMelonPiece,
+ gtCluster,
+ gtMortar,
+ gtKamikaze,
+ gtCake,
+ gtWatermelon,
+ gtGasBomb,
+ gtHellishBomb,
+ gtBall,
+ gtRCPlane,
+ gtSniperRifleShot,
+ gtShotgunShot,
+ gtDEagleShot,
+ gtSineGunShot,
+ gtMinigunBullet,
+ gtEgg,
+ gtPiano,
+ gtSMine,
+ gtSnowball,
+ gtKnife,
+ gtCreeper,
+ gtMolotov,
+ gtFlake,
+ gtGrave,
+ gtPortal,
+ gtTarget:
+ gear^.State := gear^.State or gstInBounceEdge;
+ end;
+
case Kind of
gtFlame: Gear^.Boom := 2; // some additional expl in there are x3, x4 this
gtHedgehog: Gear^.Boom := 30;
gtMine: Gear^.Boom := 50;
gtCase: Gear^.Boom := 25;
- gtAirMine: Gear^.Boom := 25;
+ gtAirMine: Gear^.Boom := 30;
gtExplosives: Gear^.Boom := 75;
gtGrenade: Gear^.Boom := 50;
gtShell: Gear^.Boom := 50;
@@ -266,7 +314,7 @@
else Gear^.Boom := 3;
gtPoisonCloud: Gear^.Boom := 20;
gtKnife: Gear^.Boom := 40000; // arbitrary scaling factor since impact-based
- gtDuck: Gear^.Boom := 40;
+ gtCreeper: Gear^.Boom := 100;
gtMinigunBullet: Gear^.Boom := 2;
end;
@@ -330,6 +378,10 @@
((GetRandom(90)+128) shl 16) or
(($d5+c) shl 8) or $ff}
end;
+ gtParachute: begin
+ gear^.Tag:= 1; // hog face dir. 1 = right, -1 = left
+ gear^.Z:= cCurrHHZ;
+ end;
gtShell: begin
gear^.Elasticity:= _0_8;
gear^.Friction:= _0_8;
@@ -353,6 +405,7 @@
Pos:= 0;
Radius:= 1;
DirAngle:= random(360);
+ Sticky:= true;
if State and gstTmpFlag = 0 then
begin
dx.isNegative:= GetRandom(2) = 0;
@@ -394,7 +447,7 @@
gear^.State:= Gear^.State or gstSubmersible
end;
gtSeduction: begin
- gear^.Radius:= 250;
+ gear^.Radius:= cSeductionDist;
end;
gtShotgunShot: begin
if gear^.Timer = 0 then gear^.Timer:= 900;
@@ -414,7 +467,7 @@
RopePoints.Count:= 0;
gear^.Tint:= $D8D8D8FF;
gear^.Tag:= 0; // normal rope render
- gear^.CollisionMask:= lfNotCurrentMask //lfNotObjMask or lfNotHHObjMask;
+ gear^.CollisionMask:= lfNotCurHogCrate //lfNotObjMask or lfNotHHObjMask;
end;
gtMine: begin
gear^.ImpactSound:= sndMineImpact;
@@ -428,12 +481,17 @@
if gear^.Timer = 0 then
begin
if cMinesTime < 0 then
- gear^.Timer:= getrandom(51)*100
+ begin
+ gear^.Timer:= getrandom(51)*100;
+ gear^.Karma:= 1;
+ end
else
- gear^.Timer:= cMinesTime
- end
+ gear^.Timer:= cMinesTime;
+ end;
+ gear^.RenderTimer:= true;
end;
gtAirMine: begin
+ gear^.AdvBounce:= 1;
gear^.ImpactSound:= sndAirMineImpact;
gear^.nImpactSounds:= 1;
gear^.Health:= 30;
@@ -445,14 +503,18 @@
gear^.Angle:= 175; // Radius at which air bombs will start "seeking". $FFFFFFFF = unlimited. check is skipped.
gear^.Power:= cMaxWindSpeed.QWordValue div 2; // hwFloat converted. 1/2 g default. defines the "seek" speed when a gear is in range.
gear^.Pos:= cMaxWindSpeed.QWordValue * 3 div 2; // air friction. slows it down when not hitting stuff
- gear^.Karma:= 30; // damage
+ gear^.Tag:= 0;
if gear^.Timer = 0 then
begin
if cMinesTime < 0 then
- gear^.Timer:= getrandom(13)*100
+ begin
+ gear^.Timer:= getrandom(13)*100;
+ gear^.Karma:= 1;
+ end
else
- gear^.Timer:= cMinesTime div 4
+ gear^.Timer:= cMinesTime div 4;
end;
+ gear^.RenderTimer:= true;
gear^.WDTimer:= gear^.Timer
end;
gtSMine: begin
@@ -463,7 +525,9 @@
gear^.Friction:= _0_995;
gear^.Density:= _1_6;
gear^.AdvBounce:= 1;
+ gear^.Sticky:= true;
if gear^.Timer = 0 then gear^.Timer:= 500;
+ gear^.RenderTimer:= true;
end;
gtKnife: begin
gear^.ImpactSound:= sndKnifeImpact;
@@ -471,10 +535,11 @@
gear^.Elasticity:= _0_8;
gear^.Friction:= _0_8;
gear^.Density:= _4;
- gear^.Radius:= 7
+ gear^.Radius:= 7;
+ gear^.Sticky:= true;
end;
gtCase: begin
- gear^.ImpactSound:= sndGraveImpact;
+ gear^.ImpactSound:= sndCaseImpact;
gear^.nImpactSounds:= 1;
gear^.Radius:= 16;
gear^.Elasticity:= _0_3;
@@ -482,6 +547,8 @@
end;
gtExplosives: begin
gear^.AdvBounce:= 1;
+ if GameType in [gmtDemo, gmtRecord] then
+ gear^.RenderHealth:= true;
gear^.ImpactSound:= sndGrenadeImpact;
gear^.nImpactSounds:= 1;
gear^.Radius:= 16;
@@ -540,6 +607,12 @@
gear^.Health:= 6;
gear^.Damage:= 30;
gear^.Z:= cHHZ+2;
+ gear^.Karma:= 0; // for sound effect: 0 = normal, 1 = underwater
+ gear^.Radius:= 150;
+ gear^.FlightTime:= 0; // for timeout in weWrap
+ gear^.Power:= 0; // count number of wraps in weWrap
+ gear^.WDTimer:= 0; // number of required wraps
+ gear^.Density:= _19;
gear^.Tint:= gear^.Hedgehog^.Team^.Clan^.Color shl 8 or $FF
end;
gtAirBomb: begin
@@ -563,7 +636,8 @@
gear^.Elasticity:= _0_3;
end;
gtTardis: begin
- gear^.Pos:= 1;
+ gear^.Pos:= 1; // tardis phase
+ gear^.Tag:= 0; // 1 = hedgehog died, disappeared, took damage or moved
gear^.Z:= cCurrHHZ+1;
end;
gtMortar: begin
@@ -613,6 +687,8 @@
gear^.Timer:= 5000;
// Tag for drill strike. if 1 then first impact occured already
gear^.Tag := 0;
+ // Pos for state. If 1, drill is drilling
+ gear^.Pos := 0;
gear^.Radius:= 4;
gear^.Density:= _1;
end;
@@ -651,7 +727,8 @@
gtBirdy: begin
gear^.Radius:= 16; // todo: check
gear^.Health := 2000;
- gear^.FlightTime := 2
+ gear^.FlightTime := 2;
+ gear^.Z:= cCurrHHZ;
end;
gtEgg: begin
gear^.AdvBounce:= 1;
@@ -671,6 +748,7 @@
gear^.Timer:= 15000;
gear^.RenderTimer:= false;
gear^.Health:= 100;
+ gear^.Sticky:= true;
end;
gtPiano: begin
gear^.Radius:= 32;
@@ -698,7 +776,7 @@
gear^.Tint:= $C0C000C0
end;
gtResurrector: begin
- gear^.Radius := 100;
+ gear^.Radius := cResurrectorDist;
gear^.Tag := 0;
gear^.Tint:= $F5DB35FF
end;
@@ -712,39 +790,30 @@
gear^.Radius:= 5;
gear^.Density:= _1_5;
end;
-{
- gtStructure: begin
- gear^.Elasticity:= _0_55;
- gear^.Friction:= _0_995;
- gear^.Density:= _0_9;
- gear^.Radius:= 13;
- gear^.Health:= 200;
- gear^.Timer:= 0;
- gear^.Tag:= TotalRounds + 3;
- gear^.Pos:= 1;
- end;
-}
gtIceGun: begin
gear^.Health:= 1000;
gear^.Radius:= 8;
+ gear^.Density:= _0;
+ gear^.Tag:= 0; // sound state: 0 = no sound, 1 = ice beam sound, 2 = idle sound
end;
- gtDuck: begin
- gear^.Pos:= 0; // 0: in air, 1-4: on water, 5-8: underwater
- // 1: bottom, 2: bottom (mirrored),
- // 3: left Sea edge, 4: right Sea edge
- // 6: bottom, 7: bottom (mirrored)
- // 7: left Sea edge, 8: right Sea edge
- gear^.Tag:= 1; // 1: facing right, -1: facing left
- if gear^.Timer = 0 then
- gear^.Timer:= 15000; // Explosion timer to avoid duck existing forever
- gear^.Radius:= 9; // Collision radius (with landscape)
- gear^.Karma:= 24; // Distance from water when swimming
- gear^.Damage:= 500; // Speed factor when swimming on water (multiplied with wind speed)
- gear^.State:= gear^.State or gstSubmersible;
- gear^.Elasticity:= _0_6;
- gear^.Friction:= _0_8;
- gear^.Density:= _0_5;
+ gtCreeper: begin
+ // TODO: Finish creeper initialization implementation
+ gear^.Radius:= cHHRadius;
+ gear^.Elasticity:= _0_35;
+ gear^.Friction:= _0_93;
+ gear^.Density:= _5;
+
gear^.AdvBounce:= 1;
+ gear^.ImpactSound:= sndAirMineImpact;
+ gear^.nImpactSounds:= 1;
+ gear^.Health:= 30;
+ gear^.Radius:= 8;
+ gear^.Angle:= 175; // Radius at which it will start "seeking". $FFFFFFFF = unlimited. check is skipped.
+ gear^.Power:= cMaxWindSpeed.QWordValue div 2; // hwFloat converted. 1/2 g default. defines the "seek" speed when a gear is in range.
+ gear^.Pos:= cMaxWindSpeed.QWordValue * 3 div 2; // air friction. slows it down when not hitting stuff
+ if gear^.Timer = 0 then
+ gear^.Timer:= 5000;
+ gear^.WDTimer:= gear^.Timer
end;
gtMinigun: begin
// Timer. First, it's the timer before shooting. Then it will become the shooting timer and is set to Karma
@@ -775,7 +844,6 @@
procedure DeleteGear(Gear: PGear);
var team: PTeam;
t,i: Longword;
- k: boolean;
cakeData: PCakeData;
iterator: PGear;
begin
@@ -783,6 +851,7 @@
ScriptCall('onGearDelete', gear^.uid);
DeleteCI(Gear);
+RemoveFromProximityCache(Gear);
FreeAndNilTexture(Gear^.Tex);
@@ -842,8 +911,8 @@
begin
t:= max(Gear^.Damage, Gear^.Health);
Gear^.Damage:= t;
- if (((not SuddenDeathDmg) and (WaterOpacity < $FF)) or (SuddenDeathDmg and (SDWaterOpacity < $FF))) then
- spawnHealthTagForHH(Gear, t);
+ // Display hedgehog damage in water
+ spawnHealthTagForHH(Gear, t);
end;
team:= Gear^.Hedgehog^.Team;
@@ -859,20 +928,36 @@
Gear^.Hedgehog^.Gear:= nil;
if Gear^.Hedgehog^.King then
+ // If king died, kill the rest of the team
begin
- // are there any other kings left? Just doing nil check. Presumably a mortally wounded king will get reaped soon enough
- k:= false;
+ with Gear^.Hedgehog^.Team^ do
+ begin
+ Gear^.Hedgehog^.Team^.hasKing:= false;
+ for t:= 0 to cMaxHHIndex do
+ if Hedgehogs[t].Gear <> nil then
+ Hedgehogs[t].Gear^.Health:= 0
+ else if (Hedgehogs[t].GearHidden <> nil) then
+ Hedgehogs[t].GearHidden^.Health:= 0 // Hog is still hidden. If tardis should return though, Lua, eh ...
+ end;
+ end;
+
+ // Update passive status of clan
+ if (not Gear^.Hedgehog^.Team^.Clan^.Passive) then
+ begin
+ Gear^.Hedgehog^.Team^.Clan^.Passive:= true;
for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
- if (team^.Clan^.Teams[i]^.Hedgehogs[0].Gear <> nil) then
- k:= true;
- if not k then
- for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
- with team^.Clan^.Teams[i]^ do
+ begin
+ with team^.Clan^.Teams[i]^ do
+ if (not Passive) then
for t:= 0 to cMaxHHIndex do
- if Hedgehogs[t].Gear <> nil then
- Hedgehogs[t].Gear^.Health:= 0
- else if (Hedgehogs[t].GearHidden <> nil) then
- Hedgehogs[t].GearHidden^.Health:= 0 // hog is still hidden. if tardis should return though, lua, eh...
+ if (Hedgehogs[t].Gear <> nil) or (Hedgehogs[t].GearHidden <> nil) then
+ begin
+ Gear^.Hedgehog^.Team^.Clan^.Passive:= false;
+ break;
+ end;
+ if (not Gear^.Hedgehog^.Team^.Clan^.Passive) then
+ break;
+ end;
end;
// should be not CurrentHedgehog, but hedgehog of the last gear which caused damage to this hog
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsRender.pas
--- a/hedgewars/uGearsRender.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsRender.pas Wed Jul 31 23:14:27 2019 +0200
@@ -6,7 +6,7 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
- * This program is distributed in the hope that it will be useful,
+ * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
@@ -37,6 +37,9 @@
end;
procedure RenderGear(Gear: PGear; x, y: LongInt);
procedure RenderGearTimer(Gear: PGear; x, y: LongInt);
+procedure RenderGearHealth(Gear: PGear; x, y: LongInt);
+procedure RenderHHGuiExtras(Gear: PGear; ox, oy: LongInt);
+procedure RenderAirMineGuiExtras(Gear: PGear; ox, oy: LongInt);
procedure DrawHHOrder();
var RopePoints: record
@@ -52,7 +55,7 @@
end;
implementation
-uses uRender, uUtils, uVariables, uAmmos, Math, uVisualGearsList;
+uses uRender, uRenderUtils, uGearsUtils, uUtils, uVariables, uAmmos, Math, uVisualGearsList;
procedure DrawRopeLinesRQ(Gear: PGear);
var n: LongInt;
@@ -68,8 +71,6 @@
if (RopePoints.Count > 0) or (Gear^.Elasticity.QWordValue > 0) then
begin
EnableTexture(false);
- //glEnable(GL_LINE_SMOOTH);
-
Tint(Gear^.Tint shr 24 div 3, Gear^.Tint shr 16 and $FF div 3, Gear^.Tint shr 8 and $FF div 3, Gear^.Tint and $FF);
@@ -91,112 +92,79 @@
openglPopMatrix();
EnableTexture(true);
- //glDisable(GL_LINE_SMOOTH)
end
end;
-function DrawRopeLine(X1, Y1, X2, Y2, roplen: LongInt): LongInt;
-var eX, eY, dX, dY: LongInt;
- i, sX, sY, x, y, d: LongInt;
- b: boolean;
+procedure DrawRopeLine(X1, Y1, X2, Y2: Real; LayerIndex: Longword; var linesLength, ropeLength: Real);
+var dX, dY, angle, lineLength: Real;
+ FrameIndex: LongWord;
begin
if (X1 = X2) and (Y1 = Y2) then
- begin
- //OutError('WARNING: zero length rope line!', false);
- DrawRopeLine:= 0;
- exit
- end;
- eX:= 0;
- eY:= 0;
+ exit;
+
dX:= X2 - X1;
dY:= Y2 - Y1;
+ lineLength:= sqrt(sqr(dX) + sqr(dY));
+ angle:= arctan2(dY, dX) * 180 / PI - 90;
- if (dX > 0) then
- sX:= 1
- else
- if (dX < 0) then
- begin
- sX:= -1;
- dX:= -dX
- end
- else sX:= dX;
+ dX:= dX / lineLength;
+ dY:= dY / lineLength;
- if (dY > 0) then
- sY:= 1
- else
- if (dY < 0) then
- begin
- sY:= -1;
- dY:= -dY
- end
- else
- sY:= dY;
-
- if (dX > dY) then
- d:= dX
- else
- d:= dY;
+ while (ropeLength - linesLength) <= lineLength do
+ begin
+ FrameIndex:= round(ropeLength / cRopeNodeStep);
+ if (FrameIndex mod cRopeLayers) = LayerIndex then
+ DrawSpriteRotatedFReal(sprRopeNode,
+ X1 + (ropeLength - linesLength) * dX,
+ Y1 + (ropeLength - linesLength) * dY,
+ FrameIndex, 1, angle);
+ ropeLength:= ropeLength + cRopeNodeStep;
+ end;
+ linesLength:= linesLength + lineLength
+end;
- x:= X1;
- y:= Y1;
-
- for i:= 0 to d do
+procedure DrawRopeLayer(Gear: PGear; LayerIndex: LongWord);
+var i: LongInt;
+ linesLength, ropeLength: Real;
+begin
+ linesLength:= 0;
+ ropeLength:= cRopeNodeStep;
+ if RopePoints.Count > 0 then
+ begin
+ i:= 0;
+ while i < Pred(RopePoints.Count) do
begin
- inc(eX, dX);
- inc(eY, dY);
- b:= false;
- if (eX > d) then
- begin
- dec(eX, d);
- inc(x, sX);
- b:= true
- end;
- if (eY > d) then
- begin
- dec(eY, d);
- inc(y, sY);
- b:= true
- end;
- if b then
- begin
- inc(roplen);
- if (roplen mod 4) = 0 then
- DrawSprite(sprRopeNode, x - 2, y - 2, 0)
- end
- end;
- DrawRopeLine:= roplen;
+ DrawRopeLine(hwFloat2Float(RopePoints.ar[i].X) + WorldDx, hwFloat2Float(RopePoints.ar[i].Y) + WorldDy,
+ hwFloat2Float(RopePoints.ar[Succ(i)].X) + WorldDx, hwFloat2Float(RopePoints.ar[Succ(i)].Y) + WorldDy,
+ LayerIndex, linesLength, ropeLength);
+ inc(i)
+ end;
+
+ DrawRopeLine(hwFloat2Float(RopePoints.ar[i].X) + WorldDx, hwFloat2Float(RopePoints.ar[i].Y) + WorldDy,
+ hwFloat2Float(Gear^.X) + WorldDx, hwFloat2Float(Gear^.Y) + WorldDy,
+ LayerIndex, linesLength, ropeLength);
+
+ DrawRopeLine(hwFloat2Float(Gear^.X) + WorldDx, hwFloat2Float(Gear^.Y) + WorldDy,
+ hwFloat2Float(Gear^.Hedgehog^.Gear^.X) + WorldDx, hwFloat2Float(Gear^.Hedgehog^.Gear^.Y) + WorldDy,
+ LayerIndex, linesLength, ropeLength);
+ end
+ else
+ if Gear^.Elasticity.QWordValue > 0 then
+ DrawRopeLine(hwFloat2Float(Gear^.X) + WorldDx, hwFloat2Float(Gear^.Y) + WorldDy,
+ hwFloat2Float(Gear^.Hedgehog^.Gear^.X) + WorldDx, hwFloat2Float(Gear^.Hedgehog^.Gear^.Y) + WorldDy,
+ LayerIndex, linesLength, ropeLength);
end;
procedure DrawRope(Gear: PGear);
-var roplen, i: LongInt;
+var i: LongInt;
begin
if Gear^.Hedgehog^.Gear = nil then exit;
if (Gear^.Tag = 1) or ((cReducedQuality and rqSimpleRope) <> 0) then
DrawRopeLinesRQ(Gear)
else
- begin
- roplen:= 0;
- if RopePoints.Count > 0 then
- begin
- i:= 0;
- while i < Pred(RopePoints.Count) do
- begin
- roplen:= DrawRopeLine(hwRound(RopePoints.ar[i].X) + WorldDx, hwRound(RopePoints.ar[i].Y) + WorldDy,
- hwRound(RopePoints.ar[Succ(i)].X) + WorldDx, hwRound(RopePoints.ar[Succ(i)].Y) + WorldDy, roplen);
- inc(i)
- end;
- roplen:= DrawRopeLine(hwRound(RopePoints.ar[i].X) + WorldDx, hwRound(RopePoints.ar[i].Y) + WorldDy,
- hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, roplen);
- roplen:= DrawRopeLine(hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy,
- hwRound(Gear^.Hedgehog^.Gear^.X) + WorldDx, hwRound(Gear^.Hedgehog^.Gear^.Y) + WorldDy, roplen);
- end
- else
- if Gear^.Elasticity.QWordValue > 0 then
- roplen:= DrawRopeLine(hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy,
- hwRound(Gear^.Hedgehog^.Gear^.X) + WorldDx, hwRound(Gear^.Hedgehog^.Gear^.Y) + WorldDy, roplen);
- end;
-
+ for i := 0 to cRopeLayers - 1 do
+ DrawRopeLayer(Gear, i);
if RopePoints.Count > 0 then
DrawSpriteRotated(sprRopeHook, hwRound(RopePoints.ar[0].X) + WorldDx, hwRound(RopePoints.ar[0].Y) + WorldDy, 1, RopePoints.HookAngle)
@@ -206,12 +174,23 @@
end;
-procedure DrawAltWeapon(Gear: PGear; sx, sy: LongInt);
+procedure DrawSelectedWeapon(Gear: PGear; sx, sy: LongInt; isAltWeapon: boolean);
begin
with Gear^.Hedgehog^ do
begin
- if not (((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and ((Gear^.State and gstAttacked) = 0)) then
+ if ((Gear^.State and gstAttacked) <> 0) then
+ exit;
+ if (isAltWeapon and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) = 0)) then
+ exit;
+ if (not isAltWeapon) and (((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_ShowSelIcon) = 0) or (
+ (((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackInMove) = 0) and ((Gear^.State and gstMoving) <> 0)))) then
exit;
+ if (not isAltWeapon) then
+ begin
+ sy:= sy - 64;
+ if (IsHogFacingLeft(Gear)) then
+ sx:= sx - 61;
+ end;
DrawTexture(sx + 16, sy + 16, ropeIconTex);
DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.75, sx + 30, sy + 30, ord(CurAmmoType) - 1, 1, 32, 32);
end;
@@ -242,7 +221,7 @@
repeat
hh:= @TeamsArray[t]^.Hedgehogs[i];
inc(i);
- if (hh <> nil) and (hh^.Gear <> nil) then
+ if (hh <> nil) and (hh^.Gear <> nil) and (not hh^.Unplaced) then
begin
inc(c);
HHGear:= hh^.Gear;
@@ -259,13 +238,145 @@
end;
+// Render some informational GUI next to hedgehog, like fuel and alternate weapon
+procedure RenderHHGuiExtras(Gear: PGear; ox, oy: LongInt);
+var HH: PHedgehog;
+ sx, sy, tx, ty, t, hogLR: LongInt;
+ dAngle: real;
+begin
+ HH:= Gear^.Hedgehog;
+ sx:= ox + 1; // this offset is very common
+ sy:= oy - 3;
+ if HH^.Unplaced then
+ exit;
+ if (Gear^.State and gstHHDeath) <> 0 then
+ exit;
+ if (Gear^.State and gstHHGone) <> 0 then
+ exit;
+ if (CinematicScript) then
+ exit;
+
+ // render finger (pointing arrow)
+ if bShowFinger and ((Gear^.State and gstHHDriven) <> 0) then
+ begin
+ ty := oy - 32;
+ // move finger higher up if tags are above hog
+ if (cTagsMask and htTeamName) <> 0 then
+ ty := ty - HH^.Team^.NameTagTex^.h - 2;
+ if (cTagsMask and htName) <> 0 then
+ ty := ty - HH^.NameTagTex^.h - 2;
+ if (cTagsMask and htHealth) <> 0 then
+ ty := ty - HH^.HealthTagTex^.h - 2;
+ tx := ox;
+
+ // don't go offscreen
+ t:= 32;
+ tx := min(max(tx, ViewLeftX + t), ViewRightX - t);
+ ty := min(ty, ViewBottomY - 96);
+ // don't overlap with HH or HH tags
+ if ty < ViewTopY + t then
+ begin
+ if abs(tx - ox) < abs(ty - oy) then
+ ty:= max(ViewTopY + t, oy + t)
+ else
+ ty:= max(ViewTopY + t, ty);
+ end;
+
+ dAngle := DxDy2Angle(int2hwfloat(ty - oy), int2hwfloat(tx - ox)) + 90;
+
+ if (IsTooDarkToRead(HH^.Team^.Clan^.Color)) then
+ DrawSpriteRotatedF(sprFingerBackInv, tx, ty, RealTicks div 32 mod 16, 1, dAngle)
+ else
+ DrawSpriteRotatedF(sprFingerBack, tx, ty, RealTicks div 32 mod 16, 1, dAngle);
+ Tint(HH^.Team^.Clan^.Color shl 8 or $FF);
+ DrawSpriteRotatedF(sprFinger, tx, ty, RealTicks div 32 mod 16, 1, dAngle);
+ untint;
+ end;
+
+ // render crosshair
+ if (CrosshairGear <> nil) and (Gear = CrosshairGear) then
+ begin
+ hogLR:= 1;
+ if IsHogFacingLeft(Gear) then
+ hogLR:= -1;
+ setTintAdd(true);
+ Tint(HH^.Team^.Clan^.Color shl 8 or $FF);
+ DrawTextureRotated(CrosshairTexture,
+ 12, 12, CrosshairX + WorldDx, CrosshairY + WorldDy, 0,
+ hogLR * (Gear^.Angle * 180.0) / cMaxAngle);
+ untint;
+ setTintAdd(false);
+ end;
+
+ // render gear-related extras: alt weapon, fuel, other
+ if ((Gear^.State and gstHHDriven) <> 0) and (CurAmmoGear <> nil) then
+ begin
+ case CurAmmoGear^.Kind of
+ gtJetpack: begin
+ // render jetpack contour if underwater
+ if (((not SuddenDeathDmg) and (WaterOpacity > cGearContourThreshold)) or (SuddenDeathDmg and (SDWaterOpacity > cGearContourThreshold))) and
+ ((cWaterLine < (hwRound(Gear^.Y) + Gear^.Radius - 16)) or
+ ((WorldEdge = weSea) and ((hwRound(Gear^.X) < LeftX) or (hwRound(Gear^.X) > RightX)))) then
+ DrawSprite(sprJetpack, sx-32, sy-32, 4);
+ if CurAmmoGear^.Tex <> nil then
+ DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex);
+ DrawSelectedWeapon(Gear, sx, sy, true);
+ end;
+ gtRope: DrawSelectedWeapon(Gear, sx, sy, true);
+ gtParachute: DrawSelectedWeapon(Gear, sx, sy, true);
+ gtLandGun: if CurAmmoGear^.Tex <> nil then
+ DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex);
+ gtFlamethrower: if CurAmmoGear^.Tex <> nil then
+ DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex);
+ gtIceGun: if CurAmmoGear^.Tex <> nil then
+ DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex);
+ end;
+ end
+ else if ((Gear^.State and gstHHDriven) <> 0) then
+ begin
+ DrawSelectedWeapon(Gear, sx, sy, false);
+ end
+end;
+
+procedure RenderAirMineGuiExtras(Gear: PGear; ox, oy: LongInt);
+var tinted: boolean;
+begin
+// render air mine contour, if underwater
+ if (((not SuddenDeathDmg) and (WaterOpacity > cGearContourThreshold)) or (SuddenDeathDmg and (SDWaterOpacity > cGearContourThreshold))) and
+ ((cWaterLine < (hwRound(Gear^.Y) + Gear^.Radius + 16)) or
+ ((WorldEdge = weSea) and ((hwRound(Gear^.X) < LeftX + 24) or (hwRound(Gear^.X) > RightX - 24)))) then
+ begin
+ tinted:= true;
+ // tint contour based on air mine state:
+ // not seeking or chasing (frozen, stunned or just launched)
+ if ((Gear^.State and gstFrozen) <> 0) or ((Gear^.State and gstTmpFlag) = 0) or (Gear^.Tag <> 0) then
+ // more transparent
+ Tint($FF, $FF, $FF, $80)
+ // chasing hog
+ else if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ // reddish
+ Tint($FF, $30, $30, $FF)
+ // not seeking or chasing (no target)
+ else if (Gear^.State and gstChooseTarget) = 0 then
+ // more transparent
+ Tint($FF, $FF, $FF, $80)
+ // seeking
+ else
+ // default color
+ tinted:= false;
+ DrawSprite(sprAirMine, ox-16, oy-16, 32);
+ if tinted then
+ untint;
+ end;
+end;
procedure DrawHH(Gear: PGear; ox, oy: LongInt);
var i, t: LongInt;
amt: TAmmoType;
- sign, hx, hy, tx, ty, sx, sy, m: LongInt; // hedgehog, crosshair, temp, sprite, direction
+ sign, hx, hy, tx, ty, sx, sy, hogLR: LongInt; // hedgehog, crosshair, temp, sprite, direction
dx, dy, ax, ay, aAngle, dAngle, hAngle, lx, ly: real; // laser, change
- defaultPos, HatVisible: boolean;
+ wraps: LongWord; // numbe of wraps for laser in world wrap
+ defaultPos, HatVisible, inWorldBounds: boolean;
HH: PHedgehog;
CurWeapon: PAmmo;
iceOffset:Longint;
@@ -273,17 +384,19 @@
curhat: PTexture;
begin
HH:= Gear^.Hedgehog;
+ CrosshairGear:= nil;
if HH^.Unplaced then
exit;
if (HH^.CurAmmoType = amKnife) and (HH = CurrentHedgehog) then
curhat:= ChefHatTexture
else curhat:= HH^.HatTex;
- m:= 1;
- if ((Gear^.State and gstHHHJump) <> 0) and (HH^.Effects[heArtillery] = 0) then
- m:= -1;
sx:= ox + 1; // this offset is very common
sy:= oy - 3;
sign:= hwSign(Gear^.dX);
+ if IsHogFacingLeft(Gear) then
+ hogLR:= -1
+ else
+ hogLR:= 1;
if (Gear^.State and gstHHDeath) <> 0 then
begin
@@ -368,7 +481,7 @@
begin
if ((Gear^.State and (gstHHThinking or gstAnimation)) = 0) and
/// If current ammo is active, and current ammo has alt attack and uses a crosshair (rope, basically, right now, with no crosshair for parachute/saucer
- (((CurAmmoGear <> nil) and //((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0) and
+ (((CurAmmoGear <> nil) and
// don't render crosshair/laser during kamikaze
((CurAmmoGear^.AmmoType <> amKamikaze) or ((Gear^.State and gstAttacking) = 0)) and
((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_NoCrossHair) = 0)) or
@@ -381,11 +494,11 @@
3: I need to extend the beam beyond land.
This routine perhaps should be pushed into uStore or somesuch instead of continuuing the increase in size of this function.
*)
- dx:= sign * m * Sin(Gear^.Angle * pi / cMaxAngle);
+ dx:= hogLR * Sin(Gear^.Angle * pi / cMaxAngle);
dy:= -Cos(Gear^.Angle * pi / cMaxAngle);
if cLaserSighting or cLaserSightingSniper then
begin
- lx:= GetLaunchX(HH^.CurAmmoType, sign * m, Gear^.Angle);
+ lx:= GetLaunchX(HH^.CurAmmoType, hogLR, Gear^.Angle);
ly:= GetLaunchY(HH^.CurAmmoType, Gear^.Angle);
// ensure we start outside the hedgehog (he's solid after all)
@@ -407,38 +520,61 @@
ty:= round(ly);
hx:= tx;
hy:= ty;
- while ((ty and LAND_HEIGHT_MASK) = 0) and
- ((tx and LAND_WIDTH_MASK) = 0) and
- (Land[ty, tx] = 0) do // TODO: check for constant variable instead
+ wraps:= 0;
+ inWorldBounds := ((ty and LAND_HEIGHT_MASK) or (tx and LAND_WIDTH_MASK)) = 0;
+ while (inWorldBounds and ((Land[ty, tx] and lfAll) = 0)) or (not inWorldBounds) do
begin
+ if wraps > cMaxLaserSightWraps then
+ break;
lx:= lx + ax;
ly:= ly + ay;
tx:= round(lx);
- ty:= round(ly)
- end;
- // reached edge of land. assume infinite beam. Extend it way out past camera
- if ((ty and LAND_HEIGHT_MASK) <> 0) or ((tx and LAND_WIDTH_MASK) <> 0) then
- begin
- tx:= round(lx + ax * (max(LAND_WIDTH,4096) div 2));
- ty:= round(ly + ay * (max(LAND_WIDTH,4096) div 2));
+ ty:= round(ly);
+ // reached edge of land.
+ if ((ty and LAND_HEIGHT_MASK) <> 0) and (((ty < LAND_HEIGHT) and (ay < 0)) or ((ty >= TopY) and (ay > 0))) then
+ begin
+ // assume infinite beam. Extend it way out past camera
+ tx:= round(lx + ax * (max(LAND_WIDTH,4096) div 2));
+ ty:= round(ly + ay * (max(LAND_WIDTH,4096) div 2));
+ break;
+ end;
+
+ if ((hogLR < 0) and (tx < LeftX)) or ((hogLR > 0) and (tx >= RightX)) then
+ if (WorldEdge = weWrap) then
+ // wrap beam
+ begin
+ if hogLR < 0 then
+ lx:= RightX - (ax - (lx - LeftX))
+ else
+ lx:= LeftX + (ax - (RightX - lx));
+ tx:= round(lx);
+ inc(wraps);
+ end
+ else if (WorldEdge = weBounce) then
+ // just stop
+ break;
+
+ if ((tx and LAND_WIDTH_MASK) <> 0) and (((ax > 0) and (tx >= RightX)) or ((ax < 0) and (tx <= LeftX))) then
+ begin
+ if (WorldEdge <> weWrap) and (WorldEdge <> weBounce) then
+ // assume infinite beam. Extend it way out past camera
+ begin
+ tx:= round(lx + ax * (max(LAND_WIDTH,4096) div 2));
+ ty:= round(ly + ay * (max(LAND_WIDTH,4096) div 2));
+ end;
+ break;
+ end;
+ inWorldBounds := ((ty and LAND_HEIGHT_MASK) or (tx and LAND_WIDTH_MASK)) = 0;
end;
- //if (abs(lx-tx)>8) or (abs(ly-ty)>8) then
- begin
- DrawLine(hx, hy, tx, ty, 1.0, $FF, $00, $00, $C0);
- end;
+ DrawLineWrapped(hx, hy, tx, ty, 1.0, hogLR < 0, wraps, $FF, $00, $00, $C0);
end;
- // draw crosshair
- CrosshairX := Round(hwRound(Gear^.X) + dx * 80 + GetLaunchX(HH^.CurAmmoType, sign * m, Gear^.Angle));
+
+ // calculate crosshair position
+ CrosshairX := Round(hwRound(Gear^.X) + dx * 80 + GetLaunchX(HH^.CurAmmoType, hogLR, Gear^.Angle));
CrosshairY := Round(hwRound(Gear^.Y) + dy * 80 + GetLaunchY(HH^.CurAmmoType, Gear^.Angle));
-
- setTintAdd(true);
- Tint(HH^.Team^.Clan^.Color shl 8 or $FF);
- DrawTextureRotated(CrosshairTexture,
- 12, 12, CrosshairX + WorldDx, CrosshairY + WorldDy, 0,
- sign * m * (Gear^.Angle * 180.0) / cMaxAngle);
- untint;
- setTintAdd(false);
+ // crosshair will be rendered in RenderHHGuiExtras
+ CrosshairGear := Gear;
end;
hx:= ox + 8 * sign;
@@ -462,7 +598,7 @@
end;
gtBallgun: DrawSpriteRotated(sprHandBallgun, hx, hy, sign, aangle);
gtRCPlane: begin
- DrawSpriteRotated(sprHandPlane, hx, hy, sign, 0);
+ DrawSpriteRotated(sprHandPlane, hx + 1, hy, sign, 0);
defaultPos:= false
end;
gtRope: begin
@@ -503,7 +639,6 @@
end
end
end;
- DrawAltWeapon(Gear, ox, oy);
defaultPos:= false
end;
gtBlowTorch:
@@ -560,6 +695,17 @@
dec(sy,20);
end;
gtTeleport: defaultPos:= false;
+ gtParachute:
+ begin
+ DrawSpriteRotatedF(sprHHIdle,
+ sx,
+ sy,
+ 0,
+ CurAmmoGear^.Tag,
+ 0);
+ HatVisible:= true;
+ defaultPos:= false;
+ end;
gtWhip:
begin
DrawSpriteRotatedF(sprWhip,
@@ -618,27 +764,14 @@
0);
// sprCensored contains English text, so only show it for English locales
// TODO: Make text translatable. But how?
- if Copy(cLocale, 1, 2) = 'en' then
+ if Copy(cLanguage, 1, 2) = 'en' then
DrawSprite(sprCensored, ox - 32, oy - 20, 0);
end;
defaultPos:= false
end;
- gtFlamethrower:
- begin
- DrawSpriteRotatedF(sprHandFlamethrower, hx, hy, (RealTicks div 125) mod 4, sign, aangle);
- if CurAmmoGear^.Tex <> nil then
- DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex)
- end;
- gtLandGun:
- begin DrawSpriteRotated(sprHandBallgun, hx, hy, sign, aangle);
- if CurAmmoGear^.Tex <> nil then
- DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex)
- end;
- gtIceGun:
- begin DrawSpriteRotated(sprIceGun, hx, hy, sign, aangle);
- if CurAmmoGear^.Tex <> nil then
- DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex)
- end;
+ gtFlamethrower: DrawSpriteRotatedF(sprHandFlamethrower, hx, hy, (RealTicks div 125) mod 4, sign, aangle);
+ gtLandGun: DrawSpriteRotated(sprHandLandGun, hx, hy, sign, aangle);
+ gtIceGun: DrawSpriteRotated(sprIceGun, hx, hy, sign, aangle);
end;
case CurAmmoGear^.Kind of
@@ -662,7 +795,7 @@
if ((Gear^.State and gstHHJumping) <> 0) then
begin
DrawHedgehog(sx, sy,
- sign*m,
+ hogLR,
1,
1,
0);
@@ -741,56 +874,51 @@
amSkip: DrawSpriteRotated(sprHandSkip, hx, hy, sign, aangle);
amClusterBomb: DrawSpriteRotated(sprHandCluster, hx, hy, sign, aangle);
amDynamite: DrawSpriteRotated(sprHandDynamite, hx, hy, sign, aangle);
- amDuck: DrawSpriteRotatedF(sprHandDuck, hx, hy, 0, sign, aangle);
+ amCreeper: DrawSpriteRotatedF(sprHandCreeper, hx, hy, 0, sign, aangle);
amHellishBomb: DrawSpriteRotated(sprHandHellish, hx, hy, sign, aangle);
amGasBomb: DrawSpriteRotated(sprHandCheese, hx, hy, sign, aangle);
amMine: DrawSpriteRotated(sprHandMine, hx, hy, sign, aangle);
amAirMine: DrawSpriteRotated(sprHandAirMine, hx, hy, sign, aangle);
amSMine: DrawSpriteRotated(sprHandSMine, hx, hy, sign, aangle);
amKnife: DrawSpriteRotatedF(sprHandKnife, hx, hy, 0, sign, aangle);
- amSeduction: begin
+ amSeduction: if ((Gear^.State and gstMoving) = 0) then
+ begin
DrawSpriteRotated(sprHandSeduction, hx, hy, sign, aangle);
- DrawCircle(ox, oy, 248, 4, $FF, $00, $00, $AA);
- //Tint($FF, $0, $0, $AA);
- //DrawTexture(ox - 240, oy - 240, SpritesData[sprVampiric].Texture, 10);
- //untint;
+ DrawCircle(ox, oy, cSeductionDist - 2, 4, $FF, $00, $00, $AA);
end;
amVampiric: DrawSpriteRotatedF(sprHandVamp, hx, hy, (RealTicks div 125) mod 4, sign, aangle);
- amRCPlane: begin
- DrawSpriteRotated(sprHandPlane, hx, hy, sign, 0);
- defaultPos:= false
- end;
amRubber,
- amGirder: begin
+ amGirder: if ((Gear^.State and gstMoving) = 0) then
+ begin
DrawSpriteRotated(sprHandConstruction, hx, hy, sign, aangle);
if cBuildMaxDist = cDefaultBuildMaxDist then
begin
if WorldEdge = weWrap then
begin
- if hwRound(Gear^.X) < LongInt(leftX) + 256 then
+ if hwRound(Gear^.X) < leftX + 256 then
DrawSpriteClipped(sprGirder,
rightX+(ox-leftX)-256,
oy-256,
- LongInt(topY)+WorldDy,
- LongInt(rightX)+WorldDx,
+ topY+WorldDy,
+ rightX+WorldDx,
cWaterLine+WorldDy,
- LongInt(leftX)+WorldDx);
- if hwRound(Gear^.X) > LongInt(rightX) - 256 then
+ leftX+WorldDx);
+ if hwRound(Gear^.X) > rightX - 256 then
DrawSpriteClipped(sprGirder,
leftX-(rightX-ox)-256,
oy-256,
- LongInt(topY)+WorldDy,
- LongInt(rightX)+WorldDx,
+ topY+WorldDy,
+ rightX+WorldDx,
cWaterLine+WorldDy,
- LongInt(leftX)+WorldDx)
+ leftX+WorldDx)
end;
DrawSpriteClipped(sprGirder,
ox-256,
oy-256,
- LongInt(topY)+WorldDy,
- LongInt(rightX)+WorldDx,
+ topY+WorldDy,
+ rightX+WorldDx,
cWaterLine+WorldDy,
- LongInt(leftX)+WorldDx)
+ leftX+WorldDx)
end
else if cBuildMaxDist > 0 then
begin
@@ -799,9 +927,11 @@
end;
amBee: DrawSpriteRotatedF(sprHandBee, hx, hy, (RealTicks div 125) mod 4, sign, aangle);
amFlamethrower: DrawSpriteRotatedF(sprHandFlamethrower, hx, hy, (RealTicks div 125) mod 4, sign, aangle);
- amLandGun: DrawSpriteRotated(sprHandBallgun, hx, hy, sign, aangle);
+ amLandGun: DrawSpriteRotated(sprHandLandGun, hx, hy, sign, aangle);
amIceGun: DrawSpriteRotated(sprIceGun, hx, hy, sign, aangle);
- amResurrector: DrawCircle(ox, oy, 98, 4, $F5, $DB, $35, $AA); // I'd rather not like to hardcode 100 here
+ amResurrector: if ((Gear^.State and gstMoving) = 0) then
+ DrawCircle(ox, oy, cResurrectorDist - 2, 4, $F5, $DB, $35, $AA);
+ amFirePunch: DrawSpriteRotatedF(sprFirePunch, hx + 6 * sign + 1, hy - 5, (RealTicks div 50) mod 16, sign, 0);
end;
case amt of
@@ -828,11 +958,16 @@
sign,
0);
amHammer: DrawSpriteRotatedF(sprHammer,
- sx,
+ sx + sign,
sy,
0,
sign,
0);
+ amRCPlane:
+ begin
+ DrawSpriteRotated(sprHandPlane, hx + 1, hy, sign, 0);
+ defaultPos:= false
+ end;
amBaseballBat, amMinigun:
begin
HatVisible:= true;
@@ -843,24 +978,24 @@
0);
end
else
- DrawHedgehog(sx, sy,
- sign,
- 0,
- 4,
- 0);
-
- HatVisible:= true;
- (* with HH^ do
- if (HatTex <> nil)
- and (HatVisibility > 0) then
- DrawTextureF(HatTex,
- HatVisibility,
- sx,
- sy - 5,
- 0,
- sign,
- 32,
- 32); *)
+ // Special hog sprite that makes hog "look" towards the selection icon.
+ // Only works without hat for now since it would look weird/creepy for many hats.
+ if ((HH^.Hat = 'NoHat') or (HH^.HatTex = nil)) and ((Gear^.State and (gstMoving or gstAttacking)) = 0) and ((Ammoz[amt].Ammo.Propz and ammoprop_ShowSelIcon) <> 0) then
+ DrawHedgehog(sx, sy,
+ sign,
+ 0,
+ 6,
+ 0)
+ // Default idle hedgehog
+ else
+ begin
+ DrawHedgehog(sx, sy,
+ sign,
+ 0,
+ 4,
+ 0);
+ HatVisible:= true;
+ end;
end;
defaultPos:= false
@@ -904,7 +1039,7 @@
if ((Gear^.State and gstHHJumping) <> 0) then
begin
DrawHedgehog(sx, sy,
- sign*m,
+ hogLR,
1,
1,
0);
@@ -1008,7 +1143,7 @@
sx,
sy - 5,
0,
- sign*m,
+ hogLR,
32,
32);
if (curhat^.w > 64) or ((curhat^.w = 64) and (curhat^.h = 32)) then
@@ -1023,7 +1158,7 @@
sx,
sy - 5,
tx,
- sign*m,
+ hogLR,
32,
32);
untint
@@ -1033,24 +1168,17 @@
if (Gear^.State and gstHHDriven) <> 0 then
begin
- (* if (CurAmmoGear = nil) then
- begin
- amt:= CurrentHedgehog^.CurAmmoType;
- case amt of
- amJetpack: DrawSprite(sprJetpack, sx-32, sy-32, 0);
- end
- end; *)
if (CurAmmoGear = nil) then
begin
- if ((Gear^.State and (gstAttacked or gstAnimation or gstHHJumping)) = 0)
- and (Gear^.Message and (gmLeft or gmRight) = 0) then
+ if ((Gear^.State and (gstAttacked or gstAnimation or gstHHJumping)) = 0)
+ and (Gear^.Message and (gmLeft or gmRight) = 0) then
begin
amt:= CurrentHedgehog^.CurAmmoType;
- case amt of
- amBaseballBat: DrawSpritePivotedF(sprHandBaseball,
- sx + 9 * sign, sy + 2, 0, sign, -8, 1, aangle);
- amMinigun: DrawSpritePivotedF(sprMinigun,
- sx + 20 * sign, sy + 4, 0, sign, -18, -2, aangle);
+ case amt of
+ amBaseballBat: DrawSpritePivotedF(sprHandBaseball,
+ sx + 9 * sign, sy + 2, 0, sign, -8, 1, aangle);
+ amMinigun: DrawSpritePivotedF(sprMinigun,
+ sx + 20 * sign, sy + 4, 0, sign, -18, -2, aangle);
end;
end;
end
@@ -1069,9 +1197,6 @@
if (CurAmmoGear^.MsgParam and gmRight) <> 0 then
DrawSprite(sprJetpack, sx-36, sy-28, 3)
end;
- if CurAmmoGear^.Tex <> nil then
- DrawTextureCentered(sx, sy - 40, CurAmmoGear^.Tex);
- DrawAltWeapon(Gear, sx, sy)
end;
gtShover: DrawSpritePivotedF(sprHandBaseball,
sx + 9 * sign, sy + 2, CurAmmoGear^.Tag, sign, -8, 1, aangle);
@@ -1113,42 +1238,8 @@
if (CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtResurrector) then
DrawTextureCentered(ox, sy - cHHRadius - 7 - HealthTagTex^.h, HealthTagTex);
- if bShowFinger and ((Gear^.State and gstHHDriven) <> 0) then
- begin
- ty := oy - 32;
- // move finger higher up if tags are above hog
- if (cTagsMask and htTeamName) <> 0 then
- ty := ty - Team^.NameTagTex^.h - 2;
- if (cTagsMask and htName) <> 0 then
- ty := ty - NameTagTex^.h - 2;
- if (cTagsMask and htHealth) <> 0 then
- ty := ty - HealthTagTex^.h - 2;
- tx := ox;
-
- // don't go offscreen
- //tx := round(max(((-cScreenWidth + 16) / cScaleFactor) + SpritesData[sprFinger].Width div 2, min(((cScreenWidth - 16) / cScaleFactor) - SpritesData[sprFinger].Width div 2, tx)));
- //ty := round(max(cScreenHeight div 2 - ((cScreenHeight - 16) / cScaleFactor) + SpritesData[sprFinger].Height div 2, min(cScreenHeight div 2 - ((-cScreenHeight + SpritesData[sprFinger].Height) / (cScaleFactor)) - SpritesData[sprFinger].Width div 2 - 96, ty)));
- t:= 32;//trunc((SpritesData[sprFinger].Width + t) / cScaleFactor);
- tx := min(max(tx, ViewLeftX + t), ViewRightX - t);
- t:= 32;//trunc((SpritesData[sprFinger].Height + t) / cScaleFactor);
- ty := min(ty, ViewBottomY - 96);
- // don't overlap with HH or HH tags
- if ty < ViewTopY + t then
- begin
- if abs(tx - ox) < abs(ty - oy) then
- ty:= max(ViewTopY + t, oy + t)
- else
- ty:= max(ViewTopY + t, ty);
- end;
-
- dAngle := DxDy2Angle(int2hwfloat(ty - oy), int2hwfloat(tx - ox)) + 90;
-
- DrawSpriteRotatedF(sprFinger, tx, ty, RealTicks div 32 mod 16, 1, dAngle);
- end;
-
-
if (Gear^.State and gstDrowning) = 0 then
- if (Gear^.State and gstHHThinking) <> 0 then
+ if ((Gear^.State and gstHHThinking) <> 0) and (not CinematicScript) then
DrawSprite(sprQuestion, ox - 10, oy - cHHRadius - 34, (RealTicks shr 9) mod 8)
end
end;
@@ -1207,17 +1298,27 @@
aAngle: real;
startX, endX, startY, endY: LongInt;
begin
- if Gear^.State and gstFrozen <> 0 then Tint($A0, $A0, $FF, $FF);
- //if Gear^.State and gstFrozen <> 0 then Tint(IceColor or $FF);
+ // airmine has its own sprite
+ if (Gear^.State and gstFrozen <> 0) and (Gear^.Kind <> gtAirMine) then Tint($A0, $A0, $FF, $FF);
if Gear^.Target.X <> NoPointX then
if Gear^.AmmoType = amBee then
DrawSpriteRotatedF(sprTargetBee, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
else if Gear^.AmmoType = amIceGun then
- //DrawSprite(sprSnowDust, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8)
- //DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1, 0, 0, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8, 1, 22, 22, (RealTicks shr 3) mod 360)
DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1/(1+(RealTicks shr 8) mod 5), 0, 0, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, (RealTicks shr 2) mod 8, 1, 22, 22, (RealTicks shr 3) mod 360)
else
+ begin
+ if CurrentHedgehog <> nil then
+ begin
+ if (IsTooDarkToRead(CurrentHedgehog^.Team^.Clan^.Color)) then
+ DrawSpriteRotatedF(sprTargetPBackInv, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
+ else
+ DrawSpriteRotatedF(sprTargetPBack, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ Tint(CurrentHedgehog^.Team^.Clan^.Color shl 8 or $FF);
+ end;
DrawSpriteRotatedF(sprTargetP, Gear^.Target.X + WorldDx, Gear^.Target.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ if CurrentHedgehog <> nil then
+ untint;
+ end;
case Gear^.Kind of
gtGrenade: DrawSpriteRotated(sprBomb, x, y, 0, Gear^.DirAngle);
@@ -1258,11 +1359,16 @@
end;
end;
- gtDrill: if (Gear^.State and gsttmpFlag) <> 0 then
- DrawSpriteRotated(sprAirDrill, x, y, 0, DxDy2Angle(Gear^.dY, Gear^.dX))
+ gtDrill: begin
+ if (Gear^.Pos = 1) then
+ i:= (RealTicks shr 5 + Gear^.uid) mod 4
else
- DrawSpriteRotated(sprDrill, x, y, 0, DxDy2Angle(Gear^.dY, Gear^.dX));
-
+ i:= Gear^.uid mod 4;
+ if (Gear^.State and gsttmpFlag) <> 0 then
+ DrawTextureRotatedF(SpritesData[sprAirDrill].texture, 0.5, 0, 0, x, y, i, 0, 64, 64, DxDy2Angle(Gear^.dY, Gear^.dX))
+ else
+ DrawTextureRotatedF(SpritesData[sprDrill].texture, 0.5, 0, 0, x, y, i, 0, 64, 64, DxDy2Angle(Gear^.dY, Gear^.dX));
+ end;
gtHedgehog: DrawHH(Gear, x, y);
gtShell: DrawSpriteRotated(sprBazookaShell, x, y, 0, DxDy2Angle(Gear^.dY, Gear^.dX));
@@ -1271,9 +1377,7 @@
DrawTextureF(Gear^.Hedgehog^.Team^.GraveTex, 1, x, y, (RealTicks shr 7+Gear^.uid) and 15, 1, 32, 32);
if Gear^.Health > 0 then
begin
- //Tint($33, $33, $FF, max($40, round($FF * abs(1 - (GameTicks mod (6000 div Gear^.Health)) / 750))));
Tint($f5, $db, $35, max($40, round($FF * abs(1 - (RealTicks mod 1500) / (750 + Gear^.Health)))));
- //Tint($FF, $FF, $FF, max($40, round($FF * abs(1 - (RealTicks mod 1500) / 750))));
DrawSprite(sprVampiric, x - 24, y - 24, 0);
untint
end
@@ -1289,18 +1393,36 @@
DrawSpriteRotated(sprMineOn, x, y, 0, Gear^.DirAngle)
else DrawSpriteRotated(sprMineDead, x, y, 0, Gear^.DirAngle);
end;
- gtAirMine: if Gear^.State and gstTmpFlag = 0 then // mine is inactive
+ gtAirMine:
+ // render air mine based on its state:
+ // frozen
+ if (Gear^.State and gstFrozen <> 0) then
+ // frozen air mine sprite
+ DrawSprite(sprFrozenAirMine, x-16, y-16, 0)
+ // stunned (after being shot)
+ else if (Gear^.Tag <> 0) then
+ // sparks animation
+ DrawSprite(sprAirMine, x-16, y-16, 16 + ((RealTicks div 50 + Gear^.Uid) mod 16))
+ // inactive / initialization phase (shortly after launched by hog)
+ else if (Gear^.State and gstTmpFlag = 0) then
begin
+ // dark air mine, signal lamp off
Tint(150,150,150,255);
DrawSprite(sprAirMine, x-16, y-16, 15);
untint
end
- else if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then // mine is chasing a hog
- DrawSprite(sprAirMine, x-16, y-16, (RealTicks div 25) mod 16)
- else if Gear^.State and gstChooseTarget <> 0 then // mine is seeking for hogs
- DrawSprite(sprAirMine, x-16, y-16, (RealTicks div 125) mod 16)
+ // actively chasing a hog
+ else if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ // signal lamp rapidly flashes
+ DrawSprite(sprAirMine, x-16, y-16, (RealTicks div 25 + Gear^.Uid) mod 16)
+ // seeking for hogs
+ else if Gear^.State and gstChooseTarget <> 0 then
+ // signal lamp on
+ DrawSprite(sprAirMine, x-16, y-16, 3)
+ // active, but not seeking for hogs
else
- DrawSprite(sprAirMine, x-16, y-16, 4); // mine is active but not seeking
+ // signal lamp off
+ DrawSprite(sprAirMine, x-16, y-16, 15);
gtSMine: if (((Gear^.State and gstAttacking) = 0)or((Gear^.Timer and $3FF) < 420)) and (Gear^.Health <> 0) then
DrawSpriteRotated(sprSMineOff, x, y, 0, Gear^.DirAngle)
@@ -1398,13 +1520,16 @@
else DrawTextureF(SpritesData[sprFlame].Texture, 2 / (Gear^.Tag mod 3 + 2), x, y, (RealTicks shr 7 + LongWord(Gear^.Tag)) mod 8, -1, 16, 16);
gtParachute: begin
DrawSprite(sprParachute, x - 24, y - 48, 0);
- DrawAltWeapon(Gear, x + 1, y - 3)
end;
gtAirAttack: begin
Tint(Gear^.Tint);
DrawSpriteRotatedF(sprAirplane, x, y, 0, Gear^.Tag, 0);
untint;
DrawSpriteRotatedF(sprAirplane, x, y, 1, Gear^.Tag, 0);
+ if WorldEdge <> weSea then
+ DrawSpriteRotatedF(sprAirplane, x, y, 2, Gear^.Tag, 0)
+ else
+ DrawSpriteRotatedF(sprAirplane, x, y, 3, Gear^.Tag, 0);
end;
gtAirBomb: DrawSpriteRotated(sprAirBomb, x, y, 0, DxDy2Angle(Gear^.dY, Gear^.dX));
gtTeleport: begin
@@ -1416,7 +1541,13 @@
DrawSpriteRotatedF(sprTeleport, hwRound(HHGear^.X) + 1 + WorldDx, hwRound(HHGear^.Y) - 3 + WorldDy, 11 - Gear^.Pos, hwSign(HHGear^.dX), 0)
end
end;
- gtSwitcher: DrawSprite(sprSwitch, x - 16, y - 56, (RealTicks shr 6) mod 12);
+ gtSwitcher: begin
+ setTintAdd(true);
+ Tint(Gear^.Hedgehog^.Team^.Clan^.Color shl 8 or $FF);
+ DrawSprite(sprSwitch, x - 16, y - 56, (RealTicks shr 6) mod 12);
+ untint;
+ setTintAdd(false);
+ end;
gtTarget: begin
Tint($FF, $FF, $FF, round($FF * Gear^.Timer / 1000));
DrawSprite(sprTarget, x - 16, y - 16, 0);
@@ -1500,8 +1631,6 @@
Tint(Gear^.Tint);
// Needs a nicer white texture to tint
DrawTextureRotatedF(SpritesData[sprSnowDust].Texture, 1, 0, 0, x, y, 0, 1, 8, 8, Gear^.DirAngle);
- //DrawSpriteRotated(sprSnowDust, x, y, 0, Gear^.DirAngle);
- //DrawTexture(x, y, SpritesData[sprVampiric].Texture, 0.1);
untint;
end
else //if not isInLag then
@@ -1516,12 +1645,9 @@
DrawSprite(sprFlake, x, y, Gear^.Timer)
else
DrawSpriteRotatedF(sprFlake, x, y, Gear^.Timer, 1, Gear^.DirAngle);
-//DrawSprite(sprFlake, x-SpritesData[sprFlake].Width div 2, y-SpritesData[sprFlake].Height div 2, Gear^.Timer)
-//DrawSpriteRotatedF(sprFlake, x-SpritesData[sprFlake].Width div 2, y-SpritesData[sprFlake].Height div 2, Gear^.Timer, 1, Gear^.DirAngle);
if Gear^.FlightTime > 0 then
untint;
end;
- //gtStructure: DrawSprite(sprTarget, x - 16, y - 16, 0);
gtTardis: if Gear^.Pos <> 4 then
begin
if Gear^.Pos = 2 then
@@ -1536,11 +1662,6 @@
DrawSprite(sprTardis, x-25, y-64,1);
if Gear^.Pos <> 2 then
untint
-(*
- Tint(Gear^.Hedgehog^.Team^.Clan^.Color shl 8 or max($00, round(Gear^.Power * abs(1 - (RealTicks mod 500) / 250))));
- DrawTexture(x-6, y-70, SpritesData[sprVampiric].Texture, 0.25);
- untint
-*)
end;
gtIceGun: begin
HHGear := Gear^.Hedgehog^.Gear;
@@ -1564,18 +1685,19 @@
i:= random(100)+100;
if Gear^.Target.X <> NoPointX then
begin
- DrawLine(Gear^.Target.X, Gear^.Target.Y, hwRound(HHGear^.X), hwRound(HHGear^.Y), 4.0, i, i, $FF, $40);
+ DrawLineWrapped(hwRound(HHGear^.X), hwRound(HHGear^.Y), Gear^.Target.X, Gear^.Target.Y, 4.0, hwSign(HHGear^.dX) < 0, Gear^.FlightTime, i, i, $FF, $40);
end
else
begin
- DrawLine(hwRound(HHGear^.X), hwRound(HHGear^.Y), hwRound(Gear^.X), hwRound(Gear^.Y), 4.0, i, i, $FF, $40);
+ DrawLineWrapped(hwRound(HHGear^.X), hwRound(HHGear^.Y), hwRound(Gear^.X), hwRound(Gear^.Y), 4.0, hwSign(HHGear^.dX) < 0, Gear^.FlightTime, i, i, $FF, $40);
end;
end
end
end;
- gtDuck: DrawSpriteRotatedF(sprDuck, x, y, 1, Gear^.Tag,
- // replace with something based on dx/dy?
- Gear^.DirAngle + 10-round(20 * abs(1 - (RealTicks mod round(0.1/max(0.00005,cWindSpeedf))) / round(0.05/max(0.00005,cWindSpeedf))) ));
+ gtCreeper: if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then
+ DrawSpriteRotatedF(sprCreeper, x, y, 1, hwRound(SignAs(_1,Gear^.Hedgehog^.Gear^.X-Gear^.X)), 0)
+ else DrawSpriteRotatedF(sprCreeper, x, y, 1, hwRound(SignAs(_1,Gear^.dX)), 0);
+
gtGenericFaller: begin
// DEBUG: draw gtGenericFaller
if Gear^.Tag <> 0 then
@@ -1589,8 +1711,19 @@
procedure RenderGearTimer(Gear: PGear; x, y: LongInt);
begin
-if Gear^.RenderTimer and (Gear^.Tex <> nil) then
+if Gear^.RenderTimer and (Gear^.Tex <> nil) and (isShowGearInfo or (not (Gear^.Kind in [gtMine, gtSMine, gtAirMine]))) then
DrawTextureCentered(x + 8, y + 8, Gear^.Tex);
end;
+procedure RenderGearHealth(Gear: PGear; x, y: LongInt);
+begin
+if isShowGearInfo and (Gear^.RenderHealth) and (Gear^.Tex <> nil) then
+ begin
+ if (Gear^.Kind = gtCase) and ((Gear^.Pos and posCaseHealth) <> 0) then
+ DrawTextureCentered(x, y - 38, Gear^.Tex);
+ if (Gear^.Kind = gtExplosives) then
+ DrawTextureCentered(x, y - 38, Gear^.Tex);
+ end;
+end;
+
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uGearsUtils.pas
--- a/hedgewars/uGearsUtils.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsUtils.pas Wed Jul 31 23:14:27 2019 +0200
@@ -25,14 +25,16 @@
procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword); inline;
procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword; const Tint: LongWord);
procedure AddSplashForGear(Gear: PGear; justSkipping: boolean);
+procedure AddBounceEffectForGear(Gear: PGear; imageScale: Single);
procedure AddBounceEffectForGear(Gear: PGear);
function ModifyDamage(dmg: Longword; Gear: PGear): Longword;
procedure ApplyDamage(Gear: PGear; AttackerHog: PHedgehog; Damage: Longword; Source: TDamageSource);
procedure spawnHealthTagForHH(HHGear: PGear; dmg: Longword);
-procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource);
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean; vgTint: Longword);
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean);
+procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource; Damage: Longword);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean; vgTint: Longword);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean);
+function IncHogHealth(Hedgehog: PHedgehog; healthBoost: LongInt): LongInt;
procedure CheckHHDamage(Gear: PGear);
procedure CalcRotationDirAngle(Gear: PGear);
procedure ResurrectHedgehog(var gear: PGear);
@@ -40,15 +42,18 @@
procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt); inline;
procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity: boolean);
+function CheckGearNear(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt): PGear;
function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;
function CheckGearDrowning(var Gear: PGear): boolean;
procedure CheckCollision(Gear: PGear); inline;
procedure CheckCollisionWithLand(Gear: PGear); inline;
procedure AmmoShove(Ammo: PGear; Damage, Power: LongInt);
+procedure AmmoShoveCache(Ammo: PGear; Damage, Power: LongInt);
procedure AmmoShoveLine(Ammo: PGear; Damage, Power: LongInt; oX, oY, tX, tY: hwFloat);
function GearsNear(X, Y: hwFloat; Kind: TGearType; r: LongInt): PGearArrayS;
-procedure SpawnBoxOfSmth;
+function SpawnBoxOfSmth: PGear;
+procedure PlayBoxSpawnTaunt(Gear: PGear);
procedure ShotgunShot(Gear: PGear);
function CanUseTardis(HHGear: PGear): boolean;
@@ -60,7 +65,9 @@
function GetUtility(Hedgehog: PHedgehog): TAmmoType;
function WorldWrap(var Gear: PGear): boolean;
+function HomingWrap(var Gear: PGear): boolean;
+function IsHogFacingLeft(Gear: PGear): boolean;
function IsHogLocal(HH: PHedgehog): boolean;
@@ -113,11 +120,6 @@
end;
if (Mask and EXPLAutoSound) <> 0 then PlaySound(sndExplosion);
-(*if (Mask and EXPLAllDamageInRadius) = 0 then
- dmgRadius:= Radius shl 1
-else
- dmgRadius:= Radius;
-dmgBase:= dmgRadius + cHHRadius div 2;*)
dmgBase:= Radius shl 1 + cHHRadius div 2;
// we might have to run twice if weWrap is enabled
@@ -131,8 +133,6 @@
while Gear <> nil do
begin
dmg:= 0;
- //dmg:= dmgRadius + cHHRadius div 2 - hwRound(Distance(Gear^.X - int2hwFloat(X), Gear^.Y - int2hwFloat(Y)));
- //if (dmg > 1) and
if (Gear^.State and gstNoDamage) = 0 then
begin
case Gear^.Kind of
@@ -142,15 +142,13 @@
gtMelonPiece,
gtGrenade,
gtClusterBomb,
- // gtCluster, too game breaking I think
gtSMine,
gtAirMine,
gtCase,
gtTarget,
gtFlame,
gtKnife,
- gtExplosives: begin //,
- //gtStructure: begin
+ gtExplosives: begin
// Run the calcs only once we know we have a type that will need damage
tdX:= Gear^.X-fX;
tdY:= Gear^.Y-fY;
@@ -176,9 +174,18 @@
Gear^.State:= (Gear^.State or gstMoving) and (not gstLoser);
if Gear^.Kind = gtKnife then Gear^.State:= Gear^.State and (not gstCollision);
if (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog^.Effects[heInvulnerable] = 0) then
- Gear^.State:= (Gear^.State or gstMoving) and (not gstWinner);
+ begin
+ Gear^.State:= (Gear^.State or gstMoving) and (not (gstHHJumping or gstHHHJump));
+ if (not GameOver) then
+ Gear^.State:= (Gear^.State and (not gstWinner));
+ end;
Gear^.Active:= true;
- if Gear^.Kind <> gtFlame then FollowGear:= Gear
+ if Gear^.Kind <> gtFlame then FollowGear:= Gear;
+ if Gear^.Kind = gtAirMine then
+ begin
+ Gear^.Tag:= 1;
+ Gear^.FlightTime:= 5000;
+ end
end;
if ((Mask and EXPLPoisoned) <> 0) and (Gear^.Kind = gtHedgehog) and
(Gear^.Hedgehog^.Effects[heInvulnerable] = 0) and (Gear^.Hedgehog^.Effects[heFrozen] = 0) and
@@ -187,7 +194,7 @@
if Gear^.Hedgehog^.Effects[hePoisoned] = 0 then
begin
s:= ansistring(Gear^.Hedgehog^.Name);
- AddCaption(FormatA(GetEventString(eidPoisoned), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidPoisoned), s), capcolDefault, capgrpMessage);
uStats.HedgehogPoisoned(Gear, AttackingHog)
end;
Gear^.Hedgehog^.Effects[hePoisoned] := 5;
@@ -215,7 +222,7 @@
end;
if (Mask and EXPLDontDraw) = 0 then
- if (GameFlags and gfSolidLand) = 0 then
+ if ((GameFlags and gfSolidLand) = 0) or ((Mask and EXPLForceDraw) <> 0) then
begin
cnt:= DrawExplosion(X, Y, Radius) div 1608; // approx 2 16x16 circles to erase per chunk
if (cnt > 0) and (SpritesData[sprChunk].Texture <> nil) then
@@ -230,12 +237,12 @@
break;
// Radius + 5 because that's the actual radius the explosion changes graphically
- if X + (Radius + 5) > LongInt(rightX) then
+ if X + (Radius + 5) > rightX then
begin
dec(X, playWidth);
wrap:= true;
end
- else if X - (Radius + 5) < LongInt(leftX) then
+ else if X - (Radius + 5) < leftX then
begin
inc(X, playWidth);
wrap:= true;
@@ -276,8 +283,16 @@
Gear^.LastDamage := AttackerHog;
Gear^.Hedgehog^.Team^.Clan^.Flawless:= false;
- HHHurt(Gear^.Hedgehog, Source);
- AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), Damage, Gear^.Hedgehog^.Team^.Clan^.Color);
+
+ if (Gear^.State and gstHHDeath) <> 0 then
+ // If hog took damage while dying, explode hog instantly (see doStepHedgehogDead)
+ Gear^.Timer:= 1
+ else
+ begin
+ HHHurt(Gear^.Hedgehog, Source, Damage);
+ AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), Damage, Gear^.Hedgehog^.Team^.Clan^.Color);
+ end;
+
tmpDmg:= min(Damage, max(0,Gear^.Health-Gear^.Damage));
if (Gear <> CurrentHedgehog^.Gear) and (CurrentHedgehog^.Gear <> nil) and (tmpDmg >= 1) then
begin
@@ -288,7 +303,7 @@
begin
// was considering pulsing on attack, Tiy thinks it should be permanent while in play
//CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstVampiric;
- inc(CurrentHedgehog^.Gear^.Health,vampDmg);
+ vampDmg:= IncHogHealth(CurrentHedgehog, vampDmg);
RenderHealth(CurrentHedgehog^);
RecountTeamHealth(CurrentHedgehog^.Team);
HHHeal(CurrentHedgehog, vampDmg, true, $FF0000FF);
@@ -305,28 +320,24 @@
uStats.HedgehogDamaged(Gear, AttackerHog, Damage, false);
- if AprilOne and (Gear^.Hedgehog^.Hat = 'fr_tomato') and (Damage > 2) then
- for i := 0 to random(min(Damage,20))+5 do
- begin
- vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot);
- if vg <> nil then
- with vg^ do
- begin
- dx:= 0.001 * (random(100)+10);
- dy:= 0.001 * (random(100)+10);
- tdy:= -cGravityf;
- if random(2) = 0 then
- dx := -dx;
- //if random(2) = 0 then
- // dy := -dy;
- FrameTicks:= random(500) + 1000;
- State:= ord(sprBubbles);
- //Tint:= $bd2f03ff
- Tint:= $ff0000ff
- end
- end
+ if AprilOne and (Gear^.Hedgehog^.Hat = 'fr_tomato') and (Damage > 2) then
+ for i := 0 to random(min(Damage,20))+5 do
+ begin
+ vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot);
+ if vg <> nil then
+ with vg^ do
+ begin
+ dx:= 0.001 * (random(100)+10);
+ dy:= 0.001 * (random(100)+10);
+ tdy:= -cGravityf;
+ if random(2) = 0 then
+ dx := -dx;
+ FrameTicks:= random(500) + 1000;
+ State:= ord(sprBubbles);
+ Tint:= $ff0000ff
+ end
+ end
end else
- //else if Gear^.Kind <> gtStructure then // not gtHedgehog nor gtStructure
Gear^.Hedgehog:= AttackerHog;
inc(Gear^.Damage, Damage);
@@ -344,11 +355,17 @@
end;
// Play effects for hurt hedgehog
-procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource);
+procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource; Damage: Longword);
begin
if Hedgehog^.Effects[heFrozen] <> 0 then exit;
-if (Source = dsFall) or (Source = dsExplosion) then
+if (Damage >= ouchDmg) and (OuchTauntTimer = 0) and ((Source = dsFall) or (Source = dsBullet) or (Source = dsShove) or (Source = dsHammer)) then
+ begin
+ PlaySoundV(sndOuch, Hedgehog^.Team^.voicepack);
+ // Prevent sndOuch from being played too often in short time
+ OuchTauntTimer:= 1250;
+ end
+else if (Source = dsFall) or (Source = dsExplosion) then
case random(3) of
0: PlaySoundV(sndOoff1, Hedgehog^.Team^.voicepack);
1: PlaySoundV(sndOoff2, Hedgehog^.Team^.voicepack);
@@ -373,9 +390,9 @@
Hedgehog: Hedgehog which gets the health boost
healthBoost: Amount of added health added
showMessage: Whether to show announcer message
-vgTint: Tint of heal particle
+vgTint: Tint of heal particle (if 0, don't render particles)
-}
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean; vgTint: Longword);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean; vgTint: Longword);
var i: LongInt;
vg: PVisualGear;
s: ansistring;
@@ -391,25 +408,45 @@
i:= 0;
// One particle for every 5 HP. Max. 200 particles
- while (i < healthBoost) and (i < 1000) do
- begin
- vg:= AddVisualGear(hwRound(Hedgehog^.Gear^.X), hwRound(Hedgehog^.Gear^.Y), vgtStraightShot);
- if vg <> nil then
- with vg^ do
- begin
- Tint:= vgTint;
- State:= ord(sprHealth)
- end;
- inc(i, 5)
- end;
+ if (vgTint <> 0) then
+ while (i < healthBoost) and (i < 1000) do
+ begin
+ vg:= AddVisualGear(hwRound(Hedgehog^.Gear^.X), hwRound(Hedgehog^.Gear^.Y), vgtStraightShot);
+ if vg <> nil then
+ with vg^ do
+ begin
+ Tint:= vgTint;
+ State:= ord(sprHealth)
+ end;
+ inc(i, 5)
+ end;
end;
// Shorthand for the same above, but with tint implied
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean);
begin
HHHeal(Hedgehog, healthBoost, showMessage, $00FF00FF);
end;
+// Increase hog health by healthBoost (at least 1).
+// Resulting health is capped at cMaxHogHealth.
+// Returns actual amount healed.
+function IncHogHealth(Hedgehog: PHedgehog; healthBoost: LongInt): LongInt;
+var oldHealth: LongInt;
+begin
+ if healthBoost < 1 then
+ begin
+ IncHogHealth:= 0;
+ exit;
+ end;
+ oldHealth:= Hedgehog^.Gear^.Health;
+ inc(Hedgehog^.Gear^.Health, healthBoost);
+ // Prevent overflow
+ if (Hedgehog^.Gear^.Health < 1) or (Hedgehog^.Gear^.Health > cMaxHogHealth) then
+ Hedgehog^.Gear^.Health:= cMaxHogHealth;
+ IncHogHealth:= Hedgehog^.Gear^.Health - oldHealth;
+end;
+
procedure CheckHHDamage(Gear: PGear);
var
dmg: LongInt;
@@ -435,11 +472,6 @@
if ((Gear^.Hedgehog^.Effects[heInvulnerable] <> 0)) then
exit;
- //if _0_6 < Gear^.dY then
- // PlaySound(sndOw4, Gear^.Hedgehog^.Team^.voicepack)
- //else
- // PlaySound(sndOw1, Gear^.Hedgehog^.Team^.voicepack);
-
if Gear^.LastDamage <> nil then
ApplyDamage(Gear, Gear^.LastDamage, dmg, dsFall)
else
@@ -453,7 +485,6 @@
dAngle: real;
begin
// Frac/Round to be kind to JS as of 2012-08-27 where there is yet no int64/uint64
- //dAngle := (Gear^.dX.QWordValue + Gear^.dY.QWordValue) / $80000000;
dAngle := (Gear^.dX.Round + Gear^.dY.Round) / 2 + (Gear^.dX.Frac/$100000000+Gear^.dY.Frac/$100000000);
if not Gear^.dX.isNegative then
Gear^.DirAngle := Gear^.DirAngle + dAngle
@@ -555,6 +586,13 @@
else Scale:= Scale + ((1-Scale) / 2);
if Scale > 1 then Timer:= round(min(Scale*0.0005/cGravityf,4))
else Timer:= 1;
+ if Scale > 1 then
+ if (not isImpactH) then
+ Y:= Y + 10
+ else if isImpactRight then
+ X:= X + 10
+ else
+ X:= X - 10;
// Low Gravity
FrameTicks:= FrameTicks*Timer;
end;
@@ -631,7 +669,7 @@
if WorldEdge = weSea then
begin
tmp:= dist2Water;
- dist2Water:= min(dist2Water, min(X - Gear^.Radius - LongInt(leftX), LongInt(rightX) - (X + Gear^.Radius)));
+ dist2Water:= min(dist2Water, min(X - Gear^.Radius - leftX, rightX - (X + Gear^.Radius)));
// if water on sides is closer than on bottom -> horizontal direction
isDirH:= tmp <> dist2Water;
end;
@@ -711,9 +749,9 @@
Gear^.State := Gear^.State and (not gstHHDriven);
s:= ansistring(Gear^.Hedgehog^.Name);
if Gear^.Hedgehog^.King then
- AddCaption(FormatA(GetEventString(eidKingDied), s), cWhiteColor, capgrpMessage)
+ AddCaption(FormatA(GetEventString(eidKingDied), s), capcolDefault, capgrpMessage)
else
- AddCaption(FormatA(GetEventString(eidDrowned), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidDrowned), s), capcolDefault, capgrpMessage);
end
end
else
@@ -723,7 +761,7 @@
end
else // submersible
begin
- // drown submersible grears if far below map
+ // drown submersible gears if far below map
if (Y > cWaterLine + cVisibleWater*4) then
begin
DrownGear(Gear);
@@ -762,7 +800,8 @@
// splash sound animation and droplets
if isImpact or isSkip then
- addSplashForGear(Gear, isSkip);
+ if (not (((dist2Water + Gear^.Radius div 2) < 0) or (abs(dist2Water + Gear^.Radius) >= Gear^.Radius))) then
+ addSplashForGear(Gear, isSkip);
if isSkip then
ScriptCall('onGearWaterSkip', Gear^.uid);
@@ -774,13 +813,14 @@
procedure ResurrectHedgehog(var gear: PGear);
var tempTeam : PTeam;
- sparkles: PVisualGear;
+ sparkles, expl: PVisualGear;
gX, gY: LongInt;
begin
if (Gear^.LastDamage <> nil) then
uStats.HedgehogDamaged(Gear, Gear^.LastDamage, 0, true)
else
uStats.HedgehogDamaged(Gear, CurrentHedgehog, 0, true);
+ // Reset gear state
AttackBar:= 0;
gear^.dX := _0;
gear^.dY := _0;
@@ -799,20 +839,25 @@
DeleteCI(gear);
gX := hwRound(gear^.X);
gY := hwRound(gear^.Y);
- // might need more sparkles for a column
+ // Spawn a few sparkles at death position.
+ // Might need more sparkles for a column.
sparkles:= AddVisualGear(gX, gY, vgtDust, 1);
if sparkles <> nil then
begin
sparkles^.Tint:= tempTeam^.Clan^.Color shl 8 or $FF;
- //sparkles^.Angle:= random(360);
end;
+ // Set new position of gear (might fail)
FindPlace(gear, false, 0, LAND_WIDTH, true);
if gear <> nil then
begin
- AddVisualGear(hwRound(gear^.X), hwRound(gear^.Y), vgtExplosion);
+ // Visual effect at position of resurrection
+ expl:= AddVisualGear(hwRound(gear^.X), hwRound(gear^.Y), vgtExplosion);
PlaySound(sndWarp);
RenderHealth(gear^.Hedgehog^);
- ScriptCall('onGearResurrect', gear^.uid);
+ if expl <> nil then
+ ScriptCall('onGearResurrect', gear^.uid, expl^.uid)
+ else
+ ScriptCall('onGearResurrect', gear^.uid);
gear^.State := gstWait;
end;
RecountTeamHealth(tempTeam);
@@ -896,7 +941,7 @@
tryAgain:= true;
if WorldEdge <> weNone then
begin
- Left:= max(Left, LongInt(leftX) + Gear^.Radius);
+ Left:= max(Left, leftX + Gear^.Radius);
Right:= min(Right,rightX-Gear^.Radius)
end;
while tryAgain do
@@ -1025,28 +1070,92 @@
end
end;
-function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;
+function CheckGearNearImpl(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt; exclude: PGear): PGear;
var t: PGear;
+ width, bound, dX, dY: hwFloat;
+ isHit: Boolean;
+ i, j: LongWord;
begin
-t:= GearsList;
-rX:= sqr(rX);
-rY:= sqr(rY);
+ bound:= _1_5 * int2hwFloat(max(rX, rY));
+ rX:= sqr(rX);
+ rY:= sqr(rY);
+ width:= int2hwFloat(RightX - LeftX);
+ if (Kind = gtHedgehog) then
+ begin
+ for j:= 0 to Pred(TeamsCount) do
+ if TeamsArray[j]^.TeamHealth > 0 then // it's impossible for a team to have hogs in game and zero health right?
+ with TeamsArray[j]^ do
+ for i:= 0 to cMaxHHIndex do
+ with Hedgehogs[i] do
+ if (Gear <> nil) and (Gear <> exclude) then
+ begin
+ // code duplication - could throw into an inline function I guess
+ dX := X - Gear^.X;
+ dY := Y - Gear^.Y;
+ isHit := (hwAbs(dX) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX) / rX + hwSqr(dY) / rY) > _1));
+
+ if (not isHit) and (WorldEdge = weWrap) then
+ begin
+ if (hwAbs(dX - width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX - width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ else if (hwAbs(dX + width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX + width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ end;
-while t <> nil do
- begin
- if (t <> Gear) and (t^.Kind = Kind) then
- if (not ((hwSqr(Gear^.X - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1)) or
- ((WorldEdge = weWrap) and (
- (not ((hwSqr(Gear^.X - int2hwFloat(RightX-LeftX) - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1)) or
- (not ((hwSqr(Gear^.X + int2hwFloat(RightX-LeftX) - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1)))) then
+ if isHit then
+ begin
+ CheckGearNearImpl:= Gear;
+ exit;
+ end
+ end;
+ end
+ else
begin
- CheckGearNear:= t;
- exit;
- end;
- t:= t^.NextGear
+ t:= GearsList;
+
+ while t <> nil do
+ begin
+ if (t <> exclude) and (t^.Kind = Kind) then
+ begin
+ dX := X - t^.X;
+ dY := Y - t^.Y;
+ isHit := (hwAbs(dX) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX) / rX + hwSqr(dY) / rY) > _1));
+
+ if (not isHit) and (WorldEdge = weWrap) then
+ begin
+ if (hwAbs(dX - width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX - width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ else if (hwAbs(dX + width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX + width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ end;
+
+ if isHit then
+ begin
+ CheckGearNearImpl:= t;
+ exit;
+ end;
+ end;
+ t:= t^.NextGear
+ end
end;
-CheckGearNear:= nil
+ CheckGearNearImpl:= nil
+end;
+
+function CheckGearNear(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt): PGear;
+begin
+ CheckGearNear := CheckGearNearImpl(Kind, X, Y, rX, rY, nil);
+end;
+
+function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;
+begin
+ CheckGearNear := CheckGearNearImpl(Kind, Gear^.X, Gear^.Y, rX, rY, Gear);
end;
procedure CheckCollision(Gear: PGear); inline;
@@ -1150,11 +1259,11 @@
gtHedgehog,
gtMine,
gtSMine,
+ gtAirMine,
gtKnife,
gtCase,
gtTarget,
- gtExplosives: begin//,
-// gtStructure: begin
+ gtExplosives: begin
//addFileLog('ShotgunShot radius: ' + inttostr(Gear^.Radius) + ', t^.Radius = ' + inttostr(t^.Radius) + ', distance = ' + inttostr(dist) + ', dmg = ' + inttostr(dmg));
dmg:= 0;
r:= Gear^.Radius + t^.Radius;
@@ -1180,7 +1289,13 @@
t^.State:= t^.State or gstMoving;
if t^.Kind = gtKnife then t^.State:= t^.State and (not gstCollision);
t^.Active:= true;
- FollowGear:= t
+ FollowGear:= t;
+
+ if t^.Kind = gtAirmine then
+ begin
+ t^.Tag:= 1;
+ t^.FlightTime:= 5000;
+ end
end
end;
gtGrave: begin
@@ -1262,8 +1377,10 @@
begin
dec(i);
Gear:= t^.ar[i];
- if (Ammo^.Data <> nil) and (Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet]) and (PGear(Ammo^.Data) = Gear)
- or ((Ammo^.Kind = gtMinigunBullet) and (not UpdateHitOrder(Gear, Ammo^.WDTimer))) then
+ if (Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet,
+ gtFirePunch, gtKamikaze, gtWhip, gtShover])
+ and (((Ammo^.Data <> nil) and (PGear(Ammo^.Data) = Gear))
+ or (not UpdateHitOrder(Gear, Ammo^.WDTimer))) then
continue;
if ((Ammo^.Kind = gtFlame) or (Ammo^.Kind = gtBlowTorch)) and
@@ -1273,30 +1390,28 @@
if (Gear^.State and gstNoDamage) = 0 then
begin
- if (Gear^.Kind <> gtMinigun) and
- ((Ammo^.Kind = gtDEagleShot)
- or (Ammo^.Kind = gtSniperRifleShot)
- or (Ammo^.Kind = gtMinigunBullet)) then
- begin
- VGear := AddVisualGear(t^.cX[i], t^.cY[i], vgtBulletHit);
- if VGear <> nil then
- VGear^.Angle := DxDy2Angle(-Ammo^.dX, Ammo^.dY);
- end;
-
if (Gear^.Kind = gtHedgehog) and (Ammo^.State and gsttmpFlag <> 0) and (Ammo^.Kind = gtShover) then
Gear^.FlightTime:= 1;
-
case Gear^.Kind of
gtHedgehog,
gtMine,
+ gtAirMine,
gtSMine,
gtKnife,
gtTarget,
gtCase,
- gtExplosives: //,
- //gtStructure:
+ gtExplosives:
begin
+ if (Ammo^.Kind in [gtFirePunch, gtKamikaze]) and (Gear^.Kind <> gtSMine) then
+ PlaySound(sndFirePunchHit);
+
+ if Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet] then
+ begin
+ VGear := AddVisualGear(t^.cX[i], t^.cY[i], vgtBulletHit);
+ if VGear <> nil then
+ VGear^.Angle := DxDy2Angle(-Ammo^.dX, Ammo^.dY);
+ end;
if (Ammo^.Kind = gtDrill) then
begin
Ammo^.Timer:= 0;
@@ -1326,7 +1441,13 @@
State:= ord(sprStar)
end
end;
- ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg, dsShove)
+ ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg, dsShove);
+
+ if Gear^.Kind = gtAirmine then
+ begin
+ Gear^.Tag:= 1;
+ Gear^.FlightTime:= 5000;
+ end
end
else
Gear^.State:= Gear^.State or gstWinner;
@@ -1334,18 +1455,18 @@
begin
if (Ammo^.Hedgehog^.Gear <> nil) then
Ammo^.Hedgehog^.Gear^.State:= Ammo^.Hedgehog^.Gear^.State and (not gstNotKickable);
- ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg * 100, dsUnknown); // crank up damage for explosives + blowtorch
+ ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg * 100, dsExplosion); // crank up damage for explosives + blowtorch
end;
if (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog^.King or (Gear^.Hedgehog^.Effects[heFrozen] > 0)) then
begin
- Gear^.dX:= Gear^.dX + Ammo^.dX * Power * _0_005;
- Gear^.dY:= Gear^.dY + Ammo^.dY * Power * _0_005
+ Gear^.dX:= Ammo^.dX * Power * _0_005;
+ Gear^.dY:= Ammo^.dY * Power * _0_005
end
else if ((Ammo^.Kind <> gtFlame) or (Gear^.Kind = gtHedgehog)) and (Power <> 0) then
begin
- Gear^.dX:= Gear^.dX + Ammo^.dX * Power * _0_01;
- Gear^.dY:= Gear^.dY + Ammo^.dY * Power * _0_01
+ Gear^.dX:= Ammo^.dX * Power * _0_01;
+ Gear^.dY:= Ammo^.dY * Power * _0_01
end;
if (not isZero(Gear^.dX)) or (not isZero(Gear^.dY)) then
@@ -1383,10 +1504,7 @@
procedure AmmoShoveLine(Ammo: PGear; Damage, Power: LongInt; oX, oY, tX, tY: hwFloat);
var t: PGearArray;
begin
- if Ammo^.Kind = gtMinigunBullet then
- t:= CheckAllGearsLineCollision(Ammo, oX, oY, tX, tY)
- else
- t:= CheckGearsLineCollision(Ammo, oX, oY, tX, tY);
+ t:= CheckAllGearsLineCollision(Ammo, oX, oY, tX, tY);
AmmoShoveImpl(Ammo, Damage, Power, t);
end;
@@ -1396,6 +1514,11 @@
CheckGearsCollision(Ammo));
end;
+procedure AmmoShoveCache(Ammo: PGear; Damage, Power: LongInt);
+begin
+ AmmoShoveImpl(Ammo, Damage, Power,
+ CheckCacheCollision(Ammo));
+end;
function CountGears(Kind: TGearType): Longword;
var t: PGear;
@@ -1481,14 +1604,14 @@
GearsNear.ar:= @GearsNearArray
end;
-
-procedure SpawnBoxOfSmth;
+function SpawnBoxOfSmth: PGear;
var t, aTot, uTot, a, h: LongInt;
i: TAmmoType;
begin
-if (PlacingHogs) or
+SpawnBoxOfSmth:= nil;
+if (PlacingHogs) or (PlacingKings) or
(cCaseFactor = 0)
- or (CountGears(gtCase) >= 5)
+ or (CountGears(gtCase) >= cMaxCaseDrops)
or (GetRandom(cCaseFactor) <> 0) then
exit;
@@ -1522,11 +1645,12 @@
if t nil) then
begin
FindPlace(FollowGear, true, 0, LAND_WIDTH);
+ PlayBoxSpawnTaunt(FollowGear);
+ SpawnBoxOfSmth:= FollowGear;
+ end
+end;
- if (FollowGear <> nil) then
- AddVoice(sndReinforce, CurrentTeam^.voicepack)
- end
+procedure PlayBoxSpawnTaunt(Gear: PGear);
+const
+ // Max. distance between hog and crate for sndThisOneIsMine taunt
+ ThisOneIsMineDistance : LongInt = 130;
+var d, minD: LongInt;
+ gi, closestHog: PGear;
+begin
+ // Taunt
+ if (Gear <> nil) then
+ begin
+ // Look for hog closest to the crate (on the X axis)
+ gi := GearsList;
+ minD := LAND_WIDTH + ThisOneIsMineDistance + 1;
+ closestHog:= nil;
+ while gi <> nil do
+ begin
+ if (gi^.Kind = gtHedgehog) then
+ begin
+ // Y axis is ignored to simplify calculations
+ d := hwRound(hwAbs(gi^.X - Gear^.X));
+ if d < minD then
+ begin
+ minD := d;
+ closestHog:= gi;
+ end;
+ end;
+ gi := gi^.NextGear;
+ end;
+
+ // Is closest hog close enough to the crate (on the X axis)?
+ if (closestHog <> nil) and (closestHog^.Hedgehog <> nil) and (minD <= ThisOneIsMineDistance) then
+ // If so, there's a chance for a special taunt
+ if random(3) > 0 then
+ AddVoice(sndThisOneIsMine, closestHog^.Hedgehog^.Team^.voicepack)
+ else
+ AddVoice(sndReinforce, CurrentTeam^.voicepack)
+ else
+ // Default crate drop taunt
+ AddVoice(sndReinforce, CurrentTeam^.voicepack);
+ end;
end;
@@ -1629,74 +1794,117 @@
Trying to make the checks a little broader than on first pass to catch things that don't move normally.
*)
function WorldWrap(var Gear: PGear): boolean;
-//var tdx: hwFloat;
+var bounced: boolean;
begin
WorldWrap:= false;
if WorldEdge = weNone then exit(false);
-if (hwRound(Gear^.X) < LongInt(leftX)) or
- (hwRound(Gear^.X) > LongInt(rightX)) then
+if (hwRound(Gear^.X) < leftX) or
+ (hwRound(Gear^.X) > rightX) then
begin
if WorldEdge = weWrap then
begin
- if (hwRound(Gear^.X) < LongInt(leftX)) then
+ if (hwRound(Gear^.X) < leftX) then
Gear^.X:= Gear^.X + int2hwfloat(rightX - leftX)
else Gear^.X:= Gear^.X - int2hwfloat(rightX - leftX);
LeftImpactTimer:= 150;
- RightImpactTimer:= 150
+ RightImpactTimer:= 150;
+ WorldWrap:= true;
end
else if WorldEdge = weBounce then
begin
- if (hwRound(Gear^.X) - Gear^.Radius < LongInt(leftX)) then
+ bounced:= false;
+ // Bounce left
+ if (hwRound(Gear^.X) - Gear^.Radius < leftX) and (((hwSign(Gear^.dX) = -1) and (not isZero(Gear^.dX))) or (Gear^.Kind = gtHedgehog)) then
begin
LeftImpactTimer:= 333;
+ // Set X coordinate to bounce edge, unless the gear spawned inside the bounce edge before
+ if (Gear^.State and gstInBounceEdge) = 0 then
+ Gear^.X:= int2hwfloat(leftX + Gear^.Radius);
+ // Invert horizontal speed
Gear^.dX.isNegative:= false;
- Gear^.X:= int2hwfloat(LongInt(leftX) + Gear^.Radius)
+ bounced:= true;
end
- else
+ // Bounce right
+ else if (hwRound(Gear^.X) + Gear^.Radius > rightX) and (((hwSign(Gear^.dX) = 1) and (not isZero(Gear^.dX))) or (Gear^.Kind = gtHedgehog)) then
begin
RightImpactTimer:= 333;
+ // Set X coordinate to bounce edge, unless the gear spawned inside the bounce edge before
+ if (Gear^.State and gstInBounceEdge) = 0 then
+ Gear^.X:= int2hwfloat(rightX - Gear^.Radius);
+ // Invert horizontal speed
Gear^.dX.isNegative:= true;
- Gear^.X:= int2hwfloat(rightX-Gear^.Radius)
+ bounced:= true;
end;
- if (Gear^.Radius > 2) and (Gear^.dX.QWordValue > _0_001.QWordValue) then
- AddBounceEffectForGear(Gear);
- end{
- else if WorldEdge = weSea then
- begin
- if (hwRound(Gear^.Y) > cWaterLine) and (Gear^.State and gstSubmersible <> 0) then
- Gear^.State:= Gear^.State and (not gstSubmersible)
- else
+ // Clear gstInBounceEdge when gear is no longer inside a bounce edge area
+ if ((Gear^.State and gstInBounceEdge) <> 0) and (hwRound(Gear^.X) - Gear^.Radius >= leftX) and (hwRound(Gear^.X) + Gear^.Radius <= rightX) then
+ Gear^.State:= Gear^.State and (not gstInBounceEdge);
+ if (bounced) then
begin
- Gear^.State:= Gear^.State or gstSubmersible;
- Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight;
- Gear^.Y:= int2hwFloat(cWaterLine+cVisibleWater+Gear^.Radius*2);
- tdx:= Gear^.dX;
- Gear^.dX:= -Gear^.dY;
- Gear^.dY:= tdx;
- Gear^.dY.isNegative:= true
- end
- end};
-(*
-* Window in the sky (Gear moved high into the sky, Y is used to determine X) [unfortunately, not a safe thing to do. shame, I thought aerial bombardment would be kinda neat
-This one would be really easy to freeze game unless it was flagged unfortunately.
-
+ WorldWrap:= true;
+ if (Gear^.dX.QWordValue > _0_001.QWordValue) then
+ AddBounceEffectForGear(Gear);
+ end;
+ end
else
- begin
- Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight;
- Gear^.Y:= -_2048-_256-_256;
- tdx:= Gear^.dX;
- Gear^.dX:= Gear^.dY;
- Gear^.dY:= tdx;
- Gear^.dY.isNegative:= false
- end
-*)
- WorldWrap:= true
+ WorldWrap:= true;
end;
end;
+(*
+Applies wrap-around logic for the target of homing gears.
+
+In wrap-around world edge, the shortest way may to the target might
+be across the border, so the X value of the target would lead the
+gear to the wrong direction across the whole map. This procedure
+changes the target X in this case.
+This function must be called after the gear passed through
+the wrap-around world edge (WorldWrap returned true).
+
+No-op for other world edges.
+
+Returns true if target has been changed.
+*)
+function HomingWrap(var Gear: PGear): boolean;
+var dist_center, dist_right, dist_left: hwFloat;
+begin
+ if WorldEdge = weWrap then
+ begin
+ HomingWrap:= false;
+ // We just check the same target 3 times:
+ // 1) in current section (no change)
+ // 2) clone in the right section
+ // 3) clone in the left section
+ // The gear will go for the target with the shortest distance to the gear.
+ // For simplicity, we only check distance on the X axis.
+ dist_center:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X));
+ dist_right:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X + (RightX-LeftX)));
+ dist_left:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X - (RightX-LeftX)));
+ if (dist_left < dist_right) and (dist_left < dist_center) then
+ begin
+ dec(Gear^.Target.X, RightX-LeftX);
+ HomingWrap:= true;
+ end
+ else if (dist_right < dist_left) and (dist_right < dist_center) then
+ begin
+ inc(Gear^.Target.X, RightX-LeftX);
+ HomingWrap:= true;
+ end;
+ end;
+end;
+
+// Add an audiovisual bounce effect for gear after it bounced from bouncy material.
+// Graphical effect is based on speed.
procedure AddBounceEffectForGear(Gear: PGear);
+begin
+ AddBounceEffectForGear(Gear, hwFloat2Float(Gear^.Density * hwAbs(Gear^.dY) + hwAbs(Gear^.dX)) / 1.5);
+end;
+
+// Same as above, but can specify the size of bounce image with imageScale manually.
+procedure AddBounceEffectForGear(Gear: PGear; imageScale: Single);
var boing: PVisualGear;
begin
+ if (Gear^.Density < _0_01) or (Gear^.Radius < 2) then
+ exit;
boing:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot, 0, false, 1);
if boing <> nil then
with boing^ do
@@ -1705,18 +1913,27 @@
dx:= 0;
dy:= 0;
FrameTicks:= 200;
- Scale:= hwFloat2Float(Gear^.Density * hwAbs(Gear^.dY) + hwAbs(Gear^.dX)) / 1.5;
+ Scale:= imageScale;
State:= ord(sprBoing)
end;
- if Gear^.Kind = gtDuck then
- PlaySound(sndDuckDrop, true)
+ PlaySound(sndMelonImpact, true)
+end;
+
+function IsHogFacingLeft(Gear: PGear): boolean;
+var sign: LongInt;
+begin
+ sign:= hwSign(Gear^.dX);
+ if (CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtParachute) then
+ IsHogFacingLeft:= CurAmmoGear^.Tag = -1
+ else if ((Gear^.State and gstHHHJump) <> 0) and (Gear^.Hedgehog^.Effects[heArtillery] = 0) then
+ IsHogFacingLeft:= sign > 0
else
- PlaySound(sndMelonImpact, true)
+ IsHogFacingLeft:= sign < 0;
end;
function IsHogLocal(HH: PHedgehog): boolean;
begin
- IsHogLocal:= (not (HH^.Team^.ExtDriven or (HH^.BotLevel > 0))) or (HH^.Team^.Clan^.ClanIndex = LocalClan) or (GameType = gmtDemo);
+ IsHogLocal:= (not (HH^.Team^.ExtDriven or (HH^.BotLevel > 0))) or (HH^.Team^.Clan^.LocalOrAlly) or (GameType = gmtDemo);
end;
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uIO.pas
--- a/hedgewars/uIO.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uIO.pas Wed Jul 31 23:14:27 2019 +0200
@@ -39,7 +39,7 @@
procedure doPut(putX, putY: LongInt; fromAI: boolean);
implementation
-uses uConsole, uConsts, uVariables, uCommands, uUtils, uDebug;
+uses uConsole, uConsts, uVariables, uCommands, uUtils, uDebug, uLocale, uSound;
const
cSendEmptyPacketTime = 1000;
@@ -147,6 +147,8 @@
procedure ParseIPCCommand(s: shortstring);
var loTicks: Word;
isProcessed: boolean;
+ nick, msg: shortstring;
+ i: LongInt;
begin
isProcessed := true;
@@ -169,13 +171,40 @@
if s[2] = '.' then
ParseCommand('campvar ' + copy(s, 3, length(s) - 2), true);
end;
+ 'v': begin
+ if s[2] = '.' then
+ ParseCommand('missvar ' + copy(s, 3, length(s) - 2), true);
+ end;
'I': ParseCommand('pause server', true);
's': if gameType = gmtNet then
ParseChatCommand('chatmsg ', s, 2)
else
isProcessed:= false;
'b': if gameType = gmtNet then
- ParseChatCommand('chatmsg ' + #4, s, 2)
+ // parse team message from net
+ // expected format: ]
+ begin
+ i:= 2;
+ nick:= '';
+ while (i <= length(s)) and (s[i] <> ']') do
+ begin
+ nick:= nick + s[i];
+ inc(i)
+ end;
+
+ inc(i);
+ msg:= '';
+ while (i <= length(s)) do
+ begin
+ msg:= msg + s[i];
+ inc(i)
+ end;
+ s:= 'b' + Format(shortstring(trmsg[sidChatTeam]), nick, msg);
+ if (nick = '') or (msg = '') then
+ isProcessed:= false
+ else
+ ParseChatCommand('chatmsg ' + #4, s, 2);
+ end
else
isProcessed:= false;
else
@@ -254,7 +283,7 @@
end;
procedure SendStat(sit: TStatInfoType; s: shortstring);
-const stc: array [TStatInfoType] of char = ('r', 'D', 'k', 'K', 'H', 'T', 'P', 's', 'S', 'B', 'c', 'g', 'p');
+const stc: array [TStatInfoType] of char = ('r', 'D', 'k', 'K', 'H', 'T', 'P', 's', 'S', 'B', 'c', 'g', 'p', 'R', 'h');
var buf: shortstring;
begin
buf:= 'i' + stc[sit] + s;
@@ -483,6 +512,7 @@
with CurrentHedgehog^.Gear^,
CurrentHedgehog^ do
if (State and gstChooseTarget) <> 0 then
+ if ((((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackInMove) <> 0) or ((State and gstMoving) = 0)) or ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackingPut) = 0)) then
begin
if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoTargetAfter) <> 0 then
isCursorVisible:= false;
@@ -498,13 +528,14 @@
TargetPoint.X:= CursorPoint.X - WorldDx;
TargetPoint.Y:= cScreenHeight - CursorPoint.Y - WorldDy;
end;
- if (WorldEdge <> weBounce) then
+ if (WorldEdge <> weBounce) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoWrapTarget) = 0) then
TargetPoint.X:= CalcWorldWrap(TargetPoint.X, 0);
SendIPCXY('p', TargetPoint.X, TargetPoint.Y);
end
else
begin
- TargetPoint.X:= CalcWorldWrap(TargetPoint.X, 0);
+ if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoWrapTarget) = 0 then
+ TargetPoint.X:= CalcWorldWrap(TargetPoint.X, 0);
TargetPoint.X:= putX;
TargetPoint.Y:= putY
end;
@@ -512,10 +543,13 @@
State:= State and (not gstChooseTarget);
if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackingPut) <> 0 then
Message:= Message or (gmAttack and InputMask);
+ Message:= Message and (not (gmHJump or gmLJump or gmLeft or gmRight or gmUp or gmDown));
end
+ else
+ PlaySound(sndDenied)
else
if CurrentTeam^.ExtDriven then
- OutError('got /put while not being in choose target mode', false)
+ OutError('Got /put while not being in choose target mode', false)
end;
procedure initModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uInputHandler.pas
--- a/hedgewars/uInputHandler.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uInputHandler.pas Wed Jul 31 23:14:27 2019 +0200
@@ -32,12 +32,17 @@
function KeyBindToName(bind: shortstring): shortstring;
//procedure MaskModifier(var code: LongInt; modifier: LongWord);
procedure MaskModifier(Modifier: shortstring; var code: LongInt);
-procedure ProcessMouse(event: TSDL_MouseButtonEvent; ButtonDown: boolean);
+procedure ProcessMouseButton(event: TSDL_MouseButtonEvent; ButtonDown: boolean);
+procedure ProcessMouseMotion(xrel, yrel: LongInt);
//procedure ProcessMouseWheel(x, y: LongInt);
procedure ProcessMouseWheel(y: LongInt);
procedure ProcessKey(event: TSDL_KeyboardEvent); inline;
procedure ProcessKey(code: LongInt; KeyDown: boolean);
+{$IFDEF USE_AM_NUMCOLUMN}
+function CheckDefaultSlotKeys: boolean;
+{$ENDIF}
+
procedure ResetKbd;
procedure ResetMouseWheel;
procedure FreezeEnterKey;
@@ -55,7 +60,7 @@
procedure ControllerButtonEvent(joy, button: Byte; pressed: Boolean);
implementation
-uses uConsole, uCommands, uVariables, uConsts, uUtils, uDebug, uPhysFSLayer;
+uses uKeyNames, uConsole, uCommands, uVariables, uConsts, uUtils, uDebug, uPhysFSLayer, uCursor;
const
LSHIFT = $0200;
@@ -66,7 +71,7 @@
RCTRL = $4000;
var tkbd: array[0..cKbdMaxIndex] of boolean;
- KeyNames: array [0..cKeyMaxIndex] of string[15];
+ KeyNames: TKeyNames;
CurrentBinds: TBinds;
ControllerNumControllers: Integer;
ControllerEnabled: Integer;
@@ -109,7 +114,7 @@
else begin
code:= 0;
while (code <= High(CurrentBinds.indices)) and (CurrentBinds.indices[code] <> index) do inc(code);
- checkFails(code <= High(CurrentBinds.indices), 'binds registry inconsistency', True);
+ checkFails(code <= High(CurrentBinds.indices), 'Inconsistency in key binding registry', True);
KeyBindToCode:= code;
end;
end;
@@ -130,7 +135,7 @@
begin
name:= SDL_GetKeyName(SDL_GetKeyFromScancode(code));
if (name = 'Escape') then
- // Let's shorten the name “Escape” for the quit menu
+ // Let's shorten the name "Escape" for the quit menu
KeyBindToName:= 'Esc'
else if (length(name) <> 0) then
KeyBindToName:= name
@@ -182,7 +187,8 @@
procedure ProcessKey(code: LongInt; KeyDown: boolean);
var
Trusted: boolean;
- s : string;
+ curBind, s: shortstring;
+ readyAborter: boolean;
begin
if not(tkbd[code] xor KeyDown) then exit;
tkbd[code]:= KeyDown;
@@ -222,44 +228,63 @@
if CurrentBinds.indices[code] > 0 then
begin
+ curBind:= CurrentBinds.binds[CurrentBinds.indices[code]];
+
+ // Check if the keypress should end the ready phase.
+ // Camera movement keys are "safe" since its equivalent to moving the mouse,
+ // which also does not end the ready phase.
+ readyAborter:= (curBind <> '+cur_u') and (curBind <> '+cur_d') and (curBind <> '+cur_l') and (curBind <> '+cur_r');
+
if (code < cKeyMaxIndex - 2) // means not mouse buttons
and KeyDown
- and (not ((CurrentBinds.binds[CurrentBinds.indices[code]] = 'put')
- or (CurrentBinds.binds[CurrentBinds.indices[code]] = 'ammomenu')
- or (CurrentBinds.binds[CurrentBinds.indices[code]] = '+cur_u')
- or (CurrentBinds.binds[CurrentBinds.indices[code]] = '+cur_d')
- or (CurrentBinds.binds[CurrentBinds.indices[code]] = '+cur_l')
- or (CurrentBinds.binds[CurrentBinds.indices[code]] = '+cur_r')))
- and (CurrentTeam <> nil)
- and (not CurrentTeam^.ExtDriven)
+ and (not ((curBind = 'put')
+ or (curBind = 'ammomenu')
+ or (curBind = '+cur_u')
+ or (curBind = '+cur_d')
+ or (curBind = '+cur_l')
+ or (curBind = '+cur_r')))
+ and (CurrentTeam <> nil)
+ and (not CurrentTeam^.ExtDriven)
then bShowAmmoMenu:= false;
if KeyDown then
begin
Trusted:= Trusted and (not isPaused); //releasing keys during pause should be allowed on the other hand
- if CurrentBinds.binds[CurrentBinds.indices[code]] = 'switch' then
+ if curBind = 'switch' then
LocalMessage:= LocalMessage or gmSwitch
- else if CurrentBinds.binds[CurrentBinds.indices[code]] = '+precise' then
+ else if curBind = '+precise' then
+ begin
LocalMessage:= LocalMessage or gmPrecise;
+ updateVolumeDelta(true);
+ updateCursorMovementDelta(true, CursorMovementX, CursorMovementX);
+ updateCursorMovementDelta(true, CursorMovementY, CursorMovementY);
+ end;
- ParseCommand(CurrentBinds.binds[CurrentBinds.indices[code]], Trusted);
- if (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) and (ReadyTimeLeft > 1) then
+ ParseCommand(curBind, Trusted);
+ // End ready phase
+ if (readyAborter) and (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) and (ReadyTimeLeft > 1) then
ParseCommand('gencmd R', true)
end
- else if (CurrentBinds.binds[CurrentBinds.indices[code]][1] = '+') then
+ else if (curBind[1] = '+') then
begin
- if CurrentBinds.binds[CurrentBinds.indices[code]] = '+precise' then
+ if curBind = '+precise' then
+ begin
LocalMessage:= LocalMessage and (not gmPrecise);
- s:= CurrentBinds.binds[CurrentBinds.indices[code]];
+ updateVolumeDelta(false);
+ updateCursorMovementDelta(false, CursorMovementX, CursorMovementX);
+ updateCursorMovementDelta(false, CursorMovementY, CursorMovementY);
+ end;
+ s:= curBind;
s[1]:= '-';
ParseCommand(s, Trusted);
- if (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) and (ReadyTimeLeft > 1) then
+ // End ready phase
+ if (readyAborter) and (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) and (ReadyTimeLeft > 1) then
ParseCommand('gencmd R', true)
end
else
begin
- if CurrentBinds.binds[CurrentBinds.indices[code]] = 'switch' then
+ if curBind = 'switch' then
LocalMessage:= LocalMessage and (not gmSwitch)
end
end
@@ -274,7 +299,7 @@
ProcessKey(code, event.type_ = SDL_KEYDOWN);
end;
-procedure ProcessMouse(event: TSDL_MouseButtonEvent; ButtonDown: boolean);
+procedure ProcessMouseButton(event: TSDL_MouseButtonEvent; ButtonDown: boolean);
begin
//writelntoconsole('[MOUSE] '+inttostr(event.button));
case event.button of
@@ -284,9 +309,18 @@
ProcessKey(KeyNameToCode('mousem'), ButtonDown);
SDL_BUTTON_RIGHT:
ProcessKey(KeyNameToCode('mouser'), ButtonDown);
+ SDL_BUTTON_X1:
+ ProcessKey(KeyNameToCode('mousex1'), ButtonDown);
+ SDL_BUTTON_X2:
+ ProcessKey(KeyNameToCode('mousex2'), ButtonDown);
end;
end;
+procedure ProcessMouseMotion(xrel, yrel: LongInt);
+begin
+ uCursor.updatePositionDelta(xrel, yrel);
+end;
+
var mwheelupCode, mwheeldownCode: Integer;
//procedure ProcessMouseWheel(x, y: LongInt);
@@ -333,7 +367,7 @@
procedure RegisterBind(var binds: TBinds; key, value: shortstring);
var code: LongInt;
begin
- checkFails(binds.lastIndex < 255, 'too many binds', true);
+ checkFails(binds.lastIndex < 255, 'Too many key bindings', true);
code:= KeyNameToCode(key);
@@ -357,10 +391,14 @@
RegisterBind(DefaultBinds, _S'`', 'history');
RegisterBind(DefaultBinds, 'delete', 'rotmask');
RegisterBind(DefaultBinds, 'home', 'rottags');
+ RegisterBind(DefaultBinds, _S'm', '+mission');
+ RegisterBind(DefaultBinds, _S'o', 'gearinfo');
//numpad
- //DefaultBinds[265]:= '+volup';
- //DefaultBinds[256]:= '+voldown';
+ RegisterBind(DefaultBinds, 'keypad_8', '+cur_u');
+ RegisterBind(DefaultBinds, 'keypad_6', '+cur_r');
+ RegisterBind(DefaultBinds, 'keypad_4', '+cur_l');
+ RegisterBind(DefaultBinds, 'keypad_2', '+cur_d');
RegisterBind(DefaultBinds, _S'0', '+volup');
RegisterBind(DefaultBinds, _S'9', '+voldown');
@@ -369,8 +407,9 @@
RegisterBind(DefaultBinds, _S'r', 'record');
RegisterBind(DefaultBinds, _S'h', 'findhh');
RegisterBind(DefaultBinds, _S'p', 'pause');
- RegisterBind(DefaultBinds, _S's', '+speedup');
+ RegisterBind(DefaultBinds, _S'f', '+speedup');
RegisterBind(DefaultBinds, _S't', 'chat');
+ RegisterBind(DefaultBinds, _S'u', 'chat team');
RegisterBind(DefaultBinds, _S'y', 'confirm');
RegisterBind(DefaultBinds, 'mousem', 'zoomreset');
@@ -379,6 +418,9 @@
RegisterBind(DefaultBinds, 'f12', 'fullscr');
+ for i:= 1 to 10 do RegisterBind(DefaultBinds, 'f'+IntToStr(i), 'slot '+char(48+i));
+ for i:= 1 to 5 do RegisterBind(DefaultBinds, IntToStr(i), 'timer '+IntToStr(i));
+ RegisterBind(DefaultBinds, _S'n', 'timer_u');
RegisterBind(DefaultBinds, 'mousel', '/put');
RegisterBind(DefaultBinds, 'mouser', 'ammomenu');
@@ -392,40 +434,26 @@
RegisterBind(DefaultBinds, 'right', '+right');
RegisterBind(DefaultBinds, 'left_shift', '+precise');
-
- RegisterBind(DefaultBinds, 'j0a0u', '+left');
- RegisterBind(DefaultBinds, 'j0a0d', '+right');
- RegisterBind(DefaultBinds, 'j0a1u', '+up');
- RegisterBind(DefaultBinds, 'j0a1d', '+down');
- for i:= 1 to 10 do RegisterBind(DefaultBinds, 'f'+IntToStr(i), 'slot '+char(48+i));
- for i:= 1 to 5 do RegisterBind(DefaultBinds, IntToStr(i), 'timer '+IntToStr(i));
-
loadBinds('dbind', cPathz[ptConfig] + '/settings.ini');
end;
procedure InitKbdKeyTable;
-var i, j, k, t: LongInt;
- s: string[15];
+var i, j, k: LongInt;
begin
+ // Mouse buttons and mouse wheel
KeyNames[cKeyMaxIndex ]:= 'mousel';
KeyNames[cKeyMaxIndex - 1]:= 'mousem';
KeyNames[cKeyMaxIndex - 2]:= 'mouser';
- mwheelupCode:= cKeyMaxIndex - 3;
+ KeyNames[cKeyMaxIndex - 3]:= 'mousex1';
+ KeyNames[cKeyMaxIndex - 4]:= 'mousex2';
+ mwheelupCode:= cKeyMaxIndex - 5;
KeyNames[mwheelupCode]:= 'wheelup';
- mwheeldownCode:= cKeyMaxIndex - 4;
+ mwheeldownCode:= cKeyMaxIndex - 6;
KeyNames[mwheeldownCode]:= 'wheeldown';
- for i:= 0 to cKeyMaxIndex - 5 do
- begin
- s:= shortstring(SDL_GetScancodeName(TSDL_Scancode(i)));
-
- for t:= 1 to Length(s) do
- if s[t] = ' ' then
- s[t]:= '_';
- KeyNames[i]:= LowerCase(s)
- end;
-
+ // Keyboard keys
+ uKeyNames.populateKeyNames(KeyNames);
// get the size of keyboard array
SDL_GetKeyboardState(@k);
@@ -458,6 +486,27 @@
end;
+{$IFDEF USE_AM_NUMCOLUMN}
+function CheckDefaultSlotKeys: boolean;
+{$IFDEF USE_TOUCH_INTERFACE}
+begin
+ CheckDefaultSlotKeys:= false;
+{$ELSE}
+var i, code: LongInt;
+begin
+ for i:=1 to cMaxSlotIndex do
+ begin
+ code:= KeyNameToCode('f'+IntToStr(i));
+ if CurrentBinds.binds[CurrentBinds.indices[code]] <> 'slot '+char(i+48) then
+ begin
+ CheckDefaultSlotKeys:= false;
+ exit;
+ end;
+ end;
+ CheckDefaultSlotKeys:= true;
+{$ENDIF}
+end;
+{$ENDIF}
{$IFNDEF MOBILE}
procedure SetBinds(var binds: TBinds);
@@ -497,7 +546,7 @@
var j: Integer;
begin
ControllerEnabled:= 0;
-{$IFDEF IPHONE}
+{$IFDEF IPHONEOS}
exit; // joystick subsystem disabled on iPhone
{$ENDIF}
@@ -513,10 +562,10 @@
begin
for j:= 0 to pred(ControllerNumControllers) do
begin
- WriteLnToConsole('Using game controller: ' + shortstring(SDL_JoystickName(j)));
+ WriteLnToConsole('Game controller no. ' + IntToStr(j) + ', name "' + shortstring(SDL_JoystickNameForIndex(j)) + '":');
Controller[j]:= SDL_JoystickOpen(j);
if Controller[j] = nil then
- WriteLnToConsole('* Failed to open game controller!')
+ WriteLnToConsole('* Failed to open game controller no. ' + IntToStr(j) + '!')
else
begin
ControllerNumAxes[j]:= SDL_JoystickNumAxes(Controller[j]);
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uKeyNames.pas
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uKeyNames.pas Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,131 @@
+(*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2019 Andrey Korotaev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *)
+
+unit uKeyNames;
+interface
+uses uConsts;
+
+type TKeyNames = array [0..cKeyMaxIndex] of string[15];
+
+procedure populateKeyNames(var KeyArray: TKeyNames);
+// procedure generateKeyNames(); // DEBUG (see below)
+
+implementation
+
+uses uPhysFSLayer, uUtils, uVariables, uTypes, uConsole;
+
+procedure populateKeyNames(var KeyArray: TKeyNames);
+var f: PfsFile;
+ l, keyname, tmp: shortstring;
+ i, scancode: LongInt;
+begin
+(*
+ KeyArray is a mapping from SDL scancodes to Hedgewars key identifiers.
+ Hedgewars key identifiers are strings with a maximum length of 15
+ and are used internally to identify keys in the engine and in settings.ini.
+*)
+
+(* Key identifiers are read from an RFC 4180-compliant CSV file.
+- 1st column: SDL scancode
+- 2nd column: Hedgewars key ID *)
+if pfsExists(cPathz[ptMisc]+'/keys.csv') then
+ begin
+ f:= pfsOpenRead(cPathz[ptMisc]+'/keys.csv');
+ l:= '';
+ pfsReadLn(f, l);
+ while (not pfsEOF(f)) and (l <> '') do
+ begin
+ tmp:= '';
+ i:= 1;
+ while (i <= length(l)) and (l[i] <> ',') do
+ begin
+ tmp:= tmp + l[i];
+ inc(i)
+ end;
+ scancode:= StrToInt(tmp);
+
+ if i < length(l) then
+ begin
+ keyname:= copy(l, i + 1, length(l) - i);
+ if (keyname[1] = '"') and (keyname[length(keyname)] = '"') then
+ keyname:= copy(keyname, 2, length(keyname) - 2)
+ else
+ keyname:= copy(keyname, 1, length(keyname) - 1);
+ end;
+
+ pfsReadLn(f, l);
+ KeyArray[scancode]:= keyname;
+ end;
+
+ pfsClose(f)
+ end
+else
+ begin
+ WriteLnToConsole('misc/keys.csv file not found');
+ AddFileLog('misc/keys.csv file not found');
+ halt(haltStartupError);
+ end;
+
+// generateKeyNames(); // DEBUG (see below)
+end;
+
+(*
+The Hedgewars key identifiers were obtained with the following algorithm:
+
+Basically:
+- For each SDL scancode, do:
+ - Take the printable SDL scancode key name (with SDL_GetScancodeName)
+ - Replace spaces with underscores
+ - Lowercase it
+ - Cap string length to 15 characters
+- Manually fix duplicates
+
+See also:
+
+https://wiki.libsdl.org/SDLScancodeLookup
+https://wiki.libsdl.org/SDL_Scancode
+
+NOTE: For compability reasons, existing identifiers should not be renamed.
+
+*)
+
+(* DEBUG
+ Uncomment this to generate a list of key names in
+ CSV format (RFC 4180) and print it out on console.
+ Don't forget to fix duplicates! *)
+(*
+procedure generateKeyNames();
+var i, t: LongInt;
+s, s2: shortstring;
+begin
+ for i := 0 to cKeyMaxIndex - 5 do
+ begin
+ s := shortstring(SDL_GetScancodeName(TSDL_Scancode(i)));
+ for t := 1 to Length(s) do
+ if s[t] = ' ' then
+ s[t] := '_';
+ s2:= copy(s, 1, 15);
+ if s2 = '"' then
+ WriteLnToConsole(IntToStr(i)+',"\""')
+ else if s2 <> '' then
+ WriteLnToConsole(IntToStr(i)+',"'+LowerCase(s2)+'"');
+ end;
+end;
+*)
+
+end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLand.pas
--- a/hedgewars/uLand.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLand.pas Wed Jul 31 23:14:27 2019 +0200
@@ -49,7 +49,6 @@
procedure DrawBorderFromImage(Surface: PSDL_Surface);
var tmpsurf: PSDL_Surface;
- //r, rr: TSDL_Rect;
x, yd, yu: LongInt;
targetMask: Word;
begin
@@ -269,7 +268,6 @@
while r.x < LAND_WIDTH do
begin
copyToXY(tmpsurf, Surface, r.x, r.y);
- //SDL_UpperBlit(tmpsurf, nil, Surface, @r);
inc(r.x, tmpsurf^.w)
end;
inc(y, tmpsurf^.h);
@@ -284,26 +282,88 @@
procedure GenDrawnMap;
+var lowerX, upperX, lowerY, upperY, lowerFS, upperFS: LongInt;
begin
- ResizeLand(4096, 2048);
- uLandPainted.Draw;
+ if (cFeatureSize <= 6) then
+ MaxHedgehogs:= 6 + (cFeatureSize-1) * 2
+ else if (cFeatureSize < 11) then
+ MaxHedgehogs:= 16 + (cFeatureSize-6) * 4
+ else if (cFeatureSize = 11) then
+ MaxHedgehogs:= 48
+ else if (cFeatureSize = 12) then
+ MaxHedgehogs:= 64
+ else
+ MaxHedgehogs:= cMaxHHs;
+
+ if GameType = gmtLandPreview then
+ cFeatureSize:= 1;
+
+ // Calculate map size for drawn map, use cFeatureSize to scale.
- MaxHedgehogs:= 48;
+ // We have pre-determined map size for cFeatureSize 1, 6, 12 and 25.
+ // The other values will be interpolated.
+ if cFeatureSize < 6 then
+ begin
+ // reference size for cFeatureSize 1
+ lowerFS:= 1;
+ lowerX:= 1024;
+ lowerY:= 512;
+ upperFS:= 6;
+ end
+ else if cFeatureSize < 12 then
+ begin
+ // reference size for cFeatureSize 6
+ lowerFS:= 6;
+ lowerX:= 2048;
+ lowerY:= 1024;
+ upperFS:= 12;
+ end
+ else
+ begin
+ // reference size for cFeatureSize 12, size of drawn maps in pre-1.0.0 versions
+ lowerFS:= 12;
+ lowerX:= 4096;
+ lowerY:= 2048;
+ upperFS:= 25;
+ end;
+
+ upperX:= lowerX * 2;
+ upperY:= lowerY * 2;
+
+ if cFeatureSize = 25 then
+ begin
+ // hardcoded size for size level 25
+ playWidth:= 8192;
+ playHeight:= 4096;
+ end
+ else
+ begin
+ // Interpolation formula
+ playWidth:= lowerX + ((upperX-lowerX) div (upperFS-lowerFS))*(cFeatureSize-lowerFS);
+ playHeight:= lowerY + ((upperY-lowerY) div (upperFS-lowerFS))*(cFeatureSize-lowerFS);
+ end;
+
+ if GameType <> gmtLandPreview then
+ WriteLnToConsole('Drawn map size: cFeatureSize='+IntToStr(cFeatureSize)+' playWidth='+IntToStr(playWidth)+' playHeight='+IntToStr(playHeight));
+
+ ResizeLand(playWidth, playHeight);
+
hasGirders:= true;
- playHeight:= 2048;
- playWidth:= 4096;
leftX:= ((LAND_WIDTH - playWidth) div 2);
rightX:= (playWidth + ((LAND_WIDTH - playWidth) div 2)) - 1;
topY:= LAND_HEIGHT - playHeight;
+
+ uLandPainted.Draw;
end;
function SelectTemplate: LongInt;
var l: LongInt;
begin
+ SelectTemplate:= 0;
if (cReducedQuality and rqLowRes) <> 0 then
SelectTemplate:= SmallTemplates[getrandom(Succ(High(SmallTemplates)))]
else
- begin
+ begin
if cTemplateFilter = 0 then
begin
l:= getRandom(GroupedTemplatesCount);
@@ -311,22 +371,23 @@
inc(cTemplateFilter);
dec(l, TemplateCounts[cTemplateFilter]);
until l < 0;
- end else getRandom(1);
+ end
+ else getRandom(1);
- case cTemplateFilter of
- 0: OutError('Ask unC0Rr about what you did wrong', true);
- 1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
- 5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
-// For lua only!
- 6: begin
- SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
- GetRandom(2) // burn 1
- end
- end
- end;
+ case cTemplateFilter of
+ 0: OutError('Error selecting TemplateFilter. Ask unC0Rr about what you did wrong', true);
+ 1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ 5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
+ // For lua only!
+ 6: begin
+ SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
+ GetRandom(2) // burn 1
+ end
+ end
+ end;
WriteLnToConsole('Selected template #'+inttostr(SelectTemplate)+' using filter #'+inttostr(cTemplateFilter));
end;
@@ -348,7 +409,7 @@
if (cReducedQuality and rqBlurryLand) = 0 then
LandPixels[y, x]:= p^[x]// or AMask
else
- LandPixels[y div 2, x div 2]:= p^[x];// or AMask;
+ LandPixels[y div 2, x div 2]:= p^[x];
p:= PLongwordArray(@(p^[Surface^.pitch div 4]));
end;
@@ -376,8 +437,8 @@
if gameFlags and gfShoppaBorder <> 0 then DrawShoppaBorder;
- for x:= leftX+2 to rightX-2 do
- for y:= topY+2 to LAND_HEIGHT-3 do
+ for x:= LongWord(leftX+2) to LongWord(rightX-2) do
+ for y:= LongWord(topY+2) to LAND_HEIGHT-3 do
if (Land[y, x] = 0) and
(((Land[y, x-1] = lfBasic) and ((Land[y+1,x] = lfBasic)) or (Land[y-1,x] = lfBasic)) or
((Land[y, x+1] = lfBasic) and ((Land[y-1,x] = lfBasic) or (Land[y+1,x] = lfBasic)))) then
@@ -692,7 +753,7 @@
var x, w, c, y: Longword;
begin
for w:= 0 to 23 do
- for x:= leftX to rightX do
+ for x:= LongWord(leftX) to LongWord(rightX) do
begin
y:= Longword(cWaterLine) - 1 - w;
Land[y, x]:= lfIndestructible;
@@ -752,8 +813,8 @@
if (GameFlags and gfBorder) <> 0 then
hasBorder:= true
else
- for y:= topY to topY + 5 do
- for x:= leftX to rightX do
+ for y:= LongWord(topY) to LongWord(topY + 5) do
+ for x:= LongWord(leftX) to LongWord(rightX) do
if Land[y, x] <> 0 then
begin
inc(c);
@@ -764,38 +825,43 @@
end;
end;
+// Indestructible map border (top, left, right)
if hasBorder then
begin
+ // Make land beyond the border indestructible
if WorldEdge = weNone then
begin
for y:= 0 to LAND_HEIGHT - 1 do
for x:= 0 to LAND_WIDTH - 1 do
- if (y < topY) or (x < leftX) or (x > rightX) then
+ if (y < LongWord(topY)) or (x < LongWord(leftX)) or (x > LongWord(rightX)) then
Land[y, x]:= lfIndestructible;
end
else if topY > 0 then
begin
- for y:= 0 to LongInt(topY) - 1 do
+ for y:= 0 to LongWord(topY - 1) do
for x:= 0 to LAND_WIDTH - 1 do
Land[y, x]:= lfIndestructible;
end;
- // experiment hardcoding cave
- // also try basing cave dimensions on map/template dimensions, if they exist
- for w:= 0 to 5 do // width of 3 allowed hogs to be knocked through with grenade
+ // Render map border
+ for w:= 0 to (cBorderWidth-1) do
begin
+ // Left and right border
if (WorldEdge <> weBounce) and (WorldEdge <> weWrap) then
- for y:= topY to LAND_HEIGHT - 1 do
+ for y:= LongWord(topY) to LAND_HEIGHT - 1 do
begin
+ // set land flags
Land[y, leftX + w]:= lfIndestructible;
Land[y, rightX - w]:= lfIndestructible;
+
+ // paint black and yellow stripes
if (y + leftX + w) mod 32 < 16 then
- c:= AMask
+ c:= AMask // black
else
- c:= AMask or RMask or GMask; // FF00FFFF
+ c:= AMask or RMask or GMask; // yellow
if (y + rightX - w) mod 32 < 16 then
- c2:= AMask
+ c2:= AMask // black
else
- c2:= AMask or RMask or GMask; // FF00FFFF
+ c2:= AMask or RMask or GMask; // yellow
if (cReducedQuality and rqBlurryLand) = 0 then
begin
@@ -809,13 +875,14 @@
end;
end;
- for x:= leftX to rightX do
+ // Top border
+ for x:= LongWord(leftX) to LongWord(rightX) do
begin
Land[topY + w, x]:= lfIndestructible;
- if (x + w) mod 32 < 16 then
- c:= AMask
+ if (topY + x + w) mod 32 < 16 then
+ c:= AMask // black
else
- c:= AMask or RMask or GMask; // FF00FFFF
+ c:= AMask or RMask or GMask; // yellow
if (cReducedQuality and rqBlurryLand) = 0 then
LandPixels[topY + w, x]:= c
@@ -825,6 +892,7 @@
end;
end;
+// Bottom border
if (GameFlags and gfBottomBorder) <> 0 then
DrawBottomBorder;
@@ -844,8 +912,8 @@
if GrayScale then
begin
if (cReducedQuality and rqBlurryLand) = 0 then
- for x:= leftX to rightX do
- for y:= topY to LAND_HEIGHT-1 do
+ for x:= LongWord(leftX) to LongWord(rightX) do
+ for y:= LongWord(topY) to LAND_HEIGHT-1 do
begin
w:= LandPixels[y,x];
w:= round(((w shr RShift and $FF) * RGB_LUMINANCE_RED +
@@ -857,8 +925,8 @@
LandPixels[y,x]:= w or (LandPixels[y, x] and AMask)
end
else
- for x:= leftX div 2 to rightX div 2 do
- for y:= topY div 2 to LAND_HEIGHT-1 div 2 do
+ for x:= LongWord(leftX div 2) to LongWord(rightX div 2) do
+ for y:= LongWord(topY div 2) to LAND_HEIGHT-1 div 2 do
begin
w:= LandPixels[y div 2,x div 2];
w:= ((w shr RShift and $FF) + (w shr BShift and $FF) + (w shr GShift and $FF)) div 3;
@@ -873,6 +941,7 @@
if (WorldEdge <> weNone) and (not hasBorder) then
InitWorldEdges();
+ScriptSetMapGlobals;
end;
procedure GenPreview(out Preview: TPreview);
@@ -883,15 +952,25 @@
mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
- mgDrawn: GenDrawnMap;
+ mgDrawn: begin GenDrawnMap; end;
mgForts: MakeFortsPreview();
else
OutError('Unknown mapgen', true);
end;
+ ScriptSetMapGlobals;
+
// strict scaling needed here since preview assumes a rectangle
- rh:= max(LAND_HEIGHT,2048);
- rw:= max(LAND_WIDTH,4096);
+ if (cMapGen <> mgDrawn) then
+ begin
+ rh:= max(LAND_HEIGHT, 2048);
+ rw:= max(LAND_WIDTH, 4096);
+ end
+ else
+ begin
+ rh:= LAND_HEIGHT;
+ rw:= LAND_WIDTH
+ end;
ox:= 0;
if rw < rh*2 then
begin
@@ -932,15 +1011,27 @@
mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
- mgDrawn: GenDrawnMap;
+ mgDrawn: begin GenDrawnMap; end;
mgForts: MakeFortsPreview;
else
OutError('Unknown mapgen', true);
end;
+ ScriptSetMapGlobals;
+
+
// strict scaling needed here since preview assumes a rectangle
- rh:= max(LAND_HEIGHT, 2048);
- rw:= max(LAND_WIDTH, 4096);
+ if (cMapGen <> mgDrawn) then
+ begin
+ rh:= max(LAND_HEIGHT, 2048);
+ rw:= max(LAND_WIDTH, 4096);
+ end
+ else
+ begin
+ rh:= LAND_HEIGHT;
+ rw:= LAND_WIDTH
+ end;
+
ox:= 0;
if rw < rh*2 then
begin
@@ -974,17 +1065,19 @@
if digest = '' then
digest:= s
else
- checkFails(s = digest, 'Different map or critical resources loaded, sorry', true);
+ checkFails(s = digest, 'Loaded map or other critical resource does not match across all players', true);
end;
procedure chSendLandDigest(var s: shortstring);
var i: LongInt;
+ landPixelDigest : LongInt;
begin
+ landPixelDigest:= 1;
for i:= 0 to LAND_HEIGHT-1 do
- syncedPixelDigest:= Adler32Update(syncedPixelDigest, @Land[i,0], LAND_WIDTH*2);
- s:= 'M' + IntToStr(syncedPixelDigest); // + cScriptName; script name is no longer needed. scripts are hashed
+ landPixelDigest:= Adler32Update(landPixelDigest, @Land[i,0], LAND_WIDTH*2);
+ s:= 'M' + IntToStr(syncedPixelDigest)+'|'+IntToStr(landPixelDigest);
- ScriptSetString('LandDigest', s);
+ ScriptSetString('LandDigest',IntToStr(landPixelDigest));
chLandCheck(s);
if allOK then SendIPCRaw(@s[0], Length(s) + 1)
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandGenPerlin.pas
--- a/hedgewars/uLandGenPerlin.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandGenPerlin.pas Wed Jul 31 23:14:27 2019 +0200
@@ -135,6 +135,43 @@
df:= detail * (6 - param2 * 2);
+ // Calculate estimate for max. hedgehog count
+ // Tunnels
+ if param1 = 0 then
+ begin
+ // Small tunnels
+ if param2 = 0 then
+ // 12..24
+ MaxHedgehogs:= 12 + 1 * (cFeatureSize - 1)
+ // Medium tunnels
+ else if param2 = 1 then
+ // 14..24
+ MaxHedgehogs:= 14 + max(0, 1 * (cFeatureSize - 3))
+ // Large tunnels
+ else if param2 = 2 then
+ // 16..24
+ MaxHedgehogs:= 16 + max(0, 1 * (cFeatureSize - 5));
+ if MaxHedgehogs > 24 then
+ MaxHedgehogs:= 24;
+ end
+ // Islands
+ else if (param1 = 1) and (cFeatureSize <= 25) then
+ // Small islands
+ if param2 = 0 then
+ // 64..32
+ MaxHedgehogs:= 32 + ((((25 - (cFeatureSize-1))*1000000) div 24) * 32) div 1000000
+ // Medium islands
+ else if param2 = 1 then
+ // 56..28
+ MaxHedgehogs:= 28 + ((((25 - (cFeatureSize-1))*1000000) div 24) * 28) div 1000000
+ // Large islands
+ else if param2 = 2 then
+ // 48..24
+ MaxHedgehogs:= 24 + ((((25 - (cFeatureSize-1))*1000000) div 24) * 24) div 1000000;
+ // We only want even numbers
+ if (MaxHedgehogs > 0) and ((MaxHedgehogs mod 2) = 1) then
+ MaxHedgehogs:= MaxHedgehogs - 1;
+
inoise_setup();
for y:= minY to pred(height) do
@@ -188,8 +225,10 @@
Land[y, x]:= 0;
end;
+ playWidth:= width;
+ playHeight:= height;
leftX:= 0;
- rightX:= 4095;
+ rightX:= playWidth - 1;
topY:= 0;
hasBorder:= false;
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandGenTemplateBased.pas
--- a/hedgewars/uLandGenTemplateBased.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandGenTemplateBased.pas Wed Jul 31 23:14:27 2019 +0200
@@ -82,21 +82,6 @@
end
end;
-
-procedure Distort1(var Template: TEdgeTemplate; var pa: TPixAr);
-var i: Longword;
-begin
- for i:= 1 to Template.BezierizeCount do
- begin
- BezierizeEdge(pa, _0_5);
- RandomizePoints(pa);
- RandomizePoints(pa)
- end;
- for i:= 1 to Template.RandPassesCount do
- RandomizePoints(pa);
- BezierizeEdge(pa, _0_1);
-end;
-
procedure FindPoint(si: LongInt; fillPointsCount: LongWord; var newPoint: TPoint; var pa: TPixAr);
const mapBorderMargin = 40;
var p1, p2, p4, fp, mp: TPoint;
@@ -129,9 +114,9 @@
// don't process too short segments or those which are too close to map borders
if (p1.x = NTPX)
or (dab < minDistance * 3)
- or (mp.x < LongInt(leftX) + mapBorderMargin)
- or (mp.x > LongInt(rightX) - mapBorderMargin)
- or (mp.y < LongInt(topY) + mapBorderMargin)
+ or (mp.x < leftX + mapBorderMargin)
+ or (mp.x > rightX - mapBorderMargin)
+ or (mp.y < topY + mapBorderMargin)
or (mp.y > LongInt(LAND_HEIGHT) - mapBorderMargin)
then
begin
@@ -143,13 +128,13 @@
if a <> 0 then
begin
// left border
- iy:= (LongInt(leftX) + mapBorderMargin - mp.x) * b div a + mp.y;
+ iy:= (leftX + mapBorderMargin - mp.x) * b div a + mp.y;
d:= DistanceI(mp.x - leftX - mapBorderMargin, mp.y - iy).Round;
t1:= a * (mp.x - mapBorderMargin) + b * (mp.y - iy);
if t1 > 0 then distL:= d else distR:= d;
// right border
- iy:= (LongInt(rightX) - mapBorderMargin - mp.x) * b div a + mp.y;
+ iy:= (rightX - mapBorderMargin - mp.x) * b div a + mp.y;
d:= DistanceI(mp.x - rightX + mapBorderMargin, mp.y - iy).Round;
if t1 > 0 then distR:= d else distL:= d;
end else
@@ -161,7 +146,7 @@
if b <> 0 then
begin
// top border
- ix:= (LongInt(topY) + mapBorderMargin - mp.y) * a div b + mp.x;
+ ix:= (topY + mapBorderMargin - mp.y) * a div b + mp.x;
d:= DistanceI(mp.y - topY - mapBorderMargin, mp.x - ix).Round;
t2:= b * (mp.y - mapBorderMargin) + a * (mp.x - ix);
if t2 > 0 then distL:= min(d, distL) else distR:= min(d, distR);
@@ -179,9 +164,9 @@
fp:= pa.ar[i + 1]
else if (i <> si) then
begin
- p4:= pa.ar[i + 1];
- if p4.x = NTPX then
- p4:= fp;
+ p4:= pa.ar[i + 1];
+ if p4.x = NTPX then
+ p4:= fp;
// check if it intersects
t1:= (mp.x - pa.ar[i].x) * b - a * (mp.y - pa.ar[i].y);
@@ -382,7 +367,7 @@
hasBorder:= true;
for y:= 0 to LAND_HEIGHT - 1 do
for x:= 0 to LAND_WIDTH - 1 do
- if (y < topY) or (x < leftX) or (x > rightX) then
+ if (y < LongWord(topY)) or (x < LongWord(leftX)) or (x > LongWord(rightX)) then
Land[y, x]:= 0
else
begin
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandGraphics.pas
--- a/hedgewars/uLandGraphics.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandGraphics.pas Wed Jul 31 23:14:27 2019 +0200
@@ -51,6 +51,7 @@
function TryPlaceOnLand(cpX, cpY: LongInt; Obj: TSprite; Frame: LongInt; doPlace: boolean; LandFlags: Word): boolean; inline;
function ForcePlaceOnLand(cpX, cpY: LongInt; Obj: TSprite; Frame: LongInt; LandFlags: Word; Tint: LongWord; Behind, flipHoriz, flipVert: boolean): boolean; inline;
function TryPlaceOnLand(cpX, cpY: LongInt; Obj: TSprite; Frame: LongInt; doPlace, outOfMap, force, behind, flipHoriz, flipVert: boolean; LandFlags: Word; Tint: LongWord): boolean;
+procedure EraseLandRectRaw(X, Y, width, height: LongWord);
procedure EraseLand(cpX, cpY: LongInt; Obj: TSprite; Frame: LongInt; LandFlags: Word; eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert: boolean);
function GetPlaceCollisionTex(cpX, cpY: LongInt; Obj: TSprite; Frame: LongInt): PTexture;
@@ -161,6 +162,7 @@
procedure DrawPixelIce(landX, landY, pixelX, pixelY: Longint); inline;
begin
if ((Land[landY, landX] and lfIce) <> 0) then exit;
+if (pixelX < LeftX) or (pixelX > RightX) or (pixelY < TopY) then exit;
if isLandscapeEdge(getPixelWeight(landX, landY)) then
begin
if (LandPixels[pixelY, pixelX] and AMask < 255) and (LandPixels[pixelY, pixelX] and AMask > 0) then
@@ -236,12 +238,12 @@
setCurrentHog:
for i:= fromPix to toPix do
begin
- Land[y, i]:= Land[y, i] or lfCurrentHog
+ Land[y, i]:= Land[y, i] or lfCurHogCrate
end;
removeCurrentHog:
for i:= fromPix to toPix do
begin
- Land[y, i]:= Land[y, i] and lfNotCurrentMask;
+ Land[y, i]:= Land[y, i] and lfNotCurHogCrate;
end;
end;
end;
@@ -422,7 +424,7 @@
if x <= leftX then
iceR:= min(leftX + iceHeight, iceR)
else {if x >= rightX then}
- iceL:= max(LongInt(rightX) - iceHeight, iceL);
+ iceL:= max(rightX - iceHeight, iceL);
end;
// don't continue if all ice is outside land array
@@ -473,8 +475,8 @@
begin
for i:= 0 to Pred(Count) do
begin
- for ty:= Max(y - Radius, 0) to Min(y + Radius, LAND_HEIGHT) do
- for tx:= Max(0, ar^[i].Left - Radius) to Min(LAND_WIDTH, ar^[i].Right + Radius) do
+ for ty:= Max(y - Radius, 0) to Min(y + Radius, TopY) do
+ for tx:= Max(LeftX, ar^[i].Left - Radius) to Min(RightX, ar^[i].Right + Radius) do
begin
if (Land[ty, tx] and lfIndestructible) = 0 then
begin
@@ -500,8 +502,8 @@
for i:= 0 to Pred(Count) do
begin
- for ty:= Max(y - Radius, 0) to Min(y + Radius, LAND_HEIGHT) do
- for tx:= Max(0, ar^[i].Left - Radius) to Min(LAND_WIDTH, ar^[i].Right + Radius) do
+ for ty:= Max(y - Radius, 0) to Min(y + Radius, TopY) do
+ for tx:= Max(LeftX, ar^[i].Left - Radius) to Min(RightX, ar^[i].Right + Radius) do
if ((Land[ty, tx] and lfBasic) <> 0) or ((Land[ty, tx] and lfObject) <> 0) then
begin
if (cReducedQuality and rqBlurryLand) = 0 then
@@ -753,8 +755,8 @@
((not force) and (Land[cpY + y, cpX + x] <> 0))) or
(not outOfMap and
- (((cpY + y) <= Longint(topY)) or ((cpY + y) >= LAND_HEIGHT) or
- ((cpX + x) <= Longint(leftX)) or ((cpX + x) >= Longint(rightX)) or
+ (((cpY + y) <= topY) or ((cpY + y) >= LAND_HEIGHT) or
+ ((cpX + x) <= leftX) or ((cpX + x) >= rightX) or
((not force) and (Land[cpY + y, cpX + x] <> 0)))) then
begin
if SDL_MustLock(Image) then
@@ -795,9 +797,11 @@
begin
if (LandFlags and lfBasic <> 0) or
((LandPixels[gY, gX] and AMask shr AShift > 128) and // This test assumes lfBasic and lfObject differ only graphically
- (LandFlags and lfObject = 0)) then
+ (LandFlags and (lfObject or lfIce) = 0)) then
Land[cpY + y, cpX + x]:= lfBasic or LandFlags
- else Land[cpY + y, cpX + x]:= lfObject or LandFlags
+ else if (LandFlags and lfIce = 0) then
+ Land[cpY + y, cpX + x]:= lfObject or LandFlags
+ else Land[cpY + y, cpX + x]:= LandFlags
end;
if (not behind) or (LandPixels[gY, gX] = 0) then
begin
@@ -837,6 +841,17 @@
end;
+procedure EraseLandRectRaw(X, Y, width, height: LongWord);
+var tx, ty: LongWord;
+begin
+for ty:= 0 to height - 1 do
+ for tx:= 0 to width - 1 do
+ begin
+ LandPixels[ty, tx]:= 0;
+ Land[Y + ty, X + tx]:= 0;
+ end;
+end;
+
procedure EraseLand(cpX, cpY: LongInt; Obj: TSprite; Frame: LongInt; LandFlags: Word; eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert: boolean);
var X, Y, bpp, h, w, row, col, gx, gy, numFramesFirstCol: LongInt;
p: PByteArray;
@@ -871,8 +886,8 @@
begin
for x:= 0 to Pred(w) do
if ((PLongword(@(p^[x * 4]))^) and AMask) <> 0 then
- if ((cpY + y) <= Longint(topY)) or ((cpY + y) >= LAND_HEIGHT) or
- ((cpX + x) <= Longint(leftX)) or ((cpX + x) >= Longint(rightX)) then
+ if ((cpY + y) <= topY) or ((cpY + y) >= LAND_HEIGHT) or
+ ((cpX + x) <= leftX) or ((cpX + x) >= rightX) then
begin
if SDL_MustLock(Image) then
SDL_UnlockSurface(Image);
@@ -974,8 +989,8 @@
begin
for x:= 0 to Pred(w) do
if ((p^[x] and AMask) <> 0)
- and (((cpY + y) < Longint(topY)) or ((cpY + y) >= LAND_HEIGHT) or
- ((cpX + x) < Longint(leftX)) or ((cpX + x) > Longint(rightX)) or (Land[cpY + y, cpX + x] <> 0)) then
+ and (((cpY + y) < topY) or ((cpY + y) >= LAND_HEIGHT) or
+ ((cpX + x) < leftX) or ((cpX + x) > rightX) or (Land[cpY + y, cpX + x] <> 0)) then
pt^[x]:= cWhiteColor
else
(pt^[x]):= cWhiteColor and (not AMask);
@@ -1072,8 +1087,8 @@
exit;
// check location
-if (Y <= LongInt(topY) + 1) or (Y >= LAND_HEIGHT-2)
-or (X <= LongInt(leftX) + 1) or (X >= LongInt(rightX) - 1) then
+if (Y <= topY + 1) or (Y >= LAND_HEIGHT-2)
+or (X <= leftX + 1) or (X >= rightX - 1) then
exit;
// counter for neighbor pixels that are not known to be undamaged
@@ -1124,8 +1139,8 @@
procedure Smooth_oldImpl(X, Y: LongInt);
begin
// a bit of AA for explosions
-if (Land[Y, X] = 0) and (Y > LongInt(topY) + 1) and
- (Y < LAND_HEIGHT-2) and (X > LongInt(leftX) + 1) and (X < LongInt(rightX) - 1) then
+if (Land[Y, X] = 0) and (Y > topY + 1) and
+ (Y < LAND_HEIGHT-2) and (X > leftX + 1) and (X < rightX - 1) then
begin
if ((((Land[y, x-1] and lfDamaged) <> 0) and (((Land[y+1,x] and lfDamaged) <> 0)) or ((Land[y-1,x] and lfDamaged) <> 0))
or (((Land[y, x+1] and lfDamaged) <> 0) and (((Land[y-1,x] and lfDamaged) <> 0) or ((Land[y+1,x] and lfDamaged) <> 0)))) then
@@ -1183,7 +1198,7 @@
end
else if ((cReducedQuality and rqBlurryLand) = 0) and ((LandPixels[Y, X] and AMask) = AMask)
and (Land[Y, X] and (lfDamaged or lfBasic) = lfBasic)
-and (Y > LongInt(topY) + 1) and (Y < LAND_HEIGHT-2) and (X > LongInt(leftX) + 1) and (X < LongInt(rightX) - 1) then
+and (Y > topY + 1) and (Y < LAND_HEIGHT-2) and (X > leftX + 1) and (X < rightX - 1) then
begin
if ((((Land[y, x-1] and lfDamaged) <> 0) and (((Land[y+1,x] and lfDamaged) <> 0)) or ((Land[y-1,x] and lfDamaged) <> 0))
or (((Land[y, x+1] and lfDamaged) <> 0) and (((Land[y-1,x] and lfDamaged) <> 0) or ((Land[y+1,x] and lfDamaged) <> 0)))) then
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandObjects.pas
--- a/hedgewars/uLandObjects.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandObjects.pas Wed Jul 31 23:14:27 2019 +0200
@@ -28,6 +28,7 @@
procedure BlitImageAndGenerateCollisionInfo(cpX, cpY, Width: Longword; Image: PSDL_Surface); inline;
procedure BlitImageAndGenerateCollisionInfo(cpX, cpY, Width: Longword; Image: PSDL_Surface; LandFlags: Word); inline;
procedure BlitImageAndGenerateCollisionInfo(cpX, cpY, Width: Longword; Image: PSDL_Surface; LandFlags: Word; Flip: boolean);
+procedure BlitOverlayAndGenerateCollisionInfo(cpX, cpY: Longword; Image: PSDL_Surface);
procedure BlitImageUsingMask(cpX, cpY: Longword; Image, Mask: PSDL_Surface);
procedure AddOnLandObjects(Surface: PSDL_Surface);
procedure SetLand(var LandWord: Word; Pixel: LongWord); inline;
@@ -42,14 +43,25 @@
MAXTHEMEOBJECTS = 32;
cThemeCFGFilename = 'theme.cfg';
-type TRectsArray = array[0..MaxRects] of TSDL_Rect;
+type PLongWord = ^LongWord;
+ TRectsArray = array[0..MaxRects] of TSDL_Rect;
PRectArray = ^TRectsArray;
+ TThemeObjectOverlay = record
+ Position: TPoint;
+ Surf: PSDL_Surface;
+ Width, Height: LongWord;
+ end;
TThemeObject = record
+ Name: ShortString;
Surf, Mask: PSDL_Surface;
inland: array[0..Pred(MAXOBJECTRECTS)] of TSDL_Rect;
outland: array[0..Pred(MAXOBJECTRECTS)] of TSDL_Rect;
- inrectcnt: Longword;
- outrectcnt: Longword;
+ anchors: array[0..Pred(MAXOBJECTRECTS)] of TSDL_Rect;
+ overlays: array[0..Pred(MAXOBJECTRECTS)] of TThemeObjectOverlay;
+ inrectcnt: LongInt;
+ outrectcnt: LongInt;
+ anchorcnt: LongInt;
+ overlaycnt: LongInt;
Width, Height: Longword;
Maxcnt: Longword;
end;
@@ -102,9 +114,15 @@
BlitImageAndGenerateCollisionInfo(cpX, cpY, Width, Image, LandFlags, false);
end;
+function LerpByte(src, dst: Byte; l: LongWord): LongWord; inline;
+begin
+ LerpByte:= ((255 - l) * src + l * dst) div 255;
+end;
+
procedure BlitImageAndGenerateCollisionInfo(cpX, cpY, Width: Longword; Image: PSDL_Surface; LandFlags: Word; Flip: boolean);
var p: PLongwordArray;
- px, x, y: Longword;
+ pLandColor: PLongWord;
+ alpha, color, landColor, x, y: LongWord;
bpp: LongInt;
begin
WriteToConsole('Generating collision info... ');
@@ -123,30 +141,91 @@
Width:= Image^.w;
p:= Image^.pixels;
+
for y:= 0 to Pred(Image^.h) do
begin
for x:= 0 to Pred(Width) do
begin
// map image pixels per line backwards if in flip mode
if Flip then
- px:= Pred(Image^.w) - x
+ color:= p^[Pred(Image^.w) - x]
+ else
+ color:= p^[x];
+
+ if (cReducedQuality and rqBlurryLand) = 0 then
+ pLandColor:= @LandPixels[cpY + y, cpX + x]
else
- px:= x;
+ pLandColor:= @LandPixels[(cpY + y) div 2, (cpX + x) div 2];
+
+ landColor:= pLandColor^;
+ alpha:= (landColor and AMask) shr AShift;
+
+ if ((color and AMask) <> 0) and (alpha <> 255) then
+ begin
+ if alpha = 0 then
+ pLandColor^:= color
+ else
+ pLandColor^:=
+ (LerpByte((color and RMask) shr RShift, (landColor and RMask) shr RShift, alpha) shl RShift)
+ or (LerpByte((color and GMask) shr GShift, (landColor and GMask) shr GShift, alpha) shl GShift)
+ or (LerpByte((color and BMask) shr BShift, (landColor and BMask) shr BShift, alpha) shl BShift)
+ or (LerpByte(alpha, 255, (color and AMask) shr AShift) shl AShift);
+
+ end;
+
+ if ((color and AMask) <> 0) and (Land[cpY + y, cpX + x] <= lfAllObjMask) then
+ Land[cpY + y, cpX + x]:= lfObject or LandFlags
+ end;
+ p:= PLongwordArray(@(p^[Image^.pitch shr 2]))
+ end;
- if (p^[px] and AMask) <> 0 then
+if SDL_MustLock(Image) then
+ SDL_UnlockSurface(Image);
+WriteLnToConsole(msgOK)
+end;
+
+procedure BlitOverlayAndGenerateCollisionInfo(cpX, cpY: Longword; Image: PSDL_Surface);
+var p: PLongwordArray;
+ pLandColor: PLongWord;
+ x, y, alpha, color, landColor: LongWord;
+begin
+WriteToConsole('Generating overlay collision info... ');
+
+if SDL_MustLock(Image) then
+ if SDLCheck(SDL_LockSurface(Image) >= 0, 'SDL_LockSurface', true) then exit;
+
+if checkFails(Image^.format^.BytesPerPixel = 4, 'Land object overlay should be 32bit', true)
+ and SDL_MustLock(Image) then
+ SDL_UnlockSurface(Image);
+
+p:= Image^.pixels;
+
+for y:= 0 to Pred(Image^.h) do
+ begin
+ for x:= 0 to Pred(Image^.w) do
+ begin
+ color:= p^[x];
+ if (color and AMask) <> 0 then
begin
if (cReducedQuality and rqBlurryLand) = 0 then
- begin
- if (LandPixels[cpY + y, cpX + x] = 0)
- or (((p^[px] and AMask) <> 0) and (((LandPixels[cpY + y, cpX + x] and AMask) shr AShift) < 255)) then
- LandPixels[cpY + y, cpX + x]:= p^[px];
- end
+ pLandColor:= @LandPixels[cpY + y, cpX + x]
else
- if LandPixels[(cpY + y) div 2, (cpX + x) div 2] = 0 then
- LandPixels[(cpY + y) div 2, (cpX + x) div 2]:= p^[px];
+ pLandColor:= @LandPixels[(cpY + y) div 2, (cpX + x) div 2];
- if (Land[cpY + y, cpX + x] <= lfAllObjMask) and ((p^[px] and AMask) <> 0) then
- Land[cpY + y, cpX + x]:= lfObject or LandFlags
+ alpha:= (color and AMask) shr AShift;
+ if ((alpha <> $FF) and ((pLandColor^) <> 0)) then
+ begin
+ landColor:= pLandColor^;
+ color:=
+ (LerpByte((landColor and RMask) shr RShift, (color and RMask) shr RShift, alpha) shl RShift)
+ or (LerpByte((landColor and GMask) shr GShift, (color and GMask) shr GShift, alpha) shl GShift)
+ or (LerpByte((landColor and BMask) shr BShift, (color and BMask) shr BShift, alpha) shl BShift)
+ or (LerpByte(alpha, 255, (landColor and AMask) shr AShift) shl AShift)
+ end;
+ pLandColor^:= color;
+
+ if Land[cpY + y, cpX + x] <= lfAllObjMask then
+ Land[cpY + y, cpX + x]:= lfObject
end;
end;
p:= PLongwordArray(@(p^[Image^.pitch shr 2]))
@@ -159,7 +238,8 @@
procedure BlitImageUsingMask(cpX, cpY: Longword; Image, Mask: PSDL_Surface);
var p, mp: PLongwordArray;
- x, y: Longword;
+ pLandColor: PLongWord;
+ alpha, color, landColor, x, y: Longword;
bpp: LongInt;
begin
WriteToConsole('Generating collision info... ');
@@ -180,19 +260,32 @@
begin
for x:= 0 to Pred(Image^.w) do
begin
+ color:= p^[x];
+
if (cReducedQuality and rqBlurryLand) = 0 then
- begin
- if (LandPixels[cpY + y, cpX + x] = 0)
- or (((p^[x] and AMask) <> 0) and (((LandPixels[cpY + y, cpX + x] and AMask) shr AShift) < 255)) then
- LandPixels[cpY + y, cpX + x]:= p^[x];
- end
+ pLandColor:= @LandPixels[cpY + y, cpX + x]
else
- if LandPixels[(cpY + y) div 2, (cpX + x) div 2] = 0 then
- LandPixels[(cpY + y) div 2, (cpX + x) div 2]:= p^[x];
+ pLandColor:= @LandPixels[(cpY + y) div 2, (cpX + x) div 2];
+
+ landColor:= pLandColor^;
+ alpha:= (landColor and AMask) shr AShift;
+
+ if ((color and AMask) <> 0) and (alpha <> 255) then
+ begin
+ if alpha = 0 then
+ pLandColor^:= color
+ else
+ pLandColor^:=
+ (LerpByte((color and RMask) shr RShift, (landColor and RMask) shr RShift, alpha) shl RShift)
+ or (LerpByte((color and GMask) shr GShift, (landColor and GMask) shr GShift, alpha) shl GShift)
+ or (LerpByte((color and BMask) shr BShift, (landColor and BMask) shr BShift, alpha) shl BShift)
+ or (LerpByte(alpha, 255, (color and AMask) shr AShift) shl AShift);
+ end;
if (Land[cpY + y, cpX + x] <= lfAllObjMask) or (Land[cpY + y, cpX + x] and lfObject <> 0) then
SetLand(Land[cpY + y, cpX + x], mp^[x]);
end;
+
p:= PLongwordArray(@(p^[Image^.pitch shr 2]));
mp:= PLongwordArray(@(mp^[Mask^.pitch shr 2]))
end;
@@ -253,6 +346,28 @@
CountNonZeroz:= lRes;
end;
+procedure ChecksumLandObjectImage(Image: PSDL_Surface);
+var y: LongInt;
+begin
+ if Image = nil then exit;
+
+ if SDL_MustLock(Image) then
+ SDL_LockSurface(Image);
+
+ if checkFails(Image^.format^.BytesPerPixel = 4, 'Land object image should be 32bit', true) then
+ begin
+ if SDL_MustLock(Image) then
+ SDL_UnlockSurface(Image);
+ exit
+ end;
+
+ for y := 0 to Image^.h-1 do
+ syncedPixelDigest:= Adler32Update(syncedPixelDigest, @PByteArray(Image^.pixels)^[y*Image^.pitch], Image^.w*4);
+
+ if SDL_MustLock(Image) then
+ SDL_UnlockSurface(Image);
+end;
+
function AddGirder(gX: LongInt; var girSurf: PSDL_Surface): boolean;
var x1, x2, y, k, i, girderHeight: LongInt;
rr: TSDL_Rect;
@@ -261,8 +376,7 @@
if girSurf = nil then
girSurf:= LoadDataImageAltPath(ptCurrTheme, ptGraphics, 'Girder', ifCritical or ifColorKey or ifIgnoreCaps);
-for y := 0 to girsurf^.h-1 do
- syncedPixelDigest:= Adler32Update(syncedPixelDigest, @PByteArray(girsurf^.pixels)^[y*girsurf^.pitch], girsurf^.w*4);
+ChecksumLandObjectImage(girsurf);
girderHeight:= girSurf^.h;
@@ -272,27 +386,27 @@
x1:= gX;
x2:= gX;
- while (x1 > Longint(leftX)+150) and (CountNonZeroz(x1, y, girderHeight) = 0) do
+ while (x1 > leftX+150) and (CountNonZeroz(x1, y, girderHeight) = 0) do
dec(x1, 2);
i:= x1 - 12;
repeat
k:= CountNonZeroz(x1, y, girderHeight);
dec(x1, 2)
- until (x1 < Longint(leftX) + 100) or (k = 0) or (k = girderHeight) or (x1 < i);
+ until (x1 < leftX + 100) or (k = 0) or (k = girderHeight) or (x1 < i);
inc(x1, 2);
if k = girderHeight then
begin
- while (x2 < (LongInt(rightX) - 100)) and (CountNonZeroz(x2, y, girderHeight) = 0) do
+ while (x2 < (rightX - 100)) and (CountNonZeroz(x2, y, girderHeight) = 0) do
inc(x2, 2);
i:= x2 + 12;
repeat
inc(x2, 2);
k:= CountNonZeroz(x2, y, girderHeight)
- until (x2 >= (LongInt(rightX)-150)) or (k = 0) or (k = girderHeight) or (x2 > i) or (x2 - x1 >= 900);
+ until (x2 >= (rightX-150)) or (k = 0) or (k = girderHeight) or (x2 > i) or (x2 - x1 >= 900);
- if (x2 < (LongInt(rightX) - 100)) and (k = girderHeight) and (x2 - x1 > 200) and (x2 - x1 < 900)
+ if (x2 < (rightX - 100)) and (k = girderHeight) and (x2 - x1 > 200) and (x2 - x1 < 900)
and (not CheckIntersect(x1 - 32, y - 64, x2 - x1 + 64, 144)) then
break;
end;
@@ -355,27 +469,85 @@
CheckLand:= bRes;
end;
+function CheckLandAny(rect: TSDL_Rect; dX, dY, LandType: Longword): boolean;
+var tmpx, tmpy, bx, by: LongInt;
+begin
+ inc(rect.x, dX);
+ inc(rect.y, dY);
+ bx:= rect.x + rect.w - 1;
+ by:= rect.y + rect.h - 1;
+ CheckLandAny:= false;
+
+ if (((rect.x and LAND_WIDTH_MASK) or (bx and LAND_WIDTH_MASK) or
+ (rect.y and LAND_HEIGHT_MASK) or (by and LAND_HEIGHT_MASK)) = 0) then
+ begin
+ for tmpx := rect.x to bx do
+ begin
+ if (((Land[rect.y, tmpx] and LandType) or (Land[by, tmpx] and LandType)) <> 0) then
+ begin
+ CheckLandAny := true;
+ exit;
+ end
+ end;
+ for tmpy := rect.y to by do
+ begin
+ if (((Land[tmpy, rect.x] and LandType) or (Land[tmpy, bx] and LandType)) <> 0) then
+ begin
+ CheckLandAny := true;
+ exit;
+ end
+ end;
+ end;
+end;
+
function CheckCanPlace(x, y: Longword; var Obj: TThemeObject): boolean;
var i: Longword;
- bRes: boolean;
+ bRes, anchored: boolean;
+ overlayP1, overlayP2: TPoint;
begin
with Obj do begin
bRes:= true;
- i:= 1;
- while bRes and (i <= inrectcnt) do
+ i:= 0;
+ while bRes and (i < overlaycnt) do
+ begin
+ overlayP1.x:= overlays[i].Position.x + x;
+ overlayP1.y:= overlays[i].Position.y + y;
+ overlayP2.x:= overlayP1.x + overlays[i].Width - 1;
+ overlayP2.y:= overlayP1.y + overlays[i].Height - 1;
+ bRes:= (((LAND_WIDTH_MASK and overlayP1.x) or (LAND_HEIGHT_MASK and overlayP1.y) or
+ (LAND_WIDTH_MASK and overlayP2.x) or (LAND_HEIGHT_MASK and overlayP2.y)) = 0)
+ and (not CheckIntersect(overlayP1.x, overlayP1.y, overlays[i].Width, overlays[i].Height));
+ inc(i)
+ end;
+
+ i:= 0;
+ while bRes and (i < inrectcnt) do
begin
bRes:= CheckLand(inland[i], x, y, lfBasic);
inc(i)
end;
- i:= 1;
- while bRes and (i <= outrectcnt) do
+ i:= 0;
+ while bRes and (i < outrectcnt) do
begin
bRes:= CheckLand(outland[i], x, y, 0);
inc(i)
end;
if bRes then
+ begin
+ anchored:= anchorcnt = 0;
+ i:= 0;
+ while i < anchorcnt do
+ begin
+ anchored := CheckLandAny(anchors[i], x, y, lfLandMask);
+ if anchored then break;
+ inc(i);
+ end;
+ bRes:= anchored;
+ end;
+
+ if bRes then
bRes:= not CheckIntersect(x, y, Width, Height);
CheckCanPlace:= bRes;
@@ -386,7 +558,7 @@
const MaxPointsIndex = 2047;
var x, y: Longword;
ar: array[0..MaxPointsIndex] of TPoint;
- cnt, i: Longword;
+ cnt, i, ii: Longword;
bRes: boolean;
begin
TryPut:= false;
@@ -395,12 +567,12 @@
begin
if Maxcnt = 0 then
exit;
- x:= 0;
+ x:= leftX;
repeat
y:= topY+32; // leave room for a hedgie to teleport in
repeat
- if (inland[1].x = 0) and (inland[1].y = 0) and (inland[1].w = 0) and (inland[1].h = 0) then
+ if (inrectcnt > 0) and (inland[0].x = 0) and (inland[0].y = 0) and (inland[0].w = 0) and (inland[0].h = 0) then
y := LAND_HEIGHT - Height;
if CheckCanPlace(x, y, Obj) then
@@ -417,7 +589,7 @@
inc(y, 3);
until y >= LAND_HEIGHT - Height;
inc(x, getrandom(6) + 3)
- until x >= LAND_WIDTH - Width;
+ until x >= rightX - Width;
bRes:= cnt <> 0;
if bRes then
begin
@@ -426,6 +598,18 @@
BlitImageUsingMask(ar[i].x, ar[i].y, Obj.Surf, Obj.Mask)
else BlitImageAndGenerateCollisionInfo(ar[i].x, ar[i].y, 0, Obj.Surf);
AddRect(ar[i].x, ar[i].y, Width, Height);
+
+ ii:= 0;
+ while ii < overlaycnt do
+ begin
+ BlitOverlayAndGenerateCollisionInfo(
+ ar[i].x + overlays[ii].Position.X,
+ ar[i].y + overlays[ii].Position.Y, overlays[ii].Surf);
+ AddRect(ar[i].x + overlays[ii].Position.X,
+ ar[i].y + overlays[ii].Position.Y,
+ Width, Height);
+ inc(ii);
+ end;
dec(Maxcnt)
end
else Maxcnt:= 0
@@ -435,7 +619,8 @@
function TryPut2(var Obj: TSprayObject; Surface: PSDL_Surface): boolean;
const MaxPointsIndex = 8095;
-var x, y: Longword;
+var x, y, xStart, yStart: Longword;
+ xWraps, yWraps: Byte;
ar: array[0..MaxPointsIndex] of TPoint;
cnt, i: Longword;
r: TSDL_Rect;
@@ -447,13 +632,20 @@
begin
if Maxcnt = 0 then
exit;
- x:= 0;
+ xWraps:= 0;
+ yWraps:= 0;
+ // Start at random coordinates
+ xStart:= getrandom(LAND_WIDTH - Width);
+ yStart:= 8 + getrandom(LAND_HEIGHT - Height - 16);
+ x:= xStart;
+ y:= yStart;
r.x:= 0;
r.y:= 0;
r.w:= Width;
r.h:= Height + 16;
+ // Then iterate through the whole map; this requires we wrap one time per axis
repeat
- y:= 8;
+ yWraps:= 0;
repeat
if CheckLand(r, x, y - 8, lfBasic)
and (not CheckIntersect(x, y, Width, Height)) then
@@ -468,9 +660,19 @@
else inc(cnt);
end;
inc(y, 12);
- until y >= LAND_HEIGHT - Height - 8;
- inc(x, getrandom(12) + 12)
- until x >= LAND_WIDTH - Width;
+ if (y >= LAND_HEIGHT - Height - 8) or ((yWraps > 0) and (y >= yStart)) then
+ begin
+ inc(yWraps);
+ y:= 8;
+ end;
+ until yWraps > 1;
+ inc(x, getrandom(12) + 12);
+ if (x >= LAND_WIDTH - Width) or ((xWraps > 0) and (x >= xStart)) then
+ begin
+ inc(xWraps);
+ x:= 0;
+ end;
+ until xWraps > 1;
bRes:= cnt <> 0;
if bRes then
begin
@@ -488,15 +690,59 @@
procedure CheckRect(Width, Height, x, y, w, h: LongWord);
begin
if (x + w > Width) then
- OutError('Object''s rectangle exceeds image: x + w (' + inttostr(x) + ' + ' + inttostr(w) + ') > Width (' + inttostr(Width) + ')', true);
+ OutError('Broken theme. Object''s rectangle exceeds image: x + w (' + inttostr(x) + ' + ' + inttostr(w) + ') > Width (' + inttostr(Width) + ')', true);
if (y + h > Height) then
- OutError('Object''s rectangle exceeds image: y + h (' + inttostr(y) + ' + ' + inttostr(h) + ') > Height (' + inttostr(Height) + ')', true);
+ OutError('Broken theme. Object''s rectangle exceeds image: y + h (' + inttostr(y) + ' + ' + inttostr(h) + ') > Height (' + inttostr(Height) + ')', true);
+end;
+
+procedure ReadRect(var rect: TSDL_Rect; var s: ShortString);
+var i: LongInt;
+begin
+with rect do
+ begin
+ i:= Pos(',', s);
+ x:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ y:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ w:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ if i = 0 then i:= Succ(Length(S));
+ h:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ end;
+end;
+
+
+
+procedure ReadOverlay(var overlay: TThemeObjectOverlay; var s: ShortString);
+var i: LongInt;
+begin
+with overlay do
+ begin
+ i:= Pos(',', s);
+ Position.X:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ Position.Y:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ if i = 0 then i:= Succ(Length(S));
+ Surf:= LoadDataImage(ptCurrTheme, Trim(Copy(s, 1, Pred(i))), ifColorKey or ifIgnoreCaps or ifCritical);
+ Width:= Surf^.w;
+ Height:= Surf^.h;
+ Delete(s, 1, i);
+ ChecksumLandObjectImage(Surf);
+ end;
end;
procedure ReadThemeInfo(var ThemeObjects: TThemeObjects; var SprayObjects: TSprayObjects);
-var s, key: shortstring;
+var s, key, nameRef: shortstring;
f: PFSFile;
- i, y: LongInt;
+ i: LongInt;
ii, t: Longword;
c2: TSDL_Color;
begin
@@ -528,7 +774,8 @@
s:= cPathz[ptCurrTheme] + '/' + cThemeCFGFilename;
WriteLnToConsole('Reading objects info...');
f:= pfsOpenRead(s);
-if checkFails(f <> nil, 'Bad data or cannot access file ' + s, true) then exit;
+if (f = nil) then
+ OutError('Error loading theme. File could not be opened: ' + s, true);
ThemeObjects.Count:= 0;
SprayObjects.Count:= 0;
@@ -687,7 +934,8 @@
with ThemeObjects.objs[Pred(ThemeObjects.Count)] do
begin
i:= Pos(',', s);
- Surf:= LoadDataImage(ptCurrTheme, Trim(Copy(s, 1, Pred(i))), ifColorKey or ifIgnoreCaps or ifCritical);
+ Name:= Trim(Copy(s, 1, Pred(i)));
+ Surf:= LoadDataImage(ptCurrTheme, Name, ifColorKey or ifIgnoreCaps or ifCritical);
Width:= Surf^.w;
Height:= Surf^.h;
Mask:= LoadDataImage(ptCurrTheme, Trim(Copy(s, 1, Pred(i)))+'_mask', ifColorKey or ifIgnoreCaps);
@@ -696,9 +944,9 @@
Maxcnt:= StrToInt(Trim(Copy(s, 1, Pred(i))));
Delete(s, 1, i);
if (Maxcnt < 1) or (Maxcnt > MAXTHEMEOBJECTS) then
- OutError('Object''s max count should be between 1 and '+ inttostr(MAXTHEMEOBJECTS) +' (it was '+ inttostr(Maxcnt) +').', true);
- for y := 0 to Surf^.h-1 do
- syncedPixelDigest:= Adler32Update(syncedPixelDigest, @PByteArray(Surf^.pixels)^[y*Surf^.pitch], Surf^.w*4);
+ OutError('Broken theme. Object''s max. count should be between 1 and '+ inttostr(MAXTHEMEOBJECTS) +' (it was '+ inttostr(Maxcnt) +').', true);
+ ChecksumLandObjectImage(Surf);
+ ChecksumLandObjectImage(Mask);
inrectcnt := 0;
@@ -714,50 +962,61 @@
Delete(s, 1, i);
end;
- for ii:= 1 to inrectcnt do
- with inland[ii] do
- begin
- i:= Pos(',', s);
- x:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- i:= Pos(',', s);
- y:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- i:= Pos(',', s);
- w:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- i:= Pos(',', s);
- h:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- CheckRect(Width, Height, x, y, w, h)
- end;
+ if inrectcnt > MAXOBJECTRECTS then
+ OutError('Broken theme. Object''s inland rectangle count should be no more than '+ inttostr(MAXOBJECTRECTS) +' (it was '+ inttostr(inrectcnt) +').', true);
+
+ for ii:= 0 to Pred(inrectcnt) do
+ ReadRect(inland[ii], s);
i:= Pos(',', s);
outrectcnt:= StrToInt(Trim(Copy(s, 1, Pred(i))));
Delete(s, 1, i);
- for ii:= 1 to outrectcnt do
- with outland[ii] do
- begin
- i:= Pos(',', s);
- x:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- i:= Pos(',', s);
- y:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- i:= Pos(',', s);
- w:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i);
- if ii = outrectcnt then
- h:= StrToInt(Trim(s))
- else
- begin
- i:= Pos(',', s);
- h:= StrToInt(Trim(Copy(s, 1, Pred(i))));
- Delete(s, 1, i)
- end;
- CheckRect(Width, Height, x, y, w, h)
- end;
+
+ if outrectcnt > MAXOBJECTRECTS then
+ OutError('Broken theme. Object''s outland rectangle count should be no more than '+ inttostr(MAXOBJECTRECTS) +' (it was '+ inttostr(outrectcnt) +').', true);
+ for ii:= 0 to Pred(outrectcnt) do
+ ReadRect(outland[ii], s);
+ end;
+ end
+ else if key = 'anchors' then
+ begin
+ i:= Pos(',', s);
+ nameRef:= Trim(Copy(s, 1, Pred(i)));
+ for ii:= 0 to Pred(ThemeObjects.Count) do
+ if ThemeObjects.objs[ii].Name = nameRef then with ThemeObjects.objs[ii] do
+ begin
+ if anchorcnt <> 0 then
+ OutError('Broken theme. Duplicate anchors declaration for object ' + nameRef, true);
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ anchorcnt:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ if anchorcnt > MAXOBJECTRECTS then
+ OutError('Broken theme. Object''s anchor rectangle count should be no more than '+ inttostr(MAXOBJECTRECTS) +' (it was '+ inttostr(anchorcnt) +').', true);
+ for t:= 0 to Pred(anchorcnt) do
+ ReadRect(anchors[t], s);
+ break
+ end;
+ end
+ else if key = 'overlays' then
+ begin
+ i:= Pos(',', s);
+ nameRef:= Trim(Copy(s, 1, Pred(i)));
+ for ii:= 0 to Pred(ThemeObjects.Count) do
+ if ThemeObjects.objs[ii].Name = nameRef then with ThemeObjects.objs[ii] do
+ begin
+ if overlaycnt <> 0 then
+ OutError('Broken theme. Duplicate overlays declaration for object ' + nameRef, true);
+ Delete(s, 1, i);
+ i:= Pos(',', s);
+ overlaycnt:= StrToInt(Trim(Copy(s, 1, Pred(i))));
+ Delete(s, 1, i);
+ if overlaycnt > MAXOBJECTRECTS then
+ OutError('Broken theme. Object''s overlay count should be no more than '+ inttostr(MAXOBJECTRECTS) +' (it was '+ inttostr(overlaycnt) +').', true);
+ for t:= 0 to Pred(overlaycnt) do
+ ReadOverlay(overlays[t], s);
+ break
end;
end
else if key = 'spray' then
@@ -820,6 +1079,10 @@
cIce:= true
else if key = 'snow' then
cSnow:= true
+ else if key = 'rope-step' then
+ cRopeNodeStep:= max(1, StrToInt(s))
+ else if key = 'rope-layers' then
+ cRopeLayers:= max(1, min(MAXROPELAYERS, StrToInt(s)))
else if key = 'sd-water-top' then
begin
i:= Pos(',', s);
@@ -1009,7 +1272,7 @@
end;
procedure FreeLandObjects();
-var i: Longword;
+var i, ii: Longword;
begin
for i:= 0 to Pred(MAXTHEMEOBJECTS) do
begin
@@ -1019,6 +1282,17 @@
SDL_FreeSurface(SprayObjects.objs[i].Surf);
ThemeObjects.objs[i].Surf:= nil;
SprayObjects.objs[i].Surf:= nil;
+
+ ii:= 0;
+ while ii < ThemeObjects.objs[i].overlaycnt do
+ begin
+ if ThemeObjects.objs[i].overlays[ii].Surf <> nil then
+ begin
+ SDL_FreeSurface(ThemeObjects.objs[i].overlays[ii].Surf);
+ ThemeObjects.objs[i].overlays[ii].Surf:= nil;
+ end;
+ inc(ii);
+ end;
end;
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandOutline.pas
--- a/hedgewars/uLandOutline.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandOutline.pas Wed Jul 31 23:14:27 2019 +0200
@@ -12,12 +12,10 @@
procedure DrawEdge(var pa: TPixAr; value: Word);
procedure FillLand(x, y: LongInt; border, value: Word);
procedure BezierizeEdge(var pa: TPixAr; Delta: hwFloat);
-procedure RandomizePoints(var pa: TPixAr);
implementation
-uses uLandGraphics, uDebug, uVariables, uLandTemplates, uRandom, uUtils;
-
+uses uLandGraphics, uDebug, uVariables, uLandTemplates;
var Stack: record
@@ -217,96 +215,4 @@
end else inc(i)
end;
-
-function CheckIntersect(V1, V2, V3, V4: TPoint): boolean;
-var c1, c2, dm: LongInt;
-begin
- CheckIntersect:= false;
- dm:= (V4.y - V3.y) * (V2.x - V1.x) - (V4.x - V3.x) * (V2.y - V1.y);
- c1:= (V4.x - V3.x) * (V1.y - V3.y) - (V4.y - V3.y) * (V1.x - V3.x);
- if dm = 0 then
- exit;
-
- CheckIntersect:= true;
- c2:= (V2.x - V3.x) * (V1.y - V3.y) - (V2.y - V3.y) * (V1.x - V3.x);
- if dm > 0 then
- begin
- if (c1 < 0) or (c1 > dm) then
- CheckIntersect:= false
- else if (c2 < 0) or (c2 > dm) then
- CheckIntersect:= false;
- end
- else
- begin
- if (c1 > 0) or (c1 < dm) then
- CheckIntersect:= false
- else if (c2 > 0) or (c2 < dm) then
- CheckIntersect:= false;
- end;
-
- //AddFileLog('1 (' + inttostr(V1.x) + ',' + inttostr(V1.y) + ')x(' + inttostr(V2.x) + ',' + inttostr(V2.y) + ')');
- //AddFileLog('2 (' + inttostr(V3.x) + ',' + inttostr(V3.y) + ')x(' + inttostr(V4.x) + ',' + inttostr(V4.y) + ')');
-end;
-
-
-function CheckSelfIntersect(var pa: TPixAr; ind: Longword): boolean;
-var i: Longword;
-begin
- CheckSelfIntersect:= false;
- if (ind <= 0) or (LongInt(ind) >= Pred(pa.Count)) then
- exit;
-
- CheckSelfIntersect:= true;
- for i:= 1 to pa.Count - 3 do
- if (i <= ind - 1) or (i >= ind + 2) then
- begin
- if (i <> ind - 1) and CheckIntersect(pa.ar[ind], pa.ar[ind - 1], pa.ar[i], pa.ar[i - 1]) then
- exit;
- if (i <> ind + 2) and CheckIntersect(pa.ar[ind], pa.ar[ind + 1], pa.ar[i], pa.ar[i - 1]) then
- exit;
- end;
- CheckSelfIntersect:= false
-end;
-
-procedure RandomizePoints(var pa: TPixAr);
-const cEdge = 55;
- cMinDist = 8;
-var radz: array[0..Pred(cMaxEdgePoints)] of LongInt;
- i, k, dist, px, py: LongInt;
-begin
- for i:= 0 to Pred(pa.Count) do
- begin
- radz[i]:= 0;
- with pa.ar[i] do
- if x <> NTPX then
- begin
- radz[i]:= Min(Max(x - cEdge, 0), Max(LAND_WIDTH - cEdge - x, 0));
- radz[i]:= Min(radz[i], Min(Max(y - cEdge, 0), Max(LAND_HEIGHT - cEdge - y, 0)));
- if radz[i] > 0 then
- for k:= 0 to Pred(i) do
- begin
- dist:= Max(abs(x - pa.ar[k].x), abs(y - pa.ar[k].y));
- radz[k]:= Max(0, Min((dist - cMinDist) div 2, radz[k]));
- radz[i]:= Max(0, Min(dist - radz[k] - cMinDist, radz[i]))
- end
- end;
- end;
-
- for i:= 0 to Pred(pa.Count) do
- with pa.ar[i] do
- if ((x and LAND_WIDTH_MASK) = 0) and ((y and LAND_HEIGHT_MASK) = 0) then
- begin
- px:= x;
- py:= y;
- x:= x + LongInt(GetRandom(7) - 3) * (radz[i] * 5 div 7) div 3;
- y:= y + LongInt(GetRandom(7) - 3) * (radz[i] * 5 div 7) div 3;
- if CheckSelfIntersect(pa, i) then
- begin
- x:= px;
- y:= py
- end;
- end
-end;
-
-
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandPainted.pas
--- a/hedgewars/uLandPainted.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandPainted.pas Wed Jul 31 23:14:27 2019 +0200
@@ -58,6 +58,7 @@
rec:= prec^;
rec.X:= SDLNet_Read16(@rec.X);
rec.Y:= SDLNet_Read16(@rec.Y);
+
if rec.X < -318 then rec.X:= -318;
if rec.X > 4096+318 then rec.X:= 4096+318;
if rec.Y < -318 then rec.Y:= -318;
@@ -81,7 +82,7 @@
var pe: PPointEntry;
prevPoint: PointRec;
radius: LongInt;
- color: Longword;
+ color, Xoffset, Yoffset: Longword;
lineNumber, linePoints: Longword;
begin
// shutup compiler
@@ -89,6 +90,8 @@
prevPoint.Y:= 0;
radius:= 0;
linePoints:= 0;
+ Xoffset:= (LAND_WIDTH-(playWidth)) div 2;
+ Yoffset:= LAND_HEIGHT-(playHeight);
pe:= pointsListHead;
while (pe <> nil) and (pe^.point.flags and $80 = 0) do
@@ -101,6 +104,8 @@
while(pe <> nil) do
begin
+ pe^.point.X:= LongInt(pe^.point.X) * playWidth div 4096 + Xoffset;
+ pe^.point.Y:= LongInt(pe^.point.Y) * playHeight div 2048 + Yoffset;
if (pe^.point.flags and $80 <> 0) then
begin
if (lineNumber > 0) and (linePoints = 0) and cAdvancedMapGenMode then
@@ -113,9 +118,10 @@
else
color:= lfBasic;
radius:= (pe^.point.flags and $3F) * 5 + 3;
+ radius:= (radius * playWidth div 4096);
linePoints:= FillRoundInLand(pe^.point.X, pe^.point.Y, radius, color);
end
- else
+ else
begin
inc(linePoints, DrawThickLine(prevPoint.X, prevPoint.Y, pe^.point.X, pe^.point.Y, radius, color));
end;
@@ -123,6 +129,13 @@
prevPoint:= pe^.point;
pe:= pe^.next;
end;
+
+ if (topY > 0) then
+ EraseLandRectRaw(0, 0, LAND_WIDTH, topY);
+ if (leftX > 0) then
+ EraseLandRectRaw(0, topY, leftX, LAND_HEIGHT - topY);
+ if (rightX < (LAND_WIDTH - 1)) then
+ EraseLandRectRaw(rightX + 1, topY, LAND_WIDTH - (rightX + 1), LAND_HEIGHT - topY);
end;
procedure initModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandTemplates.pas
--- a/hedgewars/uLandTemplates.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandTemplates.pas Wed Jul 31 23:14:27 2019 +0200
@@ -31,8 +31,6 @@
BasePointsCount: Longword;
FillPoints: PPointArray;
FillPointsCount: Longword;
- BezierizeCount: Longword;
- RandPassesCount: Longword;
TemplateHeight, TemplateWidth: Longword;
canMirror, canFlip, isNegative, canInvert: boolean;
hasGirders: boolean;
@@ -1805,7 +1803,6 @@
(x:1005; y: 805; w: 0; h: 0)
);
-
const Template46Points: array[0..19] of TSDL_Rect =
(
(x: 800; y: 1424; w: 1; h: 1),
@@ -1842,8 +1839,6 @@
BasePointsCount: Succ(High(Template0Points));
FillPoints: PPointArray(@Template0FPoints);
FillPointsCount: Succ(High(Template0FPoints));
- BezierizeCount: 3;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1853,8 +1848,6 @@
BasePointsCount: Succ(High(Template1Points));
FillPoints: PPointArray(@Template1FPoints);
FillPointsCount: Succ(High(Template1FPoints));
- BezierizeCount: 3;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1864,8 +1857,6 @@
BasePointsCount: Succ(High(Template2Points));
FillPoints: PPointArray(@Template2FPoints);
FillPointsCount: Succ(High(Template2FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1875,8 +1866,6 @@
BasePointsCount: Succ(High(Template3Points));
FillPoints: PPointArray(@Template3FPoints);
FillPointsCount: Succ(High(Template3FPoints));
- BezierizeCount: 3;
- RandPassesCount: 4;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1886,8 +1875,6 @@
BasePointsCount: Succ(High(Template4Points));
FillPoints: PPointArray(@Template4FPoints);
FillPointsCount: Succ(High(Template4FPoints));
- BezierizeCount: 3;
- RandPassesCount: 4;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1897,8 +1884,6 @@
BasePointsCount: Succ(High(Template5Points));
FillPoints: PPointArray(@Template5FPoints);
FillPointsCount: Succ(High(Template5FPoints));
- BezierizeCount: 2;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1908,8 +1893,6 @@
BasePointsCount: Succ(High(Template6Points));
FillPoints: PPointArray(@Template6FPoints);
FillPointsCount: Succ(High(Template6FPoints));
- BezierizeCount: 2;
- RandPassesCount: 5;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1919,8 +1902,6 @@
BasePointsCount: Succ(High(Template7Points));
FillPoints: PPointArray(@Template7FPoints);
FillPointsCount: Succ(High(Template7FPoints));
- BezierizeCount: 4;
- RandPassesCount: 4;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1930,8 +1911,6 @@
BasePointsCount: Succ(High(Template8Points));
FillPoints: PPointArray(@Template8FPoints);
FillPointsCount: Succ(High(Template8FPoints));
- BezierizeCount: 2;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1941,8 +1920,6 @@
BasePointsCount: Succ(High(Template9Points));
FillPoints: PPointArray(@Template9FPoints);
FillPointsCount: Succ(High(Template9FPoints));
- BezierizeCount: 1;
- RandPassesCount: 5;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1952,8 +1929,6 @@
BasePointsCount: Succ(High(Template10Points));
FillPoints: PPointArray(@Template10FPoints);
FillPointsCount: Succ(High(Template10FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1963,8 +1938,6 @@
BasePointsCount: Succ(High(Template11Points));
FillPoints: PPointArray(@Template11FPoints);
FillPointsCount: Succ(High(Template11FPoints));
- BezierizeCount: 1;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1974,8 +1947,6 @@
BasePointsCount: Succ(High(Template12Points));
FillPoints: PPointArray(@Template12FPoints);
FillPointsCount: Succ(High(Template12FPoints));
- BezierizeCount: 3;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1985,8 +1956,6 @@
BasePointsCount: Succ(High(Template13Points));
FillPoints: PPointArray(@Template13FPoints);
FillPointsCount: Succ(High(Template13FPoints));
- BezierizeCount: 3;
- RandPassesCount: 5;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -1996,8 +1965,6 @@
BasePointsCount: Succ(High(Template14Points));
FillPoints: PPointArray(@Template14FPoints);
FillPointsCount: Succ(High(Template14FPoints));
- BezierizeCount: 3;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2007,8 +1974,6 @@
BasePointsCount: Succ(High(Template15Points));
FillPoints: PPointArray(@Template15FPoints);
FillPointsCount: Succ(High(Template15FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2018,8 +1983,6 @@
BasePointsCount: Succ(High(Template16Points));
FillPoints: PPointArray(@Template16FPoints);
FillPointsCount: Succ(High(Template16FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2029,8 +1992,6 @@
BasePointsCount: Succ(High(Template17Points));
FillPoints: PPointArray(@Template17FPoints);
FillPointsCount: Succ(High(Template17FPoints));
- BezierizeCount: 3;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2040,8 +2001,6 @@
BasePointsCount: Succ(High(Template18Points));
FillPoints: PPointArray(@Template18FPoints);
FillPointsCount: Succ(High(Template18FPoints));
- BezierizeCount: 3;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2051,8 +2010,6 @@
BasePointsCount: Succ(High(Template19Points));
FillPoints: PPointArray(@Template19FPoints);
FillPointsCount: Succ(High(Template19FPoints));
- BezierizeCount: 3;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2062,8 +2019,6 @@
BasePointsCount: Succ(High(Template20Points));
FillPoints: PPointArray(@Template20FPoints);
FillPointsCount: Succ(High(Template20FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2073,8 +2028,6 @@
BasePointsCount: Succ(High(Template21Points));
FillPoints: PPointArray(@Template21FPoints);
FillPointsCount: Succ(High(Template21FPoints));
- BezierizeCount: 3;
- RandPassesCount: 4;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2084,8 +2037,6 @@
BasePointsCount: Succ(High(Template22Points));
FillPoints: PPointArray(@Template22FPoints);
FillPointsCount: Succ(High(Template22FPoints));
- BezierizeCount: 3;
- RandPassesCount: 4;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2095,8 +2046,6 @@
BasePointsCount: Succ(High(Template23Points));
FillPoints: PPointArray(@Template23FPoints);
FillPointsCount: Succ(High(Template23FPoints));
- BezierizeCount: 2;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2106,8 +2055,6 @@
BasePointsCount: Succ(High(Template24Points));
FillPoints: PPointArray(@Template24FPoints);
FillPointsCount: Succ(High(Template24FPoints));
- BezierizeCount: 2;
- RandPassesCount: 5;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2117,8 +2064,6 @@
BasePointsCount: Succ(High(Template25Points));
FillPoints: PPointArray(@Template25FPoints);
FillPointsCount: Succ(High(Template25FPoints));
- BezierizeCount: 4;
- RandPassesCount: 4;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2128,8 +2073,6 @@
BasePointsCount: Succ(High(Template26Points));
FillPoints: PPointArray(@Template26FPoints);
FillPointsCount: Succ(High(Template26FPoints));
- BezierizeCount: 2;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2139,8 +2082,6 @@
BasePointsCount: Succ(High(Template27Points));
FillPoints: PPointArray(@Template27FPoints);
FillPointsCount: Succ(High(Template27FPoints));
- BezierizeCount: 1;
- RandPassesCount: 5;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2150,8 +2091,6 @@
BasePointsCount: Succ(High(Template28Points));
FillPoints: PPointArray(@Template28FPoints);
FillPointsCount: Succ(High(Template28FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2161,8 +2100,6 @@
BasePointsCount: Succ(High(Template29Points));
FillPoints: PPointArray(@Template29FPoints);
FillPointsCount: Succ(High(Template29FPoints));
- BezierizeCount: 1;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2172,8 +2109,6 @@
BasePointsCount: Succ(High(Template30Points));
FillPoints: PPointArray(@Template30FPoints);
FillPointsCount: Succ(High(Template30FPoints));
- BezierizeCount: 3;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2183,8 +2118,6 @@
BasePointsCount: Succ(High(Template31Points));
FillPoints: PPointArray(@Template31FPoints);
FillPointsCount: Succ(High(Template31FPoints));
- BezierizeCount: 3;
- RandPassesCount: 5;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2194,8 +2127,6 @@
BasePointsCount: Succ(High(Template32Points));
FillPoints: PPointArray(@Template32FPoints);
FillPointsCount: Succ(High(Template32FPoints));
- BezierizeCount: 3;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2205,8 +2136,6 @@
BasePointsCount: Succ(High(Template33Points));
FillPoints: PPointArray(@Template33FPoints);
FillPointsCount: Succ(High(Template33FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2216,8 +2145,6 @@
BasePointsCount: Succ(High(Template34Points));
FillPoints: PPointArray(@Template34FPoints);
FillPointsCount: Succ(High(Template34FPoints));
- BezierizeCount: 2;
- RandPassesCount: 6;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2227,8 +2154,6 @@
BasePointsCount: Succ(High(Template35Points));
FillPoints: PPointArray(@Template35FPoints);
FillPointsCount: Succ(High(Template35FPoints));
- BezierizeCount: 3;
- RandPassesCount: 7;
TemplateHeight: 1424; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2238,8 +2163,6 @@
BasePointsCount: Succ(High(Template36Points));
FillPoints: PPointArray(@Template36FPoints);
FillPointsCount: Succ(High(Template36FPoints));
- BezierizeCount: 4;
- RandPassesCount: 12;
TemplateHeight: 1024; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: true; canInvert: false;
hasGirders: false;
@@ -2249,8 +2172,6 @@
BasePointsCount: Succ(High(Template37Points));
FillPoints: PPointArray(@Template37FPoints);
FillPointsCount: Succ(High(Template37FPoints));
- BezierizeCount: 3;
- RandPassesCount: 3;
TemplateHeight: 2048; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2260,8 +2181,6 @@
BasePointsCount: Succ(High(Template38Points));
FillPoints: PPointArray(@Template38FPoints);
FillPointsCount: Succ(High(Template38FPoints));
- BezierizeCount: 4;
- RandPassesCount: 4;
TemplateHeight: 2048; TemplateWidth: 4096;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2271,8 +2190,6 @@
BasePointsCount: Succ(High(Template39Points));
FillPoints: PPointArray(@Template39FPoints);
FillPointsCount: Succ(High(Template39FPoints));
- BezierizeCount: 3;
- RandPassesCount: 3;
TemplateHeight: 512; TemplateWidth: 1536;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: false;
@@ -2282,8 +2199,6 @@
BasePointsCount: Succ(High(Template40Points));
FillPoints: PPointArray(@Template40FPoints);
FillPointsCount: Succ(High(Template40FPoints));
- BezierizeCount: 3;
- RandPassesCount: 3;
TemplateHeight: 1024; TemplateWidth: 1024;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: false;
@@ -2293,8 +2208,6 @@
BasePointsCount: Succ(High(Template41Points));
FillPoints: PPointArray(@Template41FPoints);
FillPointsCount: Succ(High(Template41FPoints));
- BezierizeCount: 2;
- RandPassesCount: 9;
TemplateHeight: 2048; TemplateWidth: 4096;
canMirror: true; canFlip: true; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2304,8 +2217,6 @@
BasePointsCount: Succ(High(Template42Points));
FillPoints: PPointArray(@Template42FPoints);
FillPointsCount: Succ(High(Template42FPoints));
- BezierizeCount: 3;
- RandPassesCount: 3;
TemplateHeight: 512; TemplateWidth: 1536;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: false;
@@ -2315,19 +2226,15 @@
BasePointsCount: Succ(High(Template43Points));
FillPoints: PPointArray(@Template43FPoints);
FillPointsCount: Succ(High(Template43FPoints));
- BezierizeCount: 2;
- RandPassesCount: 9;
TemplateHeight: 4096; TemplateWidth: 4096;
canMirror: true; canFlip: true; isNegative: false; canInvert: false;
hasGirders: true;
- MaxHedgeHogs: 48;
+ MaxHedgeHogs: 64;
),
(BasePoints: PPointArray(@Template44Points);
BasePointsCount: Succ(High(Template44Points));
FillPoints: PPointArray(@Template44FPoints);
FillPointsCount: Succ(High(Template44FPoints));
- BezierizeCount: 5;
- RandPassesCount: 3;
TemplateHeight: 2048; TemplateWidth: 4096;
canMirror: false; canFlip: false; isNegative: true; canInvert: false;
hasGirders: false;
@@ -2337,8 +2244,6 @@
BasePointsCount: Succ(High(Template45Points));
FillPoints: PPointArray(@Template45FPoints);
FillPointsCount: Succ(High(Template45FPoints));
- BezierizeCount: 5;
- RandPassesCount: 7;
TemplateHeight: 2048; TemplateWidth: 4096;
canMirror: false; canFlip: false; isNegative: true; canInvert: false;
hasGirders: false;
@@ -2348,8 +2253,6 @@
BasePointsCount: Succ(High(Template46Points));
FillPoints: PPointArray(@Template46FPoints);
FillPointsCount: Succ(High(Template46FPoints));
- BezierizeCount: 2;
- RandPassesCount: 8;
TemplateHeight: 1424; TemplateWidth: 3072;
canMirror: true; canFlip: false; isNegative: false; canInvert: false;
hasGirders: true;
@@ -2359,13 +2262,12 @@
const SmallTemplates: array[0..2] of Longword = ( 39, 40, 42 );
const MediumTemplates: array[0..18] of Longword =
( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 46 );
-const LargeTemplates: array[0..20] of Longword =
+const LargeTemplates: array[0..17] of Longword =
(
18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
- 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 43
+ 28, 29, 30, 31, 32, 33, 34, 35
);
const CavernTemplates: array[0..5] of Longword = (36, 2, 3, 21, 29, 45);
-//const WackyTemplates: array[0..4] of Longword = (37, 38, 39, 40, 41);
const WackyTemplates: array[0..4] of Longword = (37, 38, 41, 43, 44);
const TemplateCounts: array[0..5] of Longword = (
0
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLandUtils.pas
--- a/hedgewars/uLandUtils.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLandUtils.pas Wed Jul 31 23:14:27 2019 +0200
@@ -42,6 +42,11 @@
lx:= LongInt(LAND_WIDTH) - 1;
+// don't change world edges for drawn maps
+if (cMapGen = mgDrawn) then
+ // edges were adjusted already in GenDrawnMap() in uLand
+ EXIT;
+
// use maximum available map width if there is no special world edge
if WorldEdge = weNone then
begin
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uLocale.pas
--- a/hedgewars/uLocale.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uLocale.pas Wed Jul 31 23:14:27 2019 +0200
@@ -23,21 +23,37 @@
uses uTypes;
const MAX_EVENT_STRINGS = 255;
-const MAX_FORMAT_STRING_SYMBOLS = 9;
procedure LoadLocale(FileName: shortstring);
-function Format(fmt: shortstring; args: array of shortstring): shortstring;
-function FormatA(fmt: ansistring; args: array of ansistring): ansistring;
-function Format(fmt: shortstring; arg: shortstring): shortstring;
-function FormatA(fmt: ansistring; arg: ansistring): ansistring;
function GetEventString(e: TEventId): ansistring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: shortstring; argCount: Byte): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2, arg3: shortstring): shortstring;
+function Format(fmt: shortstring; arg1, arg2: shortstring): shortstring;
+function Format(fmt: shortstring; arg1: shortstring): shortstring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: ansistring; argCount: Byte): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1: ansistring): ansistring;
+
{$IFDEF HWLIBRARY}
procedure LoadLocaleWrapper(path: pchar; userpath: pchar; filename: pchar); cdecl; export;
{$ENDIF}
implementation
-uses uRandom, uUtils, uVariables, uDebug, uPhysFSLayer;
+uses SysUtils, uRandom, uUtils, uVariables, uDebug, uPhysFSLayer;
var trevt: array[TEventId] of array [0..Pred(MAX_EVENT_STRINGS)] of ansistring;
trevt_n: array[TEventId] of integer;
@@ -49,6 +65,11 @@
first: array[TEventId] of boolean;
e: TEventId;
begin
+{- TODO: Add support for localized decimal separator in Pas2C -}
+{$IFNDEF PAS2C}
+lDecimalSeparator:= FormatSettings.DecimalSeparator;
+{$ENDIF}
+
for e:= Low(TEventId) to High(TEventId) do
first[e]:= true;
@@ -102,6 +123,8 @@
trammod[TAmmoStrId(b)]:= s;
5: if (b >=0) and (b <= ord(High(TGoalStrId))) then
trgoal[TGoalStrId(b)]:= s;
+ 6: if (b >=0) and (b <= ord(High(TCmdHelpStrId))) then
+ trcmd[TCmdHelpStrId(b)]:= s;
end;
end;
pfsClose(f);
@@ -117,60 +140,147 @@
end;
// Format the string fmt.
-// Take a shortstring with placeholders %1, %2, %3, etc. and replace
+// Take a shortstring with placeholders %1, %2, %3, ... %9. and replace
// them with the corresponding elements of an array with up to
-// MAX_FORMAT_STRING_SYMBOLS. Important! Each placeholder can only be
-// used exactly once and numbers MUST NOT be skipped (e.g. using %1 and %3
-// but not %2.
-function Format(fmt: shortstring; args: array of shortstring): shortstring;
+// argCount. ArgCount must not be larger than 9.
+// Each placeholder must be used exactly once and numbers MUST NOT be
+// skipped (e.g. using %1 and %3 but not %2.
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: shortstring; argCount: Byte): shortstring;
var i, p: LongInt;
-tempstr: shortstring;
+tempstr, curArg: shortstring;
begin
tempstr:= fmt;
-for i:=0 to MAX_FORMAT_STRING_SYMBOLS - 1 do
+for i:=0 to argCount - 1 do
begin
+ case i of
+ 0: curArg:= arg1;
+ 1: curArg:= arg2;
+ 2: curArg:= arg3;
+ 3: curArg:= arg4;
+ 4: curArg:= arg5;
+ 5: curArg:= arg6;
+ 6: curArg:= arg7;
+ 7: curArg:= arg8;
+ 8: curArg:= arg9;
+ end;
+
+ repeat
p:= Pos('%'+IntToStr(i+1), tempstr);
- if (p = 0) or (i >= Length(args)) then
- break
- else
+ if (p <> 0) then
begin
delete(tempstr, p, 2);
- insert(args[i], tempstr, p);
+ insert(curArg, tempstr, p);
end;
+ until (p = 0);
end;
Format:= tempstr;
end;
// Same as Format, but for ansistring
-function FormatA(fmt: ansistring; args: array of ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: ansistring; argCount: Byte): ansistring;
var i, p: LongInt;
-tempstr: ansistring;
+tempstr, curArg: ansistring;
begin
tempstr:= fmt;
-for i:=0 to MAX_FORMAT_STRING_SYMBOLS - 1 do
+for i:=0 to argCount - 1 do
begin
+ case i of
+ 0: curArg:= arg1;
+ 1: curArg:= arg2;
+ 2: curArg:= arg3;
+ 3: curArg:= arg4;
+ 4: curArg:= arg5;
+ 5: curArg:= arg6;
+ 6: curArg:= arg7;
+ 7: curArg:= arg8;
+ 8: curArg:= arg9;
+ end;
+
+ repeat
p:= Pos('%'+IntToStr(i+1), tempstr);
- if (p = 0) or (i >= Length(args)) then
- break
- else
+ if (p <> 0) then
begin
delete(tempstr, p, 2);
- insert(args[i], tempstr, p);
+ insert(curArg, tempstr, p);
end;
+ until (p = 0);
end;
FormatA:= tempstr;
end;
-// Same as Format above, but with only one placeholder %1, replaced by arg.
-function Format(fmt: shortstring; arg: shortstring): shortstring;
+// The following functions are just shortcuts of Format/FormatA, with fewer argument counts
+function Format(fmt: shortstring; arg1: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, '', '', '', '', '', '', '', '', 1);
+end;
+function Format(fmt: shortstring; arg1, arg2: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, '', '', '', '', '', '', '', 2);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, arg3, '', '', '', '', '', '', 3);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4: shortstring): shortstring;
begin
- Format:= Format(fmt, [arg]);
+ Format:= Format(fmt, arg1, arg2, arg3, arg4, '', '', '', '', '', 4);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, arg3, arg4, arg5, '', '', '', '', 5);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, '', '', '', 6);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, '', '', 7);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, '', 8);
+end;
+function Format(fmt: shortstring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: shortstring): shortstring;
+begin
+ Format:= Format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, 9);
end;
-// Same as above, but for ansistring
-function FormatA(fmt: ansistring; arg: ansistring): ansistring;
+function FormatA(fmt: ansistring; arg1: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), 1);
+end;
+function FormatA(fmt: ansistring; arg1, arg2: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), 2);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), 3);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, arg4, ansistring(''), ansistring(''), ansistring(''), ansistring(''), ansistring(''), 4);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5: ansistring): ansistring;
begin
- FormatA:= FormatA(fmt, [arg]);
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, arg4, arg5, ansistring(''), ansistring(''), ansistring(''), ansistring(''), 5);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, arg4, arg5, arg6, ansistring(''), ansistring(''), ansistring(''), 6);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, ansistring(''), ansistring(''), 7);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, ansistring(''), 8);
+end;
+function FormatA(fmt: ansistring; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9: ansistring): ansistring;
+begin
+ FormatA:= FormatA(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, 9);
end;
{$IFDEF HWLIBRARY}
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uMatrix.pas
--- a/hedgewars/uMatrix.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uMatrix.pas Wed Jul 31 23:14:27 2019 +0200
@@ -260,7 +260,7 @@
write(Result[i, j]);
writeln;
end;
- checkFails(false, 'error in matrix multiplication?!', true);
+ checkFails(false, 'Error in matrix multiplication?!', true);
end;
{$ENDIF}
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uMisc.pas
--- a/hedgewars/uMisc.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uMisc.pas Wed Jul 31 23:14:27 2019 +0200
@@ -26,7 +26,6 @@
procedure initModule;
procedure freeModule;
-procedure movecursor(dx, dy: LongInt);
function doSurfaceConversion(tmpsurf: PSDL_Surface): PSDL_Surface;
function MakeScreenshot(filename: shortstring; k: LongInt; dump: LongWord): boolean;
function GetTeamStatString(p: PTeam): shortstring;
@@ -46,17 +45,6 @@
var conversionFormat : PSDL_PixelFormat;
-procedure movecursor(dx, dy: LongInt);
-var x, y: LongInt;
-begin
-if (dx = 0) and (dy = 0) then exit;
-
-SDL_GetMouseState(@x, @y);
-Inc(x, dx);
-Inc(y, dy);
-SDL_WarpMouse(x, y);
-end;
-
{$IFDEF PNG_SCREENSHOTS}
// this funtion will be executed in separate thread
function SaveScreenshot(screenshot: pointer): LongInt; cdecl; export;
@@ -185,7 +173,7 @@
{$IFDEF USE_VIDEO_RECORDING}
// make image k times smaller (useful for saving thumbnails)
-procedure ReduceImage(img: PByte; width, height, k: LongInt);
+procedure ReduceImage(img: PByteArray; width, height, k: LongInt);
var i, j, i0, j0, w, h, r, g, b: LongInt;
begin
w:= width div k;
@@ -203,14 +191,14 @@
for i0:= 0 to k-1 do
for j0:= 0 to k-1 do
begin
- inc(r, img[4*(width*(i*k+i0) + j*k+j0)+0]);
- inc(g, img[4*(width*(i*k+i0) + j*k+j0)+1]);
- inc(b, img[4*(width*(i*k+i0) + j*k+j0)+2]);
+ inc(r, img^[4*(width*(i*k+i0) + j*k + j0)+0]);
+ inc(g, img^[4*(width*(i*k+i0) + j*k + j0)+1]);
+ inc(b, img^[4*(width*(i*k+i0) + j*k + j0)+2]);
end;
- img[4*(w*i + j)+0]:= r div (k*k);
- img[4*(w*i + j)+1]:= g div (k*k);
- img[4*(w*i + j)+2]:= b div (k*k);
- img[4*(w*i + j)+3]:= 255;
+ img^[4*(w*i + j)+0]:= r div (k*k);
+ img^[4*(w*i + j)+1]:= g div (k*k);
+ img^[4*(w*i + j)+2]:= b div (k*k);
+ img^[4*(w*i + j)+3]:= 255;
end;
end;
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uPhysFSLayer.pas
--- a/hedgewars/uPhysFSLayer.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uPhysFSLayer.pas Wed Jul 31 23:14:27 2019 +0200
@@ -5,10 +5,16 @@
interface
uses SDLh, LuaPas;
-const PhysfsLibName = {$IFDEF PHYSFS_INTERNAL}'libhwphysfs'{$ELSE}'libphysfs'{$ENDIF};
-const PhyslayerLibName = 'libphyslayer';
+const PhysfsLibName =
+{$IFDEF PHYSFS_INTERNAL}
+ 'libhwphysfs'
+{$ELSE}
+ {$IFDEF WIN32_VCPKG}'physfs'{$ELSE}'libphysfs'{$ENDIF};
+{$ENDIF}
+const PhyslayerLibName =
+ {$IFDEF WIN32_VCPKG}'physlayer'{$ELSE}'libphyslayer'{$ENDIF};
-{$IFNDEF WIN32}
+{$IFNDEF WINDOWS}
{$linklib physfs}
{$linklib physlayer}
{$ENDIF}
@@ -29,6 +35,7 @@
procedure pfsReadLn(f: PFSFile; var s: shortstring);
procedure pfsReadLnA(f: PFSFile; var s: ansistring);
procedure pfsWriteLn(f: PFSFile; s: shortstring);
+procedure pfsWriteRaw(f: PFSFile; s: PChar; len: QWord);
function pfsBlockRead(f: PFSFile; buf: pointer; size: Int64): Int64;
function pfsEOF(f: PFSFile): boolean;
@@ -40,7 +47,7 @@
procedure hedgewarsMountPackage(filename: PChar); cdecl; external PhyslayerLibName;
implementation
-uses uConsts, uUtils, uVariables{$IFNDEF PAS2C}{$IFDEF HWLIBRARY}, sysutils{$ENDIF}{$ELSE}, physfs{$ENDIF};
+uses uConsts, uUtils, uVariables{$IFNDEF PAS2C}{$IFDEF HWLIBRARY}, SysUtils{$ENDIF}{$ELSE}, physfs{$ENDIF};
function PHYSFSRWOPS_openRead(fname: PChar): PSDL_RWops; cdecl; external PhyslayerLibName;
function PHYSFSRWOPS_openWrite(fname: PChar): PSDL_RWops; cdecl; external PhyslayerLibName;
@@ -173,6 +180,11 @@
PHYSFS_writeBytes(f, @c, 1);
end;
+procedure pfsWriteRaw(f: PFSFile; s: PChar; len: QWord);
+begin
+ PHYSFS_writeBytes(f, s, len);
+end;
+
function pfsBlockRead(f: PFSFile; buf: pointer; size: Int64): Int64;
var r: Int64;
begin
@@ -202,7 +214,7 @@
{$ENDIF}
begin
{$IFDEF HWLIBRARY}
- //TODO: http://icculus.org/pipermail/physfs/2011-August/001006.html
+ //TODO: https://icculus.org/pipermail/physfs/2011-August/001006.html
cPhysfsId:= shortstring(GetCurrentDir()) + {$IFDEF DARWIN}{$IFNDEF IPHONEOS}'/Hedgewars.app/Contents/MacOS/' + {$ENDIF}{$ENDIF} ' hedgewars';
{$ELSE}
cPhysfsId:= ParamStr(0);
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uRandom.pas
--- a/hedgewars/uRandom.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uRandom.pas Wed Jul 31 23:14:27 2019 +0200
@@ -79,7 +79,7 @@
for i:= t to 54 do
cirbuf[i]:= $A98765 + 68; // odd number
-for i:= 0 to 1023 do
+for i:= 0 to 2047 do
GetNext;
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uRender.pas
--- a/hedgewars/uRender.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uRender.pas Wed Jul 31 23:14:27 2019 +0200
@@ -34,6 +34,7 @@
procedure DrawSpriteClipped (Sprite: TSprite; X, Y, TopY, RightX, BottomY, LeftX: LongInt);
procedure DrawSpriteRotated (Sprite: TSprite; X, Y, Dir: LongInt; Angle: real);
procedure DrawSpriteRotatedF (Sprite: TSprite; X, Y, Frame, Dir: LongInt; Angle: real);
+procedure DrawSpriteRotatedFReal(Sprite: TSprite; X, Y: Real; Frame, Dir: LongInt; Angle: real);
procedure DrawSpritePivotedF(Sprite: TSprite; X, Y, Frame, Dir, PivotX, PivotY: LongInt; Angle: real);
procedure DrawTexture (X, Y: LongInt; Texture: PTexture); inline;
@@ -53,6 +54,8 @@
procedure DrawLine (X0, Y0, X1, Y1, Width: Single; color: LongWord); inline;
procedure DrawLine (X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
+procedure DrawLineWrapped (X0, Y0, X1, Y1, Width: Single; goesLeft: boolean; Wraps: LongWord; color: LongWord); inline;
+procedure DrawLineWrapped (X0, Y0, X1, Y1, Width: Single; goesLeft: boolean; Wraps: LongWord; r, g, b, a: Byte);
procedure DrawLineOnScreen (X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
procedure DrawRect (rect: TSDL_Rect; r, g, b, a: Byte; Fill: boolean);
procedure DrawHedgehog (X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
@@ -65,7 +68,7 @@
procedure RenderClear (mode: TRenderMode);
{$ENDIF}
procedure RenderSetClearColor (r, g, b, a: real);
-procedure Tint (r, g, b, a: Byte); inline;
+procedure Tint (r, g, b, a: Byte);
procedure Tint (c: Longword); inline;
procedure untint(); inline;
procedure setTintAdd (enable: boolean); inline;
@@ -527,7 +530,10 @@
{$IFNDEF PAS2C}
if not Load_GL_VERSION_2_0 then
- halt;
+ begin
+ WriteLnToConsole('Load_GL_VERSION_2_0 returned false!');
+ halt(HaltStartupError);
+ end;
{$ENDIF}
shaderWater:= CompileProgram('water');
@@ -718,7 +724,7 @@
end;
procedure openglRotatef(RotX, RotY, RotZ: GLfloat; dir: LongInt); inline;
-{ workaround for pascal bug http://bugs.freepascal.org/view.php?id=27222 }
+{ workaround for pascal bug https://bugs.freepascal.org/view.php?id=27222 }
var tmpdir: LongInt;
begin
tmpdir:=dir;
@@ -1147,7 +1153,7 @@
if Angle <> 0 then
begin
- // Check the bounding circle
+ // Check the bounding circle
if isCircleOffscreen(X, Y, (sqr(SpritesData[Sprite].Width) + sqr(SpritesData[Sprite].Height)) div 4) then
exit;
end
@@ -1181,6 +1187,45 @@
end;
+procedure DrawSpriteRotatedFReal(Sprite: TSprite; X, Y: Real; Frame, Dir: LongInt; Angle: real);
+begin
+
+ if Angle <> 0 then
+ begin
+ // Check the bounding circle
+ if isCircleOffscreen(round(X), round(Y), (sqr(SpritesData[Sprite].Width) + sqr(SpritesData[Sprite].Height)) div 4) then
+ exit;
+ end
+ else
+ begin
+ if isDxAreaOffscreen(round(X) - SpritesData[Sprite].Width div 2, SpritesData[Sprite].Width) <> 0 then
+ exit;
+ if isDYAreaOffscreen(round(Y) - SpritesData[Sprite].Height div 2 , SpritesData[Sprite].Height) <> 0 then
+ exit;
+ end;
+
+
+ openglPushMatrix;
+ openglTranslatef(X, Y, 0);
+
+// mirror
+ if Dir < 0 then
+ openglScalef(-1.0, 1.0, 1.0);
+
+// apply angle after (conditional) mirroring
+ if Angle <> 0 then
+ openglRotatef(Angle, 0, 0, 1);
+
+ UpdateModelviewProjection;
+
+ DrawSprite(Sprite, -SpritesData[Sprite].Width div 2, -SpritesData[Sprite].Height div 2, Frame);
+
+ openglPopMatrix;
+
+ UpdateModelviewProjection;
+
+end;
+
procedure DrawSpritePivotedF(Sprite: TSprite; X, Y, Frame, Dir, PivotX, PivotY: LongInt; Angle: real);
begin
if Angle <> 0 then
@@ -1213,6 +1258,8 @@
openglTranslatef(-PivotX, -PivotY, 0);
end;
+UpdateModelviewProjection;
+
DrawSprite(Sprite, -SpritesData[Sprite].Width div 2, -SpritesData[Sprite].Height div 2, Frame);
openglPopMatrix;
@@ -1334,11 +1381,17 @@
end;
end;
+// Same as below, but with color as LongWord
procedure DrawLine(X0, Y0, X1, Y1, Width: Single; color: LongWord); inline;
begin
DrawLine(X0, Y0, X1, Y1, Width, (color shr 24) and $FF, (color shr 16) and $FF, (color shr 8) and $FF, color and $FF)
end;
+// Draw line between 2 points
+// X0, Y0: Start point
+// X0, Y1: End point
+// Width: Visual line width
+// r, g, b, a: Color
procedure DrawLine(X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
begin
openglPushMatrix();
@@ -1353,6 +1406,106 @@
UpdateModelviewProjection;
end;
+// Same as below, but with color as a longword
+procedure DrawLineWrapped(X0, Y0, X1, Y1, Width: Single; goesLeft: boolean; Wraps: LongWord; color: LongWord); inline;
+begin
+DrawLineWrapped(X0, Y0, X1, Y1, Width, goesLeft, Wraps, (color shr 24) and $FF, (color shr 16) and $FF, (color shr 8) and $FF, color and $FF);
+end;
+
+// Draw a line between 2 points, but it wraps around the
+// world edge for a given number of times.
+// goesLeft: true if the line direction from the start point is left
+// Wraps: Number of times the line intersects the wrapping world edge
+// r, g, b, a: color
+procedure DrawLineWrapped(X0, Y0, X1, Y1, Width: Single; goesLeft: boolean; Wraps: LongWord; r, g, b, a: Byte);
+var w: LongWord;
+ startX, startY, endX, endY: Single;
+ // total X and Y distance the line travels if you would unwrap it
+ // with this we know the slope of the line.
+ totalX, totalY: Single;
+ // x variable for the line formula
+ x: Single;
+begin
+ openglPushMatrix();
+ openglTranslatef(WorldDx, WorldDy, 0);
+
+ UpdateModelviewProjection;
+
+ startX:= X0;
+ startY:= Y0;
+ if (Wraps = 0) then
+ begin
+ // Wraps=0 is trivial: Just draw one direct connection
+ endX:= X1;
+ endY:= Y1;
+ DrawLineOnScreen(startX, startY, endX, endY, Width, r, g, b, a);
+ end
+ else
+ begin
+ // A wrapping line is drawn using multiple line segments
+ // which stop at the left and right border. We
+ // calculate the points at which the line intersects with the border.
+ // Then we draws all line segments.
+
+ // Calculate position of first wrap point
+ if goesLeft then
+ begin
+ endX:= LeftX;
+ totalX:= (RightX - X1) + (X0 - LeftX);
+ x:= X0 - LeftX;
+ end
+ else
+ begin
+ endX:= RightX;
+ totalX:= (RightX - X0) + (X1 - LeftX);
+ x:= RightX - X0;
+ end;
+ if (Wraps >= 2) then
+ totalX:= totalX + ((RightX - LeftX) * (Wraps-1));
+ totalY:= Y1 - Y0;
+ // Calculate Y of first wrap point using the line formula
+ endY:= Y0 + (totalY / totalX) * x;
+ // Draw line segment between starting point and first wrap point
+ DrawLineOnScreen(startX, startY, endX, endY, Width, r, g, b, a);
+
+ // Calculate and draw all remaining line segments
+ for w:=1 to Wraps do
+ begin
+ startY:= endY;
+ if goesLeft then
+ begin
+ startX:= RightX;
+ if w < Wraps then
+ endX:= LeftX
+ else
+ endX:= X1;
+ end
+ else
+ begin
+ startX:= LeftX;
+ if w < Wraps then
+ endX:= RightX
+ else
+ endX:= X1;
+ end;
+ if w < Wraps then
+ begin
+ x:= x + (RightX - LeftX);
+ endY:= Y0 + (totalY / totalX) * x;
+ end
+ else
+ endY:= Y1;
+
+ DrawLineOnScreen(startX, startY, endX, endY, Width, r, g, b, a);
+ end;
+
+ end;
+
+ openglPopMatrix();
+
+ UpdateModelviewProjection;
+end;
+
procedure DrawLineOnScreen(X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
begin
glEnable(GL_LINE_SMOOTH);
@@ -1734,8 +1887,8 @@
else
PrepareVbForWater(true,
OffsetY + WorldDy + cWaterLine, ViewTopY,
- LongInt(LeftX) + WorldDx - OffsetX, ViewLeftX,
- LongInt(RightX) + WorldDx + OffsetX, ViewRightX,
+ leftX + WorldDx - OffsetX, ViewLeftX,
+ rightX + WorldDx + OffsetX, ViewRightX,
ViewBottomY,
first, count);
@@ -1825,8 +1978,8 @@
dY:= -cWaveHeight + dy;
ox:= -cWaveHeight + ox;
-lx:= LongInt(LeftX) + WorldDx - ox;
-rx:= LongInt(RightX) + WorldDx + ox;
+lx:= leftX + WorldDx - ox;
+rx:= rightX + WorldDx + ox;
topy:= cWaterLine + WorldDy + dY;
@@ -1933,7 +2086,7 @@
{$ENDIF}
end;
-procedure Tint(r, g, b, a: Byte); inline;
+procedure Tint(r, g, b, a: Byte);
var
nc, tw: Longword;
begin
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uRenderUtils.pas
--- a/hedgewars/uRenderUtils.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uRenderUtils.pas Wed Jul 31 23:14:27 2019 +0200
@@ -38,6 +38,8 @@
function RenderStringTexLim(s: ansistring; Color: Longword; font: THWFont; maxLength: LongWord): PTexture;
function RenderSpeechBubbleTex(s: ansistring; SpeechType: Longword; font: THWFont): PTexture;
+function IsTooDarkToRead(TextColor: Longword): boolean; inline;
+
implementation
uses uVariables, uConsts, uTextures, SysUtils, uUtils, uDebug;
@@ -46,7 +48,7 @@
begin
r:= rect^;
if Clear then
- SDL_FillRect(Surface, @r, 0);
+ SDL_FillRect(Surface, @r, SDL_MapRGBA(Surface^.format, 0, 0, 0, 0));
BorderColor:= SDL_MapRGB(Surface^.format, BorderColor shr 16, BorderColor shr 8, BorderColor and $FF);
FillColor:= SDL_MapRGB(Surface^.format, FillColor shr 16, FillColor shr 8, FillColor and $FF);
@@ -76,11 +78,20 @@
WriteInRoundRect:= WriteInRoundRect(Surface, X, Y, Color, Font, s, 0);
end;*)
+function IsTooDarkToRead(TextColor: LongWord): boolean; inline;
+var clr: TSDL_Color;
+begin
+ clr.r:= (TextColor shr 16) and $FF;
+ clr.g:= (TextColor shr 8) and $FF;
+ clr.b:= TextColor and $FF;
+ IsTooDarkToRead:= not ((clr.r >= cInvertTextColorAt) or (clr.g >= cInvertTextColorAt) or (clr.b >= cInvertTextColorAt));
+end;
+
function WriteInRoundRect(Surface: PSDL_Surface; X, Y: LongInt; Color: LongWord; Font: THWFont; s: ansistring; maxLength: LongWord): TSDL_Rect;
var w, h: Longword;
tmpsurf: PSDL_Surface;
+ finalRect, textRect: TSDL_Rect;
clr: TSDL_Color;
- finalRect, textRect: TSDL_Rect;
begin
TTF_SizeUTF8(Fontz[Font].Handle, PChar(s), @w, @h);
if (maxLength > 0) and (w > maxLength * HDPIScaleFactor) then w := maxLength * HDPIScaleFactor;
@@ -92,10 +103,14 @@
textRect.y:= Y;
textRect.w:= w;
textRect.h:= h;
- DrawRoundRect(@finalRect, cWhiteColor, cNearBlackColor, Surface, true);
clr.r:= (Color shr 16) and $FF;
clr.g:= (Color shr 8) and $FF;
clr.b:= Color and $FF;
+ clr.a:= $FF;
+ if (not IsTooDarkToRead(Color)) then
+ DrawRoundRect(@finalRect, cWhiteColor, cNearBlackColor, Surface, true)
+ else
+ DrawRoundRect(@finalRect, cNearBlackColor, cWhiteColor, Surface, true);
tmpsurf:= TTF_RenderUTF8_Blended(Fontz[Font].Handle, PChar(s), clr);
finalRect.x:= X + cFontBorder + cFontPadding;
finalRect.y:= Y + cFontBorder;
@@ -399,7 +414,7 @@
finalSurface, tmpsurf, rotatedEdge: PSDL_Surface;
rect: TSDL_Rect;
{$IFNDEF PAS2C}
- chars: set of char = [#9,' ',';',':','?','!',','];
+ breakChars: set of char = [#9,' ','-'];
{$ENDIF}
substr: ansistring;
edge, corner, tail: TSPrite;
@@ -449,7 +464,7 @@
w:= 0;
i:= round(Sqrt(length(s)) * 2);
{$IFNDEF PAS2C}
- s:= WrapText(s, #1, chars, i);
+ s:= WrapText(s, #1, breakChars, i);
{$ENDIF}
pos:= 1; line:= 0;
// Find the longest line for the purposes of centring the text. Font dependant.
@@ -554,7 +569,7 @@
rect.h:= textHeight + cornerHeight * 2 - edgeHeight * 2;
i:= rect.w;
j:= rect.h;
- SDL_FillRect(finalSurface, @rect, cWhiteColor);
+ SDL_FillRect(finalSurface, @rect, SDL_MapRGB(finalSurface^.format, cWhiteColor shr 16, cWhiteColor shr 8, cWhiteColor and $FF));
pos:= 1; line:= 0;
while GetNextSpeechLine(s, #1, pos, substr) do
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uScript.pas
--- a/hedgewars/uScript.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uScript.pas Wed Jul 31 23:14:27 2019 +0200
@@ -34,12 +34,13 @@
procedure ScriptPrintStack;
procedure ScriptClearStack;
-procedure ScriptLoad(name : shortstring);
+function ScriptLoad(name : shortstring; mustExist : boolean): boolean;
procedure ScriptOnPreviewInit;
procedure ScriptOnGameInit;
procedure ScriptOnScreenResize;
procedure ScriptSetInteger(name : shortstring; value : LongInt);
procedure ScriptSetString(name : shortstring; value : shortstring);
+procedure ScriptSetMapGlobals;
procedure ScriptCall(fname : shortstring);
function ScriptCall(fname : shortstring; par1: LongInt) : LongInt;
@@ -86,6 +87,7 @@
uVisualGearsList,
uGearsHandlersMess,
uPhysFSLayer,
+ uLocale,
SDLh
{$IFNDEF PAS2C}
, typinfo
@@ -98,19 +100,21 @@
ScriptAmmoDelay : shortstring;
ScriptAmmoReinforcement : shortstring;
ScriptLoaded : boolean;
- mapDims : boolean;
PointsBuffer: shortstring;
PrevCursorX, PrevCursorY: LongInt;
+ PendingTurnTimeLeft, PendingReadyTimeLeft: LongWord;
+ isPendingTurnTimeLeft, isPendingReadyTimeLeft: boolean;
{$IFDEF USE_LUA_SCRIPT}
procedure ScriptPrepareAmmoStore; forward;
procedure ScriptApplyAmmoStore; forward;
procedure ScriptSetAmmo(ammo : TAmmoType; count, probability, delay, reinforcement: Byte); forward;
-procedure ScriptSetAmmoDelay(ammo : TAmmoType; delay: Byte); forward;
+procedure ScriptSetAmmoDelay(ammo : TAmmoType; delay: LongWord); forward;
var LuaDebugInfo: lua_Debug;
procedure SetGlobals; forward;
+procedure GetGlobals; forward;
procedure LuaParseString(s: shortString);
begin
SetGlobals;
@@ -120,7 +124,9 @@
begin
AddFileLog('[Lua] input string parsing error!');
AddChatString(#5 + '[Lua] Error while parsing!');
- end;
+ end
+ else
+ GetGlobals();
end;
function LuaUpdateDebugInfo(): Boolean;
@@ -135,26 +141,43 @@
exit(true);
end;
-procedure LuaError(s: shortstring);
-var src: shortstring;
+procedure LuaErrorOrWarning(s: shortstring; isWarning: boolean);
+var src, intro: shortstring;
const
maxsrclen = 20;
begin
+ if isWarning then
+ intro:= 'LUA WARNING'
+ else
+ intro:= 'LUA ERROR';
if LuaUpdateDebugInfo() then
begin
src:= StrPas(LuaDebugInfo.source);
- s:= 'LUA ERROR [ ... '
+ s:= intro + ': [ ... '
+ copy(src, Length(src) - maxsrclen, maxsrclen - 3) + ':'
+ inttostr(LuaDebugInfo.currentLine) + ']: ' + s;
end
else
- s:= 'LUA ERROR: ' + s;
+ s:= intro + ': ' + s;
WriteLnToConsole(s);
- AddChatString(#5 + s);
- if cTestLua then
+ if isWarning then
+ AddChatString(#0 + s)
+ else
+ AddChatString(#5 + s);
+ if cTestLua and (not isWarning) then
halt(HaltTestLuaError);
end;
+procedure LuaError(s: shortstring);
+begin
+ LuaErrorOrWarning(s, false);
+end;
+
+procedure LuaWarning(s: shortstring);
+begin
+ LuaErrorOrWarning(s, true);
+end;
+
procedure LuaCallError(error, call, paramsyntax: shortstring);
begin
LuaError(call + ': ' + error);
@@ -544,9 +567,13 @@
end;
function lc_showmission(L : Plua_State) : LongInt; Cdecl;
-begin
- if CheckLuaParamCount(L, 5, 'ShowMission', 'caption, subcaption, text, icon, time') then
- ShowMission(lua_tostringA(L, 1), lua_tostringA(L, 2), lua_tostringA(L, 3), Trunc(lua_tonumber(L, 4)), Trunc(lua_tonumber(L, 5)));
+var n: LongInt;
+begin
+ if CheckAndFetchParamCount(L, 5, 6, 'ShowMission', 'caption, subcaption, text, icon, time [, forceDisplay]', n) then
+ if n = 5 then
+ ShowMission(lua_tostringA(L, 1), lua_tostringA(L, 2), lua_tostringA(L, 3), Trunc(lua_tonumber(L, 4)), Trunc(lua_tonumber(L, 5)))
+ else
+ ShowMission(lua_tostringA(L, 1), lua_tostringA(L, 2), lua_tostringA(L, 3), Trunc(lua_tonumber(L, 4)), Trunc(lua_tonumber(L, 5)), lua_toboolean(L, 6));
lc_showmission:= 0;
end;
@@ -560,10 +587,18 @@
function lc_setammotexts(L : Plua_State) : LongInt; Cdecl;
const
call = 'SetAmmoTexts';
- params = 'ammoType, name, caption, description';
-begin
- if CheckLuaParamCount(L, 4, call, params) then
- SetAmmoTexts(TAmmoType(LuaToAmmoTypeOrd(L, 1, call, params)), lua_tostringA(L, 2), lua_tostringA(L, 3), lua_tostringA(L, 4));
+ params = 'ammoType, name, caption, description [, showExtra]';
+var n: integer;
+ showExtra: boolean;
+begin
+ if CheckAndFetchParamCount(L, 4, 5, call, params, n) then
+ begin
+ if n = 5 then
+ showExtra:= lua_toboolean(L, 5)
+ else
+ showExtra:= true;
+ SetAmmoTexts(TAmmoType(LuaToAmmoTypeOrd(L, 1, call, params)), lua_tostringA(L, 2), lua_tostringA(L, 3), lua_tostringA(L, 4), showExtra);
+ end;
lc_setammotexts:= 0;
end;
@@ -638,7 +673,7 @@
if CheckAndFetchParamCount(L, 1, 3, call, params, cg) then
begin
if cg = 1 then
- AddCaption(lua_tostringA(L, 1), cWhiteColor, capgrpMessage)
+ AddCaption(lua_tostringA(L, 1), capcolDefault, capgrpMessage)
else
begin
cg:= LuaToCapGroupOrd(L, 3, call, params);
@@ -651,11 +686,20 @@
function lc_spawnfakehealthcrate(L: Plua_State) : LongInt; Cdecl;
var gear: PGear;
-begin
- if CheckLuaParamCount(L, 4,'SpawnFakeHealthCrate', 'x, y, explode, poison') then
+ explode, poison: boolean;
+ n: LongInt;
+begin
+ if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeHealthCrate', 'x, y [, explode [, poison]]', n) then
begin
+ explode:= false;
+ poison:= false;
+ if (n >= 3) and (not lua_isnil(L, 3)) then
+ explode:= lua_toboolean(L, 3);
+ if (n = 4) and (not lua_isnil(L, 4)) then
+ poison:= lua_toboolean(L, 4);
+
gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
- HealthCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+ HealthCrate, explode, poison);
if gear <> nil then
lua_pushnumber(L, gear^.uid)
else lua_pushnil(L)
@@ -667,11 +711,20 @@
function lc_spawnfakeammocrate(L: PLua_State): LongInt; Cdecl;
var gear: PGear;
-begin
- if CheckLuaParamCount(L, 4,'SpawnFakeAmmoCrate', 'x, y, explode, poison') then
+ explode, poison: boolean;
+ n: LongInt;
+begin
+ if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeAmmoCrate', 'x, y [, explode [, poison]]', n) then
begin
+ explode:= false;
+ poison:= false;
+ if (n >= 3) and (not lua_isnil(L, 3)) then
+ explode:= lua_toboolean(L, 3);
+ if (n = 4) and (not lua_isnil(L, 4)) then
+ poison:= lua_toboolean(L, 4);
+
gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
- AmmoCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+ AmmoCrate, explode, poison);
if gear <> nil then
lua_pushnumber(L, gear^.uid)
else lua_pushnil(L)
@@ -683,11 +736,20 @@
function lc_spawnfakeutilitycrate(L: PLua_State): LongInt; Cdecl;
var gear: PGear;
-begin
- if CheckLuaParamCount(L, 4,'SpawnFakeUtilityCrate', 'x, y, explode, poison') then
+ explode, poison: boolean;
+ n: LongInt;
+begin
+ if CheckAndFetchParamCountRange(L, 2, 4, 'SpawnFakeUtilityCrate', 'x, y [, explode [, poison]]', n) then
begin
+ explode:= false;
+ poison:= false;
+ if (n >= 3) and (not lua_isnil(L, 3)) then
+ explode:= lua_toboolean(L, 3);
+ if (n = 4) and (not lua_isnil(L, 4)) then
+ poison:= lua_toboolean(L, 4);
+
gear := SpawnFakeCrateAt(Trunc(lua_tonumber(L, 1)), Trunc(lua_tonumber(L, 2)),
- UtilityCrate, lua_toboolean(L, 3), lua_toboolean(L, 4));
+ UtilityCrate, explode, poison);
if gear <> nil then
lua_pushnumber(L, gear^.uid)
else lua_pushnil(L)
@@ -919,17 +981,18 @@
lua_pushnumber(L, vg^.State);
lua_pushnumber(L, vg^.Timer);
lua_pushnumber(L, vg^.Tint);
+ lua_pushnumber(L, vg^.Scale);
end
else
begin
lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L);
- lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L);
+ lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L);
end
end
else
begin
lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L);
- lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L);
+ lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L); lua_pushnil(L);
end;
lc_getvisualgearvalues:= 10
end;
@@ -938,7 +1001,7 @@
var vg : PVisualGear;
begin
// Param count can be 1-11 at present
-// if CheckLuaParamCount(L, 11, 'SetVisualGearValues', 'vgUid, X, Y, dX, dY, Angle, Frame, FrameTicks, State, Timer, Tint') then
+// if CheckLuaParamCount(L, 11, 'SetVisualGearValues', 'vgUid, X, Y, dX, dY, Angle, Frame, FrameTicks, State, Timer, Tint, Scale') then
// begin
vg:= VisualGearByUID(Trunc(lua_tonumber(L, 1)));
if vg <> nil then
@@ -962,7 +1025,9 @@
if not lua_isnoneornil(L, 10) then
vg^.Timer:= Trunc(lua_tonumber(L, 10));
if not lua_isnoneornil(L, 11) then
- vg^.Tint:= Trunc(lua_tonumber(L, 11))
+ vg^.Tint:= Trunc(lua_tonumber(L, 11));
+ if not lua_isnoneornil(L, 12) then
+ vg^.Scale:= Trunc(lua_tonumber(L, 12))
end;
// end
// else
@@ -1293,20 +1358,36 @@
function lc_setclancolor(L : Plua_State) : LongInt; Cdecl;
var clan : PClan;
team : PTeam;
- hh : THedgehog;
+ hh : THedgehog;
i, j : LongInt;
+ colorArg: Int64;
+ color: LongWord;
begin
if CheckLuaParamCount(L, 2, 'SetClanColor', 'clan, color') then
begin
i:= Trunc(lua_tonumber(L,1));
if i >= ClansCount then exit(0);
clan := ClansArray[i];
- clan^.Color:= Trunc(lua_tonumber(L, 2)) shr 8;
+ colorArg:= Trunc(lua_tonumber(L, 2));
+ if (colorArg < 0) and (abs(colorArg) <= cClanColors) then
+ // Pick clan color from settings (recommended)
+ color:= ClanColorArray[Pred(abs(colorArg))]
+ else if (colorArg >= 0) and (colorArg <= $ffffffff) then
+ // Specify color directly
+ color:= colorArg shr 8
+ else
+ begin
+ OutError('Lua error: SetClanColor: Invalid ''color'' argument, must be between '+IntToStr(-cClanColors)+' and 0xffffffff!', true);
+ lc_setclancolor:= 0;
+ exit;
+ end;
+
+ clan^.Color:= color;
for i:= 0 to Pred(clan^.TeamsNumber) do
begin
team:= clan^.Teams[i];
- for j:= 0 to 7 do
+ for j:= 0 to cMaxHHIndex do
begin
hh:= team^.Hedgehogs[j];
if (hh.Gear <> nil) or (hh.GearHidden <> nil) then
@@ -1314,6 +1395,7 @@
FreeAndNilTexture(hh.NameTagTex);
hh.NameTagTex:= RenderStringTex(ansistring(hh.Name), clan^.Color, fnt16);
RenderHealth(hh);
+ team^.Hedgehogs[j]:= hh;
end;
end;
FreeAndNilTexture(team^.NameTagTex);
@@ -1393,6 +1475,28 @@
lc_gethogfort:= 1
end;
+function lc_ishogalive(L : Plua_State) : LongInt; Cdecl;
+var gear : PGear;
+begin
+ if CheckLuaParamCount(L, 1, 'IsHogAlive', 'gearUid') then
+ begin
+ gear:= GearByUID(Trunc(lua_tonumber(L, 1)));
+ if gear <> nil then
+ if gear^.Kind = gtHedgehog then
+ if (gear^.Health > 0) and (gear^.Health > gear^.Damage) and ((gear^.State and (gstDrowning or gstHHDeath)) = 0) and ((gear^.Message and gmDestroy) = 0) then
+ lua_pushboolean(L, true)
+ else
+ lua_pushboolean(L, false)
+ else
+ lua_pushboolean(L, false)
+ else
+ lua_pushboolean(L, false);
+ end
+ else
+ lua_pushnil(L); // return value on stack (nil)
+ lc_ishogalive:= 1
+end;
+
function lc_ishoglocal(L : Plua_State) : LongInt; Cdecl;
var gear : PGear;
begin
@@ -1614,7 +1718,7 @@
vgear^.Text:= lua_tostring(L, 2);
if Gear^.Kind = gtHedgehog then
begin
- AddChatString(#9+'[' + gear^.Hedgehog^.Name + '] '+vgear^.text);
+ AddChatString(#9+Format(shortstring(trmsg[sidChatHog]), gear^.Hedgehog^.Name, vgear^.text));
vgear^.Hedgehog:= gear^.Hedgehog
end
else vgear^.Frame:= gear^.uid;
@@ -1642,6 +1746,7 @@
// should we allow this when there is no current hedgehog? might do some odd(er) things to turn sequence.
if (gear <> nil) and (gear^.Kind = gtHedgehog) and (gear^.Hedgehog <> nil) and (CurrentHedgehog <> nil) then
begin
+ CurrentHedgehog^.MultiShootAttacks:= 0;
prevgear := CurrentHedgehog^.Gear;
if prevgear <> nil then
begin
@@ -1669,6 +1774,44 @@
lc_switchhog:= 0
end;
+function lc_enableswitchhog(L : Plua_State) : LongInt; Cdecl;
+var gear, iterator: PGear;
+ alreadySwitching: boolean;
+begin
+ if CheckLuaParamCount(L, 0, 'EnableSwitchHog', '') then
+ if ((CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil)) then
+ begin
+ alreadySwitching:= false;
+ iterator:= GearsList;
+ // Check if there's already a switcher gear
+ while (iterator <> nil) do
+ begin
+ if (iterator^.Kind = gtSwitcher) then
+ begin
+ alreadySwitching:= true;
+ lua_pushnumber(L, iterator^.Uid);
+ break;
+ end;
+ iterator:= iterator^.NextGear;
+ end;
+ if (not alreadySwitching) then
+ begin
+ // Enable switching and return gear UID
+ gear:= AddGear(hwRound(CurrentHedgehog^.Gear^.X), hwRound(CurrentHedgehog^.Gear^.Y), gtSwitcher, 0, _0, _0, 0);
+ CurAmmoGear:= gear;
+ lastGearByUID:= gear;
+ bShowFinger:= false;
+ lua_pushnumber(L, gear^.Uid);
+ end;
+ end
+ // Return nil on failure
+ else
+ lua_pushnil(L)
+ else
+ lua_pushnil(L);
+ lc_enableswitchhog:= 1;
+end;
+
function lc_addammo(L : Plua_State) : LongInt; Cdecl;
var gear : PGear;
at, n, c: LongInt;
@@ -1739,11 +1882,15 @@
if (gear^.Kind = gtHedgehog) and (gear^.Hedgehog <> nil) then
begin
+ if gear^.Health > cMaxHogHealth then
+ gear^.Health:= cMaxHogHealth;
+ if gear^.Health < 0 then
+ gear^.Health:= 0;
RenderHealth(gear^.Hedgehog^);
RecountTeamHealth(gear^.Hedgehog^.Team)
- end;
- // Why did this do a "setalltoactive" ?
- //SetAllToActive;
+ end
+ else if (gear^.RenderHealth) and (gear^.Tex <> nil) then
+ FreeAndNilTexture(gear^.Tex);
Gear^.Active:= true;
AllInactive:= false
end
@@ -1761,8 +1908,7 @@
healthBoost:= Trunc(lua_tonumber(L, 2));
if (gear <> nil) and (gear^.Kind = gtHedgehog) and (gear^.Hedgehog <> nil) and (healthBoost >= 1) then
begin
- gear^.Health:= gear^.Health + healthBoost;
-
+ healthBoost:= IncHogHealth(gear^.Hedgehog, healthBoost);
RenderHealth(gear^.Hedgehog^);
RecountTeamHealth(gear^.Hedgehog^.Team);
if n = 4 then
@@ -1784,7 +1930,12 @@
if CheckLuaParamCount(L, 2, 'SetTimer', 'gearUid, timer') then
begin
gear:= GearByUID(Trunc(lua_tonumber(L, 1)));
- if gear <> nil then gear^.Timer:= Trunc(lua_tonumber(L, 2))
+ if gear <> nil then
+ begin
+ gear^.Timer:= Trunc(lua_tonumber(L, 2));
+ if gear^.RenderTimer and (gear^.Tex <> nil) then
+ FreeAndNilTexture(gear^.Tex);
+ end;
end;
lc_settimer:= 0
end;
@@ -1909,6 +2060,7 @@
function lc_endgame(L : Plua_State) : LongInt; Cdecl;
begin
L:= L; // avoid compiler hint
+ GameOver:= true;
AddGear(0, 0, gtATFinishGame, 0, _0, _0, 3000);
lc_endgame:= 0
end;
@@ -1943,9 +2095,10 @@
else
respectFactor:= True;
if respectFactor then
- TurnTimeLeft:= (time * cGetAwayTime) div 100
+ PendingTurnTimeLeft:= (time * cGetAwayTime) div 100
else
- TurnTimeLeft:= time;
+ PendingTurnTimeLeft:= time;
+ isPendingTurnTimeLeft:= true;
if ((CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil)) then
begin
CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstAttacked;
@@ -2108,7 +2261,7 @@
if instaVoice then
PlaySoundV(TSound(s), gear^.Hedgehog^.Team^.Voicepack, false, true)
else
- AddVoice(TSound(s), gear^.Hedgehog^.Team^.Voicepack, true);
+ AddVoice(TSound(s), gear^.Hedgehog^.Team^.Voicepack, true, false);
end;
end;
end;
@@ -2116,12 +2269,43 @@
lc_playsound:= 0;
end;
+function lc_playmusicsound(L : Plua_State) : LongInt; Cdecl;
+var s: LongInt;
+const
+ call = 'PlayMusicSound';
+ params = 'soundId';
+begin
+ if CheckLuaParamCount(L, 1, call, params) then
+ begin
+ s:= LuaToSoundOrd(L, 1, call, params);
+ if s >= 0 then
+ PlayMusicSound(TSound(s))
+ end;
+ lc_playmusicsound:= 0;
+end;
+
+function lc_stopmusicsound(L : Plua_State) : LongInt; Cdecl;
+var s: LongInt;
+const
+ call = 'StopMusicSound';
+ params = 'soundId';
+begin
+ if CheckLuaParamCount(L, 1, call, params) then
+ begin
+ s:= LuaToSoundOrd(L, 1, call, params);
+ if s >= 0 then
+ StopMusicSound(TSound(s))
+ end;
+ lc_stopmusicsound:= 0;
+end;
+
+
function lc_setsoundmask(L : Plua_State) : LongInt; Cdecl;
var s: LongInt;
soundState: boolean;
const
- call = 'SetSoundMasked';
- params = 'soundId, isMasked]';
+ call = 'SetSoundMask';
+ params = 'soundId, isMasked';
begin
if CheckLuaParamCount(L, 2, call, params) then
begin
@@ -2137,21 +2321,95 @@
function lc_addteam(L : Plua_State) : LongInt; Cdecl;
var np: LongInt;
+ colorArg: Int64;
+ colorStr: shortstring;
begin
if CheckAndFetchParamCount(L, 5, 6, 'AddTeam', 'teamname, color, grave, fort, voicepack [, flag]', np) then
begin
- ParseCommand('addteam x ' + lua_tostring(L, 2) + ' ' + lua_tostring(L, 1), true, true);
+ colorArg:= Trunc(lua_tonumber(L, 2));
+ if (colorArg < 0) and (abs(colorArg) <= cClanColors) then
+ // Pick clan color from settings (recommended)
+ colorStr:= IntToStr(ClanColorArray[Pred(abs(colorArg))])
+ else if (colorArg >= 0) and (colorArg <= $ffffffff) then
+ // Specify color directly
+ colorStr:= IntToStr(colorArg)
+ else
+ begin
+ OutError('Lua error: AddTeam: Invalid ''color'' argument, must be between '+IntToStr(-cClanColors)+' and 0xffffffff!', true);
+ lua_pushnil(L);
+ lua_pushnil(L);
+ lc_addteam:= 2;
+ exit;
+ end;
+ ParseCommand('addteam x ' + colorStr + ' ' + lua_tostring(L, 1), true, true);
ParseCommand('grave ' + lua_tostring(L, 3), true, true);
ParseCommand('fort ' + lua_tostring(L, 4), true, true);
ParseCommand('voicepack ' + lua_tostring(L, 5), true, true);
if (np = 6) then ParseCommand('flag ' + lua_tostring(L, 6), true, true);
- CurrentTeam^.Binds:= DefaultBinds
- // fails on x64
- //lua_pushnumber(L, LongInt(CurrentTeam));
+ // If there's a mission team, copy it's control scheme.
+ // So in singleplayer missions, all teams use the player team's controls.
+ if MissionTeam <> nil then
+ CurrentTeam^.Binds:= MissionTeam^.Binds
+ // Default keys otherwise
+ else
+ CurrentTeam^.Binds:= DefaultBinds;
+ // push team name and index
+ lua_pushstring(L, str2pchar(CurrentTeam^.TeamName));
+ lua_pushnumber(L, TeamsCount - 1);
+ end
+ else
+ begin
+ lua_pushnil(L);
+ lua_pushnil(L);
end;
- //else
- //lua_pushnil(L)
- lc_addteam:= 0;//1;
+ lc_addteam:= 2;
+end;
+
+function lc_addmissionteam(L : Plua_State) : LongInt; Cdecl;
+var colorArg: Int64;
+ colorStr: shortstring;
+begin
+ if CheckLuaParamCount(L, 1, 'AddMissionTeam', 'color') then
+ begin
+ if(MissionTeam = nil) then
+ begin
+ OutError('Lua error: AddMissionTeam: Could not add team. Note: This function only works in singleplayer missions!', true);
+ lc_addmissionteam:= 0;
+ exit;
+ end;
+
+ colorArg:= Trunc(lua_tonumber(L, 1));
+ if (colorArg < 0) and (abs(colorArg) <= cClanColors) then
+ // Pick clan color from settings (recommended)
+ colorStr:= IntToStr(ClanColorArray[Pred(abs(colorArg))])
+ else if (colorArg >= 0) and (colorArg <= $ffffffff) then
+ // Specify color directly
+ colorStr:= IntToStr(colorArg)
+ else
+ begin
+ OutError('Lua error: AddMissionTeam: Invalid ''color'' argument, must be between '+IntToStr(-cClanColors)+' and 0xffffffff!', true);
+ lua_pushnil(L);
+ lua_pushnil(L);
+ lc_addmissionteam:= 2;
+ exit;
+ end;
+
+ ParseCommand('addteam x ' + colorStr + ' ' + MissionTeam^.TeamName, true, true);
+ ParseCommand('grave ' + MissionTeam^.GraveName, true, true);
+ ParseCommand('fort ' + MissionTeam^.FortName, true, true);
+ ParseCommand('voicepack ' + MissionTeam^.Voicepack^.name, true, true);
+ ParseCommand('flag ' + MissionTeam^.Flag, true, true);
+ CurrentTeam^.Binds:= MissionTeam^.Binds;
+ // push real team name and team index
+ lua_pushstring(L, str2pchar(CurrentTeam^.TeamName));
+ lua_pushnumber(L, TeamsCount - 1);
+ end
+ else
+ begin
+ lua_pushnil(L);
+ lua_pushnil(L);
+ end;
+ lc_addmissionteam:= 2;
end;
function lc_setteamlabel(L : Plua_State) : LongInt; Cdecl;
@@ -2159,6 +2417,7 @@
i, n: LongInt;
success: boolean;
begin
+ success:= false;
if CheckAndFetchParamCount(L, 1, 2, 'SetTeamLabel', 'teamname[, label]', n) then
begin
success:= false;
@@ -2196,6 +2455,46 @@
lc_setteamlabel:= 1;
end;
+function lc_setteampassive(L : Plua_State) : LongInt; Cdecl;
+var i, j: LongInt;
+ success, passive, passiveClan: boolean;
+begin
+ success:= false;
+ if CheckLuaParamCount(L, 2, 'SetTeamPassive', 'teamname, isPassive') then
+ begin
+ success:= false;
+ // fetch team
+ if TeamsCount > 0 then
+ for i:= 0 to Pred(TeamsCount) do
+ if TeamsArray[i]^.TeamName = lua_tostring(L, 1) then
+ begin
+ passive:= lua_toboolean(L, 2);
+ TeamsArray[i]^.Passive:= passive;
+ // also update clan state
+ if passive then
+ begin
+ passiveClan:= true;
+ for j:= 0 to Pred(TeamsCount) do
+ if (not TeamsArray[j]^.Passive) then
+ begin
+ passiveClan:= false;
+ break;
+ end;
+ end
+ else
+ passiveClan:= false;
+ TeamsArray[i]^.Clan^.Passive:= passiveClan;
+
+ success:= true;
+ // don't change more than one team
+ break;
+ end;
+ end;
+ // return true if operation was successful, false otherwise
+ lua_pushboolean(L, success);
+ lc_setteampassive:= 1;
+end;
+
function lc_getteamname(L : Plua_State) : LongInt; Cdecl;
var t: LongInt;
begin
@@ -2288,6 +2587,8 @@
AddVisualGear(hwRound(HHGear^.X) - 16 + Random(32), hwRound(HHGear^.Y) - 16 + Random(32), vgtSmokeWhite);
end;
HHGear^.Message:= HHGear^.Message or gmDestroy;
+ HHGear^.Active:= true;
+ AllInactive:= false;
end;
end;
// can't dismiss more than one team
@@ -2345,20 +2646,51 @@
function lc_addhog(L : Plua_State) : LongInt; Cdecl;
-var temp: ShortString;
+var hatName: ShortString;
begin
if CheckLuaParamCount(L, 4, 'AddHog', 'hogname, botlevel, health, hat') then
begin
- temp:= lua_tostring(L, 4);
+ hatName:= lua_tostring(L, 4);
ParseCommand('addhh ' + lua_tostring(L, 2) + ' ' + lua_tostring(L, 3) + ' ' + lua_tostring(L, 1), true, true);
- ParseCommand('hat ' + temp, true, true);
- lua_pushnumber(L, CurrentHedgehog^.Gear^.uid);
+ ParseCommand('hat ' + hatName, true, true);
+ if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) then
+ lua_pushnumber(L, CurrentHedgehog^.Gear^.uid)
+ else
+ OutError('Lua error: AddHog: Error adding hog. Maybe function was called outside of onGameInit.', true);
end
else
lua_pushnil(L);
lc_addhog:= 1;
end;
+function lc_addmissionhog(L : Plua_State) : LongInt; Cdecl;
+var hatName: ShortString;
+begin
+ if CheckLuaParamCount(L, 1, 'AddMissionHog', 'health') then
+ begin
+ if(MissionTeam = nil) then
+ begin
+ OutError('Lua error: AddMissionHog: Could not add hog. Mission team is not set!', true);
+ lua_pushnil(L);
+ lc_addmissionhog:= 1;
+ exit;
+ end;
+ with MissionTeam^.Hedgehogs[CurrentTeam^.HedgehogsNumber] do
+ begin
+ hatName:= Hat;
+ ParseCommand('addhh ' + IntToStr(BotLevel) + ' ' + lua_tostring(L, 1) + ' ' + Name, true, true);
+ ParseCommand('hat ' + hatName, true, true);
+ end;
+ if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) then
+ lua_pushnumber(L, CurrentHedgehog^.Gear^.uid)
+ else
+ OutError('Lua error: AddMissionHog: Error adding hog. Maybe function was called outside of onGameInit.', true);
+ end
+ else
+ lua_pushnil(L);
+ lc_addmissionhog:= 1;
+end;
+
function lc_hogturnleft(L : Plua_State) : LongInt; Cdecl;
var gear: PGear;
begin
@@ -2543,8 +2875,43 @@
lc_setammo:= 0
end;
+
+function lc_getammo(L : Plua_State) : LongInt; Cdecl;
+var i, at, rawProb, probLevel: LongInt;
+const
+ call = 'GetAmmo';
+ params = 'ammoType';
+begin
+ lc_getammo:= 0;
+ if CheckLuaParamCount(L, 1, call, params) then
+ begin
+ at:= LuaToAmmoTypeOrd(L, 1, call, params);
+ if at >= 0 then
+ begin
+ // Ammo count
+ i:= InitialAmmoCounts[TAmmoType(at)];
+ if i = AMMO_INFINITE then
+ i:= 9;
+ lua_pushnumber(L, i);
+ // Probability
+ rawProb:= Ammoz[TAmmoType(at)].Probability;
+ probLevel:= -1;
+ for i := 0 to High(probabilityLevels) do
+ if rawProb = probabilityLevels[i] then
+ probLevel:= i;
+ lua_pushnumber(L, probLevel);
+ // Delay in turns
+ lua_pushnumber(L, Ammoz[TAmmoType(at)].SkipTurns);
+ // Number in case
+ lua_pushnumber(L, Ammoz[TAmmoType(at)].NumberInCase);
+ lc_getammo:= 4
+ end
+ end;
+end;
+
+
function lc_setammodelay(L : Plua_State) : LongInt; Cdecl;
-var at: LongInt;
+var at, delay: LongInt;
const
call = 'SetAmmoDelay';
params = 'ammoType, delay';
@@ -2552,12 +2919,31 @@
if CheckLuaParamCount(L, 2, call, params) then
begin
at:= LuaToAmmoTypeOrd(L, 1, call, params);
- if at >= 0 then
- ScriptSetAmmoDelay(TAmmoType(at), Trunc(lua_tonumber(L, 2)));
+ delay:= Trunc(lua_tonumber(L, 2));
+ if (at >= 0) and (TAmmoType(at) <> amNothing) then
+ begin
+ ScriptSetAmmoDelay(TAmmoType(at), delay);
+ // Unselect weapon if neccessary
+ if (delay > 0) and (CurrentHedgehog <> nil) and (CurrentHedgehog^.CurAmmoType = TAmmoType(at)) then
+ ParseCommand('setweap ' + char(0), true, true);
+ end;
end;
lc_setammodelay:= 0
end;
+function lc_setammoslot(L : Plua_State) : LongInt; Cdecl;
+var at, slot: LongInt;
+begin
+ if CheckLuaParamCount(L, 2, 'SetAmmoSlot', 'ammoType, slot') then
+ begin
+ at:= Trunc(lua_tonumber(L, 1));
+ slot:= Trunc(lua_tonumber(L, 2));
+ Ammoz[TAmmoType(at)].Slot:= slot - 1;
+ AmmoMenuInvalidated:= true;
+ end;
+ lc_setammoslot:= 0;
+end;
+
function lc_getrandom(L : Plua_State) : LongInt; Cdecl;
var m : LongInt;
begin
@@ -2588,7 +2974,7 @@
cWindSpeedf := -cWindSpeedf;
vg:= AddVisualGear(0, 0, vgtSmoothWindBar);
if vg <> nil then vg^.dAngle:= hwFloat2Float(cWindSpeed);
- AddFileLog('Wind = '+FloatToStr(cWindSpeed));
+ AddFileLog('Script set wind = '+FloatToStr(cWindSpeed));
end;
lc_setwind:= 0
end;
@@ -2680,7 +3066,7 @@
placed, behind, flipHoriz, flipVert : boolean;
const
call = 'PlaceSprite';
- params = 'x, y, sprite, frameIdx, tint, behind, flipHoriz, flipVert, [, landFlag, ... ]';
+ params = 'x, y, sprite, frameIdx, tint, behind, flipHoriz, flipVert [, landFlag, ... ]';
begin
placed:= false;
if CheckAndFetchLuaParamMinCount(L, 4, call, params, n) then
@@ -2728,7 +3114,7 @@
eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert : boolean;
const
call = 'EraseSprite';
- params = 'x, y, sprite, frameIdx, eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert, [, landFlag, ... ]';
+ params = 'x, y, sprite, frameIdx, eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert [, landFlag, ... ]';
begin
if CheckAndFetchLuaParamMinCount(L, 4, call, params, n) then
begin
@@ -2818,6 +3204,21 @@
lc_getcampaignvar := 1;
end;
+function lc_savemissionvar(L : Plua_State): LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 2, 'SaveMissionVar', 'varname, value') then
+ SendIPC('v!' + lua_tostring(L, 1) + ' ' + lua_tostring(L, 2) + #0);
+ lc_savemissionvar := 0;
+end;
+
+function lc_getmissionvar(L : Plua_State): LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 1, 'GetMissionVar', 'varname') then
+ SendIPCAndWaitReply('v?' + lua_tostring(L, 1) + #0);
+ lua_pushstring(L, str2pchar(MissionVariable));
+ lc_getmissionvar := 1;
+end;
+
function lc_hidehog(L: Plua_State): LongInt; Cdecl;
var gear: PGear;
begin
@@ -2854,6 +3255,36 @@
lc_restorehog := 0;
end;
+function lc_ishoghidden(L: Plua_State): LongInt; Cdecl;
+var i, h: LongInt;
+ uid: LongWord;
+ gear: PGear;
+begin
+ if CheckLuaParamCount(L, 1, 'IsHogHidden', 'gearUid') then
+ begin
+ uid:= LongWord(Trunc(lua_tonumber(L, 1)));
+ gear:= GearByUID(uid);
+ if (gear <> nil) and (gear^.hedgehog <> nil) then
+ begin
+ lua_pushboolean(L, false);
+ lc_ishoghidden:= 1;
+ exit;
+ end
+ else
+ if TeamsCount > 0 then
+ for i:= 0 to Pred(TeamsCount) do
+ for h:= 0 to cMaxHHIndex do
+ if (TeamsArray[i]^.Hedgehogs[h].GearHidden <> nil) and (TeamsArray[i]^.Hedgehogs[h].GearHidden^.uid = uid) then
+ begin
+ lua_pushboolean(L, true);
+ lc_ishoghidden:= 1;
+ exit;
+ end
+ end;
+ lua_pushnil(L);
+ lc_ishoghidden:= 1;
+end;
+
// boolean TestRectForObstacle(x1, y1, x2, y2, landOnly)
function lc_testrectforobstacle(L : Plua_State) : LongInt; Cdecl;
var rtn: Boolean;
@@ -2929,12 +3360,21 @@
function lc_hedgewarsscriptload(L : Plua_State) : LongInt; Cdecl;
-begin
- if CheckLuaParamCount(L, 1, 'HedgewarsScriptLoad', 'scriptPath') then
- ScriptLoad(lua_tostring(L, 1))
+var success : boolean;
+ n : LongInt;
+begin
+ success:= false;
+ if CheckAndFetchParamCount(L, 1, 2, 'HedgewarsScriptLoad', 'scriptPath [, mustExist]', n) then
+ begin
+ if n = 1 then
+ success:= ScriptLoad(lua_tostring(L, 1), true)
+ else
+ success:= ScriptLoad(lua_tostring(L, 1), lua_toboolean(L, 2));
+ end
else
- lua_pushnil(L);
- lc_hedgewarsscriptload:= 0;
+ success:= false;
+ lua_pushboolean(L, success);
+ lc_hedgewarsscriptload:= 1;
end;
@@ -2968,6 +3408,33 @@
lc_getammoname:= 1;
end;
+function lc_getammotimer(L : Plua_state) : LongInt; Cdecl;
+var at: LongInt;
+ weapon: PAmmo;
+ gear: PGear;
+const call = 'GetAmmoTimer';
+ params = 'gearUid, ammoType';
+begin
+ if CheckLuaParamCount(L, 2, call, params) then
+ begin
+ gear:= GearByUID(Trunc(lua_tonumber(L, 1)));
+ if (gear <> nil) and (gear^.Hedgehog <> nil) then
+ begin
+ at:= LuaToAmmoTypeOrd(L, 2, call, params);
+ weapon:= GetAmmoEntry(gear^.Hedgehog^, TAmmoType(at));
+ if (Ammoz[TAmmoType(at)].Ammo.Propz and ammoprop_Timerable) <> 0 then
+ lua_pushnumber(L, weapon^.Timer)
+ else
+ lua_pushnil(L);
+ end
+ else
+ lua_pushnil(L);
+ end
+ else
+ lua_pushnil(L);
+ lc_getammotimer:= 1;
+end;
+
function lc_setvampiric(L : Plua_state) : LongInt; Cdecl;
begin
if CheckLuaParamCount(L, 1, 'SetVampiric', 'bool') then
@@ -2975,6 +3442,12 @@
lc_setvampiric := 0;
end;
+function lc_getvampiric(L : Plua_state) : LongInt; Cdecl;
+begin
+ lua_pushboolean(L, cVampiric);
+ lc_getvampiric := 1;
+end;
+
function lc_setlasersight(L : Plua_state) : LongInt; Cdecl;
begin
if CheckLuaParamCount(L, 1, 'SetLaserSight', 'bool') then
@@ -2982,6 +3455,12 @@
lc_setlasersight:= 0;
end;
+function lc_getlasersight(L : Plua_state) : LongInt; Cdecl;
+begin
+ lua_pushboolean(L, cLaserSighting);
+ lc_getlasersight:= 1;
+end;
+
function lc_explode(L : Plua_state) : LongInt; Cdecl;
var mask: LongWord;
n: LongInt;
@@ -3002,6 +3481,56 @@
lc_explode:= 1;
end;
+function lc_setturntimeleft(L : Plua_State) : LongInt; Cdecl;
+var number: Int64;
+begin
+ if CheckLuaParamCount(L, 1, 'SetTurnTimeLeft', 'newTurnTimeLeft') then
+ begin
+ number:= Trunc(lua_tonumber(L, 1));
+ if number < 0 then
+ number:= 0;
+ if number > cMaxTurnTime then
+ number:= cMaxTurnTime;
+ // The real TurnTimeLeft will be set in SetGlobals
+ PendingTurnTimeLeft:= number;
+ isPendingTurnTimeLeft:= true;
+ end;
+ lc_setturntimeleft:= 0;
+end;
+
+function lc_setreadytimeleft(L : Plua_State) : LongInt; Cdecl;
+var number: Int64;
+begin
+ if CheckLuaParamCount(L, 1, 'SetReadyTimeLeft', 'newReadyTimeLeft') then
+ begin
+ number:= Trunc(lua_tonumber(L, 1));
+ if number < 0 then
+ number:= 0;
+ if number > cMaxTurnTime then
+ number:= cMaxTurnTime;
+ // The real ReadyTimeLeft will be set in SetGlobals
+ PendingReadyTimeLeft:= number;
+ isPendingReadyTimeLeft:= true;
+ end;
+ lc_setreadytimeleft:= 0;
+end;
+
+function lc_setturntimepaused(L : Plua_State) : LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 1, 'SetTurnTimePaused', 'isPaused') then
+ LuaClockPaused:= lua_toboolean(L, 1);
+ lc_setturntimepaused:= 0;
+end;
+
+function lc_getturntimepaused(L : Plua_State) : LongInt; Cdecl;
+begin
+ if CheckLuaParamCount(L, 0, 'GetTurnTimePaused', '') then
+ lua_pushboolean(L, LuaClockPaused)
+ else
+ lua_pushnil(L);
+ lc_getturntimepaused:= 1;
+end;
+
function lc_startghostpoints(L : Plua_State) : LongInt; Cdecl;
begin
if CheckLuaParamCount(L, 1, 'StartGhostPoints', 'count') then
@@ -3131,6 +3660,12 @@
lua_setglobal(luaState, Str2PChar(name));
end;
+procedure ScriptSetLongWord(name : shortstring; value : LongWord);
+begin
+ lua_pushnumber(luaState, value);
+ lua_setglobal(luaState, Str2PChar(name));
+end;
+
procedure ScriptSetString(name : shortstring; value : shortstring);
begin
lua_pushstring(luaState, Str2PChar(value));
@@ -3201,8 +3736,10 @@
ScriptSetInteger('ScreenWidth', cScreenWidth);
ScriptSetInteger('TurnTime', cHedgehogTurnTime);
ScriptSetInteger('CaseFreq', cCaseFactor);
+ScriptSetInteger('MaxCaseDrops', cMaxCaseDrops);
ScriptSetInteger('HealthCaseProb', cHealthCaseProb);
ScriptSetInteger('HealthCaseAmount', cHealthCaseAmount);
+ScriptSetInteger('InitHealth', cInitHealth);
ScriptSetInteger('DamagePercent', cDamagePercent);
ScriptSetInteger('RopePercent', cRopePercent);
ScriptSetInteger('MinesNum', cLandMines);
@@ -3233,8 +3770,10 @@
WorldEdge := TWorldEdge(ScriptGetInteger('WorldEdge'));
cHedgehogTurnTime:= ScriptGetInteger('TurnTime');
cCaseFactor := ScriptGetInteger('CaseFreq');
+cMaxCaseDrops := ScriptGetInteger('MaxCaseDrops');
cHealthCaseProb := ScriptGetInteger('HealthCaseProb');
cHealthCaseAmount:= ScriptGetInteger('HealthCaseAmount');
+cInitHealth := ScriptGetInteger('InitHealth');
cDamagePercent := ScriptGetInteger('DamagePercent');
cRopePercent := ScriptGetInteger('RopePercent');
cLandMines := ScriptGetInteger('MinesNum');
@@ -3297,7 +3836,6 @@
ScriptSetInteger('ClansCount', ClansCount);
ScriptSetInteger('TeamsCount', TeamsCount);
-mapDims:= false
end;
@@ -3318,7 +3856,6 @@
var braceCount: LongWord;
var wordCount: LongWord;
var lastChar: char;
-// ⭒⭐⭒✨⭐⭒✨⭐☆✨⭐✨✧✨☆✨✧✨☆⭒✨☆⭐⭒☆✧✨⭒✨⭐✧⭒☆⭒✧☆✨✧⭐☆✨☆✧⭒✨✧⭒☆⭐☆✧
function ScriptReader(L: Plua_State; f: PFSFile; sz: Psize_t) : PChar; Cdecl;
var mybuf: PChar;
i: LongInt;
@@ -3331,14 +3868,15 @@
begin
if (lastChar = '-') and (mybuf[i] = '-') then
inComment := true
- // gonna add any non-magic whitespace and skip - just to make comment avoidance easier
else if not inComment and (byte(mybuf[i]) > $20) and (byte(mybuf[i]) < $7F) and (mybuf[i]<>'-') then
begin
AddRandomness(byte(mybuf[i])); // wish I had the seed...
CheckSum := CheckSum xor GetRandom($FFFFFFFF);
end;
lastChar := mybuf[i];
- if (byte(mybuf[i]) = $0D) or (byte(mybuf[i]) = $0A) then
+ // lua apparently allows -- [===============[ as a valid block comment start.
+ // I can't be bothered to check for that nonsense. Will allow limited single line without [
+ if (byte(mybuf[i]) = $0D) or (byte(mybuf[i]) = $0A) or (mybuf[i] = '[') then
inComment := false
end;
end;
@@ -3375,15 +3913,15 @@
((byte(mybuf[i]) >= $30) and (byte(mybuf[i]) < $3A))) then
inc(wordCount);
lastChar := mybuf[i];
- if (byte(mybuf[i]) = $0D) or (byte(mybuf[i]) = $0A) then
+ // this allows at least supporting the commented strings at end of line with lua script names
+ if (byte(mybuf[i]) = $0D) or (byte(mybuf[i]) = $0A) or (mybuf[i] = '[') then
inComment := false
end;
end;
ScriptLocaleReader:= mybuf
end;
-// ⭒⭐⭒✨⭐⭒✨⭐☆✨⭐✨✧✨☆✨✧✨☆⭒✨☆⭐⭒☆✧✨⭒✨⭐✧⭒☆⭒✧☆✨✧⭐☆✨☆✧⭒✨✧⭒☆⭐☆✧
-
-procedure ScriptLoad(name : shortstring);
+
+function ScriptLoad(name : shortstring; mustExist : boolean): boolean;
var ret : LongInt;
s : shortstring;
f : PFSFile;
@@ -3398,33 +3936,43 @@
s:= cPathz[ptData] + name;
if not pfsExists(s) then
begin
- AddFileLog('[LUA] Script not found: ' + name);
+ if mustExist then
+ OutError('Script not found: ' + name, true)
+ else
+ AddFileLog('[LUA] Script not found: ' + name);
+ ScriptLoad:= false;
exit;
end;
f:= pfsOpenRead(s);
if f = nil then
- exit;
-
-hedgewarsMountPackage(Str2PChar(copy(s, 1, length(s)-4)+'.hwp'));
+ OutError('Error reading script: ' + name, true);
+
+hedgewarsMountPackage(Str2PChar(copy(s, 3, length(s)-6)+'.hwp'));
physfsReaderSetBuffer(@buf);
-if Pos('Locale/',s) <> 0 then
+if (Pos('Locale/',s) <> 0) or (s = 'Scripts/OfficialChallengeHashes.lua') then
ret:= lua_load(luaState, @ScriptLocaleReader, f, Str2PChar(s))
-else ret:= lua_load(luaState, @ScriptReader, f, Str2PChar(s));
+else
+ begin
+ SetRandomSeed(cSeed,true);
+ ret:= lua_load(luaState, @ScriptReader, f, Str2PChar(s))
+ end;
pfsClose(f);
if ret <> 0 then
begin
LuaError('Failed to load ' + name + '(error ' + IntToStr(ret) + ')');
LuaError(lua_tostring(luaState, -1));
+ ScriptLoad:= false;
end
else
begin
WriteLnToConsole('Lua: ' + name + ' loaded');
// call the script file
lua_pcall(luaState, 0, 0, 0);
- ScriptLoaded:= true
+ ScriptLoaded:= true;
+ ScriptLoad:= true;
end;
end;
@@ -3434,7 +3982,7 @@
ScriptSetInteger('TurnTimeLeft', TurnTimeLeft);
ScriptSetInteger('ReadyTimeLeft', ReadyTimeLeft);
ScriptSetInteger('GameTime', GameTicks);
-ScriptSetInteger('TotalRounds', TotalRounds);
+ScriptSetInteger('TotalRounds', TotalRoundsReal);
ScriptSetInteger('WaterLine', cWaterLine);
if isCursorVisible and (not bShowAmmoMenu) then
begin
@@ -3457,31 +4005,59 @@
PrevCursorY:= NoPointX
end;
-if not mapDims then
- begin
- mapDims:= true;
- ScriptSetInteger('LAND_WIDTH', LAND_WIDTH);
- ScriptSetInteger('LAND_HEIGHT', LAND_HEIGHT);
- ScriptSetInteger('LeftX', leftX);
- ScriptSetInteger('RightX', rightX);
- ScriptSetInteger('TopY', topY)
- end;
if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) then
ScriptSetInteger('CurrentHedgehog', CurrentHedgehog^.Gear^.UID)
else
ScriptSetNil('CurrentHedgehog');
end;
+procedure ScriptSetMapGlobals;
+begin
+ScriptSetInteger('LAND_WIDTH', LAND_WIDTH);
+ScriptSetInteger('LAND_HEIGHT', LAND_HEIGHT);
+ScriptSetInteger('LeftX', leftX);
+ScriptSetInteger('RightX', rightX);
+ScriptSetInteger('TopY', topY);
+end;
+
procedure GetGlobals;
-begin
-// TODO
-// Use setters instead, because globals should be read-only!
-// Otherwise globals might be changed by Lua, but then unexpectatly overwritten by engine when a ScriptCall is triggered by whatever Lua is doing!
-// Sure, one could work around that in engine (e.g. by setting writable globals in SetGlobals only when their engine-side value has actually changed since SetGlobals was called the last time...), but things just get messier and messier then.
-// It is inconsistent anyway to have some globals be read-only and others not with no indication whatsoever.
-// -- sheepluva
-TurnTimeLeft:= ScriptGetInteger('TurnTimeLeft');
-ReadyTimeLeft:= ScriptGetInteger('ReadyTimeLeft');
+var currentTTL, currentRTL, newTTL, newRTL: LongInt;
+begin
+// Setting TurnTimeLeft and ReadyTimeLeft should now be done in the setter functions.
+// SetTurnTimeLeft and SetReadTimeLeft.
+// GetGloals should be removed in a future release.
+
+// DEPRECATED: Read TurnTimeLeft and ReadyTimeLeft from script directly.
+// TODO: Remove this behaviour in a future version.
+currentTTL:= TurnTimeLeft;
+currentRTL:= ReadyTimeLeft;
+newTTL:= ScriptGetInteger('TurnTimeLeft');
+newRTL:= ScriptGetInteger('ReadyTimeLeft');
+if (currentTTL <> newTTL) and (not isPendingTurnTimeLeft) then
+ begin
+ TurnTimeLeft:= newTTL;
+ LuaWarning('Writing to TurnTimeLeft directly is deprecated! Use SetTurnTimeLeft instead!');
+ end;
+
+if (currentRTL <> newRTL) and (not isPendingReadyTimeLeft) then
+ begin
+ ReadyTimeLeft:= newRTL;
+ LuaWarning('Writing to ReadyTimeLeft directly is deprecated! Use SetReadyTimeLeft instead!');
+ end;
+
+// Set TurnTimeLeft and ReadyTimeLeft if activated by SetTurnTimeLeft and SetReadyTimeLeft before
+if isPendingTurnTimeLeft then
+ begin
+ TurnTimeLeft:= PendingTurnTimeLeft;
+ ScriptSetInteger('TurnTimeLeft', TurnTimeLeft);
+ isPendingTurnTimeLeft:= false;
+ end;
+if isPendingReadyTimeLeft then
+ begin
+ ReadyTimeLeft:= PendingReadyTimeLeft;
+ ScriptSetInteger('ReadyTimeLeft', ReadyTimeLeft);
+ isPendingReadyTimeLeft:= false;
+ end;
end;
procedure ScriptCall(fname : shortstring);
@@ -3598,17 +4174,22 @@
ScriptAmmoReinforcement[ord(ammo)]:= inttostr(reinforcement)[1];
end;
-procedure ScriptSetAmmoDelay(ammo : TAmmoType; delay: Byte);
+procedure ScriptSetAmmoDelay(ammo : TAmmoType; delay: LongWord);
begin
// change loadout string if ammo store has not been initialized yet
if (StoreCnt = 0) then
-begin
+ begin
if (delay <= 9) then
ScriptAmmoDelay[ord(ammo)]:= inttostr(delay)[1];
-end
+ end
// change 'live' delay values
else if (CurrentTeam <> nil) then
- ammoz[ammo].SkipTurns:= CurrentTeam^.Clan^.TurnNumber + delay;
+ begin
+ ammoz[ammo].SkipTurns:= CurrentTeam^.Clan^.TurnNumber + delay;
+ if ammoz[ammo].SkipTurns > 0 then
+ dec(ammoz[ammo].SkipTurns);
+ AmmoMenuInvalidated:= true;
+ end;
end;
procedure ScriptApplyAmmoStore;
@@ -3688,7 +4269,19 @@
luaopen_table(luaState);
// import some variables
-ScriptSetString(_S'LOCALE', cLocale);
+ScriptSetString(_S'LOCALE', cLanguage);
+
+{$IFDEF USE_TOUCH_INTERFACE}
+ScriptSetString(_S'INTERFACE', 'touch');
+{$ELSE}
+ScriptSetString(_S'INTERFACE', 'desktop');
+{$ENDIF}
+
+// Add aliases for amDuck and gtDuck because rubber duck was removed.
+// amDuck and gtDuck are deprecated and should be removed later.
+// TODO: Remove these aliases in a later version.
+ScriptSetInteger('amDuck', Ord(amCreeper));
+ScriptSetInteger('gtDuck', Ord(gtCreeper));
// import game flags
ScriptSetInteger('gfSwitchHog', gfSwitchHog);
@@ -3741,6 +4334,14 @@
ScriptSetInteger('SAY_THINK', 2);
ScriptSetInteger('SAY_SHOUT', 3);
+// other
+ScriptSetInteger('AMMO_INFINITE', AMMO_INFINITE);
+ScriptSetInteger('JETPACK_FUEL_INFINITE', JETPACK_FUEL_INFINITE);
+ScriptSetInteger('BIRDY_ENERGY_INFINITE', BIRDY_ENERGY_INFINITE);
+ScriptSetInteger('NO_CURSOR', NoPointX);
+ScriptSetInteger('MAX_HOG_HEALTH', cMaxHogHealth);
+ScriptSetInteger('MAX_TURN_TIME', cMaxTurnTime);
+
// register gear types
for at:= Low(TGearType) to High(TGearType) do
ScriptSetInteger(EnumToStr(at), ord(at));
@@ -3774,6 +4375,9 @@
for we:= Low(TWorldEdge) to High(TWorldEdge) do
ScriptSetInteger(EnumToStr(we), ord(we));
+ScriptSetLongWord('capcolDefault' , capcolDefaultLua);
+ScriptSetLongWord('capcolSetting' , capcolSettingLua);
+
ScriptSetInteger('gstDrowning' , gstDrowning);
ScriptSetInteger('gstHHDriven' , gstHHDriven);
ScriptSetInteger('gstMoving' , gstMoving);
@@ -3808,7 +4412,7 @@
ScriptSetInteger('lfBouncy' , lfBouncy);
ScriptSetInteger('lfLandMask' , lfLandMask);
-ScriptSetInteger('lfCurrentHog' , lfCurrentHog);
+ScriptSetInteger('lfCurHogCrate' , lfCurHogCrate);
ScriptSetInteger('lfHHMask' , lfHHMask);
ScriptSetInteger('lfNotHHObjMask' , lfNotHHObjMask);
ScriptSetInteger('lfAllObjMask' , lfAllObjMask);
@@ -3821,12 +4425,16 @@
ScriptSetInteger('EXPLNoGfx' , EXPLNoGfx);
ScriptSetInteger('EXPLPoisoned' , EXPLPoisoned);
ScriptSetInteger('EXPLDoNotTouchAny', EXPLDoNotTouchAny);
+ScriptSetInteger('EXPLForceDraw' , EXPLForceDraw);
// register functions
lua_register(luaState, _P'HideHog', @lc_hidehog);
lua_register(luaState, _P'RestoreHog', @lc_restorehog);
+lua_register(luaState, _P'IsHogHidden', @lc_ishoghidden);
lua_register(luaState, _P'SaveCampaignVar', @lc_savecampaignvar);
lua_register(luaState, _P'GetCampaignVar', @lc_getcampaignvar);
+lua_register(luaState, _P'SaveMissionVar', @lc_savemissionvar);
+lua_register(luaState, _P'GetMissionVar', @lc_getmissionvar);
lua_register(luaState, _P'band', @lc_band);
lua_register(luaState, _P'bor', @lc_bor);
lua_register(luaState, _P'bnot', @lc_bnot);
@@ -3881,15 +4489,22 @@
lua_register(luaState, _P'SetAmmoDescriptionAppendix', @lc_setammodescriptionappendix);
lua_register(luaState, _P'AddCaption', @lc_addcaption);
lua_register(luaState, _P'SetAmmo', @lc_setammo);
+lua_register(luaState, _P'GetAmmo', @lc_getammo);
lua_register(luaState, _P'SetAmmoDelay', @lc_setammodelay);
+lua_register(luaState, _P'SetAmmoSlot', @lc_setammoslot);
lua_register(luaState, _P'PlaySound', @lc_playsound);
+lua_register(luaState, _P'PlayMusicSound', @lc_playmusicsound);
+lua_register(luaState, _P'StopMusicSound', @lc_stopmusicsound);
lua_register(luaState, _P'SetSoundMask', @lc_setsoundmask);
lua_register(luaState, _P'GetTeamName', @lc_getteamname);
lua_register(luaState, _P'GetTeamIndex', @lc_getteamindex);
lua_register(luaState, _P'GetTeamClan', @lc_getteamclan);
lua_register(luaState, _P'AddTeam', @lc_addteam);
+lua_register(luaState, _P'AddMissionTeam', @lc_addmissionteam);
lua_register(luaState, _P'SetTeamLabel', @lc_setteamlabel);
+lua_register(luaState, _P'SetTeamPassive', @lc_setteampassive);
lua_register(luaState, _P'AddHog', @lc_addhog);
+lua_register(luaState, _P'AddMissionHog', @lc_addmissionhog);
lua_register(luaState, _P'AddAmmo', @lc_addammo);
lua_register(luaState, _P'GetAmmoCount', @lc_getammocount);
lua_register(luaState, _P'HealHog', @lc_healhog);
@@ -3904,6 +4519,7 @@
lua_register(luaState, _P'GetHogFlag', @lc_gethogflag);
lua_register(luaState, _P'GetHogFort', @lc_gethogfort);
lua_register(luaState, _P'GetHogGrave', @lc_gethoggrave);
+lua_register(luaState, _P'IsHogAlive', @lc_ishogalive);
lua_register(luaState, _P'IsHogLocal', @lc_ishoglocal);
lua_register(luaState, _P'GetHogTeamName', @lc_gethogteamname);
lua_register(luaState, _P'SetHogTeamName', @lc_sethogteamname);
@@ -3928,6 +4544,7 @@
lua_register(luaState, _P'GetZoom', @lc_getzoom);
lua_register(luaState, _P'HogSay', @lc_hogsay);
lua_register(luaState, _P'SwitchHog', @lc_switchhog);
+lua_register(luaState, _P'EnableSwitchHog', @lc_enableswitchhog);
lua_register(luaState, _P'HogTurnLeft', @lc_hogturnleft);
lua_register(luaState, _P'GetGearElasticity', @lc_getgearelasticity);
lua_register(luaState, _P'SetGearElasticity', @lc_setgearelasticity);
@@ -3960,9 +4577,16 @@
lua_register(luaState, _P'SetCinematicMode', @lc_setcinematicmode);
lua_register(luaState, _P'SetMaxBuildDistance', @lc_setmaxbuilddistance);
lua_register(luaState, _P'GetAmmoName', @lc_getammoname);
+lua_register(luaState, _P'GetAmmoTimer', @lc_getammotimer);
lua_register(luaState, _P'SetVampiric', @lc_setvampiric);
+lua_register(luaState, _P'GetVampiric', @lc_getvampiric);
lua_register(luaState, _P'SetLaserSight', @lc_setlasersight);
+lua_register(luaState, _P'GetLaserSight', @lc_getlasersight);
lua_register(luaState, _P'Explode', @lc_explode);
+lua_register(luaState, _P'SetTurnTimeLeft', @lc_setturntimeleft);
+lua_register(luaState, _P'SetReadyTimeLeft', @lc_setreadytimeleft);
+lua_register(luaState, _P'SetTurnTimePaused', @lc_setturntimepaused);
+lua_register(luaState, _P'GetTurnTimePaused', @lc_getturntimepaused);
// drawn map functions
lua_register(luaState, _P'AddPoint', @lc_addPoint);
lua_register(luaState, _P'FlushPoints', @lc_flushPoints);
@@ -4084,10 +4708,11 @@
procedure initModule;
begin
-mapDims:= false;
PointsBuffer:= '';
PrevCursorX:= NoPointX;
PrevCursorY:= NoPointX;
+isPendingTurnTimeLeft:= false;
+isPendingReadyTimeLeft:= false;
end;
procedure freeModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uSound.pas
--- a/hedgewars/uSound.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uSound.pas Wed Jul 31 23:14:27 2019 +0200
@@ -43,6 +43,7 @@
procedure ReleaseSound(complete: boolean); // Releases sound-system and used resources.
procedure ResetSound; // Reset sound state to the previous state.
procedure SetSound(enabled: boolean); // Enable/disable sound-system and backup status.
+procedure SetAudioDampen(enabled: boolean); // Enable/disable automatic dampening if losing window focus.
// MUSIC
@@ -61,12 +62,19 @@
// Plays the sound snd [from a given voicepack],
// if keepPlaying is given and true,
// then the sound's playback won't be interrupted if asked to play again.
-procedure PlaySound(snd: TSound);
-procedure PlaySound(snd: TSound; keepPlaying: boolean);
-procedure PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean);
-procedure PlaySoundV(snd: TSound; voicepack: PVoicepack);
-procedure PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean);
-procedure PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean; ignoreMask: boolean);
+// Returns true if sound was found and is played, false otherwise.
+function PlaySound(snd: TSound): boolean;
+function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
+function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
+function PlaySound(snd: TSound; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
+function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
+function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
+function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
+function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
+
+// Plays/stops a sound to replace the main background music temporarily.
+procedure PlayMusicSound(snd: TSound);
+procedure StopMusicSound(snd: TSound);
// Plays sound snd [of voicepack] in a loop, but starts with fadems milliseconds of fade-in.
// Returns sound channel of the looped sound.
@@ -78,11 +86,15 @@
// Stops the normal/looped sound of the given type/in the given channel
// [with a fade-out effect for fadems milliseconds].
procedure StopSound(snd: TSound);
+procedure StopSound(snd: TSound; soundAsMusic: boolean);
procedure StopSoundChan(chn: LongInt);
procedure StopSoundChan(chn, fadems: LongInt);
+// Add voice to the voice queue
procedure AddVoice(snd: TSound; voicepack: PVoicepack);
-procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask: boolean);
+procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
+
+// Actually play next voice
procedure PlayNextVoice;
@@ -104,7 +116,7 @@
// Modifies the sound volume of the game by voldelta and returns the new volume level.
function ChangeVolume(voldelta: LongInt): LongInt;
-// Returns the current volume in percent
+// Returns the current volume in percent. Intended for display on UI.
function GetVolumePercent(): LongInt;
// Returns a pointer to the voicepack with the given name.
@@ -117,6 +129,7 @@
var Volume: LongInt;
SoundTimerTicks: Longword;
+ LastVoiceFailed: boolean;
implementation
uses uVariables, uConsole, uCommands, uDebug, uPhysFSLayer;
@@ -129,16 +142,17 @@
Mus: PMixMusic; // music pointer
isMusicEnabled: boolean;
isSoundEnabled: boolean;
+ isAutoDampening: boolean;
isSEBackup: boolean;
VoiceList : array[0..7] of TVoice = (
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil),
- ( snd: sndNone; voicepack: nil));
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false),
+ ( snd: sndNone; voicepack: nil; isFallback: false));
Soundz: array[TSound] of record
FileName: string[31];
Path, AltPath : TPathType;
@@ -154,6 +168,7 @@
(FileName: 'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGraveImpact
(FileName: 'mineimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineImpact
(FileName: 'minetick.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineTicks
+ // TODO: New mudball sound?
(FileName: 'Droplet1.ogg'; Path: ptSounds; AltPath: ptNone),// sndMudballImpact
(FileName: 'pickhammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndPickhammer
(FileName: 'gun.ogg'; Path: ptSounds; AltPath: ptNone),// sndGun
@@ -232,8 +247,8 @@
(FileName: 'Droplet3.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet3
(FileName: 'egg.ogg'; Path: ptSounds; AltPath: ptNone),// sndEggBreak
(FileName: 'drillgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndDrillRocket
- (FileName: 'PoisonCough.ogg'; Path: ptVoices; AltPath: ptNone),// sndPoisonCough
- (FileName: 'PoisonMoan.ogg'; Path: ptVoices; AltPath: ptNone),// sndPoisonMoan
+ (FileName: 'PoisonCough.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonCough
+ (FileName: 'PoisonMoan.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonMoan
(FileName: 'BirdyLay.ogg'; Path: ptSounds; AltPath: ptNone),// sndBirdyLay
(FileName: 'Whistle.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhistle
(FileName: 'beewater.ogg'; Path: ptSounds; AltPath: ptNone),// sndBeeWater
@@ -276,9 +291,10 @@
(FileName: 'countdown2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown2
(FileName: 'countdown3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown3
(FileName: 'countdown4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown4
- (FileName: 'rubberduck_drop.ogg'; Path: ptSounds; AltPath: ptNone),// sndDuckDrop
- (FileName: 'rubberduck_water.ogg'; Path: ptSounds; AltPath: ptNone),// sndDuckWater
- (FileName: 'rubberduck_die.ogg'; Path: ptSounds; AltPath: ptNone),// sndDuckDie
+ // TODO: Check which creeper (formerly rubberduck) sounds are needed, maybe rename them
+ (FileName: 'rubberduck_drop.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDrop
+ (FileName: 'rubberduck_water.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperWater
+ (FileName: 'rubberduck_die.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDie
(FileName: 'custom1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom1
(FileName: 'custom2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom2
(FileName: 'custom3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom3
@@ -287,42 +303,77 @@
(FileName: 'custom6.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom6
(FileName: 'custom7.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom7
(FileName: 'custom8.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom8
- (FileName: 'minigun.ogg'; Path: ptSounds; AltPath: ptNone) // sndMinigun
+ (FileName: 'minigun.ogg'; Path: ptSounds; AltPath: ptNone),// sndMinigun
+ (FileName: 'flamethrower.ogg'; Path: ptSounds; AltPath: ptNone),// sndFlamethrower
+ (FileName: 'ice_beam_idle.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeamIdle
+ (FileName: 'landgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndLandGun
+ (FileName: 'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndCaseImpact
+ // TODO: New Extra Damage sound
+ (FileName: 'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraDamage
+ (FileName: 'firepunch_hit.ogg'; Path: ptSounds; AltPath: ptNone),// sndFirePunchHit
+ (FileName: 'Grenade.ogg'; Path: ptVoices; AltPath: ptNone),// sndGrenade
+ (FileName: 'Thisoneismine.ogg'; Path: ptVoices; AltPath: ptNone),// sndThisOneIsMine
+ (FileName: 'Whatthe.ogg'; Path: ptVoices; AltPath: ptNone),// sndWhatThe
+ (FileName: 'Solong.ogg'; Path: ptVoices; AltPath: ptNone),// sndSoLong
+ (FileName: 'Ohdear.ogg'; Path: ptVoices; AltPath: ptNone),// sndOhDear
+ (FileName: 'Gonnagetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndGonnaGetYou
+ (FileName: 'Drat.ogg'; Path: ptVoices; AltPath: ptNone),// sndDrat
+ (FileName: 'Bugger.ogg'; Path: ptVoices; AltPath: ptNone),// sndBugger
+ (FileName: 'Amazing.ogg'; Path: ptVoices; AltPath: ptNone),// sndAmazing
+ (FileName: 'Brilliant.ogg'; Path: ptVoices; AltPath: ptNone),// sndBrilliant
+ (FileName: 'Excellent.ogg'; Path: ptVoices; AltPath: ptNone),// sndExcellent
+ (FileName: 'Fire.ogg'; Path: ptVoices; AltPath: ptNone),// sndFire
+ (FileName: 'Watchthis.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchThis
+ (FileName: 'Runaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndRunAway
+ (FileName: 'Revenge.ogg'; Path: ptVoices; AltPath: ptNone),// sndRevenge
+ (FileName: 'Cutitout.ogg'; Path: ptVoices; AltPath: ptNone),// sndCutItOut
+ (FileName: 'Leavemealone.ogg'; Path: ptVoices; AltPath: ptNone),// sndLeaveMeAlone
+ (FileName: 'Ouch.ogg'; Path: ptVoices; AltPath: ptNone),// sndOuch
+ (FileName: 'Hmm.ogg'; Path: ptVoices; AltPath: ptNone),// sndHmm
+ (FileName: 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone),// sndKiss
+ (FileName: 'Flyaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlyAway
+ (FileName: 'planewater.ogg'; Path: ptSounds; AltPath: ptNone) // sndPlaneWater
);
function AskForVoicepack(name: shortstring): Pointer;
var i: Longword;
- locName, path: shortstring;
+ tmp, nameStart, langName, path: shortstring;
begin
+ nameStart:= name;
i:= 0;
- // Adjust voicepack name if there's a localised version version of the voice
- if cLocale <> 'en' then
+ { Adjust for language suffix: Voicepacks can have an optional language suffix.
+ It's an underscore followed by an ISO 639-1 or ISO 639-2 language code.
+ The suffix “_qau” is special, it will enable automatic language selection
+ of this voicepack. For example, if team has set Default_qau as voicepack,
+ and the player language is Russian, the actual voicepack will be Default_ru,
+ provided it can be found on the disk.
+ “qau” is a valid ISO 639-2 language code reserved for local use. }
+ tmp:= Copy(name, Length(name) - 3, 4);
+ if (tmp = '_qau') then
+ name:= Copy(name, 1, Length(name) - 4);
+ if (cLanguage <> 'en') and (tmp = '_qau') then
begin
- locName:= name+'_'+cLocale;
- path:= cPathz[ptVoices] + '/' + locName;
+ langName:= name+'_'+cLanguage;
+ path:= cPathz[ptVoices] + '/' + langName;
if pfsExists(path) then
- name:= locName
+ name:= langName
else
- if Length(cLocale) > 3 then
+ if Length(cLanguage) > 3 then
begin
- locName:= name+'_'+Copy(cLocale,1,2);
- path:= cPathz[ptVoices] + '/' + locName;
+ langName:= name+'_'+Copy(cLanguage,1,2);
+ path:= cPathz[ptVoices] + '/' + langName;
if pfsExists(path) then
- name:= locName
+ name:= langName
end
end;
path:= cPathz[ptVoices] + '/' + name;
- // Fallback to Default if voicepack can't be found at all
- if (name <> 'Default') and (not pfsExists(path)) then
- begin
- path:= cPathz[ptVoices] + '/Default';
- if pfsExists(path) then
- exit(AskForVoicepack('Default'));
- end;
+ // Fallback to localized Default if voicepack can't be found at all
+ if (nameStart <> 'Default_qau') and (not pfsExists(path)) then
+ exit(AskForVoicepack('Default_qau'));
while (voicepacks[i].name <> name) and (voicepacks[i].name <> '') and (i < cMaxTeams) do
begin
@@ -337,9 +388,14 @@
procedure InitSound;
const channels: LongInt = 2;
var success: boolean;
+ s: shortstring;
begin
if not (isSoundEnabled or isMusicEnabled) then
+ begin
+ isAudioMuted:= true;
+ cInitVolume:= 0;
exit;
+ end;
WriteToConsole('Init sound...');
success:= SDL_InitSubSystem(SDL_INIT_AUDIO) = 0;
@@ -357,12 +413,25 @@
WriteLnToConsole(msgFailed);
isSoundEnabled:= false;
isMusicEnabled:= false;
+ isAudioMuted:= true;
+ cInitVolume:= 0;
end;
WriteToConsole('Init SDL_mixer... ');
- if SDLCheck(Mix_Init(MIX_INIT_OGG) <> 0, 'Mix_Init', true) then exit;
+
+ if (Mix_Init(MIX_INIT_OGG or MIX_INIT_OPUS) and MIX_INIT_OPUS) = 0 then
+ begin
+ s:= SDL_GetError();
+ WriteToConsole('Cannot init OPUS: ' + s);
+
+ if SDLCheck(Mix_Init(MIX_INIT_OGG) <> 0, 'Mix_Init', true) then exit;
+ end;
+
WriteLnToConsole(msgOK);
+ // from uVariables to be used by other modules
+ cIsSoundEnabled:= true;
+
Mix_AllocateChannels(Succ(chanTPU));
previousVolume:= cInitVolume;
ChangeVolume(cInitVolume);
@@ -379,6 +448,11 @@
isSoundEnabled:= enabled;
end;
+procedure SetAudioDampen(enabled: boolean);
+begin
+ isAutoDampening:= enabled;
+end;
+
// when complete is false, this procedure just releases some of the chucks on inactive channels
// in this way music is not stopped, nor are chucks currently being played
procedure ReleaseSound(complete: boolean);
@@ -416,36 +490,95 @@
end;
end;
-procedure PlaySound(snd: TSound);
-begin
- PlaySoundV(snd, nil, false, false);
-end;
-
-procedure PlaySound(snd: TSound; keepPlaying: boolean);
+// Get a fallback voice, assuming that snd is not available. Returns sndNone if none is found.
+function GetFallbackV(snd: TSound): TSound;
begin
- PlaySoundV(snd, nil, keepPlaying, false);
-end;
-
-procedure PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean);
-begin
- PlaySoundV(snd, nil, keepPlaying, ignoreMask);
+ // Fallback to sndFirePunch1 / sndOw1 / sndOoff1 if a "higher-numbered" sound is missing
+ if (snd in [sndFirePunch2, sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6]) then
+ GetFallbackV := sndFirePunch1
+ else if (snd in [sndOw2, sndOw3, sndOw4, sndOuch]) then
+ GetFallbackV := sndOw1
+ else if (snd in [sndOoff2, sndOoff3]) then
+ GetFallbackV := sndOoff1
+ // Other fallback sounds
+ else if (snd = sndGrenade) then
+ if random(2) = 0 then
+ GetFallbackV := sndNooo
+ else
+ GetFallbackV := sndUhOh
+ else if (snd in [sndDrat, sndBugger]) then
+ GetFallbackV := sndStupid
+ else if (snd in [sndGonnaGetYou, sndIllGetYou, sndJustYouWait, sndCutItOut, sndLeaveMeAlone]) then
+ GetFallbackV := sndRegret
+ else if (snd in [sndOhDear, sndSoLong]) then
+ GetFallbackV := sndByeBye
+ else if (snd in [sndWhatThe, sndUhOh]) then
+ GetFallbackV := sndNooo
+ else if (snd = sndRunAway) then
+ GetFallbackV := sndOops
+ else if (snd = sndThisOneIsMine) then
+ GetFallbackV := sndReinforce
+ else if (snd in [sndAmazing, sndBrilliant, sndExcellent]) then
+ GetFallbackV := sndEnemyDown
+ else if (snd = sndPoisonCough) then
+ GetFallbackV := sndPoisonMoan
+ else if (snd = sndPoisonMoan) then
+ GetFallbackV := sndPoisonCough
+ else if (snd = sndFlawless) then
+ GetFallbackV := sndVictory
+ else if (snd = sndSameTeam) then
+ GetFallbackV := sndTraitor
+ else if (snd = sndMelon) then
+ GetFallbackV := sndCover
+ // sndHmm is used for enemy turn start, so sndHello is an "okay" replacement
+ else if (snd = sndHmm) then
+ GetFallbackV := sndHello
+ else
+ GetFallbackV := sndNone;
end;
-procedure PlaySoundV(snd: TSound; voicepack: PVoicepack);
+function PlaySound(snd: TSound): boolean;
+begin
+ PlaySound:= PlaySoundV(snd, nil, false, false, false);
+end;
+
+function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
begin
- PlaySoundV(snd, voicepack, false, false);
+ PlaySound:= PlaySoundV(snd, nil, keepPlaying, false, false);
+end;
+
+function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
+begin
+ PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, false);
+end;
+
+function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask, soundAsMusic: boolean): boolean;
+begin
+ PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, soundAsMusic);
end;
-procedure PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean);
+function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
begin
- PlaySoundV(snd, voicepack, keepPlaying, false);
+ PlaySoundV:= PlaySoundV(snd, voicepack, false, false, false);
+end;
+
+function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
+begin
+ PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, false, false);
end;
-procedure PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean; ignoreMask: boolean);
-var s:shortstring;
+function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
+begin
+ PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, ignoreMask, false);
+end;
+
+function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
+var s: shortstring;
+tempSnd: TSound;
rwops: PSDL_RWops;
begin
- if (not isSoundEnabled) or fastUntilLag then
+ PlaySoundV:= false;
+ if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) or fastUntilLag then
exit;
if keepPlaying and (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
@@ -459,15 +592,15 @@
if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
begin
s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
- // Fallback to sndFirePunch1 / sndOw1 / sndOoff1 if a “higher-numbered” sound is missing
+ // Fallback taunts
if (not pfsExists(s)) then
begin
- if (snd in [sndFirePunch2, sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6]) then
- snd := sndFirePunch1
- else if (snd in [sndOw2, sndOw3, sndOw4]) then
- snd := sndOw1
- else if (snd in [sndOoff2, sndOoff3]) then
- snd := sndOoff1;
+ tempSnd := GetFallbackV(snd);
+ if tempSnd <> sndNone then
+ begin
+ snd := tempSnd;
+ LastVoice.snd := tempSnd;
+ end;
s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
end;
WriteToConsole(msgLoading + s + ' ');
@@ -475,7 +608,7 @@
if rwops = nil then
begin
- s:= cPathz[Soundz[snd].AltPath] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
+ s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ... ');
rwops := rwopsOpenRead(s);
end;
@@ -486,7 +619,8 @@
else
WriteLnToConsole(msgOK)
end;
- lastChan[snd]:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], 0, -1)
+ lastChan[snd]:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], 0, -1);
+ PlaySoundV:= true;
end
else
begin
@@ -507,16 +641,42 @@
if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit;
WriteLnToConsole(msgOK);
end;
- lastChan[snd]:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], 0, -1)
+ lastChan[snd]:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], 0, -1);
+ PlaySoundV:= true;
end;
end;
+procedure PlayMusicSound(snd: TSound);
+begin
+ PauseMusic;
+ PlaySound(snd, false, false, true);
+end;
+
+procedure StopMusicSound(snd: TSound);
+begin
+ StopSound(snd, true);
+ ResumeMusic;
+end;
+
procedure AddVoice(snd: TSound; voicepack: PVoicepack);
begin
- AddVoice(snd, voicepack, false);
+ AddVoice(snd, voicepack, false, false);
end;
-procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask: boolean);
+{
+AddVoice: Add a voice to the voice queue.
+* snd: Sound ID
+* voicepack: Hedgehog voicepack
+* ignoreMask: If true, the sound will be played anyway if masked by Lua
+* isFallback: If true, this sound is added as fallback if the sound previously added to the
+ queue was not found. Useful to make sure a voice is always played, even if
+ a voicepack is incomplete.
+ Example:
+ AddVoice(sndRevenge, voiceAttacker);
+ AddVoice(sndRegret, voiceVictim, false, true);
+ --> plays sndRegret if sndRevenge could not be played.
+}
+procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
var i : LongInt;
begin
@@ -544,11 +704,13 @@
begin
VoiceList[i].snd:= snd;
VoiceList[i].voicepack:= voicepack;
+ VoiceList[i].isFallback:= isFallback;
end
end;
procedure PlayNextVoice;
var i : LongInt;
+ played : boolean;
begin
if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd <> sndNone) and (lastChan[LastVoice.snd] <> -1) and (Mix_Playing(lastChan[LastVoice.snd]) <> 0)) then
exit;
@@ -556,14 +718,19 @@
while (i sndNone) then
+ played:= false;
+ if (VoiceList[i].snd <> sndNone) and ((not VoiceList[i].isFallback) or LastVoiceFailed) then
begin
LastVoice.snd:= VoiceList[i].snd;
LastVoice.voicepack:= VoiceList[i].voicepack;
+ LastVoice.isFallback:= VoiceList[i].isFallback;
VoiceList[i].snd:= sndNone;
- PlaySoundV(LastVoice.snd, LastVoice.voicepack)
+ played:= PlaySoundV(LastVoice.snd, LastVoice.voicepack);
+ // Remember if sound was not played.
+ LastVoiceFailed:= (not played);
end
- else LastVoice.snd:= sndNone;
+ else
+ LastVoice.snd:= sndNone;
end;
function LoopSound(snd: TSound): LongInt;
@@ -622,7 +789,7 @@
s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ');
defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1);
- if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit;
+ if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit(-1);
WriteLnToConsole(msgOK);
end;
if fadems > 0 then
@@ -634,7 +801,12 @@
procedure StopSound(snd: TSound);
begin
- if not isSoundEnabled then
+ StopSound(snd, false);
+end;
+
+procedure StopSound(snd: TSound; soundAsMusic: boolean);
+begin
+ if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) then
exit;
if (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
@@ -659,7 +831,10 @@
exit;
if (chn <> -1) and (Mix_Playing(chn) <> 0) then
- Mix_FadeOutChannel(chn, fadems);
+ if isAudioMuted then
+ Mix_HaltChannel(chn)
+ else
+ Mix_FadeOutChannel(chn, fadems);
end;
procedure PlayMusic;
@@ -713,6 +888,12 @@
function GetVolumePercent(): LongInt;
begin
GetVolumePercent:= Volume * 100 div MIX_MAX_VOLUME;
+ // 0 and 100 will only be displayed when at min/max values
+ // to avoid confusion.
+ if ((GetVolumePercent = 0) and (Volume > 0)) then
+ GetVolumePercent:= 1
+ else if ((GetVolumePercent = 100) and (Volume < MIX_MAX_VOLUME)) then
+ GetVolumePercent:= 99;
end;
function ChangeVolume(voldelta: LongInt): LongInt;
@@ -743,7 +924,7 @@
procedure DampenAudio;
begin
- if (isAudioMuted) then
+ if (isAudioMuted or (not isAutoDampening)) then
exit;
previousVolume:= Volume;
ChangeVolume(-Volume * 7 div 9);
@@ -751,7 +932,7 @@
procedure UndampenAudio;
begin
- if (isAudioMuted) then
+ if (isAudioMuted or (not isAutoDampening)) then
exit;
ChangeVolume(previousVolume - Volume);
end;
@@ -840,6 +1021,7 @@
begin
isMusicEnabled:= true;
isSoundEnabled:= true;
+ isAutoDampening:= true;
cInitVolume:= 100;
end;
@@ -851,12 +1033,15 @@
MusicFN:='';
SDMusicFN:= 'sdmusic.ogg';
+ FallbackMusicFN:='';
+ FallbackSDMusicFN:= 'sdmusic.ogg';
Mus:= nil;
isAudioMuted:= false;
isSEBackup:= isSoundEnabled;
Volume:= 0;
SoundTimerTicks:= 0;
- defVoicepack:= AskForVoicepack('Default');
+ defVoicepack:= AskForVoicepack('Default_qau');
+ LastVoiceFailed:= false;
for i:= Low(TSound) to High(TSound) do
lastChan[i]:= -1;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uStats.pas
--- a/hedgewars/uStats.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uStats.pas Wed Jul 31 23:14:27 2019 +0200
@@ -22,12 +22,16 @@
interface
uses uConsts, uTypes;
-var TotalRounds: LongInt; // Number of rounds played (-1 if game not started)
+var TotalRoundsPre: LongInt; // Helper variable for calculating start of Sudden Death and more. Starts at -1 and is incremented on the turn BEFORE the turn which marks the start of the next round. Always -1 while in hog placing phase
+ TotalRoundsReal: LongInt; // Total number of rounds played (-1 if not started or in hog placing phase). Exported to Lua as 'TotalRounds'
FinishedTurnsTotal: LongInt;
+ // Variables to disable certain portions of game stats (set by Lua)
SendGameResultOn : boolean = true;
SendRankingStatsOn : boolean = true;
SendAchievementsStatsOn : boolean = true;
SendHealthStatsOn : boolean = true;
+ // Clan death log, used for game stats
+ ClanDeathLog : PClanDeathLogEntry;
procedure initModule;
procedure freeModule;
@@ -38,7 +42,10 @@
procedure HedgehogDamaged(Gear: PGear; Attacker: PHedgehog; Damage: Longword; killed: boolean);
procedure TargetHit;
procedure Skipped;
+function getIsTurnSkipped: boolean;
+procedure TurnStats;
procedure TurnReaction;
+procedure TurnStatsReset;
procedure SendStats;
procedure hedgehogFlight(Gear: PGear; time: Longword);
procedure declareAchievement(id, teamname, location: shortstring; value: LongInt);
@@ -48,32 +55,43 @@
implementation
uses uSound, uLocale, uVariables, uUtils, uIO, uCaptions, uMisc, uConsole, uScript;
-var DamageClan : Longword = 0;
- DamageTotal : Longword = 0;
- DamageTurn : Longword = 0;
- PoisonTurn : Longword = 0; // Poisoned enemies per turn
- PoisonClan : Longword = 0; // Poisoned own clan members in turn
- PoisonTotal : Longword = 0; // Poisoned hogs in whole round
- KillsClan : LongWord = 0;
- Kills : LongWord = 0;
- KillsTotal : LongWord = 0;
- HitTargets : LongWord = 0; // Target (gtTarget) hits per turn
- AmmoUsedCount : Longword = 0;
- AmmoDamagingUsed : boolean = false;
- SkippedTurns: LongWord = 0;
- isTurnSkipped: boolean = false;
- vpHurtSameClan: PVoicepack = nil;
- vpHurtEnemy: PVoicepack = nil;
+var DamageClan : Longword = 0; // Damage of own clan in turn
+ DamageTeam : Longword = 0; // Damage of own team in turn
+ DamageTotal : Longword = 0; // Total damage dealt in game
+ DamageTurn : Longword = 0; // Damage in turn
+ PoisonTurn : Longword = 0; // Poisoned enemies in turn
+ PoisonClan : Longword = 0; // Poisoned own clan members in turn
+ PoisonTeam : Longword = 0; // Poisoned own team members in turn
+ PoisonTotal : Longword = 0; // Poisoned hogs in whole round
+ KillsClan : LongWord = 0; // Own clan members killed in turn
+ KillsTeam : LongWord = 0; // Own team members killed in turn
+ KillsSD : LongWord = 0; // Killed hedgehogs in turn that died by Sudden Death water rise
+ Kills : LongWord = 0; // Killed hedgehogs in turn (including those that died by Sudden Death water rise)
+ KillsTotal : LongWord = 0; // Total killed hedgehogs in game
+ HitTargets : LongWord = 0; // Target (gtTarget) hits in turn
+ AmmoUsedCount : Longword = 0; // Number of times an ammo has been used this turn
+ AmmoDamagingUsed : boolean = false; // true if damaging ammo was used in turn
+ FirstBlood : boolean = false; // true if the “First blood” taunt has been used in this game
+ StepFirstBlood : boolean = false; // true if the “First blood” taunt is to be used this turn
+ LeaveMeAlone : boolean = false; // true if the “Leave me alone” taunt is to be used this turn
+ SkippedTurns: LongWord = 0; // number of skipped turns in game
+ isTurnSkipped: boolean = false; // true if this turn was skipped
+ vpHurtSameClan: PVoicepack = nil; // voicepack of current clan (used for taunts)
+ vpHurtEnemy: PVoicepack = nil; // voicepack of enemy (used for taunts)
procedure HedgehogPoisoned(Gear: PGear; Attacker: PHedgehog);
begin
- if Attacker^.Team^.Clan = Gear^.HEdgehog^.Team^.Clan then
+ if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
begin
- vpHurtSameClan:= CurrentHedgehog^.Team^.voicepack;
- inc(PoisonClan)
+ vpHurtSameClan:= Gear^.Hedgehog^.Team^.voicepack;
+ inc(PoisonClan);
+ if Attacker^.Team = Gear^.Hedgehog^.Team then
+ inc(PoisonTeam);
end
else
begin
+ if not FirstBlood then
+ StepFirstBlood:= true;
vpHurtEnemy:= Gear^.Hedgehog^.Team^.voicepack;
inc(PoisonTurn)
end;
@@ -89,34 +107,74 @@
procedure HedgehogDamaged(Gear: PGear; Attacker: PHedgehog; Damage: Longword; killed: boolean);
begin
if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
- vpHurtSameClan:= CurrentHedgehog^.Team^.voicepack
+ vpHurtSameClan:= Gear^.Hedgehog^.Team^.voicepack
else
+ begin
+ if not FirstBlood then
+ StepFirstBlood:= true;
vpHurtEnemy:= Gear^.Hedgehog^.Team^.voicepack;
+ if (not killed) and (not bDuringWaterRise) then
+ begin
+ // Check if victim got attacked by RevengeHog again
+ if (Gear^.Hedgehog^.RevengeHog <> nil) and (Gear^.Hedgehog^.RevengeHog = Attacker) and (Gear^.Hedgehog^.stats.StepRevenge = false) then
+ LeaveMeAlone:= true;
+ // Check if attacker got revenge
+ if (Attacker^.RevengeHog <> nil) and (Attacker^.RevengeHog = Gear^.Hedgehog) then
+ begin
+ Attacker^.stats.GotRevenge:= true;
+ // Also reset the "in-row" counter to restore LeaveMeAlone/CutItOut taunts
+ Attacker^.stats.StepDamageRecvInRow:= 0;
+ Attacker^.RevengeHog:= nil;
+ end
+ // If not, victim remembers their attacker to plan *their* revenge
+ else
+ begin
+ Gear^.Hedgehog^.RevengeHog:= Attacker;
+ // To prevent "LeaveMeAlone" being activated if same hog is hit by attacker
+ // multiple times in the same turn.
+ Gear^.Hedgehog^.stats.StepRevenge:= true;
+ end;
+ end
+ end;
//////////////////////////
-inc(Attacker^.stats.StepDamageGiven, Damage);
-inc(Attacker^.stats.DamageGiven, Damage);
-inc(Gear^.Hedgehog^.stats.StepDamageRecv, Damage);
+if (not bDuringWaterRise) then
+ begin
+ inc(Attacker^.stats.StepDamageGiven, Damage);
+ inc(Gear^.Hedgehog^.stats.StepDamageRecv, Damage);
+ end;
if CurrentHedgehog^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then inc(DamageClan, Damage);
+if CurrentHedgehog^.Team = Gear^.Hedgehog^.Team then inc(DamageTeam, Damage);
if killed then
begin
Gear^.Hedgehog^.stats.StepDied:= true;
- inc(Attacker^.stats.StepKills);
inc(Kills);
+
inc(KillsTotal);
- inc(Attacker^.Team^.stats.Kills);
- if (Attacker^.Team^.TeamName = Gear^.Hedgehog^.Team^.TeamName) then
+
+ if bDuringWaterRise then
+ inc(KillsSD)
+ else
begin
- inc(Attacker^.Team^.stats.TeamKills);
- inc(Attacker^.Team^.stats.TeamDamage, Gear^.Damage);
- end;
- if Gear = Attacker^.Gear then
- inc(Attacker^.Team^.stats.Suicides);
- if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
- inc(KillsClan);
+ inc(Attacker^.stats.StepKills);
+ inc(Attacker^.Team^.stats.Kills);
+ if (Attacker^.Team^.TeamName = Gear^.Hedgehog^.Team^.TeamName) then
+ begin
+ inc(Attacker^.Team^.stats.TeamKills);
+ inc(Attacker^.Team^.stats.TeamDamage, Gear^.Damage);
+ end;
+ if Gear = Attacker^.Gear then
+ inc(Attacker^.Team^.stats.Suicides);
+ if Attacker^.Team^.Clan = Gear^.Hedgehog^.Team^.Clan then
+ begin
+ inc(KillsClan);
+ if Attacker^.Team = Gear^.Hedgehog^.Team then
+ inc(KillsTeam);
+ end;
+ end;
end;
inc(DamageTotal, Damage);
@@ -134,85 +192,22 @@
isTurnSkipped:= true
end;
-procedure TurnReaction;
-var i, t: LongInt;
- killsCheck: LongInt;
- s: ansistring;
+function getIsTurnSkipped: boolean;
begin
-//TryDo(not bBetweenTurns, 'Engine bug: TurnReaction between turns', true);
-
-inc(FinishedTurnsTotal);
-if FinishedTurnsTotal <> 0 then
- begin
- s:= ansistring(CurrentHedgehog^.Name);
- inc(CurrentHedgehog^.stats.FinishedTurns);
- // If the hog sacrificed (=kamikaze/piano) itself, this needs to be taken into accounts for the reactions later
- if (CurrentHedgehog^.stats.Sacrificed) then
- killsCheck:= 1
- else
- killsCheck:= 0;
-
- // First blood (first damage, poison or kill)
- if ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) and ((CurrentHedgehog^.stats.DamageGiven = DamageTotal) and (CurrentHedgehog^.stats.StepKills = KillsTotal) and (PoisonTotal = PoisonTurn + PoisonClan)) then
- AddVoice(sndFirstBlood, CurrentTeam^.voicepack)
-
- // Hog hurts, poisons or kills itself (except sacrifice)
- else if (CurrentHedgehog^.stats.Sacrificed = false) and ((CurrentHedgehog^.stats.StepDamageRecv > 0) or (CurrentHedgehog^.stats.StepPoisoned) or (CurrentHedgehog^.stats.StepDied)) then
- begin
- AddVoice(sndStupid, PreviousTeam^.voicepack);
- // Message for hurting itself only (not drowning)
- if (CurrentHedgehog^.stats.DamageGiven = CurrentHedgehog^.stats.StepDamageRecv) and (CurrentHedgehog^.stats.StepDamageRecv >= 1) then
- AddCaption(FormatA(GetEventString(eidHurtSelf), s), cWhiteColor, capgrpMessage);
- end
+getIsTurnSkipped:= isTurnSkipped;
+end;
- // Hog hurts, poisons or kills own team/clan member. Sacrifice is taken into account
- else if (DamageClan <> 0) or (KillsClan > killsCheck) or (PoisonClan <> 0) then
- if (DamageTurn > DamageClan) or (Kills > KillsClan) then
- if random(2) = 0 then
- AddVoice(sndNutter, CurrentTeam^.voicepack)
- else
- AddVoice(sndWatchIt, vpHurtSameClan)
- else
- if random(2) = 0 then
- AddVoice(sndSameTeam, vpHurtSameClan)
- else
- AddVoice(sndTraitor, vpHurtSameClan)
-
- // Hog hurts, kills or poisons enemy
- else if (CurrentHedgehog^.stats.StepDamageGiven <> 0) or (CurrentHedgehog^.stats.StepKills > killsCheck) or (PoisonTurn <> 0) then
- if Kills > killsCheck then
- AddVoice(sndEnemyDown, CurrentTeam^.voicepack)
- else
- AddVoice(sndRegret, vpHurtEnemy)
-
- // Missed shot
- // A miss is defined as a shot with a damaging weapon with 0 kills, 0 damage, 0 hogs poisoned and 0 targets hit
- else if AmmoDamagingUsed and (Kills <= killsCheck) and (PoisonTurn = 0) and (PoisonClan = 0) and (DamageTurn = 0) and (HitTargets = 0) then
- // Chance to call hedgehog stupid if sacrificed for nothing
- if CurrentHedgehog^.stats.Sacrificed then
- if random(2) = 0 then
- AddVoice(sndMissed, PreviousTeam^.voicepack)
- else
- AddVoice(sndStupid, PreviousTeam^.voicepack)
- else
- AddVoice(sndMissed, PreviousTeam^.voicepack)
-
- // Timeout
- else if (AmmoUsedCount > 0) and (not isTurnSkipped) then
- begin end// nothing ?
-
- // Turn skipped
- else if isTurnSkipped and (not PlacingHogs) then
- begin
- AddVoice(sndCoward, PreviousTeam^.voicepack);
- AddCaption(FormatA(GetEventString(eidTurnSkipped), s), cWhiteColor, capgrpMessage);
- end
- end;
-
+procedure TurnStats;
+var i, t: LongInt;
+ c: Longword;
+ newEntry: PClanDeathLogEntry;
+begin
+inc(FinishedTurnsTotal);
for t:= 0 to Pred(TeamsCount) do // send even on zero turn
with TeamsArray[t]^ do
for i:= 0 to cMaxHHIndex do
+ begin
with Hedgehogs[i].stats do
begin
inc(DamageRecv, StepDamageRecv);
@@ -223,30 +218,218 @@
MaxStepDamageGiven:= StepDamageGiven;
if StepKills > MaxStepKills then
MaxStepKills:= StepKills;
+ if (Hedgehogs[i].Team <> nil) and (Hedgehogs[i].Team^.Clan^.ClanIndex <> CurrentHedgehog^.Team^.Clan^.ClanIndex) then
+ begin
+ if StepDamageRecv > 0 then
+ inc(StepDamageRecvInRow)
+ else
+ StepDamageRecvInRow:= 0;
+ if StepDamageRecvInRow >= 3 then
+ LeaveMeAlone:= true;
+ end;
+ end;
+ end;
+
+
+// Write into the death log which clans died in this turn,
+// important for final rankings.
+c:= 0;
+newEntry:= nil;
+for t:= 0 to Pred(ClansCount) do
+ with ClansArray[t]^ do
+ begin
+ if (ClanHealth = 0) and (ClansArray[t]^.DeathLogged = false) then
+ begin
+ if c = 0 then
+ begin
+ new(newEntry);
+ newEntry^.Turn := FinishedTurnsTotal;
+ newEntry^.NextEntry := nil;
+ end;
+
+ newEntry^.KilledClans[c]:= ClansArray[t];
+ inc(c);
+ newEntry^.KilledClansCount := c;
+ ClansArray[t]^.DeathLogged:= true;
+ end;
+
+ if SendHealthStatsOn then
+ SendStat(siClanHealth, IntToStr(Color) + ' ' + IntToStr(ClanHealth));
+ end;
+if newEntry <> nil then
+ begin
+ if ClanDeathLog <> nil then
+ begin
+ newEntry^.NextEntry:= ClanDeathLog;
+ end;
+ ClanDeathLog:= newEntry;
+ end;
+
+end;
+
+procedure TurnReaction;
+var killsCheck: LongInt;
+ s: ansistring;
+begin
+//TryDo(not bBetweenTurns, 'Engine bug: TurnReaction between turns', true);
+
+if FinishedTurnsTotal <> 0 then
+ begin
+ s:= ansistring(CurrentHedgehog^.Name);
+ inc(CurrentHedgehog^.stats.FinishedTurns);
+
+ // killsCheck is used to take deaths into account that were not a traditional "kill"
+ // Hogs that died during SD water rise do not count as "kills" for taunts
+ killsCheck:= KillsSD;
+ // If the hog sacrificed (=kamikaze/piano) itself, this needs to be taken into account for the reactions later
+ if (CurrentHedgehog^.stats.Sacrificed) then
+ inc(killsCheck);
+
+ // First blood (first damage, poison or kill of enemy)
+ if (StepFirstBlood) and (not FirstBlood) and (ClansCount > 1) and ((DamageTotal > 0) or (KillsTotal > 0) or (PoisonTotal > 0)) then
+ begin
+ FirstBlood:= true;
+ AddVoice(sndFirstBlood, CurrentTeam^.voicepack);
+ end
+
+ // Hog hurts, poisons or kills itself (except sacrifice)
+ else if (CurrentHedgehog^.stats.Sacrificed = false) and ((CurrentHedgehog^.stats.StepDamageRecv > 0) or (CurrentHedgehog^.stats.StepPoisoned) or (CurrentHedgehog^.stats.StepDied)) then
+ // Hurt itself only (without dying)
+ if (CurrentHedgehog^.stats.StepDamageGiven = CurrentHedgehog^.stats.StepDamageRecv) and (CurrentHedgehog^.stats.StepDamageRecv >= 1) and (not CurrentHedgehog^.stats.StepDied) then
+ begin
+ // Announcer message + random taunt
+ AddCaption(FormatA(GetEventString(eidHurtSelf), s), capcolDefault, capgrpMessage);
+ if (CurrentHedgehog^.stats.StepDamageGiven <= CurrentHedgehog^.stats.StepDamageRecv) and (CurrentHedgehog^.stats.StepDamageRecv >= 1) then
+ case random(3) of
+ 0: AddVoice(sndStupid, PreviousTeam^.voicepack);
+ 1: AddVoice(sndBugger, CurrentTeam^.voicepack);
+ 2: AddVoice(sndDrat, CurrentTeam^.voicepack);
+ end;
+ end
+ // Hurt itself and others, or died
+ else
+ AddVoice(sndStupid, PreviousTeam^.voicepack)
+
+ // Hog hurts, poisons or kills own team/clan member. Sacrifice is taken into account
+ else if (DamageClan <> 0) or (KillsClan > killsCheck) or (PoisonClan <> 0) then
+ if (DamageTurn > DamageClan) or ((Kills-KillsSD) > KillsClan) then
+ if random(2) = 0 then
+ AddVoice(sndNutter, CurrentTeam^.voicepack)
+ else
+ AddVoice(sndWatchIt, vpHurtSameClan)
+ else
+ // Attacked same team
+ if (random(2) = 0) and ((DamageTeam <> 0) or (KillsTeam > killsCheck) or (PoisonTeam <> 0)) then
+ AddVoice(sndSameTeam, vpHurtSameClan)
+ // Attacked same team or a clan member
+ else
+ AddVoice(sndTraitor, vpHurtSameClan)
+
+ // Hog hurts, kills or poisons enemy
+ else if (CurrentHedgehog^.stats.StepDamageGiven <> 0) or (CurrentHedgehog^.stats.StepKills > killsCheck) or (PoisonTurn <> 0) then
+ // 3 kills or more
+ if Kills > killsCheck + 2 then
+ AddVoice(sndAmazing, CurrentTeam^.voicepack)
+ // 2 kills
+ else if Kills = (killsCheck + 2) then
+ if random(2) = 0 then
+ AddVoice(sndBrilliant, CurrentTeam^.voicepack)
+ else
+ AddVoice(sndExcellent, CurrentTeam^.voicepack)
+ // 1 kill
+ else if Kills = (killsCheck + 1) then
+ AddVoice(sndEnemyDown, CurrentTeam^.voicepack)
+ // 0 kills, only damage or poison
+ else
+ // possible reactions of victim, in the order of preference:
+ // 1. claiming revenge
+ // 2. complaining about getting attacked too often
+ // 3. threatening enemy with retaliation
+ if CurrentHedgehog^.stats.GotRevenge then
+ begin
+ AddVoice(sndRevenge, CurrentHedgehog^.Team^.voicepack);
+ // If revenge taunt was added, one of the following voices is
+ // added as fallback (4th param), in case of a missing Revenge sound file.
+ case random(4) of
+ 0: AddVoice(sndRegret, vpHurtEnemy, false, true);
+ 1: AddVoice(sndGonnaGetYou, vpHurtEnemy, false, true);
+ 2: AddVoice(sndIllGetYou, vpHurtEnemy, false, true);
+ 3: AddVoice(sndJustYouWait, vpHurtEnemy, false, true);
+ end;
+ end
+ else
+ if LeaveMeAlone then
+ if random(2) = 0 then
+ AddVoice(sndCutItOut, vpHurtEnemy)
+ else
+ AddVoice(sndLeaveMeAlone, vpHurtEnemy)
+ else
+ case random(4) of
+ 0: AddVoice(sndRegret, vpHurtEnemy);
+ 1: AddVoice(sndGonnaGetYou, vpHurtEnemy);
+ 2: AddVoice(sndIllGetYou, vpHurtEnemy);
+ 3: AddVoice(sndJustYouWait, vpHurtEnemy);
+ end
+
+ // Missed shot
+ // A miss is defined as a shot with a damaging weapon with 0 kills, 0 damage, 0 hogs poisoned and 0 targets hit
+ else if AmmoDamagingUsed and (Kills <= killsCheck) and (PoisonTurn = 0) and (PoisonClan = 0) and (DamageTurn = 0) and (HitTargets = 0) then
+ // Chance to call hedgehog stupid or nutter if sacrificed for nothing
+ if CurrentHedgehog^.stats.Sacrificed then
+ case random(3) of
+ 0: AddVoice(sndMissed, PreviousTeam^.voicepack);
+ 1: AddVoice(sndStupid, PreviousTeam^.voicepack);
+ 2: AddVoice(sndNutter, PreviousTeam^.voicepack);
+ end
+ else
+ AddVoice(sndMissed, PreviousTeam^.voicepack)
+
+ // Timeout
+ else if (AmmoUsedCount > 0) and (not isTurnSkipped) then
+ begin end// nothing ?
+
+ // Turn skipped
+ else if isTurnSkipped and (not PlacingHogs) and (not PlacingKings) then
+ begin
+ AddVoice(sndCoward, PreviousTeam^.voicepack);
+ AddCaption(FormatA(GetEventString(eidTurnSkipped), s), capcolDefault, capgrpMessage);
+ end
+ end;
+end;
+
+procedure TurnStatsReset;
+var t, i: LongInt;
+begin
+for t:= 0 to Pred(TeamsCount) do // send even on zero turn
+ with TeamsArray[t]^ do
+ for i:= 0 to cMaxHHIndex do
+ with Hedgehogs[i].stats do
+ begin
StepKills:= 0;
StepDamageRecv:= 0;
StepDamageGiven:= 0;
StepPoisoned:= false;
StepDied:= false;
+ GotRevenge:= false;
+ StepRevenge:= false;
end;
-if SendHealthStatsOn then
- for t:= 0 to Pred(ClansCount) do
- with ClansArray[t]^ do
- begin
- SendStat(siClanHealth, IntToStr(Color) + ' ' + IntToStr(ClanHealth));
- end;
-
Kills:= 0;
+KillsSD:= 0;
KillsClan:= 0;
+KillsTeam:= 0;
DamageClan:= 0;
+DamageTeam:= 0;
DamageTurn:= 0;
HitTargets:= 0;
PoisonClan:= 0;
+PoisonTeam:= 0;
PoisonTurn:= 0;
AmmoUsedCount:= 0;
+LeaveMeAlone:= false;
AmmoDamagingUsed:= false;
-isTurnSkipped:= false
+isTurnSkipped:= false;
+StepFirstBlood:= false;
end;
procedure AmmoUsed(am: TAmmoType);
@@ -267,7 +450,7 @@
end;
procedure SendStats;
-var i, t: LongInt;
+var i, t, c: LongInt;
msd, msk: Longword; msdhh, mskhh: PHedgehog;
mskcnt: Longword;
maxTeamKills : Longword;
@@ -277,6 +460,8 @@
maxTeamDamage : Longword;
maxTeamDamageName : shortstring;
winnersClan : PClan;
+ deathEntry : PClanDeathLogEntry;
+ currentRank: Longword;
begin
if SendHealthStatsOn then
msd:= 0; msdhh:= nil;
@@ -286,6 +471,7 @@
maxTurnSkips := 0;
maxTeamDamage := 0;
winnersClan:= nil;
+ currentRank:= 0;
for t:= 0 to Pred(TeamsCount) do
with TeamsArray[t]^ do
@@ -310,13 +496,18 @@
end;
end;
- { send player stats for winner teams }
- if Clan^.ClanHealth > 0 then
+ { Send player stats for winner clans/teams.
+ The clan that survived is ranked 1st. }
+ if (Clan^.ClanHealth > 0) then
begin
winnersClan:= Clan;
if SendRankingStatsOn then
+ begin
+ currentRank:= 1;
+ SendStat(siTeamRank, _S'1');
SendStat(siPlayerKills, IntToStr(Clan^.Color) + ' ' +
IntToStr(stats.Kills) + ' ' + TeamName);
+ end;
end;
{ determine maximum values of TeamKills, TurnSkips, TeamDamage }
@@ -338,21 +529,43 @@
end;
- { now send player stats for loser teams }
+ inc(currentRank);
+
+ { Now send player stats for loser teams/clans.
+ The losing clans are ranked in the reverse order they died.
+ The clan that died last is ranked 2nd,
+ the clan that died second to last is ranked 3rd,
+ and so on.
+ Clans that died in the same turn share their rank.
+ If a clan died multiple times in the match
+ (e.g. due to resurrection), only the *latest* death of
+ that clan counts (handled in gtResurrector).
+ }
+ deathEntry := ClanDeathLog;
+ i:= 0;
if SendRankingStatsOn then
- for t:= 0 to Pred(TeamsCount) do
+ while (deathEntry <> nil) do
begin
- with TeamsArray[t]^ do
- begin
- if Clan^.ClanHealth = 0 then
+ for c:= 0 to Pred(deathEntry^.KilledClansCount) do
+ if ((deathEntry^.KilledClans[c]^.ClanHealth) = 0) and (not deathEntry^.KilledClans[c]^.StatsHandled) then
begin
- SendStat(siPlayerKills, IntToStr(Clan^.Color) + ' ' +
- IntToStr(stats.Kills) + ' ' + TeamName);
- end;
+ for t:= 0 to Pred(TeamsCount) do
+ if (TeamsArray[t]^.Clan^.ClanIndex = deathEntry^.KilledClans[c]^.ClanIndex) then
+ begin
+ SendStat(siTeamRank, IntToStr(currentRank));
+ SendStat(siPlayerKills, IntToStr(deathEntry^.killedClans[c]^.Color) + ' ' +
+ IntToStr(TeamsArray[t]^.stats.Kills) + ' ' + TeamsArray[t]^.TeamName);
+ end;
+ deathEntry^.KilledClans[c]^.StatsHandled:= true;
+ inc(i);
+ end;
+ if i > 0 then
+ inc(currentRank, i);
+ i:= 0;
+ deathEntry:= deathEntry^.NextEntry;
end;
- end;
- // “Achievements” / Details part of stats screen
+ // "Achievements" / Details part of stats screen
if SendAchievementsStatsOn then
begin
if msdhh <> nil then
@@ -374,13 +587,17 @@
// now to console
if winnersClan <> nil then
begin
+ ScriptCall('onGameResult', winnersClan^.ClanIndex);
WriteLnToConsole('WINNERS');
WriteLnToConsole(inttostr(winnersClan^.TeamsNumber));
for t:= 0 to winnersClan^.TeamsNumber - 1 do
WriteLnToConsole(winnersClan^.Teams[t]^.TeamName);
end
else
+ begin
+ ScriptCall('onGameResult', -1);
WriteLnToConsole('DRAW');
+ end;
ScriptCall('onAchievementsDeclaration');
end;
@@ -410,22 +627,31 @@
procedure initModule;
begin
DamageClan := 0;
+ DamageTeam := 0;
DamageTotal := 0;
DamageTurn := 0;
PoisonClan := 0;
+ PoisonTeam := 0;
PoisonTurn := 0;
KillsClan := 0;
+ KillsTeam := 0;
+ KillsSD := 0;
Kills := 0;
KillsTotal := 0;
HitTargets := 0;
AmmoUsedCount := 0;
AmmoDamagingUsed := false;
+ FirstBlood:= false;
+ StepFirstblood:= false;
+ LeaveMeAlone := false;
SkippedTurns:= 0;
isTurnSkipped:= false;
vpHurtSameClan:= nil;
vpHurtEnemy:= nil;
- TotalRounds:= -1;
+ TotalRoundsPre:= -1;
+ TotalRoundsReal:= -1;
FinishedTurnsTotal:= -1;
+ ClanDeathLog:= nil;
end;
procedure freeModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uStore.pas
--- a/hedgewars/uStore.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uStore.pas Wed Jul 31 23:14:27 2019 +0200
@@ -46,6 +46,8 @@
procedure LoadHedgehogHat(var HH: THedgehog; newHat: shortstring);
procedure LoadHedgehogHat2(var HH: THedgehog; newHat: shortstring; allowSurfReuse: boolean);
+procedure LoadDefaultClanColors(s: shortstring);
+
procedure InitZoom(zoom: real);
procedure SetupOpenGL;
@@ -112,6 +114,7 @@
clr.r:= Color shr 16;
clr.g:= (Color shr 8) and $FF;
clr.b:= Color and $FF;
+clr.a:= $FF;
tmpsurf:= TTF_RenderUTF8_Blended(Fontz[Font].Handle, s, clr);
if tmpsurf = nil then exit;
tmpsurf:= doSurfaceConversion(tmpsurf);
@@ -277,19 +280,23 @@
if ExtDriven then
NameTagTex:= RenderStringTexLim(ansistring(Name), Clan^.Color, fnt16, cTeamHealthWidth)
else NameTagTex:= RenderStringTex(ansistring(Name), Clan^.Color, fnt16);
- if Hat = 'NoHat' then
+ if cHolidaySilliness then
begin
- if (month = 4) and (md = 20) then
- Hat := 'eastertop' // Easter
- else if (month = 12) and ((md = 24) or (md = 25) or (md = 26)) then
- Hat := 'Santa' // Christmas Eve/Christmas/Boxing Day
- else if (month = 10) and (md = 31) then
- Hat := 'fr_pumpkin'; // Halloween/Hedgewars' birthday
- end;
- if (month = 4) and (md = 1) then
- begin
- AprilOne:= true;
- Hat := 'fr_tomato'; // avoid promoting violence to hedgehogs. see http://hedgewars.org/node/5818
+ // Special hats on special days
+ if Hat = 'NoHat' then
+ begin
+ if (month = 4) and (md = 20) then
+ Hat := 'eastertop' // Easter
+ else if (month = 12) and ((md = 24) or (md = 25) or (md = 26)) then
+ Hat := 'Santa' // Christmas Eve/Christmas/Boxing Day
+ else if (month = 10) and (md = 31) then
+ Hat := 'fr_pumpkin'; // Halloween/Hedgewars' birthday
+ end;
+ if (month = 4) and (md = 1) then
+ begin
+ AprilOne:= true;
+ Hat := 'fr_tomato'; // avoid promoting violence to hedgehogs. see https://hedgewars.org/node/5818
+ end;
end;
if Hat <> 'NoHat' then
@@ -321,9 +328,9 @@
for t:= 0 to Pred(ClansCount) do
with ClansArray[t]^ do
- HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, Teams[0]^.NameTagTex^.h, Color);
+ HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, 19 * HDPIScaleFactor, Color);
-GenericHealthTexture:= makeHealthBarTexture(cTeamHealthWidth + 5, TeamsArray[0]^.NameTagTex^.h, cWhiteColor)
+GenericHealthTexture:= makeHealthBarTexture(cTeamHealthWidth + 5, 19 * HDPIScaleFactor, cWhiteColor)
end;
@@ -518,12 +525,12 @@
InitHealth;
- PauseTexture:= RenderStringTex(trmsg[sidPaused], cYellowColor, fntBig);
- AFKTexture:= RenderStringTex(trmsg[sidAFK], cYellowColor, fntBig);
+ PauseTexture:= RenderStringTex(trmsg[sidPaused], cCentralMessageColor, fntBig);
+ AFKTexture:= RenderStringTex(trmsg[sidAFK], cCentralMessageColor, fntBig);
keyConfirm:= KeyBindToName('confirm');
keyQuit:= KeyBindToName('quit');
- ConfirmTexture:= RenderStringTex(Format(trmsg[sidConfirm], [keyConfirm, keyQuit]), cYellowColor, fntBig);
- SyncTexture:= RenderStringTex(trmsg[sidSync], cYellowColor, fntBig);
+ ConfirmTexture:= RenderStringTex(FormatA(trmsg[sidConfirm], ansistring(keyConfirm), ansistring(keyQuit)), cCentralMessageColor, fntBig);
+ SyncTexture:= RenderStringTex(trmsg[sidSync], cCentralMessageColor, fntBig);
if not reload then
AddProgress;
@@ -544,7 +551,8 @@
// number of weapons in ammo menu
for i:= Low(CountTexz) to High(CountTexz) do
begin
- tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i) + 'x'), cWhiteColorChannels);
+ tmpsurf:= TTF_RenderUTF8_Blended(Fontz[CheckCJKFont(trmsg[sidAmmoCount],fnt16)].Handle, Str2PChar(Format(shortstring(trmsg[sidAmmoCount]), IntToStr(i))), cWhiteColorChannels);
+ if checkFails(tmpsurf <> nil,'Number texture creation for ammo type #' + intToStr(ord(ai)) + ' failed!',true) then exit;
tmpsurf:= doSurfaceConversion(tmpsurf);
FreeAndNilTexture(CountTexz[i]);
CountTexz[i]:= Surface2Tex(tmpsurf, false);
@@ -629,18 +637,24 @@
procedure RenderHealth(var Hedgehog: THedgehog);
var s: shortstring;
begin
-s:= IntToStr(Hedgehog.Gear^.Health);
FreeAndNilTexture(Hedgehog.HealthTagTex);
+if Hedgehog.Gear <> nil then
+ s:= IntToStr(Hedgehog.Gear^.Health)
+else if Hedgehog.GearHidden <> nil then
+ s:= IntToStr(Hedgehog.GearHidden^.Health)
+else
+ exit;
Hedgehog.HealthTagTex:= RenderStringTex(ansistring(s), Hedgehog.Team^.Clan^.Color, fnt16)
end;
function LoadImage(const filename: shortstring; imageFlags: LongInt): PSDL_Surface;
var tmpsurf: PSDL_Surface;
s: shortstring;
+ logMsg: shortstring;
rwops: PSDL_RWops;
begin
LoadImage:= nil;
- WriteToConsole(msgLoading + filename + '.png [flags: ' + inttostr(imageFlags) + '] ');
+ logMsg:= msgLoading + filename + '.png [flags: ' + inttostr(imageFlags) + ']';
s:= filename + '.png';
@@ -664,20 +678,22 @@
if rwops <> nil then
begin
// anounce that loading failed
- OutError(msgFailed, false);
+ OutError(logMsg + ' ' + msgFailed, false);
- if SDLCheck(false, 'LoadImage', (imageFlags and ifCritical) <> 0) then exit;
+ if SDLCheck(false, 'LoadImage: ' + logMsg + ' ' + msgFailed, (imageFlags and ifCritical) <> 0) then
+ exit;
// rwops was already freed by IMG_Load_RW
rwops:= nil;
- end else
- OutError(msgFailed, (imageFlags and ifCritical) <> 0);
+ end
+ else
+ OutError(logMsg + ' ' + msgFailed, (imageFlags and ifCritical) <> 0);
exit;
end;
if ((imageFlags and ifIgnoreCaps) = 0) and ((tmpsurf^.w > MaxTextureSize) or (tmpsurf^.h > MaxTextureSize)) then
begin
SDL_FreeSurface(tmpsurf);
- OutError(msgFailedSize, ((not cOnlyStats) and ((imageFlags and ifCritical) <> 0)));
+ OutError(logMsg + ' ' + msgFailedSize, ((not cOnlyStats) and ((imageFlags and ifCritical) <> 0)));
// dummy surface to replace non-critical textures that failed to load due to their size
LoadImage:= SDL_CreateRGBSurface(SDL_SWSURFACE, 2, 2, 32, RMask, GMask, BMask, AMask);
exit;
@@ -688,7 +704,8 @@
if (imageFlags and ifColorKey) <> 0 then
if checkFails(SDL_SetColorKey(tmpsurf, SDL_TRUE, 0) = 0, errmsgTransparentSet, true) then exit;
- WriteLnToConsole(msgOK + ' (' + inttostr(tmpsurf^.w) + 'x' + inttostr(tmpsurf^.h) + ')');
+ // log success
+ WriteLnToConsole(logMsg + ' ' + msgOK + ' (' + inttostr(tmpsurf^.w) + 'x' + inttostr(tmpsurf^.h) + ')');
LoadImage:= tmpsurf //Result
end;
@@ -774,6 +791,72 @@
end;
end;
+// Load default clan colors from config fiile
+procedure LoadDefaultClanColors(s: shortstring);
+var i: LongInt;
+ f: PFSFile;
+ key, value, l, temp: shortstring;
+ color, tempColor: Longword;
+ clanID, tempClanID: byte;
+begin
+ if cOnlyStats then exit;
+
+ WriteLnToConsole('Loading default clan colors from: ' + s);
+
+ l:= '';
+ if pfsExists(s) then
+ begin
+ f:= pfsOpenRead(s);
+ while (not pfsEOF(f)) and (l <> '[colors]') do
+ pfsReadLn(f, l);
+
+ while (not pfsEOF(f)) and (l <> '') do
+ begin
+ pfsReadLn(f, l);
+
+ key:= '';
+ i:= 1;
+ while (i <= length(l)) and (l[i] <> '=') do
+ begin
+ key:= key + l[i];
+ inc(i)
+ end;
+ temp:= copy(key, 1, 5);
+ if temp = 'color' then
+ begin
+ temp:= copy(key, 6, length(key) - 5);
+ tempClanID:= StrToInt(temp);
+ clanID:= tempClanID
+ end
+ else
+ continue;
+
+ if i < length(l) then
+ begin
+ value:= copy(l, i + 1, length(l) - i);
+ if (length(value) = 2) and (value[1] = '\') then
+ value:= value[1] + ''
+ else if (value[1] = '"') and (value[length(value)] = '"') then
+ value:= copy(value, 2, length(value) - 2);
+ if value[1] <> '#' then
+ continue;
+ temp:= copy(value, 2, length(value) - 1);
+ tempColor:= StrToInt('$'+temp);
+ color:= tempColor
+ end;
+
+ if clanID <= cClanColors then
+ ClanColorArray[clanID]:= color;
+
+ end;
+
+ pfsClose(f)
+ end
+ else
+ WriteLnToConsole('Settings file not found');
+end;
+
+
procedure SetupOpenGLAttributes;
begin
{$IFDEF IPHONEOS}
@@ -838,7 +921,7 @@
{$ENDIF}
end;
- if checkFails((ProgrTex <> nil) and (LoadingText <> nil), 'Error - Progress or Loading Texture is nil!', true) then exit;
+ if checkFails((ProgrTex <> nil) and (LoadingText <> nil), 'Error - Progress or Loading texture is nil!', true) then exit;
RenderClear();
if Step < numsquares then
@@ -877,7 +960,7 @@
font: THWFont;
r, r2: TSDL_Rect;
wa, ha: LongInt;
- tmpline, tmpline2, tmpdesc: ansistring;
+ tmpline, tmpline2, tmpline3, tmpdesc: ansistring;
begin
// make sure there is a caption as well as a sub caption - description is optional
if length(caption) = 0 then
@@ -917,7 +1000,9 @@
while length(tmpdesc) > 0 do
begin
tmpline:= tmpdesc;
+ EscapeCharA(tmpline, '|');
SplitByCharA(tmpline, tmpdesc, '|');
+ UnEscapeCharA(tmpline, '|');
if length(tmpline) > 0 then
begin
TTF_SizeUTF8(Fontz[font].Handle, PChar(tmpline), @i, @j);
@@ -960,21 +1045,38 @@
while length(tmpdesc) > 0 do
begin
tmpline:= tmpdesc;
+ EscapeCharA(tmpline, '|');
SplitByCharA(tmpline, tmpdesc, '|');
+ UnEscapeCharA(tmpline, '|');
r2:= r;
if length(tmpline) > 0 then
begin
- r:= WriteInRect(tmpsurf, cFontBorder + 2, r.y + r.h, $ff707070, font, PChar(tmpline));
- // render highlighted caption (if there is a ':')
+ // Render highlighted caption if there is a ':',
+ // from the beginning of the line to (and including) the ':'.
+ // With '::', the colons will be suppressed in the final text.
+ EscapeCharA(tmpline, ':');
tmpline2:= _S'';
SplitByCharA(tmpline, tmpline2, ':');
if length(tmpline2) > 0 then
begin
- tmpline:= tmpline + ':';
+ if (tmpline2[1] <> ':') then
+ begin
+ tmpline:= tmpline + ':';
+ tmpline3:= tmpline + tmpline2;
+ end
+ else
+ tmpline3:= tmpline + Copy(tmpline2, 2, Length(tmpline2)-1);
+ UnEscapeCharA(tmpline3, ':');
+ r:= WriteInRect(tmpsurf, cFontBorder + 2, r.y + r.h, $ff707070, font, PChar(tmpline3));
WriteInRect(tmpsurf, cFontBorder + 2, r2.y + r2.h, $ffc7c7c7, font, PChar(tmpline));
- end;
- end
+ end
+ else
+ begin
+ UnEscapeCharA(tmpline, ':');
+ r:= WriteInRect(tmpsurf, cFontBorder + 2, r.y + r.h, $ff707070, font, PChar(tmpline));
+ end
+ end;
end;
if length(extra) > 0 then
@@ -984,7 +1086,7 @@
r.y:= cFontBorder + 4;
r.w:= 32;
r.h:= 32;
-SDL_FillRect(tmpsurf, @r, $ff000000);
+SDL_FillRect(tmpsurf, @r, SDL_MapRGB(tmpsurf^.format, 0, 0, 0));
SDL_UpperBlit(iconsurf, iconrect, tmpsurf, @r);
RenderHelpWindow:= Surface2Tex(tmpsurf, true);
@@ -1021,25 +1123,21 @@
extra:= _S'';
extracolor:= 0;
-if (CurrentTeam <> nil) and (Ammoz[atype].SkipTurns >= CurrentTeam^.Clan^.TurnNumber) then // weapon or utility is not yet available
- begin
- if (atype = amTardis) and (SuddenDeathActive) then
- extra:= trmsg[sidNotAvailableInSD]
- else
- extra:= trmsg[sidNotYetAvailable];
- extracolor:= LongInt($ffc77070);
- end
-else if (((GameFlags and gfInfAttack) <> 0) and ((Ammoz[atype].Ammo.Propz and ammoprop_ForceTurnEnd) = 0)) or ((Ammoz[atype].Ammo.Propz and ammoprop_NoRoundEnd) <> 0) then
- // weapon or utility will not end your turn
- begin
- extra:= trmsg[sidNoEndTurn];
- extracolor:= LongInt($ff70c770);
- end
-else
- begin
- extra:= _S'';
- extracolor:= 0;
- end;
+if (trluaammoe[Ammoz[atype].NameId] = true) then
+ if (CurrentTeam <> nil) and (Ammoz[atype].SkipTurns >= CurrentTeam^.Clan^.TurnNumber) then // weapon or utility is not yet available
+ begin
+ if (atype = amTardis) and (SuddenDeathActive) then
+ extra:= trmsg[sidNotAvailableInSD]
+ else
+ extra:= trmsg[sidNotYetAvailable];
+ extracolor:= LongInt($ffc77070);
+ end
+ else if ((((GameFlags and gfInfAttack) <> 0) and ((Ammoz[atype].Ammo.Propz and ammoprop_ForceTurnEnd) = 0)) or ((Ammoz[atype].Ammo.Propz and ammoprop_NoRoundEnd) <> 0)) and (not (PlacingHogs and (atype = amTeleport))) then
+ // weapon or utility will not end your turn
+ begin
+ extra:= trmsg[sidNoEndTurn];
+ extracolor:= LongInt($ff70c770);
+ end;
if length(trluaammo[Ammoz[atype].NameId]) > 0 then
ammoname := trluaammo[Ammoz[atype].NameId]
@@ -1144,7 +1242,7 @@
end;
// these attributes must be set up before creating the sdl window
-{$IFNDEF WIN32}
+{$IFNDEF WINDOWS}
(* On a large number of testers machines, SDL default to software rendering
when opengl attributes were set. These attributes were "set" after
CreateWindow in .15, which probably did nothing.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uTeams.pas
--- a/hedgewars/uTeams.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uTeams.pas Wed Jul 31 23:14:27 2019 +0200
@@ -29,6 +29,7 @@
procedure freeModule;
function AddTeam(TeamColor: Longword): PTeam;
+function SetMissionTeam(): PTeam;
procedure SwitchHedgehog;
procedure AfterSwitchHedgehog;
procedure InitTeams;
@@ -51,44 +52,59 @@
var TeamsGameOver: boolean;
NextClan: boolean;
+ SwapClanPre, SwapClanReal: LongInt;
function CheckForWin: boolean;
var AliveClan: PClan;
s, cap: ansistring;
ts: array[0..(cMaxTeams - 1)] of ansistring;
- t, AliveCount, i, j: LongInt;
+ t, ActiveAliveCount, i, j: LongInt;
+ allWin, winCamera: boolean;
begin
CheckForWin:= false;
-AliveCount:= 0;
+ActiveAliveCount:= 0;
+// Victory if there is 1 living and non-passive clan left
for t:= 0 to Pred(ClansCount) do
- if ClansArray[t]^.ClanHealth > 0 then
+ if (ClansArray[t]^.ClanHealth > 0) and (not ClansArray[t]^.Passive) then
begin
- inc(AliveCount);
+ inc(ActiveAliveCount);
AliveClan:= ClansArray[t]
end;
-if (AliveCount > 1) or ((AliveCount = 1) and ((GameFlags and gfOneClanMode) <> 0)) then
+// Exception: gfOneClanMode, then there is no winner
+if (ActiveAliveCount > 1) or ((ActiveAliveCount = 1) and ((GameFlags and gfOneClanMode) <> 0)) then
exit;
CheckForWin:= true;
TurnTimeLeft:= 0;
ReadyTimeLeft:= 0;
-// if the game ends during a multishot, do last TurnReaction
-if (not bBetweenTurns) and isInMultiShoot then
- TurnReaction();
+// If the game ends during a multishot, or after the Sudden Death
+// water has risen, do last turn stats / reaction.
+if ((not bBetweenTurns) and isInMultiShoot) or (bDuringWaterRise) then
+ begin
+ TurnStats();
+ if (not bDuringWaterRise) then
+ TurnReaction();
+ TurnStatsReset();
+ end;
if not TeamsGameOver then
begin
- if AliveCount = 0 then
+ if ActiveAliveCount = 0 then
begin // draw
- AddCaption(GetEventString(eidRoundDraw), cWhiteColor, capgrpGameState);
+ AddCaption(GetEventString(eidRoundDraw), capcolDefault, capgrpGameState);
if SendGameResultOn then
SendStat(siGameResult, shortstring(trmsg[sidDraw]));
+ if PreviousTeam <> nil then
+ AddVoice(sndStupid, PreviousTeam^.voicepack)
+ else
+ AddVoice(sndStupid, TeamsArray[0]^.voicepack);
AddGear(0, 0, gtATFinishGame, 0, _0, _0, 3000);
end
else // win
begin
+ allWin:= false;
with AliveClan^ do
begin
if TeamsNumber = 1 then // single team wins
@@ -96,7 +112,7 @@
s:= ansistring(Teams[0]^.TeamName);
// Victory caption is randomly selected
cap:= FormatA(GetEventString(eidRoundWin), s);
- AddCaption(cap, cWhiteColor, capgrpGameState);
+ AddCaption(cap, capcolDefault, capgrpGameState);
s:= FormatA(trmsg[sidWinner], s);
end
else // clan with at least 2 teams wins
@@ -109,24 +125,47 @@
// Write victory message for caption and stats page
if (TeamsNumber = cMaxTeams) or (TeamsCount = TeamsNumber) then
- // No enemies for some reason … Everyone wins!!1!
- s:= trmsg[sidWinnerAll]
+ begin
+ // No enemies for some reason ... Everyone wins!!1!
+ s:= trmsg[sidWinnerAll];
+ allWin:= true;
+ end
else if (TeamsNumber >= 2) and (TeamsNumber < cMaxTeams) then
// List all winning teams in a list
- s:= FormatA(trmsg[TMsgStrId(Ord(sidWinner2) + (TeamsNumber - 2))], ts);
+ if (TeamsNumber = 2) then
+ s:= FormatA(trmsg[TMsgStrId(sidWinner2)], ts[0], ts[1])
+ else if (TeamsNumber = 3) then
+ s:= FormatA(trmsg[TMsgStrId(sidWinner3)], ts[0], ts[1], ts[2])
+ else if (TeamsNumber = 4) then
+ s:= FormatA(trmsg[TMsgStrId(sidWinner4)], ts[0], ts[1], ts[2], ts[3])
+ else if (TeamsNumber = 5) then
+ s:= FormatA(trmsg[TMsgStrId(sidWinner5)], ts[0], ts[1], ts[2], ts[3], ts[4])
+ else if (TeamsNumber = 6) then
+ s:= FormatA(trmsg[TMsgStrId(sidWinner6)], ts[0], ts[1], ts[2], ts[3], ts[4], ts[5])
+ else if (TeamsNumber = 7) then
+ s:= FormatA(trmsg[TMsgStrId(sidWinner7)], ts[0], ts[1], ts[2], ts[3], ts[4], ts[5], ts[6]);
// The winner caption is the same as the stats message and not randomized
cap:= s;
- AddCaption(cap, cWhiteColor, capgrpGameState);
+ AddCaption(cap, capcolDefault, capgrpGameState);
// TODO (maybe): Show victory animation/captions per-team instead of all winners at once?
end;
+ // Enable winner state for winning hogs and move camera to a winning hedgehog
+ winCamera:= false;
for j:= 0 to Pred(TeamsNumber) do
with Teams[j]^ do
for i:= 0 to cMaxHHIndex do
with Hedgehogs[i] do
if (Gear <> nil) then
+ begin
+ if (not winCamera) then
+ begin
+ FollowGear:= Gear;
+ winCamera:= true;
+ end;
Gear^.State:= gstWinner;
+ end;
if Flawless then
AddVoice(sndFlawless, Teams[0]^.voicepack)
else
@@ -135,6 +174,8 @@
if SendGameResultOn then
SendStat(siGameResult, shortstring(s));
+ if allWin and SendAchievementsStatsOn then
+ SendStat(siEverAfter, '');
AddGear(0, 0, gtATFinishGame, 0, _0, _0, 3000)
end;
SendStats;
@@ -148,7 +189,7 @@
PrevHH, PrevTeam : LongWord;
begin
TargetPoint.X:= NoPointX;
-if checkFails(CurrentTeam <> nil, 'nil Team', true) then exit;
+if checkFails(CurrentTeam <> nil, 'Team is nil!', true) then exit;
with CurrentHedgehog^ do
if (PreviousTeam <> nil) and PlacingHogs and Unplaced then
begin
@@ -160,7 +201,9 @@
if Gear <> nil then
AddCI(Gear)
end
- end;
+ end
+ else if (PreviousTeam <> nil) and PlacingKings and UnplacedKing then
+ UnplacedKing:= false;
PreviousTeam:= CurrentTeam;
@@ -192,27 +235,45 @@
c:= CurrentTeam^.Clan^.ClanIndex;
repeat
- with ClansArray[c]^ do
- if (GameFlags and gfTagTeam) <> 0 then
+ if (GameFlags and gfTagTeam) <> 0 then
+ begin
+ with ClansArray[c]^ do
begin
if (CurrTeam = TagTeamIndex) then
begin
- if (c = 0) and (not PlacingHogs) then
- inc(TotalRounds);
TagTeamIndex:= Pred(TagTeamIndex) mod TeamsNumber;
CurrTeam:= Pred(CurrTeam) mod TeamsNumber;
inc(c);
+ if c = ClansCount then
+ c:= 0;
+ if c = SwapClanReal then
+ inc(TotalRoundsReal);
NextClan:= true;
end;
- end
- else if (c = 0) and (not PlacingHogs) then
- inc(TotalRounds);
+ end;
- if (GameFlags and gfTagTeam) = 0 then
+ with ClansArray[c]^ do
+ begin
+ if (not PlacingHogs) and (not PlacingKings) and ((Succ(CurrTeam) mod TeamsNumber) = TagTeamIndex) then
+ begin
+ if c = SwapClanPre then
+ inc(TotalRoundsPre);
+ end;
+ end;
+ end
+ else
+ begin
inc(c);
-
- if c = ClansCount then
- c:= 0;
+ if c = ClansCount then
+ c:= 0;
+ if (not PlacingHogs) and (not PlacingKings) then
+ begin
+ if c = SwapClanPre then
+ inc(TotalRoundsPre);
+ if c = SwapClanReal then
+ inc(TotalRoundsReal);
+ end;
+ end;
with ClansArray[c]^ do
begin
@@ -221,15 +282,16 @@
CurrTeam:= Succ(CurrTeam) mod TeamsNumber;
CurrentTeam:= Teams[CurrTeam];
with CurrentTeam^ do
- begin
- PrevHH:= CurrHedgehog mod HedgehogsNumber; // prevent infinite loop when CurrHedgehog = 7, but HedgehogsNumber < 8 (team is destroyed before its first turn)
- repeat
- CurrHedgehog:= Succ(CurrHedgehog) mod HedgehogsNumber;
- until ((Hedgehogs[CurrHedgehog].Gear <> nil) and (Hedgehogs[CurrHedgehog].Effects[heFrozen] < 256)) or (CurrHedgehog = PrevHH)
- end
- until ((CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear <> nil) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] < 256)) or (PrevTeam = CurrTeam) or ((CurrTeam = TagTeamIndex) and ((GameFlags and gfTagTeam) <> 0))
+ if (not Passive) then
+ begin
+ PrevHH:= CurrHedgehog mod HedgehogsNumber; // prevent infinite loop when CurrHedgehog = 7, but HedgehogsNumber < 8 (team is destroyed before its first turn)
+ repeat
+ CurrHedgehog:= Succ(CurrHedgehog) mod HedgehogsNumber;
+ until ((Hedgehogs[CurrHedgehog].Gear <> nil) and (Hedgehogs[CurrHedgehog].Effects[heFrozen] < 256)) or (CurrHedgehog = PrevHH)
+ end
+ until ((not CurrentTeam^.Passive) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear <> nil) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] < 256)) or (PrevTeam = CurrTeam) or ((CurrTeam = TagTeamIndex) and ((GameFlags and gfTagTeam) <> 0))
end;
- if (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear = nil) or (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] > 255) then
+ if (CurrentTeam^.Passive) or (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear = nil) or (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] > 255) then
begin
with CurrentTeam^.Clan^ do
for t:= 0 to Pred(TeamsNumber) do
@@ -241,10 +303,10 @@
if (Gear <> nil) and (Effects[heFrozen] < 256) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] > 255) then
CurrHedgehog:= i
end;
- if (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear = nil) or (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] > 255) then
+ if (not CurrentTeam^.Passive) and ((CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear = nil) or (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] > 255)) then
inc(CurrentTeam^.Clan^.TurnNumber);
- end
-until (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear <> nil) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] < 256);
+ end;
+until (not CurrentTeam^.Passive) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear <> nil) and (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Effects[heFrozen] < 256);
SwitchCurrentHedgehog(@(CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog]));
{$IFDEF USE_TOUCH_INTERFACE}
@@ -274,21 +336,49 @@
g: PGear;
s: ansistring;
begin
-if PlacingHogs then
+if PlacingHogs or PlacingKings then
begin
PlacingHogs:= false;
+ PlacingKings:= false;
for t:= 0 to Pred(TeamsCount) do
for i:= 0 to cMaxHHIndex do
- if (TeamsArray[t]^.Hedgehogs[i].Gear <> nil) and (TeamsArray[t]^.Hedgehogs[i].Unplaced) then
- PlacingHogs:= true;
+ if ((GameFlags and gfPlaceHog) <> 0) and (TeamsArray[t]^.Hedgehogs[i].Gear <> nil) and (TeamsArray[t]^.Hedgehogs[i].Unplaced) then
+ PlacingHogs:= true
+ else if ((GameFlags and gfPlaceHog) = 0) and ((GameFlags and gfKing) <> 0) and (TeamsArray[t]^.Hedgehogs[i].Gear <> nil) and (TeamsArray[t]^.Hedgehogs[i].UnplacedKing) then
+ PlacingKings:= true;
- if not PlacingHogs then // Reset various things I mucked with
+ if (not PlacingHogs) and (not PlacingKings) then // Reset various things I mucked with
begin
for i:= 0 to ClansCount do
if ClansArray[i] <> nil then
+ begin
ClansArray[i]^.TurnNumber:= 0;
+ end;
ResetWeapons
- end
+ end;
+
+ end;
+
+if (not PlacingHogs) and (not PlacingKings) then
+ begin
+ if (TotalRoundsReal = -1) then
+ TotalRoundsReal:= 0;
+ if (TotalRoundsPre = -1) and (ClansCount = 1) then
+ TotalRoundsPre:= 0;
+ end;
+
+// Determine clan ID to check to determine whether to increase TotalRoundsPre/TotalRoundsReal
+if (not PlacingHogs) and (not PlacingKings) then
+ begin
+ if SwapClanPre = -1 then
+ begin
+ if (GameFlags and gfRandomOrder) <> 0 then
+ SwapClanPre:= 0
+ else
+ SwapClanPre:= ClansCount - 1;
+ end;
+ if SwapClanReal = -1 then
+ SwapClanReal:= CurrentTeam^.Clan^.ClanIndex;
end;
inc(CurrentTeam^.Clan^.TurnNumber);
@@ -339,6 +429,11 @@
TurnTimeLeft:= 15000
else TurnTimeLeft:= 0
end
+else if PlacingKings then
+ if CurrentHedgehog^.King and CurrentHedgehog^.UnplacedKing then
+ TurnTimeLeft:= cHedgehogTurnTime
+ else
+ TurnTimeLeft:= 0
else
begin
if ((GameFlags and gfTagTeam) <> 0) and (not NextClan) then
@@ -354,7 +449,11 @@
NextClan:= false;
end;
- if (GameFlags and gfSwitchHog) <> 0 then
+ // Enable switching mode when gfSwitchHog is active
+ if ((GameFlags and gfSwitchHog) <> 0) and (not CurrentTeam^.hasGone) and
+ // Exception: During the special "Place your King" round in King Mode;
+ // you're not supposed to switch away from your king in this round.
+ (not (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0) and (TotalRoundsReal <= 0))) then
begin
g:= AddGear(hwRound(CurrentHedgehog^.Gear^.X), hwRound(CurrentHedgehog^.Gear^.Y), gtSwitcher, 0, _0, _0, 0);
CurAmmoGear:= g;
@@ -365,36 +464,41 @@
end;
IsGetAwayTime:= false;
+// turn start taunt: sndYesSir for own team, sndHmm for enemy or computer team
if (TurnTimeLeft > 0) and (CurrentHedgehog^.BotLevel = 0) then
begin
- if CurrentTeam^.ExtDriven then
- begin
- if GetRandom(2) = 0 then
- AddVoice(sndIllGetYou, CurrentTeam^.voicepack)
- else AddVoice(sndJustYouWait, CurrentTeam^.voicepack)
- end
- else
- begin
- GetRandom(2); // needed to avoid extdriven desync
- AddVoice(sndYesSir, CurrentTeam^.voicepack);
- end;
+ if (not CinematicScript) then
+ if CurrentTeam^.ExtDriven then
+ AddVoice(sndHmm, CurrentTeam^.voicepack)
+ else
+ AddVoice(sndYesSir, CurrentTeam^.voicepack);
if cHedgehogTurnTime < 1000000 then
ReadyTimeLeft:= cReadyDelay;
s:= ansistring(CurrentTeam^.TeamName);
- AddCaption(FormatA(trmsg[sidReady], s), cWhiteColor, capgrpGameState)
+ AddCaption(FormatA(trmsg[sidReady], s), capcolDefault, capgrpGameState)
end
else
begin
- if TurnTimeLeft > 0 then
- begin
- if GetRandom(2) = 0 then
- AddVoice(sndIllGetYou, CurrentTeam^.voicepack)
- else AddVoice(sndJustYouWait, CurrentTeam^.voicepack)
- end;
- ReadyTimeLeft:= 0
+ if (TurnTimeLeft > 0) and (not CinematicScript) then
+ AddVoice(sndHmm, CurrentTeam^.voicepack);
+ ReadyTimeLeft:= 0;
end;
end;
+function SetMissionTeam(): PTeam;
+var team: PTeam;
+begin
+New(team);
+if checkFails(team <> nil, 'AddTeam: team = nil', true) then exit(nil);
+FillChar(team^, sizeof(TTeam), 0);
+team^.HedgehogsNumber:= 0;
+team^.Binds:= DefaultBinds;
+
+CurrentTeam:= team;
+MissionTeam:= team;
+SetMissionTeam:= team;
+end;
+
function AddTeam(TeamColor: Longword): PTeam;
var team: PTeam;
c: LongInt;
@@ -412,6 +516,7 @@
inc(VisibleTeamsCount);
team^.Binds:= DefaultBinds;
+team^.Passive:= false;
c:= Pred(ClansCount);
while (c >= 0) and (ClansArray[c]^.Color <> TeamColor) do dec(c);
@@ -426,7 +531,10 @@
ClanIndex:= Pred(ClansCount);
Color:= TeamColor;
TagTeamIndex:= 0;
- Flawless:= true
+ Flawless:= true;
+ LocalOrAlly:= false;
+ DeathLogged:= false;
+ StatsHandled:= false;
end
end
else
@@ -437,6 +545,7 @@
with team^.Clan^ do
begin
Teams[TeamsNumber]:= team;
+ Passive:= false;
inc(TeamsNumber)
end;
@@ -464,7 +573,8 @@
begin
if (not ExtDriven) and (Hedgehogs[0].BotLevel = 0) then
begin
- LocalClan:= Clan^.ClanIndex;
+ if (MissionTeam = nil) or (MissionTeam^.TeamName = TeamName) then
+ Clan^.LocalOrAlly:= true;
LocalTeam:= t;
LocalAmmo:= Hedgehogs[0].AmmoStore
end;
@@ -477,6 +587,7 @@
// Some initial King buffs
if (GameFlags and gfKing) <> 0 then
begin
+ hasKing:= true;
Hedgehogs[0].King:= true;
Hedgehogs[0].Hat:= 'crown';
Hedgehogs[0].Effects[hePoisoned] := 0;
@@ -491,6 +602,9 @@
end
else
Hedgehogs[0].Gear^.Health:= h;
+ // Prevent overflow
+ if (Hedgehogs[0].Gear^.Health < 0) or (Hedgehogs[0].Gear^.Health > cMaxHogHealth) then
+ Hedgehogs[0].Gear^.Health:= cMaxHogHealth;
Hedgehogs[0].InitialHealth:= Hedgehogs[0].Gear^.Health
end;
end;
@@ -592,6 +706,31 @@
end
end;
+procedure chAddMissionHH(var id: shortstring);
+var s: shortstring;
+ Health: LongInt;
+begin
+s:= '';
+if (not isDeveloperMode) then
+ exit;
+if checkFails((CurrentTeam <> nil), 'Can''t add hedgehogs yet, add a team first!', true) then exit;
+with CurrentTeam^ do
+ begin
+ if checkFails(HedgehogsNumber<=cMaxHHIndex, 'Can''t add hedgehog to "' + TeamName + '"! (already ' + intToStr(HedgehogsNumber) + ' hogs)', true) then exit;
+ SplitBySpace(id, s);
+ CurrentHedgehog:= @Hedgehogs[HedgehogsNumber];
+ CurrentHedgehog^.BotLevel:= StrToInt(id);
+ CurrentHedgehog^.Team:= CurrentTeam;
+ SplitBySpace(s, id);
+ Health:= StrToInt(s);
+ if checkFails((Health > 0) and (Health <= cMaxHogHealth), 'Invalid hedgehog health (must be between 1 and '+IntToStr(cMaxHogHealth)+')', true) then exit;
+ CurrentHedgehog^.Name:= id;
+ CurrentHedgehog^.InitialHealth:= Health;
+ CurrentHedgehog^.RevengeHog:= nil;
+ inc(HedgehogsNumber)
+ end
+end;
+
procedure chAddHH(var id: shortstring);
var s: shortstring;
Gear: PGear;
@@ -610,7 +749,7 @@
Gear:= AddGear(0, 0, gtHedgehog, 0, _0, _0, 0);
SplitBySpace(s, id);
Gear^.Health:= StrToInt(s);
- if checkFails(Gear^.Health > 0, 'Invalid hedgehog health', true) then exit;
+ if checkFails((Gear^.Health > 0) and (Gear^.Health <= cMaxHogHealth), 'Invalid hedgehog health (must be between 1 and '+IntToStr(cMaxHogHealth)+')', true) then exit;
if (GameFlags and gfSharedAmmo) <> 0 then
CurrentHedgehog^.AmmoStore:= Clan^.ClanIndex
else if (GameFlags and gfPerHogAmmo) <> 0 then
@@ -622,6 +761,8 @@
CurrentHedgehog^.Gear:= Gear;
CurrentHedgehog^.Name:= id;
CurrentHedgehog^.InitialHealth:= Gear^.Health;
+ CurrentHedgehog^.RevengeHog:= nil;
+ CurrentHedgehog^.FlownOffMap:= false;
CurrHedgehog:= HedgehogsNumber;
inc(HedgehogsNumber)
end
@@ -641,6 +782,63 @@
loadBinds('bind', s);
end;
+// Make sure the team name of chTeam is unique.
+// If it isn't, the name is changed to be unique.
+procedure makeTeamNameUnique(chTeam: PTeam);
+var tail: shortstring;
+ t, numLen, numTail: LongInt;
+// valOK: Word; -- see pas2c-related FIXME below
+ nameDupeCheck: boolean;
+ chChar: char;
+begin
+ nameDupeCheck:= false;
+ while(nameDupeCheck = false) do
+ begin
+ nameDupeCheck:= true;
+ for t:=0 to TeamsCount - 1 do
+ begin
+ // Name collision?
+ if (chTeam <> teamsArray[t]) and (TeamsArray[t]^.TeamName = chTeam^.TeamName) then
+ begin
+ // Change the name by appending a sequence number, starting from 2
+ numLen:= 0;
+ chChar:= chTeam^.TeamName[Length(chTeam^.TeamName) - numLen];
+ // Parse number at end of team name (if any)
+ while (chChar >= '0') and (chChar <= '9') and (numLen < Length(chTeam^.TeamName)) do
+ begin
+ inc(numLen);
+ chChar:= chTeam^.TeamName[Length(chTeam^.TeamName) - numLen];
+ end;
+
+ if numLen > 0 then
+ // Number found: Increment it by 1
+ begin
+ tail:= Copy(chTeam^.TeamName, Length(chTeam^.TeamName) - numLen + 1, numLen);
+(* FIXME - pas2c missing 3rd param for val
+ valOK:= 1;
+ Val(tail, numTail, valOK);
+ Inc(numTail);
+ if valOK = 0 then
+ tail:= IntToStr(numTail)
+ else
+ // This should not happen
+ tail:= shortstring('X');
+*)
+ Val(tail, numTail);
+ Inc(numTail);
+ tail:= IntToStr(numTail);
+ chTeam^.TeamName:= Copy(chTeam^.TeamName, 0, Length(chTeam^.TeamName) - numLen) + tail;
+ end
+ else
+ // No number at team end: Just append a '2'
+ chTeam^.TeamName:= chTeam^.TeamName + ' 2';
+ nameDupeCheck:= false;
+ break;
+ end;
+ end;
+ end;
+end;
+
procedure chAddTeam(var s: shortstring);
var Color: Longword;
ts, cs: shortstring;
@@ -656,17 +854,41 @@
// color is always little endian so the mask must be constant also in big endian archs
Color:= Color or $FF000000;
AddTeam(Color);
-
+
if CurrentTeam <> nil then
begin
CurrentTeam^.TeamName:= ts;
+ makeTeamNameUnique(CurrentTeam);
+
CurrentTeam^.PlayerHash:= s;
loadTeamBinds(ts);
if GameType in [gmtDemo, gmtSave, gmtRecord] then
CurrentTeam^.ExtDriven:= true;
- CurrentTeam^.voicepack:= AskForVoicepack('Default')
+ CurrentTeam^.voicepack:= AskForVoicepack('Default_qau')
+ end
+ end
+end;
+
+procedure chSetMissionTeam(var s: shortstring);
+var ts, cs: shortstring;
+begin
+cs:= '';
+ts:= '';
+if isDeveloperMode then
+ begin
+ SplitBySpace(s, cs);
+ SplitBySpace(cs, ts);
+
+ SetMissionTeam();
+
+ if CurrentTeam <> nil then
+ begin
+ CurrentTeam^.TeamName:= ts;
+ CurrentTeam^.PlayerHash:= s;
+ loadTeamBinds(ts);
+ CurrentTeam^.voicepack:= AskForVoicepack('Default_qau')
end
end
end;
@@ -716,7 +938,8 @@
begin
if (not hasGone) and isGoneFlagPendingToBeSet then
begin
- AddChatString(#7 + '* '+ FormatA(trmsg[sidTeamGone], TeamName));
+ if (not TeamsGameOver) then
+ AddChatString(#7 + Format('* '+shortstring(trmsg[sidTeamGone]), TeamName));
if not CurrentTeam^.ExtDriven then SendIPC(_S'f' + s);
hasGone:= true;
skippedTurns:= 0;
@@ -755,7 +978,7 @@
with TeamsArray[t]^ do
if hasGone then
begin
- AddChatString(#8 + '* '+ FormatA(trmsg[sidTeamBack], TeamName));
+ AddChatString(#8 + Format('* '+shortstring(trmsg[sidTeamBack]), TeamName));
if not CurrentTeam^.ExtDriven then SendIPC(_S'g' + s);
hasGone:= false;
@@ -853,7 +1076,9 @@
procedure initModule;
begin
RegisterVariable('addhh', @chAddHH, false);
+RegisterVariable('addmisshh', @chAddMissionHH, false);
RegisterVariable('addteam', @chAddTeam, false);
+RegisterVariable('setmissteam', @chSetMissionTeam, false);
RegisterVariable('hhcoords', @chSetHHCoords, false);
RegisterVariable('bind', @chBind, true );
RegisterVariable('teamgone', @chTeamGone, true );
@@ -869,11 +1094,12 @@
CurrentHedgehog:= nil;
TeamsCount:= 0;
ClansCount:= 0;
-LocalClan:= -1;
LocalTeam:= -1;
LocalAmmo:= -1;
-GameOver:= false;
+TeamsGameOver:= false;
NextClan:= true;
+SwapClanPre:= -1;
+SwapClanReal:= -1;
MaxTeamHealth:= 0;
end;
@@ -919,6 +1145,8 @@
end;
TeamsCount:= 0;
ClansCount:= 0;
+SwapClanPre:= -1;
+SwapClanReal:= -1;
end;
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uTouch.pas
--- a/hedgewars/uTouch.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uTouch.pas Wed Jul 31 23:14:27 2019 +0200
@@ -22,7 +22,7 @@
interface
-uses SysUtils, uUtils, uConsole, uVariables, SDLh, uFloat, uConsts, uCommands, GLUnit, uTypes, uCaptions, uAmmos, uWorld;
+uses SysUtils, uUtils, uConsole, uVariables, SDLh, uFloat, uConsts, uCommands, GLUnit, uTypes, uCaptions, uWorld, uGearsHedgehog;
procedure initModule;
@@ -74,9 +74,6 @@
moveCursor : boolean;
invertCursor : boolean;
- xTouchClick,yTouchClick : LongInt;
- timeSinceClick : Longword;
-
//Pinch to zoom
pinchSize : LongInt;
baseZoomValue: GLFloat;
@@ -92,7 +89,7 @@
procedure onTouchDown(x, y: Single; pointerId: TSDL_FingerId);
var
finger: PTouch_Data;
- xr, yr: LongWord;
+ xr, yr, tmp: LongWord;
begin
xr:= round(x * cScreenWidth);
yr:= round(y * cScreenHeight);
@@ -149,7 +146,7 @@
if isOnWidget(pauseButton, finger^) then
begin
- isPaused:= not isPaused;
+ ParseTeamCommand('pause');
moveCursor:= false;
finger^.pressedWidget:= @pauseButton;
exit;
@@ -162,10 +159,35 @@
if(CurrentHedgehog <> nil) then
begin
if Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_Timerable <> 0 then
- ParseTeamCommand('/timer ' + inttostr((GetCurAmmoEntry(CurrentHedgeHog^)^.Timer div 1000) mod 5 + 1));
+ begin
+ tmp:= HHGetTimerMsg(CurrentHedgehog^.Gear);
+ if tmp <> MSGPARAM_INVALID then
+ ParseTeamCommand('/timer ' + inttostr(tmp mod 5 + 1));
+ end;
end;
exit;
end;
+
+if isOnWidget(utilityWidget2, finger^) then
+ begin
+ finger^.pressedWidget:= @utilityWidget2;
+ moveCursor:= false;
+ if(CurrentHedgehog <> nil) then
+ begin
+ if Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_SetBounce <> 0 then
+ begin
+ tmp := HHGetBouncinessMsg(CurrentHedgehog^.Gear);
+ if tmp <> MSGPARAM_INVALID then
+ begin
+ ParseTeamCommand('+precise');
+ ParseTeamCommand('timer ' + inttostr(tmp mod 5 + 1));
+ bounceButtonPressed:= true;
+ end;
+ end;
+ end;
+ exit;
+ end;
+
dec(buttonsDown);//no buttonsDown, undo the inc() above
if buttonsDown = 0 then
begin
@@ -296,7 +318,7 @@
end;
if targetting then
- AddCaption(trmsg[sidPressTarget], cWhiteColor, capgrpAmmoInfo);
+ AddCaption(trmsg[sidPressTarget], capcolDefault, capgrpAmmoInfo);
deleteFinger(pointerId);
end;
@@ -317,16 +339,6 @@
procedure onTouchClick(finger: TTouch_Data);
begin
-//if (RealTicks - timeSinceClick < 300) and (sqrt(sqr(finger.X-xTouchClick) + sqr(finger.Y-yTouchClick)) < 30) then
-// begin
-// onTouchDoubleClick(finger);
-// timeSinceClick:= 0;//we make an assumption there won't be an 'click' in the first 300 ticks(milliseconds)
-// exit;
-// end;
-
-xTouchClick:= finger.x;
-yTouchClick:= finger.y;
-timeSinceClick:= RealTicks;
if bShowAmmoMenu then
begin
@@ -509,6 +521,12 @@
aimingDown:= false;
end;
end;
+
+if bounceButtonPressed then
+ begin
+ ParseTeamCommand('-precise');
+ bounceButtonPressed:= false;
+ end;
end;
function findFinger(id: TSDL_FingerId): PTouch_Data;
@@ -637,10 +655,10 @@
procedure initModule;
var
index: Longword;
- //uRenderCoordScaleX, uRenderCoordScaleY: Longword;
begin
buttonsDown:= 0;
pointerCount:= 0;
+ bounceButtonPressed:= false;
setLength(fingers, 4);
for index := 0 to (Length(fingers)-1) do
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uTypes.pas
--- a/hedgewars/uTypes.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uTypes.pas Wed Jul 31 23:14:27 2019 +0200
@@ -39,13 +39,13 @@
TGameState = (gsLandGen, gsStart, gsGame, gsConfirm, gsExit, gsSuspend);
// Game types that help determining what the engine is actually supposed to do
- TGameType = (gmtLocal, gmtDemo, gmtNet, gmtSave, gmtLandPreview, gmtSyntax, gmtRecord);
+ TGameType = (gmtLocal, gmtDemo, gmtNet, gmtSave, gmtLandPreview, gmtBadSyntax, gmtRecord, gmtSyntaxHelp);
// Different files are stored in different folders, this enumeration is used to tell which folder to use
TPathType = (ptNone, ptData, ptGraphics, ptThemes, ptCurrTheme, ptConfig, ptTeams, ptMaps,
ptMapCurrent, ptDemos, ptSounds, ptGraves, ptFonts, ptForts, ptLocale,
ptAmmoMenu, ptHedgehog, ptVoices, ptHats, ptFlags, ptMissionMaps,
- ptSuddenDeath, ptButtons, ptShaders);
+ ptSuddenDeath, ptButtons, ptShaders, ptDefaultVoice, ptMisc);
// Available sprites for displaying stuff
TSprite = (sprWater, sprCloud, sprBomb, sprBigDigit, sprBigDigitGray, sprBigDigitGreen,
@@ -61,7 +61,7 @@
{$IFDEF USE_TOUCH_INTERFACE}
sprFireButton, sprArrowUp, sprArrowDown, sprArrowLeft, sprArrowRight,
sprJumpWidget, sprAMWidget, sprPauseButton, sprTimerButton, sprTargetButton,
- sprSwitchButton,
+ sprSwitchButton, sprBounceButton,
{$ENDIF}
sprFlake, sprHandRope, sprHandBazooka, sprHandShotgun,
sprHandDEagle, sprHandAirAttack, sprHandBaseball, sprPHammer,
@@ -90,8 +90,11 @@
sprSDFlake, sprSDWater, sprSDCloud, sprSDSplash, sprSDDroplet, sprTardis,
sprSlider, sprBotlevels, sprHandKnife, sprKnife, sprStar, sprIceTexture, sprIceGun,
sprFrozenHog, sprAmRubber, sprBoing, sprCustom1, sprCustom2, sprCustom3, sprCustom4,
- sprCustom5, sprCustom6, sprCustom7, sprCustom8, sprAirMine, sprHandAirMine,
- sprFlakeL, sprSDFlakeL, sprCloudL, sprSDCloudL, sprDuck, sprHandDuck, sprMinigun
+ sprCustom5, sprCustom6, sprCustom7, sprCustom8, sprFrozenAirMine, sprAirMine, sprHandAirMine,
+ sprFlakeL, sprSDFlakeL, sprCloudL, sprSDCloudL, sprCreeper, sprHandCreeper, sprMinigun,
+ sprSliderInverted, sprFingerBack, sprFingerBackInv, sprTargetPBack, sprTargetPBackInv,
+ sprHealthHud, sprHealthPoisonHud, sprVampHud, sprKarmaHud, sprMedicHud, sprMedicPoisonHud,
+ sprHaloHud, sprInvulnHUD, sprAmPiano, sprHandLandGun, sprFirePunch, sprThroughWrap
);
// Gears that interact with other Gears and/or Land
@@ -108,8 +111,8 @@
gtSniperRifleShot, gtJetpack, gtMolotov, gtBirdy, // 45
gtEgg, gtPortal, gtPiano, gtGasBomb, gtSineGunShot, gtFlamethrower, // 51
gtSMine, gtPoisonCloud, gtHammer, gtHammerHit, gtResurrector, // 56
- gtNapalmBomb, gtSnowball, gtFlake, {gtStructure,} gtLandGun, gtTardis, // 61
- gtIceGun, gtAddAmmo, gtGenericFaller, gtKnife, gtDuck, gtMinigun, gtMinigunBullet); // 68
+ gtNapalmBomb, gtSnowball, gtFlake, gtLandGun, gtTardis, // 61
+ gtIceGun, gtAddAmmo, gtGenericFaller, gtKnife, gtCreeper, gtMinigun, gtMinigunBullet); // 68
// Gears that are _only_ of visual nature (e.g. background stuff, visual effects, speechbubbles, etc.)
TVisualGearType = (vgtFlake, vgtCloud, vgtExplPart, vgtExplPart2, vgtFire,
@@ -121,7 +124,7 @@
vgtSmoothWindBar, vgtStraightShot, vgtNoPlaceWarn);
// Damage can be caused by different sources
- TDamageSource = (dsUnknown, dsFall, dsBullet, dsExplosion, dsShove, dsPoison);
+ TDamageSource = (dsUnknown, dsFall, dsBullet, dsExplosion, dsShove, dsPoison, dsHammer);
// Available sounds
TSound = (sndNone,
@@ -150,8 +153,12 @@
sndIceBeam, sndHogFreeze, sndAirMineImpact, sndKnifeImpact, sndExtraTime, sndLaserSight,
sndInvulnerable, sndJetpackLaunch, sndJetpackBoost, sndPortalShot, sndPortalSwitch,
sndPortalOpen, sndBlowTorch, sndCountdown1, sndCountdown2, sndCountdown3, sndCountdown4,
- sndDuckDrop, sndDuckWater, sndDuckDie, sndCustom1, sndCustom2, sndCustom3, sndCustom4,
- sndCustom5, sndCustom6, sndCustom7, sndCustom8, sndMinigun);
+ sndCreeperDrop, sndCreeperWater, sndCreeperDie, sndCustom1, sndCustom2, sndCustom3, sndCustom4,
+ sndCustom5, sndCustom6, sndCustom7, sndCustom8, sndMinigun, sndFlamethrower, sndIceBeamIdle,
+ sndLandGun, sndCaseImpact, sndExtraDamage, sndFirePunchHit, sndGrenade, sndThisOneIsMine,
+ sndWhatThe, sndSoLong, sndOhDear, sndGonnaGetYou, sndDrat, sndBugger, sndAmazing,
+ sndBrilliant, sndExcellent, sndFire, sndWatchThis, sndRunAway, sndRevenge, sndCutItOut,
+ sndLeaveMeAlone, sndOuch, sndHmm, sndKiss, sndFlyAway, sndPlaneWater);
// Available ammo types to be used by hedgehogs
TAmmoType = (amNothing, amGrenade, amClusterBomb, amBazooka, amBee, amShotgun, amPickHammer, // 6
@@ -162,8 +169,10 @@
amRCPlane, amLowGravity, amExtraDamage, amInvulnerable, amExtraTime, // 35
amLaserSight, amVampiric, amSniperRifle, amJetpack, amMolotov, amBirdy, amPortalGun, // 42
amPiano, amGasBomb, amSineGun, amFlamethrower, amSMine, amHammer, // 48
- amResurrector, amDrillStrike, amSnowball, amTardis, {amStructure,} amLandGun, // 53
- amIceGun, amKnife, amRubber, amAirMine, amDuck, amMinigun); // 59
+ amResurrector, amDrillStrike, amSnowball, amTardis, amLandGun, // 53
+ amIceGun, amKnife, amRubber, amAirMine, amCreeper, amMinigun); // 59
+ // NOTE: If we ever reach 126 ammo types, make sure to skip ammo type number 126 because it's
+ // reserved as synonym for amNothing. See also chSetWeapon.
// Different kind of crates that e.g. hedgehogs can pick up
TCrateType = (HealthCrate, AmmoCrate, UtilityCrate);
@@ -176,7 +185,7 @@
TStatInfoType = (siGameResult, siMaxStepDamage, siMaxStepKills, siKilledHHs,
siClanHealth, siTeamStats, siPlayerKills, siMaxTeamDamage,
siMaxTeamKills, siMaxTurnSkips, siCustomAchievement, siGraphTitle,
- siPointType);
+ siPointType, siTeamRank, siEverAfter);
// Various 'emote' animations a hedgehog can do
TWave = (waveRollup, waveSad, waveWave, waveHurrah, waveLemonade, waveShrug, waveJuggle);
@@ -250,6 +259,7 @@
doStep: TGearStepProcedure; // Code the gear is running
AmmoType : TAmmoType; // Ammo type associated with this kind of gear
RenderTimer: Boolean; // Will visually display Timer if true
+ RenderHealth: Boolean; // Will visually display Health if true
Target : TPoint; // Gear target. Will render in uGearsRender unless a special case is added
AIHints: LongWord; // hints for ai.
LastDamage: PHedgehog; // Used to track damage source for stats
@@ -268,6 +278,7 @@
Radius: LongInt; // Radius. If not using uCollisions, is usually used to indicate area of effect
CollisionMask: Word; // Masking off Land impact FF7F for example ignores current hog and crates
AdvBounce: Longword; // Triggers 45 bounces. Is a counter to avoid edge cases
+ Sticky: Boolean; // True if gear is *normally* able to stick to landscape on impact
Elasticity: hwFloat;
Friction : hwFloat;
Density : hwFloat; // Density is kind of a mix of size and density. Impacts distance thrown, wind.
@@ -326,17 +337,20 @@
end;
TStatistics = record
- DamageRecv,
- DamageGiven: Longword;
- StepDamageRecv,
- StepDamageGiven,
- StepKills: Longword;
- StepPoisoned,
- StepDied,
- Sacrificed: boolean;
- MaxStepDamageRecv,
- MaxStepDamageGiven,
- MaxStepKills: Longword;
+ DamageRecv, // total damage received
+ DamageGiven: Longword; // total damage dealt
+ StepDamageRecvInRow, // number of enemy turns in row this hog received any damage
+ StepDamageRecv, // damage received in this turn
+ StepDamageGiven, // damage dealt in this turn
+ StepKills: Longword; // kills in this turn
+ StepPoisoned, // whether hog got poisoned this turn
+ StepDied, // whether hog died this turn
+ Sacrificed, // whether hog was sacrificed in suicide attack (kamikaze, piano)
+ GotRevenge: boolean; // whether hog got revenge in this turn
+ StepRevenge: boolean; // whether hog's revenge mode was activated in this turn
+ MaxStepDamageRecv, // most damage received in one turn
+ MaxStepDamageGiven, // most damage dealt in one turn
+ MaxStepKills: Longword; // most kills in one turn
FinishedTurns: Longword;
end;
@@ -349,6 +363,16 @@
TeamDamage : Longword;
end;
+ PClanDeathLogEntry = ^TClanDeathLogEntry;
+
+ TClanDeathLogEntry = record
+ Turn : Longword; // turn in which the clans were killed
+ KilledClans : array[0..Pred(cMaxTeams)] of PClan; // array of clans that have died
+ KilledClansCount: Longword; // number of clans that died
+ NextEntry : PClanDeathLogEntry; // linked list
+ end;
+
+
TBinds = record
indices: array[0..cKbdMaxIndex] of byte;
// zeroth element is reserved, indices[i] == 0 means no binding
@@ -367,6 +391,7 @@
TVoice = record
snd: TSound;
voicepack: PVoicePack;
+ isFallback: boolean;
end;
THHAmmo = array[0..cMaxSlotIndex, 0..cMaxSlotAmmoIndex] of TAmmo;
@@ -395,9 +420,12 @@
InitialHealth: LongInt; // used for gfResetHealth
King: boolean; // Flag for a bunch of hedgehog attributes
Unplaced: boolean; // Flag for hog placing mode
+ UnplacedKing: boolean; // Flag for king placing phase in King Mode
Timer: Longword;
HealthBarHealth: LongInt;
Effects: array[THogEffect] of LongInt;
+ RevengeHog: PHedgehog; // For which hog this hog wants revenge most. For sndRevenge taunt
+ FlownOffMap: boolean; // When hedgehog has flown far away off the map left or right
end;
TTeam = record
@@ -425,6 +453,9 @@
voicepack: PVoicepack;
PlayerHash: shortstring; // md5 hash of player name. For temporary enabling of hats as thank you. Hashed for privacy of players
stats: TTeamStats;
+ Passive: boolean; // if true, team will not participate in game. It is treated as if all its hedgehogs are frozen.
+ // updating this value requires updating Passive in TClan as well!
+ hasKing: boolean; // true if team has a living king
hasGone: boolean;
skippedTurns: Longword;
isGoneFlagPendingToBeSet, isGoneFlagPendingToBeUnset: boolean;
@@ -442,7 +473,11 @@
ClanHealth: LongInt;
ClanIndex: LongInt;
TurnNumber: LongWord;
+ DeathLogged: boolean; // true if clan is dead and its latest death has been logged in the clan death log
+ StatsHandled : boolean; // true if clan's rank has been handled for stats screen
Flawless: boolean;
+ Passive: boolean; // informational. Must be set to true if all of the teams are passive
+ LocalOrAlly: boolean; // true if at least 1 team in the clan is a local team. A local team is a non-extdriven team controlled by a human
end;
cdeclPtr = procedure; cdecl;
@@ -468,8 +503,8 @@
sidMolotov, sidBirdy, sidPortalGun, sidPiano, sidGasBomb,
sidSineGun, sidFlamethrower,sidSMine, sidHammer, sidResurrector,
sidDrillStrike, sidSnowball, sidNothing, sidTardis,
- {sidStructure,} sidLandGun, sidIceGun, sidKnife, sidRubber, sidAirMine,
- sidDuck, sidMinigun);
+ sidLandGun, sidIceGun, sidKnife, sidRubber, sidAirMine,
+ sidCreeper, sidMinigun);
TMsgStrId = (sidLoading, sidDraw, sidWinner, sidVolume, sidPaused,
sidConfirm, sidSuddenDeath, sidRemaining, sidFuel, sidSync,
@@ -479,7 +514,19 @@
sidNotAvailableInSD, sidHealthGain, sidEmptyCrate, sidUnknownKey,
sidWinner2, sidWinner3, sidWinner4, sidWinner5, sidWinner6,
sidWinner7, sidWinnerAll, sidTeamGone, sidTeamBack, sidAutoSkip,
- sidFPS);
+ sidFPS, sidLuaParsingOff, sidLuaParsingOn, sidLuaParsingDenied,
+ sidAmmoCount, sidChat, sidChatTeam, sidChatHog, sidUnknownGearValue);
+
+ TCmdHelpStrId = (
+ sidCmdHeaderBasic, sidCmdTogglechat, sidCmdTeam, sidCmdMe,
+ sidCmdPause, sidCmdPauseNet, sidCmdFullscreen, sidCmdQuit,
+ sidCmdHelp, sidCmdHelpTaunts, sidCmdHistory, sidLua,
+
+ sidCmdHeaderTaunts, sidCmdSpeech, sidCmdThink, sidCmdYell,
+ sidCmdSpeechNumberHint, sidCmdHsa, sidCmdHta, sidCmdHya,
+ sidCmdHurrah, sidCmdIlovelotsoflemonade, sidCmdJuggle,
+ sidCmdRollup, sidCmdShrug, sidCmdWave, sidCmdUnknown,
+ sidCmdHelpRoom, sidCmdHelpRoomFail);
// Events that are important for the course of the game or at least interesting for other reasons
TEventId = (eidDied, eidDrowned, eidRoundStart, eidRoundWin, eidRoundDraw,
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uUtils.pas
--- a/hedgewars/uUtils.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uUtils.pas Wed Jul 31 23:14:27 2019 +0200
@@ -30,6 +30,9 @@
procedure SplitByChar(var a, b: shortstring; c: char);
procedure SplitByCharA(var a, b: ansistring; c: char);
+procedure EscapeCharA(var a: ansistring; e: char);
+procedure UnEscapeCharA(var a: ansistring; e: char);
+
function ExtractFileDir(s: shortstring) : shortstring;
function ExtractFileName(s: shortstring) : shortstring;
@@ -50,6 +53,7 @@
function IntToStr(n: LongInt): shortstring;
function StrToInt(s: shortstring): LongInt;
+//function StrToInt(s: shortstring; var success: boolean): LongInt;
function FloatToStr(n: hwFloat): shortstring;
function DxDy2Angle(const _dY, _dX: hwFloat): real; inline;
@@ -79,6 +83,9 @@
function CalcWorldWrap(X, radius: LongInt): LongInt;
+procedure updateVolumeDelta(precise: boolean);
+procedure updateCursorMovementDelta(precise: boolean; dir: LongInt; var cursorVar: LongInt);
+
function read1stLn(filePath: shortstring): shortstring;
function readValueFromINI(key, filePath: shortstring): shortstring;
@@ -107,12 +114,12 @@
implementation
-uses {$IFNDEF PAS2C}typinfo, {$ENDIF}Math, uConsts, uVariables, uPhysFSLayer, uDebug;
+uses {$IFNDEF PAS2C}typinfo, SDLh, {$ENDIF}Math, uConsts, uVariables, uPhysFSLayer, uDebug;
{$IFDEF DEBUGFILE}
var logFile: PFSFile;
{$IFDEF USE_VIDEO_RECORDING}
- logMutex: TRTLCriticalSection; // mutex for debug file
+ logMutex: PSDL_mutex; // mutex for debug file
{$ENDIF}
{$ENDIF}
var CharArray: array[0..255] of Char;
@@ -250,6 +257,40 @@
end else b:= '';
end; { SplitByCharA }
+// In the ansistring a, escapes all instances of
+// '\e' with an ASCII ESC character, where e is
+// a char chosen by you.
+procedure EscapeCharA(var a: ansistring; e: char);
+var i: LongInt;
+begin
+repeat
+ i:= Pos(e, a);
+ if (i > 1) and (a[i - 1] = '\') then
+ begin
+ a[i]:= Char($1B); // ASCII ESC
+ Delete(a, i - 1, 1);
+ end
+ else
+ break;
+until (i <= 0);
+end; { EscapeCharA }
+
+// Unescapes a previously escaped string and inserts
+// e back into the string. e is a char chosen by you.
+procedure UnEscapeCharA(var a: ansistring; e: char);
+var i: LongInt;
+begin
+repeat
+ i:= Pos(Char($1B), a); // ASCII ESC
+ if (i > 0) then
+ begin
+ a[i]:= e;
+ end
+ else
+ break;
+until (i <= 0);
+end; { UnEscapeCharA }
+
function EnumToStr(const en : TGearType) : shortstring; overload;
begin
EnumToStr:= GetEnumName(TypeInfo(TGearType), ord(en))
@@ -331,6 +372,17 @@
str(n, IntToStr)
end;
+// Convert string to longint, with error checking.
+// Success will be set to false when conversion failed.
+// See documentation on Val procedure for syntax of s
+//function StrToInt(s: shortstring; var success: boolean): LongInt;
+//var Code: Word;
+//begin
+//val(s, StrToInt, Code);
+//success:= Code = 0;
+//end;
+
+// Convert string to longint, without error checking
function StrToInt(s: shortstring): LongInt;
begin
val(s, StrToInt);
@@ -457,7 +509,8 @@
{$IFDEF DEBUGFILE}
{$IFDEF USE_VIDEO_RECORDING}
-EnterCriticalSection(logMutex);
+if SDL_LockMutex(logMutex) <> 0 then
+ OutError('Logging mutex could not be locked!', true);
{$ENDIF}
if logFile <> nil then
pfsWriteLn(logFile, inttostr(GameTicks) + ': ' + s)
@@ -465,25 +518,37 @@
WriteLn(stdout, inttostr(GameTicks) + ': ' + s);
{$IFDEF USE_VIDEO_RECORDING}
-LeaveCriticalSection(logMutex);
+if SDL_UnlockMutex(logMutex) <> 0 then
+ OutError('Logging mutex could not be unlocked!', true);
{$ENDIF}
{$ENDIF}
end;
procedure AddFileLogRaw(s: pchar); cdecl;
+var msgLine: PChar;
begin
s:= s;
{$IFNDEF PAS2C}
{$IFDEF DEBUGFILE}
{$IFDEF USE_VIDEO_RECORDING}
-EnterCriticalSection(logMutex);
+if SDL_LockMutex(logMutex) <> 0 then
+ OutError('Logging mutex could not be locked!', true);
{$ENDIF}
-// TODO: uncomment next two lines
-// write(logFile, s);
-// flush(logFile);
+msgLine:= Str2PChar(IntToStr(GameTicks) + ': ');
+if (logFile <> nil) then
+ begin
+ pfsWriteRaw(logFile, msgLine, StrLen(msgLine));
+ pfsWriteRaw(logFile, s, StrLen(s));
+ end
+else
+ begin
+ Write(stdout, msgLine);
+ Flush(stdout);
+ end;
{$IFDEF USE_VIDEO_RECORDING}
-LeaveCriticalSection(logMutex);
+if SDL_UnlockMutex(logMutex) <> 0 then
+ OutError('Logging mutex could not be unlocked!', true);
{$ENDIF}
{$ENDIF}
{$ENDIF}
@@ -518,7 +583,7 @@
((#$AC00 <= u) and (u <= #$D7AF)) or // Hangul Syllables
((#$F900 <= u) and (u <= #$FAFF)) or // CJK Compatibility Ideographs
((#$FE30 <= u) and (u <= #$FE4F)) or // CJK Compatibility Forms
- ((#$FF66 <= u) and (u <= #$FF9D))) // halfwidth katakana
+ ((#$FF00 <= u) and (u <= #$FFEF))) // half- and fullwidth characters
then
begin
CheckCJKFont:= THWFont( ord(font) + ((ord(High(THWFont))+1) div 2) );
@@ -645,6 +710,40 @@
sanitizeCharForLog:= r
end;
+// helper function for volume change controls
+procedure updateVolumeDelta(precise: boolean);
+begin
+if cVolumeUpKey and (not cVolumeDownKey) then
+ if precise then
+ cVolumeDelta:= 1
+ else
+ cVolumeDelta:= 3
+else if cVolumeDownKey and (not cVolumeUpKey) then
+ if precise then
+ cVolumeDelta:= -1
+ else
+ cVolumeDelta:= -3
+else
+ cVolumeDelta:= 0;
+end;
+
+// helper function for cursor movement change controls
+procedure updateCursorMovementDelta(precise: boolean; dir: LongInt; var cursorVar: LongInt);
+begin
+if dir > 0 then
+ if precise then
+ cursorVar:= cameraKeyboardSpeedSlow
+ else
+ cursorVar:= cameraKeyboardSpeed
+else if dir < 0 then
+ if precise then
+ cursorVar:= - cameraKeyboardSpeedSlow
+ else
+ cursorVar:= - cameraKeyboardSpeed
+else
+ cursorVar:= 0;
+end;
+
function read1stLn(filePath: shortstring): shortstring;
var f: pfsFile;
begin
@@ -715,7 +814,9 @@
logfileBase:= 'preview';
{$ENDIF}
{$IFDEF USE_VIDEO_RECORDING}
- InitCriticalSection(logMutex);
+ logMutex:= SDL_CreateMutex();
+ if (logMutex = nil) then
+ OutError('Could not create mutex for logging', true);
{$ENDIF}
if not pfsExists('/Logs') then
pfsMakeDir('/Logs');
@@ -760,7 +861,7 @@
else
WriteLn(stdout, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft));
{$IFDEF USE_VIDEO_RECORDING}
- DoneCriticalSection(logMutex);
+ SDL_DestroyMutex(logMutex);
{$ENDIF}
{$ENDIF}
end;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uVariables.pas
--- a/hedgewars/uVariables.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uVariables.pas Wed Jul 31 23:14:27 2019 +0200
@@ -41,8 +41,8 @@
ipcPort : Word;
AprilOne : boolean;
cFullScreen : boolean;
- cLocaleFName : shortstring;
- cLocale : shortstring;
+ cLanguageFName : shortstring;
+ cLanguage : shortstring;
cTimerInterval : LongInt;
PathPrefix : ansistring;
UserPathPrefix : ansistring;
@@ -55,6 +55,7 @@
cAltDamage : boolean;
cReducedQuality : LongWord;
+ cHolidaySilliness : boolean;
UserNick : shortstring;
recordFileName : shortstring;
cReadyDelay : Longword;
@@ -78,6 +79,9 @@
isInMultiShoot : boolean;
isSpeed : boolean;
isAFK : boolean;
+ isShowMission : boolean;
+ isShowGearInfo : boolean;
+ isForceMission : boolean;
SpeedStart : LongWord;
fastUntilLag : boolean;
@@ -86,7 +90,9 @@
CheckSum : LongWord;
CampaignVariable: shortstring;
+ MissionVariable : shortstring;
GameTicks : LongWord;
+ OuchTauntTimer : LongWord; // Timer which blocks sndOuch from being played too often and fast
GameState : TGameState;
GameType : TGameType;
InputMask : LongWord;
@@ -101,12 +107,17 @@
IsGetAwayTime : boolean;
GameOver : boolean;
cSuddenDTurns : LongInt;
+ LastSuddenDWarn : LongInt; // last round in which the last SD warning appeared. -2 = no warning so far
+ cInitHealth : LongInt; // initial hedgehog health (from game scheme. note the real hog health is sent directly
+ // from frontend, this is only used to inform Lua scripts)
cDamagePercent : LongInt;
cMineDudPercent : LongWord;
cTemplateFilter : LongInt;
cFeatureSize : LongInt;
cMapGen : TMapGen;
cRopePercent : LongWord;
+ cRopeNodeStep : LongWord;
+ cRopeLayers : LongInt;
cGetAwayTime : LongWord;
cAdvancedMapGenMode: boolean;
@@ -125,8 +136,9 @@
cTagsMask : byte;
cPrevTagsMask : byte;
- zoom : GLfloat;
- ZoomValue : GLfloat;
+ zoom : GLfloat; // current zoom
+ ZoomValue : GLfloat; // aimed zoom
+ UserZoom : GLfloat; // user-chosen initial and default zoom
ChatScaleValue : real;
cDefaultChatScale: real;
UIScaleValue : real;
@@ -162,6 +174,8 @@
cScreenSpace : Longword;
cCaseFactor : Longword;
+ cMaxCaseDrops : Longword; // Max. number of crates which can be in the game when dropping
+
cLandMines : Longword;
cAirMines : Longword;
cExplosives : Longword;
@@ -169,16 +183,21 @@
cScriptName : shortstring;
cScriptParam : shortstring;
cSeed : shortstring;
+ cIsSoundEnabled : boolean; // If the sound system is enabled
cVolumeDelta : LongInt;
+ cVolumeUpKey : boolean;
+ cVolumeDownKey : boolean;
cMuteToggle : boolean; // Mute toggle requested
cHasFocus : boolean;
cInactDelay : Longword;
bBetweenTurns : boolean;
bWaterRising : boolean;
+ bDuringWaterRise: boolean;
CrosshairX : LongInt;
CrosshairY : LongInt;
+ CrosshairGear : PGear;
CursorMovementX : LongInt;
CursorMovementY : LongInt;
cWaveHeight : LongInt;
@@ -203,7 +222,9 @@
cMaxZoomLevel : real;
cMinZoomLevel : real;
cZoomDelta : real;
+ cZoomDeltaSmall : real;
cMinMaxZoomLevelDelta : real;
+ cDemoClockFPSOffsetY : LongInt;
flagMakeCapture : boolean;
@@ -215,9 +236,11 @@
WaterColorArray : array[0..7] of HwColor4f;
SDWaterColorArray : array[0..7] of HwColor4f;
+ ClanColorArray : array[0..Pred(cClanColors)] of Longword;
TargetCursorPoint : TPoint;
CursorPoint : TPoint;
+ CursorPointDelta : TPoint;
TargetPoint : TPoint;
ScreenFade : TScreenFade;
@@ -259,6 +282,12 @@
LuaEndTurnRequested: boolean;
LuaNoEndTurnTaunts: boolean;
+ // whether Lua requested to pause the clock
+ LuaClockPaused: boolean;
+
+ // whether /lua command was used
+ LuaCmdUsed: boolean;
+
MaskedSounds : array[TSound] of boolean;
LastVoice : TVoice;
@@ -274,10 +303,12 @@
//Buttons
{$IFDEF USE_TOUCH_INTERFACE}
buttonScale: GLFloat;
+ bounceButtonPressed: boolean;
arrowUp, arrowDown, arrowLeft, arrowRight : TOnScreenWidget;
firebutton, jumpWidget, AMWidget : TOnScreenWidget;
pauseButton, utilityWidget : TOnScreenWidget;
+ utilityWidget2 : TOnScreenWidget;
{$ENDIF}
@@ -291,7 +322,7 @@
'//', // ptData
'/Graphics', // ptGraphics
'/Themes', // ptThemes
- '/Themes/Bamboo', // ptCurrTheme
+ '/Themes/Nature', // ptCurrTheme
'/Config', // ptConfig
'/Config/Teams', // ptTeams
'/Maps', // ptMaps
@@ -310,7 +341,9 @@
'/Missions/Maps', // ptMissionMaps
'/Graphics/SuddenDeath', // ptSuddenDeath
'/Graphics/Buttons', // ptButton
- '/Shaders' // ptShaders
+ '/Shaders', // ptShaders
+ '/Sounds/voices/Default', // ptDefaultVoice
+ '/misc' // ptMisc
);
var
@@ -451,8 +484,8 @@
Width: 48; Height: 48; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprParachute
(FileName: 'Target'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprTarget
- (FileName: 'RopeNode'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
- Width: 6; Height: 6; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprRopeNode
+ (FileName: 'RopeNode'; Path: ptCurrTheme; AltPath: ptGraphics; Texture: nil; Surface: nil;
+ Width: 16; Height: 16; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprRopeNode
(FileName: 'thinking'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpLowest; getDimensions: false; getImageDimensions: true),// sprQuestion
(FileName: 'PowerBar'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
@@ -486,6 +519,8 @@
Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true), // sprTargetButton
(FileName: 'switchbutton'; Path: ptButtons; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true), // sprSwitchButton
+ (FileName: 'bouncebutton'; Path: ptButtons; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true), // sprBounceButton
{$ENDIF}
(FileName: 'Flake'; Path:ptCurrTheme; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprFlake
@@ -743,7 +778,7 @@
(FileName: 'botlevels'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 22; Height: 15; imageWidth: 22; imageHeight: 15; saveSurf: true; critical: true; checkSum: false; priority: tpLow; getDimensions: false; getImageDimensions: false), // sprBotlevels
(FileName: 'amCleaver'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
- Width: 64; Height: 64; imageWidth: 64; imageHeight: 64; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: false),// sprHandKnife
+ Width: 128; Height: 128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprHandKnife
(FileName: 'cleaver'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 64; Height: 64; imageWidth: 64; imageHeight: 128; saveSurf: false; critical: true; checkSum: false; priority: tpLow; getDimensions: false; getImageDimensions: false), // sprKnife
(FileName: 'star'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
@@ -774,6 +809,8 @@
Width: 0; Height: 0; imageWidth: 0; imageHeight: 0; saveSurf: true; critical: true; checkSum: true; priority: tpLow; getDimensions: true; getImageDimensions: true), // sprCustom7
(FileName: 'custom8'; Path: ptCurrTheme;AltPath: ptGraphics; Texture: nil; Surface: nil;
Width: 0; Height: 0; imageWidth: 0; imageHeight: 0; saveSurf: true; critical: true; checkSum: true; priority: tpLow; getDimensions: true; getImageDimensions: true), // sprCustom8
+ (FileName: 'FrozenAirMine'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 32; Height: 32; imageWidth: 32; imageHeight: 32; saveSurf: true; critical: true; checkSum: true; priority: tpHighest; getDimensions: false; getImageDimensions: true), // sprFrozenAirMine
(FileName: 'AirMine'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true), // sprAirMine
(FileName: 'amAirMine'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
@@ -786,14 +823,51 @@
Width: 256; Height:128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: false; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true),// sprCloudL
(FileName: 'SDCloudsL'; Path: ptCurrTheme;AltPath: ptGraphics; Texture: nil; Surface: nil;
Width: 256; Height:128; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: false; checkSum: false; priority: tpHigh; getDimensions: false; getImageDimensions: true),// sprSDCloudL
+ // TODO: Rename creeper image
(FileName: 'Duck'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
- Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprDuck
+ Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprCreeper
+ // TODO: Rename creeper hand image
(FileName: 'amDuck'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
- Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true), // sprHandDuck
+ Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true), // sprHandCreeper
(FileName: 'amMinigun'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
- Width: 64; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true) // sprMinigun
+ Width: 64; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true), // sprMinigun
+ (FileName: 'sliderInverted'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 3; Height: 17; imageWidth: 3; imageHeight: 17; saveSurf: false; critical: true; checkSum: false; priority: tpLow; getDimensions: false; getImageDimensions: false), // sprSliderInverted
+ (FileName: 'FingerBack'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 32; Height: 48; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true), // sprFingerBack
+ (FileName: 'FingerBackInv'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 32; Height: 48; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprFingerBackInv
+ (FileName: 'TargetpBack'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprTargetPBack
+ (FileName: 'TargetpBackInv'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprTargetPBackInv
+ (FileName: 'HealthHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 18; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprHealthHud
+ (FileName: 'HealthPoisonHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 18; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprHealthPoisonHud
+ (FileName: 'VampHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 24; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprVampHUD
+ (FileName: 'KarmaHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 18; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprKarmaHUD
+ (FileName: 'MedicHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 18; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprMedicHud
+ (FileName: 'MedicPoisonHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 18; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprMedicPoisonHud
+ (FileName: 'HaloHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 22; Height: 11; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprHaloHUD
+ (FileName: 'InvulnHUD'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 18; Height: 18; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprInvulnHUD
+ (FileName: 'amPiano'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 42; Height: 42; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprAmPiano
+ (FileName: 'amLandGun'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true), // sprHandLandGun
+ (FileName: 'amShoryuken'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprFirePunch
+ (FileName: 'throughWrap'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+ Width: 16; Height: 13; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true) // sprTroughWrap
);
+
const
Wavez: array [TWave] of record
Sprite: TSprite;
@@ -827,9 +901,11 @@
PosSprite: TSprite;
ejectX, ejectY: Longint;
end;
+ TAmmoCounts = array[TAmmoType] of Longword;
var
Ammoz: array [TAmmoType] of TAmmozRec;
+ InitialAmmoCounts: TAmmoCounts;
const
AmmozInit: array [TAmmoType] of TAmmozRec = (
@@ -844,14 +920,14 @@
Pos: 0;
AmmoType: amNothing;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: cHiddenSlotIndex;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
- SkipTurns: 9999;
- PosCount: 1;
+ SkipTurns: 0;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -872,14 +948,14 @@
Pos: 0;
AmmoType: amGrenade;
AttackVoice: sndCover;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 1;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -900,14 +976,14 @@
Pos: 0;
AmmoType: amClusterBomb;
AttackVoice: sndCover;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 1;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -925,15 +1001,15 @@
Timer: 0;
Pos: 0;
AmmoType: amBazooka;
- AttackVoice: sndNone;
- Bounciness: 1000);
+ AttackVoice: sndFire;
+ Bounciness: defaultBounciness);
Slot: 0;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //20;
ejectY: -6),
@@ -946,15 +1022,17 @@
Ammo: (Propz: ammoprop_Power or
ammoprop_NeedTarget or
ammoprop_NoTargetAfter or
+ ammoprop_NoWrapTarget or
ammoprop_DontHold or
+ ammoprop_AltUse or
ammoprop_NeedUpDown;
Count: 2;
NumPerTurn: 0;
Timer: 0;
Pos: 0;
AmmoType: amBee;
- AttackVoice: sndNone;
- Bounciness: 1000);
+ AttackVoice: sndFire;
+ Bounciness: defaultBounciness);
Slot: 0;
TimeAfterTurn: 3000;
minAngle: 0;
@@ -980,14 +1058,14 @@
Pos: 0;
AmmoType: amShotgun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 2;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //26;
ejectY: -6),
@@ -1008,14 +1086,14 @@
Pos: 0;
AmmoType: amPickHammer;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 6;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1035,14 +1113,14 @@
Pos: 0;
AmmoType: amSkip;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 9;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1065,14 +1143,14 @@
Pos: 0;
AmmoType: amRope;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 7;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: cMaxAngle div 2;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1093,14 +1171,14 @@
Pos: 0;
AmmoType: amMine;
AttackVoice: sndLaugh;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 4;
TimeAfterTurn: 5000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1117,14 +1195,14 @@
Pos: 0;
AmmoType: amDEagle;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 2;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //23;
ejectY: -6),
@@ -1144,14 +1222,14 @@
Pos: 0;
AmmoType: amDynamite;
AttackVoice: sndLaugh;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 4;
TimeAfterTurn: 5000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1170,14 +1248,14 @@
Pos: 0;
AmmoType: amFirePunch;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 3;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1194,14 +1272,14 @@
Pos: 0;
AmmoType: amWhip;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 3;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1219,14 +1297,14 @@
Pos: 0;
AmmoType: amBaseballBat;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 3;
TimeAfterTurn: 5000;
minAngle: 0;
maxAngle: cMaxAngle div 2;
isDamaging: true;
SkipTurns: 2;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1243,6 +1321,7 @@
ammoprop_DontHold or
ammoprop_Utility or
ammoprop_AltAttack or
+ ammoprop_ShowSelIcon or
ammoprop_NeedUpDown;
Count: 2;
NumPerTurn: 0;
@@ -1250,14 +1329,14 @@
Pos: 0;
AmmoType: amParachute;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 7;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1277,8 +1356,8 @@
Timer: 0;
Pos: 0;
AmmoType: amAirAttack;
- AttackVoice: sndIncoming;
- Bounciness: 1000);
+ AttackVoice: sndNone; // handled in doStepAirAttack
+ Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
minAngle: 0;
@@ -1305,8 +1384,8 @@
Timer: 0;
Pos: 0;
AmmoType: amMineStrike;
- AttackVoice: sndIncoming;
- Bounciness: 1000);
+ AttackVoice: sndNone; // handled in doStepAirAttack
+ Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
minAngle: 0;
@@ -1332,14 +1411,14 @@
Pos: 0;
AmmoType: amBlowTorch;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 6;
TimeAfterTurn: 3000;
minAngle: 804;
maxAngle: 1327;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1360,7 +1439,7 @@
Pos: 0;
AmmoType: amGirder;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 6;
TimeAfterTurn: 3000;
minAngle: 0;
@@ -1381,6 +1460,7 @@
ammoprop_NoCrosshair or
ammoprop_NeedTarget or
ammoprop_AttackingPut or
+ ammoprop_AttackInMove or
ammoprop_Utility or
ammoprop_DontHold;
Count: 2;
@@ -1389,7 +1469,7 @@
Pos: 0;
AmmoType: amTeleport;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 7;
TimeAfterTurn: 0;
minAngle: 0;
@@ -1410,6 +1490,7 @@
ammoprop_ForwMsgs or
ammoprop_NoCrosshair or
ammoprop_Utility or
+ ammoprop_ShowSelIcon or
ammoprop_DontHold;
Count: 3;
NumPerTurn: 0;
@@ -1417,14 +1498,14 @@
Pos: 0;
AmmoType: amSwitch;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 9;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1441,14 +1522,14 @@
Pos: 0;
AmmoType: amMortar;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 0;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //20;
ejectY: -6),
@@ -1469,14 +1550,14 @@
Pos: 0;
AmmoType: amKamikaze;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 3;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1496,14 +1577,14 @@
Pos: 0;
AmmoType: amCake;
AttackVoice: sndLaugh;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 4;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 4;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1522,14 +1603,14 @@
Pos: 0;
AmmoType: amSeduction;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 3;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1549,14 +1630,14 @@
Pos: 0;
AmmoType: amWatermelon;
AttackVoice: sndMelon;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 1;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1574,15 +1655,15 @@
Timer: 5000;
Pos: 0;
AmmoType: amHellishBomb;
- AttackVoice: sndNone;
- Bounciness: 1000);
+ AttackVoice: sndWatchThis;
+ Bounciness: defaultBounciness);
Slot: 1;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1602,8 +1683,8 @@
Timer: 0;
Pos: 0;
AmmoType: amNapalm;
- AttackVoice: sndIncoming;
- Bounciness: 1000);
+ AttackVoice: sndNone; // handled in doStepAirAttack
+ Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
minAngle: 0;
@@ -1628,15 +1709,15 @@
Timer: 0;
Pos: 0;
AmmoType: amDrill;
- AttackVoice: sndNone;
- Bounciness: 1000);
+ AttackVoice: sndFire;
+ Bounciness: defaultBounciness);
Slot: 0;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprDrill;
ejectX: 0; //20;
ejectY: -6),
@@ -1655,14 +1736,14 @@
Pos: 0;
AmmoType: amBallgun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 4;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //20;
ejectY: -3),
@@ -1682,14 +1763,14 @@
Pos: 0;
AmmoType: amRCPlane;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 4;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 4;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1703,6 +1784,7 @@
ammoprop_NoCrosshair or
ammoprop_DontHold or
ammoprop_AltUse or
+ ammoprop_ShowSelIcon or
ammoprop_Utility or
ammoprop_Effect;
Count: 1;
@@ -1711,14 +1793,14 @@
Pos: 0;
AmmoType: amLowGravity;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 9;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1732,6 +1814,7 @@
ammoprop_NoCrosshair or
ammoprop_DontHold or
ammoprop_AltUse or
+ ammoprop_ShowSelIcon or
ammoprop_Utility or
ammoprop_Effect;
Count: 1;
@@ -1740,14 +1823,14 @@
Pos: 0;
AmmoType: amExtraDamage;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 9;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1761,6 +1844,7 @@
ammoprop_NoCrosshair or
ammoprop_DontHold or
ammoprop_AltUse or
+ ammoprop_ShowSelIcon or
ammoprop_Utility or
ammoprop_Effect;
Count: 1;
@@ -1769,14 +1853,14 @@
Pos: 0;
AmmoType: amInvulnerable;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 8;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1790,6 +1874,7 @@
ammoprop_NoCrosshair or
ammoprop_DontHold or
ammoprop_AltUse or
+ ammoprop_ShowSelIcon or
ammoprop_Utility or
ammoprop_Effect;
Count: 1;
@@ -1798,14 +1883,14 @@
Pos: 0;
AmmoType: amExtraTime;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 9;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1819,6 +1904,7 @@
ammoprop_NoCrosshair or
ammoprop_DontHold or
ammoprop_AltUse or
+ ammoprop_ShowSelIcon or
ammoprop_Utility or
ammoprop_NeedUpDown or
ammoprop_Effect;
@@ -1828,14 +1914,14 @@
Pos: 0;
AmmoType: amLaserSight;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 8;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1857,14 +1943,14 @@
Pos: 0;
AmmoType: amVampiric;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 8;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1884,14 +1970,14 @@
Pos: 0;
AmmoType: amSniperRifle;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 2;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //40;
ejectY: -5),
@@ -1907,6 +1993,7 @@
ammoprop_DontHold or
ammoprop_Utility or
ammoprop_NeedUpDown or
+ ammoprop_ShowSelIcon or
ammoprop_AltAttack;
Count: 1;
NumPerTurn: 0;
@@ -1914,14 +2001,14 @@
Pos: 0;
AmmoType: amJetpack;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 7;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1939,15 +2026,15 @@
Timer: 3000;
Pos: 0;
AmmoType: amMolotov;
- AttackVoice: sndNone;
- Bounciness: 1000);
+ AttackVoice: sndWatchThis;
+ Bounciness: defaultBounciness);
Slot: 1;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1960,6 +2047,7 @@
Ammo: (Propz: ammoprop_ForwMsgs or
ammoprop_NoCrosshair or
ammoprop_NeedUpDown or
+ ammoprop_ShowSelIcon or
ammoprop_DontHold;
Count: 1;
NumPerTurn: 0;
@@ -1967,14 +2055,14 @@
Pos: 0;
AmmoType: amBirdy;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 7;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -1995,14 +2083,14 @@
Pos: 0;
AmmoType: amPortalGun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 7;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: -5; //29;
ejectY: -7),
@@ -2014,7 +2102,7 @@
NumberInCase: 1;
Ammo: (Propz: ammoprop_NoCrosshair or
ammoprop_NeedTarget or
- ammoprop_NoTargetAfter or
+ // NoTargetAfter is handled manually in doStepPiano
ammoprop_AttackingPut or
ammoprop_DontHold or
ammoprop_NotBorder or
@@ -2024,8 +2112,8 @@
Timer: 0;
Pos: 0;
AmmoType: amPiano;
- AttackVoice: sndIncoming;
- Bounciness: 1000);
+ AttackVoice: sndNone; // handled in doStepPiano
+ Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
minAngle: 0;
@@ -2033,7 +2121,7 @@
isDamaging: true;
SkipTurns: 7;
PosCount: 1;
- PosSprite: sprWater;
+ PosSprite: sprAmPiano;
ejectX: 0;
ejectY: 0),
@@ -2053,14 +2141,14 @@
Pos: 0;
AmmoType: amGasBomb;
AttackVoice: sndCover;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 1;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2078,14 +2166,14 @@
Pos: 0;
AmmoType: amSineGun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 2;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2104,14 +2192,14 @@
Pos: 0;
AmmoType: amFlamethrower;
AttackVoice: sndNone;
- Bounciness: 1000);
- Slot: 2;
+ Bounciness: defaultBounciness);
+ Slot: 6;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //20;
ejectY: -3),
@@ -2130,14 +2218,14 @@
Pos: 0;
AmmoType: amSMine;
AttackVoice: sndLaugh;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 4;
TimeAfterTurn: 5000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2154,14 +2242,14 @@
Pos: 0;
AmmoType: amHammer;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 3;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2173,21 +2261,22 @@
NumberInCase: 1;
Ammo: (Propz: ammoprop_NoCrosshair or
ammoprop_Utility or
- ammoprop_NoRoundEnd;
+ ammoprop_NoRoundEnd or
+ ammoprop_DoesntStopTimerWhileAttacking;
Count: 1;
NumPerTurn: 0;
Timer: 0;
Pos: 0;
AmmoType: amResurrector;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 8;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2208,8 +2297,8 @@
Timer: 5000;
Pos: 0;
AmmoType: amDrillStrike;
- AttackVoice: sndIncoming;
- Bounciness: 1000);
+ AttackVoice: sndNone; // handled in doStepAirAttack
+ Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 0;
minAngle: 0;
@@ -2236,14 +2325,14 @@
Pos: 0;
AmmoType: amSnowball;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 0;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2256,6 +2345,7 @@
Ammo: (Propz: ammoprop_ForwMsgs or
ammoprop_NoCrosshair or
ammoprop_Utility or
+ ammoprop_ShowSelIcon or
ammoprop_DontHold or
ammoprop_ForceTurnEnd;
Count: 2;
@@ -2264,7 +2354,7 @@
Pos: 0;
AmmoType: amTardis;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 8;
TimeAfterTurn: 0;
minAngle: 0;
@@ -2276,35 +2366,6 @@
ejectX: 0;
ejectY: 0),
-// Structure
-{
- (NameId: sidStructure;
- NameTex: nil;
- Probability: 0;
- NumberInCase: 1;
- Ammo: (Propz: ammoprop_ForwMsgs or
- ammoprop_NoCrosshair or
- ammoprop_Utility or
- ammoprop_DontHold;
- Count: 1;
- NumPerTurn: 0;
- Timer: 0;
- Pos: 0;
- AmmoType: amStructure;
- AttackVoice: sndNone;
- Bounciness: 1000);
- Slot: 6;
- TimeAfterTurn: 0;
- minAngle: 0;
- maxAngle: 0;
- isDamaging: false;
- SkipTurns: 0;
- PosCount: 1;
- PosSprite: sprWater;
- ejectX: 0;
- ejectY: 0),
-}
-
// Land Gun
(NameId: sidLandGun;
NameTex: nil;
@@ -2319,14 +2380,14 @@
Pos: 0;
AmmoType: amLandGun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 6;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //20;
ejectY: -3),
@@ -2344,14 +2405,14 @@
Pos: 0;
AmmoType: amIceGun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 2;
TimeAfterTurn: 0;
minAngle: 0;
maxAngle: 0;
isDamaging: false;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //20;
ejectY: -3),
@@ -2368,15 +2429,15 @@
Timer: 0;
Pos: 0;
AmmoType: amKnife;
- AttackVoice: sndNone;
- Bounciness: 1000);
- Slot: 6;
+ AttackVoice: sndWatchThis;
+ Bounciness: defaultBounciness);
+ Slot: 0;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
@@ -2396,7 +2457,7 @@
Pos: 0;
AmmoType: amRubber;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 6;
TimeAfterTurn: 3000;
minAngle: 0;
@@ -2421,19 +2482,19 @@
Pos: 0;
AmmoType: amAirMine;
AttackVoice: sndLaugh;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 5;
TimeAfterTurn: 5000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0;
ejectY: 0),
-// Rubber duck
- (NameId: sidDuck;
+// Creeper
+ (NameId: sidCreeper;
NameTex: nil;
Probability: 100;
NumberInCase: 1;
@@ -2445,16 +2506,18 @@
NumPerTurn: 0;
Timer: 15000;
Pos: 0;
- AmmoType: amDuck;
+ AmmoType: amCreeper;
AttackVoice: sndNone;
- Bounciness: 1000);
- Slot: 0;
+ Bounciness: defaultBounciness);
+ // Slot chosen to prevent ammo column overflow
+ // TODO: Change slot when creeper is finished
+ Slot: 8;
TimeAfterTurn: 3000;
minAngle: 0;
maxAngle: 0;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 15;
ejectY: -7),
@@ -2470,14 +2533,14 @@
Pos: 0;
AmmoType: amMinigun;
AttackVoice: sndNone;
- Bounciness: 1000);
+ Bounciness: defaultBounciness);
Slot: 2;
TimeAfterTurn: 3000;
minAngle: cMaxAngle div 6;
maxAngle: 5 * cMaxAngle div 6;
isDamaging: true;
SkipTurns: 0;
- PosCount: 1;
+ PosCount: 0;
PosSprite: sprWater;
ejectX: 0; //23;
ejectY: 0) //-6;
@@ -2489,7 +2552,8 @@
LandDirty: TDirtyTag;
hasBorder: boolean;
hasGirders: boolean;
- playHeight, playWidth, leftX, rightX, topY, MaxHedgehogs: Longword; // idea is that a template can specify height/width. Or, a map, a height/width by the dimensions of the image. If the map has pixels near top of image, it triggers border.
+ playHeight, playWidth, leftX, rightX, topY: LongInt; // idea is that a template can specify height/width. Or, a map, a height/width by the dimensions of the image. If the map has pixels near top of image, it triggers border.
+ MaxHedgehogs: LongWord;
LandBackSurface: PSDL_Surface;
CurAmmoGear: PGear;
lastGearByUID: PGear;
@@ -2503,18 +2567,19 @@
SpeechType: Longword;
SpeechText: shortstring;
PlacingHogs: boolean; // a convenience flag to indicate placement of hogs is still in progress
+ PlacingKings: boolean; // a convenience flag to indicate placement of kings in King Mode is still in progress
StepSoundTimer: LongInt;
StepSoundChannel: LongInt;
CurrentTeam: PTeam;
PreviousTeam: PTeam;
+ MissionTeam: PTeam;
CurrentHedgehog: PHedgehog;
TeamsArray: array[0..Pred(cMaxTeams)] of PTeam;
TeamsCount: Longword; // number of teams on game start
VisibleTeamsCount: Longword; // number of teams visible in team bar
ClansArray, SpawnClansArray: TClansArray;
ClansCount: Longword;
- LocalClan: LongInt; // last non-bot, non-extdriven clan
LocalTeam: LongInt; // last non-bot, non-extdriven clan first team
LocalAmmo: LongInt; // last non-bot, non-extdriven clan's first team's ammo index, updated to next upcoming hog for per-hog-ammo
CurMinAngle, CurMaxAngle: Longword;
@@ -2584,6 +2649,8 @@
aTexCoord: GLint;
aColor: GLint;
+ lDecimalSeparator: Char;
+
var trammo: array[TAmmoStrId] of ansistring; // name of the weapon
trammoc: array[TAmmoStrId] of ansistring; // caption of the weapon
trammod: array[TAmmoStrId] of ansistring; // description of the weapon
@@ -2591,8 +2658,10 @@
trluaammoc: array[TAmmoStrId] of ansistring; // caption of the weapon (Lua overwrite)
trluaammod: array[TAmmoStrId] of ansistring; // description of the weapon (Lua overwrite)
trluaammoa: array[TAmmoStrId] of ansistring; // description appendix of the weapon (Lua only)
+ trluaammoe: array[TAmmoStrId] of boolean; // whether to render extra text (Lua overwrite)
trmsg: array[TMsgStrId] of ansistring; // message of the event
trgoal: array[TGoalStrId] of ansistring; // message of the goal
+ trcmd: array[TCmdHelpStrId] of ansistring; // chat command help
cTestLua : Boolean;
procedure preInitModule;
@@ -2614,9 +2683,10 @@
cShowFPS := false;
cAltDamage := false;
+ cHolidaySilliness := true;
cTimerInterval := 8;
cReducedQuality := rqNone;
- cLocaleFName := 'en.txt';
+ cLanguageFName := 'en.txt';
cFullScreen := false;
UserPathPrefix := '';
@@ -2632,6 +2702,22 @@
cScriptParam := '';
cTestLua := False;
+ UserZoom := cDefaultZoomLevel;
+ zoom := cDefaultZoomLevel;
+ ZoomValue := cDefaultZoomLevel;
+
+{$IFDEF MOBILE}
+ cMaxZoomLevel := 0.5;
+ cMinZoomLevel := 3.5;
+ cZoomDelta := 0.20;
+ cZoomDeltaSmall := 0.10;
+{$ELSE}
+ cMaxZoomLevel := 1.0;
+ cMinZoomLevel := 3.0;
+ cZoomDelta := 0.25;
+ cZoomDeltaSmall := 0.125;
+{$ENDIF}
+
{$IFDEF USE_VIDEO_RECORDING}
RecPrefix := '';
cAVFormat := '';
@@ -2660,6 +2746,7 @@
var s: shortstring;
i: integer;
t: TSound;
+ a: TAmmoStrId;
begin
// init LastVoice
LastVoice.snd:= sndNone;
@@ -2672,8 +2759,8 @@
Move(AmmozInit, Ammoz, sizeof(Ammoz));
- cLocale:= cLocaleFName;
- SplitByChar(cLocale, s, '.');
+ cLanguage:= cLanguageFName;
+ SplitByChar(cLanguage, s, '.');
cFlattenFlakes := false;
cFlattenClouds := false;
@@ -2723,6 +2810,19 @@
WaterOpacity:= $80;
+ // default clan colors
+ // always keep in sync with QTfrontend/hwconsts.h
+
+ ClanColorArray[0] := $ffff0204;
+ ClanColorArray[1] := $ff4980c1;
+ ClanColorArray[2] := $ff1de6ba;
+ ClanColorArray[3] := $ffb541ef;
+ ClanColorArray[4] := $ffe55bb0;
+ ClanColorArray[5] := $ff20bf00;
+ ClanColorArray[6] := $fffe8b0e;
+ ClanColorArray[7] := $ff8f5902;
+ ClanColorArray[8] := $ffffff01;
+
// default sudden death water
// deep water
@@ -2773,27 +2873,22 @@
cDamageModifier := _1;
TargetPoint := cTargetPointRef;
-{$IFDEF MOBILE}
- cMaxZoomLevel:= 0.5;
- cMinZoomLevel:= 3.5;
- cZoomDelta:= 0.20;
-{$ELSE}
- cMaxZoomLevel:= 1.0;
- cMinZoomLevel:= 3.0;
- cZoomDelta:= 0.25;
- {$ENDIF}
-
aVertex:= 0;
aTexCoord:= 1;
aColor:= 2;
+ lDecimalSeparator := '.';
+
cMinMaxZoomLevelDelta:= cMaxZoomLevel - cMinZoomLevel;
+ cDemoClockFPSOffsetY := 0;
+
// int, longint longword and byte
CursorMovementX := 0;
CursorMovementY := 0;
GameTicks := 0;
+ OuchTauntTimer := 0;
CheckSum := 0;
cWaterLine := LAND_HEIGHT;
cGearScrEdgesDist := 240;
@@ -2809,12 +2904,16 @@
TurnClockActive := true;
TagTurnTimeLeft := 0;
cSuddenDTurns := 15;
+ LastSuddenDWarn := -2;
+ cInitHealth := 100;
cDamagePercent := 100;
cRopePercent := 100;
+ cRopeNodeStep := 4;
+ cRopeLayers := 1;
cGetAwayTime := 100;
cMineDudPercent := 0;
cTemplateFilter := 0;
- cFeatureSize := 50;
+ cFeatureSize := 12;
cMapGen := mgRandom;
cHedgehogTurnTime := 45000;
cMinesTime := 3000;
@@ -2831,6 +2930,7 @@
RealTicks := 0;
AttackBar := 0; // 0 - none, 1 - just bar at the right-down corner, 2 - from weapon
cCaseFactor := 5; {0..9}
+ cMaxCaseDrops := 5;
cLandMines := 4;
cAirMines := 0;
cExplosives := 2;
@@ -2854,18 +2954,25 @@
flagDumpLand := false;
bBetweenTurns := false;
bWaterRising := false;
+ bDuringWaterRise:= false;
isCursorVisible := false;
isInLag := false;
isPaused := false;
isInMultiShoot := false;
isSpeed := false;
isAFK := false;
+ isShowMission := false;
+ isShowGearInfo := false;
+ isForceMission := false;
SpeedStart := 0;
fastUntilLag := false;
fastScrolling := false;
autoCameraOn := true;
cSeed := '';
+ cIsSoundEnabled := false;
cVolumeDelta := 0;
+ cVolumeUpKey := false;
+ cVolumeDownKey := false;
cMuteToggle := false;
cHasFocus := true;
cInactDelay := 100;
@@ -2929,9 +3036,14 @@
LuaEndTurnRequested:= false;
LuaNoEndTurnTaunts:= false;
+ LuaCmdUsed:= false;
+
for t:= Low(TSound) to High(TSound) do
MaskedSounds[t]:= false;
+ for a:= Low(TAmmoStrId) to High(TAmmoStrId) do
+ trluaammoe[a]:= true;
+
UIDisplay:= uiAll;
LocalMessage:= 0;
@@ -2946,6 +3058,7 @@
GearsList:= nil;
CurrentTeam:= nil;
PreviousTeam:= nil;
+ MissionTeam:= nil;
CurrentHedgehog:= nil;
FollowGear:= nil;
lastVisualGearByUID:= nil;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uVideoRec.pas
--- a/hedgewars/uVideoRec.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uVideoRec.pas Wed Jul 31 23:14:27 2019 +0200
@@ -27,7 +27,7 @@
end.
{$ELSE}
-{$IFNDEF WIN32}
+{$IFNDEF WINDOWS}
{$linklib avwrapper}
{$ENDIF}
@@ -36,7 +36,7 @@
var flagPrerecording: boolean = false;
function BeginVideoRecording: Boolean;
-function LoadNextCameraPosition(out newRealTicks, newGameTicks: LongInt): Boolean;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
procedure EncodeFrame;
procedure StopVideoRecording;
@@ -48,7 +48,7 @@
procedure freeModule;
implementation
-uses uVariables, GLunit, SDLh, SysUtils, uUtils, uIO, uMisc, uTypes, uDebug;
+uses uVariables, GLunit, SDLh, SysUtils, uUtils, uSound, uIO, uMisc, uTypes, uDebug;
type TAddFileLogRaw = procedure (s: pchar); cdecl;
const AvwrapperLibName = 'libavwrapper';
@@ -68,27 +68,30 @@
end;
var RGB_Buffer: PByte;
- cameraFile: File of TFrame;
+ cameraFile: File;
+ cameraFileName: shortstring;
audioFile: File;
numPixels: LongWord;
startTime, numFrames, curTime, progress, maxProgress: LongWord;
soundFilePath: shortstring;
- thumbnailSaved : Boolean;
+ thumbnailSaved: boolean;
+ recordAudio: boolean;
function BeginVideoRecording: Boolean;
var filename, desc: shortstring;
+ filenameA, descA, soundFilePathA, cAVFormatA, cVideoCodecA, cAudioCodecA: ansistring;
begin
AddFileLog('BeginVideoRecording');
{$IOCHECKS OFF}
// open file with prerecorded camera positions
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
- Assign(cameraFile, filename);
- Reset(cameraFile);
+ cameraFileName:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.txtin';
+ Assign(cameraFile, cameraFileName);
+ Reset(cameraFile, SizeOf(TFrame));
maxProgress:= FileSize(cameraFile);
if IOResult <> 0 then
begin
- AddFileLog('Error: Could not read from ' + filename);
+ AddFileLog('Error: Could not read from ' + cameraFileName);
exit(false);
end;
{$IOCHECKS ON}
@@ -112,16 +115,27 @@
desc:= desc + 'Theme: ' + Theme + #10;
desc:= desc + 'prefix[' + RecPrefix + ']prefix';
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix;
- soundFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw';
+ filename:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix);
+ recordAudio:= (cAudioCodec <> 'no');
+ if recordAudio then
+ soundFilePath:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.sw'
+ else
+ soundFilePath:= '';
+
+ filenameA:= ansistring(filename);
+ descA:= ansistring(desc);
+ soundFilePathA:= ansistring(soundFilePath);
+ cAVFormatA:= ansistring(cAVFormat);
+ cVideoCodecA:= ansistring(cVideoCodec);
+ cAudioCodecA:= ansistring(cAudioCodec);
if checkFails(AVWrapper_Init(@AddFileLogRaw
- , PChar(ansistring(filename))
- , PChar(ansistring(desc))
- , PChar(ansistring(soundFilePath))
- , PChar(ansistring(cAVFormat))
- , PChar(ansistring(cVideoCodec))
- , PChar(ansistring(cAudioCodec))
+ , PChar(filenameA)
+ , PChar(descA)
+ , PChar(soundFilePathA)
+ , PChar(cAVFormatA)
+ , PChar(cVideoCodecA)
+ , PChar(cAudioCodecA)
, cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, cVideoQuality) >= 0,
'AVWrapper_Init failed',
true) then exit(false);
@@ -147,9 +161,17 @@
FreeMem(RGB_Buffer, 4*numPixels);
Close(cameraFile);
if AVWrapper_Close() < 0 then
- halt(-1);
- Erase(cameraFile);
- DeleteFile(soundFilePath);
+ begin
+ OutError('AVWrapper_Close() has failed.', true);
+ end;
+{$IOCHECKS OFF}
+ if FileExists(cameraFileName) then
+ DeleteFile(cameraFileName)
+ else
+ AddFileLog('Warning: Tried to delete the cameraFile but it was already deleted');
+{$IOCHECKS ON}
+ if recordAudio and FileExists(soundFilePath) then
+ DeleteFile(soundFilePath);
SendIPC(_S'v'); // inform frontend that we finished
end;
@@ -160,7 +182,9 @@
glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer);
if AVWrapper_WriteFrame(RGB_Buffer) < 0 then
- halt(-1);
+ begin
+ OutError('AVWrapper_WriteFrame(RGB_Buffer) has failed.', true);
+ end;
// inform frontend that we have encoded new frame
s[0]:= #3;
@@ -170,16 +194,18 @@
inc(numFrames);
end;
-function LoadNextCameraPosition(out newRealTicks, newGameTicks: LongInt): Boolean;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
var frame: TFrame = (realTicks: 0; gameTicks: 0; CamX: 0; CamY: 0; zoom: 0);
+ res: LongInt;
begin
// we need to skip or duplicate frames to match target framerate
while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do
begin
+ res:= 0;
{$IOCHECKS OFF}
if eof(cameraFile) then
exit(false);
- BlockRead(cameraFile, frame, 1);
+ BlockRead(cameraFile, frame, 1, res);
{$IOCHECKS ON}
curTime:= frame.realTicks;
WorldDx:= frame.CamX;
@@ -196,10 +222,12 @@
// Callback which records sound.
// This procedure may be called from different thread.
procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl;
+var result: LongInt;
begin
+ result:= 0; // avoid warning
udata:= udata; // avoid warning
{$IOCHECKS OFF}
- BlockWrite(audioFile, stream^, len);
+ BlockWrite(audioFile, stream^, len, result);
{$IOCHECKS ON}
end;
@@ -207,7 +235,7 @@
var thumbpath: shortstring;
k: LongInt;
begin
- thumbpath:= '/VideoTemp/' + RecPrefix;
+ thumbpath:= '/VideoThumbnails/' + RecPrefix;
AddFileLog('Saving thumbnail ' + thumbpath);
k:= max(max(cScreenWidth, cScreenHeight) div 400, 1); // here 400 is minimum size of thumbnail
MakeScreenshot(thumbpath, k, 0);
@@ -218,11 +246,12 @@
procedure CopyFile(src, dest: shortstring);
var inF, outF: file;
buffer: array[0..1023] of byte;
- result: LongInt;
+ result, result2: LongInt;
i: integer;
begin
{$IOCHECKS OFF}
result:= 0; // avoid compiler hint and warning
+ result2:= 0; // avoid compiler hint and warning
for i:= 0 to 1023 do
buffer[i]:= 0;
@@ -244,7 +273,7 @@
repeat
BlockRead(inF, buffer, 1024, result);
- BlockWrite(outF, buffer, result);
+ BlockWrite(outF, buffer, result, result2);
until result < 1024;
{$IOCHECKS ON}
end;
@@ -253,11 +282,21 @@
var format: word;
filename: shortstring;
frequency, channels: LongInt;
+ result: LongInt;
begin
+ result:= 0;
AddFileLog('BeginPreRecording');
+ // Videos don't work if /lua command was used, so we forbid them
+ if luaCmdUsed then
+ begin
+ // TODO: Show message to player
+ PlaySound(sndDenied);
+ AddFileLog('Pre-recording prevented; /lua command was used before');
+ exit;
+ end;
thumbnailSaved:= false;
- RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', Now());
+ RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', TDateTime(Now()));
// If this video is recorded from demo executed directly (without frontend)
// then we need to copy demo so that frontend will be able to find it later.
@@ -265,46 +304,52 @@
begin
if GameType <> gmtDemo then // this is save and game demo is not recording, abort
exit;
- CopyFile(recordFileName, UserPathPrefix + '/VideoTemp/' + RecPrefix + '.hwd');
+ CopyFile(recordFileName, shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.hwd');
end;
- Mix_QuerySpec(@frequency, @format, @channels);
- AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
- if format <> $8010 then
- begin
- // TODO: support any audio format
- AddFileLog('Error: Unexpected audio format ' + IntToStr(format));
- exit;
- end;
+ if cIsSoundEnabled then
+ begin
+ Mix_QuerySpec(@frequency, @format, @channels);
+ AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
+ if format <> $8010 then
+ begin
+ // TODO: support any audio format
+ AddFileLog('Error: Unexpected audio format ' + IntToStr(format));
+ exit;
+ end;
{$IOCHECKS OFF}
- // create sound file
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw';
- Assign(audioFile, filename);
- Rewrite(audioFile, 1);
- if IOResult <> 0 then
- begin
- AddFileLog('Error: Could not write to ' + filename);
- exit;
- end;
+ // create sound file
+ filename:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.sw';
+ Assign(audioFile, filename);
+ Rewrite(audioFile, 1);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not write to ' + filename);
+ exit;
+ end;
+ end;
// create file with camera positions
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtout';
+ filename:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.txtout';
Assign(cameraFile, filename);
- Rewrite(cameraFile);
+ Rewrite(cameraFile, SizeOf(TFrame));
if IOResult <> 0 then
- begin
+ begin
AddFileLog('Error: Could not write to ' + filename);
exit;
- end;
+ end;
- // save audio parameters in sound file
- BlockWrite(audioFile, frequency, 4);
- BlockWrite(audioFile, channels, 4);
+ if cIsSoundEnabled then
+ begin
+ // save audio parameters in sound file
+ BlockWrite(audioFile, frequency, 4, result);
+ BlockWrite(audioFile, channels, 4, result);
{$IOCHECKS ON}
- // register callback for actual audio recording
- Mix_SetPostMix(@RecordPostMix, nil);
+ // register callback for actual audio recording
+ Mix_SetPostMix(@RecordPostMix, nil);
+ end;
startTime:= SDL_GetTicks();
flagPrerecording:= true;
@@ -315,12 +360,18 @@
AddFileLog('StopPreRecording');
flagPrerecording:= false;
- // call SDL_LockAudio because RecordPostMix may be executing right now
- SDL_LockAudio();
- Close(audioFile);
+ if cIsSoundEnabled then
+ begin
+ // call SDL_LockAudio because RecordPostMix may be executing right now
+ SDL_LockAudio();
+ Close(audioFile);
+ end;
Close(cameraFile);
- Mix_SetPostMix(nil, nil);
- SDL_UnlockAudio();
+ if cIsSoundEnabled then
+ begin
+ Mix_SetPostMix(nil, nil);
+ SDL_UnlockAudio();
+ end;
if not thumbnailSaved then
SaveThumbnail();
@@ -328,7 +379,9 @@
procedure SaveCameraPosition;
var frame: TFrame;
+ result: LongInt;
begin
+ result:= 0;
if (not thumbnailSaved) and (ScreenFade = sfNone) then
SaveThumbnail();
@@ -337,7 +390,7 @@
frame.CamX:= WorldDx;
frame.CamY:= WorldDy - cScreenHeight div 2;
frame.zoom:= zoom/cScreenWidth;
- BlockWrite(cameraFile, frame, 1);
+ BlockWrite(cameraFile, frame, 1, result);
end;
procedure initModule;
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uVisualGears.pas
--- a/hedgewars/uVisualGears.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uVisualGears.pas Wed Jul 31 23:14:27 2019 +0200
@@ -35,7 +35,7 @@
procedure freeModule;
procedure ProcessVisualGears(Steps: Longword);
-procedure DrawVisualGears(Layer: LongWord);
+procedure DrawVisualGears(Layer: LongWord; worldIsShifted: boolean);
procedure AddClouds;
procedure AddFlakes;
@@ -74,7 +74,7 @@
for i:= 0 to 6 do
begin
- t:= VisualGearLayers[i];
+ t:= VisualGearLayersStart[i];
while t <> nil do
begin
Gear:= t;
@@ -93,7 +93,7 @@
for i:= 2 to 6 do
if i <> 3 then
begin
- t:= VisualGearLayers[i];
+ t:= VisualGearLayersStart[i];
while t <> nil do
begin
Gear:= t;
@@ -138,7 +138,7 @@
exit(@SpritesData[GetSprite(sprite, SDsprite)]);
end;
-procedure DrawVisualGears(Layer: LongWord);
+procedure DrawVisualGears(Layer: LongWord; worldIsShifted: boolean);
var Gear: PVisualGear;
tinted, speedlessFlakes: boolean;
tmp: real;
@@ -154,7 +154,7 @@
case Layer of
// this layer is very distant in the background when stereo
0: begin
- Gear:= VisualGearLayers[0];
+ Gear:= VisualGearLayersStart[0];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then Tint(Gear^.Tint);
@@ -163,23 +163,24 @@
spriteData:= GetSpriteData(GetSpriteByWind(sprCloud, sprCloudL), GetSpriteByWind(sprSDCloud, sprSDCloudL));
DrawTextureF(spriteData^.Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height)
end;
- vgtFlake: begin
- sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
- if cFlattenFlakes then
+ vgtFlake: if (not worldIsShifted) then
begin
- if speedlessFlakes then
- DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
+ sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
+ if cFlattenFlakes then
+ begin
+ if speedlessFlakes then
+ DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
+ else
+ DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
+ end
else
- DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
- end
- else
- begin
- if speedlessFlakes then
- DrawTextureF(SpritesData[sprite].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
- else
- DrawTextureRotatedF(SpritesData[sprite].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle);
+ begin
+ if speedlessFlakes then
+ DrawTextureF(SpritesData[sprite].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
+ else
+ DrawTextureRotatedF(SpritesData[sprite].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle);
+ end;
end;
- end;
end;
if Gear^.Tint <> $FFFFFFFF then
untint;
@@ -188,20 +189,20 @@
end;
// this layer is on the land level (which is close but behind the screen plane) when stereo
1: begin
- Gear:= VisualGearLayers[1];
+ Gear:= VisualGearLayersStart[1];
while Gear <> nil do
begin
- //tinted:= false;
if Gear^.Tint <> $FFFFFFFF then
Tint(Gear^.Tint);
case Gear^.Kind of
- vgtFlake: begin
- sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
- if speedlessFlakes then
- DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
- else
- DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
- end;
+ vgtFlake: if (not worldIsShifted) then
+ begin
+ sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
+ if speedlessFlakes then
+ DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
+ else
+ DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
+ end;
vgtSmokeTrace: if Gear^.State < 8 then
DrawSprite(sprSmokeTrace, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.State);
vgtEvilTrace: if Gear^.State < 8 then
@@ -225,14 +226,13 @@
if Gear^.Angle <> 0 then
DrawTextureRotatedF(spriteData^.Texture, Gear^.scale, 0, 0, round(Gear^.X + WorldDx + (((spriteData^.Height+8)*Gear^.Scale)/2) * (Gear^.Angle / abs(Gear^.Angle))), round(Gear^.Y + WorldDy), 19 - (Gear^.FrameTicks div Gear^.Timer div 37), 1, spriteData^.Width, spriteData^.Height, Gear^.Angle)
else
- //DrawSprite(sprite, round(Gear^.X) + WorldDx - 40, round(Gear^.Y) + WorldDy - 58, 19 - (Gear^.FrameTicks div 37))
DrawTextureF(spriteData^.Texture, Gear^.scale, round(Gear^.X + WorldDx), round(Gear^.Y + WorldDy - ((spriteData^.Height+8)*Gear^.Scale)/2), 19 - (Gear^.FrameTicks div Gear^.Timer div 37), 1, spriteData^.Width, spriteData^.Height);
end;
vgtDroplet: begin
sprite:= GetSprite(sprDroplet, sprSDDroplet);
DrawSprite(sprite, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);
end;
- vgtBubble: DrawSprite(sprBubbles, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);//(RealTicks div 64 + Gear^.Frame) mod 8);
+ vgtBubble: DrawSprite(sprBubbles, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);
vgtStraightShot: begin
if Gear^.dX < 0 then
i:= -1
@@ -241,7 +241,6 @@
DrawTextureRotatedF(SpritesData[TSprite(Gear^.State)].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, i, SpritesData[TSprite(Gear^.State)].Width, SpritesData[TSprite(Gear^.State)].Height, Gear^.Angle);
end;
end;
- //if (Gear^.Tint <> $FFFFFFFF) or tinted then untint;
if (Gear^.Tint <> $FFFFFFFF) then
untint;
Gear:= Gear^.NextGear
@@ -249,21 +248,13 @@
end;
// this layer is on the screen plane (depth = 0) when stereo
3: begin
- Gear:= VisualGearLayers[3];
+ Gear:= VisualGearLayersStart[3];
while Gear <> nil do
begin
tinted:= false;
if Gear^.Tint <> $FFFFFFFF then
Tint(Gear^.Tint);
case Gear^.Kind of
-(*
- vgtFlake: begin
- sprite:= GetSprite(sprFlake, sprSDFlake);
- if speedlessFlakes then
- DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
- else
- DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
- end;*)
vgtSpeechBubble: if (Gear^.Angle <> 0) then
// ^ Before this gear renders, Angle must be set to mark it ready (e.g. coordinates properly initialized)
if (Gear^.Tex <> nil) and (((Gear^.State = 0) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Team <> CurrentTeam)) or (Gear^.State = 1)) then
@@ -316,7 +307,7 @@
end;
// this layer is outside the screen when stereo
2: begin
- Gear:= VisualGearLayers[2];
+ Gear:= VisualGearLayersStart[2];
while Gear <> nil do
begin
tinted:= false;
@@ -368,13 +359,14 @@
vgtBulletHit: DrawSpriteRotatedF(sprBulletHit, round(Gear^.X) + WorldDx - 0, round(Gear^.Y) + WorldDy - 0, 7 - (Gear^.FrameTicks div 50), 1, Gear^.Angle);
end;
case Gear^.Kind of
- vgtFlake: begin
- spriteData:= GetSpriteData(GetSpriteByWind(sprFlake, sprFlakeL), GetSpriteByWind(sprSDFlake, sprSDFlakeL));
- if speedlessFlakes then
- DrawTextureF(spriteData^.Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height)
- else
- DrawTextureRotatedF(spriteData^.Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height, Gear^.Angle);
- end;
+ vgtFlake: if (not worldIsShifted) then
+ begin
+ spriteData:= GetSpriteData(GetSpriteByWind(sprFlake, sprFlakeL), GetSpriteByWind(sprSDFlake, sprSDFlakeL));
+ if speedlessFlakes then
+ DrawTextureF(spriteData^.Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height)
+ else
+ DrawTextureRotatedF(spriteData^.Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height, Gear^.Angle);
+ end;
vgtCircle: if gear^.Angle = 1 then
begin
tmp:= Gear^.State / 100;
@@ -390,7 +382,7 @@
end;
// this layer is half-way between the screen plane (depth = 0) when in stereo, and the land
4: begin
- Gear:= VisualGearLayers[4];
+ Gear:= VisualGearLayersStart[4];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then
@@ -400,13 +392,14 @@
spriteData:= GetSpriteData(GetSpriteByWind(sprCloud, sprCloudL), GetSpriteByWind(sprSDCloud, sprSDCloudL));
DrawTextureF(spriteData^.Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height);
end;
- vgtFlake: begin
- spriteData:= GetSpriteData(GetSpriteByWind(sprFlake, sprFlakeL), GetSpriteByWind(sprSDFlake, sprSDFlakeL));
- if speedlessFlakes then
- DrawTextureF(spriteData^.Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height)
- else
- DrawTextureRotatedF(spriteData^.Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height, Gear^.Angle);
- end;
+ vgtFlake: if (not worldIsShifted) then
+ begin
+ spriteData:= GetSpriteData(GetSpriteByWind(sprFlake, sprFlakeL), GetSpriteByWind(sprSDFlake, sprSDFlakeL));
+ if speedlessFlakes then
+ DrawTextureF(spriteData^.Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height)
+ else
+ DrawTextureRotatedF(spriteData^.Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, spriteData^.Width, spriteData^.Height, Gear^.Angle);
+ end;
end;
if (Gear^.Tint <> $FFFFFFFF) then
untint;
@@ -415,7 +408,7 @@
end;
// this layer is on the screen plane (depth = 0) when stereo, but just behind the land
5: begin
- Gear:= VisualGearLayers[5];
+ Gear:= VisualGearLayersStart[5];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then
@@ -425,13 +418,14 @@
sprite:= GetSpriteByWind(GetSprite(sprCloud, sprSDCloud), GetSprite(sprCloudL, sprSDCloudL));
DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame);
end;
- vgtFlake: begin
- sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
- if speedlessFlakes then
- DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
- else
- DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
- end;
+ vgtFlake: if not (worldIsShifted) then
+ begin
+ sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
+ if speedlessFlakes then
+ DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
+ else
+ DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
+ end;
end;
if (Gear^.Tint <> $FFFFFFFF) then
untint;
@@ -440,19 +434,20 @@
end;
// this layer is on the screen plane (depth = 0) when stereo, but just in front of the land
6: begin
- Gear:= VisualGearLayers[6];
+ Gear:= VisualGearLayersStart[6];
while Gear <> nil do
begin
if Gear^.Tint <> $FFFFFFFF then
Tint(Gear^.Tint);
case Gear^.Kind of
- vgtFlake: begin
- sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
- if speedlessFlakes then
- DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
- else
- DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
- end;
+ vgtFlake: if (not worldIsShifted) then
+ begin
+ sprite:= GetSpriteByWind(GetSprite(sprFlake, sprSDFlake), GetSprite(sprFlakeL, sprSDFlakeL));
+ if speedlessFlakes then
+ DrawSprite(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
+ else
+ DrawSpriteRotatedF(sprite, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
+ end;
vgtNoPlaceWarn:
DrawTexture(round(Gear^.X) + WorldDx - round(Gear^.Tex^.w * Gear^.Scale) div 2, round(Gear^.Y) + WorldDy - round(Gear^.Tex^.h * Gear^.Scale) div 2, Gear^.Tex, Gear^.Scale);
end;
@@ -479,7 +474,7 @@
exit;
for i:= 0 to 6 do
begin
- vg:= VisualGearLayers[i];
+ vg:= VisualGearLayersStart[i];
while vg <> nil do
if vg^.Kind = vgtCloud then
begin
@@ -519,7 +514,7 @@
exit;
for i:= 0 to 6 do
begin
- vg:= VisualGearLayers[i];
+ vg:= VisualGearLayersStart[i];
while vg <> nil do
if vg^.Kind = vgtFlake then
begin
@@ -542,7 +537,10 @@
begin
VGCounter:= 0;
for i:= 0 to 6 do
- VisualGearLayers[i]:= nil;
+ begin
+ VisualGearLayersStart[i]:= nil;
+ VisualGearLayersEnd[i]:= nil;
+ end;
end;
procedure freeModule;
@@ -550,7 +548,7 @@
begin
VGCounter:= 0;
for i:= 0 to 6 do
- while VisualGearLayers[i] <> nil do DeleteVisualGear(VisualGearLayers[i]);
+ while VisualGearLayersStart[i] <> nil do DeleteVisualGear(VisualGearLayersStart[i]);
end;
end.
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uVisualGearsHandlers.pas
--- a/hedgewars/uVisualGearsHandlers.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uVisualGearsHandlers.pas Wed Jul 31 23:14:27 2019 +0200
@@ -246,7 +246,6 @@
Gear^.X:= Gear^.X + Gear^.dX * s;
Gear^.Y:= Gear^.Y + Gear^.dY * s;
-//Gear^.dY:= Gear^.dY + cGravityf;
if Gear^.FrameTicks <= Steps then
if Gear^.Frame = 0 then
@@ -317,7 +316,7 @@
begin
Gear^.X:= Gear^.X + Gear^.dX * Steps;
-Gear^.Y:= Gear^.Y + Gear^.dY * Steps;// + cGravityf * (Steps * Steps);
+Gear^.Y:= Gear^.Y + Gear^.dY * Steps;
if (Gear^.State and gstTmpFlag) = 0 then
begin
Gear^.dY:= Gear^.dY + cGravityf * Steps;
@@ -421,7 +420,6 @@
Gear^.Y:= Gear^.Y - (cDrownSpeedf + Gear^.dY) * Steps;
Gear^.dX := Gear^.dX + (cWindSpeedf * 0.3 * Steps);
-//Gear^.dY := Gear^.dY - (cDrownSpeedf * 0.995);
if Gear^.FrameTicks <= Steps then
if Gear^.Frame = 0 then
@@ -472,7 +470,7 @@
Gear^.Y:= Gear^.Y + Gear^.dY * Steps;
Gear^.dY:= Gear^.dY + cGravityf * Steps;
-if round(Gear^.Y) > cWaterLine then
+if CheckCoordInWater(round(Gear^.X), round(Gear^.Y)) then
begin
DeleteVisualGear(Gear);
PlaySound(TSound(ord(sndDroplet1) + Random(3)));
@@ -624,7 +622,6 @@
Gear^.Timer:= cSorterWorkTime;
Gear^.doStep:= @doStepTeamHealthSorterWork;
currsorter:= Gear;
-//doStepTeamHealthSorterWork(Gear, Steps)
end;
////////////////////////////////////////////////////////////////////////////////
@@ -709,8 +706,10 @@
if round(Gear^.Y) - 10 < cWaterLine then
DeleteVisualGear(Gear)
else
- Gear^.Y:= Gear^.Y - 0.08 * Steps;
-
+ begin
+ Gear^.X:= Gear^.X + Gear^.dX * Steps;
+ Gear^.Y:= Gear^.Y + Gear^.dY * Steps;
+ end;
end;
procedure doStepHealthTag(Gear: PVisualGear; Steps: Longword);
@@ -725,7 +724,7 @@
Gear^.doStep:= @doStepHealthTagWork;
-if (round(Gear^.Y) > cWaterLine) and (Gear^.Frame = 0) then
+if (round(Gear^.Y) > cWaterLine) and (Gear^.Frame = 0) and (Gear^.FrameTicks = 0) then
Gear^.doStep:= @doStepHealthTagWorkUnderWater;
Gear^.Y:= Gear^.Y - Gear^.Tex^.h;
@@ -849,9 +848,10 @@
Gear^.Angle:= round(Gear^.Angle + Steps) mod cMaxAngle;
-if (round(Gear^.Y) > cWaterLine) and ((cReducedQuality and rqPlainSplash) = 0) then
+if CheckCoordInWater(round(Gear^.X), round(Gear^.Y)) then
begin
- AddVisualGear(round(Gear^.X), round(Gear^.Y), vgtDroplet);
+ if ((cReducedQuality and rqPlainSplash) = 0) then
+ AddVisualGear(round(Gear^.X), round(Gear^.Y), vgtDroplet);
DeleteVisualGear(Gear);
end
end;
@@ -900,6 +900,7 @@
currwindbar: PVisualGear = nil;
procedure doStepSmoothWindBarWork(Gear: PVisualGear; Steps: Longword);
+const maxWindBarWidth = 73;
begin
if currwindbar = Gear then
begin
@@ -912,6 +913,11 @@
inc(WindBarWidth)
else if WindBarWidth > Gear^.Tag then
dec(WindBarWidth);
+ // Prevent wind bar from overflowing
+ if WindBarWidth > maxWindBarWidth then
+ WindBarWidth:= maxWindBarWidth;
+ if WindBarWidth < - maxWindBarWidth then
+ WindBarWidth:= - maxWindBarWidth;
end;
if cWindspeedf > Gear^.dAngle then
begin
@@ -925,7 +931,7 @@
end;
end;
- if ((WindBarWidth = Gear^.Tag) and (cWindspeedf = Gear^.dAngle)) or (currwindbar <> Gear) then
+ if (((WindBarWidth = Gear^.Tag) or (Abs(WindBarWidth) >= maxWindBarWidth)) and (cWindspeedf = Gear^.dAngle)) or (currwindbar <> Gear) then
begin
if currwindbar = Gear then currwindbar:= nil;
DeleteVisualGear(Gear)
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uVisualGearsList.pas
--- a/hedgewars/uVisualGearsList.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uVisualGearsList.pas Wed Jul 31 23:14:27 2019 +0200
@@ -33,7 +33,8 @@
cExplFrameTicks = 110;
var VGCounter: LongWord;
- VisualGearLayers: array[0..6] of PVisualGear;
+ VisualGearLayersStart: array[0..6] of PVisualGear;
+ VisualGearLayersEnd: array[0..6] of PVisualGear;
implementation
uses uCollisions, uFloat, uVariables, uConsts, uTextures, uVisualGearsHandlers, uScript;
@@ -76,6 +77,7 @@
vgtSpeechBubble,
vgtHealthTag,
vgtExplosion,
+ vgtBigExplosion,
vgtSmokeTrace,
vgtEvilTrace,
vgtNote,
@@ -231,9 +233,6 @@
end;
vgtDroplet:
begin
- // old dx & dy calcs
- // dx:= 0.001 * (random(180) - 90);
- // dy:= -0.001 * (random(160) + 40);
// => min speed ~ 0.098, max speed ~ 0.218, speed range ~ 0.120
// => min angle(4096) ~ 129, max angle ~ 1919, angle range ~ 1790
dx:= 0.001 * (98 + random(121)); // speed
@@ -276,10 +275,10 @@
vgtHealthTag:
begin
Frame:= 0;
+ FrameTicks:= 0;
Timer:= 1500;
dY:= -0.08;
dX:= 0;
- //gear^.Z:= 2002;
end;
vgtSmokeTrace,
vgtEvilTrace:
@@ -287,7 +286,6 @@
gear^.X:= gear^.X - 16;
gear^.Y:= gear^.Y - 16;
gear^.State:= 8;
- //gear^.Z:= cSmokeZ
end;
vgtBigExplosion:
begin
@@ -433,12 +431,15 @@
if Layer <> -1 then gear^.Layer:= Layer;
-if VisualGearLayers[gear^.Layer] <> nil then
+if VisualGearLayersStart[gear^.Layer] = nil then
+ VisualGearLayersStart[gear^.Layer]:= gear;
+
+if VisualGearLayersEnd[gear^.Layer] <> nil then
begin
- VisualGearLayers[gear^.Layer]^.PrevGear:= gear;
- gear^.NextGear:= VisualGearLayers[gear^.Layer]
+ VisualGearLayersEnd[gear^.Layer]^.NextGear:= gear;
+ gear^.PrevGear:= VisualGearLayersEnd[gear^.Layer]
end;
-VisualGearLayers[gear^.Layer]:= gear;
+VisualGearLayersEnd[gear^.Layer]:= gear;
AddVisualGear:= gear;
ScriptCall('onVisualGearAdd', gear^.uid);
@@ -449,12 +450,19 @@
ScriptCall('onVisualGearDelete', Gear^.uid);
FreeAndNilTexture(Gear^.Tex);
- if Gear^.NextGear <> nil then
- Gear^.NextGear^.PrevGear:= Gear^.PrevGear;
+ if (Gear^.NextGear = nil) and (Gear^.PrevGear = nil) then
+ begin
+ VisualGearLayersStart[Gear^.Layer]:= nil;
+ VisualGearLayersEnd[Gear^.Layer]:= nil;
+ end;
if Gear^.PrevGear <> nil then
Gear^.PrevGear^.NextGear:= Gear^.NextGear
- else
- VisualGearLayers[Gear^.Layer]:= Gear^.NextGear;
+ else if Gear^.NextGear <> nil then
+ VisualGearLayersStart[Gear^.Layer]:= Gear^.NextGear;
+ if Gear^.NextGear <> nil then
+ Gear^.NextGear^.PrevGear:= Gear^.PrevGear
+ else if Gear^.PrevGear <> nil then
+ VisualGearLayersEnd[Gear^.Layer]:= Gear^.PrevGear;
if lastVisualGearByUID = Gear then
lastVisualGearByUID:= nil;
@@ -477,7 +485,7 @@
// search in an order that is more likely to return layers they actually use. Could perhaps track statistically AddVisualGear in uScript, since that is most likely the ones they want
for i:= 2 to 5 do
begin
- vg:= VisualGearLayers[i mod 4];
+ vg:= VisualGearLayersStart[i mod 4];
while vg <> nil do
begin
if vg^.uid = uid then
diff -r 0135e64c6c66 -r c4fd2813b127 hedgewars/uWorld.pas
--- a/hedgewars/uWorld.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uWorld.pas Wed Jul 31 23:14:27 2019 +0200
@@ -31,8 +31,9 @@
procedure DrawWorld(Lag: LongInt);
procedure DrawWorldStereo(Lag: LongInt; RM: TRenderMode);
procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt);
+procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt; forceDisplay : boolean);
procedure HideMission;
-procedure SetAmmoTexts(ammoType: TAmmoType; name: ansistring; caption: ansistring; description: ansistring);
+procedure SetAmmoTexts(ammoType: TAmmoType; name: ansistring; caption: ansistring; description: ansistring; autoLabels: boolean);
procedure ShakeCamera(amount: LongInt);
procedure InitCameraBorders;
procedure InitTouchInterface;
@@ -41,6 +42,7 @@
procedure MoveCamera;
procedure onFocusStateChanged;
procedure updateCursorVisibility;
+procedure updateTouchWidgets(ammoType: TAmmoType);
implementation
uses
@@ -62,6 +64,7 @@
, uCommands
, uTeams
, uDebug
+ , uInputHandler
{$IFDEF USE_VIDEO_RECORDING}
, uVideoRec
{$ENDIF}
@@ -75,7 +78,7 @@
timeTexture: PTexture;
FPS: Longword;
CountTicks: Longword;
- prevPoint{, prevTargetPoint}: TPoint;
+ prevPoint: TPoint;
amSel: TAmmoType = amNothing;
missionTex: PTexture;
missionTimer: LongInt;
@@ -104,7 +107,6 @@
AMTypeMaskX = $00000001;
AMTypeMaskY = $00000002;
AMTypeMaskAlpha = $00000004;
- //AMTypeMaskSlide = $00000008;
{$IFDEF MOBILE}
AMSlotSize = 48;
@@ -113,6 +115,22 @@
{$ENDIF}
AMSlotPadding = (AMSlotSize - 32) shr 1;
+{$IFDEF USE_LANDSCAPE_AMMOMENU}
+ amNumOffsetX = 0;
+ {$IFDEF USE_AM_NUMCOLUMN}
+ amNumOffsetY = AMSlotSize;
+ {$ELSE}
+ amNumOffsetY = 0;
+ {$ENDIF}
+{$ELSE}
+ amNumOffsetY = 0;
+ {$IFDEF USE_AM_NUMCOLUMN}
+ amNumOffsetX = AMSlotSize;
+ {$ELSE}
+ amNumOffsetX = 0;
+ {$ENDIF}
+{$ENDIF}
+
cSendCursorPosTime = 50;
cCursorEdgesDist = 100;
@@ -120,22 +138,18 @@
function AddGoal(s: ansistring; gf: longword; si: TGoalStrId; i: LongInt): ansistring;
var t: ansistring;
begin
-{$IFNDEF PAS2C}
if (GameFlags and gf) <> 0 then
begin
t:= inttostr(i);
s:= s + FormatA(trgoal[si], t) + '|'
end;
-{$ENDIF}
AddGoal:= s;
end;
function AddGoal(s: ansistring; gf: longword; si: TGoalStrId): ansistring;
begin
-{$IFNDEF PAS2C}
if (GameFlags and gf) <> 0 then
s:= s + trgoal[si] + '|';
-{$ENDIF}
AddGoal:= s;
end;
@@ -158,15 +172,14 @@
ClansArray[t]:= cp;
ClansArray[t]^.ClanIndex:= t;
ClansArray[0]^.ClanIndex:= 0;
- if (LocalClan = t) then
- LocalClan:= 0
- else if (LocalClan = 0) then
- LocalClan:= t
end;
end;
CurrentTeam:= ClansArray[0]^.Teams[0];
end;
+if (GameFlags and gfInvulnerable) <> 0 then
+ cTagsMask:= cTagsMask and (not htHealth);
+
// if special game flags/settings are changed, add them to the game mode notice window and then show it
g:= ''; // no text/things to note yet
@@ -175,10 +188,10 @@
g:= LuaGoals + '|';
// check different game flags
-g:= AddGoal(g, gfPlaceHog, gidPlaceHog); // placement?
g:= AddGoal(g, gfKing, gidKing); // king?
if ((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0) then
g:= AddGoal(g, gfAny, gidPlaceKing);
+g:= AddGoal(g, gfPlaceHog, gidPlaceHog); // placement?
g:= AddGoal(g, gfTagTeam, gidTagTeam); // tag team mode?
g:= AddGoal(g, gfSharedAmmo, gidSharedAmmo); // shared ammo?
g:= AddGoal(g, gfPerHogAmmo, gidPerHogAmmo);
@@ -216,19 +229,21 @@
// if the string has been set, show it for (default timeframe) seconds
if length(g) > 0 then
- ShowMission(trgoal[gidCaption], trgoal[gidSubCaption], g, 1, 0);
+ // choose icon
+ if ((GameFlags and gfKing) <> 0) then
+ // crown icon for King Mode
+ ShowMission(trgoal[gidCaption], trgoal[gidSubCaption], g, 0, 0)
+ else
+ // target icon for anything else
+ ShowMission(trgoal[gidCaption], trgoal[gidSubCaption], g, 1, 0);
-//cWaveWidth:= SpritesData[sprWater].Width;
-//cWaveHeight:= SpritesData[sprWater].Height;
cWaveHeight:= 32;
InitCameraBorders();
uCursor.init();
prevPoint.X:= 0;
prevPoint.Y:= cScreenHeight div 2;
-//prevTargetPoint.X:= 0;
-//prevTargetPoint.Y:= 0;
-WorldDx:= -(LongInt(leftX + (playWidth div 2))); // -(LAND_WIDTH div 2);// + cScreenWidth div 2;
+WorldDx:= -(LongInt(leftX + (playWidth div 2)));
WorldDy:= -(LAND_HEIGHT - (playHeight div 2)) + (cScreenHeight div 2);
//aligns it to the bottom of the screen, minus the border
@@ -398,6 +413,28 @@
source.y:= frame.y;
end;
end;
+
+with utilityWidget2 do
+ begin
+ show:= false;
+ sprite:= sprBounceButton;
+ frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
+ frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
+ frame.x:= utilityWidget.frame.x + Round(frame.w * 1.25);
+ frame.y:= arrowLeft.frame.y - Round(frame.h * 1.25);
+ active.x:= frame.x;
+ active.y:= frame.y;
+ active.w:= frame.w;
+ active.h:= frame.h;
+ with moveAnim do
+ begin
+ target.x:= frame.x;
+ target.y:= frame.y;
+ source.x:= frame.x;
+ source.y:= frame.y;
+ end;
+ end;
+
{$ENDIF}
end;
@@ -418,7 +455,10 @@
STurns: LongInt;
amSurface: PSDL_Surface;
AMRect: TSDL_Rect;
-{$IFDEF USE_AM_NUMCOLUMN}tmpsurf: PSDL_Surface;{$ENDIF}
+{$IFDEF USE_AM_NUMCOLUMN}
+ tmpsurf: PSDL_Surface;
+ usesDefaultSlotKeys: boolean;
+{$ENDIF}
begin
if cOnlyStats then exit(nil);
@@ -454,6 +494,9 @@
x:= AMRect.x;
y:= AMRect.y;
+{$IFDEF USE_AM_NUMCOLUMN}
+ usesDefaultSlotKeys:= CheckDefaultSlotKeys;
+{$ENDIF USE_AM_NUMCOLUMN}
for i:= 0 to cMaxSlotIndex do
if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
begin
@@ -463,7 +506,13 @@
x:= AMRect.x;
{$ENDIF}
{$IFDEF USE_AM_NUMCOLUMN}
- tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F' + IntToStr(i+1)), cWhiteColorChannels);
+ // Ammo slot number column
+ if usesDefaultSlotKeys then
+ // F1, F2, F3, F4, ...
+ tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F'+IntToStr(i+1)), cWhiteColorChannels)
+ else
+ // 1, 2, 3, 4, ...
+ tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i+1)), cWhiteColorChannels);
copyToXY(tmpsurf, amSurface,
x + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.w shr 1),
y + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.h shr 1));
@@ -603,12 +652,16 @@
if AMState = AMShowingUp then // show ammo menu
begin
- if (cReducedQuality and rqSlowMenu) <> 0 then
+ // No "appear" animation in low quality or playing with very short turn time.
+ if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
begin
AMShiftX:= 0;
AMShiftY:= 0;
+ CursorPoint.X:= AmmoRect.x + AmmoRect.w - 3;
+ CursorPoint.Y:= cScreenHeight - AmmoRect.y - amNumOffsetY - 1;
AMState:= AMShowing;
end
+ // "Appear" animation
else
if AMAnimState < 1 then
begin
@@ -621,19 +674,22 @@
begin
AMShiftX:= 0;
AMShiftY:= 0;
- CursorPoint.X:= AmmoRect.x + AmmoRect.w;
- CursorPoint.Y:= AmmoRect.y;
+ CursorPoint.X:= AmmoRect.x + AmmoRect.w - 3;
+ CursorPoint.Y:= cScreenHeight - AmmoRect.y - amNumOffsetY - 1;
AMState:= AMShowing;
end;
end;
if AMState = AMHiding then // hide ammo menu
begin
- if (cReducedQuality and rqSlowMenu) <> 0 then
+ // No "disappear" animation (see above)
+ if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
begin
AMShiftX:= AMShiftTargetX;
AMShiftY:= AMShiftTargetY;
+ prevPoint:= CursorPoint;
AMState:= AMHidden;
end
+ // "Disappear" animation
else
if AMAnimState < 1 then
begin
@@ -647,7 +703,6 @@
AMShiftX:= AMShiftTargetX;
AMShiftY:= AMShiftTargetY;
prevPoint:= CursorPoint;
- //prevTargetPoint:= TargetCursorPoint;
AMState:= AMHidden;
end;
end;
@@ -736,7 +791,7 @@
Ammoz[Ammo^[Slot, Pos].AmmoType].NameTex);
if Ammo^[Slot, Pos].Count < AMMO_INFINITE then
DrawTexture(AmmoRect.x + AmmoRect.w - 20 - (CountTexz[Ammo^[Slot, Pos].Count]^.w),
- AmmoRect.y + AmmoRect.h - BORDERSIZE - (AMslotSize shr 1) - (CountTexz[Ammo^[Slot, Pos].Count]^.w shr 1),
+ AmmoRect.y + AmmoRect.h - BORDERSIZE - (AMslotSize shr 1) - (CountTexz[Ammo^[Slot, Pos].Count]^.h shr 1),
CountTexz[Ammo^[Slot, Pos].Count]);
if bSelected and (Ammoz[Ammo^[Slot, Pos].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber < 0) then
@@ -745,23 +800,7 @@
SetWeapon(Ammo^[Slot, Pos].AmmoType);
bSelected:= false;
FreeAndNilTexture(WeaponTooltipTex);
-{$IFDEF USE_TOUCH_INTERFACE}//show the aiming buttons + animation
- if (Ammo^[Slot, Pos].Propz and ammoprop_NeedUpDown) <> 0 then
- begin
- if (not arrowUp.show) then
- begin
- animateWidget(@arrowUp, true, true);
- animateWidget(@arrowDown, true, true);
- end;
- end
- else
- if arrowUp.show then
- begin
- animateWidget(@arrowUp, true, false);
- animateWidget(@arrowDown, true, false);
- end;
- SetUtilityWidgetState(Ammo^[Slot, Pos].AmmoType);
-{$ENDIF}
+ updateTouchWidgets(Ammo^[Slot, Pos].AmmoType);
exit
end;
end
@@ -883,10 +922,8 @@
procedure RenderWorldEdge;
var
- //VertexBuffer: array [0..3] of TVertex2f;
tmp, w: LongInt;
rect: TSDL_Rect;
- //c1, c2: LongWord; // couple of colours for edges
begin
if (WorldEdge <> weNone) and (WorldEdge <> weSea) then
begin
@@ -894,7 +931,7 @@
rect.y:= ViewTopY;
rect.h:= ViewHeight;
- tmp:= LongInt(leftX) + WorldDx;
+ tmp:= leftX + WorldDx;
w:= tmp - ViewLeftX;
if w > 0 then
@@ -906,7 +943,7 @@
DrawLineOnScreen(tmp - 1, ViewTopY, tmp - 1, ViewBottomY, 2, $54, $54, $FF, $FF);
end;
- tmp:= LongInt(rightX) + WorldDx;
+ tmp:= rightX + WorldDx;
w:= ViewRightX - tmp;
if w > 0 then
@@ -918,110 +955,6 @@
DrawLineOnScreen(tmp - 1, ViewTopY, tmp - 1, ViewBottomY, 2, $54, $54, $FF, $FF);
end;
- (*
- WARNING: the following render code is outdated and does not work with
- current Render.pas ! - don't just uncomment without fixing it first
-
- glDisable(GL_TEXTURE_2D);
- glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- if (WorldEdge = weWrap) or (worldEdge = weBounce) then
- glColor4ub($00, $00, $00, $40)
- else
- begin
- glEnableClientState(GL_COLOR_ARRAY);
- glColorPointer(4, GL_UNSIGNED_BYTE, 0, @WorldFade[0]);
- end;
-
- glPushMatrix;
- glTranslatef(WorldDx, WorldDy, 0);
-
- VertexBuffer[0].X:= leftX-20;
- VertexBuffer[0].Y:= -3500;
- VertexBuffer[1].X:= leftX-20;
- VertexBuffer[1].Y:= cWaterLine+cVisibleWater;
- VertexBuffer[2].X:= leftX+30;
- VertexBuffer[2].Y:= cWaterLine+cVisibleWater;
- VertexBuffer[3].X:= leftX+30;
- VertexBuffer[3].Y:= -3500;
-
- glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
- glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
-
- VertexBuffer[0].X:= rightX+20;
- VertexBuffer[1].X:= rightX+20;
- VertexBuffer[2].X:= rightX-30;
- VertexBuffer[3].X:= rightX-30;
-
- glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
- glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
-
- glColorPointer(4, GL_UNSIGNED_BYTE, 0, @WorldEnd[0]);
-
- VertexBuffer[0].X:= -5000;
- VertexBuffer[1].X:= -5000;
- VertexBuffer[2].X:= leftX-20;
- VertexBuffer[3].X:= leftX-20;
-
- glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
- glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
-
- VertexBuffer[0].X:= rightX+5000;
- VertexBuffer[1].X:= rightX+5000;
- VertexBuffer[2].X:= rightX+20;
- VertexBuffer[3].X:= rightX+20;
-
- glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
- glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
-
- glPopMatrix;
- glDisableClientState(GL_COLOR_ARRAY);
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-
- glColor4ub($FF, $FF, $FF, $FF); // must not be Tint() as color array seems to stay active and color reset is required
- glEnable(GL_TEXTURE_2D);
-
- // I'd still like to have things happen to the border when a wrap or bounce just occurred, based on a timer
- if WorldEdge = weBounce then
- begin
- // could maybe alternate order of these on a bounce, or maybe drop the outer ones.
- if LeftImpactTimer mod 2 = 0 then
- begin
- c1:= $5454FFFF; c2:= $FFFFFFFF;
- end
- else begin
- c1:= $FFFFFFFF; c2:= $5454FFFF;
- end;
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 7.0, c1);
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 5.0, c2);
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 3.0, c1);
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 1.0, c2);
- if RightImpactTimer mod 2 = 0 then
- begin
- c1:= $5454FFFF; c2:= $FFFFFFFF;
- end
- else begin
- c1:= $FFFFFFFF; c2:= $5454FFFF;
- end;
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 7.0, c1);
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 5.0, c2);
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 3.0, c1);
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 1.0, c2)
- end
- else if WorldEdge = weWrap then
- begin
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 5.0, $A0, $30, $60, max(50,255-LeftImpactTimer));
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 2.0, $FF0000FF);
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 5.0, $A0, $30, $60, max(50,255-RightImpactTimer));
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 2.0, $FF0000FF);
- end
- else
- begin
- DrawLine(leftX, -3000, leftX, cWaterLine+cVisibleWater, 5.0, $2E8B5780);
- DrawLine(rightX, -3000, rightX, cWaterLine+cVisibleWater, 5.0, $2E8B5780)
- end;
- if LeftImpactTimer > Lag then dec(LeftImpactTimer,Lag) else LeftImpactTimer:= 0;
- if RightImpactTimer > Lag then dec(RightImpactTimer,Lag) else RightImpactTimer:= 0
- *)
end;
end;
@@ -1075,25 +1008,29 @@
TeamHealthBarWidth:= cTeamHealthWidth * TeamHealthBarHealth div MaxTeamHealth;
- // draw health bar
+ // draw team health bar
r.x:= 0;
r.y:= 0;
r.w:= 2 + TeamHealthBarWidth;
r.h:= htex^.h;
DrawTextureFromRect(14, cScreenHeight + DrawHealthY + smallScreenOffset, @r, htex);
- // draw health bars right border
+ // draw health bar's right border
inc(r.x, cTeamHealthWidth + 2);
r.w:= 3;
DrawTextureFromRect(TeamHealthBarWidth + 15, cScreenHeight + DrawHealthY + smallScreenOffset, @r, htex);
+ // draw hedgehog health separators in team health bar
h:= 0;
if not hasGone then
for i:= 0 to cMaxHHIndex do
begin
inc(h, Hedgehogs[i].HealthBarHealth);
if (h < TeamHealthBarHealth) and (Hedgehogs[i].HealthBarHealth > 0) then
- DrawTexture(15 + h * TeamHealthBarWidth div TeamHealthBarHealth, cScreenHeight + DrawHealthY + smallScreenOffset + 1, SpritesData[sprSlider].Texture);
+ if (IsTooDarkToRead(Clan^.Color)) then
+ DrawTexture(15 + h * TeamHealthBarWidth div TeamHealthBarHealth, cScreenHeight + DrawHealthY + smallScreenOffset + 1, SpritesData[sprSlider].Texture)
+ else
+ DrawTexture(15 + h * TeamHealthBarWidth div TeamHealthBarHealth, cScreenHeight + DrawHealthY + smallScreenOffset + 1, SpritesData[sprSliderInverted].Texture);
end;
// draw Lua value, if set
@@ -1149,7 +1086,13 @@
h:= -NameTagTex^.w - 24;
if OwnerTex <> nil then
h:= h - OwnerTex^.w - 4;
+ if (IsTooDarkToRead(TeamsArray[t]^.Clan^.Color)) then
+ DrawSpriteRotatedF(sprFingerBackInv, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFingerBackInv].Width div 4, 0, 1, -90)
+ else
+ DrawSpriteRotatedF(sprFingerBack, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFingerBack].Width div 4, 0, 1, -90);
+ Tint(TeamsArray[t]^.Clan^.Color shl 8 or $FF);
DrawSpriteRotatedF(sprFinger, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFinger].Width div 4, 0, 1, -90);
+ untint;
end;
end;
end;
@@ -1162,6 +1105,24 @@
VisibleTeamsCount:= v;
end;
+procedure RenderAttackBar();
+var i: LongInt;
+ tdx, tdy: Double;
+begin
+ if CurrentTeam <> nil then
+ case AttackBar of
+ 2: with CurrentHedgehog^ do
+ begin
+ tdx:= hwSign(Gear^.dX) * Sin(Gear^.Angle * Pi / cMaxAngle);
+ tdy:= - Cos(Gear^.Angle * Pi / cMaxAngle);
+ for i:= (Gear^.Power * 24) div cPowerDivisor downto 0 do
+ DrawSprite(sprPower,
+ hwRound(Gear^.X) + GetLaunchX(CurAmmoType, hwSign(Gear^.dX), Gear^.Angle) + LongInt(round(WorldDx + tdx * (24 + i * 2))) - 16,
+ hwRound(Gear^.Y) + GetLaunchY(CurAmmoType, Gear^.Angle) + LongInt(round(WorldDy + tdy * (24 + i * 2))) - 16,
+ i)
+ end;
+ end;
+end;
var preShiftWorldDx: LongInt;
@@ -1181,10 +1142,9 @@
var i, t: LongInt;
spr: TSprite;
r: TSDL_Rect;
- tdx, tdy: Double;
s: shortstring;
offsetX, offsetY, screenBottom: LongInt;
- replicateToLeft, replicateToRight, tmp: boolean;
+ replicateToLeft, replicateToRight, isNotHiddenByCinematic: boolean;
{$IFDEF USE_VIDEO_RECORDING}
a: Byte;
{$ENDIF}
@@ -1196,8 +1156,8 @@
end
else
begin
- replicateToLeft := (LongInt(leftX) + WorldDx > ViewLeftX);
- replicateToRight:= (LongInt(rightX) + WorldDx < ViewRightX);
+ replicateToLeft := (leftX + WorldDx > ViewLeftX);
+ replicateToRight:= (rightX + WorldDx < ViewRightX);
end;
ScreenBottom:= (WorldDy - trunc(cScreenHeight/cScaleFactor) - (cScreenHeight div 2) + cWaterLine);
@@ -1205,6 +1165,7 @@
// note: offsetY is negative!
offsetY:= 10 * Min(0, -145 - ScreenBottom); // TODO limit this in the other direction too
+// Sky and horizont
if (cReducedQuality and rqNoBackground) = 0 then
begin
// Offsets relative to camera - spare them to wimpier cpus, no bg or flakes for them anyway
@@ -1224,9 +1185,9 @@
untint;
end;
-DrawVisualGears(0);
+DrawVisualGears(0, false);
ChangeDepth(RM, -cStereo_MidDistance);
-DrawVisualGears(4);
+DrawVisualGears(4, false);
if (cReducedQuality and rq2DWater) = 0 then
begin
@@ -1245,7 +1206,7 @@
DrawWaves(-1, 100, - cWaveHeight div 2, - cWaveHeight div 2, 0);
ChangeDepth(RM, cStereo_Land);
-DrawVisualGears(5);
+DrawVisualGears(5, false);
DrawLand(WorldDx, WorldDy);
if replicateToLeft then
@@ -1264,56 +1225,27 @@
DrawWater(255, 0, 0);
-(*
-// Attack bar
- if CurrentTeam <> nil then
- case AttackBar of
- //1: begin
- //r:= StuffPoz[sPowerBar];
- //{$WARNINGS OFF}
- //r.w:= (CurrentHedgehog^.Gear^.Power * 256) div cPowerDivisor;
- //{$WARNINGS ON}
- //DrawSpriteFromRect(r, cScreenWidth - 272, cScreenHeight - 48, 16, 0, Surface);
- //end;
- 2: with CurrentHedgehog^ do
- begin
- tdx:= hwSign(Gear^.dX) * Sin(Gear^.Angle * Pi / cMaxAngle);
- tdy:= - Cos(Gear^.Angle * Pi / cMaxAngle);
- for i:= (Gear^.Power * 24) div cPowerDivisor downto 0 do
- DrawSprite(sprPower,
- hwRound(Gear^.X) + GetLaunchX(CurAmmoType, hwSign(Gear^.dX), Gear^.Angle) + LongInt(round(WorldDx + tdx * (24 + i * 2))) - 16,
- hwRound(Gear^.Y) + GetLaunchY(CurAmmoType, Gear^.Angle) + LongInt(round(WorldDy + tdy * (24 + i * 2))) - 16,
- i)
- end
- end;
-*)
-
-tmp:= bShowFinger;
-bShowFinger:= false;
-
if replicateToLeft then
begin
ShiftWorld(-1);
- DrawVisualGears(1);
+ DrawVisualGears(1, true);
DrawGears();
- DrawVisualGears(6);
+ DrawVisualGears(6, true);
UnshiftWorld();
end;
if replicateToRight then
begin
ShiftWorld(1);
- DrawVisualGears(1);
+ DrawVisualGears(1, true);
DrawGears();
- DrawVisualGears(6);
+ DrawVisualGears(6, true);
UnshiftWorld();
end;
-bShowFinger:= tmp;
-
-DrawVisualGears(1);
+DrawVisualGears(1, false);
DrawGears;
-DrawVisualGears(6);
+DrawVisualGears(6, false);
if SuddenDeathDmg then
@@ -1321,13 +1253,12 @@
else
DrawWater(WaterOpacity, 0, 0);
- // Waves
+// Waves
ChangeDepth(RM, cStereo_Water_near);
DrawWaves( 1, 25 - WorldDx div 9, 0, 0, 12);
if (cReducedQuality and rq2DWater) = 0 then
begin
- //DrawWater(WaterOpacity, - offsetY div 40);
ChangeDepth(RM, cStereo_Water_near);
DrawWaves(-1, 50 + WorldDx div 6, - offsetY div 40, 23, 8);
if SuddenDeathDmg then
@@ -1346,7 +1277,27 @@
else
DrawWaves(-1, 50, cWaveHeight div 2, cWaveHeight div 2, 0);
-DrawGearsTimers;
+// line at airplane height for certain airstrike types (when spawning height is important)
+with CurrentHedgehog^ do
+ if (isCursorVisible) and ((CurAmmoType = amNapalm) or (CurAmmoType = amMineStrike) or (((GameFlags and gfMoreWind) <> 0) and ((CurAmmoType = amDrillStrike) or (CurAmmoType = amAirAttack)))) then
+ DrawLine(-3000, topY-300, 7000, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF);
+
+// gear HUD extras (fuel indicator, secondary ammo, etc.)
+if replicateToLeft then
+ begin
+ ShiftWorld(-1);
+ DrawGearsGui();
+ UnshiftWorld();
+ end;
+
+if replicateToRight then
+ begin
+ ShiftWorld(1);
+ DrawGearsGui();
+ UnshiftWorld();
+ end;
+
+DrawGearsGui();
// everything after this ChangeDepth will be drawn outside the screen
// note: negative parallax gears should last very little for a smooth stereo effect
@@ -1355,18 +1306,18 @@
if replicateToLeft then
begin
ShiftWorld(-1);
- DrawVisualGears(2);
+ DrawVisualGears(2, true);
UnshiftWorld();
end;
if replicateToRight then
begin
ShiftWorld(1);
- DrawVisualGears(2);
+ DrawVisualGears(2, true);
UnshiftWorld();
end;
- DrawVisualGears(2);
+ DrawVisualGears(2, false);
// everything after this ResetDepth will be drawn at screen level (depth = 0)
// note: everything that needs to be readable should be on this level
@@ -1375,39 +1326,101 @@
if replicateToLeft then
begin
ShiftWorld(-1);
- DrawVisualGears(3);
+ DrawVisualGears(3, true);
UnshiftWorld();
end;
if replicateToRight then
begin
ShiftWorld(1);
- DrawVisualGears(3);
+ DrawVisualGears(3, true);
UnshiftWorld();
end;
- DrawVisualGears(3);
+ DrawVisualGears(3, false);
-{$WARNINGS OFF}
-// Target
+// Target (e.g. air attack, bee, ...)
if (TargetPoint.X <> NoPointX) and (CurrentTeam <> nil) and (CurrentHedgehog <> nil) then
begin
with PHedgehog(CurrentHedgehog)^ do
begin
if CurAmmoType = amBee then
- DrawSpriteRotatedF(sprTargetBee, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
+ spr:= sprTargetBee
else
- DrawSpriteRotatedF(sprTargetP, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
- end
+ spr:= sprTargetP;
+ if replicateToLeft then
+ begin
+ ShiftWorld(-1);
+ if spr = sprTargetP then
+ begin
+ if IsTooDarkToRead(Team^.Clan^.Color) then
+ DrawSpriteRotatedF(sprTargetPBackInv, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
+ else
+ DrawSpriteRotatedF(sprTargetPBack, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ Tint(Team^.Clan^.Color shl 8 or $FF);
+ end;
+ DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ if spr = sprTargetP then
+ untint;
+ UnshiftWorld();
+ end;
+
+ if replicateToRight then
+ begin
+ ShiftWorld(1);
+ if spr = sprTargetP then
+ begin
+ if IsTooDarkToRead(Team^.Clan^.Color) then
+ DrawSpriteRotatedF(sprTargetPBackInv, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
+ else
+ DrawSpriteRotatedF(sprTargetPBack, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ Tint(Team^.Clan^.Color shl 8 or $FF);
+ end;
+ DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ if spr = sprTargetP then
+ untint;
+ UnshiftWorld();
+ end;
+
+ if spr = sprTargetP then
+ begin
+ if IsTooDarkToRead(Team^.Clan^.Color) then
+ DrawSpriteRotatedF(sprTargetPBackInv, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
+ else
+ DrawSpriteRotatedF(sprTargetPBack, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ Tint(Team^.Clan^.Color shl 8 or $FF);
+ end;
+ DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
+ if spr = sprTargetP then
+ untint;
+ end;
end;
-{$WARNINGS ON}
+
+// Attack bar
+if replicateToLeft then
+ begin
+ ShiftWorld(-1);
+ RenderAttackBar();
+ UnshiftWorld();
+ end;
+if replicateToRight then
+ begin
+ ShiftWorld(1);
+ RenderAttackBar();
+ UnshiftWorld();
+ end;
+
+RenderAttackBar();
+
+// World edge
RenderWorldEdge();
-// this scale is used to keep the various widgets at the same dimension at all zoom levels
+// This scale is used to keep the various widgets at the same dimension at all zoom levels
SetScale(cDefaultZoomLevel);
-// cinematic effects
+isNotHiddenByCinematic:= true;
+// Cinematic Mode: Determine effects and state
if CinematicScript or (InCinematicMode and autoCameraOn
and ((CurrentHedgehog = nil) or CurrentHedgehog^.Team^.ExtDriven
or (CurrentHedgehog^.BotLevel <> 0) or (GameType = gmtDemo))) then
@@ -1416,7 +1429,10 @@
begin
inc(CinematicSteps, Lag);
if CinematicSteps > 300 then
- CinematicSteps:= 300;
+ begin
+ CinematicSteps:= 300;
+ isNotHiddenByCinematic:= false;
+ end;
end;
end
else if CinematicSteps > 0 then
@@ -1426,22 +1442,8 @@
CinematicSteps:= 0;
end;
-// render black bars
-if CinematicSteps > 0 then
- begin
- r.x:= ViewLeftX;
- r.w:= ViewWidth;
- r.y:= ViewTopY;
- CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
- r.h:= CinematicBarH;
- DrawRect(r, 0, 0, 0, $FF, true);
- r.y:= ViewBottomY - r.h;
- DrawRect(r, 0, 0, 0, $FF, true);
- end;
-
-
// Turn time
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
begin
{$IFDEF USE_TOUCH_INTERFACE}
offsetX:= cScreenHeight - 13;
@@ -1480,33 +1482,79 @@
DrawSprite(sprFrame, -(cScreenWidth shr 1) + t - 4 + offsetY, cScreenHeight - offsetX, 0);
end;
-// Captions
- DrawCaptions
end;
+// Team bars
+if (UIDisplay = uiAll) and (isNotHiddenByCinematic) then
+ RenderTeamsHealth;
+
+// Current hedgehog health in top left corner
+if ((UIDisplay = uiAll) or (UIDisplay = uiNoTeams)) and (isNotHiddenByCinematic) and
+ (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and
+ (CurrentHedgehog^.HealthTagTex <> nil) and
+ ((CurrentHedgehog^.Gear^.State and gstHHDriven) <> 0) then
+ begin
+ t:= 11;
+ i:= t;
{$IFDEF USE_TOUCH_INTERFACE}
-// Draw buttons Related to the Touch interface
-DrawScreenWidget(@arrowLeft);
-DrawScreenWidget(@arrowRight);
-DrawScreenWidget(@arrowUp);
-DrawScreenWidget(@arrowDown);
-
-DrawScreenWidget(@fireButton);
-DrawScreenWidget(@jumpWidget);
-DrawScreenWidget(@AMWidget);
-DrawScreenWidget(@pauseButton);
-DrawScreenWidget(@utilityWidget);
+ i:= t + pauseButton.frame.y + pauseButton.frame.h;
{$ENDIF}
-if UIDisplay = uiAll then
- RenderTeamsHealth;
+ // Hide health and healh icons in gfInvulnerable mode (except heResurrectable)
+ if ((GameFlags and gfInvulnerable) = 0) then
+ begin
+ // Health tag
+ DrawTexture(cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - 16, i, CurrentHedgehog^.HealthTagTex);
+ inc(t, CurrentHedgehog^.HealthTagTex^.h);
+ cDemoClockFPSOffsetY:= t;
+
+ t:= SpritesData[sprHealthHud].Width + 18;
+ // Main health icon. Appearance depends on game mode and poisoning state
+ if ((GameFlags and gfResetHealth) = 0) then
+ if (CurrentHedgehog^.Effects[hePoisoned] <> 0) then
+ DrawSprite(sprHealthPoisonHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
+ else
+ DrawSprite(sprHealthHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
+ else
+ if (CurrentHedgehog^.Effects[hePoisoned] <> 0) then
+ DrawSprite(sprMedicPoisonHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
+ else
+ DrawSprite(sprMedicHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0);
+ // Put halo above health icon for resurrectable hog
+ if (CurrentHedgehog^.Effects[heResurrectable] <> 0) then
+ DrawSprite(sprHaloHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t - 2), i - SpritesData[sprHaloHud].Height + 1, 0);
-// Lag alert
-if isInLag then
- DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
+ // Additional health-related states
+ inc(t, 2);
+ // Invulnerable
+ if (CurrentHedgehog^.Effects[heInvulnerable] <> 0) then
+ begin
+ inc(t, SpritesData[sprInvulnHud].Width + 2);
+ DrawSprite(sprInvulnHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
+ end
+ // Karma
+ else if ((GameFlags and gfKarma) <> 0) then
+ begin
+ inc(t, SpritesData[sprKarmaHud].Width + 2);
+ DrawSprite(sprKarmaHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
+ end;
+ // Vampirism
+ if cVampiric then
+ begin
+ inc(t, SpritesData[sprVampHud].Width + 2);
+ DrawSprite(sprVampHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0);
+ end;
+ end
+ // in gfInvulnerable mode ...
+ else if (CurrentHedgehog^.Effects[heResurrectable] <> 0) then
+ // show halo for resurrectable hog
+ DrawSprite(sprHaloHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t - 2), i, 0);
+ end
+else
+ cDemoClockFPSOffsetY:= 0;
// Wind bar
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
begin
{$IFDEF USE_TOUCH_INTERFACE}
offsetX:= cScreenHeight - 13;
@@ -1519,7 +1567,7 @@
if WindBarWidth > 0 then
begin
{$WARNINGS OFF}
- r.x:= 8 - (RealTicks shr 6) mod 8;
+ r.x:= 8 - (RealTicks shr 6) mod 9;
{$WARNINGS ON}
r.y:= 0;
r.w:= WindBarWidth;
@@ -1530,7 +1578,7 @@
if WindBarWidth < 0 then
begin
{$WARNINGS OFF}
- r.x:= (Longword(WindBarWidth) + RealTicks shr 6) mod 8;
+ r.x:= (Longword(WindBarWidth) + RealTicks shr 6) mod 9;
{$WARNINGS ON}
r.y:= 0;
r.w:= - WindBarWidth;
@@ -1539,28 +1587,97 @@
end
end;
-{$IFNDEF USE_TOUCH_INTERFACE}
// Indicators for global effects (extra damage, low gravity)
-// TODO: Add support for touch interface (need to find out correct offset)
-if UIDisplay <> uiNone then
+if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
begin
+{$IFDEF USE_TOUCH_INTERFACE}
+ offsetX:= (cScreenWidth shr 1) - 95;
+ offsetY:= cScreenHeight - 21;
+{$ELSE}
offsetX:= 45;
offsetY:= 51;
+{$ENDIF}
if cDamageModifier = _1_5 then
begin
DrawTextureF(ropeIconTex, 1, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, 0, 1, 32, 32);
DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.90, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, ord(amExtraDamage) - 1, 1, 32, 32);
+{$IFDEF USE_TOUCH_INTERFACE}
+ offsetX := offsetX - 33
+{$ELSE}
offsetX := offsetX + 33
+{$ENDIF}
end;
if (cLowGravity) or ((GameFlags and gfLowGravity) <> 0) then
begin
DrawTextureF(ropeIconTex, 1, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, 0, 1, 32, 32);
DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.90, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, ord(amLowGravity) - 1, 1, 32, 32);
+{$IFDEF USE_TOUCH_INTERFACE}
+ offsetX := offsetX - 33
+{$ELSE}
+ offsetX := offsetX + 33
+{$ENDIF}
+ end;
+ if cLaserSighting then
+ begin
+ DrawTextureF(ropeIconTex, 1, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, 0, 1, 32, 32);
+ DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.90, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, ord(amLaserSight) - 1, 1, 32, 32);
end;
end;
+
+// Cinematic Mode: Render black bars
+if CinematicSteps > 0 then
+ begin
+ r.x:= ViewLeftX;
+ r.w:= ViewWidth;
+ r.y:= ViewTopY;
+ CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
+ r.h:= CinematicBarH;
+ DrawRect(r, 0, 0, 0, $FF, true);
+ r.y:= ViewBottomY - r.h;
+ DrawRect(r, 0, 0, 0, $FF, true);
+ end;
+
+// Touchscreen interface widgets
+{$IFDEF USE_TOUCH_INTERFACE}
+DrawScreenWidget(@arrowLeft);
+DrawScreenWidget(@arrowRight);
+DrawScreenWidget(@arrowUp);
+DrawScreenWidget(@arrowDown);
+
+DrawScreenWidget(@fireButton);
+DrawScreenWidget(@jumpWidget);
+DrawScreenWidget(@AMWidget);
+DrawScreenWidget(@utilityWidget);
+DrawScreenWidget(@utilityWidget2);
+DrawScreenWidget(@pauseButton);
{$ENDIF}
+// Captions
+if UIDisplay <> uiNone then
+ DrawCaptions;
+
+// Lag alert
+if isInLag then
+ DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
+
+// Chat
+DrawChat;
+
+
+// Mission panel
+if not isFirstFrame and (missionTimer <> 0) or isShowMission or isPaused or fastUntilLag or (GameState = gsConfirm) then
+ begin
+ if (ReadyTimeLeft = 0) and (missionTimer > 0) then
+ dec(missionTimer, Lag);
+ if missionTimer < 0 then
+ missionTimer:= 0; // avoid subtracting below 0
+ if missionTex <> nil then
+ DrawTextureCentered(0, Min((cScreenHeight shr 1) + 100, cScreenHeight - 48 - missionTex^.h), missionTex);
+ end;
+if missionTimer = 0 then
+ isForceMission := false;
+
// AmmoMenu
if bShowAmmoMenu and ((AMState = AMHidden) or (AMState = AMHiding)) then
begin
@@ -1582,44 +1699,33 @@
if bShowAmmoMenu or (AMState = AMHiding) then
ShowAmmoMenu;
+// Centered status/menu messages (synchronizing, auto skip, pause, etc.)
+if fastUntilLag then
+ DrawTextureCentered(0, (cScreenHeight shr 1), SyncTexture)
+else if isAFK then
+ DrawTextureCentered(0, (cScreenHeight shr 1), AFKTexture)
+else if isPaused then
+ DrawTextureCentered(0, (cScreenHeight shr 1), PauseTexture);
+
// Cursor
if isCursorVisible and bShowAmmoMenu then
DrawSprite(sprArrow, CursorPoint.X, cScreenHeight - CursorPoint.Y, (RealTicks shr 6) mod 8);
-// Chat
-DrawChat;
-
-
-// various captions
-if fastUntilLag then
- DrawTextureCentered(0, (cScreenHeight shr 1), SyncTexture);
-if isPaused then
- DrawTextureCentered(0, (cScreenHeight shr 1), PauseTexture);
-if isAFK then
- DrawTextureCentered(0, (cScreenHeight shr 1), AFKTexture);
-if not isFirstFrame and (missionTimer <> 0) or isPaused or fastUntilLag or (GameState = gsConfirm) then
- begin
- if (ReadyTimeLeft = 0) and (missionTimer > 0) then
- dec(missionTimer, Lag);
- if missionTimer < 0 then
- missionTimer:= 0; // avoid subtracting below 0
- if missionTex <> nil then
- DrawTextureCentered(0, Min((cScreenHeight shr 1) + 100, cScreenHeight - 48 - missionTex^.h), missionTex);
- end;
-
-// fps
+// FPS and demo replay time
{$IFDEF USE_TOUCH_INTERFACE}
-offsetX:= pauseButton.frame.y + pauseButton.frame.h + 12;
+offsetY:= cDemoClockFPSOffsetY + 10 + pauseButton.frame.y + pauseButton.frame.h;
{$ELSE}
-offsetX:= 10;
+offsetY:= cDemoClockFPSOffsetY + 10;
{$ENDIF}
-offsetY:= cOffsetY;
+offsetX:= cOffsetY;
if (RM = rmDefault) or (RM = rmRightEye) then
begin
inc(Frames);
if cShowFPS or (GameType = gmtDemo) then
inc(CountTicks, Lag);
+
+ // Demo replay time
if (GameType = gmtDemo) and (CountTicks >= 1000) then
begin
i:= GameTicks div 1000;
@@ -1642,29 +1748,36 @@
SDL_FreeSurface(tmpSurface)
end;
- if timeTexture <> nil then
- DrawTexture((cScreenWidth shr 1) - 20 - timeTexture^.w - offsetY, offsetX + timeTexture^.h+5, timeTexture);
+ if (timeTexture <> nil) and (UIDisplay <> uiNone) then
+ DrawTexture((cScreenWidth shr 1) - 20 - timeTexture^.w - offsetX, offsetY, timeTexture);
- if cShowFPS then
+ // FPS counter
+ if cShowFPS and (UIDisplay <> uiNone) then
begin
if CountTicks >= 1000 then
begin
FPS:= Frames;
Frames:= 0;
CountTicks:= 0;
- s:= Format(trmsg[sidFPS], inttostr(FPS));
- tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(s), cWhiteColorChannels);
+ s:= Format(shortstring(trmsg[sidFPS]), inttostr(FPS));
+ tmpSurface:= TTF_RenderUTF8_Blended(Fontz[CheckCJKFont(trmsg[sidFPS],fnt16)].Handle, Str2PChar(s), cWhiteColorChannels);
tmpSurface:= doSurfaceConversion(tmpSurface);
FreeAndNilTexture(fpsTexture);
fpsTexture:= Surface2Tex(tmpSurface, false);
SDL_FreeSurface(tmpSurface)
end;
if fpsTexture <> nil then
- DrawTexture((cScreenWidth shr 1) - 60 - offsetY, offsetX, fpsTexture);
+ begin
+ if timeTexture <> nil then
+ i:= fpsTexture^.h + 5
+ else
+ i:= 0;
+ DrawTexture((cScreenWidth shr 1) - 20 - fpsTexture^.w - offsetX, offsetY + i, fpsTexture);
+ end;
end;
end;
-
+// Quit Y/N question
if GameState = gsConfirm then
DrawTextureCentered(0, (cScreenHeight shr 1)-40, ConfirmTexture);
@@ -1699,7 +1812,7 @@
end;
{$IFDEF USE_VIDEO_RECORDING}
-// during video prerecording draw red blinking circle and text 'rec'
+// During video prerecording draw red blinking circle and text 'rec'
if flagPrerecording then
begin
if recTexture = nil then
@@ -1713,8 +1826,8 @@
end;
DrawTexture( -(cScreenWidth shr 1) + 50, 20, recTexture);
- //a:= Byte(Round(127*(1 + sin(RealTicks*0.007))));
- a:= Byte(min(255, abs(-255 + ((RealTicks div 2) and 511))));
+ t:= -255 + ((RealTicks div 2) and 511);
+ a:= Byte(min(255, abs(t)));
// draw red circle
DrawCircleFilled(-(cScreenWidth shr 1) + 30, 35, 10, $FF, $00, $00, a);
@@ -1723,29 +1836,6 @@
SetScale(zoom);
-// Attack bar
- if CurrentTeam <> nil then
- case AttackBar of
-(* 1: begin
- r:= StuffPoz[sPowerBar];
- {$WARNINGS OFF}
- r.w:= (CurrentHedgehog^.Gear^.Power * 256) div cPowerDivisor;
- {$WARNINGS ON}
- DrawSpriteFromRect(r, cScreenWidth - 272, cScreenHeight - 48, 16, 0, Surface);
- end;*)
- 2: with CurrentHedgehog^ do
- begin
- tdx:= hwSign(Gear^.dX) * Sin(Gear^.Angle * Pi / cMaxAngle);
- tdy:= - Cos(Gear^.Angle * Pi / cMaxAngle);
- for i:= (Gear^.Power * 24) div cPowerDivisor downto 0 do
- DrawSprite(sprPower,
- hwRound(Gear^.X) + GetLaunchX(CurAmmoType, hwSign(Gear^.dX), Gear^.Angle) + LongInt(round(WorldDx + tdx * (24 + i * 2))) - 16,
- hwRound(Gear^.Y) + GetLaunchY(CurAmmoType, Gear^.Angle) + LongInt(round(WorldDy + tdy * (24 + i * 2))) - 16,
- i)
- end
- end;
-
-
// Cursor
if isCursorVisible and (not bShowAmmoMenu) then
begin
@@ -1753,11 +1843,9 @@
with CurrentHedgehog^ do
if (Gear <> nil) and ((Gear^.State and gstChooseTarget) <> 0) then
begin
- if (CurAmmoType = amNapalm) or (CurAmmoType = amMineStrike) or (((GameFlags and gfMoreWind) <> 0) and ((CurAmmoType = amDrillStrike) or (CurAmmoType = amAirAttack))) then
- DrawLine(-3000, topY-300, 7000, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF);
i:= GetCurAmmoEntry(CurrentHedgehog^)^.Pos;
with Ammoz[CurAmmoType] do
- if PosCount > 1 then
+ if PosCount > 0 then
begin
if (CurAmmoType = amGirder) or (CurAmmoType = amTeleport) then
begin
@@ -1769,9 +1857,15 @@
end;
DrawSprite(PosSprite, TargetCursorPoint.X - (SpritesData[PosSprite].Width shr 1), cScreenHeight - TargetCursorPoint.Y - (SpritesData[PosSprite].Height shr 1),i);
Untint();
+ if (WorldEdge = weWrap) and (CurAmmoType = amBee) then
+ begin
+ if (TargetCursorPoint.X - WorldDx > rightX) then
+ DrawSprite(sprThroughWrap, TargetCursorPoint.X - (SpritesData[sprThroughWrap].Width shr 1), cScreenHeight - TargetCursorPoint.Y - (SpritesData[PosSprite].Height shr 1) - SpritesData[sprThroughWrap].Height - 2, 0)
+ else if (TargetCursorPoint.X - WorldDx < leftX) then
+ DrawSprite(sprThroughWrap, TargetCursorPoint.X - (SpritesData[sprThroughWrap].Width shr 1), cScreenHeight - TargetCursorPoint.Y - (SpritesData[PosSprite].Height shr 1) - SpritesData[sprThroughWrap].Height - 2, 1);
+ end;
end;
end;
- //DrawSprite(sprArrow, TargetCursorPoint.X, cScreenHeight - TargetCursorPoint.Y, (RealTicks shr 6) mod 8)
DrawTextureF(SpritesData[sprArrow].Texture, cDefaultZoomLevel / cScaleFactor, TargetCursorPoint.X + round(SpritesData[sprArrow].Width / cScaleFactor), cScreenHeight + round(SpritesData[sprArrow].Height / cScaleFactor) - TargetCursorPoint.Y, (RealTicks shr 6) mod 8, 1, SpritesData[sprArrow].Width, SpritesData[sprArrow].Height);
end;
@@ -1791,7 +1885,7 @@
var PrevSentPointTime: LongWord = 0;
procedure MoveCamera;
-var EdgesDist, wdy, shs,z, amNumOffsetX, amNumOffsetY, dstX: LongInt;
+var EdgesDist, wdy, shs,z, dstX: LongInt;
inbtwnTrgtAttks: Boolean;
begin
{$IFNDEF MOBILE}
@@ -1813,9 +1907,9 @@
if (WorldEdge = weWrap) then
begin
- if dstX - prevPoint.X < (LongInt(leftX) - rightX) div 2 then
+ if dstX - prevPoint.X < (leftX - rightX) div 2 then
CursorPoint.X:= (prevPoint.X * 7 + dstX - (leftX - rightX)) div 8
- else if dstX - prevPoint.X > (LongInt(rightX) - leftX) div 2 then
+ else if dstX - prevPoint.X > (rightX - leftX) div 2 then
CursorPoint.X:= (prevPoint.X * 7 + dstX - (rightX - leftX)) div 8
else
CursorPoint.X:= (prevPoint.X * 7 + dstX) div 8;
@@ -1834,9 +1928,9 @@
if (WorldEdge = weWrap) then
begin
if -WorldDx < leftX then
- WorldDx:= WorldDx - LongInt(rightX) + leftX
+ WorldDx:= WorldDx - rightX + leftX
else if -WorldDx > rightX then
- WorldDx:= WorldDx + LongInt(rightX) - leftX;
+ WorldDx:= WorldDx + rightX - leftX;
end;
wdy:= trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - cWaterLine - (cVisibleWater + trunc(CinematicBarH / (cScaleFactor / 2.0)));
@@ -1848,22 +1942,6 @@
if (AMState = AMShowingUp) or (AMState = AMShowing) then
begin
-{$IFDEF USE_LANDSCAPE_AMMOMENU}
- amNumOffsetX:= 0;
- {$IFDEF USE_AM_NUMCOLUMN}
- amNumOffsetY:= AMSlotSize;
- {$ELSE}
- amNumOffsetY:= 0;
- {$ENDIF}
-{$ELSE}
- amNumOffsetY:= 0;
- {$IFDEF USE_AM_NUMCOLUMN}
- amNumOffsetX:= AMSlotSize;
- {$ELSE}
- amNumOffsetX:= 0;
- {$ENDIF}
-
-{$ENDIF}
if CursorPoint.X < AmmoRect.x + amNumOffsetX + 3 then//check left
CursorPoint.X:= AmmoRect.x + amNumOffsetX + 3;
if CursorPoint.X > AmmoRect.x + AmmoRect.w - 3 then//check right
@@ -1873,7 +1951,6 @@
if CursorPoint.Y < cScreenHeight - (AmmoRect.y + AmmoRect.h - AMSlotSize - 5) then//check bottom
CursorPoint.Y:= cScreenHeight - (AmmoRect.y + AmmoRect.h - AMSlotSize - 5);
prevPoint:= CursorPoint;
- //if cHasFocus then SDL_WarpMouse(CursorPoint.X + cScreenWidth div 2, cScreenHeight - CursorPoint.Y);
exit
end;
@@ -1929,7 +2006,6 @@
// this moves the camera according to CursorPoint X and Y
prevPoint:= CursorPoint;
-//if cHasFocus then SDL_WarpMouse(CursorPoint.X + (cScreenWidth shr 1), cScreenHeight - CursorPoint.Y);
if WorldDy > LAND_HEIGHT + 1024 then
WorldDy:= LAND_HEIGHT + 1024;
if WorldDy < wdy then
@@ -1941,6 +2017,11 @@
end;
procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt);
+begin
+ ShowMission(caption, subcaption, text, icon, time, false);
+end;
+
+procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt; forceDisplay : boolean);
var r: TSDL_Rect;
begin
if cOnlyStats then exit;
@@ -1948,6 +2029,10 @@
r.w:= 32;
r.h:= 32;
+// If true, then mission panel cannot be hidden by releasing the mission panel key.
+// Is in effect until timer runs out, is hidden with HideMission or ShowMission is called with forceDisplay=false.
+isForceMission := forceDisplay;
+
if time = 0 then
time:= 5000;
missionTimer:= time;
@@ -1970,14 +2055,17 @@
procedure HideMission;
begin
missionTimer:= 0;
+ isForceMission:= false;
end;
-procedure SetAmmoTexts(ammoType: TAmmoType; name: ansistring; caption: ansistring; description: ansistring);
+procedure SetAmmoTexts(ammoType: TAmmoType; name: ansistring; caption: ansistring; description: ansistring; autoLabels: boolean);
var
ammoStrId: TAmmoStrId;
ammoStr: ansistring;
tmpsurf: PSDL_Surface;
begin
+ if cOnlyStats then exit;
+
ammoStrId := Ammoz[ammoType].NameId;
trluaammo[ammoStrId] := name;
@@ -1987,7 +2075,7 @@
ammoStr:= trammo[ammoStrId];
if checkFails(length(ammoStr) > 0,'No default text/translation found for ammo type #' + intToStr(ord(ammoType)) + '!',true) then exit;
-
+
tmpsurf:= TTF_RenderUTF8_Blended(Fontz[CheckCJKFont(ammoStr,fnt16)].Handle, PChar(ammoStr), cWhiteColorChannels);
if checkFails(tmpsurf <> nil,'Name-texture creation for ammo type #' + intToStr(ord(ammoType)) + ' failed!',true) then exit;
tmpsurf:= doSurfaceConversion(tmpsurf);
@@ -1997,6 +2085,7 @@
trluaammoc[ammoStrId] := caption;
trluaammod[ammoStrId] := description;
+ trluaammoe[ammoStrId] := autoLabels;
end;
procedure ShakeCamera(amount: LongInt);
@@ -2006,19 +2095,19 @@
amount:= Max(1, round(amount*zoom/2));
WorldDx:= WorldDx - amount + LongInt(random(1 + amount * 2));
WorldDy:= WorldDy - amount + LongInt(random(1 + amount * 2));
-//CursorPoint.X:= CursorPoint.X - amount + LongInt(random(1 + amount * 2));
-//CursorPoint.Y:= CursorPoint.Y - amount + LongInt(random(1 + amount * 2))
end;
procedure onFocusStateChanged;
begin
-if (not cHasFocus) and (GameState <> gsConfirm) then
- ParseCommand('quit', true);
{$IFDEF MOBILE}
+if (not cHasFocus) and (not isPaused) then
+ ParseCommand('pause', true);
// when created SDL receives an exposure event that calls UndampenAudio at full power, muting audio
exit;
{$ENDIF}
+if (not cHasFocus) and (GameState <> gsConfirm) then
+ ParseCommand('quit', true);
{$IFDEF USE_VIDEO_RECORDING}
// do not change volume during prerecording as it will affect sound in video file
@@ -2032,10 +2121,51 @@
procedure updateCursorVisibility;
begin
- if isPaused or isAFK then
- SDL_ShowCursor(1)
+ if isPaused or isAFK or (GameState = gsConfirm) then
+ begin
+{$IFNDEF USE_TOUCH_INTERFACE}
+ SDL_SetRelativeMouseMode(SDL_FALSE);
+{$ENDIF}
+ if SDL_ShowCursor(SDL_QUERY) = SDL_DISABLE then
+ begin
+ uCursor.resetPosition;
+{$IFNDEF USE_TOUCH_INTERFACE}
+ SDL_ShowCursor(SDL_ENABLE);
+{$ENDIF}
+ end;
+ end
else
- SDL_ShowCursor(ord(GameState = gsConfirm))
+ begin
+ uCursor.resetPositionDelta;
+{$IFNDEF USE_TOUCH_INTERFACE}
+ SDL_ShowCursor(SDL_DISABLE);
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+{$ENDIF}
+ end;
+end;
+
+procedure updateTouchWidgets(ammoType: TAmmoType);
+begin
+{$IFDEF USE_TOUCH_INTERFACE}
+//show the aiming buttons + animation
+if (Ammoz[ammoType].Ammo.Propz and ammoprop_NeedUpDown) <> 0 then
+ begin
+ if (not arrowUp.show) then
+ begin
+ animateWidget(@arrowUp, true, true);
+ animateWidget(@arrowDown, true, true);
+ end;
+ end
+else
+ if arrowUp.show then
+ begin
+ animateWidget(@arrowUp, true, false);
+ animateWidget(@arrowDown, true, false);
+ end;
+SetUtilityWidgetState(ammoType);
+{$ELSE}
+ammoType:= ammoType; // avoid hint
+{$ENDIF}
end;
procedure SetUtilityWidgetState(ammoType: TAmmoType);
@@ -2048,20 +2178,32 @@
if ((Ammoz[ammoType].Ammo.Propz and ammoprop_Timerable) <> 0) and (ammoType <> amDrillStrike) then
begin
utilityWidget.sprite:= sprTimerButton;
- animateWidget(@utilityWidget, true, true);
+ if (not utilityWidget.show) then
+ animateWidget(@utilityWidget, true, true);
end
else if (Ammoz[ammoType].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
begin
utilityWidget.sprite:= sprTargetButton;
- animateWidget(@utilityWidget, true, true);
+ if (not utilityWidget.show) then
+ animateWidget(@utilityWidget, true, true);
end
else if ammoType = amSwitch then
begin
utilityWidget.sprite:= sprSwitchButton;
- animateWidget(@utilityWidget, true, true);
+ if (not utilityWidget.show) then
+ animateWidget(@utilityWidget, true, true);
end
else if utilityWidget.show then
animateWidget(@utilityWidget, true, false);
+
+ if ((Ammoz[ammoType].Ammo.Propz and ammoprop_SetBounce) <> 0) then
+ begin
+ utilityWidget2.sprite:= sprBounceButton;
+ if (not utilityWidget2.show) then
+ animateWidget(@utilityWidget2, true, true);
+ end
+ else if utilityWidget2.show then
+ animateWidget(@utilityWidget2, true, false);
{$ELSE}
ammoType:= ammoType; // avoid hint
{$ENDIF}
diff -r 0135e64c6c66 -r c4fd2813b127 man/hedgewars.6
--- a/man/hedgewars.6 Wed May 16 18:22:28 2018 +0200
+++ b/man/hedgewars.6 Wed Jul 31 23:14:27 2019 +0200
@@ -42,7 +42,7 @@
.
.SH "COPYRIGHT"
.
-Copyright \(co 2004\-2015 Andrey Korotaev, Igor Ulyanov
+Copyright \(co 2004\-2018 Andrey Korotaev, Igor Ulyanov
.br
This is Free Software; this software is licensed under the GPL version 2, as published by the Free Software Foundation.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff -r 0135e64c6c66 -r c4fd2813b127 misc/OfficialChallenges/racer_#17.hwmap
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/OfficialChallenges/racer_#17.hwmap Wed Jul 31 23:14:27 2019 +0200
@@ -0,0 +1,1 @@
+AAAA4Xja43vHWMfAc4SFi4FrB5DFrsEiy8DCyriegTGGRYeBIYldlIGfkX02A/di9qIO7sWsrxhARMf/K0AWLy9bUwffXiDBEst2qPP/b3bRzQwO7FIXGO3YpUAGmC0GGrVlMbsui9JiEPcSkLgDMn7LJRZWpkwGoIT6JSDxn4HvCWPvZq7NjHWbQc64wX2FSYmB7yVj8w0+VqZ0Bp6TLAybgY5kvcFzhOk0AwMDu+gmkNMuMFYC3ceSw3bhApM7u0AXgwgjbyvDJwZ9Bj5p9tBW/nvsoQwAuyw7Aw==
diff -r 0135e64c6c66 -r c4fd2813b127 misc/dmgBackground.png
Binary file misc/dmgBackground.png has changed
diff -r 0135e64c6c66 -r c4fd2813b127 misc/hats_js_anim.xhtml
--- a/misc/hats_js_anim.xhtml Wed May 16 18:22:28 2018 +0200
+++ b/misc/hats_js_anim.xhtml Wed Jul 31 23:14:27 2019 +0200
@@ -9,11 +9,13 @@
body
{
background: url('//hg.hedgewars.org/hedgewars/raw-file/tip/share/hedgewars/Data/Themes/Nature/Sky.png') fixed no-repeat bottom left;
+ background-color: #0B203D;
+ color: #FFD902;
-moz-background-size: 200%;
background-size: 100% 100%;
font-family: sans-serif;
}
-h1 { text-shadow: 0 0 2px white; }
+h1 { text-shadow: 0 0 2px white; color: black;}
a
{
margin-top: 12px;
@@ -43,7 +45,7 @@