diff --git a/CREDITS b/CREDITS
index c436b2e42..be97421d3 100644
--- a/CREDITS
+++ b/CREDITS
@@ -95,8 +95,6 @@ template.level OD Team GPLv3+
Name Author License Source URL
------------------------------------------------------------------------------------------------------------------------
-RTShaderLib/* The Ogre 3D Team MIT http://www.ogre3d.org/licensing
-
scripts/
-> *.material OD Team GPLv3+
@@ -185,6 +183,8 @@ textures/
-> MysteryBox*.png Nobiax, Danimal CC-BY-SA 3.0 http://opengameart.org/users/danimal
-> OrcBed*.png Bart, p0ss CC-BY-SA 3.0 http://forum.freegamedev.net/viewtopic.php?p=60191#p60191
-> Orc{,Normal}.png
+-> Panels_Diffuse.png The Ogre 3D Team MIT https://github.com/OGRECave/ogre - Samples/Media/materials/textures
+-> Panels_Normal_Tangent.png The Ogre 3D Team MIT https://github.com/OGRECave/ogre - Samples/Media/materials/textures
-> PitDemon*.png
-> Podium*.png
-> Portal{,Normal}.png Yughues CC0 http://opengameart.org/users/yughues
diff --git a/gui/LoadConfirm.layout b/gui/LoadConfirm.layout
index e84f22e81..6f7004eed 100644
--- a/gui/LoadConfirm.layout
+++ b/gui/LoadConfirm.layout
@@ -7,20 +7,25 @@
+
-
+
+
+
-
+
-
+
diff --git a/gui/OD.looknfeel b/gui/OD.looknfeel
index 26f70ceef..72eee5267 100644
--- a/gui/OD.looknfeel
+++ b/gui/OD.looknfeel
@@ -973,6 +973,10 @@
+
+
@@ -1000,6 +1004,7 @@
+
diff --git a/gui/WindowApplyChanges.layout b/gui/WindowApplyChanges.layout
index 001b0adb4..37a8780df 100644
--- a/gui/WindowApplyChanges.layout
+++ b/gui/WindowApplyChanges.layout
@@ -2,27 +2,30 @@
-
+
+
-
+
-
+
-
+
-
+
diff --git a/gui/WindowQuit.layout b/gui/WindowQuit.layout
index d7a85262a..fcb0553c2 100644
--- a/gui/WindowQuit.layout
+++ b/gui/WindowQuit.layout
@@ -2,24 +2,27 @@
-
+
+
-
+
-
+
-
+
diff --git a/materials/RTShaderLib/GLSL/RTSLib_Colour.glsl b/materials/RTShaderLib/GLSL/RTSLib_Colour.glsl
deleted file mode 100644
index f0ffb3495..000000000
--- a/materials/RTShaderLib/GLSL/RTSLib_Colour.glsl
+++ /dev/null
@@ -1,16 +0,0 @@
-// This file is part of the OGRE project.
-// It is subject to the license terms in the LICENSE file found in the top-level directory
-// of this distribution and at https://www.ogre3d.org/licensing.
-// SPDX-License-Identifier: MIT
-
-#ifdef USE_LINEAR_COLOURS
-#define ENABLE_LINEAR_COLOUR(colour) colour.rgb = pow(colour.rgb, vec3_splat(2.2))
-#else
-#define ENABLE_LINEAR_COLOUR(colour)
-#endif
-
-#if defined(USE_LINEAR_COLOURS) && !defined(TARGET_CONSUMES_LINEAR)
-#define COLOUR_TRANSFER(colour) colour.rgb = pow(colour.rgb, vec3_splat(1.0/2.2))
-#else
-#define COLOUR_TRANSFER(colour)
-#endif
diff --git a/materials/RTShaderLib/GLSL/SGXLib_NormalMap.glsl b/materials/RTShaderLib/GLSL/SGXLib_NormalMap.glsl
deleted file mode 100644
index 31f03b5a8..000000000
--- a/materials/RTShaderLib/GLSL/SGXLib_NormalMap.glsl
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
------------------------------------------------------------------------------
-This source file is part of OGRE
-(Object-oriented Graphics Rendering Engine)
-For the latest info, see http://www.ogre3d.org
-
-Copyright (c) 2000-2014 Torus Knot Software Ltd
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
------------------------------------------------------------------------------
-*/
-
-//-----------------------------------------------------------------------------
-// Program Name: SGXLib_NormalMapLighting
-// Program Desc: Normal map lighting functions.
-// Program Type: Vertex/Pixel shader
-// Language: GLSL
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-void SGX_FetchNormal(in sampler2D s,
- in vec2 uv,
- out vec3 vOut)
-{
- vOut = 2.0 * texture2D(s, uv).xyz - 1.0;
-}
-
-//-----------------------------------------------------------------------------
-void SGX_CalculateTBN(in vec3 vNormal,
- in vec4 vTangent,
- out mat3 TBN)
-{
- vec3 vBinormal = cross(vNormal, vTangent.xyz) * vTangent.w;
-
- // direction: from tangent space to world
- TBN = mtxFromCols(vTangent.xyz, vBinormal, vNormal);
-}
-
-//-----------------------------------------------------------------------------
-void SGX_Generate_Parallax_Texcoord(in sampler2D normalHeightMap,
- in vec2 texCoord,
- in vec3 viewPos,
- in float heightScale,
- in mat3 TBN,
- out f32vec2 newTexCoord)
-{
- //Calculate eye direction
- f32vec3 eyeVec = mul(-viewPos, TBN);
- eyeVec = normalize(eyeVec);
-#ifndef TERRAIN_PARALLAX_MAPPING
- eyeVec.y = -eyeVec.y; //Inverse y
-#endif
-
- newTexCoord = texCoord;
-
-#ifndef POM_LAYER_COUNT
- //Simple parallax mapping
- float height = 1.0 - texture2D(normalHeightMap, newTexCoord).a;
-
- #ifndef TERRAIN_PARALLAX_MAPPING
- vec2 p = eyeVec.xy / eyeVec.z * (height * heightScale);
- #else
- vec2 p = eyeVec.xy * (height * heightScale);
- #endif
-
- newTexCoord = newTexCoord - p;
-#else
- // parallax occlusion mapping
-#ifdef POM_MAX_DISTANCE
- if (abs(viewPos.z) > POM_MAX_DISTANCE)
- return;
-#endif
-
- //Configure steep mapping layering.
- float layerDepth = 1.0 / float(POM_LAYER_COUNT);
- float currentLayerDepth = 0.0;
- vec2 parallaxShift = (eyeVec.xy) * heightScale;
- vec2 deltaTexCoords = parallaxShift / float(POM_LAYER_COUNT);
-
- float currentDepthMapValue = 1.0 - texture2D(normalHeightMap, newTexCoord).a;
-
- //Loop through layers and break early if match found.
- for (int currentLayerId = 0; currentLayerId < POM_LAYER_COUNT; currentLayerId++)
- {
- // shift texture coordinates along direction of P
- newTexCoord -= deltaTexCoords;
-
- // get depthmap value at current texture coordinates
- currentDepthMapValue = 1.0 - texture2D(normalHeightMap, newTexCoord).a;
-
- //Break if layer height matched
- if (currentLayerDepth > currentDepthMapValue)
- break;
-
- // get depth of next layer
- currentLayerDepth += layerDepth;
- }
-
- // get texture coordinates before collision (reverse operations)
- vec2 prevTexCoords = newTexCoord + deltaTexCoords;
-
- // get depth after and before collision for linear interpolation
- float afterDepth = currentDepthMapValue - currentLayerDepth;
- float beforeDepth = (1.0 - texture2D(normalHeightMap, prevTexCoords).a) - currentLayerDepth + layerDepth;
-
- // interpolation of texture coordinates
- float weight = afterDepth / (afterDepth - beforeDepth);
- newTexCoord = mix(newTexCoord, prevTexCoords, weight);
-#endif
-}
diff --git a/materials/textures/Panels_Diffuse.png b/materials/textures/Panels_Diffuse.png
new file mode 100644
index 000000000..15b242c4a
Binary files /dev/null and b/materials/textures/Panels_Diffuse.png differ
diff --git a/source/entities/Creature.cpp b/source/entities/Creature.cpp
index 6a6e029ab..a740ba4cd 100755
--- a/source/entities/Creature.cpp
+++ b/source/entities/Creature.cpp
@@ -1962,13 +1962,17 @@ void Creature::createStatsWindow()
mStatsWindow = wmgr->createWindow("OD/FrameWindow", std::string("CreatureStatsWindows_") + getName());
mStatsWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.3, 0), CEGUI::UDim(0.3, 0)));
- mStatsWindow->setSize(CEGUI::USize(CEGUI::UDim(0, 380), CEGUI::UDim(0, 400)));
+ mStatsWindow->setSize(CEGUI::USize(CEGUI::UDim(0, 380), CEGUI::UDim(0, 460)));
+ // The frame's client area already excludes the titlebar, so the text pane
+ // can use almost all of it; the scrollbar keeps the tail of the stats
+ // reachable when the text is still taller than the window.
CEGUI::Window* textWindow = wmgr->createWindow("OD/StaticText", "TextDisplay");
- textWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.05, 0), CEGUI::UDim(0.1, 0)));
- textWindow->setSize(CEGUI::USize(CEGUI::UDim(0.9, 0), CEGUI::UDim(0.85, 0)));
+ textWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.05, 0), CEGUI::UDim(0.02, 0)));
+ textWindow->setSize(CEGUI::USize(CEGUI::UDim(0.9, 0), CEGUI::UDim(0.96, 0)));
textWindow->setProperty("FrameEnabled", "False");
textWindow->setProperty("BackgroundEnabled", "False");
+ textWindow->setProperty("VertScrollbar", "True");
// We want to close the window when the cross is clicked
mStatsWindow->subscribeEvent(CEGUI::FrameWindow::EventCloseClicked,
diff --git a/source/entities/Tile.cpp b/source/entities/Tile.cpp
index b7e7dc3f0..b931f214e 100755
--- a/source/entities/Tile.cpp
+++ b/source/entities/Tile.cpp
@@ -2323,11 +2323,15 @@ void Tile::createStatsWindow()
mStatsWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.3, 0), CEGUI::UDim(0.3, 0)));
mStatsWindow->setSize(CEGUI::USize(CEGUI::UDim(0, 380), CEGUI::UDim(0, 400)));
+ // The frame's client area already excludes the titlebar, so the text pane
+ // can use almost all of it; the scrollbar keeps the tail of the stats
+ // reachable when the text is still taller than the window.
CEGUI::Window* textWindow = wmgr->createWindow("OD/StaticText", "TextDisplay");
- textWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.05, 0), CEGUI::UDim(0.1, 0)));
- textWindow->setSize(CEGUI::USize(CEGUI::UDim(0.9, 0), CEGUI::UDim(0.85, 0)));
+ textWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.05, 0), CEGUI::UDim(0.02, 0)));
+ textWindow->setSize(CEGUI::USize(CEGUI::UDim(0.9, 0), CEGUI::UDim(0.96, 0)));
textWindow->setProperty("FrameEnabled", "False");
textWindow->setProperty("BackgroundEnabled", "False");
+ textWindow->setProperty("VertScrollbar", "True");
// We want to close the window when the cross is clicked
mStatsWindow->subscribeEvent(CEGUI::FrameWindow::EventCloseClicked,
diff --git a/source/modes/InputManager.cpp b/source/modes/InputManager.cpp
index 785700088..43535df8d 100755
--- a/source/modes/InputManager.cpp
+++ b/source/modes/InputManager.cpp
@@ -91,14 +91,13 @@ InputManager::InputManager(Ogre::RenderWindow* renderWindow):
paramList.insert(std::make_pair(std::string("w32_keyboard"), std::string(keyboardGrab ? "DISCL_EXCLUSIVE" : "DISCL_NONEXCLUSIVE")));
#elif defined OIS_LINUX_PLATFORM
paramList.insert(std::make_pair(std::string("x11_mouse_grab"), std::string(mouseGrab ? "true" : "false")));
- // When grabbing, OIS tracks the pointer by accumulating relative motion and warps
- // the real pointer back to the window centre near the edges. Leaving the system
- // cursor visible then shows it drifting away from the one CEGUI draws, so hide it.
- /* paramList.insert(std::make_pair(std::string("x11_mouse_hide"), std::string(mouseGrab ? "true" : "false")));
- * FIXME: While I was debugging cursor issues I found that hiding actually hides the cursor,
- * not showing the system one. It works somehow. Did not have much time to think about
- * this yet. Need to return to it later.
- */
+ // Always hide the X11 cursor over the window: CEGUI draws its own cursor,
+ // so a visible system cursor would show as a second pointer — and when
+ // grabbing, OIS additionally warps the real pointer back to the window
+ // centre, which makes the system cursor visibly drift away from CEGUI's.
+ // (OIS implements this by setting a blank cursor on the window, so if the
+ // CEGUI cursor ever fails to render there is no pointer at all — that is
+ // the trade-off being made here.)
paramList.insert(std::make_pair(std::string("x11_mouse_hide"), std::string("true")));
paramList.insert(std::make_pair(std::string("x11_keyboard_grab"), std::string(keyboardGrab ? "true" : "false")));
paramList.insert(std::make_pair(std::string("XAutoRepeatOn"), std::string("true")));
diff --git a/source/modes/SettingsWindow.cpp b/source/modes/SettingsWindow.cpp
index 7827d7f44..610435076 100755
--- a/source/modes/SettingsWindow.cpp
+++ b/source/modes/SettingsWindow.cpp
@@ -115,6 +115,7 @@ SettingsWindow::SettingsWindow(CEGUI::Window* rootWindow):
mSettingsWindow(nullptr),
mApplyWindow(nullptr),
mRootWindow(rootWindow),
+ mUiScale(1.0f),
dynamicShadowsChanged(false)
{
if (rootWindow == nullptr)
@@ -214,22 +215,58 @@ SettingsWindow::SettingsWindow(CEGUI::Window* rootWindow):
initConfig();
// The SettingsWindow layout was designed for 800x600. Scale and center it
- // on higher resolutions so it stays usable.
- CEGUI::Sizef displaySize = CEGUI::System::getSingleton().getRenderer()->getDisplaySize();
+ // on higher resolutions so it stays usable, and re-fit it whenever the
+ // display size changes (this is the window resolutions are applied from).
+ mSettingsWindowOriginalArea = mSettingsWindow->getProperty("Area");
+ applyUiScale();
+ addEventConnection(
+ CEGUI::System::getSingleton().subscribeEvent(
+ CEGUI::System::EventDisplaySizeChanged,
+ CEGUI::Event::Subscriber(&SettingsWindow::onDisplaySizeChanged, this)
+ )
+ );
+}
+
+void SettingsWindow::applyUiScale()
+{
float scale = computeUiScale();
- if (scale > 1.0f)
+
+ // Every widget below the two top-level windows only ever gets its pixel
+ // offsets multiplied, so moving from the currently applied scale to the
+ // new one is a plain ratio. This also covers the video-settings widgets
+ // initConfig() recreates, as long as they are brought to mUiScale when
+ // they are created.
+ float ratio = scale / mUiScale;
+ if (ratio != 1.0f)
{
- centerAndScaleWindow(mSettingsWindow, scale, displaySize);
- scaleWindowTree(mSettingsWindow, scale);
+ scaleWindowTree(mSettingsWindow, ratio);
CEGUI::Window* tabControl = mSettingsWindow->getChild("MainTabControl");
if (tabControl)
- scaleDimProperty(tabControl, "TabHeight", scale);
+ scaleDimProperty(tabControl, "TabHeight", ratio);
// The apply-changes popup also uses pixel offsets relative to the centre.
- scaleWindowArea(mApplyWindow, scale);
- scaleWindowTree(mApplyWindow, scale);
+ scaleWindowArea(mApplyWindow, ratio);
+ scaleWindowTree(mApplyWindow, ratio);
}
+
+ // The top-level window is re-centered from its designed area rather than
+ // by ratio, because centering flattens it to pixel offsets for the current
+ // display size.
+ mSettingsWindow->setProperty("Area", mSettingsWindowOriginalArea);
+ if (scale > 1.0f)
+ {
+ CEGUI::Sizef displaySize = CEGUI::System::getSingleton().getRenderer()->getDisplaySize();
+ centerAndScaleWindow(mSettingsWindow, scale, displaySize);
+ }
+
+ mUiScale = scale;
+}
+
+bool SettingsWindow::onDisplaySizeChanged(const CEGUI::EventArgs&)
+{
+ applyUiScale();
+ return true;
}
SettingsWindow::~SettingsWindow()
@@ -457,6 +494,15 @@ void SettingsWindow::initConfig()
videoCb->setReadOnly(true);
videoCb->setSortingEnabled(true);
+ // These are laid out in design-space pixels; bring them to the UI
+ // scale the rest of the window is currently at (applyUiScale() then
+ // keeps them in sync through its ratio scaling).
+ if (mUiScale != 1.0f)
+ {
+ scaleWindowArea(videoCbText, mUiScale);
+ scaleWindowArea(videoCb, mUiScale);
+ }
+
// Register the widgets for potential later deletion.
mCustomVideoTexts.push_back(videoCbText);
mCustomVideoComboBoxes.push_back(videoCb);
diff --git a/source/modes/SettingsWindow.h b/source/modes/SettingsWindow.h
index cbb51fa06..4fe4fb4e5 100755
--- a/source/modes/SettingsWindow.h
+++ b/source/modes/SettingsWindow.h
@@ -75,9 +75,26 @@ class SettingsWindow
std::vector mCustomVideoComboBoxes;
std::vector mCustomVideoTexts;
+ //! \brief The UI scale currently applied to the settings/apply windows
+ //! (1.0 means the layout is at its designed 800x600 geometry).
+ float mUiScale;
+
+ //! \brief The settings window "Area" as designed in the layout, kept so the
+ //! window can be re-centered from scratch when the display size changes.
+ CEGUI::String mSettingsWindowOriginalArea;
+
//! \brief Set the different widget values according to current config.
void initConfig();
+ //! \brief Scale and center the settings/apply windows for the current
+ //! display size. Safe to call again whenever the display size changes.
+ void applyUiScale();
+
+ //! \brief Called when the display size changes (e.g. after applying a new
+ //! resolution) so the windows are re-fitted instead of staying pinned to
+ //! the pixel geometry of the old resolution.
+ bool onDisplaySizeChanged(const CEGUI::EventArgs&);
+
//! \brief Save the config, potentially stopping the application if it needs to.
void saveConfig();
diff --git a/source/network/ODServer.cpp b/source/network/ODServer.cpp
index 1b50117dc..a4da31a5d 100755
--- a/source/network/ODServer.cpp
+++ b/source/network/ODServer.cpp
@@ -126,9 +126,15 @@ bool ODServer::startServer(const std::string& creator, const std::string& levelF
return false;
}
- // Set up the socket to listen on the specified port
+ // Set up the socket to listen on the specified port. Local games (single
+ // player, editor, loaded saves) reach the server through getNetworkPort(),
+ // which reports the port actually bound, so they may fall back to an
+ // ephemeral port when the configured one is busy. A multiplayer host must
+ // not: remote clients dial the advertised port, so failing loudly here
+ // beats a connection that never establishes.
int32_t port = getNetworkPort();
- if (!createServer(port))
+ bool allowPortFallback = (mode != ServerMode::ModeGameMultiPlayer);
+ if (!createServer(port, allowPortFallback))
{
mServerMode = ServerMode::ModeNone;
mServerState = ServerState::StateNone;
diff --git a/source/network/ODSocketServer.cpp b/source/network/ODSocketServer.cpp
index e2d17f654..10037dc1b 100755
--- a/source/network/ODSocketServer.cpp
+++ b/source/network/ODSocketServer.cpp
@@ -37,7 +37,7 @@ ODSocketServer::~ODSocketServer()
stopServer();
}
-bool ODSocketServer::createServer(int listeningPort)
+bool ODSocketServer::createServer(int listeningPort, bool allowPortFallback)
{
mIsConnected = false;
mActualPort = 0;
@@ -46,6 +46,12 @@ bool ODSocketServer::createServer(int listeningPort)
sf::Socket::Status status = mSockListener.listen(listeningPort);
if (status != sf::Socket::Done)
{
+ if (!allowPortFallback)
+ {
+ OD_LOG_ERR("Could not listen to server port " + Helper::toString(listeningPort)
+ + " status=" + Helper::toString(status));
+ return false;
+ }
OD_LOG_WRN("Could not listen to server port " + Helper::toString(listeningPort)
+ " status=" + Helper::toString(status) + ", trying ephemeral port.");
status = mSockListener.listen(0);
diff --git a/source/network/ODSocketServer.h b/source/network/ODSocketServer.h
index 9f3d713e7..db4bb5e84 100755
--- a/source/network/ODSocketServer.h
+++ b/source/network/ODSocketServer.h
@@ -39,7 +39,13 @@ class ODSocketServer
{ return mActualPort; }
// Data Transimission
- virtual bool createServer(int listeningPort);
+ //! \brief Starts listening on listeningPort. If the port is busy and
+ //! allowPortFallback is true, falls back to an ephemeral port (callers
+ //! can retrieve it with getActualPort()). Hosting a multiplayer game
+ //! must NOT allow the fallback: remote clients dial the advertised
+ //! port, and silently binding another one would make them fail to
+ //! connect with no error on the host side.
+ virtual bool createServer(int listeningPort, bool allowPortFallback);
virtual void stopServer();
protected:
diff --git a/source/render/ODFrameListener.cpp b/source/render/ODFrameListener.cpp
index 984446b69..42fe96574 100755
--- a/source/render/ODFrameListener.cpp
+++ b/source/render/ODFrameListener.cpp
@@ -294,6 +294,10 @@ bool ODFrameListener::frameStarted(const Ogre::FrameEvent& evt)
return true;
}
+// Since Ogre 14 (OgreRenderQueueListener.h), the second parameter carries the
+// name of the camera being rendered; SceneManager passes
+// mCameraInProgress->getName(). The comparison below decides whether CEGUI is
+// drawn for this render, so the parameter's meaning matters.
void ODFrameListener::renderQueueStarted(Ogre::uint8 queueGroupId, const Ogre::String& cameraName,
bool&)
{